Skip to content

Commit 0f3a375

Browse files
committed
Add opening and closing workflow node box function
1 parent abe0486 commit 0f3a375

File tree

1 file changed

+65
-15
lines changed
  • workflow-trace-viewer/src/jvmMain/kotlin/com/squareup/workflow1/traceviewer/ui

1 file changed

+65
-15
lines changed

workflow-trace-viewer/src/jvmMain/kotlin/com/squareup/workflow1/traceviewer/ui/WorkflowTree.kt

Lines changed: 65 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package com.squareup.workflow1.traceviewer.ui
22

33
import androidx.compose.foundation.background
44
import androidx.compose.foundation.border
5-
import androidx.compose.foundation.clickable
65
import androidx.compose.foundation.layout.Arrangement
76
import androidx.compose.foundation.layout.Box
87
import androidx.compose.foundation.layout.Column
@@ -13,12 +12,18 @@ import androidx.compose.material.Text
1312
import androidx.compose.runtime.Composable
1413
import androidx.compose.runtime.LaunchedEffect
1514
import androidx.compose.runtime.getValue
15+
import androidx.compose.runtime.mutableStateMapOf
1616
import androidx.compose.runtime.mutableStateOf
1717
import androidx.compose.runtime.remember
1818
import androidx.compose.runtime.setValue
1919
import androidx.compose.ui.Alignment
20+
import androidx.compose.ui.ExperimentalComposeUiApi
2021
import androidx.compose.ui.Modifier
2122
import androidx.compose.ui.graphics.Color
23+
import androidx.compose.ui.input.pointer.PointerEventType
24+
import androidx.compose.ui.input.pointer.isPrimaryPressed
25+
import androidx.compose.ui.input.pointer.isSecondaryPressed
26+
import androidx.compose.ui.input.pointer.onPointerEvent
2227
import androidx.compose.ui.unit.dp
2328
import com.squareup.workflow1.traceviewer.model.Node
2429
import com.squareup.workflow1.traceviewer.util.ParseResult
@@ -68,19 +73,29 @@ internal fun RenderDiagram(
6873

6974
if (!isLoading) {
7075
val previousFrame = if (frameInd > 0) fullTree[frameInd - 1] else null
71-
DrawTree(fullTree[frameInd], previousFrame, affectedNodes[frameInd], onNodeSelect)
76+
DrawTree(
77+
node = fullTree[frameInd],
78+
previousNode = previousFrame,
79+
affectedNodes = affectedNodes[frameInd],
80+
expandedNodes = remember(frameInd) { mutableStateMapOf() },
81+
onNodeSelect = onNodeSelect,
82+
)
7283
}
7384
}
7485

7586
/**
7687
* Since the workflow nodes present a tree structure, we utilize a recursive function to draw the tree
7788
* The Column holds a subtree of nodes, and the Row holds all the children of the current node
89+
*
90+
* A mutable map is used to persist the expansion state of the nodes, allowing them to be open and
91+
* closed from user clicks.
7892
*/
7993
@Composable
8094
private fun DrawTree(
8195
node: Node,
8296
previousNode: Node?,
8397
affectedNodes: Set<Node>,
98+
expandedNodes: MutableMap<String, Boolean>,
8499
onNodeSelect: (Node, Node?) -> Unit,
85100
modifier: Modifier = Modifier,
86101
) {
@@ -92,21 +107,42 @@ private fun DrawTree(
92107
horizontalAlignment = Alignment.CenterHorizontally,
93108
) {
94109
val isAffected = affectedNodes.contains(node)
95-
DrawNode(node, previousNode, isAffected, onNodeSelect)
110+
// By default, nodes that relevant to this specific frame are expanded. All others are closed.
111+
LaunchedEffect(expandedNodes) {
112+
expandedNodes[node.id] = isAffected
113+
}
114+
val isExpanded = expandedNodes[node.id] == true
115+
116+
DrawNode(
117+
node,
118+
previousNode,
119+
isAffected,
120+
isExpanded,
121+
onNodeSelect,
122+
onExpandToggle = { expandedNodes[node.id] = !expandedNodes[node.id]!! }
123+
)
96124

97125
// Draws the node's children recursively.
98-
Row(
99-
horizontalArrangement = Arrangement.Center,
100-
verticalAlignment = Alignment.Top
101-
) {
102-
/*
126+
if (isExpanded) {
127+
Row(
128+
horizontalArrangement = Arrangement.Center,
129+
verticalAlignment = Alignment.Top
130+
) {
131+
/*
103132
We pair up the current node's children with previous frame's children.
104133
In the edge case that the current frame has additional children compared to the previous
105134
frame, we replace with null and will check before next recursive call.
106135
*/
107-
node.children.forEachIndexed { index, childNode ->
108-
val prevChildNode = previousNode?.children?.getOrNull(index)
109-
DrawTree(childNode, prevChildNode, affectedNodes, onNodeSelect)
136+
node.children.forEachIndexed { index, childNode ->
137+
val prevChildNode = previousNode?.children?.getOrNull(index)
138+
DrawTree(
139+
node = childNode,
140+
previousNode = prevChildNode,
141+
affectedNodes = affectedNodes,
142+
expandedNodes = expandedNodes,
143+
onNodeSelect = onNodeSelect
144+
)
145+
}
110146
}
111147
}
112148
}
@@ -115,24 +151,38 @@ private fun DrawTree(
115151
/**
116152
* A basic box that represents a workflow node
117153
*/
154+
@OptIn(ExperimentalComposeUiApi::class)
118155
@Composable
119156
private fun DrawNode(
120157
node: Node,
121158
previousNode: Node?,
122159
isAffected: Boolean,
160+
isExpanded: Boolean,
123161
onNodeSelect: (Node, Node?) -> Unit,
162+
onExpandToggle: (Node) -> Unit,
124163
) {
125164
Box(
126165
modifier = Modifier
127166
.background(if (isAffected) Color.Green else Color.Transparent)
128-
.clickable {
129-
// Selecting a node will bubble back up to the main view to handle the selection
130-
onNodeSelect(node, previousNode)
167+
.onPointerEvent(PointerEventType.Press) {
168+
if (it.buttons.isPrimaryPressed) {
169+
onNodeSelect(node, previousNode)
170+
} else if (it.buttons.isSecondaryPressed) {
171+
onExpandToggle(node)
172+
}
131173
}
132174
.padding(10.dp)
133175
) {
134176
Column(horizontalAlignment = Alignment.CenterHorizontally) {
135-
Text(text = node.name)
177+
Row(
178+
verticalAlignment = Alignment.CenterVertically,
179+
horizontalArrangement = Arrangement.spacedBy(4.dp)
180+
) {
181+
if (node.children.isNotEmpty()) {
182+
Text(text = if (isExpanded) "" else "")
183+
}
184+
Text(text = node.name)
185+
}
136186
Text(text = "ID: ${node.id}")
137187
}
138188
}

0 commit comments

Comments
 (0)