Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Switch screens #232

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
326 changes: 326 additions & 0 deletions _demos/screens.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,326 @@
package main

import (
"unicode/utf8"

"github.com/mattn/go-runewidth"
"github.com/nsf/termbox-go"
)

func tbprint(x, y int, fg, bg termbox.Attribute, msg string) {
for _, c := range msg {
termbox.SetCell(x, y, c, fg, bg)
x += runewidth.RuneWidth(c)
}
}

func fill(x, y, w, h int, cell termbox.Cell) {
for ly := 0; ly < h; ly++ {
for lx := 0; lx < w; lx++ {
termbox.SetCell(x+lx, y+ly, cell.Ch, cell.Fg, cell.Bg)
}
}
}

func rune_advance_len(r rune, pos int) int {
if r == '\t' {
return tabstop_length - pos%tabstop_length
}
return runewidth.RuneWidth(r)
}

func voffset_coffset(text []byte, boffset int) (voffset, coffset int) {
text = text[:boffset]
for len(text) > 0 {
r, size := utf8.DecodeRune(text)
text = text[size:]
coffset += 1
voffset += rune_advance_len(r, voffset)
}
return
}

func byte_slice_grow(s []byte, desired_cap int) []byte {
if cap(s) < desired_cap {
ns := make([]byte, len(s), desired_cap)
copy(ns, s)
return ns
}
return s
}

func byte_slice_remove(text []byte, from, to int) []byte {
size := to - from
copy(text[from:], text[to:])
text = text[:len(text)-size]
return text
}

func byte_slice_insert(text []byte, offset int, what []byte) []byte {
n := len(text) + len(what)
text = byte_slice_grow(text, n)
text = text[:n]
copy(text[offset+len(what):], text[offset:])
copy(text[offset:], what)
return text
}

const preferred_horizontal_threshold = 5
const tabstop_length = 8

type EditBox struct {
text []byte
line_voffset int
cursor_boffset int // cursor offset in bytes
cursor_voffset int // visual cursor offset in termbox cells
cursor_coffset int // cursor offset in unicode code points
}

// Draws the EditBox in the given location, 'h' is not used at the moment
func (eb *EditBox) Draw(x, y, w, h int) {
eb.AdjustVOffset(w)

const coldef = termbox.ColorDefault
const colred = termbox.ColorRed

fill(x, y, w, h, termbox.Cell{Ch: ' '})

t := eb.text
lx := 0
tabstop := 0
for {
rx := lx - eb.line_voffset
if len(t) == 0 {
break
}

if lx == tabstop {
tabstop += tabstop_length
}

if rx >= w {
termbox.SetCell(x+w-1, y, arrowRight,
colred, coldef)
break
}

r, size := utf8.DecodeRune(t)
if r == '\t' {
for ; lx < tabstop; lx++ {
rx = lx - eb.line_voffset
if rx >= w {
goto next
}

if rx >= 0 {
termbox.SetCell(x+rx, y, ' ', coldef, coldef)
}
}
} else {
if rx >= 0 {
termbox.SetCell(x+rx, y, r, coldef, coldef)
}
lx += runewidth.RuneWidth(r)
}
next:
t = t[size:]
}

if eb.line_voffset != 0 {
termbox.SetCell(x, y, arrowLeft, colred, coldef)
}
}

// Adjusts line visual offset to a proper value depending on width
func (eb *EditBox) AdjustVOffset(width int) {
ht := preferred_horizontal_threshold
max_h_threshold := (width - 1) / 2
if ht > max_h_threshold {
ht = max_h_threshold
}

threshold := width - 1
if eb.line_voffset != 0 {
threshold = width - ht
}
if eb.cursor_voffset-eb.line_voffset >= threshold {
eb.line_voffset = eb.cursor_voffset + (ht - width + 1)
}

if eb.line_voffset != 0 && eb.cursor_voffset-eb.line_voffset < ht {
eb.line_voffset = eb.cursor_voffset - ht
if eb.line_voffset < 0 {
eb.line_voffset = 0
}
}
}

func (eb *EditBox) MoveCursorTo(boffset int) {
eb.cursor_boffset = boffset
eb.cursor_voffset, eb.cursor_coffset = voffset_coffset(eb.text, boffset)
}

func (eb *EditBox) RuneUnderCursor() (rune, int) {
return utf8.DecodeRune(eb.text[eb.cursor_boffset:])
}

func (eb *EditBox) RuneBeforeCursor() (rune, int) {
return utf8.DecodeLastRune(eb.text[:eb.cursor_boffset])
}

func (eb *EditBox) MoveCursorOneRuneBackward() {
if eb.cursor_boffset == 0 {
return
}
_, size := eb.RuneBeforeCursor()
eb.MoveCursorTo(eb.cursor_boffset - size)
}

func (eb *EditBox) MoveCursorOneRuneForward() {
if eb.cursor_boffset == len(eb.text) {
return
}
_, size := eb.RuneUnderCursor()
eb.MoveCursorTo(eb.cursor_boffset + size)
}

func (eb *EditBox) MoveCursorToBeginningOfTheLine() {
eb.MoveCursorTo(0)
}

func (eb *EditBox) MoveCursorToEndOfTheLine() {
eb.MoveCursorTo(len(eb.text))
}

func (eb *EditBox) DeleteRuneBackward() {
if eb.cursor_boffset == 0 {
return
}

eb.MoveCursorOneRuneBackward()
_, size := eb.RuneUnderCursor()
eb.text = byte_slice_remove(eb.text, eb.cursor_boffset, eb.cursor_boffset+size)
}

