-
Notifications
You must be signed in to change notification settings - Fork 35
Expand file tree
/
Copy pathTreeListComponent.kt
More file actions
181 lines (148 loc) · 5.41 KB
/
Copy pathTreeListComponent.kt
File metadata and controls
181 lines (148 loc) · 5.41 KB
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
package gg.essential.elementa.components
import gg.essential.elementa.UIComponent
import gg.essential.elementa.constraints.*
import gg.essential.elementa.dsl.basicWidthConstraint
import gg.essential.elementa.dsl.childOf
import gg.essential.elementa.dsl.constrain
import gg.essential.elementa.dsl.pixels
import gg.essential.universal.UKeyboard
import kotlin.math.max
abstract class TreeArrowComponent : UIComponent() {
abstract fun open()
abstract fun close()
}
open class TreeListComponent(roots: List<TreeNode>) : UIContainer() {
constructor(root: TreeNode) : this(listOf(root))
constructor() : this(emptyList())
init {
constrain {
width = ChildBasedMaxSizeConstraint()
height = ChildBasedSizeConstraint()
}
setRoots(roots)
}
fun setRoots(roots: List<TreeNode>) = apply {
clearChildren()
roots.forEach { it.displayComponent childOf this }
}
}
abstract class TreeNode {
open var indentationOffset: Float = 10f
private var displayComponentBacker: TreeNodeComponent? = null
val displayComponent: TreeNodeComponent
get() {
if (displayComponentBacker == null)
displayComponentBacker = TreeNodeComponent()
return displayComponentBacker!!
}
abstract fun getArrowComponent(): TreeArrowComponent
abstract fun getPrimaryComponent(): UIComponent
fun addChild(node: TreeNode) = apply {
displayComponent.addNodeChild(node.displayComponent)
}
fun addChildAt(index: Int, node: TreeNode) = apply {
displayComponent.addNodeChildAt(index, node.displayComponent)
}
fun removeChildAt(index: Int) = apply {
displayComponent.removeNodeChildAt(index)
}
fun clearChildren() = apply {
displayComponent.clearNodeChildren()
}
fun withChildren(childBuilder: TreeNodeBuilder.() -> Unit): TreeNode {
val builder = TreeNodeBuilder(this)
builder.apply(childBuilder)
return builder.root
}
inner class TreeNodeComponent : UIContainer() {
private val node: TreeNode = this@TreeNode
private val arrowComponent = getArrowComponent()
internal var opened = false
set(value) {
if (value == field) {
return
}
field = value
if (value) {
arrowComponent.open()
childContainer.unhide()
} else {
arrowComponent.close()
childContainer.hide()
close()
}
}
private val childContainer = UIContainer().constrain {
x = indentationOffset.pixels()
y = SiblingConstraint()
width = ChildBasedMaxSizeConstraint()
height = ChildBasedSizeConstraint()
}
private val ownContent = UIContainer().constrain {
width = ChildBasedSizeConstraint()
height = ChildBasedMaxSizeConstraint()
}
init {
constrain {
y = SiblingConstraint()
width = basicWidthConstraint {
max(ownContent.getWidth(), childContainer.getWidth() + indentationOffset)
}
height = ChildBasedSizeConstraint()
}
ownContent childOf this
arrowComponent childOf ownContent
UIContainer().constrain {
x = SiblingConstraint()
width = 5.pixels()
} childOf ownContent
getPrimaryComponent() childOf ownContent
childContainer childOf this
childContainer.hide(instantly = true)
arrowComponent.onMouseClick { event ->
event.stopImmediatePropagation()
val isRecursive = UKeyboard.isShiftKeyDown()
if (opened) {
close(isRecursive)
} else {
open(isRecursive)
}
}
}
fun open(isRecursive: Boolean = false) {
opened = true
if (isRecursive)
recursivelyApplyToChildNodes(this@TreeNodeComponent, TreeNodeComponent::open)
}
fun close(isRecursive: Boolean = false) {
opened = false
if (isRecursive)
recursivelyApplyToChildNodes(this@TreeNodeComponent, TreeNodeComponent::close)
}
fun addNodeChild(component: UIComponent) = apply {
childContainer.addChild(component)
}
fun addNodeChildAt(index: Int, component: UIComponent) = apply {
childContainer.insertChildAt(component, index)
}
fun removeNodeChildAt(index: Int) = apply {
childContainer.children.removeAt(index)
}
fun clearNodeChildren() = apply {
childContainer.clearChildren()
}
private fun recursivelyApplyToChildNodes(node: TreeNodeComponent, method: (TreeNodeComponent) -> Unit) {
node.childContainer.children.filterIsInstance<TreeNodeComponent>().forEach {
method(it)
recursivelyApplyToChildNodes(it, method)
}
}
internal val childNodes
get() = childContainer.children.mapNotNull { (it as? TreeNodeComponent)?.node }
}
}
class TreeNodeBuilder(val root: TreeNode) {
fun add(node: TreeNode) {
root.addChild(node)
}
}