build: update tview and tcell, with titles and footers as widgets styled by ui.Label

This commit is contained in:
ayn2op 2026-10-18 16:25:03 -04:00
parent 23b45b9c16
commit f25149a05e
13 changed files with 43 additions and 29 deletions

6
go.mod
View file

@ -16,10 +16,10 @@ require (
github.com/alecthomas/chroma/v2 v2.27.0 github.com/alecthomas/chroma/v2 v2.27.0
github.com/ayn2op/arikawa/v3 v3.0.0-20261016043052-54a68a54e42e github.com/ayn2op/arikawa/v3 v3.0.0-20261016043052-54a68a54e42e
github.com/ayn2op/ningen/v3 v3.0.1-0.20261014035153-5e43590a1426 github.com/ayn2op/ningen/v3 v3.0.1-0.20261014035153-5e43590a1426
github.com/ayn2op/tview v0.0.0-20261017205514-f83a3769b18a github.com/ayn2op/tview v0.0.0-20261018225919-d32257af60fc
github.com/bogdanfinn/fhttp v0.6.9 github.com/bogdanfinn/fhttp v0.6.9
github.com/bogdanfinn/tls-client v1.16.0 github.com/bogdanfinn/tls-client v1.16.0
github.com/gdamore/tcell/v3 v3.6.0 github.com/gdamore/tcell/v3 v3.6.1-0.20261018194208-6ad9db1fd512
github.com/gen2brain/malgo v0.11.26 github.com/gen2brain/malgo v0.11.26
github.com/gorilla/websocket v1.5.3 github.com/gorilla/websocket v1.5.3
github.com/mattn/go-shellwords v1.0.16 github.com/mattn/go-shellwords v1.0.16
@ -51,7 +51,7 @@ require (
github.com/dchest/jsmin v1.0.0 // indirect github.com/dchest/jsmin v1.0.0 // indirect
github.com/disgoorg/godave v0.3.0 // indirect github.com/disgoorg/godave v0.3.0 // indirect
github.com/dlclark/regexp2/v2 v2.9.1 // indirect github.com/dlclark/regexp2/v2 v2.9.1 // indirect
github.com/ebitengine/purego v0.11.1 // indirect github.com/ebitengine/purego v0.11.2 // indirect
github.com/gdamore/encoding v1.0.1 // indirect github.com/gdamore/encoding v1.0.1 // indirect
github.com/godbus/dbus/v5 v5.2.4 // indirect github.com/godbus/dbus/v5 v5.2.4 // indirect
github.com/gorilla/schema v1.4.1 // indirect github.com/gorilla/schema v1.4.1 // indirect

12
go.sum
View file

@ -16,8 +16,8 @@ github.com/ayn2op/fhttp v0.6.10-0.20261016015330-2d5e34c6b935 h1:oVxkY1/nVsVQ8Cn
github.com/ayn2op/fhttp v0.6.10-0.20261016015330-2d5e34c6b935/go.mod h1:7phoj3f/q54WuiTnCTlAhEoaVcdlruKkSzCSD47LzHk= github.com/ayn2op/fhttp v0.6.10-0.20261016015330-2d5e34c6b935/go.mod h1:7phoj3f/q54WuiTnCTlAhEoaVcdlruKkSzCSD47LzHk=
github.com/ayn2op/ningen/v3 v3.0.1-0.20261014035153-5e43590a1426 h1:0ndRWpAiSSgR0z9b7K+pwMlCHEk3VzQeS5ls+zWtbr0= github.com/ayn2op/ningen/v3 v3.0.1-0.20261014035153-5e43590a1426 h1:0ndRWpAiSSgR0z9b7K+pwMlCHEk3VzQeS5ls+zWtbr0=
github.com/ayn2op/ningen/v3 v3.0.1-0.20261014035153-5e43590a1426/go.mod h1:4S2B9frwrjELeAldoqPgasA/VfnOySgRlektwYLuVks= github.com/ayn2op/ningen/v3 v3.0.1-0.20261014035153-5e43590a1426/go.mod h1:4S2B9frwrjELeAldoqPgasA/VfnOySgRlektwYLuVks=
github.com/ayn2op/tview v0.0.0-20261017205514-f83a3769b18a h1:gJyFlYT+igFchVDNUMapJdy8iM0cVA0/LxOqZ8n6FZc= github.com/ayn2op/tview v0.0.0-20261018225919-d32257af60fc h1:Siy7K+UEnISG7BVsrTxRqL81RGcU9H3NOvn11tiHfQ8=
github.com/ayn2op/tview v0.0.0-20261017205514-f83a3769b18a/go.mod h1:kn8Up2Tk2vhDUtW6H9nqAvRk9hDVyCzk18CCAo2qkjA= github.com/ayn2op/tview v0.0.0-20261018225919-d32257af60fc/go.mod h1:kn8Up2Tk2vhDUtW6H9nqAvRk9hDVyCzk18CCAo2qkjA=
github.com/bdandy/go-errors v1.2.2 h1:WdFv/oukjTJCLa79UfkGmwX7ZxONAihKu4V0mLIs11Q= github.com/bdandy/go-errors v1.2.2 h1:WdFv/oukjTJCLa79UfkGmwX7ZxONAihKu4V0mLIs11Q=
github.com/bdandy/go-errors v1.2.2/go.mod h1:NkYHl4Fey9oRRdbB1CoC6e84tuqQHiqrOcZpqFEkBxM= github.com/bdandy/go-errors v1.2.2/go.mod h1:NkYHl4Fey9oRRdbB1CoC6e84tuqQHiqrOcZpqFEkBxM=
github.com/bdandy/go-socks4 v1.2.3 h1:Q6Y2heY1GRjCtHbmlKfnwrKVU/k81LS8mRGLRlmDlic= github.com/bdandy/go-socks4 v1.2.3 h1:Q6Y2heY1GRjCtHbmlKfnwrKVU/k81LS8mRGLRlmDlic=
@ -44,12 +44,12 @@ github.com/disgoorg/godave v0.3.0 h1:37F3ZuiMd8/EXeoCtTeE8iP2eT2nWsfEa4/ITwoKfe4
github.com/disgoorg/godave v0.3.0/go.mod h1:OreAC3hpabr39bMVA+jwOVDq1EUPXH5A0XUBiZaDI1Y= github.com/disgoorg/godave v0.3.0/go.mod h1:OreAC3hpabr39bMVA+jwOVDq1EUPXH5A0XUBiZaDI1Y=
github.com/dlclark/regexp2/v2 v2.9.1 h1:q6bZW/NN9VodqMjYXpuic2lZxdHPfFTzGS+MJxIJmKQ= github.com/dlclark/regexp2/v2 v2.9.1 h1:q6bZW/NN9VodqMjYXpuic2lZxdHPfFTzGS+MJxIJmKQ=
github.com/dlclark/regexp2/v2 v2.9.1/go.mod h1:avUrQvPaLz2DrFNHJF0taWAFFX2C1GMSSoeiqFjcBmU= github.com/dlclark/regexp2/v2 v2.9.1/go.mod h1:avUrQvPaLz2DrFNHJF0taWAFFX2C1GMSSoeiqFjcBmU=
github.com/ebitengine/purego v0.11.1 h1:2zpWRSQNVKN4eKsKO9eM1ILDgWfYMY9GwqRmK6XeQ/0= github.com/ebitengine/purego v0.11.2 h1:WWiTx8rD8zYdOwSj8O/NiNbsOvSAgJMTlyV1XlbUSeU=
github.com/ebitengine/purego v0.11.1/go.mod h1:DCHPP08djqhNSoTfImcnHYQRZmd0qhakvrozqaEYhGQ= github.com/ebitengine/purego v0.11.2/go.mod h1:DCHPP08djqhNSoTfImcnHYQRZmd0qhakvrozqaEYhGQ=
github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uhw= github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uhw=
github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo= github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo=
github.com/gdamore/tcell/v3 v3.6.0 h1:jW20qS6Tm38KVDqLHq9KWSvyjNl00FbhbD8rIAnHBws= github.com/gdamore/tcell/v3 v3.6.1-0.20261018194208-6ad9db1fd512 h1:cC1srpdzj/kwTj+kJ3sORrrd5C2Quu7scDy6oLxgBIg=
github.com/gdamore/tcell/v3 v3.6.0/go.mod h1:CBZxkl2T+XxEyuBfOh4zaahqm27dngvhfBfCCZxJsgc= github.com/gdamore/tcell/v3 v3.6.1-0.20261018194208-6ad9db1fd512/go.mod h1:TMjaFRfs5NCU3o1aiuxoRHdEdyixCy1mdGcHdvj7lvc=
github.com/gen2brain/malgo v0.11.26 h1:k5WcPIKw1bbJAbPqrvNPt7nehPLoaPNcOFde2+eruiM= github.com/gen2brain/malgo v0.11.26 h1:k5WcPIKw1bbJAbPqrvNPt7nehPLoaPNcOFde2+eruiM=
github.com/gen2brain/malgo v0.11.26/go.mod h1:xLVG3ROA33Bzol1quF3e4ehqcFuqh8QK4B8T6LQUs/M= github.com/gen2brain/malgo v0.11.26/go.mod h1:xLVG3ROA33Bzol1quF3e4ehqcFuqh8QK4B8T6LQUs/M=
github.com/godbus/dbus/v5 v5.2.4 h1:F2gUCy++NMIxGmo9J+z5le/+ZC8W1OgeR51P4i9ZB/U= github.com/godbus/dbus/v5 v5.2.4 h1:F2gUCy++NMIxGmo9J+z5le/+ZC8W1OgeR51P4i9ZB/U=

View file

@ -155,7 +155,10 @@ func (m *Model) box(focused bool) box.Widget {
Focused(focused && !m.disabled). Focused(focused && !m.disabled).
OnInput(func(text string, cursor int) tview.Msg { return editMsg{text, cursor} }). OnInput(func(text string, cursor int) tview.Msg { return editMsg{text, cursor} }).
Height(layout.Shrink.Max(m.cfg.Composer.MaxHeight)) Height(layout.Shrink.Max(m.cfg.Composer.MaxHeight))
return ui.Box(text, m.cfg, focused).Title(m.title()).Footer(m.footer()).Height(layout.Shrink) return ui.Box(text, m.cfg, focused).
Title(ui.Label(m.title(), m.cfg.Theme.Title, focused)).
Footer(ui.Label(m.footer(), m.cfg.Theme.Footer, focused)).
Height(layout.Shrink)
} }
// title returns what the composer is doing: editing, replying, or nothing. // title returns what the composer is doing: editing, replying, or nothing.

View file

@ -72,8 +72,8 @@ func (m Model) View(focused bool) tview.Widget {
onChange, onChange,
onSelect, onSelect,
). ).
Title("Guilds"). Title(ui.Label("Guilds", m.cfg.Theme.Title, focused)).
Footer(m.voiceFooter()) Footer(ui.Label(m.voiceFooter(), m.cfg.Theme.Footer, focused))
} }
func (m *Model) reset() *tree.Node { func (m *Model) reset() *tree.Node {

View file

@ -10,6 +10,7 @@ import (
"github.com/ayn2op/arikawa/v3/gateway" "github.com/ayn2op/arikawa/v3/gateway"
"github.com/ayn2op/arikawa/v3/state" "github.com/ayn2op/arikawa/v3/state"
"github.com/ayn2op/discordo/internal/config" "github.com/ayn2op/discordo/internal/config"
"github.com/ayn2op/discordo/internal/ui"
uitree "github.com/ayn2op/discordo/internal/ui/tree" uitree "github.com/ayn2op/discordo/internal/ui/tree"
"github.com/ayn2op/ningen/v3" "github.com/ayn2op/ningen/v3"
"github.com/ayn2op/tview" "github.com/ayn2op/tview"
@ -99,7 +100,7 @@ func (m Model) View(focused bool) tview.Widget {
onChange, onChange,
onSelect, onSelect,
). ).
Title("Members") Title(ui.Label("Members", m.cfg.Theme.Title, focused))
} }
// setCurrentNode selects node and scrolls to it. // setCurrentNode selects node and scrolls to it.

