refactor(ui): share tree and picker widgets
This commit is contained in:
parent
d9ce1260af
commit
05e622e753
11 changed files with 215 additions and 174 deletions
|
|
@ -62,7 +62,7 @@ type PickerKeybinds struct {
|
|||
Cancel Keybind `toml:"cancel"`
|
||||
}
|
||||
|
||||
type GuildsTreeKeybinds struct {
|
||||
type TreeKeybinds struct {
|
||||
SelectionKeybinds
|
||||
SelectCurrent Keybind `toml:"select_current"`
|
||||
YankID Keybind `toml:"yank_id"`
|
||||
|
|
@ -128,7 +128,7 @@ type Keybinds struct {
|
|||
FocusNext Keybind `toml:"focus_next"`
|
||||
|
||||
Picker PickerKeybinds `toml:"picker"`
|
||||
GuildsTree GuildsTreeKeybinds `toml:"guilds_tree"`
|
||||
GuildsTree TreeKeybinds `toml:"guilds_tree"`
|
||||
MessagesList MessagesListKeybinds `toml:"messages_list"`
|
||||
Composer ComposerKeybinds `toml:"composer"`
|
||||
MentionsList MentionsListKeybinds `toml:"mentions_list"`
|
||||
|
|
@ -154,8 +154,8 @@ func defaultPickerKeybinds() PickerKeybinds {
|
|||
}
|
||||
}
|
||||
|
||||
func defaultGuildsTreeKeybinds() GuildsTreeKeybinds {
|
||||
return GuildsTreeKeybinds{
|
||||
func defaultTreeKeybinds() TreeKeybinds {
|
||||
return TreeKeybinds{
|
||||
SelectionKeybinds: defaultSelectionKeybinds(),
|
||||
SelectCurrent: desc("select"),
|
||||
YankID: desc("copy id"),
|
||||
|
|
@ -229,7 +229,7 @@ func defaultKeybinds() Keybinds {
|
|||
Quit: desc("quit"),
|
||||
|
||||
Picker: defaultPickerKeybinds(),
|
||||
GuildsTree: defaultGuildsTreeKeybinds(),
|
||||
GuildsTree: defaultTreeKeybinds(),
|
||||
MessagesList: defaultMessagesListKeybinds(),
|
||||
Composer: defaultComposerKeybinds(),
|
||||
MentionsList: defaultMentionsListKeybinds(),
|
||||
|
|
|
|||
|
|
@ -208,10 +208,9 @@ type (
|
|||
ActiveSet BorderSetWrapper `toml:"active_set"`
|
||||
}
|
||||
|
||||
GuildsTreeTheme struct {
|
||||
AutoExpandFolders bool `toml:"auto_expand_folders"`
|
||||
Graphics bool `toml:"graphics"`
|
||||
GraphicsColor string `toml:"graphics_color"`
|
||||
CommonTreeTheme struct {
|
||||
Graphics bool `toml:"graphics"`
|
||||
GraphicsColor string `toml:"graphics_color"`
|
||||
|
||||
OnlineStyle StyleWrapper `toml:"online_style"`
|
||||
IdleStyle StyleWrapper `toml:"idle_style"`
|
||||
|
|
@ -219,6 +218,11 @@ type (
|
|||
OfflineStyle StyleWrapper `toml:"offline_style"`
|
||||
}
|
||||
|
||||
GuildsTreeTheme struct {
|
||||
CommonTreeTheme
|
||||
AutoExpandFolders bool `toml:"auto_expand_folders"`
|
||||
}
|
||||
|
||||
MessagesListTheme struct {
|
||||
ReplyIndicator string `toml:"reply_indicator"`
|
||||
ForwardedIndicator string `toml:"forwarded_indicator"`
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package attachmentspicker
|
|||
|
||||
import (
|
||||
"github.com/ayn2op/discordo/internal/config"
|
||||
"github.com/ayn2op/discordo/internal/ui"
|
||||
uipicker "github.com/ayn2op/discordo/internal/ui/picker"
|
||||
"github.com/ayn2op/tview"
|
||||
"github.com/ayn2op/tview/picker"
|
||||
)
|
||||
|
|
@ -13,35 +13,24 @@ type Item struct {
|
|||
}
|
||||
|
||||
type Model struct {
|
||||
items picker.Items
|
||||
searchState picker.SearchState
|
||||
cfg *config.Config
|
||||
uipicker.Model
|
||||
}
|
||||
|
||||
func NewModel(cfg *config.Config) Model {
|
||||
return Model{searchState: picker.NewSearchState(), cfg: cfg}
|
||||
return Model{uipicker.NewModel(cfg, "Attachments")}
|
||||
}
|
||||
|
||||
var _ tview.Model[Model] = Model{}
|
||||
|
||||
// changeMsg changes the picker.
|
||||
type changeMsg picker.Change
|
||||
|
||||
func (Model) Init() tview.Cmd { return nil }
|
||||
|
||||
// View shows the picker in a box titled Attachments.
|
||||
func (m Model) View() tview.Element {
|
||||
p := ui.Picker(m.items, &m.searchState, m.cfg).
|
||||
OnChange(func(a picker.Change) tview.Msg { return changeMsg(a) }).
|
||||
OnSelect(func(item picker.Item) tview.Msg {
|
||||
action, ok := item.Reference.(tview.Cmd)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return SelectedMsg{Action: action}
|
||||
}).
|
||||
OnCancel(CancelMsg{})
|
||||
return ui.Box(p, &m.cfg.Theme, true).Title("Attachments")
|
||||
return m.Model.View(func(item picker.Item) tview.Msg {
|
||||
action, ok := item.Reference.(tview.Cmd)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return SelectedMsg{Action: action}
|
||||
}, CancelMsg{})
|
||||
}
|
||||
|
||||
func (m *Model) SetItems(items []Item) {
|
||||
|
|
@ -49,13 +38,11 @@ func (m *Model) SetItems(items []Item) {
|
|||
for i, item := range items {
|
||||
pickerItems[i] = picker.Item{Text: item.Label, Reference: item.Action}
|
||||
}
|
||||
m.items = pickerItems
|
||||
m.searchState.Reset()
|
||||
m.Model.SetItems(pickerItems)
|
||||
}
|
||||
|
||||
func (m Model) Update(msg tview.Msg) (Model, tview.Cmd) {
|
||||
if msg, ok := msg.(changeMsg); ok {
|
||||
m.searchState.Apply(picker.Change(msg))
|
||||
}
|
||||
return m, nil
|
||||
var cmd tview.Cmd
|
||||
m.Model, cmd = m.Model.Update(msg)
|
||||
return m, cmd
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,53 +7,40 @@ import (
|
|||
"github.com/ayn2op/arikawa/v3/discord"
|
||||
"github.com/ayn2op/discordo/internal/config"
|
||||
"github.com/ayn2op/discordo/internal/ui"
|
||||
uipicker "github.com/ayn2op/discordo/internal/ui/picker"
|
||||
"github.com/ayn2op/ningen/v3"
|
||||
"github.com/ayn2op/tview"
|
||||
"github.com/ayn2op/tview/picker"
|
||||
)
|
||||
|
||||
type Model struct {
|
||||
items picker.Items
|
||||
searchState picker.SearchState
|
||||
cfg *config.Config
|
||||
uipicker.Model
|
||||
cfg *config.Config
|
||||
}
|
||||
|
||||
func NewModel(cfg *config.Config) Model {
|
||||
return Model{searchState: picker.NewSearchState(), cfg: cfg}
|
||||
return Model{Model: uipicker.NewModel(cfg, "Channels"), cfg: cfg}
|
||||
}
|
||||
|
||||
var _ tview.Model[Model] = Model{}
|
||||
|
||||
// changeMsg changes the picker.
|
||||
type changeMsg picker.Change
|
||||
|
||||
func (Model) Init() tview.Cmd { return nil }
|
||||
|
||||
// View shows the picker in a box titled Channels.
|
||||
func (m Model) View() tview.Element {
|
||||
p := ui.Picker(m.items, &m.searchState, m.cfg).
|
||||
OnChange(func(a picker.Change) tview.Msg { return changeMsg(a) }).
|
||||
OnSelect(func(item picker.Item) tview.Msg {
|
||||
channelID, ok := item.Reference.(discord.ChannelID)
|
||||
if !ok || !channelID.IsValid() {
|
||||
return nil
|
||||
}
|
||||
return SelectedMsg{ChannelID: channelID}
|
||||
}).
|
||||
OnCancel(CancelMsg{})
|
||||
return ui.Box(p, &m.cfg.Theme, true).Title("Channels")
|
||||
return m.Model.View(func(item picker.Item) tview.Msg {
|
||||
channelID, ok := item.Reference.(discord.ChannelID)
|
||||
if !ok || !channelID.IsValid() {
|
||||
return nil
|
||||
}
|
||||
return SelectedMsg{ChannelID: channelID}
|
||||
}, CancelMsg{})
|
||||
}
|
||||
|
||||
func (m Model) Update(msg tview.Msg) (Model, tview.Cmd) {
|
||||
if msg, ok := msg.(changeMsg); ok {
|
||||
m.searchState.Apply(picker.Change(msg))
|
||||
}
|
||||
return m, nil
|
||||
var cmd tview.Cmd
|
||||
m.Model, cmd = m.Model.Update(msg)
|
||||
return m, cmd
|
||||
}
|
||||
|
||||
// Reset clears the query.
|
||||
func (m *Model) Reset() { m.searchState.Reset() }
|
||||
|
||||
func (m *Model) RefreshChannels(state *ningen.State) {
|
||||
var items picker.Items
|
||||
|
||||
|
|
@ -86,8 +73,7 @@ func (m *Model) RefreshChannels(state *ningen.State) {
|
|||
}
|
||||
}
|
||||
|
||||
m.items = items
|
||||
m.searchState.Reset()
|
||||
m.SetItems(items)
|
||||
}
|
||||
|
||||
func (m Model) channelItem(state *ningen.State, guild *discord.Guild, channel discord.Channel) picker.Item {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import (
|
|||
"github.com/ayn2op/arikawa/v3/gateway"
|
||||
"github.com/ayn2op/discordo/internal/config"
|
||||
"github.com/ayn2op/discordo/internal/ui"
|
||||
uitree "github.com/ayn2op/discordo/internal/ui/tree"
|
||||
"github.com/ayn2op/ningen/v3"
|
||||
"github.com/ayn2op/ningen/v3/states/read"
|
||||
"github.com/ayn2op/tview"
|
||||
|
|
@ -50,26 +51,8 @@ type Msg tree.Change
|
|||
|
||||
// View shows the tree in a box titled Guilds.
|
||||
func (m Model) View(focused bool) tview.Element {
|
||||
theme := &m.cfg.Theme
|
||||
set := theme.Border.NormalSet.BorderSet
|
||||
if focused {
|
||||
set = theme.Border.ActiveSet.BorderSet
|
||||
}
|
||||
kbs := m.cfg.Keybinds.GuildsTree
|
||||
t := tree.New(m.root, &m.selectionState).
|
||||
TopLevel(1).
|
||||
Markers(tree.Markers{
|
||||
Expanded: m.cfg.Sidebar.Markers.Expanded,
|
||||
Collapsed: m.cfg.Sidebar.Markers.Collapsed,
|
||||
Leaf: m.cfg.Sidebar.Markers.Leaf,
|
||||
}).
|
||||
Graphics(theme.GuildsTree.Graphics).
|
||||
GraphicsSet(set).
|
||||
GraphicsStyle(tcell.StyleDefault.Foreground(tcell.GetColor(theme.GuildsTree.GraphicsColor))).
|
||||
Keybind(ui.TreeKeybind(kbs)).
|
||||
Focused(focused).
|
||||
OnChange(func(a tree.Change) tview.Msg { return Msg(a) })
|
||||
return ui.Box(t, theme, focused).Title("Guilds")
|
||||
onChange := func(c tree.Change) tview.Msg { return Msg(c) }
|
||||
return uitree.New(m.root, &m.selectionState, m.cfg, m.cfg.Theme.GuildsTree.CommonTreeTheme, m.cfg.Keybinds.GuildsTree, focused, onChange).Title("Guilds")
|
||||
}
|
||||
|
||||
func (m *Model) reset() *tree.Node {
|
||||
|
|
@ -365,7 +348,7 @@ func (m *Model) update(msg tview.Msg) tview.Cmd {
|
|||
m.collapseParentNode(m.selectionState.CurrentNode())
|
||||
return nil
|
||||
case keybind.Matches(msg, m.cfg.Keybinds.GuildsTree.YankID.Keybind):
|
||||
return yankID(m.selectionState.CurrentNode())
|
||||
return uitree.YankID(m.selectionState.CurrentNode())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
package guildstree
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
|
||||
"github.com/ayn2op/arikawa/v3/discord"
|
||||
|
|
@ -10,7 +8,6 @@ import (
|
|||
"github.com/ayn2op/ningen/v3"
|
||||
"github.com/ayn2op/tview"
|
||||
"github.com/ayn2op/tview/tree"
|
||||
"golang.design/x/clipboard"
|
||||
)
|
||||
|
||||
type ChannelLoadedMsg struct {
|
||||
|
|
@ -106,20 +103,3 @@ func loadChannel(state *ningen.State, limit uint, channel discord.Channel) tview
|
|||
return ChannelLoadedMsg{Channel: channel, Messages: messages}
|
||||
}
|
||||
}
|
||||
|
||||
func yankID(node *tree.Node) tview.Cmd {
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
id, ok := node.Reference().(fmt.Stringer)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return func() tview.Msg {
|
||||
if _, err := clipboard.Write(context.Background(), clipboard.FmtText, []byte(id.String())); err != nil {
|
||||
slog.Error("failed to write to clipboard", "err", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -103,11 +103,28 @@ func (ml *Model) listView(focused bool) list.Widget {
|
|||
return list.New(&ml.selectionState, len(ml.items), ml.buildItem).
|
||||
SelectedStyle(ml.cfg.Theme.MessagesList.SelectedMessageStyle.Style).
|
||||
ScrollBar(ml.scrollBar, ml.scrollBarVisibility).
|
||||
Keybind(ui.ScrollKeybind(ml.cfg.Keybinds.MessagesList.ScrollKeybinds)).
|
||||
Keybind(scrollKeybind(ml.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, bool) {
|
||||
return func(key tview.KeyMsg) (list.Action, bool) {
|
||||
switch {
|
||||
case keybind.Matches(key, kbs.ScrollUp.Keybind):
|
||||
return list.ActionScrollUp, true
|
||||
case keybind.Matches(key, kbs.ScrollDown.Keybind):
|
||||
return list.ActionScrollDown, true
|
||||
case keybind.Matches(key, kbs.ScrollTop.Keybind):
|
||||
return list.ActionScrollTop, true
|
||||
case keybind.Matches(key, kbs.ScrollBottom.Keybind):
|
||||
return list.ActionScrollBottom, true
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
}
|
||||
|
||||
func (ml *Model) cursor() int {
|
||||
return ml.selectionState.Cursor()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,8 +5,6 @@ import (
|
|||
"github.com/ayn2op/tview"
|
||||
"github.com/ayn2op/tview/keybind"
|
||||
"github.com/ayn2op/tview/list"
|
||||
"github.com/ayn2op/tview/picker"
|
||||
"github.com/ayn2op/tview/tree"
|
||||
)
|
||||
|
||||
// SelectionKeybind binds kbs to moving a list's selection.
|
||||
|
|
@ -25,53 +23,3 @@ func SelectionKeybind(kbs config.SelectionKeybinds) func(tview.KeyMsg) (list.Act
|
|||
return 0, false
|
||||
}
|
||||
}
|
||||
|
||||
// ScrollKeybind binds kbs to scrolling a list.
|
||||
func ScrollKeybind(kbs config.ScrollKeybinds) func(tview.KeyMsg) (list.Action, bool) {
|
||||
return func(key tview.KeyMsg) (list.Action, bool) {
|
||||
switch {
|
||||
case keybind.Matches(key, kbs.ScrollUp.Keybind):
|
||||
return list.ActionScrollUp, true
|
||||
case keybind.Matches(key, kbs.ScrollDown.Keybind):
|
||||
return list.ActionScrollDown, true
|
||||
case keybind.Matches(key, kbs.ScrollTop.Keybind):
|
||||
return list.ActionScrollTop, true
|
||||
case keybind.Matches(key, kbs.ScrollBottom.Keybind):
|
||||
return list.ActionScrollBottom, true
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
}
|
||||
|
||||
// TreeKeybind binds kbs to moving through a tree and selecting its nodes.
|
||||
func TreeKeybind(kbs config.GuildsTreeKeybinds) func(tview.KeyMsg) (tree.Action, bool) {
|
||||
return func(key tview.KeyMsg) (tree.Action, bool) {
|
||||
switch {
|
||||
case keybind.Matches(key, kbs.SelectUp.Keybind):
|
||||
return tree.ActionUp, true
|
||||
case keybind.Matches(key, kbs.SelectDown.Keybind):
|
||||
return tree.ActionDown, true
|
||||
case keybind.Matches(key, kbs.SelectTop.Keybind):
|
||||
return tree.ActionTop, true
|
||||
case keybind.Matches(key, kbs.SelectBottom.Keybind):
|
||||
return tree.ActionBottom, true
|
||||
case keybind.Matches(key, kbs.MoveToParentNode.Keybind):
|
||||
return tree.ActionMoveToParent, true
|
||||
case keybind.Matches(key, kbs.SelectCurrent.Keybind):
|
||||
return tree.ActionSelect, true
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
}
|
||||
|
||||
func pickerKeybind(kbs config.PickerKeybinds) func(tview.KeyMsg) (picker.Action, bool) {
|
||||
return func(key tview.KeyMsg) (picker.Action, bool) {
|
||||
switch {
|
||||
case keybind.Matches(key, kbs.Select.Keybind):
|
||||
return picker.ActionSelect, true
|
||||
case keybind.Matches(key, kbs.Cancel.Keybind):
|
||||
return picker.ActionCancel, true
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
}
|
||||
|
|
|
|||
74
internal/ui/picker/picker.go
Normal file
74
internal/ui/picker/picker.go
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
// Package picker holds what discordo's pickers share.
|
||||
package picker
|
||||
|
||||
import (
|
||||
"github.com/ayn2op/discordo/internal/config"
|
||||
"github.com/ayn2op/discordo/internal/ui"
|
||||
"github.com/ayn2op/tview"
|
||||
"github.com/ayn2op/tview/keybind"
|
||||
"github.com/ayn2op/tview/picker"
|
||||
"github.com/ayn2op/tview/scrollbar"
|
||||
)
|
||||
|
||||
// Model is a picker of items shown in a box titled title.
|
||||
type Model struct {
|
||||
items picker.Items
|
||||
searchState picker.SearchState
|
||||
cfg *config.Config
|
||||
title string
|
||||
}
|
||||
|
||||
func NewModel(cfg *config.Config, title string) Model {
|
||||
return Model{searchState: picker.NewSearchState(), cfg: cfg, title: title}
|
||||
}
|
||||
|
||||
// changeMsg changes the picker.
|
||||
type changeMsg picker.Change
|
||||
|
||||
func (Model) Init() tview.Cmd { return nil }
|
||||
|
||||
// View shows the picker, turning the selected item into the message onSelect returns and canceling with onCancel.
|
||||
func (m Model) View(onSelect func(picker.Item) tview.Msg, onCancel tview.Msg) tview.Element {
|
||||
cfg := m.cfg
|
||||
bar := scrollbar.New().
|
||||
TrackStyle(cfg.Theme.ScrollBar.TrackStyle.Style).
|
||||
ThumbStyle(cfg.Theme.ScrollBar.ThumbStyle.Style).
|
||||
GlyphSet(cfg.Theme.ScrollBar.GlyphSet.GlyphSet).
|
||||
Arrows(scrollbar.ArrowsBoth)
|
||||
p := picker.New(m.items, &m.searchState).
|
||||
ScrollBar(bar, cfg.Theme.ScrollBar.Visibility.ScrollBarVisibility).
|
||||
Keybind(bindKeys(cfg.Keybinds.Picker)).
|
||||
ListKeybind(ui.SelectionKeybind(cfg.Keybinds.Picker.SelectionKeybinds)).
|
||||
OnChange(func(c picker.Change) tview.Msg { return changeMsg(c) }).
|
||||
OnSelect(onSelect).
|
||||
OnCancel(onCancel)
|
||||
return ui.Box(p, &cfg.Theme, true).Title(m.title)
|
||||
}
|
||||
|
||||
func (m Model) Update(msg tview.Msg) (Model, tview.Cmd) {
|
||||
if msg, ok := msg.(changeMsg); ok {
|
||||
m.searchState.Apply(picker.Change(msg))
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// SetItems replaces the items and clears the query.
|
||||
func (m *Model) SetItems(items picker.Items) {
|
||||
m.items = items
|
||||
m.searchState.Reset()
|
||||
}
|
||||
|
||||
// Reset clears the query.
|
||||
func (m *Model) Reset() { m.searchState.Reset() }
|
||||
|
||||
func bindKeys(kbs config.PickerKeybinds) func(tview.KeyMsg) (picker.Action, bool) {
|
||||
return func(key tview.KeyMsg) (picker.Action, bool) {
|
||||
switch {
|
||||
case keybind.Matches(key, kbs.Select.Keybind):
|
||||
return picker.ActionSelect, true
|
||||
case keybind.Matches(key, kbs.Cancel.Keybind):
|
||||
return picker.ActionCancel, true
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
}
|
||||
77
internal/ui/tree/tree.go
Normal file
77
internal/ui/tree/tree.go
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
// Package tree holds what discordo's trees share.
|
||||
package tree
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
|
||||
"github.com/ayn2op/discordo/internal/config"
|
||||
"github.com/ayn2op/discordo/internal/ui"
|
||||
"github.com/ayn2op/tview"
|
||||
"github.com/ayn2op/tview/box"
|
||||
"github.com/ayn2op/tview/keybind"
|
||||
"github.com/ayn2op/tview/tree"
|
||||
"github.com/gdamore/tcell/v3"
|
||||
"golang.design/x/clipboard"
|
||||
)
|
||||
|
||||
// New returns a boxed tree of the nodes under root, drawn with theme and moved with kbs, that turns changes into the message onChange returns.
|
||||
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) box.Widget {
|
||||
set := cfg.Theme.Border.NormalSet.BorderSet
|
||||
if focused {
|
||||
set = cfg.Theme.Border.ActiveSet.BorderSet
|
||||
}
|
||||
t := tree.New(root, selectionState).
|
||||
TopLevel(1).
|
||||
Markers(tree.Markers{
|
||||
Expanded: cfg.Sidebar.Markers.Expanded,
|
||||
Collapsed: cfg.Sidebar.Markers.Collapsed,
|
||||
Leaf: cfg.Sidebar.Markers.Leaf,
|
||||
}).
|
||||
Graphics(theme.Graphics).
|
||||
GraphicsSet(set).
|
||||
GraphicsStyle(tcell.StyleDefault.Foreground(tcell.GetColor(theme.GraphicsColor))).
|
||||
Keybind(bindKeys(kbs)).
|
||||
Focused(focused).
|
||||
OnChange(onChange)
|
||||
return ui.Box(t, &cfg.Theme, focused)
|
||||
}
|
||||
|
||||
func bindKeys(kbs config.TreeKeybinds) func(tview.KeyMsg) (tree.Action, bool) {
|
||||
return func(key tview.KeyMsg) (tree.Action, bool) {
|
||||
switch {
|
||||
case keybind.Matches(key, kbs.SelectUp.Keybind):
|
||||
return tree.ActionUp, true
|
||||
case keybind.Matches(key, kbs.SelectDown.Keybind):
|
||||
return tree.ActionDown, true
|
||||
case keybind.Matches(key, kbs.SelectTop.Keybind):
|
||||
return tree.ActionTop, true
|
||||
case keybind.Matches(key, kbs.SelectBottom.Keybind):
|
||||
return tree.ActionBottom, true
|
||||
case keybind.Matches(key, kbs.MoveToParentNode.Keybind):
|
||||
return tree.ActionMoveToParent, true
|
||||
case keybind.Matches(key, kbs.SelectCurrent.Keybind):
|
||||
return tree.ActionSelect, true
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
}
|
||||
|
||||
// YankID copies the ID of node to the clipboard.
|
||||
func YankID(node *tree.Node) tview.Cmd {
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
id, ok := node.Reference().(fmt.Stringer)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return func() tview.Msg {
|
||||
if _, err := clipboard.Write(context.Background(), clipboard.FmtText, []byte(id.String())); err != nil {
|
||||
slog.Error("failed to write to clipboard", "err", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
|
@ -10,8 +10,6 @@ import (
|
|||
"github.com/ayn2op/ningen/v3"
|
||||
"github.com/ayn2op/tview"
|
||||
"github.com/ayn2op/tview/box"
|
||||
"github.com/ayn2op/tview/picker"
|
||||
"github.com/ayn2op/tview/scrollbar"
|
||||
)
|
||||
|
||||
// Box returns child in a box styled by the theme, highlighted while focused.
|
||||
|
|
@ -36,19 +34,6 @@ func Box(child tview.Element, theme *config.Theme, focused bool) box.Widget {
|
|||
FooterStyle(theme.Footer.NormalStyle.Style)
|
||||
}
|
||||
|
||||
// Picker returns a picker of items searched with searchState, with the configured scroll bar and keybinds.
|
||||
func Picker(items picker.Items, searchState *picker.SearchState, cfg *config.Config) picker.Widget {
|
||||
bar := scrollbar.New().
|
||||
TrackStyle(cfg.Theme.ScrollBar.TrackStyle.Style).
|
||||
ThumbStyle(cfg.Theme.ScrollBar.ThumbStyle.Style).
|
||||
GlyphSet(cfg.Theme.ScrollBar.GlyphSet.GlyphSet).
|
||||
Arrows(scrollbar.ArrowsBoth)
|
||||
return picker.New(items, searchState).
|
||||
ScrollBar(bar, cfg.Theme.ScrollBar.Visibility.ScrollBarVisibility).
|
||||
Keybind(pickerKeybind(cfg.Keybinds.Picker)).
|
||||
ListKeybind(SelectionKeybind(cfg.Keybinds.Picker.SelectionKeybinds))
|
||||
}
|
||||
|
||||
// ChannelToString returns how channel is shown in the UI: an icon for its type and its name, or the recipients' names for a DM.
|
||||
func ChannelToString(channel discord.Channel, icons config.Icons, state *ningen.State) string {
|
||||
var icon string
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue