From 07c09508e896b79dbd58b74746f7324191e66361 Mon Sep 17 00:00:00 2001 From: ayn2op Date: Sun, 8 Mar 2026 18:25:39 -0400 Subject: [PATCH] feat(ui): add root-level help footer and dedupe keymap bindings --- internal/ui/chat/keybinds.go | 4 +- internal/ui/chat/view.go | 33 +---------------- internal/ui/root/keybinds.go | 41 ++++++++++++++++++++ internal/ui/root/view.go | 72 ++++++++++++++++++++++++++++-------- 4 files changed, 101 insertions(+), 49 deletions(-) create mode 100644 internal/ui/root/keybinds.go diff --git a/internal/ui/chat/keybinds.go b/internal/ui/chat/keybinds.go index ef7900a..6c0123e 100644 --- a/internal/ui/chat/keybinds.go +++ b/internal/ui/chat/keybinds.go @@ -52,7 +52,7 @@ func (v *View) baseShortHelp() []keybind.Keybind { if !v.messageInput.GetDisabled() { short = append(short, cfg.FocusMessageInput.Keybind) } - short = append(short, cfg.ToggleGuildsTree.Keybind, cfg.ToggleChannelsPicker.Keybind, cfg.ToggleHelp.Keybind) + short = append(short, cfg.ToggleGuildsTree.Keybind, cfg.ToggleChannelsPicker.Keybind) return short } @@ -66,6 +66,6 @@ func (v *View) baseFullHelp() [][]keybind.Keybind { focus, {cfg.FocusPrevious.Keybind, cfg.FocusNext.Keybind}, {cfg.ToggleGuildsTree.Keybind, cfg.ToggleChannelsPicker.Keybind}, - {cfg.ToggleHelp.Keybind, cfg.Suspend.Keybind, cfg.Logout.Keybind, cfg.Quit.Keybind}, + {cfg.Logout.Keybind}, } } diff --git a/internal/ui/chat/view.go b/internal/ui/chat/view.go index 1a33e54..4d6620c 100644 --- a/internal/ui/chat/view.go +++ b/internal/ui/chat/view.go @@ -9,7 +9,6 @@ import ( "github.com/ayn2op/discordo/internal/config" "github.com/ayn2op/discordo/internal/ui" "github.com/ayn2op/tview" - "github.com/ayn2op/tview/help" "github.com/ayn2op/tview/keybind" "github.com/ayn2op/tview/layers" "github.com/diamondburned/arikawa/v3/discord" @@ -39,7 +38,6 @@ type View struct { messagesList *messagesList messageInput *messageInput channelsPicker *channelsPicker - help *help.Help selectedChannel *discord.Channel selectedChannelMu sync.RWMutex @@ -74,20 +72,6 @@ func NewView(app *tview.Application, cfg *config.Config, token string) *View { v.channelsPicker = newChannelsPicker(cfg, v) v.channelsPicker.SetCancelFunc(v.closePicker) - v.help = help.New() - - styles := help.DefaultStyles() - styles.ShortKeyStyle = cfg.Theme.Help.ShortKeyStyle.Style - styles.ShortDescStyle = cfg.Theme.Help.ShortDescStyle.Style - styles.FullKeyStyle = cfg.Theme.Help.FullKeyStyle.Style - styles.FullDescStyle = cfg.Theme.Help.FullDescStyle.Style - v.help.SetStyles(styles) - - v.help.SetKeyMap(v) - v.help.SetCompactModifiers(cfg.Help.CompactModifiers) - v.help.SetShortSeparator(cfg.Help.Separator) - v.help.SetBorderPadding(0, 0, cfg.Help.Padding[0], cfg.Help.Padding[1]) - v.SetBackgroundLayerStyle(v.cfg.Theme.Dialog.BackgroundStyle.Style) v.buildLayout() return v @@ -122,10 +106,7 @@ func (v *View) buildLayout() { v.rootFlex. SetDirection(tview.FlexRow). - AddItem(v.mainFlex, 0, 1, true). - AddItem(v.help, 1, 0, false) - - v.updateHelpHeight() + AddItem(v.mainFlex, 0, 1, true) v.AddLayer(v.rootFlex, layers.WithName(flexLayerName), layers.WithResize(true), layers.WithVisible(true)) v.AddLayer(v.messageInput.mentionsList, layers.WithName(mentionsListLayerName), layers.WithResize(false), layers.WithVisible(false)) } @@ -238,10 +219,6 @@ func (v *View) HandleEvent(event tcell.Event) tview.Command { case *tview.KeyEvent: redraw := tview.RedrawCommand{} switch { - case keybind.Matches(event, v.cfg.Keybinds.ToggleHelp.Keybind): - v.help.SetShowAll(!v.help.ShowAll()) - v.updateHelpHeight() - return redraw case keybind.Matches(event, v.cfg.Keybinds.FocusGuildsTree.Keybind): v.messageInput.removeMentionsList() v.focusGuildsTree() @@ -321,14 +298,6 @@ func (v *View) consumeLayerCommands(command tview.Command) tview.Command { return tview.BatchCommand(remaining) } -func (v *View) updateHelpHeight() { - height := 1 - if v.help.ShowAll() { - height = max(len(v.help.FullHelpLines(v.FullHelp(), 0)), 1) - } - v.rootFlex.ResizeItem(v.help, height, 0) -} - func (v *View) showConfirmModal(prompt string, buttons []string, onDone func(label string)) { previousFocus := v.app.GetFocus() diff --git a/internal/ui/root/keybinds.go b/internal/ui/root/keybinds.go new file mode 100644 index 0000000..381098a --- /dev/null +++ b/internal/ui/root/keybinds.go @@ -0,0 +1,41 @@ +package root + +import ( + "github.com/ayn2op/tview/help" + "github.com/ayn2op/tview/keybind" +) + +var _ help.KeyMap = (*View)(nil) + +func (v *View) ShortHelp() []keybind.Keybind { + global := []keybind.Keybind{ + v.cfg.Keybinds.ToggleHelp.Keybind, + v.cfg.Keybinds.Suspend.Keybind, + v.cfg.Keybinds.Quit.Keybind, + } + if active := v.activeKeyMap(); active != nil { + short := active.ShortHelp() + return append(short, global...) + } + return global +} + +func (v *View) FullHelp() [][]keybind.Keybind { + global := []keybind.Keybind{ + v.cfg.Keybinds.ToggleHelp.Keybind, + v.cfg.Keybinds.Suspend.Keybind, + v.cfg.Keybinds.Quit.Keybind, + } + if active := v.activeKeyMap(); active != nil { + full := active.FullHelp() + return append(full, global) + } + return [][]keybind.Keybind{global} +} + +func (v *View) activeKeyMap() help.KeyMap { + if keyMap, ok := v.inner.(help.KeyMap); ok { + return keyMap + } + return nil +} diff --git a/internal/ui/root/view.go b/internal/ui/root/view.go index bfd44f9..1ba26de 100644 --- a/internal/ui/root/view.go +++ b/internal/ui/root/view.go @@ -6,6 +6,7 @@ import ( "github.com/ayn2op/discordo/internal/ui/chat" "github.com/ayn2op/discordo/internal/ui/login" "github.com/ayn2op/tview" + "github.com/ayn2op/tview/help" "github.com/ayn2op/tview/keybind" "github.com/gdamore/tcell/v3" ) @@ -13,34 +14,68 @@ import ( const tokenEnvVarKey = "DISCORDO_TOKEN" type View struct { - app *tview.Application - inner tview.Primitive - cfg *config.Config + app *tview.Application + rootFlex *tview.Flex // inner + help + inner tview.Primitive + help *help.Help + + cfg *config.Config } func NewView(cfg *config.Config, app *tview.Application) *View { - return &View{ - app: app, + v := &View{ + app: app, + rootFlex: tview.NewFlex(), + help: help.New(), + cfg: cfg, } + + v.rootFlex.SetDirection(tview.FlexRow) + + styles := help.DefaultStyles() + styles.ShortKeyStyle = cfg.Theme.Help.ShortKeyStyle.Style + styles.ShortDescStyle = cfg.Theme.Help.ShortDescStyle.Style + styles.FullKeyStyle = cfg.Theme.Help.FullKeyStyle.Style + styles.FullDescStyle = cfg.Theme.Help.FullDescStyle.Style + v.help.SetStyles(styles) + + v.help.SetKeyMap(v) + v.help.SetCompactModifiers(cfg.Help.CompactModifiers) + v.help.SetShortSeparator(cfg.Help.Separator) + v.help.SetBorderPadding(0, 0, cfg.Help.Padding[0], cfg.Help.Padding[1]) + v.buildLayout() + return v } func (v *View) showLoginView() tview.Command { v.inner = login.NewForm(v.app, v.cfg) + v.buildLayout() return v.inner.HandleEvent(tview.NewInitEvent()) } func (v *View) showChatView(token string) tview.Command { v.inner = chat.NewView(v.app, v.cfg, token) + v.buildLayout() return v.inner.HandleEvent(tview.NewInitEvent()) } +func (v *View) buildLayout() { + v.rootFlex.Clear() + + content := v.inner + if content == nil { + content = tview.NewBox() + } + v.rootFlex.AddItem(content, 0, 1, true) + v.rootFlex.AddItem(v.help, 1, 0, false) + v.updateHelpHeight() +} + var _ tview.Primitive = (*View)(nil) func (v *View) Draw(screen tcell.Screen) { - if v.inner != nil { - v.inner.Draw(screen) - } + v.rootFlex.Draw(screen) } func (v *View) HandleEvent(event tcell.Event) tview.Command { @@ -68,6 +103,10 @@ func (v *View) HandleEvent(event tcell.Event) tview.Command { case *tview.KeyEvent: switch { + case keybind.Matches(event, v.cfg.Keybinds.ToggleHelp.Keybind): + v.help.SetShowAll(!v.help.ShowAll()) + v.updateHelpHeight() + return tview.RedrawCommand{} case keybind.Matches(event, v.cfg.Keybinds.Suspend.Keybind): v.suspend() return nil @@ -86,17 +125,20 @@ func (v *View) HandleEvent(event tcell.Event) tview.Command { return nil } -func (v *View) GetRect() (int, int, int, int) { - if v.inner != nil { - return v.inner.GetRect() +func (v *View) updateHelpHeight() { + height := 1 + if v.help.ShowAll() { + height = max(len(v.help.FullHelpLines(v.FullHelp(), 0)), 1) } - return 0, 0, 0, 0 + v.rootFlex.ResizeItem(v.help, height, 0) +} + +func (v *View) GetRect() (int, int, int, int) { + return v.rootFlex.GetRect() } func (v *View) SetRect(x int, y int, width int, height int) { - if v.inner != nil { - v.inner.SetRect(x, y, width, height) - } + v.rootFlex.SetRect(x, y, width, height) } func (v *View) Focus(delegate func(p tview.Primitive)) {