View file

@ -35,7 +35,7 @@ func (m Model) View() tview.Widget {
list, width = list.Width(layout.Fixed(max(int(cfg.MinWidth), m.maxDisplayWidth()))), layout.Shrink list, width = list.Width(layout.Fixed(max(int(cfg.MinWidth), m.maxDisplayWidth()))), layout.Shrink
} }
return ui.Box(list, m.cfg, false).Title("Mentions").BorderSet(set).Width(width).Height(layout.Shrink) return ui.Box(list, m.cfg, false).Title(ui.Label("Mentions", m.cfg.Theme.Title, false)).BorderSet(set).Width(width).Height(layout.Shrink)
} }
func (Model) Init() tview.Cmd { return nil } func (Model) Init() tview.Cmd { return nil }

View file

@ -83,7 +83,9 @@ func NewModel(cfg *config.Config, state *ningen.State) Model {
// View shows the messages in a box titled with the channel and footed with footer, such as who is typing. // View shows the messages in a box titled with the channel and footed with footer, such as who is typing.
func (m Model) View(focused bool, footer string) tview.Widget { func (m Model) View(focused bool, footer string) tview.Widget {
return ui.Box(m.listView(focused), m.cfg, focused).Title(m.title()).Footer(footer) return ui.Box(m.listView(focused), m.cfg, focused).
Title(ui.Label(m.title(), m.cfg.Theme.Title, focused)).
Footer(ui.Label(footer, m.cfg.Theme.Footer, focused))
} }
// title returns the selected channel and its topic, or "Messages" when none is selected. // title returns the selected channel and its topic, or "Messages" when none is selected.

View file

@ -80,7 +80,7 @@ func (m Model) voiceMessageView(a discord.Attachment) tview.Widget {
return rail[cell], style.Dim(true) return rail[cell], style.Dim(true)
}) })
play := button.New().Label(symbol).OnClick(playMsg(a)) play := button.New(text.New(symbol)).OnClick(playMsg(a))
return row.New(play, waveform, text.New(label)).Spacing(1).Height(layout.Fixed(1)) return row.New(play, waveform, text.New(label)).Spacing(1).Height(layout.Fixed(1))
} }

