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"
|
2018-01-02 11:15:05 -08:00
|
|
|
"strings"
|
2015-09-21 20:54:39 -07:00
|
|
|
)
|
|
|
|
|
2015-10-28 14:17:49 -07:00
|
|
|
// OnChange sets the callback that is called when EditField content is changed
|
2015-09-21 20:54:39 -07:00
|
|
|
func (e *EditField) OnChange(fn func(Event)) {
|
|
|
|
e.onChange = fn
|
|
|
|
}
|
|
|
|
|
2015-11-24 12:03:06 -08:00
|
|
|
// OnKeyPress sets the callback that is called when a user presses a Key while
|
|
|
|
// the controls is active. If a handler processes the key it should return
|
|
|
|
// true. If handler returns false it means that the default handler will
|
|
|
|
// process the key
|
2018-04-03 13:07:49 -07:00
|
|
|
func (e *EditField) OnKeyPress(fn func(term.Key, rune) bool) {
|
2015-11-24 12:03:06 -08:00
|
|
|
e.onKeyPress = fn
|
|
|
|
}
|
|
|
|
|
2015-10-28 14:17:49 -07:00
|
|
|
// SetTitle changes the EditField content and emits OnChage eventif the new value does not equal to old one
|
2015-10-16 10:27:43 -07:00
|
|
|
func (e *EditField) SetTitle(title string) {
|
2018-03-15 20:56:29 -07:00
|
|
|
e.setTitleInternal(title)
|
|
|
|
e.end()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e *EditField) setTitleInternal(title string) {
|
2015-09-21 20:54:39 -07:00
|
|
|
if e.title != title {
|
|
|
|
e.title = title
|
2018-03-15 20:56:29 -07:00
|
|
|
|
2015-09-21 20:54:39 -07:00
|
|
|
if e.onChange != nil {
|
2016-10-13 14:16:05 -07:00
|
|
|
ev := Event{Msg: title}
|
2018-01-27 14:21:28 -08:00
|
|
|
e.onChange(ev)
|
2015-09-21 20:54:39 -07:00
|
|
|
}
|
|
|
|
}
|
2018-03-20 14:54:52 -07:00
|
|
|
|
|
|
|
if title == "" {
|
|
|
|
e.cursorPos = xs.Len(title)
|
|
|
|
}
|
2015-09-21 20:54:39 -07:00
|
|
|
}
|
|
|
|
|
2015-10-28 14:17:49 -07:00
|
|
|
// Repaint draws the control on its View surface
|
2016-10-13 14:16:05 -07:00
|
|
|
func (e *EditField) Draw() {
|
2018-03-13 22:14:40 -07:00
|
|
|
if e.hidden {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-10-13 14:16:05 -07:00
|
|
|
PushAttributes()
|
|
|
|
defer PopAttributes()
|
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
|
|
|
|
2016-10-13 14:16:05 -07:00
|
|
|
parts := []rune(SysObject(ObjEdit))
|
2015-10-16 10:27:43 -07:00
|
|
|
chLeft, chRight := string(parts[0]), string(parts[1])
|
2018-01-02 11:15:05 -08:00
|
|
|
chStar := "*"
|
|
|
|
if len(parts) > 3 {
|
|
|
|
chStar = string(parts[3])
|
|
|
|
}
|
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 {
|
2018-01-02 11:15:05 -08:00
|
|
|
if e.showStars {
|
|
|
|
textOut = strings.Repeat(chStar, xs.Len(e.title))
|
|
|
|
} else {
|
|
|
|
textOut = e.title
|
|
|
|
}
|
2015-09-21 20:54:39 -07:00
|
|
|
} else {
|
|
|
|
fromIdx := 0
|
|
|
|
toIdx := 0
|
|
|
|
if e.offset == 0 {
|
2015-10-16 10:27:43 -07:00
|
|
|
toIdx = e.width - 1
|
2018-01-02 11:15:05 -08:00
|
|
|
if e.showStars {
|
|
|
|
textOut = strings.Repeat(chStar, toIdx) + chRight
|
|
|
|
} else {
|
|
|
|
textOut = xs.Slice(e.title, 0, toIdx) + chRight
|
|
|
|
}
|
2015-09-21 20:54:39 -07:00
|
|
|
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
|
2018-01-02 11:15:05 -08:00
|
|
|
if e.showStars {
|
|
|
|
textOut = chLeft + strings.Repeat(chStar, toIdx-fromIdx) + chRight
|
|
|
|
} else {
|
|
|
|
textOut = chLeft + xs.Slice(e.title, fromIdx, toIdx) + chRight
|
|
|
|
}
|
2015-09-21 20:54:39 -07:00
|
|
|
} else {
|
2018-01-02 11:15:05 -08:00
|
|
|
if e.showStars {
|
|
|
|
textOut = chLeft + strings.Repeat(chStar, xs.Len(e.title)-fromIdx)
|
|
|
|
} else {
|
|
|
|
textOut = chLeft + xs.Slice(e.title, fromIdx, -1)
|
|
|
|
}
|
2015-09-21 20:54:39 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-10-13 14:16:05 -07:00
|
|
|
fg, bg := RealColor(e.fg, ColorEditText), RealColor(e.bg, ColorEditBack)
|
2015-10-19 12:05:43 -07:00
|
|
|
if !e.Enabled() {
|
2016-10-13 14:16:05 -07:00
|
|
|
fg, bg = RealColor(e.fg, ColorDisabledText), RealColor(e.fg, ColorDisabledBack)
|
2015-10-19 12:05:43 -07:00
|
|
|
} else if e.Active() {
|
2016-10-13 14:16:05 -07:00
|
|
|
fg, bg = RealColor(e.fg, ColorEditActiveText), RealColor(e.bg, ColorEditActiveBack)
|
2015-10-19 12:05:43 -07:00
|
|
|
}
|
2015-09-21 20:54:39 -07:00
|
|
|
|
2016-10-13 14:16:05 -07:00
|
|
|
SetTextColor(fg)
|
|
|
|
SetBackColor(bg)
|
|
|
|
FillRect(x, y, w, 1, ' ')
|
|
|
|
DrawRawText(x, y, textOut)
|
|
|
|
if e.Active() {
|
|
|
|
SetCursorPos(e.cursorPos+e.x+curOff, 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 {
|
2018-03-15 20:56:29 -07:00
|
|
|
e.setTitleInternal(string(ch) + e.title)
|
2015-09-21 20:54:39 -07:00
|
|
|
} else if idx >= xs.Len(e.title) {
|
2018-03-15 20:56:29 -07:00
|
|
|
e.setTitleInternal(e.title + string(ch))
|
2015-09-21 20:54:39 -07:00
|
|
|
} else {
|
2018-03-15 20:56:29 -07:00
|
|
|
e.setTitleInternal(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--
|
2018-03-15 20:56:29 -07:00
|
|
|
e.setTitleInternal(xs.Slice(e.title, 0, length-1))
|
2015-09-21 20:54:39 -07:00
|
|
|
} else if e.cursorPos == 1 {
|
|
|
|
e.cursorPos = 0
|
2018-03-15 20:56:29 -07:00
|
|
|
e.setTitleInternal(xs.Slice(e.title, 1, -1))
|
2015-09-21 20:54:39 -07:00
|
|
|
e.offset = 0
|
|
|
|
} else {
|
|
|
|
e.cursorPos--
|
2018-03-15 20:56:29 -07:00
|
|
|
e.setTitleInternal(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 {
|
2018-03-15 20:56:29 -07:00
|
|
|
e.setTitleInternal(xs.Slice(e.title, 0, length-1))
|
2015-09-21 20:54:39 -07:00
|
|
|
} else {
|
2018-03-15 20:56:29 -07:00
|
|
|
e.setTitleInternal(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)
|
|
|
|
}
|
|
|
|
|
2015-10-28 14:17:49 -07:00
|
|
|
// Clear empties the EditField and emits OnChange event
|
2015-09-21 20:54:39 -07:00
|
|
|
func (e *EditField) Clear() {
|
|
|
|
e.home()
|
2018-03-15 20:56:29 -07:00
|
|
|
e.setTitleInternal("")
|
2015-09-21 20:54:39 -07:00
|
|
|
}
|
|
|
|
|
2015-10-28 14:17:49 -07:00
|
|
|
// SetMaxWidth sets the maximum lenght of the EditField text. If the current text is longer it is truncated
|
2015-09-21 20:54:39 -07:00
|
|
|
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()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-10-28 14:17:49 -07:00
|
|
|
// MaxWidth returns the current maximum text length. Zero means no limit
|
|
|
|
func (e *EditField) MaxWidth() int {
|
2015-09-21 20:54:39 -07:00
|
|
|
return e.maxWidth
|
|
|
|
}
|
2015-11-04 10:34:30 -08:00
|
|
|
|
|
|
|
// SetSize changes control size. Constant DoNotChange can be
|
|
|
|
// used as placeholder to indicate that the control attrubute
|
|
|
|
// should be unchanged.
|
|
|
|
// Method does nothing if new size is less than minimal size
|
|
|
|
// EditField height cannot be changed - it equals 1 always
|
|
|
|
func (e *EditField) SetSize(width, height int) {
|
2016-10-13 14:16:05 -07:00
|
|
|
if width != KeepValue && (width > 1000 || width < e.minW) {
|
2015-11-04 10:34:30 -08:00
|
|
|
return
|
|
|
|
}
|
2016-10-13 14:16:05 -07:00
|
|
|
if height != KeepValue && (height > 200 || height < e.minH) {
|
2015-11-04 10:34:30 -08:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-10-13 14:16:05 -07:00
|
|
|
if width != KeepValue {
|
2015-11-04 10:34:30 -08:00
|
|
|
e.width = width
|
|
|
|
}
|
|
|
|
|
|
|
|
e.height = 1
|
|
|
|
}
|
2018-01-02 11:15:05 -08:00
|
|
|
|
|
|
|
// PasswordMode returns whether password mode is enabled for the control
|
|
|
|
func (e *EditField) PasswordMode() bool {
|
|
|
|
return e.showStars
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetPasswordMode changes the way an EditField displays it content.
|
|
|
|
// If PasswordMode is false then the EditField works as regular text entry
|
|
|
|
// control. If PasswordMode is true then the EditField shows its content hidden
|
|
|
|
// with star characters ('*' by default)
|
|
|
|
func (e *EditField) SetPasswordMode(pass bool) {
|
|
|
|
e.showStars = pass
|
|
|
|
}
|