diff --git a/ui/ui.go b/ui/ui.go index cc3e0ce..930f646 100644 --- a/ui/ui.go +++ b/ui/ui.go @@ -515,3 +515,84 @@ func (p *KeyValue) SetVisible(b bool) { func (p *KeyValue) GetValue() string { return p.value } + +type EditableTextLine struct { + x, y int + h, w int + text string + style tcell.Style + visible bool + cursorPos int +} + +func NewEditableTextLine(initialText string) *EditableTextLine { + return &EditableTextLine{ + text: initialText, + visible: true, + } +} + +func (p *EditableTextLine) SetSize(x, y, _, _ int) { + p.x, p.y, p.h, p.w = x, y, 1, len(p.text) +} + +func (p *EditableTextLine) SetStyle(s tcell.Style) { + p.style = s +} + +func (p *EditableTextLine) Draw(s tcell.Screen) { + if !p.visible { + return + } + for j, r := range p.text { + s.SetContent(p.x+j, p.y, r, nil, p.style) + } +} + +func (p *EditableTextLine) SetText(t string) { + p.text = t + if len(p.text) == 0 { + p.ResetCursor(true) + return + } + p.ResetCursor(false) +} + +func (p *EditableTextLine) ResetCursor(beginning bool) { + if beginning { + p.cursorPos = 0 + } else { + p.cursorPos = len(p.text) + } +} + +func (p *EditableTextLine) InsertAtCursor(r rune) { + if len(p.text) == 0 { + p.text = string(r) + p.cursorPos = 1 + return + } + p.text = p.text[0:p.cursorPos] + string(r) + p.text[p.cursorPos:len(p.text)] + p.cursorPos = p.cursorPos + 1 +} + +func (p *EditableTextLine) MoveCursor(i int) { + if p.cursorPos+i < 0 { + p.cursorPos = 0 + return + } + if p.cursorPos+i > len(p.text) { + p.cursorPos = len(p.text) + return + } + p.cursorPos = p.cursorPos + i +} + +func (p *EditableTextLine) DeleteAtCursor() { + if len(p.text) == 0 { + p.cursorPos = 0 + return + } + p.text = p.text[0:p.cursorPos-1] + p.text[p.cursorPos:len(p.text)] + p.cursorPos = p.cursorPos - 1 +} diff --git a/ui/ui_test.go b/ui/ui_test.go index 33452e4..c08e49f 100644 --- a/ui/ui_test.go +++ b/ui/ui_test.go @@ -174,3 +174,66 @@ func TestContainerTwoBoxesPercentageVStack(t *testing.T) { t.Fail() } } + +func TestNewEditableTextLine(t *testing.T) { + e := NewEditableTextLine("") + e.InsertAtCursor('a') + e.InsertAtCursor('b') + e.InsertAtCursor('c') + if e.text != "abc" { + fmt.Printf("expected: 'abc', actual: '%+v'", e.text) + t.Fail() + } + e.MoveCursor(-1) + e.InsertAtCursor('d') + if e.text != "abdc" { + fmt.Printf("expected: 'abdc', actual: '%+v'", e.text) + t.Fail() + } + e.MoveCursor(-20) + e.InsertAtCursor('e') + if e.text != "eabdc" { + fmt.Printf("expected: 'eabdc', actual: '%+v'", e.text) + t.Fail() + } + e.MoveCursor(20) + e.InsertAtCursor('f') + if e.text != "eabdcf" { + fmt.Printf("expected: 'eabdcf', actual: '%+v'", e.text) + t.Fail() + } + e.MoveCursor(1) + e.InsertAtCursor('g') + if e.text != "eabdcfg" { + fmt.Printf("expected: 'eabdcfg', actual: '%+v'", e.text) + t.Fail() + } + e.DeleteAtCursor() + e.DeleteAtCursor() + e.MoveCursor(-1) + e.DeleteAtCursor() + if e.text != "eabc" { + fmt.Printf("expected: 'eabc', actual: '%+v'", e.text) + t.Fail() + } + e.ResetCursor(false) + e.InsertAtCursor('h') + e.ResetCursor(true) + e.InsertAtCursor('g') + if e.text != "geabch" { + fmt.Printf("expected: 'geabch', actual: '%+v'", e.text) + t.Fail() + } + e.SetText("the rain in spain") + e.InsertAtCursor('s') + if e.text != "the rain in spains" { + fmt.Printf("expected: 'the rain in spains', actual: '%+v'", e.text) + t.Fail() + } + e.SetText("") + e.InsertAtCursor('s') + if e.text != "s" { + fmt.Printf("expected: 's', actual: '%+v'", e.text) + t.Fail() + } +}