View file

@ -1,6 +1,7 @@
package login package login
import ( import (
"github.com/ayn2op/tview/layout"
"github.com/ayn2op/tview/tabs" "github.com/ayn2op/tview/tabs"
"github.com/ayn2op/discordo/internal/config" "github.com/ayn2op/discordo/internal/config"
@ -72,6 +73,7 @@ func (m Model) View() tview.Widget {
func (m Model) tabs() tabs.Widget { func (m Model) tabs() tabs.Widget {
cfg := m.cfg.UI.Tabs cfg := m.cfg.UI.Tabs
return tabs.New(m.password.Label(), m.qr.Label(), m.token.Label()). return tabs.New(m.password.Label(), m.qr.Label(), m.token.Label()).
Width(layout.Fill).
Alignment(cfg.Alignment.HorizontalAlignment). Alignment(cfg.Alignment.HorizontalAlignment).
Wrap(cfg.Wrap). Wrap(cfg.Wrap).
Separator(cfg.Separator). Separator(cfg.Separator).

View file

@ -73,8 +73,7 @@ func (m Model) View() tview.Widget {
Focused(m.focus == focusPassword). Focused(m.focus == focusPassword).
OnInput(func(text string, cursor int) tview.Msg { return passwordMsg{text, cursor} }). OnInput(func(text string, cursor int) tview.Msg { return passwordMsg{text, cursor} }).
OnSubmit(focusMsg(focusButton)) OnSubmit(focusMsg(focusButton))
submit := button.New(). submit := button.New(text.New("Login")).
Label("Login").
Width(layout.Fixed(len("Login") + 4)). Width(layout.Fixed(len("Login") + 4)).
Focused(m.focus == focusButton). Focused(m.focus == focusButton).
OnClick(submitMsg{}) OnClick(submitMsg{})

View file

@ -54,8 +54,7 @@ func (m Model) View() tview.Widget {
Focused(!m.buttonFocused). Focused(!m.buttonFocused).
OnInput(func(text string, cursor int) tview.Msg { return tokenMsg{text, cursor} }). OnInput(func(text string, cursor int) tview.Msg { return tokenMsg{text, cursor} }).
OnSubmit(focusButtonMsg{}) OnSubmit(focusButtonMsg{})
submit := button.New(). submit := button.New(text.New("Login")).
Label("Login").
Width(layout.Fixed(len("Login") + 4)). Width(layout.Fixed(len("Login") + 4)).
Focused(m.buttonFocused). Focused(m.buttonFocused).
OnClick(submitMsg{}) OnClick(submitMsg{})

View file

@ -38,7 +38,7 @@ func (m Model) View(onSelect func(picker.Item) tview.Msg, onCancel tview.Msg) tv
OnChange(func(s *picker.SearchState) tview.Msg { return changeMsg(*s) }). OnChange(func(s *picker.SearchState) tview.Msg { return changeMsg(*s) }).
OnSelect(onSelect). OnSelect(onSelect).
OnCancel(onCancel) OnCancel(onCancel)
return ui.Box(p, cfg, true).Title(m.title) return ui.Box(p, cfg, true).Title(ui.Label(m.title, cfg.Theme.Title, true))
} }
func (m Model) Update(msg tview.Msg) (Model, tview.Cmd) { func (m Model) Update(msg tview.Msg) (Model, tview.Cmd) {

View file

@ -10,6 +10,7 @@ import (
"github.com/ayn2op/ningen/v3" "github.com/ayn2op/ningen/v3"
"github.com/ayn2op/tview" "github.com/ayn2op/tview"
"github.com/ayn2op/tview/box" "github.com/ayn2op/tview/box"
"github.com/ayn2op/tview/text"
) )
// Box returns child in a box laid out and styled by cfg, highlighted while focused. // Box returns child in a box laid out and styled by cfg, highlighted while focused.
@ -24,15 +25,22 @@ func Box(child tview.Widget, cfg *config.Config, focused bool) box.Widget {
b = b.Borders(tview.BordersAll) b = b.Borders(tview.BordersAll)
} }
if focused { if focused {
return b.BorderStyle(theme.Border.ActiveStyle.Style). return b.BorderStyle(theme.Border.ActiveStyle.Style).BorderSet(ui.Border.ActiveSet.BorderSet)
BorderSet(ui.Border.ActiveSet.BorderSet).
TitleStyle(theme.Title.ActiveStyle.Style).
FooterStyle(theme.Footer.ActiveStyle.Style)
} }
return b.BorderStyle(theme.Border.NormalStyle.Style). return b.BorderStyle(theme.Border.NormalStyle.Style).BorderSet(ui.Border.NormalSet.BorderSet)
BorderSet(ui.Border.NormalSet.BorderSet). }
TitleStyle(theme.Title.NormalStyle.Style).
FooterStyle(theme.Footer.NormalStyle.Style) // Label returns s on one line in the style of theme for whether its pane is focused, as the title or footer of a box, or nil if s is empty, so the box keeps no row for it.
func Label(s string, theme config.StyleThemeConfig, focused bool) tview.Widget {
if s == "" {
return nil
}
style := theme.NormalStyle.Style
if focused {
style = theme.ActiveStyle.Style
}
return text.New(s).Style(style).Wrapping(text.WrappingNone)
} }
// ChannelToString returns how channel is shown in the UI: an icon for its type and its name, or the recipients' names for a DM. // ChannelToString returns how channel is shown in the UI: an icon for its type and its name, or the recipients' names for a DM.