refactor(ui): update to tview's layout package and Element methods
This commit is contained in:
parent
5ee830bfaa
commit
31c5d185ca
14 changed files with 92 additions and 37 deletions
2
go.mod
2
go.mod
|
|
@ -15,7 +15,7 @@ require (
|
|||
github.com/andybalholm/brotli v1.2.6
|
||||
github.com/ayn2op/arikawa/v3 v3.0.0-20260929021956-7d0802bf4d7c
|
||||
github.com/ayn2op/ningen/v3 v3.0.1-0.20260929010920-29794d5fa70e
|
||||
github.com/ayn2op/tview v0.0.0-20261001183738-08b3e2ae6452
|
||||
github.com/ayn2op/tview v0.0.0-20261002024202-f7619deb5073
|
||||
github.com/bogdanfinn/fhttp v0.6.9
|
||||
github.com/bogdanfinn/tls-client v1.16.0
|
||||
github.com/gdamore/tcell/v3 v3.5.0
|
||||
|
|
|
|||
4
go.sum
4
go.sum
|
|
@ -14,8 +14,8 @@ github.com/ayn2op/arikawa/v3 v3.0.0-20260929021956-7d0802bf4d7c h1:Qkl3W5RXzUQUR
|
|||
github.com/ayn2op/arikawa/v3 v3.0.0-20260929021956-7d0802bf4d7c/go.mod h1:SHqcr+EmZKeNtVKZybHJn4HNz+ufA1XbeKNSTigBCEE=
|
||||
github.com/ayn2op/ningen/v3 v3.0.1-0.20260929010920-29794d5fa70e h1:QAei/IVEyZV7GcpkpcFUh37+QRqwrt57z7OT3ciHrhI=
|
||||
github.com/ayn2op/ningen/v3 v3.0.1-0.20260929010920-29794d5fa70e/go.mod h1:RsvYiMzi4Si1F7vmml5NCL2DaKIZXkNUQC7QlGTMtCw=
|
||||
github.com/ayn2op/tview v0.0.0-20261001183738-08b3e2ae6452 h1:DXlBoek+AEXAxZVnOSu/ld06rwr+e7IsalIpAdegP8I=
|
||||
github.com/ayn2op/tview v0.0.0-20261001183738-08b3e2ae6452/go.mod h1:oRPTSZgaEJPhv3dmmNvo6grBq/BemrLG3wFQLMTZJkY=
|
||||
github.com/ayn2op/tview v0.0.0-20261002024202-f7619deb5073 h1:ipmYpvyH7prpXsLYDYWFAgXLC++xr8ss6x7nlgCOhvU=
|
||||
github.com/ayn2op/tview v0.0.0-20261002024202-f7619deb5073/go.mod h1:oRPTSZgaEJPhv3dmmNvo6grBq/BemrLG3wFQLMTZJkY=
|
||||
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-socks4 v1.2.3 h1:Q6Y2heY1GRjCtHbmlKfnwrKVU/k81LS8mRGLRlmDlic=
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"bytes"
|
||||
"cmp"
|
||||
"context"
|
||||
"github.com/ayn2op/tview/layout"
|
||||
"io"
|
||||
"log/slog"
|
||||
"regexp"
|
||||
|
|
@ -117,6 +118,14 @@ type view struct {
|
|||
focused bool
|
||||
}
|
||||
|
||||
func (v view) Size() (width, height layout.Length) {
|
||||
return v.box(v.focused).Size()
|
||||
}
|
||||
|
||||
func (v view) Layout(limits layout.Limits) layout.Size {
|
||||
return v.box(v.focused).Layout(limits)
|
||||
}
|
||||
|
||||
func (v view) Draw(screen tview.Screen, area tview.Rectangle) {
|
||||
v.box(v.focused).Draw(screen, area)
|
||||
}
|
||||
|
|
@ -492,6 +501,14 @@ type mentionsPopup struct {
|
|||
c *Model
|
||||
}
|
||||
|
||||
// Size returns Fill, as the popup places the list within its whole area.
|
||||
func (mentionsPopup) Size() (width, height layout.Length) { return layout.Fill, layout.Fill }
|
||||
|
||||
// Layout returns the size of limits, as the popup places the list within its whole area.
|
||||
func (mentionsPopup) Layout(limits layout.Limits) layout.Size {
|
||||
return layout.Atomic(limits, layout.Fill, layout.Fill)
|
||||
}
|
||||
|
||||
func (p mentionsPopup) Draw(screen tview.Screen, area tview.Rectangle) {
|
||||
p.c.mentionsList.View().Draw(screen, p.area(area))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ func (m *Model) CurrentNode() *tree.Node {
|
|||
func (m Model) View(focused bool) tview.Element {
|
||||
onChange := func(c tree.Change) tview.Msg { return Msg(c) }
|
||||
onSelect := func(n *tree.Node) tview.Msg { return SelectedMsg{Node: n} }
|
||||
return uitree.New(m.root, &m.selectionState, m.cfg, m.cfg.Theme.GuildsTree.CommonTreeTheme, m.cfg.Keybinds.GuildsTree, focused, onChange, onSelect).Title("Guilds")
|
||||
return uitree.New(m.root, m.selectionState, m.cfg, m.cfg.Theme.GuildsTree.CommonTreeTheme, m.cfg.Keybinds.GuildsTree, focused, onChange, onSelect).Title("Guilds")
|
||||
}
|
||||
|
||||
func (m *Model) reset() *tree.Node {
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ func (m *Model) SetChannel(channel *discord.Channel) tview.Cmd {
|
|||
func (m Model) View(focused bool) tview.Element {
|
||||
onChange := func(c tree.Change) tview.Msg { return Msg(c) }
|
||||
onSelect := func(n *tree.Node) tview.Msg { return SelectedMsg{Node: n} }
|
||||
return uitree.New(m.root, &m.selectionState, m.cfg, m.cfg.Theme.MembersTree, m.cfg.Keybinds.MembersTree, focused, onChange, onSelect).Title("Members")
|
||||
return uitree.New(m.root, m.selectionState, m.cfg, m.cfg.Theme.MembersTree, m.cfg.Keybinds.MembersTree, focused, onChange, onSelect).Title("Members")
|
||||
}
|
||||
|
||||
func (m Model) Update(msg tview.Msg) (Model, tview.Cmd) {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"github.com/ayn2op/discordo/internal/config"
|
||||
"github.com/ayn2op/discordo/internal/ui"
|
||||
"github.com/ayn2op/tview"
|
||||
"github.com/ayn2op/tview/layout"
|
||||
"github.com/ayn2op/tview/list"
|
||||
"github.com/gdamore/tcell/v3"
|
||||
"github.com/rivo/uniseg"
|
||||
|
|
@ -30,7 +31,7 @@ func (m Model) View() tview.Element {
|
|||
func (Model) Init() tview.Cmd { return nil }
|
||||
|
||||
func (m Model) listView() list.Widget {
|
||||
return list.New(&m.selectionState, len(m.items), func(i int) list.Item {
|
||||
return list.New(m.selectionState, len(m.items), func(i int) tview.Element {
|
||||
item := m.items[i]
|
||||
return row{text: item.DisplayText, style: item.Style}
|
||||
}).
|
||||
|
|
@ -89,7 +90,13 @@ type row struct {
|
|||
style tcell.Style
|
||||
}
|
||||
|
||||
func (row) Rows(int) int { return 1 }
|
||||
// Size returns Fill and a height of one row.
|
||||
func (row) Size() (width, height layout.Length) { return layout.Fill, layout.Fixed(1) }
|
||||
|
||||
// Layout returns a height of one row.
|
||||
func (row) Layout(limits layout.Limits) layout.Size {
|
||||
return layout.Atomic(limits, layout.Fill, layout.Fixed(1))
|
||||
}
|
||||
|
||||
func (r row) Draw(screen tview.Screen, area tview.Rectangle) {
|
||||
for x := area.X; x < area.X+area.Width; x++ {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package messageslist
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/ayn2op/tview/layout"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
|
|
@ -36,7 +37,7 @@ import (
|
|||
|
||||
type messageItem struct {
|
||||
message discord.Message
|
||||
view list.Item
|
||||
view tview.Element
|
||||
separator bool
|
||||
timestamp discord.Timestamp
|
||||
|
||||
|
|
@ -106,7 +107,7 @@ func (ml *Model) title() string {
|
|||
}
|
||||
|
||||
func (ml *Model) listView(focused bool) list.Widget {
|
||||
return list.New(&ml.selectionState, len(ml.items), ml.buildItem).
|
||||
return list.New(ml.selectionState, len(ml.items), ml.buildItem).
|
||||
SelectedStyle(ml.cfg.Theme.MessagesList.SelectedMessageStyle.Style).
|
||||
ScrollBar(ml.scrollBar, ml.scrollBarVisibility).
|
||||
Keybind(scrollKeybind(ml.cfg.Keybinds.MessagesList.ScrollKeybinds)).
|
||||
|
|
@ -239,7 +240,7 @@ func (ml *Model) clearSelection() {
|
|||
ml.setCursor(-1)
|
||||
}
|
||||
|
||||
func (ml *Model) buildItem(index int) list.Item {
|
||||
func (ml *Model) buildItem(index int) tview.Element {
|
||||
if index < 0 || index >= len(ml.items) {
|
||||
return nil
|
||||
}
|
||||
|
|
@ -284,14 +285,20 @@ func (v *messageView) at(width int) textview.Widget {
|
|||
return textview.New(v.lines).Wrap(false)
|
||||
}
|
||||
|
||||
func (v *messageView) Rows(width int) int {
|
||||
v.at(width)
|
||||
rows := len(v.lines)
|
||||
for _, preview := range v.images {
|
||||
_, height := preview.Size()
|
||||
rows += height.Cells()
|
||||
}
|
||||
return rows
|
||||
// Size returns Fill and Shrink, as the message is as tall as its content.
|
||||
func (v *messageView) Size() (width, height layout.Length) { return layout.Fill, layout.Shrink }
|
||||
|
||||
// Layout returns the size of the message within limits, as tall as the rows it takes at the width of limits.
|
||||
func (v *messageView) Layout(limits layout.Limits) layout.Size {
|
||||
return layout.Sized(limits, layout.Fill, layout.Shrink, func(limits layout.Limits) layout.Size {
|
||||
v.at(limits.Max.Width)
|
||||
rows := len(v.lines)
|
||||
for _, preview := range v.images {
|
||||
_, height := preview.Size()
|
||||
rows += height.Cells()
|
||||
}
|
||||
return layout.Size{Height: rows}
|
||||
})
|
||||
}
|
||||
|
||||
func (v *messageView) Draw(screen tview.Screen, area tview.Rectangle) {
|
||||
|
|
@ -312,7 +319,13 @@ type dateSeparator struct {
|
|||
style tcell.Style
|
||||
}
|
||||
|
||||
func (dateSeparator) Rows(int) int { return 1 }
|
||||
// Size returns Fill and a height of one row.
|
||||
func (dateSeparator) Size() (width, height layout.Length) { return layout.Fill, layout.Fixed(1) }
|
||||
|
||||
// Layout returns a height of one row.
|
||||
func (dateSeparator) Layout(limits layout.Limits) layout.Size {
|
||||
return layout.Atomic(limits, layout.Fill, layout.Fixed(1))
|
||||
}
|
||||
|
||||
func (d dateSeparator) Draw(screen tview.Screen, area tview.Rectangle) {
|
||||
line := d.date
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package chat
|
|||
import (
|
||||
"cmp"
|
||||
"fmt"
|
||||
"github.com/ayn2op/tview/layout"
|
||||
"log/slog"
|
||||
"time"
|
||||
|
||||
|
|
@ -351,6 +352,14 @@ type paneElement struct {
|
|||
child tview.Element
|
||||
}
|
||||
|
||||
func (p paneElement) Size() (width, height layout.Length) {
|
||||
return p.child.Size()
|
||||
}
|
||||
|
||||
func (p paneElement) Layout(limits layout.Limits) layout.Size {
|
||||
return p.child.Layout(limits)
|
||||
}
|
||||
|
||||
func (p paneElement) Draw(screen tview.Screen, area tview.Rectangle) {
|
||||
p.child.Draw(screen, area)
|
||||
}
|
||||
|
|
@ -419,7 +428,7 @@ func (m Model) View() tview.Element {
|
|||
|
||||
var right tview.Element = column.New(
|
||||
m.paneView(paneMessagesList),
|
||||
column.New(m.paneView(paneComposer)).Height(tview.Fixed(m.composer.Height())),
|
||||
column.New(m.paneView(paneComposer)).Height(layout.Fixed(m.composer.Height())),
|
||||
)
|
||||
if mentions := m.composer.MentionsView(); mentions != nil {
|
||||
right = stack.New(right, mentions)
|
||||
|
|
@ -429,15 +438,15 @@ func (m Model) View() tview.Element {
|
|||
if m.membersTreeShown() {
|
||||
width := m.cfg.MembersTree.WidthPercent
|
||||
main = row.New(
|
||||
column.New(main).Width(tview.FillPortion(100-width)),
|
||||
column.New(m.paneView(paneMembersTree)).Width(tview.FillPortion(width)),
|
||||
column.New(main).Width(layout.FillPortion(100-width)),
|
||||
column.New(m.paneView(paneMembersTree)).Width(layout.FillPortion(width)),
|
||||
)
|
||||
}
|
||||
if m.guildsTreeVisible {
|
||||
width := m.cfg.Sidebar.WidthPercent
|
||||
main = row.New(
|
||||
column.New(m.paneView(paneGuildsTree)).Width(tview.FillPortion(width)),
|
||||
column.New(main).Width(tview.FillPortion(100-width)),
|
||||
column.New(m.paneView(paneGuildsTree)).Width(layout.FillPortion(width)),
|
||||
column.New(main).Width(layout.FillPortion(100-width)),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -448,7 +457,7 @@ func (m Model) View() tview.Element {
|
|||
// The panes behind the picker take no input.
|
||||
inert.New(main),
|
||||
backdrop.New().Style(m.cfg.Theme.Dialog.BackgroundStyle.Style),
|
||||
center.New(column.New(picker).Width(tview.Fixed(m.cfg.Picker.Width)).Height(tview.Fixed(m.cfg.Picker.Height))),
|
||||
center.New(column.New(picker).Width(layout.Fixed(m.cfg.Picker.Width)).Height(layout.Fixed(m.cfg.Picker.Height))),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"github.com/ayn2op/tview"
|
||||
"github.com/ayn2op/tview/button"
|
||||
"github.com/ayn2op/tview/column"
|
||||
"github.com/ayn2op/tview/layout"
|
||||
"github.com/ayn2op/tview/row"
|
||||
"github.com/ayn2op/tview/text"
|
||||
"github.com/ayn2op/tview/textinput"
|
||||
|
|
@ -60,7 +61,7 @@ func (m Model) Update(msg tview.Msg) (Model, tview.Cmd) {
|
|||
}
|
||||
|
||||
func (m Model) View() tview.Element {
|
||||
labelWidth := tview.Fixed(len("Password") + 1)
|
||||
labelWidth := layout.Fixed(len("Password") + 1)
|
||||
login := textinput.New(&m.login).
|
||||
Focused(m.focus == focusLogin).
|
||||
OnChange(func(c textinput.Change) tview.Msg { return loginMsg(c) }).
|
||||
|
|
@ -72,13 +73,13 @@ func (m Model) View() tview.Element {
|
|||
OnSubmit(focusMsg(focusButton))
|
||||
submit := button.New().
|
||||
Label("Login").
|
||||
Width(tview.Fixed(len("Login") + 4)).
|
||||
Height(tview.Fixed(1)).
|
||||
Width(layout.Fixed(len("Login") + 4)).
|
||||
Height(layout.Fixed(1)).
|
||||
Focused(m.focus == focusButton).
|
||||
OnClick(submitMsg{})
|
||||
return column.New(
|
||||
row.New(column.New(text.New("Login")).Width(labelWidth), login).Height(tview.Fixed(1)),
|
||||
row.New(column.New(text.New("Password")).Width(labelWidth), password).Height(tview.Fixed(1)),
|
||||
row.New(column.New(text.New("Login")).Width(labelWidth), login).Height(layout.Fixed(1)),
|
||||
row.New(column.New(text.New("Password")).Width(labelWidth), password).Height(layout.Fixed(1)),
|
||||
submit,
|
||||
).Spacing(1)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package qr
|
|||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"github.com/ayn2op/tview/layout"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
|
@ -140,7 +141,7 @@ func (m Model) View() tview.Element {
|
|||
ScrollState(&m.scrollState).
|
||||
Wrap(false).
|
||||
Alignment(tview.AlignmentCenter).
|
||||
Height(tview.Fixed(len(m.code))).
|
||||
Height(layout.Fixed(len(m.code))).
|
||||
Focused(true).
|
||||
OnChange(func(a textview.Change) tview.Msg { return scrollMsg(a) }),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"github.com/ayn2op/tview"
|
||||
"github.com/ayn2op/tview/button"
|
||||
"github.com/ayn2op/tview/column"
|
||||
"github.com/ayn2op/tview/layout"
|
||||
"github.com/ayn2op/tview/row"
|
||||
"github.com/ayn2op/tview/text"
|
||||
"github.com/ayn2op/tview/textinput"
|
||||
|
|
@ -53,12 +54,12 @@ func (m Model) View() tview.Element {
|
|||
OnSubmit(focusButtonMsg{})
|
||||
submit := button.New().
|
||||
Label("Login").
|
||||
Width(tview.Fixed(len("Login") + 4)).
|
||||
Height(tview.Fixed(1)).
|
||||
Width(layout.Fixed(len("Login") + 4)).
|
||||
Height(layout.Fixed(1)).
|
||||
Focused(m.buttonFocused).
|
||||
OnClick(submitMsg{})
|
||||
return column.New(
|
||||
row.New(column.New(text.New("Token")).Width(tview.Fixed(len("Token")+1)), input).Height(tview.Fixed(1)),
|
||||
row.New(column.New(text.New("Token")).Width(layout.Fixed(len("Token")+1)), input).Height(layout.Fixed(1)),
|
||||
submit,
|
||||
).Spacing(1)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package root
|
||||
|
||||
import (
|
||||
"github.com/ayn2op/tview/layout"
|
||||
"os"
|
||||
|
||||
"github.com/ayn2op/discordo/internal/config"
|
||||
|
|
@ -144,6 +145,11 @@ func (m *Model) updateScreen(msg tview.Msg) tview.Cmd {
|
|||
return cmd
|
||||
}
|
||||
|
||||
// helpHeight returns the rows the help takes at an unlimited width, as the width of the screen is not known in View.
|
||||
func (m Model) helpHeight() int {
|
||||
return m.helpView().Layout(layout.Limits{Infinite: layout.Axes{Height: true}}).Height
|
||||
}
|
||||
|
||||
// View shows the inner model above help, with the modal dialog on top of both when one is open.
|
||||
func (m Model) View() tview.Element {
|
||||
var innerView, helpView tview.Element
|
||||
|
|
@ -155,7 +161,7 @@ func (m Model) View() tview.Element {
|
|||
}
|
||||
if m.helpVisible {
|
||||
padded := box.New(m.helpView()).Padding(0, 0, m.cfg.Help.Padding[0], m.cfg.Help.Padding[1])
|
||||
helpView = column.New(padded).Height(tview.Fixed(max(m.helpView().Rows(0), 1)))
|
||||
helpView = column.New(padded).Height(layout.Fixed(max(m.helpHeight(), 1)))
|
||||
}
|
||||
content := column.New(innerView, helpView)
|
||||
if m.modal == nil {
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ func TestNewModel(t *testing.T) {
|
|||
if !m.helpVisible {
|
||||
t.Fatal("help is hidden")
|
||||
}
|
||||
if height := m.helpView().Rows(0); height != 1 {
|
||||
if height := m.helpHeight(); height != 1 {
|
||||
t.Fatalf("height = %d, want 1", height)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import (
|
|||
)
|
||||
|
||||
// New returns a boxed tree of the nodes under root, drawn with theme and moved with kbs, that turns changes and selected nodes into the messages onChange and onSelect return.
|
||||
func New(root *tree.Node, selectionState *tree.SelectionState, cfg *config.Config, theme config.CommonTreeTheme, kbs config.TreeKeybinds, focused bool, onChange func(tree.Change) tview.Msg, onSelect func(*tree.Node) tview.Msg) box.Widget {
|
||||
func New(root *tree.Node, selectionState tree.SelectionState, cfg *config.Config, theme config.CommonTreeTheme, kbs config.TreeKeybinds, focused bool, onChange func(tree.Change) tview.Msg, onSelect func(*tree.Node) tview.Msg) box.Widget {
|
||||
set := cfg.Theme.Border.NormalSet.BorderSet
|
||||
if focused {
|
||||
set = cfg.Theme.Border.ActiveSet.BorderSet
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue