2015-10-21 16:14:57 +00:00
|
|
|
package termboxUtil
|
2015-05-18 14:39:31 +00:00
|
|
|
|
2020-02-13 16:20:49 +00:00
|
|
|
import (
|
|
|
|
"strconv"
|
|
|
|
|
|
|
|
"github.com/nsf/termbox-go"
|
|
|
|
)
|
2015-05-18 14:39:31 +00:00
|
|
|
|
2015-10-21 16:14:57 +00:00
|
|
|
// InputField is a field for inputting text
|
2015-05-18 14:39:31 +00:00
|
|
|
type InputField struct {
|
2016-02-09 16:21:57 +00:00
|
|
|
id string
|
2019-03-25 15:10:25 +00:00
|
|
|
title string
|
2015-05-18 14:39:31 +00:00
|
|
|
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
|
2016-02-19 14:51:24 +00:00
|
|
|
cursorFg, cursorBg termbox.Attribute
|
2015-05-18 14:39:31 +00:00
|
|
|
bordered bool
|
2016-01-26 21:33:25 +00:00
|
|
|
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
|
2020-02-13 16:20:49 +00:00
|
|
|
|
|
|
|
filter func(*InputField, string, string) string
|
2015-05-18 14:39:31 +00:00
|
|
|
}
|
|
|
|
|
2015-10-21 16:14:57 +00:00
|
|
|
// CreateInputField creates an input field at x, y that is w by h
|
2015-05-18 14:39:31 +00:00
|
|
|
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,
|
|
|
|
}
|
2020-02-13 16:20:49 +00:00
|
|
|
c.filter = func(fld *InputField, o, n string) string { return n }
|
2019-03-25 15:10:25 +00:00
|
|
|
return &c
|
2015-05-18 14:39:31 +00:00
|
|
|
}
|
|
|
|
|
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-05-18 14:39:31 +00:00
|
|
|
}
|
|
|
|
|
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
|
2020-02-13 16:20:49 +00:00
|
|
|
func (c *InputField) SetX(x int) { c.x = x }
|
2015-05-18 14:39:31 +00:00
|
|
|
|
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
|
2020-02-13 16:20:49 +00:00
|
|
|
func (c *InputField) SetY(y int) { c.y = y }
|
2015-05-18 14:39:31 +00:00
|
|
|
|
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
|
2020-02-13 16:20:49 +00:00
|
|
|
func (c *InputField) SetWidth(w int) { c.width = w }
|
2015-05-18 14:39:31 +00:00
|
|
|
|
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
|
2020-02-13 16:20:49 +00:00
|
|
|
func (c *InputField) SetHeight(h int) { c.height = h }
|
2015-05-18 14:39:31 +00:00
|
|
|
|
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
|
2020-02-13 16:20:49 +00:00
|
|
|
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
|
2020-02-13 16:20:49 +00:00
|
|
|
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
|
|
|
|
2020-02-13 16:20:49 +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-01-26 21:33:25 +00:00
|
|
|
}
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
2016-01-26 21:33:25 +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 }
|
2016-01-26 21:33:25 +00:00
|
|
|
|
|
|
|
// 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
|
2016-01-26 21:33:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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 }
|
2016-01-26 21:33:25 +00:00
|
|
|
|
|
|
|
// 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
|
2015-05-18 14:39:31 +00:00
|
|
|
}
|
|
|
|
|
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 {
|
2020-02-13 16:20:49 +00:00
|
|
|
prev := c.value
|
|
|
|
if event.Key == termbox.KeyTab { // There is no tabbing in here
|
|
|
|
return false
|
|
|
|
}
|
2016-02-19 14:51:24 +00:00
|
|
|
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]
|
2016-02-19 14:51:24 +00:00
|
|
|
}
|
|
|
|
} else if event.Key == termbox.KeyArrowLeft {
|
2019-03-25 15:10:25 +00:00
|
|
|
if c.cursor+len(c.value) > 0 {
|
|
|
|
c.cursor--
|
2016-02-19 14:51:24 +00:00
|
|
|
}
|
|
|
|
} else if event.Key == termbox.KeyArrowRight {
|
2019-03-25 15:10:25 +00:00
|
|
|
if c.cursor < 0 {
|
|
|
|
c.cursor++
|
2016-02-19 14:51:24 +00:00
|
|
|
}
|
|
|
|
} 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):]
|
2016-02-19 14:51:24 +00:00
|
|
|
} 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"
|
2016-02-25 16:31:28 +00:00
|
|
|
case termbox.KeyEnter:
|
2019-03-25 15:10:25 +00:00
|
|
|
if c.multiline {
|
2016-02-25 16:31:28 +00:00
|
|
|
ch = "\n"
|
|
|
|
}
|
2016-02-19 14:51:24 +00:00
|
|
|
default:
|
|
|
|
if KeyIsAlphaNumeric(event) || KeyIsSymbol(event) {
|
|
|
|
ch = string(event.Ch)
|
2016-01-26 21:33:25 +00:00
|
|
|
}
|
2016-02-19 14:51:24 +00:00
|
|
|
}
|
2015-10-21 16:00:06 +00:00
|
|
|
|
2016-02-25 16:31:28 +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)
|
2016-02-19 14:51:24 +00:00
|
|
|
} 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
|
2015-05-18 14:39:31 +00:00
|
|
|
}
|
|
|
|
}
|
2020-02-13 16:20:49 +00:00
|
|
|
c.value = c.filter(c, prev, c.value)
|
2016-02-19 14:51:24 +00:00
|
|
|
return true
|
2015-05-18 14:39:31 +00:00
|
|
|
}
|
|
|
|
|
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)
|
2016-01-26 21:33:25 +00:00
|
|
|
maxWidth--
|
|
|
|
maxHeight--
|
|
|
|
x++
|
|
|
|
y++
|
|
|
|
startX++
|
|
|
|
startY++
|
2015-05-18 14:39:31 +00:00
|
|
|
}
|
|
|
|
|
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 = ' '
|
2015-05-18 14:39:31 +00:00
|
|
|
} 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])
|
2015-05-18 14:39:31 +00:00
|
|
|
}
|
|
|
|
} else {
|
2015-10-21 16:14:57 +00:00
|
|
|
strPt1, strPt2, cursorRune = "", "", ' '
|
2015-05-18 14:39:31 +00:00
|
|
|
}
|
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 {
|
2016-01-26 21:33:25 +00:00
|
|
|
// 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)
|
2016-01-26 21:33:25 +00:00
|
|
|
x = startX
|
|
|
|
y++
|
|
|
|
strPt1 = strPt1[breakAt:]
|
|
|
|
}
|
2019-03-25 15:10:25 +00:00
|
|
|
x, y = DrawStringAtPoint(strPt1, x, y, useFg, useBg)
|
2016-01-26 21:33:25 +00:00
|
|
|
if x >= maxWidth {
|
|
|
|
y++
|
|
|
|
x = startX
|
|
|
|
}
|
2019-03-25 15:10:25 +00:00
|
|
|
termbox.SetCell(x, y, cursorRune, c.cursorFg, c.cursorBg)
|
2016-01-26 21:33:25 +00:00
|
|
|
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)
|
2016-01-26 21:33:25 +00:00
|
|
|
strPt2 = strPt2[lenLeft:]
|
|
|
|
}
|
|
|
|
for len(strPt2) > maxWidth {
|
|
|
|
breakAt := maxWidth
|
2019-03-25 15:10:25 +00:00
|
|
|
DrawStringAtPoint(strPt2[:breakAt], x, y, useFg, useBg)
|
2016-01-26 21:33:25 +00:00
|
|
|
x = startX
|
|
|
|
y++
|
|
|
|
strPt2 = strPt2[breakAt:]
|
|
|
|
}
|
2019-03-25 15:10:25 +00:00
|
|
|
x, y = DrawStringAtPoint(strPt2, x, y, useFg, useBg)
|
2016-01-26 21:33:25 +00:00
|
|
|
}
|
|
|
|
} 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)
|
2016-01-26 21:33:25 +00:00
|
|
|
}
|
2015-05-18 14:39:31 +00:00
|
|
|
}
|
2020-02-13 16:20:49 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
}
|