refactor(ui): scroll the trees and lists with a viewport
This commit is contained in:
parent
715ed5aaeb
commit
6449f7d470
15 changed files with 166 additions and 79 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-20261008190544-5da45c25c67d
|
||||
github.com/ayn2op/tview v0.0.0-20261008230130-de4c692f9141
|
||||
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-20261008190544-5da45c25c67d h1:nueCN+u4FjTTU2ZdyDfRG68eCwa9Oq4dQY3CMgRUZQc=
|
||||
github.com/ayn2op/tview v0.0.0-20261008190544-5da45c25c67d/go.mod h1:sMf5NqBlGJjsaGCUycTqTyNG5vewJU6jq/3BdJIzbCU=
|
||||
github.com/ayn2op/tview v0.0.0-20261008230130-de4c692f9141 h1:c3pb7UfbQqtmBn2HbJ+XRXJTdo/vHgIsK+CKuYoFPYU=
|
||||
github.com/ayn2op/tview v0.0.0-20261008230130-de4c692f9141/go.mod h1:sMf5NqBlGJjsaGCUycTqTyNG5vewJU6jq/3BdJIzbCU=
|
||||
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,8 +4,8 @@ import (
|
|||
"fmt"
|
||||
|
||||
"github.com/ayn2op/tview"
|
||||
"github.com/ayn2op/tview/list"
|
||||
"github.com/ayn2op/tview/scrollbar"
|
||||
"github.com/ayn2op/tview/viewport"
|
||||
)
|
||||
|
||||
// enum pairs the names an option accepts with their values; a value is written back as the first of its names.
|
||||
|
|
@ -56,13 +56,13 @@ var (
|
|||
{"vertical", scrollbar.SymbolSetVertical()},
|
||||
{"double_vertical", scrollbar.SymbolSetDoubleVertical()},
|
||||
}
|
||||
scrollBarVisibilities = enum[list.ScrollBarVisibility]{
|
||||
{"auto", list.ScrollBarVisibilityAutomatic},
|
||||
{"automatic", list.ScrollBarVisibilityAutomatic},
|
||||
{"always", list.ScrollBarVisibilityAlways},
|
||||
{"never", list.ScrollBarVisibilityNever},
|
||||
{"hidden", list.ScrollBarVisibilityNever},
|
||||
{"off", list.ScrollBarVisibilityNever},
|
||||
scrollBarVisibilities = enum[viewport.ScrollBarVisibility]{
|
||||
{"auto", viewport.ScrollBarVisibilityAutomatic},
|
||||
{"automatic", viewport.ScrollBarVisibilityAutomatic},
|
||||
{"always", viewport.ScrollBarVisibilityAlways},
|
||||
{"never", viewport.ScrollBarVisibilityNever},
|
||||
{"hidden", viewport.ScrollBarVisibilityNever},
|
||||
{"off", viewport.ScrollBarVisibilityNever},
|
||||
}
|
||||
)
|
||||
|
||||
|
|
@ -93,7 +93,7 @@ func (sw *SymbolSetWrapper) UnmarshalText(text []byte) (err error) {
|
|||
|
||||
func (sw SymbolSetWrapper) MarshalText() ([]byte, error) { return symbolSets.text(sw.SymbolSet) }
|
||||
|
||||
type ScrollBarVisibilityWrapper struct{ list.ScrollBarVisibility }
|
||||
type ScrollBarVisibilityWrapper struct{ viewport.ScrollBarVisibility }
|
||||
|
||||
func (vw *ScrollBarVisibilityWrapper) UnmarshalText(text []byte) (err error) {
|
||||
vw.ScrollBarVisibility, err = scrollBarVisibilities.parse(string(text))
|
||||
|
|
@ -195,7 +195,7 @@ func defaultUI() UIConfig {
|
|||
Border: BorderUIConfig{Enabled: true, Padding: [4]int{0, 0, 1, 1}, NormalSet: round, ActiveSet: round},
|
||||
GuildsTree: GuildsTreeUIConfig{TreeUIConfig{Graphics: true}, true},
|
||||
MembersTree: TreeUIConfig{Graphics: true},
|
||||
ScrollBar: ScrollBarUIConfig{SymbolSet: SymbolSetWrapper{boxDrawing}},
|
||||
ScrollBar: ScrollBarUIConfig{SymbolSet: SymbolSetWrapper{boxDrawing}, Visibility: ScrollBarVisibilityWrapper{viewport.ScrollBarVisibilityAutomatic}},
|
||||
MessagesList: MessagesListUIConfig{ReplyIndicator: ">", ForwardedIndicator: "<"},
|
||||
MentionsList: MentionsListUIConfig{MinWidth: 20},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import (
|
|||
"github.com/ayn2op/tview"
|
||||
"github.com/ayn2op/tview/keybind"
|
||||
"github.com/ayn2op/tview/tree"
|
||||
"github.com/ayn2op/tview/viewport"
|
||||
"github.com/gdamore/tcell/v3"
|
||||
)
|
||||
|
||||
|
|
@ -22,6 +23,7 @@ type dmNode struct{}
|
|||
type Model struct {
|
||||
root *tree.Node
|
||||
selectionState tree.SelectionState
|
||||
scrollState viewport.ScrollState
|
||||
|
||||
cfg *config.Config
|
||||
state *ningen.State
|
||||
|
|
@ -53,6 +55,7 @@ func (m Model) View(focused bool) tview.Widget {
|
|||
return uitree.New(
|
||||
m.root,
|
||||
m.selectionState,
|
||||
m.scrollState,
|
||||
m.cfg,
|
||||
m.cfg.Theme.GuildsTree,
|
||||
m.cfg.UI.GuildsTree.Graphics,
|
||||
|
|
@ -109,7 +112,7 @@ func (m *Model) rebuild(event *gateway.ReadyEvent) {
|
|||
m.createFolderNode(folder, guildsByID)
|
||||
}
|
||||
}
|
||||
m.selectionState.SetCurrentNode(root)
|
||||
m.setCurrentNode(root)
|
||||
}
|
||||
|
||||
func (m Model) refreshReadStyles(event *read.UpdateEvent) {
|
||||
|
|
@ -298,7 +301,13 @@ func (m *Model) collapseParentNode(node *tree.Node) {
|
|||
}
|
||||
parent := path[len(path)-2]
|
||||
parent.Collapse()
|
||||
m.selectionState.SetCurrentNode(parent)
|
||||
m.setCurrentNode(parent)
|
||||
}
|
||||
|
||||
// setCurrentNode selects node and scrolls to it.
|
||||
func (m *Model) setCurrentNode(node *tree.Node) {
|
||||
m.selectionState.SetCurrentNode(node)
|
||||
m.scrollState.ScrollToTarget()
|
||||
}
|
||||
|
||||
// Update returns m changed in response to msg and a command to run, or nil.
|
||||
|
|
@ -323,9 +332,16 @@ func (m Model) Update(msg tview.Msg) (Model, tview.Cmd) {
|
|||
return m, m.navigate(msg.ChannelID)
|
||||
case Msg:
|
||||
m.selectionState.Apply(tree.Change(msg))
|
||||
m.scrollState.ScrollToTarget()
|
||||
return m, nil
|
||||
case uitree.ScrollMsg:
|
||||
m.scrollState.Apply(msg.Change)
|
||||
if msg.Msg != nil {
|
||||
return m.Update(msg.Msg)
|
||||
}
|
||||
return m, nil
|
||||
case SelectedMsg:
|
||||
m.selectionState.SetCurrentNode(msg.Node)
|
||||
m.setCurrentNode(msg.Node)
|
||||
return m, m.selectNode(msg.Node)
|
||||
case tview.KeyMsg:
|
||||
switch {
|
||||
|
|
@ -348,7 +364,7 @@ func (m Model) Update(msg tview.Msg) (Model, tview.Cmd) {
|
|||
}
|
||||
}
|
||||
m.expandPathToNode(node)
|
||||
m.selectionState.SetCurrentNode(node)
|
||||
m.setCurrentNode(node)
|
||||
}
|
||||
return m, nil
|
||||
case keybind.Matches(msg, m.cfg.Keybinds.GuildsTree.YankID.Keybind):
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import (
|
|||
"github.com/ayn2op/tview/tree"
|
||||
)
|
||||
|
||||
// Msg moves or scrolls the tree.
|
||||
// Msg moves the tree.
|
||||
type Msg tree.Change
|
||||
|
||||
// SelectedMsg is sent when the user selects a node.
|
||||
|
|
@ -37,7 +37,7 @@ func (m *Model) navigate(channelID discord.ChannelID) tview.Cmd {
|
|||
return nil
|
||||
}
|
||||
m.expandPathToNode(node)
|
||||
m.selectionState.SetCurrentNode(node)
|
||||
m.setCurrentNode(node)
|
||||
if channel.Type == discord.GuildCategory {
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import (
|
|||
"github.com/ayn2op/tview/keybind"
|
||||
"github.com/ayn2op/tview/richtext"
|
||||
"github.com/ayn2op/tview/tree"
|
||||
"github.com/ayn2op/tview/viewport"
|
||||
"github.com/gdamore/tcell/v3"
|
||||
)
|
||||
|
||||
|
|
@ -40,6 +41,7 @@ func (r memberRef) String() string {
|
|||
type Model struct {
|
||||
root *tree.Node
|
||||
selectionState tree.SelectionState
|
||||
scrollState viewport.ScrollState
|
||||
|
||||
cfg *config.Config
|
||||
state *ningen.State
|
||||
|
|
@ -85,7 +87,13 @@ func (m *Model) SetChannel(channel *discord.Channel) tview.Cmd {
|
|||
func (m Model) View(focused bool) tview.Widget {
|
||||
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.UI.MembersTree.Graphics, m.cfg.Keybinds.MembersTree, focused, onChange, onSelect).Title("Members")
|
||||
return uitree.New(m.root, m.selectionState, m.scrollState, m.cfg, m.cfg.Theme.MembersTree, m.cfg.UI.MembersTree.Graphics, m.cfg.Keybinds.MembersTree, focused, onChange, onSelect).Title("Members")
|
||||
}
|
||||
|
||||
// setCurrentNode selects node and scrolls to it.
|
||||
func (m *Model) setCurrentNode(node *tree.Node) {
|
||||
m.selectionState.SetCurrentNode(node)
|
||||
m.scrollState.ScrollToTarget()
|
||||
}
|
||||
|
||||
// Update returns m changed in response to msg and a command to run, or nil.
|
||||
|
|
@ -106,9 +114,16 @@ func (m Model) Update(msg tview.Msg) (Model, tview.Cmd) {
|
|||
|
||||
case Msg:
|
||||
m.selectionState.Apply(tree.Change(msg))
|
||||
m.scrollState.ScrollToTarget()
|
||||
return m, m.requestSelectedMembers()
|
||||
case uitree.ScrollMsg:
|
||||
m.scrollState.Apply(msg.Change)
|
||||
if msg.Msg != nil {
|
||||
return m.Update(msg.Msg)
|
||||
}
|
||||
return m, nil
|
||||
case SelectedMsg:
|
||||
m.selectionState.SetCurrentNode(msg.Node)
|
||||
m.setCurrentNode(msg.Node)
|
||||
if _, ok := msg.Node.Reference().(groupRef); ok {
|
||||
msg.Node.SetExpanded(!msg.Node.Expanded())
|
||||
}
|
||||
|
|
@ -124,7 +139,7 @@ func (m Model) Update(msg tview.Msg) (Model, tview.Cmd) {
|
|||
case keybind.Matches(msg, kbs.CollapseParentNode.Keybind):
|
||||
if parent := m.parent(m.selectionState.CurrentNode()); parent != nil {
|
||||
parent.Collapse()
|
||||
m.selectionState.SetCurrentNode(parent)
|
||||
m.setCurrentNode(parent)
|
||||
}
|
||||
return m, nil
|
||||
case keybind.Matches(msg, kbs.YankID.Keybind):
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import (
|
|||
"github.com/ayn2op/tview/tree"
|
||||
)
|
||||
|
||||
// Msg moves or scrolls the tree.
|
||||
// Msg moves the tree.
|
||||
type Msg tree.Change
|
||||
|
||||
// SelectedMsg is sent when the user selects a node.
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ import (
|
|||
"github.com/ayn2op/tview"
|
||||
"github.com/ayn2op/tview/layout"
|
||||
"github.com/ayn2op/tview/list"
|
||||
"github.com/ayn2op/tview/scrollbar"
|
||||
"github.com/ayn2op/tview/viewport"
|
||||
"github.com/rivo/uniseg"
|
||||
)
|
||||
|
||||
|
|
@ -13,6 +15,7 @@ import (
|
|||
type Model struct {
|
||||
cfg *config.Config
|
||||
selectionState list.SelectionState
|
||||
scrollState viewport.ScrollState
|
||||
items []Item
|
||||
}
|
||||
|
||||
|
|
@ -29,8 +32,8 @@ func (m Model) View() tview.Widget {
|
|||
|
||||
func (Model) Init() tview.Cmd { return nil }
|
||||
|
||||
func (m Model) listView() list.Widget {
|
||||
return list.New(m.selectionState, len(m.items), func(i int) tview.Widget {
|
||||
func (m Model) listView() viewport.Widget {
|
||||
l := list.New(m.selectionState, len(m.items), func(i int) tview.Widget {
|
||||
item := m.items[i]
|
||||
return row{text: item.DisplayText, style: item.Style}
|
||||
}).
|
||||
|
|
@ -38,12 +41,20 @@ func (m Model) listView() list.Widget {
|
|||
Keybind(ui.SelectionKeybind(m.cfg.Keybinds.MentionsList.SelectionKeybinds)).
|
||||
// The list is only shown while mentions are being completed, so it takes its keys first.
|
||||
Focused(true).
|
||||
OnChange(func(a list.Change) tview.Msg { return Msg(a) })
|
||||
OnChange(func(a list.Change) tview.Msg { return listMsg(a) })
|
||||
return viewport.New(l, m.scrollState).
|
||||
Target(l.Target).
|
||||
ScrollBar(scrollbar.New(), viewport.ScrollBarVisibilityAutomatic).
|
||||
OnChange(func(a viewport.Change) tview.Msg { return scrollMsg(a) })
|
||||
}
|
||||
|
||||
func (m Model) Update(msg tview.Msg) (Model, tview.Cmd) {
|
||||
if msg, ok := msg.(Msg); ok {
|
||||
switch msg := msg.(type) {
|
||||
case listMsg:
|
||||
m.selectionState.Apply(list.Change(msg))
|
||||
m.scrollState.ScrollToTarget()
|
||||
case scrollMsg:
|
||||
m.scrollState.Apply(viewport.Change(msg))
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
|
@ -79,6 +90,7 @@ func (m Model) MaxDisplayWidth() int {
|
|||
|
||||
func (m *Model) Rebuild() {
|
||||
m.selectionState.SetCursor(min(0, len(m.items)-1))
|
||||
m.scrollState.ScrollToTarget()
|
||||
}
|
||||
|
||||
var _ tview.Model[Model] = Model{}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,17 @@
|
|||
package mentionslist
|
||||
|
||||
import "github.com/ayn2op/tview/list"
|
||||
import (
|
||||
"github.com/ayn2op/tview/list"
|
||||
"github.com/ayn2op/tview/viewport"
|
||||
)
|
||||
|
||||
// Msg moves the list.
|
||||
type Msg list.Change
|
||||
// Msg is implemented by the messages that move or scroll the list.
|
||||
type Msg interface{ mentionsList() }
|
||||
|
||||
type listMsg list.Change
|
||||
|
||||
func (listMsg) mentionsList() {}
|
||||
|
||||
type scrollMsg viewport.Change
|
||||
|
||||
func (scrollMsg) mentionsList() {}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import (
|
|||
"github.com/ayn2op/tview/richtext"
|
||||
"github.com/ayn2op/tview/scrollbar"
|
||||
"github.com/ayn2op/tview/textview"
|
||||
"github.com/ayn2op/tview/viewport"
|
||||
"github.com/gdamore/tcell/v3"
|
||||
"github.com/gdamore/tcell/v3/color"
|
||||
"github.com/rivo/uniseg"
|
||||
|
|
@ -48,6 +49,7 @@ type messageItem struct {
|
|||
|
||||
type Model struct {
|
||||
selectionState list.SelectionState
|
||||
scrollState viewport.ScrollState
|
||||
|
||||
cfg *config.Config
|
||||
state *ningen.State
|
||||
|
|
@ -75,7 +77,7 @@ func NewModel(cfg *config.Config, state *ningen.State) Model {
|
|||
renderer: markdown.NewRenderer(cfg),
|
||||
}
|
||||
|
||||
ml.selectionState.ScrollToEnd()
|
||||
ml.scrollState.ScrollToEnd()
|
||||
return ml
|
||||
}
|
||||
|
||||
|
|
@ -96,43 +98,34 @@ func (m Model) title() string {
|
|||
return title
|
||||
}
|
||||
|
||||
func (m Model) listView(focused bool) list.Widget {
|
||||
func (m Model) listView(focused bool) viewport.Widget {
|
||||
cfg, theme := m.cfg.UI.ScrollBar, m.cfg.Theme.ScrollBar
|
||||
bar := scrollbar.New().
|
||||
SymbolSet(cfg.SymbolSet.SymbolSet).
|
||||
Style(theme.TrackStyle.Style).
|
||||
ThumbStyle(theme.ThumbStyle.Style)
|
||||
return list.New(m.selectionState, len(m.items), m.buildItem).
|
||||
// The model moves the selection itself, so the list is not focused and only takes clicks.
|
||||
l := list.New(m.selectionState, len(m.items), m.buildItem).
|
||||
SelectedStyle(m.cfg.Theme.MessagesList.SelectedMessageStyle.Style).
|
||||
OnChange(func(a list.Change) tview.Msg { return listMsg(a) })
|
||||
return viewport.New(l, m.scrollState).
|
||||
Target(l.Target).
|
||||
ScrollBar(bar, cfg.Visibility.ScrollBarVisibility).
|
||||
TrackEnd(true).
|
||||
Keybind(scrollKeybind(m.cfg.Keybinds.MessagesList.ScrollKeybinds)).
|
||||
Keybind(ui.ScrollKeybind(m.cfg.Keybinds.MessagesList.ScrollKeybinds)).
|
||||
Focused(focused).
|
||||
OnChange(func(a list.Change) tview.Msg { return listMsg(a) })
|
||||
}
|
||||
|
||||
// scrollKeybind binds kbs to scrolling the list.
|
||||
func scrollKeybind(kbs config.ScrollKeybinds) func(tview.KeyMsg) list.Action {
|
||||
return func(key tview.KeyMsg) list.Action {
|
||||
switch {
|
||||
case keybind.Matches(key, kbs.ScrollUp.Keybind):
|
||||
return list.ActionScrollUp
|
||||
case keybind.Matches(key, kbs.ScrollDown.Keybind):
|
||||
return list.ActionScrollDown
|
||||
case keybind.Matches(key, kbs.ScrollTop.Keybind):
|
||||
return list.ActionScrollTop
|
||||
case keybind.Matches(key, kbs.ScrollBottom.Keybind):
|
||||
return list.ActionScrollBottom
|
||||
}
|
||||
return list.ActionNone
|
||||
}
|
||||
OnChange(func(a viewport.Change) tview.Msg { return scrollMsg(a) })
|
||||
}
|
||||
|
||||
func (m Model) cursor() int {
|
||||
return m.selectionState.Cursor()
|
||||
}
|
||||
|
||||
// setCursor selects the item at index and scrolls to it if it changed, or selects none if it is negative.
|
||||
func (m *Model) setCursor(index int) {
|
||||
if index >= 0 && index != m.cursor() {
|
||||
m.scrollState.ScrollToTarget()
|
||||
}
|
||||
m.selectionState.SetCursor(index)
|
||||
}
|
||||
|
||||
|
|
@ -150,7 +143,7 @@ func (m *Model) SetChannel(channel *discord.Channel, messages []discord.Message)
|
|||
// ShowNewest clears the selection and scrolls to the newest message, which then stays in view as messages arrive until the user scrolls up.
|
||||
func (m *Model) ShowNewest() {
|
||||
m.clearSelection()
|
||||
m.selectionState.ScrollToEnd()
|
||||
m.scrollState.ScrollToEnd()
|
||||
}
|
||||
|
||||
// UpdateMessage replaces the shown message with the same ID.
|
||||
|
|
@ -182,7 +175,8 @@ func (m Model) indexOf(channelID discord.ChannelID, id discord.MessageID) int {
|
|||
func (m *Model) reset() {
|
||||
m.items = nil
|
||||
m.selectionState = list.NewSelectionState()
|
||||
m.selectionState.ScrollToEnd()
|
||||
m.scrollState = viewport.ScrollState{}
|
||||
m.scrollState.ScrollToEnd()
|
||||
}
|
||||
|
||||
func (m *Model) setMessages(messages []discord.Message) {
|
||||
|
|
@ -928,6 +922,8 @@ func (m Model) Update(msg tview.Msg) (Model, tview.Cmd) {
|
|||
// WezTerm and Konsole support kitty graphics but not Unicode placeholders.
|
||||
m.kitty = msg.Name == "kitty" || msg.Name == "ghostty"
|
||||
}
|
||||
case scrollMsg:
|
||||
m.scrollState.Apply(viewport.Change(msg))
|
||||
case listMsg:
|
||||
top := m.atTop()
|
||||
m.selectionState.Apply(list.Change(msg))
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import (
|
|||
"github.com/ayn2op/tview"
|
||||
"github.com/ayn2op/tview/list"
|
||||
"github.com/ayn2op/tview/picker"
|
||||
"github.com/ayn2op/tview/viewport"
|
||||
"github.com/ncruces/zenity"
|
||||
"github.com/skratchdot/open-golang/open"
|
||||
"golang.design/x/clipboard"
|
||||
|
|
@ -44,9 +45,12 @@ type EditMsg discord.Message
|
|||
// ShowAttachmentsMsg asks to pick one of the attachments and links of the selected message.
|
||||
type ShowAttachmentsMsg picker.Items
|
||||
|
||||
// listMsg moves or scrolls the messages list.
|
||||
// listMsg moves the messages list.
|
||||
type listMsg list.Change
|
||||
|
||||
// scrollMsg scrolls the messages list.
|
||||
type scrollMsg viewport.Change
|
||||
|
||||
func (m *Model) selectUp() tview.Cmd {
|
||||
cursor := m.cursor()
|
||||
if cursor == -1 {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import (
|
|||
"github.com/ayn2op/tview"
|
||||
"github.com/ayn2op/tview/keybind"
|
||||
"github.com/ayn2op/tview/list"
|
||||
"github.com/ayn2op/tview/viewport"
|
||||
)
|
||||
|
||||
// SelectionKeybind binds kbs to moving a list's selection.
|
||||
|
|
@ -23,3 +24,20 @@ func SelectionKeybind(kbs config.SelectionKeybinds) func(tview.KeyMsg) list.Acti
|
|||
return list.ActionNone
|
||||
}
|
||||
}
|
||||
|
||||
// ScrollKeybind binds kbs to scrolling a viewport.
|
||||
func ScrollKeybind(kbs config.ScrollKeybinds) func(tview.KeyMsg) viewport.Action {
|
||||
return func(key tview.KeyMsg) viewport.Action {
|
||||
switch {
|
||||
case keybind.Matches(key, kbs.ScrollUp.Keybind):
|
||||
return viewport.ActionUp
|
||||
case keybind.Matches(key, kbs.ScrollDown.Keybind):
|
||||
return viewport.ActionDown
|
||||
case keybind.Matches(key, kbs.ScrollTop.Keybind):
|
||||
return viewport.ActionTop
|
||||
case keybind.Matches(key, kbs.ScrollBottom.Keybind):
|
||||
return viewport.ActionBottom
|
||||
}
|
||||
return viewport.ActionNone
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,12 +10,13 @@ import (
|
|||
"github.com/ayn2op/tview/center"
|
||||
"github.com/ayn2op/tview/richtext"
|
||||
"github.com/ayn2op/tview/textview"
|
||||
"github.com/ayn2op/tview/viewport"
|
||||
"github.com/gdamore/tcell/v3"
|
||||
"github.com/skip2/go-qrcode"
|
||||
)
|
||||
|
||||
type Model struct {
|
||||
scrollState textview.ScrollState
|
||||
scrollState viewport.ScrollState
|
||||
|
||||
conn *gatewayConn
|
||||
heartbeatInterval time.Duration
|
||||
|
|
@ -55,7 +56,7 @@ func (m Model) Update(msg tview.Msg) (Model, tview.Cmd) {
|
|||
}
|
||||
return m, nil
|
||||
case scrollMsg:
|
||||
m.scrollState.Apply(textview.Change(msg))
|
||||
m.scrollState.Apply(viewport.Change(msg))
|
||||
return m, nil
|
||||
|
||||
case connCreateMsg:
|
||||
|
|
@ -131,13 +132,10 @@ func halfBlock(top, bottom bool) rune {
|
|||
func (m Model) View() tview.Widget {
|
||||
code := m.code()
|
||||
return center.New(
|
||||
textview.New(code).
|
||||
ScrollState(&m.scrollState).
|
||||
Wrap(false).
|
||||
Alignment(tview.AlignmentCenter).
|
||||
viewport.New(textview.New(code).Wrap(false).Alignment(tview.AlignmentCenter), m.scrollState).
|
||||
Height(layout.Fixed(len(code))).
|
||||
Focused(true).
|
||||
OnChange(func(a textview.Change) tview.Msg { return scrollMsg(a) }),
|
||||
OnChange(func(a viewport.Change) tview.Msg { return scrollMsg(a) }),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import (
|
|||
"github.com/ayn2op/discordo/internal/gateway"
|
||||
"github.com/ayn2op/discordo/internal/http"
|
||||
"github.com/ayn2op/tview"
|
||||
"github.com/ayn2op/tview/textview"
|
||||
"github.com/ayn2op/tview/viewport"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/skip2/go-qrcode"
|
||||
)
|
||||
|
|
@ -302,7 +302,7 @@ func exchangeTicket(fingerprint string, privateKey *rsa.PrivateKey, ticket strin
|
|||
}
|
||||
|
||||
// scrollMsg scrolls the code.
|
||||
type scrollMsg textview.Change
|
||||
type scrollMsg viewport.Change
|
||||
|
||||
// startMsg starts connecting to the Remote Auth Gateway.
|
||||
type startMsg struct{}
|
||||
|
|
|
|||
|
|
@ -7,12 +7,20 @@ import (
|
|||
"github.com/ayn2op/tview"
|
||||
"github.com/ayn2op/tview/box"
|
||||
"github.com/ayn2op/tview/keybind"
|
||||
"github.com/ayn2op/tview/layout"
|
||||
"github.com/ayn2op/tview/tree"
|
||||
"github.com/ayn2op/tview/viewport"
|
||||
"github.com/gdamore/tcell/v3"
|
||||
)
|
||||
|
||||
// New returns a boxed tree of the nodes under root, drawn with theme and graphics 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.TreeThemeConfig, graphics bool, kbs config.TreeKeybinds, focused bool, onChange func(tree.Change) tview.Msg, onSelect func(*tree.Node) tview.Msg) box.Widget {
|
||||
// ScrollMsg scrolls the tree, before passing on Msg if the tree sent one.
|
||||
type ScrollMsg struct {
|
||||
Change viewport.Change
|
||||
Msg tview.Msg
|
||||
}
|
||||
|
||||
// New returns a boxed tree of the nodes under root in a viewport.
|
||||
func New(root *tree.Node, selectionState tree.SelectionState, scrollState viewport.ScrollState, cfg *config.Config, theme config.TreeThemeConfig, graphics bool, kbs config.TreeKeybinds, focused bool, onChange func(tree.Change) tview.Msg, onSelect func(*tree.Node) tview.Msg) box.Widget {
|
||||
set := cfg.UI.Border.NormalSet.BorderSet
|
||||
if focused {
|
||||
set = cfg.UI.Border.ActiveSet.BorderSet
|
||||
|
|
@ -31,7 +39,15 @@ func New(root *tree.Node, selectionState tree.SelectionState, cfg *config.Config
|
|||
Focused(focused).
|
||||
OnChange(onChange).
|
||||
OnSelect(onSelect)
|
||||
return ui.Box(t, cfg, focused)
|
||||
v := viewport.New(t, scrollState).
|
||||
Target(t.Target).
|
||||
ContentWidth(t.RowsWidth).
|
||||
OnChildMsg(func(c viewport.Change, msg tview.Msg) tview.Msg { return ScrollMsg{Change: c, Msg: msg} }).
|
||||
Axes(layout.Axes{Width: true, Height: true}).
|
||||
Keybind(bindScrollKeys(kbs)).
|
||||
Focused(focused).
|
||||
OnChange(func(c viewport.Change) tview.Msg { return ScrollMsg{Change: c} })
|
||||
return ui.Box(v, cfg, focused)
|
||||
}
|
||||
|
||||
func bindKeys(kbs config.TreeKeybinds) func(tview.KeyMsg) tree.Action {
|
||||
|
|
@ -49,19 +65,20 @@ func bindKeys(kbs config.TreeKeybinds) func(tview.KeyMsg) tree.Action {
|
|||
return tree.ActionMoveToParent
|
||||
case keybind.Matches(key, kbs.SelectCurrent.Keybind):
|
||||
return tree.ActionSelect
|
||||
case keybind.Matches(key, kbs.ScrollUp.Keybind):
|
||||
return tree.ActionScrollUp
|
||||
case keybind.Matches(key, kbs.ScrollDown.Keybind):
|
||||
return tree.ActionScrollDown
|
||||
case keybind.Matches(key, kbs.ScrollTop.Keybind):
|
||||
return tree.ActionScrollTop
|
||||
case keybind.Matches(key, kbs.ScrollBottom.Keybind):
|
||||
return tree.ActionScrollBottom
|
||||
case keybind.Matches(key, kbs.ScrollLeft.Keybind):
|
||||
return tree.ActionScrollLeft
|
||||
case keybind.Matches(key, kbs.ScrollRight.Keybind):
|
||||
return tree.ActionScrollRight
|
||||
}
|
||||
return tree.ActionNone
|
||||
}
|
||||
}
|
||||
|
||||
func bindScrollKeys(kbs config.TreeKeybinds) func(tview.KeyMsg) viewport.Action {
|
||||
scroll := ui.ScrollKeybind(kbs.ScrollKeybinds)
|
||||
return func(key tview.KeyMsg) viewport.Action {
|
||||
switch {
|
||||
case keybind.Matches(key, kbs.ScrollLeft.Keybind):
|
||||
return viewport.ActionLeft
|
||||
case keybind.Matches(key, kbs.ScrollRight.Keybind):
|
||||
return viewport.ActionRight
|
||||
}
|
||||
return scroll(key)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue