forked from g3n/engine
-
Notifications
You must be signed in to change notification settings - Fork 0
/
tree.go
465 lines (390 loc) · 10.6 KB
/
tree.go
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
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
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
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
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
// Copyright 2016 The G3N Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package gui
import (
"github.com/g3n/engine/math32"
"github.com/g3n/engine/window"
)
// Tree is the tree structure GUI element.
type Tree struct {
List // Embedded list panel
styles *TreeStyles // Pointer to styles
}
// TreeStyles contains the styling of all tree components for each valid GUI state.
type TreeStyles struct {
List *ListStyles // Styles for the embedded list
Node *TreeNodeStyles // Styles for the node panel
Padlevel float32 // Left padding indentation
}
// TreeNodeStyles contains a TreeNodeStyle for each valid GUI state.
type TreeNodeStyles struct {
Normal TreeNodeStyle
}
// TreeNodeStyle contains the styling of a TreeNode.
type TreeNodeStyle struct {
PanelStyle
FgColor math32.Color4
Icons [2]string
}
// TreeNode is a tree node.
type TreeNode struct {
Panel // Embedded panel
label Label // Node label
icon Label // Node icon
tree *Tree // Parent tree
parNode *TreeNode // Parent node
items []IPanel // List of node items
expanded bool // Node expanded flag
}
// NewTree creates and returns a pointer to a new tree widget.
func NewTree(width, height float32) *Tree {
t := new(Tree)
t.Initialize(width, height)
return t
}
// Initialize initializes the tree with the specified initial width and height
// It is normally used when the folder is embedded in another object.
func (t *Tree) Initialize(width, height float32) {
t.List.initialize(true, width, height)
t.SetStyles(&StyleDefault().Tree)
t.List.Subscribe(OnKeyDown, t.onKey)
t.List.Subscribe(OnKeyUp, t.onKey)
t.List.Subscribe(OnCursor, t.onCursor)
}
// SetStyles sets the tree styles overriding the default style.
func (t *Tree) SetStyles(s *TreeStyles) {
t.styles = s
t.List.SetStyles(t.styles.List)
t.update()
}
// InsertAt inserts a child panel at the specified position in the tree.
func (t *Tree) InsertAt(pos int, child IPanel) {
t.List.InsertAt(pos, child)
}
// Add child panel to the end tree.
func (t *Tree) Add(ichild IPanel) {
t.List.Add(ichild)
}
// InsertNodeAt inserts at the specified position a new tree node
// with the specified text at the end of this tree
// and returns pointer to the new node.
func (t *Tree) InsertNodeAt(pos int, text string) *TreeNode {
n := newTreeNode(text, t, nil)
n.update()
n.recalc()
t.List.InsertAt(pos, n)
return n
}
// AddNode adds a new tree node with the specified text
// at the end of this tree and returns a pointer to the new node.
func (t *Tree) AddNode(text string) *TreeNode {
n := newTreeNode(text, t, nil)
n.update()
n.recalc()
t.List.Add(n)
return n
}
// Remove removes the specified child from the tree or any
// of its children nodes.
func (t *Tree) Remove(child IPanel) {
for idx := 0; idx < t.List.Len(); idx++ {
curr := t.List.ItemAt(idx)
if curr == child {
node, ok := curr.(*TreeNode)
if ok {
node.remove()
} else {
t.List.Remove(child)
}
return
}
node, ok := curr.(*TreeNode)
if ok {
node.Remove(child)
}
}
}
// Selected returns the currently selected element or nil
func (t *Tree) Selected() IPanel {
sel := t.List.Selected()
if len(sel) == 0 {
return nil
}
return sel[0]
}
// FindChild searches for the specified child in the tree and
// all its children. If found, returns the parent node and
// its position relative to the parent.
// If the parent is the tree returns nil as the parent
// If not found returns nil and -1
func (t *Tree) FindChild(child IPanel) (*TreeNode, int) {
for idx := 0; idx < t.List.Len(); idx++ {
curr := t.List.ItemAt(idx)
if curr == child {
return nil, idx
}
node, ok := curr.(*TreeNode)
if ok {
par, pos := node.FindChild(child)
if pos >= 0 {
return par, pos
}
}
}
return nil, -1
}
// onCursor receives subscribed cursor events over the tree
func (t *Tree) onCursor(evname string, ev interface{}) {
// Do not propagate any cursor events
t.root.StopPropagation(StopAll)
}
// onKey receives key down events for the embedded list
func (t *Tree) onKey(evname string, ev interface{}) {
// Get selected item
item := t.Selected()
if item == nil {
return
}
// If item is not a tree node, dispatch event to item
node, ok := item.(*TreeNode)
if !ok {
item.SetRoot(t.root)
item.GetPanel().Dispatch(evname, ev)
return
}
// If not enter key pressed, ignore
kev := ev.(*window.KeyEvent)
if evname != OnKeyDown || kev.Keycode != window.KeyEnter {
return
}
// Toggles the expansion state of the node
node.expanded = !node.expanded
node.update()
node.updateItems()
}
//
// TreeNode methods
//
// newTreeNode creates and returns a pointer to a new TreeNode with
// the specified text, tree and parent node
func newTreeNode(text string, tree *Tree, parNode *TreeNode) *TreeNode {
n := new(TreeNode)
n.Panel.Initialize(0, 0)
// Initialize node label
n.label.initialize(text, StyleDefault().Font)
n.Panel.Add(&n.label)
// Create node icon
n.icon.initialize("", StyleDefault().FontIcon)
n.icon.SetFontSize(StyleDefault().Label.PointSize * 1.3)
n.Panel.Add(&n.icon)
// Subscribe to events
n.Panel.Subscribe(OnMouseDown, n.onMouse)
n.Panel.Subscribe(OnListItemResize, func(evname string, ev interface{}) {
n.recalc()
})
n.tree = tree
n.parNode = parNode
n.update()
n.recalc()
return n
}
// Len returns the number of immediate children of this node
func (n *TreeNode) Len() int {
return len(n.items)
}
// SetExpanded sets the expanded state of this node
func (n *TreeNode) SetExpanded(state bool) {
n.expanded = state
n.update()
n.updateItems()
}
// FindChild searches for the specified child in this node and
// all its children. If found, returns the parent node and
// its position relative to the parent.
// If not found returns nil and -1
func (n *TreeNode) FindChild(child IPanel) (*TreeNode, int) {
for pos, curr := range n.items {
if curr == child {
return n, pos
}
node, ok := curr.(*TreeNode)
if ok {
par, pos := node.FindChild(child)
if par != nil {
return par, pos
}
}
}
return nil, -1
}
// InsertAt inserts a child panel at the specified position in this node
// If the position is invalid, the function panics
func (n *TreeNode) InsertAt(pos int, child IPanel) {
if pos < 0 || pos > len(n.items) {
panic("TreeNode.InsertAt(): Invalid position")
}
// Insert item in the items array
n.items = append(n.items, nil)
copy(n.items[pos+1:], n.items[pos:])
n.items[pos] = child
if n.expanded {
n.updateItems()
}
}
// Add adds a child panel to this node
func (n *TreeNode) Add(child IPanel) {
n.InsertAt(n.Len(), child)
}
// InsertNodeAt inserts a new node at the specified position in this node
// If the position is invalid, the function panics
func (n *TreeNode) InsertNodeAt(pos int, text string) *TreeNode {
if pos < 0 || pos > len(n.items) {
panic("TreeNode.InsertNodeAt(): Invalid position")
}
childNode := newTreeNode(text, n.tree, n)
// Insert item in the items array
n.items = append(n.items, nil)
copy(n.items[pos+1:], n.items[pos:])
n.items[pos] = childNode
if n.expanded {
n.updateItems()
}
return childNode
}
// AddNode adds a new node to this one and return its pointer
func (n *TreeNode) AddNode(text string) *TreeNode {
return n.InsertNodeAt(n.Len(), text)
}
// Remove removes the specified child from this node or any
// of its children nodes
func (n *TreeNode) Remove(child IPanel) {
for pos, curr := range n.items {
if curr == child {
copy(n.items[pos:], n.items[pos+1:])
n.items[len(n.items)-1] = nil
n.items = n.items[:len(n.items)-1]
node, ok := curr.(*TreeNode)
if ok {
node.remove()
} else {
n.tree.List.Remove(curr)
}
n.updateItems()
return
}
node, ok := curr.(*TreeNode)
if ok {
node.Remove(child)
}
}
}
// onMouse receives mouse button events over the tree node panel
func (n *TreeNode) onMouse(evname string, ev interface{}) {
switch evname {
case OnMouseDown:
n.expanded = !n.expanded
n.update()
n.recalc()
n.updateItems()
default:
return
}
}
// level returns the level of this node from the start of the tree
func (n *TreeNode) level() int {
level := 0
parNode := n.parNode
for parNode != nil {
parNode = parNode.parNode
level++
}
return level
}
// applyStyles applies the specified style to this tree node
func (n *TreeNode) applyStyle(s *TreeNodeStyle) {
n.Panel.ApplyStyle(&s.PanelStyle)
icode := 0
if n.expanded {
icode = 1
}
n.icon.SetText(string(s.Icons[icode]))
n.icon.SetColor4(&s.FgColor)
n.label.SetColor4(&s.FgColor)
}
// update updates this tree node style
func (n *TreeNode) update() {
n.applyStyle(&n.tree.styles.Node.Normal)
}
// recalc recalculates the positions of the internal node panels
func (n *TreeNode) recalc() {
// icon position
n.icon.SetPosition(0, 0)
// Label position and width
n.label.SetPosition(n.icon.Width()+4, 0)
n.Panel.SetContentHeight(n.label.Height())
n.Panel.SetWidth(n.tree.ContentWidth())
}
// remove removes this node and all children from the tree list
func (n *TreeNode) remove() {
n.tree.List.Remove(n)
n.removeItems()
}
// removeItems removes this node children from the tree list
func (n *TreeNode) removeItems() {
for _, ipanel := range n.items {
// Remove item from scroller
n.tree.List.Remove(ipanel)
// If item is a node, remove all children
node, ok := ipanel.(*TreeNode)
if ok {
node.removeItems()
continue
}
}
}
// insert inserts this node and its expanded children in the tree list
// at the specified position
func (n *TreeNode) insert(pos int) int {
n.update()
n.tree.List.InsertAt(pos, n)
var padLeft float32 = n.tree.styles.Padlevel * float32(n.level())
n.tree.List.SetItemPadLeftAt(pos, padLeft)
pos++
return n.insertItems(pos)
}
// insertItems inserts this node items in the tree list
// at the specified position
func (n *TreeNode) insertItems(pos int) int {
if !n.expanded {
return pos
}
level := n.level() + 1
var padLeft float32 = n.tree.styles.Padlevel * float32(level)
for _, ipanel := range n.items {
// Insert node and its children
node, ok := ipanel.(*TreeNode)
if ok {
node.update()
n.tree.List.InsertAt(pos, ipanel)
n.tree.List.SetItemPadLeftAt(pos, padLeft)
pos++
pos = node.insertItems(pos)
continue
}
// Insert item
n.tree.List.InsertAt(pos, ipanel)
n.tree.List.SetItemPadLeftAt(pos, padLeft)
pos++
}
return pos
}
// updateItems updates this node items, removing or inserting them into the tree scroller
func (n *TreeNode) updateItems() {
pos := n.tree.ItemPosition(n)
if pos < 0 {
return
}
n.removeItems()
n.insertItems(pos + 1)
}