diff --git a/internal/events/ui.go b/internal/events/ui.go index 43e4834..a43b326 100644 --- a/internal/events/ui.go +++ b/internal/events/ui.go @@ -1,6 +1,8 @@ package events import ( + tea "charm.land/bubbletea/v2" + nm "github.com/thecentinol/tuwi/internal/networkmanager" ) @@ -12,3 +14,21 @@ type PasswordResultMsg struct { Cancelled bool Password string } + +type OpenKeybindsModalMsg struct{} + +type CloseKeybindsModalMsg struct{} + +// returns OpenKeybindsModalMsg{} +func OpenKeybindsModal() tea.Cmd { + return func() tea.Msg { + return OpenKeybindsModalMsg{} + } +} + +// returns CloseKeybindsModalMsg{} +func CloseKeybindsModal() tea.Cmd { + return func() tea.Msg { + return CloseKeybindsModalMsg{} + } +} diff --git a/internal/keybindings/keybindings.go b/internal/keybindings/keybindings.go index 9108249..c94d638 100644 --- a/internal/keybindings/keybindings.go +++ b/internal/keybindings/keybindings.go @@ -1,45 +1,65 @@ package keybindings -import ( - "charm.land/bubbles/v2/key" -) - const ( - // ViewNames's + global = "global" + + // ViewName + globalView = global wifiSavedView = "wifi-saved" wifiAvailableView = "wifi-available" errModalView = "error-modal" passwordView = "password-modal" - // Groups - globalGroup = "global" - wifiGroup = "wifi" - tableGroup = "table" - errModalGroup = "error" - passwordGroup = "password" + // Group + globalGroup = global + wifiSavedGroup = "saved connections" + wifiAvailableGroup = "available networks" + navGroup = "navigation" + errModalGroup = "error modal" + passwordGroup = "password modal" + + // Keys + keyEnter = "enter" + keyEsc = "esc" ) func DefaultKeybindings() Keybindings { return Keybindings{ // Global FocusedWifiSaved: Binding{ - ViewName: "", + ViewName: globalView, Group: globalGroup, Keys: []string{"1"}, - HelpKey: "", + HelpKey: "1", HelpDescription: "", Description: "Switch focus to saved wifi connections pane", }, FocusedWifiAvailable: Binding{ - ViewName: "", + ViewName: globalView, Group: globalGroup, Keys: []string{"2"}, - HelpKey: "", + HelpKey: "2", HelpDescription: "", Description: "Switch focus to available wifi networks pane", }, + Cancel: Binding{ + ViewName: globalView, + Group: globalGroup, + Keys: []string{keyEsc}, + HelpKey: keyEsc, + HelpDescription: "cancel", + Description: "Cancel", + }, + OpenKeybindingsModal: Binding{ + ViewName: globalView, + Group: globalGroup, + Keys: []string{"?"}, + HelpKey: "?", + HelpDescription: "keybindings", + Description: "Open keybindings menu", + }, Quit: Binding{ - ViewName: "", + ViewName: globalView, Group: globalGroup, Keys: []string{"q", "ctrl+c"}, HelpKey: "q", @@ -47,93 +67,94 @@ func DefaultKeybindings() Keybindings { Description: "Quit the application", }, - // WiFi + // WiFi saved WifiConnectSaved: Binding{ ViewName: wifiSavedView, - Group: wifiGroup, - Keys: []string{"enter"}, - HelpKey: "Enter", + Group: wifiSavedGroup, + Keys: []string{keyEnter}, + HelpKey: keyEnter, HelpDescription: "connect", - Description: "Connect to a saved wifi connection", + Description: "Connect", }, WifiDisconnect: Binding{ ViewName: wifiSavedView, - Group: wifiGroup, + Group: wifiSavedGroup, Keys: []string{"d"}, HelpKey: "d", - HelpDescription: "diconnect", - Description: "Disconnect from current wifi network", + HelpDescription: "disconnect", + Description: "Disconnect from current WiFi network", }, WifiForget: Binding{ ViewName: wifiSavedView, - Group: wifiGroup, + Group: wifiSavedGroup, Keys: []string{"f"}, HelpKey: "f", HelpDescription: "forget", - Description: "Remove the selected wifi connection", + Description: "Remove the selected WiFi connection", }, WifiAutoConnect: Binding{ ViewName: wifiSavedView, - Group: wifiGroup, + Group: wifiSavedGroup, Keys: []string{"a"}, HelpKey: "a", - HelpDescription: "toggle auto-connect", - Description: "Toggle auto-connect state for the selected wifi connection", + HelpDescription: "auto-connect", + Description: "Toggle auto-connect on/off for the selected WiFi connection", }, + + // WiFi available WifiScan: Binding{ ViewName: wifiAvailableView, - Group: wifiGroup, + Group: wifiAvailableGroup, Keys: []string{"s"}, HelpKey: "s", HelpDescription: "Scan", - Description: "Scan for nearby wifi networks", + Description: "Scan for nearby WiFi networks", }, WifiConnectAvailable: Binding{ ViewName: wifiAvailableView, - Group: wifiGroup, - Keys: []string{"enter"}, - HelpKey: "enter", + Group: wifiAvailableGroup, + Keys: []string{keyEnter}, + HelpKey: keyEnter, HelpDescription: "connect", - Description: "Connect to a new wifi network", + Description: "Connect", }, - // table navigation - // ViewName is left nil because multiple models will use a table. - LineUp: Binding{ - Group: tableGroup, + // Navigation + Up: Binding{ + Group: navGroup, Keys: []string{"k", "up"}, HelpKey: "↑", - HelpDescription: "move up", + HelpDescription: "up", Description: "Move up by one line", }, - LineDown: Binding{ - Group: tableGroup, + Down: Binding{ + Group: navGroup, Keys: []string{"j", "down"}, HelpKey: "", - HelpDescription: "move down", + HelpDescription: "down", Description: "Move down by one line", }, GotoTop: Binding{ - Group: tableGroup, + Group: navGroup, Keys: []string{"t"}, HelpKey: "t", - HelpDescription: "move to top", - Description: "Go to the top of the table", + HelpDescription: "top", + Description: "Move/Scroll to top", }, GotoBottom: Binding{ - Group: tableGroup, + Group: navGroup, Keys: []string{"b"}, HelpKey: "b", - HelpDescription: "move to bottom", - Description: "Go to the bottom of the table", + HelpDescription: "bottom", + Description: "Move/Scroll to bottom", }, // Error modal ErrorDismiss: Binding{ ViewName: errModalView, Group: errModalGroup, - Keys: []string{"esc", "enter"}, - HelpKey: "esc", + Keys: []string{keyEsc, keyEnter}, + HelpKey: keyEsc, HelpDescription: "dismiss", Description: "Dismiss the error modal", }, @@ -145,24 +166,16 @@ func DefaultKeybindings() Keybindings { Keys: []string{"ctrl+t"}, HelpKey: "ctrl+t", HelpDescription: "hide/show password", - Description: "Toggle the password inputs hidden state", + Description: "Toggle the password input's hidden state", }, PasswordSubmit: Binding{ ViewName: passwordView, Group: passwordGroup, - Keys: []string{"enter"}, - HelpKey: "enter", + Keys: []string{keyEnter}, + HelpKey: keyEnter, HelpDescription: "submit", Description: "Submit password", }, - PasswordCancel: Binding{ - ViewName: passwordView, - Group: passwordGroup, - Keys: []string{"esc"}, - HelpKey: "esc", - HelpDescription: "cancel", - Description: "Cancel", - }, } } @@ -173,6 +186,8 @@ type Keybindings struct { // Global FocusedWifiSaved Binding FocusedWifiAvailable Binding + Cancel Binding + OpenKeybindingsModal Binding // NOTE: Cancel is used to close the modal Quit Binding // WiFi @@ -183,9 +198,9 @@ type Keybindings struct { WifiScan Binding WifiConnectAvailable Binding - // Table navigation - LineUp Binding - LineDown Binding + // Navigation + Up Binding + Down Binding GotoTop Binding GotoBottom Binding @@ -195,7 +210,6 @@ type Keybindings struct { // Password modal PasswordVisibility Binding PasswordSubmit Binding - PasswordCancel Binding } type Binding struct { @@ -211,30 +225,7 @@ type Binding struct { Description string // the longer full description displayed in the keybindings modal } -// converts the keybind to a bubbles keybinding -func (b Binding) ToBubbles() key.Binding { - return key.NewBinding( - key.WithKeys(b.Keys...), - key.WithHelp(b.HelpKey, b.HelpDescription), - ) -} - -// returns all keybindings -func (k Keybindings) All() []Binding { - return []Binding{ - k.FocusedWifiSaved, k.FocusedWifiAvailable, k.Quit, k.WifiConnectSaved, k.WifiDisconnect, - k.WifiForget, k.WifiAutoConnect, k.WifiScan, k.WifiConnectAvailable, k.LineUp, k.LineDown, - k.GotoTop, k.GotoBottom, k.ErrorDismiss, k.PasswordSubmit, k.PasswordCancel, - } -} - -// get keybindings by ViewName -func (k Keybindings) ByView(viewName string) []Binding { - var binds []Binding - for _, b := range k.All() { - if b.ViewName == viewName { - binds = append(binds, b) - } - } - return binds +type BindingGroup struct { + Title string + Bindings []Binding } diff --git a/internal/keybindings/utils.go b/internal/keybindings/utils.go new file mode 100644 index 0000000..8f06a99 --- /dev/null +++ b/internal/keybindings/utils.go @@ -0,0 +1,118 @@ +package keybindings + +import ( + "charm.land/bubbles/v2/key" +) + +// converts a single keybinding to a bubbles keybinding +func (b Binding) ToBubbles() key.Binding { + return key.NewBinding( + key.WithKeys(b.Keys...), + key.WithHelp(b.HelpKey, b.HelpDescription), + ) +} + +// converts multiple keybindings to a bubbles keybindings +func ToBubblesBatch(keys []Binding) []key.Binding { + binds := make([]key.Binding, 0, len(keys)) + + for _, k := range keys { + bind := key.NewBinding( + key.WithKeys(k.Keys...), + key.WithHelp(k.HelpKey, k.Description), + ) + binds = append(binds, bind) + } + + return binds +} + +// Returns all keybindings +func (k Keybindings) All() []Binding { + return []Binding{ + k.FocusedWifiSaved, k.FocusedWifiAvailable, k.Cancel, k.Quit, k.WifiConnectSaved, + k.WifiDisconnect, k.WifiForget, k.WifiAutoConnect, k.WifiScan, k.WifiConnectAvailable, + k.Up, k.Down, k.GotoTop, k.GotoBottom, k.OpenKeybindingsModal, k.ErrorDismiss, + k.PasswordVisibility, k.PasswordSubmit, + } +} + +// Get all Keybindings and sort them by Group +func (k Keybindings) AllGrouped() []BindingGroup { + groups := make([]BindingGroup, 0, 6) + var global BindingGroup + var wifiSaved BindingGroup + var wifiAvailable BindingGroup + var nav BindingGroup + var err BindingGroup + var password BindingGroup + + for _, b := range k.All() { + if b.Group == globalGroup { + global = BindingGroup{ + Title: b.Group, + Bindings: k.ByGroup(globalGroup), + } + } + if b.Group == navGroup { + nav = BindingGroup{ + Title: b.Group, + Bindings: k.ByGroup(navGroup), + } + } + if b.Group == wifiSavedGroup { + wifiSaved = BindingGroup{ + Title: b.Group, + Bindings: k.ByGroup(wifiSavedGroup), + } + } + if b.Group == wifiAvailableGroup { + wifiAvailable = BindingGroup{ + Title: b.Group, + Bindings: k.ByGroup(wifiAvailableGroup), + } + } + if b.Group == errModalGroup { + err = BindingGroup{ + Title: b.Group, + Bindings: k.ByGroup(errModalGroup), + } + } + if b.Group == passwordGroup { + password = BindingGroup{ + Title: b.Group, + Bindings: k.ByGroup(passwordGroup), + } + } + } + + groups = append(groups, global) + groups = append(groups, nav) + groups = append(groups, wifiSaved) + groups = append(groups, wifiAvailable) + groups = append(groups, err) + groups = append(groups, password) + return groups +} + +// Get keybindings by ViewName +func (k Keybindings) ByView(viewName string) []Binding { + var binds []Binding + for _, b := range k.All() { + if b.ViewName == viewName { + binds = append(binds, b) + } + } + return binds +} + +// Get keybindings by Group +func (k Keybindings) ByGroup(group string) []Binding { + var binds []Binding + for _, kb := range k.All() { + if kb.Group == group { + binds = append(binds, kb) + } + } + return binds +} diff --git a/internal/ui/components/password_modal.go b/internal/ui/components/password_modal.go index eaddb82..adaebb9 100644 --- a/internal/ui/components/password_modal.go +++ b/internal/ui/components/password_modal.go @@ -75,7 +75,7 @@ func (p PasswordModel) Update(msg tea.Msg) (PasswordModel, tea.Cmd) { password := p.Input.Value() p.Input.Reset() return p, handlePasswordSubmit(password) - case key.Matches(msg, p.keys.PasswordCancel.ToBubbles()): + case key.Matches(msg, p.keys.Cancel.ToBubbles()): p.Input.Reset() return p, handlePasswordClose() } @@ -101,7 +101,7 @@ func (p PasswordModel) HelpView() []key.Binding { help := []key.Binding{ p.keys.PasswordVisibility.ToBubbles(), p.keys.PasswordSubmit.ToBubbles(), - p.keys.PasswordCancel.ToBubbles(), + p.keys.Cancel.ToBubbles(), } return help } diff --git a/internal/ui/components/table.go b/internal/ui/components/table.go index 085cd72..d7fde5e 100644 --- a/internal/ui/components/table.go +++ b/internal/ui/components/table.go @@ -69,9 +69,9 @@ func (t *TableModel) Update(msg tea.Msg) (TableModel, tea.Cmd) { switch msg := msg.(type) { case tea.KeyPressMsg: switch { - case key.Matches(msg, t.keys.LineUp.ToBubbles()): + case key.Matches(msg, t.keys.Up.ToBubbles()): t.table.MoveUp(1) - case key.Matches(msg, t.keys.LineDown.ToBubbles()): + case key.Matches(msg, t.keys.Down.ToBubbles()): t.table.MoveDown(1) case key.Matches(msg, t.keys.GotoTop.ToBubbles()): t.table.GotoTop() @@ -90,8 +90,8 @@ func (t *TableModel) View() tea.View { func (t TableModel) HelpView() []key.Binding { return []key.Binding{ - t.keys.LineUp.ToBubbles(), - t.keys.LineDown.ToBubbles(), + t.keys.Up.ToBubbles(), + t.keys.Down.ToBubbles(), t.keys.GotoTop.ToBubbles(), t.keys.GotoBottom.ToBubbles(), } diff --git a/internal/ui/keybindings_modal/model.go b/internal/ui/keybindings_modal/model.go new file mode 100644 index 0000000..29ae8e5 --- /dev/null +++ b/internal/ui/keybindings_modal/model.go @@ -0,0 +1,159 @@ +package keybindingsmodal + +import ( + "strings" + + "charm.land/bubbles/v2/help" + "charm.land/bubbles/v2/key" + "charm.land/bubbles/v2/viewport" + tea "charm.land/bubbletea/v2" + "charm.land/lipgloss/v2" + + "github.com/thecentinol/tuwi/internal/events" + kb "github.com/thecentinol/tuwi/internal/keybindings" + comp "github.com/thecentinol/tuwi/internal/ui/components" + "github.com/thecentinol/tuwi/internal/ui/theme" +) + +type Model struct { + theme *theme.Theme + vp viewport.Model + + keys kb.Keybindings + help help.Model + + Height, Width int + X, Y int + IsFocused bool +} + +func NewKeybindsModal(theme *theme.Theme, keys kb.Keybindings) Model { + m := Model{ + theme: theme, + vp: viewport.New(), + keys: keys, + help: help.New(), + } + + m.renderContent() + + return m +} + +func (m Model) Init() tea.Cmd { + return nil +} + +func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { + var cmd tea.Cmd + + switch msg := msg.(type) { + case tea.KeyPressMsg: + switch { + case key.Matches(msg, m.keys.Up.ToBubbles()): + m.vp.ScrollUp(3) + + case key.Matches(msg, m.keys.Down.ToBubbles()): + m.vp.ScrollDown(3) + + case key.Matches(msg, m.keys.GotoTop.ToBubbles()): + m.vp.GotoTop() + return m, nil + + case key.Matches(msg, m.keys.GotoBottom.ToBubbles()): + m.vp.GotoBottom() + return m, nil + + case key.Matches(msg, m.keys.Cancel.ToBubbles()): + return m, events.CloseKeybindsModal() + } + } + + m.vp, cmd = m.vp.Update(msg) + return m, cmd +} + +func (m Model) View() tea.View { + container := comp.NewBorderContent( + m.theme.KeybindsModal.Border, + m.theme.KeybindsModal.BorderContent, + m.Height, + ) + + return tea.NewView(container.Render("Keybindings", m.vp.View(), m.Width)) +} + +func (m Model) HelpView() []key.Binding { + // Note: OpenKeybindingsModal is excluded because it is handled + // by the root model's HelpView. + + // Override default help descriptions to provide modal-specific context. + cancel := m.keys.Cancel.ToBubbles() + scrollUp := m.keys.Up.ToBubbles() + scrollDown := m.keys.Down.ToBubbles() + cancel.SetHelp(m.keys.Cancel.HelpKey, "Close") + scrollUp.SetHelp(m.keys.Up.HelpKey, "Scroll up") + scrollDown.SetHelp(m.keys.Down.HelpKey, "Scroll down") + + binds := []key.Binding{ + scrollUp, + scrollDown, + m.keys.GotoTop.ToBubbles(), + m.keys.GotoBottom.ToBubbles(), + cancel, + } + return binds +} + +func (m *Model) SetSize(width, height int) { + m.Width = width + m.Height = height + m.vp.SetWidth(width) + m.vp.SetHeight(height) +} + +func (m *Model) renderContent() { + var builder strings.Builder + + titleStyle := lipgloss.NewStyle(). + Bold(true). + Foreground(m.theme.KeybindsModal.GroupText). + MarginBottom(1) + + groupStyle := lipgloss.NewStyle(). + PaddingLeft(5). + MarginBottom(2) + + groups := m.keys.AllGrouped() + + // styling the bindings + help := m.help + help.Styles.FullKey = lipgloss.NewStyle(). + Foreground(m.theme.KeybindsModal.KeybindsText) + + help.Styles.FullDesc = lipgloss.NewStyle(). + Foreground(m.theme.KeybindsModal.DescriptionText) + + for _, group := range groups { + title := titleStyle.Render("── " + group.Title + " ──") + + bindings := help.FullHelpView([][]key.Binding{ + kb.ToBubblesBatch(group.Bindings), + }) + + section := groupStyle.Render( + lipgloss.JoinVertical( + lipgloss.Left, + title, + bindings, + ), + ) + + // WriteString returns a nil error, the `revive` linter will complain about not handling the + // error's for the below WriteString's. So just like the liberals, we tell em to shut up. + builder.WriteString(section) //nolint:revive + builder.WriteString("\n") //nolint:revive + } + + m.vp.SetContent(builder.String()) +} diff --git a/internal/ui/root/model.go b/internal/ui/root/model.go index 97626bf..4889127 100644 --- a/internal/ui/root/model.go +++ b/internal/ui/root/model.go @@ -11,6 +11,7 @@ import ( "github.com/thecentinol/tuwi/internal/keybindings" nm "github.com/thecentinol/tuwi/internal/networkmanager" comp "github.com/thecentinol/tuwi/internal/ui/components" + kbmodal "github.com/thecentinol/tuwi/internal/ui/keybindings_modal" "github.com/thecentinol/tuwi/internal/ui/theme" wifiui "github.com/thecentinol/tuwi/internal/ui/wifi" wifidomain "github.com/thecentinol/tuwi/internal/wifi" @@ -18,12 +19,12 @@ import ( type Focused string -// make sure these match Keybindings ViewName's const ( - FocusSaved Focused = "wifi-saved" - FocusAvailable Focused = "wifi-available" - FocusPasswordModal Focused = "password-modal" - FocusErrorModal Focused = "error-modal" + FocusWifiSaved Focused = "wifi-saved" + FocusWifiAvailable Focused = "wifi-available" + FocusKeybindingsModal Focused = "keybindings-modal" + FocusPasswordModal Focused = "password-modal" + FocusErrorModal Focused = "error-modal" ) type Model struct { @@ -39,6 +40,9 @@ type Model struct { wifiAvailable wifiui.AvailableModel selectedNetwork *nm.AccessPoint + keybindingsModal kbmodal.Model + showKeybindingsModal bool + passwordModal comp.PasswordModel showPasswordModal bool @@ -49,16 +53,18 @@ type Model struct { func NewModel(client *dbus.Client, keys keybindings.Keybindings) Model { th := theme.Default state := &wifidomain.State{} + return Model{ - Client: client, - focus: FocusSaved, - help: help.New(), - wifiSaved: wifiui.NewWifiSavedModel(client, state, keys, &th), - wifiAvailable: wifiui.NewWifiAvailableModel(client, state, keys, &th), - passwordModal: comp.NewPasswordModal(&th, keys), - errorModal: comp.NewErrorModal(&th, keys), - keys: keys, - theme: &th, + Client: client, + focus: FocusWifiSaved, + help: help.New(), + wifiSaved: wifiui.NewWifiSavedModel(client, state, keys, &th), + wifiAvailable: wifiui.NewWifiAvailableModel(client, state, keys, &th), + keybindingsModal: kbmodal.NewKeybindsModal(&th, keys), + passwordModal: comp.NewPasswordModal(&th, keys), + errorModal: comp.NewErrorModal(&th, keys), + keys: keys, + theme: &th, } } @@ -83,6 +89,15 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.height = msg.Height m.sizeComponents() + case events.OpenKeybindsModalMsg: + m.showKeybindingsModal = true + return m, m.keybindingsModal.Init() + + case events.CloseKeybindsModalMsg: + m.showKeybindingsModal = false + // TODO: get previous focus before the keybindingsModal was opened and switch back to that. + m.focus = FocusWifiAvailable + case events.ShowPasswordModalMsg: m.showPasswordModal = true m.selectedNetwork = msg.Network @@ -125,10 +140,15 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.errorModal, cmd = m.errorModal.Update(msg) cmds = append(cmds, cmd) } + if m.showKeybindingsModal { + m.keybindingsModal, cmd = m.keybindingsModal.Update(msg) + cmds = append(cmds, cmd) + } } - m.wifiSaved.IsFocused = m.focus == FocusSaved - m.wifiAvailable.IsFocused = m.focus == FocusAvailable + m.wifiSaved.IsFocused = m.focus == FocusWifiSaved + m.wifiAvailable.IsFocused = m.focus == FocusWifiAvailable + m.keybindingsModal.IsFocused = m.focus == FocusKeybindingsModal return m, tea.Batch(cmds...) } @@ -137,6 +157,7 @@ func (m Model) View() tea.View { var view tea.View wifiSavedView := m.wifiSaved.View().Content wifiAvailableView := m.wifiAvailable.View().Content + kbView := m.keybindingsModal.View().Content helpView := m.HelpView() passwordView := m.passwordModal.View().Content errorModalView := m.errorModal.View().Content @@ -175,6 +196,15 @@ func (m Model) View() tea.View { layers = append(layers, errorModal) } + if m.showKeybindingsModal && !m.showErrorModal && !m.showPasswordModal { + kbModal := lipgloss.NewLayer(kbView). + Z(1). + X(m.keybindingsModal.X). + Y(m.keybindingsModal.Y) + + layers = append(layers, kbModal) + } + comp := lipgloss.NewCompositor(layers...) view.SetContent(comp.Render()) view.BackgroundColor = m.theme.BG @@ -184,7 +214,7 @@ func (m Model) View() tea.View { func (m *Model) sizeComponents() { halfWidth := m.width / 2 - // halfHeight := m.height / 2 + halfHeight := m.height / 2 helpHeight := 2 modalWidth := m.width / 3 tableHeight := m.height - helpHeight - 2 @@ -199,6 +229,11 @@ func (m *Model) sizeComponents() { m.wifiAvailable.Table.SetWidth(halfWidth) m.wifiAvailable.Table.SetHeight(tableHeight) + // Keybindings modal + m.keybindingsModal.SetSize(halfWidth, (m.height*3)/4) + m.keybindingsModal.X = halfWidth - (m.keybindingsModal.Width / 2) + m.keybindingsModal.Y = halfHeight - (m.keybindingsModal.Height / 2) + // Password Modal iconWidth := 2 m.passwordModal.Width = modalWidth @@ -232,11 +267,18 @@ func (m *Model) handleKeyPressMsg(msg tea.KeyPressMsg) tea.Cmd { case m.showErrorModal: m.errorModal, cmd = m.errorModal.Update(msg) + case key.Matches(msg, m.keys.OpenKeybindingsModal.ToBubbles()): + m.focus = FocusKeybindingsModal + return events.OpenKeybindsModal() + + case m.showKeybindingsModal: + m.keybindingsModal, cmd = m.keybindingsModal.Update(msg) + case key.Matches(msg, m.keys.FocusedWifiSaved.ToBubbles()): - m.focus = FocusSaved + m.focus = FocusWifiSaved case key.Matches(msg, m.keys.FocusedWifiAvailable.ToBubbles()): - m.focus = FocusAvailable + m.focus = FocusWifiAvailable case m.wifiSaved.IsFocused: m.wifiSaved, cmd = m.wifiSaved.Update(msg) @@ -254,14 +296,22 @@ func (m Model) HelpView() string { if m.showPasswordModal { help = append(help, m.passwordModal.HelpView()...) } - if m.focus == FocusSaved { + if m.showKeybindingsModal { + help = append(help, m.keybindingsModal.HelpView()...) + } + if m.focus == FocusWifiSaved { help = append(help, m.wifiSaved.HelpView()...) } - if m.focus == FocusAvailable && !m.showPasswordModal { + if m.focus == FocusWifiAvailable && !m.showPasswordModal { help = append(help, m.wifiAvailable.HelpView()...) } help = append(help, m.keys.Quit.ToBubbles()) + + if !m.showKeybindingsModal { + help = append(help, m.keys.OpenKeybindingsModal.ToBubbles()) + } + render := m.help render.Styles.Ellipsis = lipgloss.NewStyle(). Foreground(m.theme.Help.Ellipsis) diff --git a/internal/ui/theme/default.go b/internal/ui/theme/default.go index 334ebc4..7fc24ef 100644 --- a/internal/ui/theme/default.go +++ b/internal/ui/theme/default.go @@ -34,6 +34,13 @@ var Default = Theme{ PromptBlurred: lipgloss.Color("238"), Cursor: lipgloss.White, }, + KeybindsModal: KeybindsModalTheme{ + Border: lipgloss.Green, + BorderContent: lipgloss.Green, + GroupText: lipgloss.Green, + KeybindsText: lipgloss.Color("#038B5A"), + DescriptionText: lipgloss.White, + }, Help: HelpTheme{ Ellipsis: nil, ShortKey: lipgloss.White, diff --git a/internal/ui/theme/rose_pine.go b/internal/ui/theme/rose_pine.go index 134c6b7..0e51176 100644 --- a/internal/ui/theme/rose_pine.go +++ b/internal/ui/theme/rose_pine.go @@ -77,6 +77,13 @@ var RosePine = Theme{ Cursor: lipgloss.Color(foam), }, + KeybindsModal: KeybindsModalTheme{ + Border: lipgloss.Color(foam), + BorderContent: lipgloss.Color(foam), + GroupText: lipgloss.Color(foam), + KeybindsText: lipgloss.Color(gold), + DescriptionText: lipgloss.White, + }, Help: HelpTheme{ Ellipsis: nil, ShortKey: lipgloss.Color(gold), @@ -128,6 +135,13 @@ var RosePineMoon = Theme{ Cursor: lipgloss.Color(moonFoam), }, + KeybindsModal: KeybindsModalTheme{ + Border: lipgloss.Color(moonFoam), + BorderContent: lipgloss.Color(moonFoam), + GroupText: lipgloss.Color(moonFoam), + KeybindsText: lipgloss.Color(moonGold), + DescriptionText: lipgloss.White, + }, Help: HelpTheme{ Ellipsis: nil, ShortKey: lipgloss.Color(moonGold), diff --git a/internal/ui/theme/theme.go b/internal/ui/theme/theme.go index b9c4387..5caed68 100644 --- a/internal/ui/theme/theme.go +++ b/internal/ui/theme/theme.go @@ -13,11 +13,12 @@ type Theme struct { Success color.Color - Table TableTheme - Error ErrorTheme - Password PasswordTheme - Input InputTheme - Help HelpTheme + Table TableTheme + Error ErrorTheme + Password PasswordTheme + Input InputTheme + KeybindsModal KeybindsModalTheme + Help HelpTheme } type TableTheme struct { @@ -55,6 +56,14 @@ type InputTheme struct { Cursor color.Color } +type KeybindsModalTheme struct { + Border, + BorderContent, + GroupText, + KeybindsText, + DescriptionText color.Color +} + // the help view type HelpTheme struct { Ellipsis,