1
1
package com.squareup.workflow1.traceviewer
2
2
3
+ import androidx.compose.desktop.ui.tooling.preview.Preview
4
+ import androidx.compose.foundation.Canvas
3
5
import androidx.compose.foundation.background
4
6
import androidx.compose.foundation.border
5
7
import androidx.compose.foundation.clickable
@@ -8,20 +10,31 @@ import androidx.compose.foundation.layout.Box
8
10
import androidx.compose.foundation.layout.Column
9
11
import androidx.compose.foundation.layout.Row
10
12
import androidx.compose.foundation.layout.Spacer
13
+ import androidx.compose.foundation.layout.fillMaxSize
11
14
import androidx.compose.foundation.layout.padding
15
+ import androidx.compose.foundation.shape.RoundedCornerShape
16
+ import androidx.compose.material.MaterialTheme
12
17
import androidx.compose.material.Text
13
18
import androidx.compose.runtime.Composable
14
19
import androidx.compose.runtime.LaunchedEffect
15
20
import androidx.compose.runtime.MutableState
21
+ import androidx.compose.runtime.mutableStateMapOf
16
22
import androidx.compose.runtime.mutableStateOf
17
23
import androidx.compose.runtime.remember
18
24
import androidx.compose.ui.Alignment
19
25
import androidx.compose.ui.Modifier
20
26
import androidx.compose.ui.geometry.Offset
21
27
import androidx.compose.ui.graphics.Color
28
+ import androidx.compose.ui.graphics.graphicsLayer
29
+ import androidx.compose.ui.layout.LayoutCoordinates
22
30
import androidx.compose.ui.layout.onGloballyPositioned
31
+ import androidx.compose.ui.layout.positionInParent
23
32
import androidx.compose.ui.layout.positionInRoot
33
+ import androidx.compose.ui.layout.positionOnScreen
34
+ import androidx.compose.ui.platform.LocalDensity
24
35
import androidx.compose.ui.unit.dp
36
+ import androidx.compose.ui.util.fastAny
37
+ import javax.swing.tree.TreeNode
25
38
26
39
/* *
27
40
* Since the logic of Workflow is hierarchical (where each workflow may have parent workflows and/or children workflows,
@@ -33,7 +46,27 @@ public data class WorkflowNode (
33
46
val id : String ,
34
47
val name : String ,
35
48
val children : List <WorkflowNode >
36
- )
49
+ ) {
50
+ // fun findParentById(id: String): WorkflowNode? {
51
+ // if (this.id == id) return this
52
+ // for (child in children) {
53
+ // val found = child.findParentById(id)
54
+ // if (found != null) return found
55
+ // }
56
+ // return null
57
+ // }
58
+ fun findParentForId (id : String ): WorkflowNode ? {
59
+ if (this .id == id) {
60
+ return null // This is the root node, so it has no parent
61
+ }
62
+ if (children.any { it.id == id }) {
63
+ return this
64
+ }
65
+ return children.firstNotNullOfOrNull {
66
+ it.findParentForId(id)
67
+ }
68
+ }
69
+ }
37
70
38
71
/* *
39
72
* Main access point for drawing the workflow tree. This does 2 tasks:
@@ -43,22 +76,53 @@ public data class WorkflowNode (
43
76
* nodes first, then launch an event to draw the arrows once all nodes have been placed.
44
77
*/
45
78
@Composable
46
- public fun DrawWorkflowTree (root : WorkflowNode ) {
79
+ public fun DrawWorkflowTree (
80
+ root : WorkflowNode ,
81
+ translationXArg : Float ,
82
+ translationYArg : Float ,
83
+ scale : Float ,
84
+ ) {
47
85
val nodePositions = remember { mutableMapOf<String , Offset >() }
48
- val nodeCount = remember { mutableStateOf(0 ) }
49
- val nodeMapSize = remember { mutableStateOf(0 )}
50
- val readyToDraw = remember { mutableStateOf(false ) }
51
-
52
- drawTree(root, nodePositions, nodeCount, nodeMapSize)
86
+ // val nodeCount = remember { mutableStateOf(0) }
87
+ // val nodeMapSize = remember { mutableStateOf(0)}
88
+ // val readyToDraw = remember { mutableStateOf(false) }
89
+ Box (modifier = Modifier .fillMaxSize()) {
53
90
54
- LaunchedEffect (nodeMapSize.value) {
55
- if (nodePositions.size == nodeCount.value) {
56
- readyToDraw.value = true
91
+ drawTree(root, nodePositions
92
+ // , nodeCount, nodeMapSize
93
+ )
94
+ Canvas (modifier = Modifier .fillMaxSize()
95
+ // .graphicsLayer {
96
+ // translationX = translationXArg
97
+ // translationY = translationYArg
98
+ // scaleX = scale
99
+ // scaleY = scale
100
+ // }
101
+ ) {
102
+ nodePositions.forEach { (id, position) ->
103
+ val start = position
104
+ val end = root.findParentForId(id)?.let { parent -> nodePositions[parent.id] } ? : return @forEach
105
+ drawLine(
106
+ color = Color .Black ,
107
+ start = Offset (start.x.dp.toPx(), start.y.dp.toPx()),
108
+ end = Offset (end.x.dp.toPx(), end.y.dp.toPx()),
109
+ strokeWidth = 2 .dp.toPx()
110
+ )
111
+ }
57
112
}
58
- }
113
+ // LaunchedEffect(nodeMapSize.value) {
114
+ // if (nodePositions.size == nodeCount.value) {
115
+ // readyToDraw.value = true
116
+ // }
117
+ // }
59
118
60
- if (readyToDraw.value) {
61
- drawArrows(root, nodePositions)
119
+ // if (readyToDraw.value) {
120
+ // LaunchedEffect(nodePositions) {
121
+ // drawArrows(root, nodePositions)
122
+ // drawArrows(root, nodePositions)
123
+ //
124
+ // }
125
+ // }
62
126
}
63
127
}
64
128
@@ -70,24 +134,31 @@ public fun DrawWorkflowTree(root: WorkflowNode) {
70
134
private fun drawTree (
71
135
node : WorkflowNode ,
72
136
nodePositions : MutableMap <String , Offset >,
73
- nodeCount : MutableState <Int >,
74
- nodeMapSize : MutableState <Int >
137
+
138
+ // nodeCount: MutableState<Int>,
139
+ // nodeMapSize: MutableState<Int>
75
140
) {
141
+
76
142
Column (
77
- modifier = Modifier .padding(10 .dp),
143
+ modifier = Modifier .padding(10 .dp).border( 1 .dp, Color . Black ) ,
78
144
horizontalAlignment = Alignment .CenterHorizontally ,
79
145
) {
80
- drawNode(node, nodePositions, nodeMapSize)
81
- nodeCount.value + = 1
146
+ drawNode(node, nodePositions
147
+ // , nodeMapSize
148
+ )
149
+ // nodeCount.value += 1
82
150
if (node.children.isEmpty()) return @Column
83
151
84
- Spacer (modifier = Modifier .padding(30 .dp))
152
+ // Spacer(modifier = Modifier.padding(30.dp))
85
153
Row (
86
154
horizontalArrangement = Arrangement .Center ,
87
- verticalAlignment = Alignment .Top
155
+ verticalAlignment = Alignment .Top ,
156
+ modifier = Modifier .border(1 .dp, Color .Black )
88
157
) {
89
158
node.children.forEach { childNode ->
90
- drawTree (childNode, nodePositions, nodeCount, nodeMapSize)
159
+ drawTree (childNode, nodePositions
160
+ // , nodeCount, nodeMapSize
161
+ )
91
162
}
92
163
}
93
164
}
@@ -101,17 +172,19 @@ private fun drawTree(
101
172
private fun drawNode (
102
173
node : WorkflowNode ,
103
174
nodePositions : MutableMap <String , Offset >,
104
- nodeMapSize : MutableState <Int >
175
+ // nodeMapSize: MutableState<Int>
105
176
) {
177
+ val density = LocalDensity .current
178
+
106
179
val open = remember { mutableStateOf(false ) }
107
180
Box (
108
181
modifier = Modifier
109
- .border(1 .dp, Color .Black )
182
+ // .border(1.dp, Color.Black)
110
183
.clickable { open.value = ! open.value }
111
184
.onGloballyPositioned {
112
185
val coords = it.positionInRoot()
113
- nodePositions[node.id] = coords
114
- nodeMapSize.value + = 1
186
+ nodePositions[node.id] = with (density) { coords.toDp(density) }
187
+ // nodeMapSize.value += 1
115
188
}
116
189
){
117
190
Column (horizontalAlignment = Alignment .CenterHorizontally ) {
@@ -129,11 +202,11 @@ private fun drawArrows(
129
202
node : WorkflowNode ,
130
203
nodePositions : MutableMap <String , Offset >
131
204
){
132
- if (node.children.isEmpty()) return
133
-
205
+ // if (node.children.isEmpty()) return
206
+ println (nodePositions)
134
207
node.children.forEach { childNode ->
135
- val parentPosition = nodePositions[node.id] ? : Offset . Zero
136
- val childPosition = nodePositions[childNode.id] ? : Offset . Zero
208
+ val parentPosition = nodePositions[node.id] ? : error( " Child must have a position " )
209
+ val childPosition = nodePositions[childNode.id] ? : error( " Child must have a position " )
137
210
138
211
Arrow (
139
212
start = parentPosition,
@@ -143,3 +216,66 @@ private fun drawArrows(
143
216
drawArrows(childNode, nodePositions)
144
217
}
145
218
}
219
+ @Composable
220
+ fun TreeNode (
221
+ id : String ,
222
+ modifier : Modifier = Modifier ,
223
+ onPositioned : ((LayoutCoordinates ) -> Unit )? = null
224
+ ) {
225
+ Box (
226
+ modifier = modifier
227
+ .padding(8 .dp)
228
+ // .onGloballyPositioned { coords ->
229
+ // onPositioned?.invoke(coords)
230
+ // }
231
+ .background(Color (0xFFE0F7FA ), shape = RoundedCornerShape (8 .dp))
232
+ .border(1 .dp, Color .Black , shape = RoundedCornerShape (8 .dp))
233
+ .padding(horizontal = 12 .dp, vertical = 6 .dp)
234
+ ) {
235
+ Text (text = id)
236
+ }
237
+ }
238
+
239
+ @Preview
240
+ @Composable
241
+ fun test () {
242
+ val nodePositions = remember { mutableStateMapOf<String , Offset >() }
243
+ val density = LocalDensity .current
244
+
245
+ Box (modifier = Modifier .fillMaxSize()) {
246
+ Column {
247
+ TreeNode (
248
+ id = " A" ,
249
+ modifier = Modifier
250
+ .padding(horizontal = 200 .dp)
251
+ .onGloballyPositioned { coordinates ->
252
+ val localOffset = coordinates.positionInParent()
253
+ nodePositions[" A" ] = with (density) { localOffset.toDp(density) }
254
+ }
255
+ )
256
+
257
+ TreeNode (
258
+ id = " B" ,
259
+ modifier = Modifier
260
+ .onGloballyPositioned { coordinates ->
261
+ val localOffset = coordinates.positionInParent()
262
+ nodePositions[" B" ] = with (density) { localOffset.toDp(density) }
263
+ }
264
+ )
265
+ }
266
+
267
+ Canvas (modifier = Modifier .fillMaxSize()) {
268
+ val start = nodePositions[" A" ]
269
+ val end = nodePositions[" B" ]
270
+ if (start != null && end != null ) {
271
+ // Convert dp to px inside the DrawScope
272
+ drawLine(
273
+ color = Color .Black ,
274
+ start = Offset (start.x.dp.toPx(), start.y.dp.toPx()),
275
+ end = Offset (end.x.dp.toPx(), end.y.dp.toPx()),
276
+ strokeWidth = 2 .dp.toPx()
277
+ )
278
+ }
279
+ }
280
+ }
281
+ }
0 commit comments