~pierrec/giox

ref: c164e218831e giox/widgetx/tree.go -rw-r--r-- 2.4 KiB
c164e218Pierre Curto cm/iconx: update the set of icons 9 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
package widgetx

import (
	"gioui.org/io/pointer"
	"gioui.org/layout"
	"gioui.org/widget"
)

// TreeNode contains information about a node.
type TreeNode struct {
	ID          int
	ChildrenNum int
	Depth       int
}

// TreeElement lays out the TreeNode element.
type TreeElement func(layout.Context, *widget.Bool, TreeNode) layout.Dimensions

// Tree is a simple tree data structure that can lay itself out.
// Tree items are referenced by a unique id.
// Clicking on an item toggles the visibility of its children.
type Tree struct {
	Root     func() []int
	Children func(id int) []int
	Axis     layout.Axis

	list    layout.List
	visible []TreeNode
	opened  map[int]*widget.Bool
}

func (t *Tree) init() {
	if t.opened == nil {
		t.opened = make(map[int]*widget.Bool)
	}
	if t.list.Axis != t.Axis {
		t.list.Axis = t.Axis
	}
}

func (t *Tree) updateVisible(ids []int, depth int) {
	for _, id := range ids {
		children := t.Children(id)
		t.visible = append(t.visible, TreeNode{
			ID:          id,
			ChildrenNum: len(children),
			Depth:       depth,
		})
		if len(children) > 0 {
			if v, ok := t.opened[id]; !ok {
				t.opened[id] = new(widget.Bool)
			} else if v.Value {
				t.updateVisible(children, depth+1)
			}
		}
	}
}

func (t *Tree) update() {
	for _, node := range t.visible {
		if v, ok := t.opened[node.ID]; ok && v.Changed() {
			t.visible = t.visible[:0]
			break
		}
	}
	if len(t.visible) == 0 {
		t.updateVisible(t.Root(), 0)
	}
}

func (t *Tree) Layout(gtx layout.Context, el TreeElement) layout.Dimensions {
	t.init()
	t.update()
	return t.list.Layout(gtx, len(t.visible), func(gtx layout.Context, i int) layout.Dimensions {
		node := t.visible[i]
		click := t.opened[node.ID]
		if click == nil {
			return el(gtx, click, node)
		}
		defer pointer.PassOp{}.Push(gtx.Ops).Pop()
		return click.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
			return el(gtx, click, node)
		})
	})
}

// Reset is to be called when the layout of the tree has changed or its number of elements.
//
// Must be called before any of its element is to be laid out.
func (t *Tree) Reset() {
	t.visible = t.visible[:0]
}

func (t *Tree) Closed(id int) bool {
	v, ok := t.opened[id]
	return !ok || !v.Value
}

func (t *Tree) setState(id int, state bool) {
	if v, ok := t.opened[id]; ok {
		v.Value = state
	}
}

func (t *Tree) Close(id int) {
	t.setState(id, false)
}

func (t *Tree) Open(id int) {
	t.setState(id, true)
}