feat(ui): add root-level help footer and dedupe keymap bindings

This commit is contained in:
ayn2op 2026-03-08 18:25:39 -04:00
parent f0b7dd5e78
commit 07c09508e8
4 changed files with 101 additions and 49 deletions

View file

@ -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},
}
}

View file

@ -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()

View file

@ -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
}

View file

@ -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)) {