1 package tview
2
3 import (
4 "github.com/gdamore/tcell/v2"
5 )
6
7
8 const (
9 treeNone int = iota
10 treeHome
11 treeEnd
12 treeMove
13 treeParent
14 treeChild
15 treeScroll
16 )
17
18
19 type TreeNode struct {
20
21 reference interface{}
22
23
24 children []*TreeNode
25
26
27 text string
28
29
30 color tcell.Color
31
32
33 selectable bool
34
35
36 expanded bool
37
38
39 indent int
40
41
42 selected func()
43
44
45
46
47 level int
48
49
50 parent *TreeNode
51 graphicsX int
52 textX int
53 }
54
55
56 func NewTreeNode(text string) *TreeNode {
57 return &TreeNode{
58 text: text,
59 color: Styles.PrimaryTextColor,
60 indent: 2,
61 expanded: true,
62 selectable: true,
63 }
64 }
65
66
67
68
69
70
71 func (n *TreeNode) Walk(callback func(node, parent *TreeNode) bool) *TreeNode {
72 n.parent = nil
73 nodes := []*TreeNode{n}
74 for len(nodes) > 0 {
75
76 node := nodes[len(nodes)-1]
77 nodes = nodes[:len(nodes)-1]
78 if !callback(node, node.parent) {
79
80 continue
81 }
82
83
84 for index := len(node.children) - 1; index >= 0; index-- {
85 node.children[index].parent = node
86 nodes = append(nodes, node.children[index])
87 }
88 }
89
90 return n
91 }
92
93
94
95
96 func (n *TreeNode) SetReference(reference interface{}) *TreeNode {
97 n.reference = reference
98 return n
99 }
100
101
102 func (n *TreeNode) GetReference() interface{} {
103 return n.reference
104 }
105
106
107 func (n *TreeNode) SetChildren(childNodes []*TreeNode) *TreeNode {
108 n.children = childNodes
109 return n
110 }
111
112
113 func (n *TreeNode) GetText() string {
114 return n.text
115 }
116
117
118 func (n *TreeNode) GetChildren() []*TreeNode {
119 return n.children
120 }
121
122
123 func (n *TreeNode) ClearChildren() *TreeNode {
124 n.children = nil
125 return n
126 }
127
128
129 func (n *TreeNode) AddChild(node *TreeNode) *TreeNode {
130 n.children = append(n.children, node)
131 return n
132 }
133
134
135
136 func (n *TreeNode) RemoveChild(node *TreeNode) *TreeNode {
137 for index, child := range n.children {
138 if child == node {
139 n.children = append(n.children[:index], n.children[index+1:]...)
140 break
141 }
142 }
143 return n
144 }
145
146
147
148 func (n *TreeNode) SetSelectable(selectable bool) *TreeNode {
149 n.selectable = selectable
150 return n
151 }
152
153
154
155 func (n *TreeNode) SetSelectedFunc(handler func()) *TreeNode {
156 n.selected = handler
157 return n
158 }
159
160
161 func (n *TreeNode) SetExpanded(expanded bool) *TreeNode {
162 n.expanded = expanded
163 return n
164 }
165
166
167 func (n *TreeNode) Expand() *TreeNode {
168 n.expanded = true
169 return n
170 }
171
172
173 func (n *TreeNode) Collapse() *TreeNode {
174 n.expanded = false
175 return n
176 }
177
178
179 func (n *TreeNode) ExpandAll() *TreeNode {
180 n.Walk(func(node, parent *TreeNode) bool {
181 node.expanded = true
182 return true
183 })
184 return n
185 }
186
187
188 func (n *TreeNode) CollapseAll() *TreeNode {
189 n.Walk(func(node, parent *TreeNode) bool {
190 node.expanded = false
191 return true
192 })
193 return n
194 }
195
196
197 func (n *TreeNode) IsExpanded() bool {
198 return n.expanded
199 }
200
201
202 func (n *TreeNode) SetText(text string) *TreeNode {
203 n.text = text
204 return n
205 }
206
207
208 func (n *TreeNode) GetColor() tcell.Color {
209 return n.color
210 }
211
212
213 func (n *TreeNode) SetColor(color tcell.Color) *TreeNode {
214 n.color = color
215 return n
216 }
217
218
219
220
221 func (n *TreeNode) SetIndent(indent int) *TreeNode {
222 n.indent = indent
223 return n
224 }
225
226
227
228
229
230 func (n *TreeNode) GetLevel() int {
231 return n.level
232 }
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268 type TreeView struct {
269 *Box
270
271
272 root *TreeNode
273
274
275 currentNode *TreeNode
276
277
278 lastNode *TreeNode
279
280
281
282 movement int
283
284
285
286
287 step int
288
289
290 topLevel int
291
292
293 prefixes []string
294
295
296 offsetY int
297
298
299 align bool
300
301
302 graphics bool
303
304
305 graphicsColor tcell.Color
306
307
308
309 changed func(node *TreeNode)
310
311
312 selected func(node *TreeNode)
313
314
315
316 done func(key tcell.Key)
317
318
319 nodes []*TreeNode
320
321
322
323 stableNodes bool
324 }
325
326
327 func NewTreeView() *TreeView {
328 return &TreeView{
329 Box: NewBox(),
330 graphics: true,
331 graphicsColor: Styles.GraphicsColor,
332 }
333 }
334
335
336 func (t *TreeView) SetRoot(root *TreeNode) *TreeView {
337 t.root = root
338 return t
339 }
340
341
342
343 func (t *TreeView) GetRoot() *TreeNode {
344 return t.root
345 }
346
347
348
349
350
351
352
353
354 func (t *TreeView) SetCurrentNode(node *TreeNode) *TreeView {
355 t.currentNode = node
356 return t
357 }
358
359
360
361 func (t *TreeView) GetCurrentNode() *TreeNode {
362 return t.currentNode
363 }
364
365
366
367
368 func (t *TreeView) SetTopLevel(topLevel int) *TreeView {
369 t.topLevel = topLevel
370 return t
371 }
372
373
374
375
376
377
378
379
380
381
382
383
384 func (t *TreeView) SetPrefixes(prefixes []string) *TreeView {
385 t.prefixes = prefixes
386 return t
387 }
388
389
390
391
392 func (t *TreeView) SetAlign(align bool) *TreeView {
393 t.align = align
394 return t
395 }
396
397
398
399 func (t *TreeView) SetGraphics(showGraphics bool) *TreeView {
400 t.graphics = showGraphics
401 return t
402 }
403
404
405 func (t *TreeView) SetGraphicsColor(color tcell.Color) *TreeView {
406 t.graphicsColor = color
407 return t
408 }
409
410
411
412 func (t *TreeView) SetChangedFunc(handler func(node *TreeNode)) *TreeView {
413 t.changed = handler
414 return t
415 }
416
417
418
419 func (t *TreeView) SetSelectedFunc(handler func(node *TreeNode)) *TreeView {
420 t.selected = handler
421 return t
422 }
423
424
425
426 func (t *TreeView) SetDoneFunc(handler func(key tcell.Key)) *TreeView {
427 t.done = handler
428 return t
429 }
430
431
432
433
434 func (t *TreeView) GetScrollOffset() int {
435 return t.offsetY
436 }
437
438
439
440
441
442 func (t *TreeView) GetRowCount() int {
443 return len(t.nodes)
444 }
445
446
447
448
449
450
451
452
453 func (t *TreeView) Move(offset int) *TreeView {
454 if offset == 0 {
455 return t
456 }
457 t.movement = treeMove
458 t.step = offset
459 t.process(false)
460 return t
461 }
462
463
464
465
466
467 func (t *TreeView) process(drawingAfter bool) {
468 t.stableNodes = drawingAfter
469 _, _, _, height := t.GetInnerRect()
470
471
472 t.nodes = nil
473 if t.root == nil {
474 return
475 }
476 parentSelectedIndex, selectedIndex, topLevelGraphicsX := -1, -1, -1
477 var graphicsOffset, maxTextX int
478 if t.graphics {
479 graphicsOffset = 1
480 }
481 t.root.Walk(func(node, parent *TreeNode) bool {
482
483 node.parent = parent
484 if parent == nil {
485 node.level = 0
486 node.graphicsX = 0
487 node.textX = 0
488 } else {
489 node.level = parent.level + 1
490 node.graphicsX = parent.textX
491 node.textX = node.graphicsX + graphicsOffset + node.indent
492 }
493 if !t.graphics && t.align {
494
495 node.textX = 0
496 }
497 if node.level == t.topLevel {
498
499 node.graphicsX = 0
500 node.textX = 0
501 }
502
503
504 if node.level >= t.topLevel {
505
506 if node.textX > maxTextX {
507 maxTextX = node.textX
508 }
509 if node == t.currentNode && node.selectable {
510 selectedIndex = len(t.nodes)
511
512
513 for index := len(t.nodes) - 1; index >= 0; index-- {
514 if t.nodes[index] == parent && t.nodes[index].selectable {
515 parentSelectedIndex = index
516 break
517 }
518 }
519 }
520
521
522 if t.topLevel == node.level && (topLevelGraphicsX < 0 || node.graphicsX < topLevelGraphicsX) {
523 topLevelGraphicsX = node.graphicsX
524 }
525
526 t.nodes = append(t.nodes, node)
527 }
528
529
530 return node.expanded
531 })
532
533
534 for _, node := range t.nodes {
535
536 if t.align && node.level > t.topLevel {
537 node.textX = maxTextX
538 }
539
540
541 if topLevelGraphicsX > 0 {
542 node.graphicsX -= topLevelGraphicsX
543 node.textX -= topLevelGraphicsX
544 }
545 }
546
547
548 if selectedIndex >= 0 {
549
550 switch t.movement {
551 case treeMove:
552 for t.step < 0 {
553 index := selectedIndex
554 for index > 0 {
555 index--
556 if t.nodes[index].selectable {
557 selectedIndex = index
558 break
559 }
560 }
561 t.step++
562 }
563 for t.step > 0 {
564 index := selectedIndex
565 for index < len(t.nodes)-1 {
566 index++
567 if t.nodes[index].selectable {
568 selectedIndex = index
569 break
570 }
571 }
572 t.step--
573 }
574 case treeParent:
575 if parentSelectedIndex >= 0 {
576 selectedIndex = parentSelectedIndex
577 }
578 case treeChild:
579 index := selectedIndex
580 for index < len(t.nodes)-1 {
581 index++
582 if t.nodes[index].selectable && t.nodes[index].parent == t.nodes[selectedIndex] {
583 selectedIndex = index
584 }
585 }
586 }
587 t.currentNode = t.nodes[selectedIndex]
588
589
590 if t.movement != treeScroll {
591 if selectedIndex-t.offsetY >= height {
592 t.offsetY = selectedIndex - height + 1
593 }
594 if selectedIndex < t.offsetY {
595 t.offsetY = selectedIndex
596 }
597 if t.movement != treeHome && t.movement != treeEnd {
598
599 t.movement = treeNone
600 t.step = 0
601 }
602 }
603 } else {
604
605 if t.currentNode != nil {
606 for index, node := range t.nodes {
607 if node.selectable {
608 selectedIndex = index
609 t.currentNode = node
610 break
611 }
612 }
613 }
614 if selectedIndex < 0 {
615 t.currentNode = nil
616 }
617 }
618
619
620 if t.changed != nil && t.currentNode != nil && t.currentNode != t.lastNode {
621 t.changed(t.currentNode)
622 }
623 t.lastNode = t.currentNode
624 }
625
626
627 func (t *TreeView) Draw(screen tcell.Screen) {
628 t.Box.DrawForSubclass(screen, t)
629 if t.root == nil {
630 return
631 }
632 _, totalHeight := screen.Size()
633
634 if !t.stableNodes {
635 t.process(false)
636 } else {
637 t.stableNodes = false
638 }
639
640
641
642 x, y, width, height := t.GetInnerRect()
643 switch t.movement {
644 case treeMove, treeScroll:
645 t.offsetY += t.step
646 case treeHome:
647 t.offsetY = 0
648 case treeEnd:
649 t.offsetY = len(t.nodes)
650 }
651 t.movement = treeNone
652
653
654 if t.offsetY >= len(t.nodes)-height {
655 t.offsetY = len(t.nodes) - height
656 }
657 if t.offsetY < 0 {
658 t.offsetY = 0
659 }
660
661
662 posY := y
663 lineStyle := tcell.StyleDefault.Background(t.backgroundColor).Foreground(t.graphicsColor)
664 for index, node := range t.nodes {
665
666 if posY >= y+height+1 || posY >= totalHeight {
667 break
668 }
669 if index < t.offsetY {
670 continue
671 }
672
673
674 if t.graphics {
675
676 ancestor := node.parent
677 for ancestor != nil && ancestor.parent != nil && ancestor.parent.level >= t.topLevel {
678 if ancestor.graphicsX >= width {
679 continue
680 }
681
682
683 if ancestor.parent.children[len(ancestor.parent.children)-1] != ancestor {
684 if posY-1 >= y && ancestor.textX > ancestor.graphicsX {
685 PrintJoinedSemigraphics(screen, x+ancestor.graphicsX, posY-1, Borders.Vertical, lineStyle)
686 }
687 if posY < y+height {
688 screen.SetContent(x+ancestor.graphicsX, posY, Borders.Vertical, nil, lineStyle)
689 }
690 }
691 ancestor = ancestor.parent
692 }
693
694 if node.textX > node.graphicsX && node.graphicsX < width {
695
696 if posY-1 >= y && t.nodes[index-1].graphicsX <= node.graphicsX && t.nodes[index-1].textX > node.graphicsX {
697 PrintJoinedSemigraphics(screen, x+node.graphicsX, posY-1, Borders.TopLeft, lineStyle)
698 }
699
700
701 if posY < y+height {
702 screen.SetContent(x+node.graphicsX, posY, Borders.BottomLeft, nil, lineStyle)
703 for pos := node.graphicsX + 1; pos < node.textX && pos < width; pos++ {
704 screen.SetContent(x+pos, posY, Borders.Horizontal, nil, lineStyle)
705 }
706 }
707 }
708 }
709
710
711 if node.textX < width && posY < y+height {
712
713 var prefixWidth int
714 if len(t.prefixes) > 0 {
715 _, prefixWidth = Print(screen, t.prefixes[(node.level-t.topLevel)%len(t.prefixes)], x+node.textX, posY, width-node.textX, AlignLeft, node.color)
716 }
717
718
719 if node.textX+prefixWidth < width {
720 style := tcell.StyleDefault.Background(t.backgroundColor).Foreground(node.color)
721 if node == t.currentNode {
722 style = tcell.StyleDefault.Background(node.color).Foreground(t.backgroundColor)
723 }
724 printWithStyle(screen, node.text, x+node.textX+prefixWidth, posY, 0, width-node.textX-prefixWidth, AlignLeft, style, false)
725 }
726 }
727
728
729 posY++
730 }
731 }
732
733
734 func (t *TreeView) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {
735 return t.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {
736 selectNode := func() {
737 node := t.currentNode
738 if node != nil {
739 if t.selected != nil {
740 t.selected(node)
741 }
742 if node.selected != nil {
743 node.selected()
744 }
745 }
746 }
747
748
749
750 switch key := event.Key(); key {
751 case tcell.KeyTab, tcell.KeyBacktab, tcell.KeyEscape:
752 if t.done != nil {
753 t.done(key)
754 }
755 case tcell.KeyDown, tcell.KeyRight:
756 t.movement = treeMove
757 t.step = 1
758 case tcell.KeyUp, tcell.KeyLeft:
759 t.movement = treeMove
760 t.step = -1
761 case tcell.KeyHome:
762 t.movement = treeHome
763 case tcell.KeyEnd:
764 t.movement = treeEnd
765 case tcell.KeyPgDn, tcell.KeyCtrlF:
766 _, _, _, height := t.GetInnerRect()
767 t.movement = treeMove
768 t.step = height
769 case tcell.KeyPgUp, tcell.KeyCtrlB:
770 _, _, _, height := t.GetInnerRect()
771 t.movement = treeMove
772 t.step = -height
773 case tcell.KeyRune:
774 switch event.Rune() {
775 case 'g':
776 t.movement = treeHome
777 case 'G':
778 t.movement = treeEnd
779 case 'j':
780 t.movement = treeMove
781 t.step = 1
782 case 'J':
783 t.movement = treeChild
784 case 'k':
785 t.movement = treeMove
786 t.step = -1
787 case 'K':
788 t.movement = treeParent
789 case ' ':
790 selectNode()
791 }
792 case tcell.KeyEnter:
793 selectNode()
794 }
795
796 t.process(true)
797 })
798 }
799
800
801 func (t *TreeView) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
802 return t.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
803 x, y := event.Position()
804 if !t.InRect(x, y) {
805 return false, nil
806 }
807
808 switch action {
809 case MouseLeftDown:
810 setFocus(t)
811 consumed = true
812 case MouseLeftClick:
813 _, rectY, _, _ := t.GetInnerRect()
814 y += t.offsetY - rectY
815 if y >= 0 && y < len(t.nodes) {
816 node := t.nodes[y]
817 if node.selectable {
818 previousNode := t.currentNode
819 t.currentNode = node
820 if previousNode != node && t.changed != nil {
821 t.changed(node)
822 }
823 if t.selected != nil {
824 t.selected(node)
825 }
826 if node.selected != nil {
827 node.selected()
828 }
829 }
830 }
831 consumed = true
832 case MouseScrollUp:
833 t.movement = treeScroll
834 t.step = -1
835 consumed = true
836 case MouseScrollDown:
837 t.movement = treeScroll
838 t.step = 1
839 consumed = true
840 }
841
842 return
843 })
844 }
845
View as plain text