discordo/internal/ui/login/form/model.go

89 lines
2.3 KiB
Go

// Package form lays out labeled text inputs above a submit button.
package form
import (
"github.com/ayn2op/tview"
"github.com/ayn2op/tview/button"
"github.com/ayn2op/tview/column"
"github.com/ayn2op/tview/row"
"github.com/ayn2op/tview/text"
"github.com/ayn2op/tview/textinput"
"github.com/gdamore/tcell/v3"
"github.com/rivo/uniseg"
)
// Field is a labeled text input. A non-empty Mask hides what is typed, such as for passwords.
type Field struct {
Label, Mask string
EditState textinput.EditState
}
// Model is a column of fields above a button. Tab and the arrow keys move the focus, Enter in a field moves to the next one, and Enter on the button or clicking it submits.
type Model struct {
fields []Field
button string
focus int
}
// SubmitMsg is returned when the button is pressed.
type SubmitMsg struct{}
type editMsg struct {
index int
action textinput.Action
}
type nextMsg struct{}
func New(button string, fields ...Field) *Model {
return &Model{fields: fields, button: button}
}
// Value returns the text of field index.
func (m *Model) Value(index int) string {
return m.fields[index].EditState.Value()
}
func (m *Model) Init() tview.Cmd { return nil }
func (m *Model) Update(msg tview.Msg) tview.Cmd {
count := len(m.fields) + 1
switch msg := msg.(type) {
case editMsg:
m.fields[msg.index].EditState.Perform(msg.action)
case nextMsg:
m.focus = (m.focus + 1) % count
case tview.KeyMsg:
switch msg.Key() {
case tcell.KeyTab, tcell.KeyDown:
m.focus = (m.focus + 1) % count
case tcell.KeyBacktab, tcell.KeyUp:
m.focus = (m.focus + count - 1) % count
}
}
return nil
}
func (m *Model) View() tview.Element {
labelWidth := 0
for _, f := range m.fields {
labelWidth = max(labelWidth, uniseg.StringWidth(f.Label)+1)
}
layout := column.New().Spacing(1)
for i := range m.fields {
f := &m.fields[i]
input := textinput.New(&f.EditState).
Mask(f.Mask).
Focused(m.focus == i).
OnAction(func(a textinput.Action) tview.Msg { return editMsg{index: i, action: a} }).
OnSubmit(nextMsg{})
layout = layout.Push(row.New(column.New(text.New(f.Label)).Width(tview.Fixed(labelWidth)), input).Height(tview.Fixed(1)))
}
submit := button.New().
Label(m.button).
Width(tview.Fixed(uniseg.StringWidth(m.button) + 4)).
Height(tview.Fixed(1)).
Focused(m.focus == len(m.fields)).
OnClick(SubmitMsg{})
return layout.Push(submit)
}