func (eb *EditBox) DeleteRuneForward() {
if eb.cursor_boffset == len(eb.text) {
return
}
_, size := eb.RuneUnderCursor()
eb.text = byte_slice_remove(eb.text, eb.cursor_boffset, eb.cursor_boffset+size)
}

func (eb *EditBox) DeleteTheRestOfTheLine() {
eb.text = eb.text[:eb.cursor_boffset]
}

func (eb *EditBox) InsertRune(r rune) {
var buf [utf8.UTFMax]byte
n := utf8.EncodeRune(buf[:], r)
eb.text = byte_slice_insert(eb.text, eb.cursor_boffset, buf[:n])
eb.MoveCursorOneRuneForward()
}

// Please, keep in mind that cursor depends on the value of line_voffset, which
// is being set on Draw() call, so.. call this method after Draw() one.
func (eb *EditBox) CursorX() int {
return eb.cursor_voffset - eb.line_voffset
}

var edit_box EditBox

const edit_box_width = 30

func redraw_all() {
const coldef = termbox.ColorDefault
termbox.Clear(coldef, coldef)
w, h := termbox.Size()

midy := h / 2
midx := (w - edit_box_width) / 2

// unicode box drawing chars around the edit box
if runewidth.EastAsianWidth {
termbox.SetCell(midx-1, midy, '|', coldef, coldef)
termbox.SetCell(midx+edit_box_width, midy, '|', coldef, coldef)
termbox.SetCell(midx-1, midy-1, '+', coldef, coldef)
termbox.SetCell(midx-1, midy+1, '+', coldef, coldef)
termbox.SetCell(midx+edit_box_width, midy-1, '+', coldef, coldef)
termbox.SetCell(midx+edit_box_width, midy+1, '+', coldef, coldef)
fill(midx, midy-1, edit_box_width, 1, termbox.Cell{Ch: '-'})
fill(midx, midy+1, edit_box_width, 1, termbox.Cell{Ch: '-'})
} else {
termbox.SetCell(midx-1, midy, '│', coldef, coldef)
termbox.SetCell(midx+edit_box_width, midy, '│', coldef, coldef)
termbox.SetCell(midx-1, midy-1, '┌', coldef, coldef)
termbox.SetCell(midx-1, midy+1, '└', coldef, coldef)
termbox.SetCell(midx+edit_box_width, midy-1, '┐', coldef, coldef)
termbox.SetCell(midx+edit_box_width, midy+1, '┘', coldef, coldef)
fill(midx, midy-1, edit_box_width, 1, termbox.Cell{Ch: '─'})
fill(midx, midy+1, edit_box_width, 1, termbox.Cell{Ch: '─'})
}

edit_box.Draw(midx, midy, edit_box_width, 1)
termbox.SetCursor(midx+edit_box.CursorX(), midy)

tbprint(midx+6, midy+3, coldef, coldef, "Press ESC to quit")
termbox.Flush()
}

var arrowLeft = '←'
var arrowRight = '→'

func init() {
if runewidth.EastAsianWidth {
arrowLeft = '<'
arrowRight = '>'
}
}

func main() {
err := termbox.Init()
if err != nil {
panic(err)
}
defer termbox.Close()
termbox.SetInputMode(termbox.InputEsc)

termbox.SelectScreen(false)

redraw_all()
mainloop:
for {
switch ev := termbox.PollEvent(); ev.Type {
case termbox.EventKey:
switch ev.Key {
case termbox.KeyEsc:
break mainloop
case termbox.KeyArrowLeft, termbox.KeyCtrlB:
edit_box.MoveCursorOneRuneBackward()
case termbox.KeyArrowRight, termbox.KeyCtrlF:
edit_box.MoveCursorOneRuneForward()
case termbox.KeyBackspace, termbox.KeyBackspace2:
edit_box.DeleteRuneBackward()
case termbox.KeyDelete, termbox.KeyCtrlD:
edit_box.DeleteRuneForward()
case termbox.KeyTab:
edit_box.InsertRune('\t')
case termbox.KeySpace:
edit_box.InsertRune(' ')
case termbox.KeyCtrlK:
edit_box.DeleteTheRestOfTheLine()
case termbox.KeyHome, termbox.KeyCtrlA:
edit_box.MoveCursorToBeginningOfTheLine()
case termbox.KeyEnd, termbox.KeyCtrlE:
edit_box.MoveCursorToEndOfTheLine()
default:
if ev.Ch != 0 {
edit_box.InsertRune(ev.Ch)
}
}
case termbox.EventError:
panic(ev.Err)
}
redraw_all()
}
}
15 changes: 14 additions & 1 deletion api.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,9 @@ func Close() {
quit <- 1
out.WriteString(funcs[t_show_cursor])
out.WriteString(funcs[t_sgr0])
out.WriteString(funcs[t_clear_screen])
if isAltScreen {
out.WriteString(funcs[t_clear_screen])
}
out.WriteString(funcs[t_exit_ca])
out.WriteString(funcs[t_exit_keypad])
out.WriteString(funcs[t_exit_mouse])
Expand All @@ -167,6 +169,17 @@ func Close() {
IsInit = false
}

var isAltScreen bool = true

func SelectScreen(alt bool) {
isAltScreen = alt
if alt {
out.WriteString(funcs[t_enter_ca])
} else {
out.WriteString(funcs[t_exit_ca])
}
}

// Synchronizes the internal back buffer with the terminal.
func Flush() error {
// invalidate cursor position
Expand Down