@@ -63,7 +63,11 @@ internal fun RenderTrace(
63
63
val affectedNodes = remember(traceSource) { mutableStateListOf<Set <Node >>() }
64
64
65
65
// Updates current state with the new data from trace source.
66
- fun addToStates (frame : List <Node >, tree : List <Node >, affected : List <Set <Node >>) {
66
+ fun addToStates (
67
+ frame : List <Node >,
68
+ tree : List <Node >,
69
+ affected : List <Set <Node >>
70
+ ) {
67
71
frames.addAll(frame)
68
72
fullTree.addAll(tree)
69
73
affectedNodes.addAll(affected)
@@ -82,6 +86,7 @@ internal fun RenderTrace(
82
86
error = parseResult.error
83
87
false
84
88
}
89
+
85
90
is ParseResult .Success -> {
86
91
addToStates(
87
92
frame = parseResult.trace,
@@ -160,15 +165,12 @@ private fun DrawTree(
160
165
.fillMaxSize(),
161
166
horizontalAlignment = Alignment .CenterHorizontally ,
162
167
) {
163
- val groupKey = " ${node.id} -unaffected"
164
168
val isAffected = affectedNodes.contains(node)
165
169
// By default, nodes that relevant to this specific frame are expanded. All others are closed.
166
170
LaunchedEffect (expandedNodes) {
167
171
expandedNodes[node.id] = isAffected
168
- expandedNodes[groupKey] = false
169
172
}
170
173
val isExpanded = expandedNodes[node.id] == true
171
- val unaffectedChildrenExpanded = expandedNodes[groupKey] == true
172
174
173
175
DrawNode (
174
176
node = node,
@@ -179,49 +181,88 @@ private fun DrawTree(
179
181
onExpandToggle = { expandedNodes[node.id] = ! expandedNodes[node.id]!! }
180
182
)
181
183
182
- // Draws the node's children recursively.
183
184
if (isExpanded) {
184
- // Draw the affected children, and only draw the unaffected children it is clicked annd expanded.
185
185
val (affectedChildren, unaffectedChildren) = node.children.values
186
186
.partition { affectedNodes.contains(it) }
187
187
188
+ UnaffectedChildrenGroup (
189
+ node = node,
190
+ children = unaffectedChildren,
191
+ previousFrameNode = previousFrameNode,
192
+ affectedNodes = affectedNodes,
193
+ expandedNodes = expandedNodes,
194
+ onNodeSelect = onNodeSelect
195
+ )
188
196
189
- if (unaffectedChildren.isNotEmpty()) {
190
- Box (
191
- modifier = Modifier
192
- .onPointerEvent(PointerEventType .Press ) {
193
- if (it.buttons.isSecondaryPressed) {
194
- // The open/close state for this group is always set when this node is first composed.
195
- expandedNodes[groupKey] = ! expandedNodes[groupKey]!!
196
- }
197
- }
198
- ) {
199
- if (! unaffectedChildrenExpanded) {
200
- Column (
201
- modifier = Modifier
202
- .background(Color .LightGray .copy(alpha = 0.3f ), shape = RoundedCornerShape (4 .dp))
203
- .border(1 .dp, Color .Gray )
204
- .padding(8 .dp),
205
- horizontalAlignment = Alignment .CenterHorizontally
206
- ) {
207
- Text (text = " ${node.name} 's" , color = Color .DarkGray )
208
- Text (text = " ${unaffectedChildren.size} unaffected children" , color = Color .DarkGray , fontSize = 12 .sp)
209
- }
210
- } else {
211
- DrawChildren (
212
- children = unaffectedChildren,
213
- previousFrameNode = previousFrameNode,
214
- affectedNodes = affectedNodes,
215
- expandedNodes = expandedNodes,
216
- onNodeSelect = onNodeSelect
217
- )
218
- }
197
+ AffectedChildrenGroup (
198
+ children = affectedChildren,
199
+ previousFrameNode = previousFrameNode,
200
+ affectedNodes = affectedNodes,
201
+ expandedNodes = expandedNodes,
202
+ onNodeSelect = onNodeSelect
203
+ )
204
+ }
205
+ }
206
+ }
207
+
208
+ /* *
209
+ * Draws the group of unaffected children, which can be open and closed to expand/collapse them.
210
+ *
211
+ * If an unaffected children also has other children, it cannot be opened, since the this group
212
+ * treats all nodes as one entity, so the onClick for the whole group overrides the onClick for the
213
+ * individual nodes.
214
+ */
215
+ @OptIn(ExperimentalComposeUiApi ::class )
216
+ @Composable
217
+ private fun UnaffectedChildrenGroup (
218
+ node : Node ,
219
+ children : List <Node >,
220
+ previousFrameNode : Node ? ,
221
+ affectedNodes : Set <Node >,
222
+ expandedNodes : MutableMap <String , Boolean >,
223
+ onNodeSelect : (NodeUpdate ) -> Unit
224
+ ) {
225
+ if (children.isEmpty()) return
226
+
227
+ val groupKey = " ${node.id} _unaffected_group"
228
+ LaunchedEffect (Unit ) {
229
+ expandedNodes[groupKey] = false
230
+ }
231
+ val isGroupExpanded = expandedNodes[groupKey] == true
232
+
233
+ Box (
234
+ modifier = Modifier
235
+ .onPointerEvent(PointerEventType .Press ) {
236
+ if (it.buttons.isSecondaryPressed) {
237
+ expandedNodes[groupKey] = ! isGroupExpanded
219
238
}
220
239
}
221
-
222
- if (affectedChildren.isNotEmpty()) {
223
- DrawChildren (
224
- children = affectedChildren,
240
+ ) {
241
+ if (! isGroupExpanded) {
242
+ Column (
243
+ modifier = Modifier
244
+ .background(Color .LightGray .copy(alpha = 0.3f ), shape = RoundedCornerShape (4 .dp))
245
+ .border(1 .dp, Color .Gray )
246
+ .padding(8 .dp),
247
+ horizontalAlignment = Alignment .CenterHorizontally
248
+ ) {
249
+ Text (text = " ${node.name} 's" , color = Color .DarkGray )
250
+ Text (
251
+ text = " ${children.size} unaffected children" ,
252
+ color = Color .DarkGray ,
253
+ fontSize = 12 .sp
254
+ )
255
+ }
256
+ } else {
257
+ Column (
258
+ modifier = Modifier
259
+ .fillMaxWidth()
260
+ .padding(6 .dp)
261
+ .border(3 .dp, Color .Black ),
262
+ horizontalAlignment = Alignment .CenterHorizontally ,
263
+ ) {
264
+ DrawChildrenInGroups (
265
+ children = children,
225
266
previousFrameNode = previousFrameNode,
226
267
affectedNodes = affectedNodes,
227
268
expandedNodes = expandedNodes,
@@ -232,29 +273,92 @@ private fun DrawTree(
232
273
}
233
274
}
234
275
276
+ /* *
277
+ * Draws the group of affected children
278
+ */
235
279
@Composable
236
- private fun DrawChildren (
280
+ private fun AffectedChildrenGroup (
237
281
children : List <Node >,
238
282
previousFrameNode : Node ? ,
239
283
affectedNodes : Set <Node >,
240
284
expandedNodes : MutableMap <String , Boolean >,
241
- onNodeSelect : (NodeUpdate ) -> Unit ,
285
+ onNodeSelect : (NodeUpdate ) -> Unit
242
286
) {
243
- Row (
244
- modifier = Modifier
245
- .fillMaxWidth()
246
- .padding(8 .dp),
247
- horizontalArrangement = Arrangement .SpaceEvenly ,
248
- verticalAlignment = Alignment .Top
287
+ if (children.isEmpty()) return
288
+
289
+ DrawChildrenInGroups (
290
+ children = children,
291
+ previousFrameNode = previousFrameNode,
292
+ affectedNodes = affectedNodes,
293
+ expandedNodes = expandedNodes,
294
+ onNodeSelect = onNodeSelect
295
+ )
296
+ }
297
+
298
+ /* *
299
+ * Draws the children in a grid manner, to avoid horizontal clutter and make better use of space.
300
+ */
301
+ @Composable
302
+ private fun DrawChildrenInGroups (
303
+ children : List <Node >,
304
+ previousFrameNode : Node ? ,
305
+ affectedNodes : Set <Node >,
306
+ expandedNodes : MutableMap <String , Boolean >,
307
+ onNodeSelect : (NodeUpdate ) -> Unit
308
+ ) {
309
+ // Split children into those with children (nested) and those without
310
+ val (nestedChildren, simpleChildren) = children.partition { it.children.isNotEmpty() }
311
+
312
+ Column (
313
+ verticalArrangement = Arrangement .spacedBy(16 .dp), // Increased spacing between sections
249
314
) {
250
- children.forEach { childNode ->
251
- DrawTree (
252
- node = childNode,
253
- previousFrameNode = previousFrameNode?.children?.get(childNode.id),
254
- affectedNodes = affectedNodes,
255
- expandedNodes = expandedNodes,
256
- onNodeSelect = onNodeSelect
257
- )
315
+ // Draw simple children in a grid at the top
316
+ if (simpleChildren.isNotEmpty()) {
317
+ val groupedSimpleChildren = simpleChildren.chunked(5 )
318
+
319
+ groupedSimpleChildren.forEach { group ->
320
+ Row (
321
+ modifier = Modifier
322
+ .fillMaxWidth()
323
+ .padding(8 .dp)
324
+ .align(Alignment .CenterHorizontally ),
325
+ horizontalArrangement = Arrangement .SpaceEvenly ,
326
+ verticalAlignment = Alignment .Top
327
+ ) {
328
+ group.forEach { childNode ->
329
+ DrawTree (
330
+ node = childNode,
331
+ previousFrameNode = previousFrameNode?.children?.get(childNode.id),
332
+ affectedNodes = affectedNodes,
333
+ expandedNodes = expandedNodes,
334
+ onNodeSelect = onNodeSelect
335
+ )
336
+ }
337
+ }
338
+
339
+ }
340
+ }
341
+
342
+ // Draw nested children in a single row at the bottom
343
+ if (nestedChildren.isNotEmpty()) {
344
+ Row (
345
+ modifier = Modifier
346
+ .fillMaxWidth()
347
+ .padding(horizontal = 8 .dp)
348
+ .align(Alignment .CenterHorizontally ),
349
+ horizontalArrangement = Arrangement .SpaceEvenly ,
350
+ verticalAlignment = Alignment .Top
351
+ ) {
352
+ nestedChildren.forEach { childNode ->
353
+ DrawTree (
354
+ node = childNode,
355
+ previousFrameNode = previousFrameNode?.children?.get(childNode.id),
356
+ affectedNodes = affectedNodes,
357
+ expandedNodes = expandedNodes,
358
+ onNodeSelect = onNodeSelect
359
+ )
360
+ }
361
+ }
258
362
}
259
363
}
260
364
}
0 commit comments