Make textinput cursor visible whenever it changes its position

This improves the UX because you never lose track of the cursor
moving around while it's currently hidden.
This commit is contained in:
Christian Muehlhaeuser 2020-10-24 05:44:51 +02:00 committed by Christian Rocha
parent 1cb36774ed
commit a0fe547fdb

View File

@ -11,7 +11,7 @@ import (
"github.com/muesli/termenv" "github.com/muesli/termenv"
) )
const defaultBlinkSpeed = time.Millisecond * 600 const defaultBlinkSpeed = time.Millisecond * 530
const ( const (
// EchoNormal displays text as is. This is the default behavior. // EchoNormal displays text as is. This is the default behavior.
@ -91,7 +91,7 @@ func (m *Model) SetValue(s string) {
m.value = runes m.value = runes
} }
if m.pos > len(m.value) { if m.pos > len(m.value) {
m.pos = len(m.value) m.SetCursor(len(m.value))
} }
m.handleOverflow() m.handleOverflow()
} }
@ -105,19 +105,18 @@ func (m Model) Value() string {
// out of bounds the cursor will be moved to the start or end accordingly. // out of bounds the cursor will be moved to the start or end accordingly.
func (m *Model) SetCursor(pos int) { func (m *Model) SetCursor(pos int) {
m.pos = clamp(pos, 0, len(m.value)) m.pos = clamp(pos, 0, len(m.value))
m.blink = false
m.handleOverflow() m.handleOverflow()
} }
// CursorStart moves the cursor to the start of the field. // CursorStart moves the cursor to the start of the field.
func (m *Model) CursorStart() { func (m *Model) CursorStart() {
m.pos = 0 m.SetCursor(0)
m.handleOverflow()
} }
// CursorEnd moves the cursor to the end of the field. // CursorEnd moves the cursor to the end of the field.
func (m *Model) CursorEnd() { func (m *Model) CursorEnd() {
m.pos = len(m.value) m.SetCursor(len(m.value))
m.handleOverflow()
} }
// Focused returns the focus state on the model. // Focused returns the focus state on the model.
@ -140,8 +139,7 @@ func (m *Model) Blur() {
// Reset sets the input to its default state with no input. // Reset sets the input to its default state with no input.
func (m *Model) Reset() { func (m *Model) Reset() {
m.value = nil m.value = nil
m.pos = 0 m.SetCursor(0)
m.blink = false
} }
// Paste pastes the contents of the clipboard into the text area (if supported). // Paste pastes the contents of the clipboard into the text area (if supported).
@ -175,7 +173,7 @@ func (m *Model) Paste() {
for _, r := range paste { for _, r := range paste {
head = append(head, r) head = append(head, r)
availSpace-- availSpace--
m.pos++ m.SetCursor(m.pos + 1)
if m.CharLimit > 0 && availSpace <= 0 { if m.CharLimit > 0 && availSpace <= 0 {
break break
} }
@ -259,7 +257,7 @@ func (m *Model) wordLeft() {
for i >= 0 { for i >= 0 {
if unicode.IsSpace(m.value[i]) { if unicode.IsSpace(m.value[i]) {
m.pos-- m.SetCursor(m.pos - 1)
i-- i--
} else { } else {
break break
@ -268,7 +266,7 @@ func (m *Model) wordLeft() {
for i >= 0 { for i >= 0 {
if !unicode.IsSpace(m.value[i]) { if !unicode.IsSpace(m.value[i]) {
m.pos-- m.SetCursor(m.pos - 1)
i-- i--
} else { } else {
break break
@ -285,7 +283,7 @@ func (m *Model) wordRight() {
for i < len(m.value) { for i < len(m.value) {
if unicode.IsSpace(m.value[i]) { if unicode.IsSpace(m.value[i]) {
m.pos++ m.SetCursor(m.pos + 1)
i++ i++
} else { } else {
break break
@ -294,7 +292,7 @@ func (m *Model) wordRight() {
for i < len(m.value) { for i < len(m.value) {
if !unicode.IsSpace(m.value[i]) { if !unicode.IsSpace(m.value[i]) {
m.pos++ m.SetCursor(m.pos + 1)
i++ i++
} else { } else {
break break
@ -354,7 +352,7 @@ func Update(msg tea.Msg, m Model) (Model, tea.Cmd) {
if len(m.value) > 0 { if len(m.value) > 0 {
m.value = append(m.value[:max(0, m.pos-1)], m.value[m.pos:]...) m.value = append(m.value[:max(0, m.pos-1)], m.value[m.pos:]...)
if m.pos > 0 { if m.pos > 0 {
m.pos-- m.SetCursor(m.pos - 1)
} }
} }
case tea.KeyLeft: case tea.KeyLeft:
@ -363,7 +361,7 @@ func Update(msg tea.Msg, m Model) (Model, tea.Cmd) {
break break
} }
if m.pos > 0 { if m.pos > 0 {
m.pos-- m.SetCursor(m.pos - 1)
} }
case tea.KeyRight: case tea.KeyRight:
if msg.Alt { // alt+right arrow, forward one word if msg.Alt { // alt+right arrow, forward one word
@ -371,7 +369,7 @@ func Update(msg tea.Msg, m Model) (Model, tea.Cmd) {
break break
} }
if m.pos < len(m.value) { if m.pos < len(m.value) {
m.pos++ m.SetCursor(m.pos + 1)
} }
case tea.KeyCtrlF: // ^F, forward one character case tea.KeyCtrlF: // ^F, forward one character
fallthrough fallthrough
@ -387,15 +385,14 @@ func Update(msg tea.Msg, m Model) (Model, tea.Cmd) {
m.CursorEnd() m.CursorEnd()
case tea.KeyCtrlK: // ^K, kill text after cursor case tea.KeyCtrlK: // ^K, kill text after cursor
m.value = m.value[:m.pos] m.value = m.value[:m.pos]
m.pos = len(m.value) m.SetCursor(len(m.value))
case tea.KeyCtrlU: // ^U, kill text before cursor case tea.KeyCtrlU: // ^U, kill text before cursor
m.value = m.value[m.pos:] m.value = m.value[m.pos:]
m.pos = 0 m.SetCursor(0)
m.offset = 0 m.offset = 0
case tea.KeyCtrlV: // ^V paste case tea.KeyCtrlV: // ^V paste
m.Paste() m.Paste()
case tea.KeyRune: // input a regular character case tea.KeyRune: // input a regular character
if msg.Alt { if msg.Alt {
if msg.Rune == 'b' { // alt+b, back one word if msg.Rune == 'b' { // alt+b, back one word
m.wordLeft() m.wordLeft()
@ -410,7 +407,7 @@ func Update(msg tea.Msg, m Model) (Model, tea.Cmd) {
// Input a regular character // Input a regular character
if m.CharLimit <= 0 || len(m.value) < m.CharLimit { if m.CharLimit <= 0 || len(m.value) < m.CharLimit {
m.value = append(m.value[:m.pos], append([]rune{msg.Rune}, m.value[m.pos:]...)...) m.value = append(m.value[:m.pos], append([]rune{msg.Rune}, m.value[m.pos:]...)...)
m.pos++ m.SetCursor(m.pos + 1)
} }
} }