clui/edit.go

298 lines
6.0 KiB
Go
Raw Normal View History

2015-09-21 20:54:39 -07:00
package clui
import (
xs "github.com/huandu/xstrings"
2015-10-16 10:27:43 -07:00
term "github.com/nsf/termbox-go"
2015-09-21 20:54:39 -07:00
)
/*
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 {
2015-10-16 10:27:43 -07:00
ControlBase
2015-09-21 20:54:39 -07:00
// 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
2015-10-16 10:27:43 -07:00
offset int
readonly bool
maxWidth int
2015-09-21 20:54:39 -07:00
onChange func(Event)
}
2015-10-16 10:27:43 -07:00
func NewEditField(view View, parent Control, width int, text string, scale int) *EditField {
2015-09-21 20:54:39 -07:00
e := new(EditField)
e.onChange = nil
2015-10-16 10:27:43 -07:00
e.SetTitle(text)
2015-09-21 20:54:39 -07:00
e.SetEnabled(true)
2015-10-20 09:43:56 -07:00
if width == AutoSize {
width = xs.Len(text) + 1
}
2015-09-21 20:54:39 -07:00
e.SetSize(width, 1)
e.cursorPos = xs.Len(text)
e.offset = 0
e.parent = parent
2015-10-16 10:27:43 -07:00
e.view = view
e.parent = parent
e.readonly = false
2015-09-21 20:54:39 -07:00
2015-10-16 10:27:43 -07:00
e.SetConstraints(width, 1)
2015-09-21 20:54:39 -07:00
e.end()
2015-10-16 10:27:43 -07:00
if parent != nil {
parent.AddChild(e, scale)
}
2015-09-21 20:54:39 -07:00
return e
}
func (e *EditField) OnChange(fn func(Event)) {
e.onChange = fn
}
2015-10-16 10:27:43 -07:00
func (e *EditField) SetTitle(title string) {
2015-09-21 20:54:39 -07:00
if e.title != title {
e.title = title
if e.onChange != nil {
2015-10-16 10:27:43 -07:00
ev := Event{Msg: title, Sender: e}
2015-09-21 20:54:39 -07:00
go e.onChange(ev)
}
}
}
2015-10-16 10:27:43 -07:00
func (e *EditField) Repaint() {
canvas := e.view.Canvas()
2015-09-21 20:54:39 -07:00
2015-10-16 10:27:43 -07:00
x, y := e.Pos()
w, _ := e.Size()
2015-09-21 20:54:39 -07:00
2015-10-16 10:27:43 -07:00
tm := e.view.Screen().Theme()
parts := []rune(tm.SysObject(ObjEdit))
chLeft, chRight := string(parts[0]), string(parts[1])
2015-09-21 20:54:39 -07:00
var textOut string
curOff := 0
2015-10-16 10:27:43 -07:00
if e.offset == 0 && xs.Len(e.title) < e.width {
2015-09-21 20:54:39 -07:00
textOut = e.title
} else {
fromIdx := 0
toIdx := 0
if e.offset == 0 {
2015-10-16 10:27:43 -07:00
toIdx = e.width - 1
2015-09-21 20:54:39 -07:00
textOut = xs.Slice(e.title, 0, toIdx) + chRight
curOff = -e.offset
} else {
curOff = 1 - e.offset
fromIdx = e.offset
2015-10-16 10:27:43 -07:00
if e.width-1 <= xs.Len(e.title)-e.offset {
2015-09-21 20:54:39 -07:00
toIdx = e.offset + e.width - 2
textOut = chLeft + xs.Slice(e.title, fromIdx, toIdx) + chRight
} else {
textOut = chLeft + xs.Slice(e.title, fromIdx, -1)
}
}
}
2015-10-16 10:27:43 -07:00
fg, bg := RealColor(tm, e.fg, ColorEditText), RealColor(tm, e.bg, ColorEditBack)
2015-10-19 12:05:43 -07:00
if !e.Enabled() {
fg, bg = RealColor(tm, e.fg, ColorDisabledText), RealColor(tm, e.fg, ColorDisabledBack)
} else if e.Active() {
fg, bg = RealColor(tm, e.fg, ColorEditActiveText), RealColor(tm, e.bg, ColorEditActiveBack)
}
2015-09-21 20:54:39 -07:00
2015-10-16 10:27:43 -07:00
canvas.FillRect(x, y, w, 1, term.Cell{Ch: ' ', Bg: bg})
canvas.PutText(x, y, textOut, fg, bg)
2015-09-21 20:54:39 -07:00
if e.active {
2015-10-16 10:27:43 -07:00
wx, wy := e.view.Pos()
canvas.SetCursorPos(e.cursorPos+curOff+wx+e.x, wy+e.y)
2015-09-21 20:54:39 -07:00
}
}
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 {
2015-10-16 10:27:43 -07:00
e.SetTitle(string(ch) + e.title)
2015-09-21 20:54:39 -07:00
} else if idx >= xs.Len(e.title) {
2015-10-16 10:27:43 -07:00
e.SetTitle(e.title + string(ch))
2015-09-21 20:54:39 -07:00
} else {
2015-10-16 10:27:43 -07:00
e.SetTitle(xs.Slice(e.title, 0, idx) + string(ch) + xs.Slice(e.title, idx, -1))
2015-09-21 20:54:39 -07:00
}
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--
2015-10-16 10:27:43 -07:00
e.SetTitle(xs.Slice(e.title, 0, length-1))
2015-09-21 20:54:39 -07:00
} else if e.cursorPos == 1 {
e.cursorPos = 0
2015-10-16 10:27:43 -07:00
e.SetTitle(xs.Slice(e.title, 1, -1))
2015-09-21 20:54:39 -07:00
e.offset = 0
} else {
e.cursorPos--
2015-10-16 10:27:43 -07:00
e.SetTitle(xs.Slice(e.title, 0, e.cursorPos) + xs.Slice(e.title, e.cursorPos+1, -1))
2015-09-21 20:54:39 -07:00
}
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 {
2015-10-16 10:27:43 -07:00
e.SetTitle(xs.Slice(e.title, 0, length-1))
2015-09-21 20:54:39 -07:00
} else {
2015-10-16 10:27:43 -07:00
e.SetTitle(xs.Slice(e.title, 0, e.cursorPos) + xs.Slice(e.title, e.cursorPos+1, -1))
2015-09-21 20:54:39 -07:00
}
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()
2015-10-16 10:27:43 -07:00
e.SetTitle("")
2015-09-21 20:54:39 -07:00
}
func (e *EditField) ProcessEvent(event Event) bool {
2015-10-16 10:27:43 -07:00
if !e.Active() || !e.Enabled() {
2015-09-21 20:54:39 -07:00
return false
}
if event.Type == EventActivate && event.X == 0 {
2015-10-16 10:27:43 -07:00
term.HideCursor()
2015-09-21 20:54:39 -07:00
}
2015-10-16 10:27:43 -07:00
if event.Type == EventKey && event.Key != term.KeyTab && event.Key != term.KeyEnter {
2015-09-21 20:54:39 -07:00
switch event.Key {
2015-10-16 10:27:43 -07:00
case term.KeySpace:
2015-09-21 20:54:39 -07:00
e.insertRune(' ')
return true
2015-10-16 10:27:43 -07:00
case term.KeyBackspace:
2015-09-21 20:54:39 -07:00
e.backspace()
return true
2015-10-16 10:27:43 -07:00
case term.KeyDelete:
2015-09-21 20:54:39 -07:00
e.del()
return true
2015-10-16 10:27:43 -07:00
case term.KeyArrowLeft:
2015-09-21 20:54:39 -07:00
e.charLeft()
return true
2015-10-16 10:27:43 -07:00
case term.KeyHome:
2015-09-21 20:54:39 -07:00
e.home()
return true
2015-10-16 10:27:43 -07:00
case term.KeyEnd:
2015-09-21 20:54:39 -07:00
e.end()
return true
2015-10-16 10:27:43 -07:00
case term.KeyCtrlR:
2015-09-21 20:54:39 -07:00
if !e.readonly {
e.Clear()
}
return true
2015-10-16 10:27:43 -07:00
case term.KeyArrowRight:
2015-09-21 20:54:39 -07:00
e.charRight()
return true
default:
if event.Ch != 0 {
e.insertRune(event.Ch)
return true
}
}
return false
}
return false
}
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
}