package widgets import ( "errors" h "git.bullercodeworks.com/brian/expds/helpers" t "git.bullercodeworks.com/brian/tcell-widgets" th "git.bullercodeworks.com/brian/tcell-widgets/helpers" wh "git.bullercodeworks.com/brian/tcell-widgets/helpers" "github.com/gdamore/tcell" ) type TreeBrowser struct { id string title string style tcell.Style active bool visible bool focusable bool x, y int w, h int border []rune list []string listNodes []*TreeNode cursor int cursorWrap bool nodes []*TreeNode onChange func(*TreeNode) bool onSelect func(*TreeNode) bool keyMap *t.KeyMap vimMode bool logger func(string, ...any) } var _ t.Widget = (*TreeBrowser)(nil) func NewTreeBrowser(id string, s tcell.Style) *TreeBrowser { ret := &TreeBrowser{id: id, style: s} ret.Init(id, s) return ret } func (w *TreeBrowser) Init(id string, style tcell.Style) { w.visible = true w.focusable = true w.keyMap = t.NewKeyMap( t.NewKey(t.BuildEK(tcell.KeyUp), func(_ *tcell.EventKey) bool { return w.MoveUp() }), t.NewKey(t.BuildEK(tcell.KeyDown), func(_ *tcell.EventKey) bool { return w.MoveDown() }), t.NewKey(t.BuildEK(tcell.KeyEnter), func(ev *tcell.EventKey) bool { if w.onSelect != nil { n, err := w.GetActiveNode() if err != nil || n == nil { return false } w.onSelect(n) return true } return false }), t.NewKey(t.BuildEK(tcell.KeyPgDn), func(_ *tcell.EventKey) bool { return w.PageDn() }), t.NewKey(t.BuildEK(tcell.KeyPgUp), func(_ *tcell.EventKey) bool { return w.PageUp() }), t.NewKey(t.BuildEKr('j'), func(ev *tcell.EventKey) bool { if !w.vimMode { return false } return w.MoveDown() }), t.NewKey(t.BuildEKr('k'), func(ev *tcell.EventKey) bool { if !w.vimMode { return false } return w.MoveUp() }), t.NewKey(t.BuildEKr('b'), func(ev *tcell.EventKey) bool { if !w.vimMode { return false } if ev.Modifiers()&tcell.ModCtrl != 0 { return w.PageUp() } return false }), t.NewKey(t.BuildEKr('f'), func(ev *tcell.EventKey) bool { if !w.vimMode { return false } if ev.Modifiers()&tcell.ModCtrl != 0 { return w.PageDn() } return false }), ) } func (w *TreeBrowser) Id() string { return w.id } func (w *TreeBrowser) HandleResize(ev *tcell.EventResize) { w.w, w.h = ev.Size() } func (w *TreeBrowser) GetKeyMap() *t.KeyMap { return w.keyMap } func (w *TreeBrowser) SetKeyMap(km *t.KeyMap) { w.keyMap = km } func (w *TreeBrowser) HandleKey(ev *tcell.EventKey) bool { if !w.active || !w.focusable { return false } return w.keyMap.Handle(ev) } func (w *TreeBrowser) HandleTime(ev *tcell.EventTime) {} func (w *TreeBrowser) Draw(screen tcell.Screen) { if !w.visible { return } dS := w.style if !w.active { dS = dS.Dim(true) } x, y := w.x, w.y brdSz := 0 if len(w.border) > 0 { brdSz = 2 if len(w.title) > 0 { th.TitledBorderFilled(x, y, x+w.w, y+w.h, w.title, w.border, dS, screen) } else { th.BorderFilled(x, y, x+w.w, y+w.h, w.border, dS, screen) } } x, y = x+1, y+1 h := w.h - brdSz for i := range w.list { th.DrawText(x, y, w.list[i], w.style.Reverse(i == w.cursor), screen) y++ if y > x+h { break } } } func (w *TreeBrowser) SetStyle(s tcell.Style) { w.style = s } func (w *TreeBrowser) Active() bool { return false } func (w *TreeBrowser) SetActive(a bool) {} func (w *TreeBrowser) Visible() bool { return w.visible } func (w *TreeBrowser) SetVisible(a bool) { w.visible = a } func (w *TreeBrowser) Focusable() bool { return false } func (w *TreeBrowser) SetFocusable(b bool) {} func (w *TreeBrowser) SetX(x int) { w.SetPos(t.Coord{X: x, Y: w.y}) } func (w *TreeBrowser) SetY(y int) { w.SetPos(t.Coord{X: w.x, Y: y}) } func (w *TreeBrowser) GetX() int { return w.x } func (w *TreeBrowser) GetY() int { return w.y } func (w *TreeBrowser) GetPos() t.Coord { return t.Coord{X: w.x, Y: w.y} } func (w *TreeBrowser) SetPos(c t.Coord) { w.x, w.y = c.X, c.Y } func (w *TreeBrowser) GetW() int { return w.w } func (w *TreeBrowser) GetH() int { return w.h } func (w *TreeBrowser) SetW(wd int) { w.SetSize(t.Coord{X: wd, Y: w.h}) } func (w *TreeBrowser) SetH(h int) { w.SetSize(t.Coord{X: w.w, Y: h}) } func (w *TreeBrowser) SetSize(c t.Coord) { w.w, w.h = c.X, c.Y } func (w *TreeBrowser) WantW() int { var want int for i := range w.list { want = h.MaxI(want, len(w.list[i])) } return w.w } func (w *TreeBrowser) WantH() int { want := len(w.list) if len(w.border) > 0 { return want + 2 } return want } func (w *TreeBrowser) MinW() int { return w.w } func (w *TreeBrowser) MinH() int { return 5 } func (w *TreeBrowser) SetLogger(l func(string, ...any)) { w.logger = l } func (w *TreeBrowser) Log(txt string, args ...any) { w.logger(txt, args...) } func (w *TreeBrowser) SetBorder(brd []rune) { if len(brd) == 0 { w.border = wh.BRD_SIMPLE } else { w.border = wh.ValidateBorder(brd) } } func (w *TreeBrowser) ClearBorder() { w.border = []rune{} } func (w *TreeBrowser) SetOnChange(c func(*TreeNode) bool) { w.onChange = c } func (w *TreeBrowser) SetOnSelect(s func(*TreeNode) bool) { w.onSelect = s } func (w *TreeBrowser) SetVimMode(b bool) { w.vimMode = b } func (w *TreeBrowser) GetActiveNode() (*TreeNode, error) { if len(w.listNodes) < 0 { return nil, errors.New("no nodes") } if w.cursor < 0 { return w.listNodes[0], nil } if w.cursor >= len(w.listNodes) { return w.listNodes[len(w.listNodes)-1], nil } return w.listNodes[w.cursor], nil } func (w *TreeBrowser) SetCursorWrap(b bool) { w.cursorWrap = b } func (w *TreeBrowser) MoveUp() bool { if w.cursor > 0 { w.cursor-- if w.onChange != nil { n, err := w.GetActiveNode() if err == nil && n != nil { w.onChange(n) } } return true } else if w.cursorWrap { w.cursor = len(w.list) - 1 if w.onChange != nil { n, err := w.GetActiveNode() if err == nil && n != nil { w.onChange(n) } } return true } return false } func (w *TreeBrowser) MoveDown() bool { if w.cursor <= len(w.list) { w.cursor++ if w.onChange != nil { n, err := w.GetActiveNode() if err == nil && n != nil { w.onChange(n) } } return true } else if w.cursorWrap { w.cursor = 0 if w.WantH() > w.cursor && w.onChange != nil { n, err := w.GetActiveNode() if err == nil && n != nil { w.onChange(n) } } return true } return false } func (w *TreeBrowser) PageUp() bool { w.cursor -= w.h if len(w.border) > 0 { w.cursor += 2 } if w.cursor < 0 { w.cursor = 0 } return true } func (w *TreeBrowser) PageDn() bool { w.cursor += w.h if len(w.border) > 0 { w.cursor -= 1 } if w.cursor > len(w.list)-1 { w.cursor = len(w.list) - 1 } return true } func (w *TreeBrowser) Title() string { return w.title } func (w *TreeBrowser) SetTitle(ttl string) { w.title = ttl } func (w *TreeBrowser) SetTree(l []*TreeNode) { w.nodes = l w.updateList() } func (w *TreeBrowser) Clear() { w.nodes = []*TreeNode{} w.updateList() } func (w *TreeBrowser) Add(n *TreeNode) { w.nodes = append(w.nodes, n) w.updateList() } func (w *TreeBrowser) updateList() { w.list = []string{} w.listNodes = []*TreeNode{} for i := range w.nodes { w.list = append(w.list, w.nodes[i].getList()...) w.listNodes = append(w.listNodes, w.nodes[i].getVisibleNodeList()...) } if w.cursor >= len(w.list) { w.cursor = len(w.list) - 1 } } /* * Tree Node */ type TreeNode struct { label string value string expanded bool parent *TreeNode children []*TreeNode } func NewTreeNode(l, v string) *TreeNode { return &TreeNode{ label: l, value: v, } } func (tn *TreeNode) getList() []string { ret := []string{tn.label} if tn.expanded { for i := range tn.children { ret = append(ret, tn.children[i].getList()...) } } return ret } func (tn *TreeNode) getVisibleNodeList() []*TreeNode { ret := []*TreeNode{tn} if tn.expanded { for i := range tn.children { ret = append(ret, tn.children[i].getVisibleNodeList()...) } } return ret } func (tn *TreeNode) ToggleExpand() { tn.expanded = !tn.expanded } func (tn *TreeNode) AddChild(t *TreeNode, rest ...*TreeNode) { t.parent = tn tn.children = append(tn.children, t) for i := range rest { rest[i].parent = tn tn.children = append(tn.children, rest[i]) } }