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