mirror of
https://github.com/VladimirMarkelov/clui.git
synced 2025-04-26 13:49:01 +08:00
563 lines
11 KiB
Go
563 lines
11 KiB
Go
package clui
|
|
|
|
import (
|
|
"github.com/VladimirMarkelov/termbox-go"
|
|
xs "github.com/huandu/xstrings"
|
|
"strings"
|
|
)
|
|
|
|
/*
|
|
Text edit field contol. Can be simple edit field(default mode) and edit field
|
|
with drop down list. The EditField mode is set during creation and cannot be changed on the fly.
|
|
Edit field consumes some keyboard events when it is active: all printable charaters;
|
|
Delete, BackSpace, Home, End, left and right arrows; Enter, up and down arrows if EditField
|
|
in combobox mode and drop down list is visible; F5 to open drop down list in combobox mode;
|
|
Ctrl+R to clear EditField.
|
|
Edit text can be limited. By default a user can enter text of any lenght. Use SetMaxWidth to limit the maximum text length. If the text is longer than maximun then the text is automatically truncated.
|
|
EditField call funtion onChage in case of its text is changed. Event field Msg contains the new text
|
|
*/
|
|
type EditField struct {
|
|
posX, posY int
|
|
width int
|
|
title string
|
|
anchor Anchor
|
|
id WinId
|
|
enabled bool
|
|
align Align
|
|
active bool
|
|
// cursor position in edit text
|
|
cursorPos int
|
|
// the number of the first displayed text character - it is used in case of text is longer than edit width
|
|
offset int
|
|
readonly bool
|
|
visible bool
|
|
tabStop bool
|
|
editBoxMode EditBoxMode
|
|
parent Window
|
|
maxWidth int
|
|
textColor Color
|
|
backColor Color
|
|
scale int
|
|
|
|
minW, minH int
|
|
|
|
// items below are used only in combobox mode
|
|
list *ListBox
|
|
listboxVisible bool
|
|
listWidth int
|
|
listHeight int
|
|
itemList []string
|
|
|
|
onChange func(Event)
|
|
}
|
|
|
|
func NewEditField(parent Window, id WinId, x, y, width int, text string, props Props) *EditField {
|
|
e := new(EditField)
|
|
e.onChange = nil
|
|
e.SetText(text)
|
|
e.SetEnabled(true)
|
|
e.SetPos(x, y)
|
|
e.SetSize(width, 1)
|
|
e.cursorPos = xs.Len(text)
|
|
e.offset = 0
|
|
e.visible = true
|
|
e.tabStop = true
|
|
e.id = id
|
|
e.editBoxMode = EditBoxSimple
|
|
e.parent = parent
|
|
e.minW, e.minH = 3, 1
|
|
e.anchor = props.Anchors
|
|
|
|
e.end()
|
|
|
|
return e
|
|
}
|
|
|
|
func NewComboBox(parent Window, id WinId, x, y, width int, text string, props Props) *EditField {
|
|
e := new(EditField)
|
|
e.onChange = nil
|
|
e.SetText(text)
|
|
e.SetEnabled(true)
|
|
e.SetPos(x, y)
|
|
e.SetSize(width, 1)
|
|
e.cursorPos = xs.Len(text)
|
|
e.offset = 0
|
|
e.visible = true
|
|
e.tabStop = true
|
|
e.id = id
|
|
e.editBoxMode = EditBoxCombo
|
|
e.SetEnabled(true)
|
|
e.readonly = props.ReadOnly
|
|
|
|
e.listboxVisible = false
|
|
e.listWidth, e.listHeight = -1, -1
|
|
e.itemList = strings.Split(props.Text, "|")
|
|
e.parent = parent
|
|
e.visible = true
|
|
e.minW, e.minH = 4, 1
|
|
e.anchor = props.Anchors
|
|
|
|
e.end()
|
|
|
|
return e
|
|
}
|
|
|
|
func (e *EditField) OnChange(fn func(Event)) {
|
|
e.onChange = fn
|
|
}
|
|
|
|
func (e *EditField) SetText(title string) {
|
|
if e.title != title {
|
|
e.title = title
|
|
if e.onChange != nil {
|
|
ev := Event{Msg: title}
|
|
go e.onChange(ev)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (e *EditField) GetText() string {
|
|
return e.title
|
|
}
|
|
|
|
func (e *EditField) GetId() WinId {
|
|
return e.id
|
|
}
|
|
|
|
func (e *EditField) GetSize() (int, int) {
|
|
return e.width, 1
|
|
}
|
|
|
|
func (e *EditField) GetConstraints() (int, int) {
|
|
return e.minW, e.minH
|
|
}
|
|
|
|
func (e *EditField) SetConstraints(minW, minH int) {
|
|
if minW <= DoNotChange {
|
|
e.minW = minW
|
|
}
|
|
if minH <= DoNotChange {
|
|
e.minH = minH
|
|
}
|
|
}
|
|
|
|
func (e *EditField) SetSize(width, height int) {
|
|
width, height = ApplyConstraints(e, width, height)
|
|
e.width = width
|
|
}
|
|
|
|
func (e *EditField) GetPos() (int, int) {
|
|
return e.posX, e.posY
|
|
}
|
|
|
|
func (e *EditField) SetPos(x, y int) {
|
|
e.posX = x
|
|
e.posY = y
|
|
}
|
|
|
|
func (e *EditField) drawButton(canvas Canvas) {
|
|
tm := canvas.Theme()
|
|
arrow := tm.GetSysObject(ObjComboboxDropDown)
|
|
|
|
fg, bg := ColorDefault, ColorDefault
|
|
if fg == ColorDefault {
|
|
fg = tm.GetSysColor(ColorControlText)
|
|
}
|
|
if bg == ColorDefault {
|
|
bg = tm.GetSysColor(ColorControlBack)
|
|
}
|
|
|
|
canvas.DrawRune(e.posX+e.width-1, e.posY, arrow, fg, bg)
|
|
}
|
|
|
|
func (e *EditField) Redraw(canvas Canvas) {
|
|
x, y := e.GetPos()
|
|
w, _ := e.GetSize()
|
|
|
|
dw := 0
|
|
if e.editBoxMode == EditBoxCombo {
|
|
dw = 1
|
|
}
|
|
|
|
tm := canvas.Theme()
|
|
chLeft := string(tm.GetSysObject(ObjEditLeftArrow))
|
|
chRight := string(tm.GetSysObject(ObjEditRightArrow))
|
|
|
|
var textOut string
|
|
curOff := 0
|
|
if e.offset == 0 && xs.Len(e.title) < e.width-dw {
|
|
textOut = e.title
|
|
} else {
|
|
fromIdx := 0
|
|
toIdx := 0
|
|
if e.offset == 0 {
|
|
toIdx = e.width - dw - 1
|
|
textOut = xs.Slice(e.title, 0, toIdx) + chRight
|
|
curOff = -e.offset
|
|
} else {
|
|
curOff = 1 - e.offset
|
|
fromIdx = e.offset
|
|
if e.width-1-dw <= xs.Len(e.title)-e.offset {
|
|
toIdx = e.offset + e.width - 2
|
|
textOut = chLeft + xs.Slice(e.title, fromIdx, toIdx) + chRight
|
|
} else {
|
|
textOut = chLeft + xs.Slice(e.title, fromIdx, -1)
|
|
}
|
|
}
|
|
}
|
|
|
|
fg, bg := e.textColor, e.backColor
|
|
if fg == ColorDefault {
|
|
fg = tm.GetSysColor(ColorEditText)
|
|
}
|
|
if bg == ColorDefault {
|
|
bg = tm.GetSysColor(ColorEditBack)
|
|
}
|
|
|
|
canvas.ClearRect(x, y, w-dw, 1, bg)
|
|
canvas.DrawText(x, y, w-dw, textOut, fg, bg)
|
|
if e.active {
|
|
canvas.SetCursorPos(e, e.cursorPos+curOff, 0)
|
|
}
|
|
|
|
if e.listboxVisible {
|
|
e.list.Redraw(canvas)
|
|
}
|
|
|
|
if e.editBoxMode == EditBoxCombo {
|
|
e.drawButton(canvas)
|
|
}
|
|
}
|
|
|
|
func (e *EditField) GetEnabled() bool {
|
|
return e.enabled
|
|
}
|
|
|
|
func (e *EditField) SetEnabled(active bool) {
|
|
e.enabled = active
|
|
}
|
|
|
|
func (e *EditField) SetAlign(align Align) {
|
|
e.align = align
|
|
}
|
|
|
|
func (e *EditField) GetAlign() Align {
|
|
return e.align
|
|
}
|
|
|
|
func (e *EditField) SetAnchors(anchor Anchor) {
|
|
e.anchor = anchor
|
|
}
|
|
|
|
func (e *EditField) GetAnchors() Anchor {
|
|
return e.anchor
|
|
}
|
|
|
|
func (e *EditField) SetId(id WinId) {
|
|
e.id = id
|
|
}
|
|
|
|
func (e *EditField) GetActive() bool {
|
|
return e.active
|
|
}
|
|
|
|
func (e *EditField) SetActive(active bool) {
|
|
e.active = active
|
|
}
|
|
|
|
func (e *EditField) GetTabStop() bool {
|
|
return e.tabStop
|
|
}
|
|
|
|
func (e *EditField) SetTabStop(tab bool) {
|
|
e.tabStop = tab
|
|
}
|
|
|
|
func (e *EditField) insertRune(ch rune) {
|
|
if e.readonly {
|
|
return
|
|
}
|
|
|
|
if e.maxWidth > 0 && xs.Len(e.title) >= e.maxWidth {
|
|
return
|
|
}
|
|
|
|
idx := e.cursorPos
|
|
|
|
if idx == 0 {
|
|
e.SetText(string(ch) + e.title)
|
|
} else if idx >= xs.Len(e.title) {
|
|
e.SetText(e.title + string(ch))
|
|
} else {
|
|
e.SetText(xs.Slice(e.title, 0, idx) + string(ch) + xs.Slice(e.title, idx, -1))
|
|
}
|
|
|
|
e.cursorPos++
|
|
|
|
if e.cursorPos >= e.width {
|
|
if e.offset == 0 {
|
|
e.offset = 2
|
|
} else {
|
|
e.offset++
|
|
}
|
|
}
|
|
}
|
|
|
|
func (e *EditField) backspace() {
|
|
if e.title == "" || e.cursorPos == 0 || e.readonly {
|
|
return
|
|
}
|
|
|
|
length := xs.Len(e.title)
|
|
if e.cursorPos >= length {
|
|
e.cursorPos--
|
|
e.SetText(xs.Slice(e.title, 0, length-1))
|
|
} else if e.cursorPos == 1 {
|
|
e.cursorPos = 0
|
|
e.SetText(xs.Slice(e.title, 1, -1))
|
|
e.offset = 0
|
|
} else {
|
|
e.cursorPos--
|
|
e.SetText(xs.Slice(e.title, 0, e.cursorPos) + xs.Slice(e.title, e.cursorPos+1, -1))
|
|
}
|
|
|
|
if length-1 < e.width {
|
|
e.offset = 0
|
|
}
|
|
}
|
|
|
|
func (e *EditField) del() {
|
|
length := xs.Len(e.title)
|
|
|
|
if e.title == "" || e.cursorPos == length || e.readonly {
|
|
return
|
|
}
|
|
|
|
if e.cursorPos == length-1 {
|
|
e.SetText(xs.Slice(e.title, 0, length-1))
|
|
} else {
|
|
e.SetText(xs.Slice(e.title, 0, e.cursorPos) + xs.Slice(e.title, e.cursorPos+1, -1))
|
|
}
|
|
|
|
if length-1 < e.width {
|
|
e.offset = 0
|
|
}
|
|
}
|
|
|
|
func (e *EditField) charLeft() {
|
|
if e.cursorPos == 0 || e.title == "" {
|
|
return
|
|
}
|
|
|
|
if e.cursorPos == e.offset {
|
|
e.offset--
|
|
}
|
|
|
|
e.cursorPos--
|
|
}
|
|
|
|
func (e *EditField) charRight() {
|
|
length := xs.Len(e.title)
|
|
if e.cursorPos == length || e.title == "" {
|
|
return
|
|
}
|
|
|
|
e.cursorPos++
|
|
if e.cursorPos != length && e.cursorPos >= e.offset+e.width-2 {
|
|
e.offset++
|
|
}
|
|
}
|
|
|
|
func (e *EditField) home() {
|
|
e.offset = 0
|
|
e.cursorPos = 0
|
|
}
|
|
|
|
func (e *EditField) end() {
|
|
length := xs.Len(e.title)
|
|
e.cursorPos = length
|
|
|
|
if length < e.width {
|
|
return
|
|
}
|
|
|
|
e.offset = length - (e.width - 2)
|
|
}
|
|
|
|
func (e *EditField) Clear() {
|
|
e.home()
|
|
e.SetText("")
|
|
}
|
|
|
|
func (e *EditField) ProcessEvent(event Event) bool {
|
|
if !e.active || !e.enabled {
|
|
return false
|
|
}
|
|
|
|
if event.Type == EventActivate && event.X == 0 {
|
|
termbox.HideCursor()
|
|
}
|
|
|
|
if event.Type == EventKey && event.Key != termbox.KeyTab && event.Key != termbox.KeyEnter {
|
|
switch event.Key {
|
|
case termbox.KeySpace:
|
|
e.insertRune(' ')
|
|
return true
|
|
case termbox.KeyBackspace:
|
|
e.backspace()
|
|
return true
|
|
case termbox.KeyDelete:
|
|
e.del()
|
|
return true
|
|
case termbox.KeyArrowLeft:
|
|
e.charLeft()
|
|
return true
|
|
case termbox.KeyHome:
|
|
e.home()
|
|
return true
|
|
case termbox.KeyEnd:
|
|
e.end()
|
|
return true
|
|
case termbox.KeyCtrlR:
|
|
if !e.readonly {
|
|
e.Clear()
|
|
}
|
|
return true
|
|
case termbox.KeyF5:
|
|
if e.listboxVisible {
|
|
e.closeUpList()
|
|
} else {
|
|
e.dropDownList()
|
|
}
|
|
case termbox.KeyArrowRight:
|
|
e.charRight()
|
|
return true
|
|
default:
|
|
if event.Ch != 0 {
|
|
e.insertRune(event.Ch)
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
if e.editBoxMode == EditBoxCombo && (event.Type == EventMouse || event.Type == EventMouseClick) {
|
|
if event.Y == e.posY && event.X == e.posX+e.width-1 {
|
|
if e.listboxVisible {
|
|
e.closeUpList()
|
|
} else {
|
|
e.dropDownList()
|
|
}
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func (e *EditField) SetVisible(visible bool) {
|
|
e.visible = visible
|
|
}
|
|
|
|
func (e *EditField) GetVisible() bool {
|
|
return e.visible
|
|
}
|
|
|
|
//-----------------------------------------------
|
|
|
|
func (c *EditField) dropDownList() {
|
|
if c.listboxVisible {
|
|
return
|
|
}
|
|
|
|
c.listboxVisible = true
|
|
|
|
height := 5
|
|
if c.listHeight != -1 {
|
|
height = c.listHeight
|
|
}
|
|
if height > len(c.itemList) && len(c.itemList) > 0 {
|
|
height = len(c.itemList)
|
|
}
|
|
width := c.width
|
|
if c.listWidth != -1 {
|
|
width = c.listWidth
|
|
}
|
|
|
|
var props Props
|
|
idL := c.parent.GetNextControlId()
|
|
c.list = NewListBox(c.parent, idL, c.posX, c.posY+1, width, height, props)
|
|
c.parent.AddControl(c.list)
|
|
c.list.SetTabStop(false)
|
|
|
|
for _, str := range c.itemList {
|
|
c.list.AddItem(str)
|
|
}
|
|
|
|
if c.title != "" {
|
|
id := c.list.FindItem(c.title, false)
|
|
if id != -1 {
|
|
c.list.SelectItem(id)
|
|
}
|
|
}
|
|
|
|
c.list.OnSelectItem(func(ev Event) {
|
|
str := c.list.GetSelectedItem()
|
|
c.closeUpList()
|
|
c.SetText(str)
|
|
if c.parent != nil {
|
|
ev := InternalEvent{act: EventRedraw, sender: c.id}
|
|
c.parent.SendEvent(ev)
|
|
}
|
|
})
|
|
|
|
c.parent.ActivateControl(c.list)
|
|
}
|
|
|
|
func (c *EditField) closeUpList() {
|
|
if !c.listboxVisible {
|
|
return
|
|
}
|
|
c.listboxVisible = false
|
|
c.parent.RemoveControl(c.list)
|
|
c.list = nil
|
|
c.SetActive(true)
|
|
}
|
|
|
|
func (e *EditField) SetMaxWidth(w int) {
|
|
e.maxWidth = w
|
|
if w > 0 && xs.Len(e.title) > w {
|
|
e.title = xs.Slice(e.title, 0, w)
|
|
e.end()
|
|
}
|
|
}
|
|
|
|
func (e *EditField) GetMaxWidth() int {
|
|
return e.maxWidth
|
|
}
|
|
|
|
func (e *EditField) GetColors() (Color, Color) {
|
|
return e.textColor, e.backColor
|
|
}
|
|
|
|
func (e *EditField) SetTextColor(clr Color) {
|
|
e.textColor = clr
|
|
}
|
|
|
|
func (e *EditField) SetBackColor(clr Color) {
|
|
e.backColor = clr
|
|
}
|
|
|
|
func (e *EditField) HideChildren() {
|
|
if e.editBoxMode == EditBoxCombo {
|
|
e.closeUpList()
|
|
}
|
|
}
|
|
|
|
func (e *EditField) GetScale() int {
|
|
return e.scale
|
|
}
|
|
|
|
func (e *EditField) SetScale(scale int) {
|
|
e.scale = scale
|
|
}
|