You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
cointop/pkg/termbox/_demos/editbox.go

301 lines
6.9 KiB
Go

package main
import (
"github.com/mattn/go-runewidth"
"github.com/nsf/termbox-go"
"unicode/utf8"
)
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
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, '→',
coldef, 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, '←', coldef, 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
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()
}
func main() {
err := termbox.Init()
if err != nil {
panic(err)
}
defer termbox.Close()
termbox.SetInputMode(termbox.InputEsc)
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()
}
}