From 2e2a7eccb71334640c0be3e8e9de60f62f6902ad Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Fri, 30 Oct 2020 13:01:48 +0100 Subject: [PATCH] Add vertical layout --- layouts/vertical.go | 118 +++++++++++++++++++++++++++++++++++++++++ textinput/textinput.go | 27 +++++++--- 2 files changed, 139 insertions(+), 6 deletions(-) create mode 100644 layouts/vertical.go diff --git a/layouts/vertical.go b/layouts/vertical.go new file mode 100644 index 0000000..425e7c9 --- /dev/null +++ b/layouts/vertical.go @@ -0,0 +1,118 @@ +package layouts + +import ( + tea "github.com/charmbracelet/bubbletea" +) + +// Model is the Bubble Tea model for a vertical layout element. +type Model struct { + Index int + Items []tea.Model + + // Focus indicates whether user focus should be on this component + focus bool +} + +type FocusItem interface { + Focus() tea.Model + Blur() tea.Model +} + +// NewModel creates a new model with default settings. +func NewModel() Model { + return Model{} +} + +// Update is the Tea update loop. +func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { + if !m.focus { + return m, nil + } + + switch msg := msg.(type) { + case tea.KeyMsg: + switch msg.String() { + case "shift+tab", "up": + m.Index-- + if m.Index < 0 { + m.Index = len(m.Items) - 1 + } + m.updateFocus() + + case "tab", "down": + m.Index++ + if m.Index >= len(m.Items) { + m.Index = 0 + } + m.updateFocus() + } + } + + cmd := m.updateItems(msg) + return m, cmd +} + +// View renders the layout in its current state. +func (m Model) View() string { + var view string + + for _, v := range m.Items { + if mi, ok := v.(tea.Model); ok { + view += mi.View() + "\n" + } + } + + return view +} + +func (m *Model) updateFocus() { + for i, v := range m.Items { + if m.Index == i { + if fi, ok := v.(FocusItem); ok { + // new focused item + m.Items[i] = fi.Focus() + } + } else { + if fi, ok := v.(FocusItem); ok { + m.Items[i] = fi.Blur() + } + } + } +} + +// Pass messages and models through to text input components. Only text inputs +// with Focus() set will respond, so it's safe to simply update all of them +// here without any further logic. +func (m *Model) updateItems(msg tea.Msg) tea.Cmd { + var ( + cmd tea.Cmd + cmds []tea.Cmd + ) + + for i, v := range m.Items { + if mi, ok := v.(tea.Model); ok { + m.Items[i], cmd = mi.Update(msg) + if cmd != nil { + cmds = append(cmds, cmd) + } + } + } + + return tea.Batch(cmds...) +} + +// Focused returns the focus state on the model. +func (m Model) Focused() bool { + return m.focus +} + +// Focus sets the focus state on the model. +func (m *Model) Focus() { + m.focus = true + m.updateFocus() +} + +// Blur removes the focus state on the model. +func (m *Model) Blur() { + m.focus = false +} diff --git a/textinput/textinput.go b/textinput/textinput.go index 9ececea..bde8950 100644 --- a/textinput/textinput.go +++ b/textinput/textinput.go @@ -67,6 +67,7 @@ type Model struct { Cursor string BlinkSpeed time.Duration TextColor string + FocusedTextColor string BackgroundColor string PlaceholderColor string CursorColor string @@ -114,6 +115,7 @@ func NewModel() Model { Placeholder: "", BlinkSpeed: defaultBlinkSpeed, TextColor: "", + FocusedTextColor: "205", PlaceholderColor: "240", CursorColor: "", EchoCharacter: '*', @@ -182,15 +184,19 @@ func (m Model) Focused() bool { } // Focus sets the focus state on the model. -func (m *Model) Focus() { +func (m Model) Focus() tea.Model { m.focus = true m.blink = m.cursorMode == cursorHide // show the cursor unless we've explicitly hidden it + + return m } // Blur removes the focus state on the model. -func (m *Model) Blur() { +func (m Model) Blur() tea.Model { m.focus = false m.blink = true + + return m } // Reset sets the input to its default state with no input. Returns whether @@ -450,8 +456,12 @@ func (m Model) echoTransform(v string) string { } } +func (m Model) Init() tea.Cmd { + return Blink +} + // Update is the Bubble Tea update loop. -func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { +func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if !m.focus { m.blink = true return m, nil @@ -560,9 +570,14 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { // View renders the textinput in its current state. func (m Model) View() string { + prompt := termenv.String(m.Prompt) + if m.focus { + prompt = prompt.Foreground(color(m.FocusedTextColor)) + } + // Placeholder text if len(m.value) == 0 && m.Placeholder != "" { - return m.placeholderView() + return prompt.String() + m.placeholderView() } value := m.value[m.offset:m.offsetRight] @@ -590,7 +605,7 @@ func (m Model) View() string { ) } - return m.Prompt + v + return prompt.String() + v } // placeholderView returns the prompt and placeholder view, if any. @@ -610,7 +625,7 @@ func (m Model) placeholderView() string { // The rest of the placeholder text v += m.colorPlaceholder(p[1:]) - return m.Prompt + v + return v } // cursorView styles the cursor.