termbox-util/termbox_inputfield.go

317 lines
8.6 KiB
Go
Raw Permalink Normal View History

2015-10-21 16:14:57 +00:00
package termboxUtil
import (
"strconv"
"github.com/nsf/termbox-go"
)
2015-10-21 16:14:57 +00:00
// InputField is a field for inputting text
type InputField struct {
2016-02-09 16:21:57 +00:00
id string
2019-03-25 15:10:25 +00:00
title string
value string
x, y, width, height int
cursor int
fg, bg termbox.Attribute
2019-03-25 15:10:25 +00:00
activeFg, activeBg termbox.Attribute
cursorFg, cursorBg termbox.Attribute
bordered bool
wrap bool
multiline bool
2016-02-09 16:21:57 +00:00
tabSkip bool
2019-03-25 15:10:25 +00:00
active bool
2020-02-20 14:07:10 +00:00
justified bool
filter func(*InputField, string, string) string
}
2015-10-21 16:14:57 +00:00
// CreateInputField creates an input field at x, y that is w by h
func CreateInputField(x, y, w, h int, fg, bg termbox.Attribute) *InputField {
2019-03-25 15:10:25 +00:00
c := InputField{x: x, y: y, width: w, height: h,
fg: fg, bg: bg, cursorFg: bg, cursorBg: fg, activeFg: fg, activeBg: bg,
}
c.filter = func(fld *InputField, o, n string) string { return n }
2019-03-25 15:10:25 +00:00
return &c
}
2019-03-25 15:10:25 +00:00
func (c *InputField) SetTitle(title string) { c.title = title }
func (c *InputField) SetActiveFgColor(fg termbox.Attribute) { c.activeFg = fg }
func (c *InputField) SetActiveBgColor(bg termbox.Attribute) { c.activeBg = bg }
func (c *InputField) SetActive(a bool) { c.active = a }
func (c *InputField) IsActive() bool { return c.active }
2016-02-09 16:21:57 +00:00
// GetID returns this control's ID
2019-03-25 15:10:25 +00:00
func (c *InputField) GetID() string { return c.id }
2016-02-09 16:21:57 +00:00
// SetID sets this control's ID
2019-03-25 15:10:25 +00:00
func (c *InputField) SetID(newID string) {
c.id = newID
2016-02-09 16:21:57 +00:00
}
2015-10-21 16:14:57 +00:00
// GetValue gets the current text that is in the InputField
2019-03-25 15:10:25 +00:00
func (c *InputField) GetValue() string { return c.value }
2015-10-21 16:14:57 +00:00
// SetValue sets the current text in the InputField to s
2019-03-25 15:10:25 +00:00
func (c *InputField) SetValue(s string) {
c.value = s
}
2015-10-21 16:14:57 +00:00
// GetX returns the x position of the input field
2019-03-25 15:10:25 +00:00
func (c *InputField) GetX() int { return c.x }
2015-10-21 16:14:57 +00:00
// SetX sets the x position of the input field
func (c *InputField) SetX(x int) { c.x = x }
2015-10-21 16:14:57 +00:00
// GetY returns the y position of the input field
2019-03-25 15:10:25 +00:00
func (c *InputField) GetY() int { return c.y }
2015-10-21 16:14:57 +00:00
// SetY sets the y position of the input field
func (c *InputField) SetY(y int) { c.y = y }
2015-10-21 16:14:57 +00:00
// GetWidth returns the current width of the input field
2019-03-25 15:10:25 +00:00
func (c *InputField) GetWidth() int { return c.width }
2015-10-21 16:14:57 +00:00
// SetWidth sets the current width of the input field
func (c *InputField) SetWidth(w int) { c.width = w }
2015-10-21 16:14:57 +00:00
// GetHeight returns the current height of the input field
2019-03-25 15:10:25 +00:00
func (c *InputField) GetHeight() int { return c.height }
2015-10-21 16:14:57 +00:00
// SetHeight sets the current height of the input field
func (c *InputField) SetHeight(h int) { c.height = h }
2016-02-09 16:21:57 +00:00
// GetFgColor returns the foreground color
2019-03-25 15:10:25 +00:00
func (c *InputField) GetFgColor() termbox.Attribute { return c.fg }
2016-02-09 16:21:57 +00:00
// SetFgColor sets the foreground color
func (c *InputField) SetFgColor(fg termbox.Attribute) { c.fg = fg }
2016-02-09 16:21:57 +00:00
// GetBgColor returns the background color
2019-03-25 15:10:25 +00:00
func (c *InputField) GetBgColor() termbox.Attribute { return c.bg }
2016-02-09 16:21:57 +00:00
// SetBgColor sets the current background color
func (c *InputField) SetBgColor(bg termbox.Attribute) { c.bg = bg }
func (c *InputField) SetCursorFg(fg termbox.Attribute) { c.cursorFg = fg }
2016-02-09 16:21:57 +00:00
2019-03-25 15:10:25 +00:00
func (c *InputField) GetCursorFg() termbox.Attribute { return c.cursorFg }
2019-03-09 20:42:18 +00:00
func (c *InputField) SetCursorBg(bg termbox.Attribute) { c.cursorBg = bg }
2019-03-25 15:10:25 +00:00
func (c *InputField) GetCursorBg() termbox.Attribute { return c.cursorBg }
2019-03-09 20:42:18 +00:00
2015-10-21 16:14:57 +00:00
// IsBordered returns true or false if this input field has a border
2019-03-25 15:10:25 +00:00
func (c *InputField) IsBordered() bool { return c.bordered }
2015-10-21 16:14:57 +00:00
// SetBordered sets whether we render a border around the input field
2019-03-25 15:10:25 +00:00
func (c *InputField) SetBordered(b bool) {
c.bordered = b
}
2016-02-09 16:21:57 +00:00
// IsTabSkipped returns whether this modal has it's tabskip flag set
2019-03-25 15:10:25 +00:00
func (c *InputField) IsTabSkipped() bool {
return c.tabSkip
2016-02-09 16:21:57 +00:00
}
// SetTabSkip sets the tabskip flag for this control
2019-03-25 15:10:25 +00:00
func (c *InputField) SetTabSkip(b bool) {
c.tabSkip = b
2016-02-09 16:21:57 +00:00
}
// DoesWrap returns true or false if this input field wraps text
2019-03-25 15:10:25 +00:00
func (c *InputField) DoesWrap() bool { return c.wrap }
// SetWrap sets whether we wrap the text at width.
2019-03-25 15:10:25 +00:00
func (c *InputField) SetWrap(b bool) {
c.wrap = b
}
// IsMultiline returns true or false if this field can have multiple lines
2019-03-25 15:10:25 +00:00
func (c *InputField) IsMultiline() bool { return c.multiline }
// SetMultiline sets whether the field can have multiple lines
2019-03-25 15:10:25 +00:00
func (c *InputField) SetMultiline(b bool) {
c.multiline = b
}
2020-02-20 14:07:10 +00:00
func (c *InputField) SetJustified(b bool) {
c.justified = b
}
2016-02-09 16:21:57 +00:00
// HandleEvent accepts the termbox event and returns whether it was consumed
2019-03-25 15:10:25 +00:00
func (c *InputField) HandleEvent(event termbox.Event) bool {
prev := c.value
if event.Key == termbox.KeyTab { // There is no tabbing in here
return false
}
if event.Key == termbox.KeyBackspace || event.Key == termbox.KeyBackspace2 {
2019-03-25 15:10:25 +00:00
if c.cursor+len(c.value) > 0 {
crs := len(c.value)
if c.cursor < 0 {
crs = c.cursor + len(c.value)
2017-08-30 13:51:39 +00:00
}
2019-03-25 15:10:25 +00:00
c.value = c.value[:crs-1] + c.value[crs:]
//c.value = c.value[:len(c.value)-1]
}
} else if event.Key == termbox.KeyArrowLeft {
2019-03-25 15:10:25 +00:00
if c.cursor+len(c.value) > 0 {
c.cursor--
}
} else if event.Key == termbox.KeyArrowRight {
2019-03-25 15:10:25 +00:00
if c.cursor < 0 {
c.cursor++
}
} else if event.Key == termbox.KeyCtrlU {
// Ctrl+U Clears the Input (before the cursor)
2019-03-25 15:10:25 +00:00
c.value = c.value[c.cursor+len(c.value):]
} else {
// Get the rune to add to our value. Space and Tab are special cases where
// we can't use the event's rune directly
var ch string
switch event.Key {
case termbox.KeySpace:
ch = " "
case termbox.KeyTab:
ch = "\t"
case termbox.KeyEnter:
2019-03-25 15:10:25 +00:00
if c.multiline {
ch = "\n"
}
default:
if KeyIsAlphaNumeric(event) || KeyIsSymbol(event) {
ch = string(event.Ch)
}
}
2015-10-21 16:00:06 +00:00
// TODO: Handle newlines
2019-03-25 15:10:25 +00:00
if c.cursor+len(c.value) == 0 {
c.value = string(ch) + c.value
} else if c.cursor == 0 {
c.value = c.value + string(ch)
} else {
2019-03-25 15:10:25 +00:00
strPt1 := c.value[:(len(c.value) + c.cursor)]
strPt2 := c.value[(len(c.value) + c.cursor):]
c.value = strPt1 + string(ch) + strPt2
}
}
c.value = c.filter(c, prev, c.value)
return true
}
2015-10-21 16:14:57 +00:00
// Draw outputs the input field on the screen
2019-03-25 15:10:25 +00:00
func (c *InputField) Draw() {
maxWidth := c.width
maxHeight := c.height
x, y := c.x, c.y
startX := c.x
startY := c.y
useFg, useBg := c.fg, c.bg
if c.active {
useFg, useBg = c.activeFg, c.activeBg
}
if c.bordered {
DrawBorder(c.x, c.y, c.x+c.width, c.y+c.height, useFg, useBg)
maxWidth--
maxHeight--
x++
y++
startX++
startY++
}
2015-10-21 16:14:57 +00:00
var strPt1, strPt2 string
var cursorRune rune
2019-03-25 15:10:25 +00:00
if len(c.value) > 0 {
if c.cursor+len(c.value) == 0 {
2015-10-21 16:14:57 +00:00
strPt1 = ""
2019-03-25 15:10:25 +00:00
strPt2 = c.value[1:]
cursorRune = rune(c.value[0])
} else if c.cursor == 0 {
strPt1 = c.value
2015-10-21 16:14:57 +00:00
strPt2 = ""
cursorRune = ' '
} else {
2019-03-25 15:10:25 +00:00
strPt1 = c.value[:(len(c.value) + c.cursor)]
strPt2 = c.value[(len(c.value)+c.cursor)+1:]
cursorRune = rune(c.value[len(c.value)+c.cursor])
}
} else {
2015-10-21 16:14:57 +00:00
strPt1, strPt2, cursorRune = "", "", ' '
}
2019-03-25 15:10:25 +00:00
if c.title != "" {
if c.active {
DrawStringAtPoint(c.title, x, y, c.activeFg, c.activeBg)
} else {
DrawStringAtPoint(c.title, x, y, useFg, useBg)
}
}
if c.wrap {
// Split the text into maxWidth chunks
for len(strPt1) > maxWidth {
breakAt := maxWidth
2019-03-25 15:10:25 +00:00
DrawStringAtPoint(strPt1[:breakAt], x, y, useFg, useBg)
x = startX
y++
strPt1 = strPt1[breakAt:]
}
2019-03-25 15:10:25 +00:00
x, y = DrawStringAtPoint(strPt1, x, y, useFg, useBg)
if x >= maxWidth {
y++
x = startX
}
2019-03-25 15:10:25 +00:00
termbox.SetCell(x, y, cursorRune, c.cursorFg, c.cursorBg)
x++
if len(strPt2) > 0 {
lenLeft := maxWidth - len(strPt1) - 1
if lenLeft > 0 && len(strPt2) > lenLeft {
2019-03-25 15:10:25 +00:00
DrawStringAtPoint(strPt2[:lenLeft], x+1, y, useFg, useBg)
strPt2 = strPt2[lenLeft:]
}
for len(strPt2) > maxWidth {
breakAt := maxWidth
2019-03-25 15:10:25 +00:00
DrawStringAtPoint(strPt2[:breakAt], x, y, useFg, useBg)
x = startX
y++
strPt2 = strPt2[breakAt:]
}
2019-03-25 15:10:25 +00:00
x, y = DrawStringAtPoint(strPt2, x, y, useFg, useBg)
}
} else {
for len(strPt1)+len(strPt2)+1 > maxWidth {
if len(strPt1) >= len(strPt2) {
if len(strPt1) == 0 {
break
}
strPt1 = strPt1[1:]
} else {
strPt2 = strPt2[:len(strPt2)-1]
}
}
2020-02-20 14:07:10 +00:00
stX := c.x + len(c.title)
if c.justified {
stX = c.x + c.width - len(strPt1) - len(strPt2) - 1
}
x, y = DrawStringAtPoint(strPt1, stX, c.y, useFg, useBg)
2019-03-25 15:10:25 +00:00
if c.active {
termbox.SetCell(x, y, cursorRune, c.cursorFg, c.cursorBg)
} else {
termbox.SetCell(x, y, cursorRune, useFg, useBg)
}
DrawStringAtPoint(strPt2, x+1, y, useFg, useBg)
}
}
func (c *InputField) SetTextFilter(filter func(*InputField, string, string) string) {
c.filter = filter
}
// Some handy text filters
func (c *InputField) InputFieldNumberFilter(fld *InputField, o, n string) string {
_, err := strconv.Atoi(n)
if err != nil {
return o
}
return n
}