Skip to content

Commit 325b239

Browse files
committed
Add search box for available nodes in the specified frame
The goal is to have the viewport snap to the node when it's being searched for, but current use of Offset seems to give some positional errors.
1 parent a2c98ae commit 325b239

File tree

5 files changed

+150
-15
lines changed

5 files changed

+150
-15
lines changed

workflow-trace-viewer/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ kotlin {
1515
implementation(compose.runtime)
1616
implementation(compose.foundation)
1717
implementation(compose.material)
18+
implementation(compose.material3)
1819
implementation(compose.ui)
1920
implementation(compose.components.resources)
2021
implementation(compose.components.uiToolingPreview)

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

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,28 @@
11
package com.squareup.workflow1.traceviewer
22

33
import androidx.compose.foundation.layout.Box
4+
import androidx.compose.foundation.layout.Column
45
import androidx.compose.runtime.Composable
56
import androidx.compose.runtime.LaunchedEffect
67
import androidx.compose.runtime.getValue
78
import androidx.compose.runtime.mutableFloatStateOf
89
import androidx.compose.runtime.mutableIntStateOf
10+
import androidx.compose.runtime.mutableStateMapOf
911
import androidx.compose.runtime.mutableStateOf
1012
import androidx.compose.runtime.remember
1113
import androidx.compose.runtime.setValue
1214
import androidx.compose.runtime.snapshotFlow
15+
import androidx.compose.runtime.snapshots.SnapshotStateMap
1316
import androidx.compose.ui.Alignment
1417
import androidx.compose.ui.Modifier
1518
import androidx.compose.ui.geometry.Offset
19+
import com.squareup.workflow1.traceviewer.model.Node
1620
import com.squareup.workflow1.traceviewer.model.NodeUpdate
1721
import com.squareup.workflow1.traceviewer.ui.ColorLegend
1822
import com.squareup.workflow1.traceviewer.ui.DisplayDevices
1923
import com.squareup.workflow1.traceviewer.ui.FrameSelectTab
2024
import com.squareup.workflow1.traceviewer.ui.RightInfoPanel
25+
import com.squareup.workflow1.traceviewer.ui.SearchBox
2126
import com.squareup.workflow1.traceviewer.ui.TraceModeToggleSwitch
2227
import com.squareup.workflow1.traceviewer.util.FileDump
2328
import com.squareup.workflow1.traceviewer.util.RenderTrace
@@ -37,11 +42,14 @@ internal fun App(
3742
var rawRenderPass by remember { mutableStateOf("") }
3843
var frameIndex by remember { mutableIntStateOf(0) }
3944
val sandboxState = remember { SandboxState() }
45+
val nodeLocations = remember { mutableListOf<SnapshotStateMap<Node, Offset>>() }
4046

4147
// Default to File mode, and can be toggled to be in Live mode.
4248
var active by remember { mutableStateOf(false) }
4349
var traceMode by remember { mutableStateOf<TraceMode>(TraceMode.File(null)) }
4450
var selectedTraceFile by remember { mutableStateOf<PlatformFile?>(null) }
51+
// frameIndex is set to -1 when app is in Live Mode, so we increment it by one to avoid off-by-one errors
52+
val frameInd = if (traceMode is TraceMode.Live) frameIndex + 1 else frameIndex
4553

4654
LaunchedEffect(sandboxState) {
4755
snapshotFlow { frameIndex }.collect {
@@ -57,6 +65,8 @@ internal fun App(
5765
selectedNode = null
5866
frameIndex = 0
5967
frameSize = 0
68+
active = false
69+
nodeLocations.clear()
6070
}
6171

6272
// Main content
@@ -76,16 +86,40 @@ internal fun App(
7686
onNodeSelect = { selectedNode = it },
7787
onNewFrame = { frameIndex += 1 },
7888
onNewData = { rawRenderPass += "$it," },
89+
storeNodeLocation = { node, loc -> nodeLocations[frameInd] += (node to loc) }
7990
)
8091
}
8192
}
8293

83-
FrameSelectTab(
84-
size = frameSize,
85-
currentIndex = frameIndex,
86-
onIndexChange = { frameIndex = it },
94+
Column(
8795
modifier = Modifier.align(Alignment.TopCenter)
88-
)
96+
) {
97+
if (active) {
98+
FrameSelectTab(
99+
size = frameSize,
100+
currentIndex = frameIndex,
101+
onIndexChange = { frameIndex = it },
102+
)
103+
104+
// Since we can jump from frame to frame, we fill in the map during each recomposition
105+
if (nodeLocations.getOrNull(frameInd) == null) {
106+
// frameSize has not been updated yet, so on the first frame, frameSize = nodeLocations.size = 0,
107+
// and it will append a new map
108+
while (nodeLocations.size <= frameSize) {
109+
nodeLocations.add(mutableStateMapOf())
110+
}
111+
}
112+
113+
SearchBox(
114+
nodes = nodeLocations[frameInd].keys.toList(),
115+
onSearch = { name ->
116+
val node = nodeLocations[frameInd].keys.firstOrNull { it.name == name }
117+
sandboxState.offset = nodeLocations[frameInd][node] ?: sandboxState.offset
118+
},
119+
modifier = Modifier.align(Alignment.CenterHorizontally)
120+
)
121+
}
122+
}
89123

90124
TraceModeToggleSwitch(
91125
onToggle = {
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package com.squareup.workflow1.traceviewer.ui
2+
3+
import androidx.compose.foundation.clickable
4+
import androidx.compose.foundation.layout.Column
5+
import androidx.compose.material.Icon
6+
import androidx.compose.material.IconButton
7+
import androidx.compose.material.Text
8+
import androidx.compose.material.icons.Icons
9+
import androidx.compose.material.icons.filled.Close
10+
import androidx.compose.material3.DockedSearchBar
11+
import androidx.compose.material3.ExperimentalMaterial3Api
12+
import androidx.compose.material3.ListItem
13+
import androidx.compose.material3.SearchBarColors
14+
import androidx.compose.material3.SearchBarDefaults
15+
import androidx.compose.runtime.Composable
16+
import androidx.compose.runtime.getValue
17+
import androidx.compose.runtime.mutableStateOf
18+
import androidx.compose.runtime.remember
19+
import androidx.compose.runtime.setValue
20+
import androidx.compose.ui.Modifier
21+
import androidx.compose.ui.graphics.Color
22+
import com.squareup.workflow1.traceviewer.model.Node
23+
24+
@OptIn(ExperimentalMaterial3Api::class)
25+
@Composable
26+
internal fun SearchBox(
27+
nodes: List<Node>,
28+
onSearch: (String) -> Unit,
29+
modifier: Modifier = Modifier
30+
) {
31+
var searchText by remember { mutableStateOf("") }
32+
var expanded by remember { mutableStateOf(false) }
33+
34+
DockedSearchBar(
35+
modifier = modifier,
36+
inputField = {
37+
SearchBarDefaults.InputField(
38+
query = searchText,
39+
onQueryChange = { searchText = it },
40+
onSearch = {
41+
expanded = false
42+
},
43+
expanded = expanded,
44+
onExpandedChange = { expanded = it },
45+
placeholder = { Text("search for a node...") },
46+
trailingIcon = {
47+
IconButton(
48+
onClick = {
49+
expanded = false
50+
}
51+
) {
52+
Icon(
53+
imageVector = Icons.Default.Close,
54+
contentDescription = "Clear search"
55+
)
56+
}
57+
}
58+
)
59+
},
60+
colors = SearchBarColors(Color.White, Color.Black),
61+
expanded = expanded,
62+
onExpandedChange = { expanded = it },
63+
) {
64+
val relevantNodes = nodes.filter { it.name.contains(searchText, ignoreCase = true) }
65+
Column {
66+
relevantNodes.take(5).forEach { node ->
67+
ListItem(
68+
headlineContent = { Text(node.name) },
69+
modifier = Modifier
70+
.clickable {
71+
onSearch(node.name)
72+
expanded = false
73+
}
74+
)
75+
}
76+
}
77+
}
78+
}

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

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,13 @@ import androidx.compose.runtime.LaunchedEffect
1717
import androidx.compose.ui.Alignment
1818
import androidx.compose.ui.ExperimentalComposeUiApi
1919
import androidx.compose.ui.Modifier
20+
import androidx.compose.ui.geometry.Offset
2021
import androidx.compose.ui.graphics.Color
2122
import androidx.compose.ui.input.pointer.PointerEventType
2223
import androidx.compose.ui.input.pointer.isSecondaryPressed
2324
import androidx.compose.ui.input.pointer.onPointerEvent
25+
import androidx.compose.ui.layout.onGloballyPositioned
26+
import androidx.compose.ui.layout.positionInParent
2427
import androidx.compose.ui.unit.dp
2528
import androidx.compose.ui.unit.sp
2629
import com.squareup.workflow1.traceviewer.model.Node
@@ -40,6 +43,7 @@ internal fun DrawTree(
4043
affectedNodes: Set<Node>,
4144
expandedNodes: MutableMap<String, Boolean>,
4245
onNodeSelect: (NodeUpdate) -> Unit,
46+
storeNodeLocation: (Node, Offset) -> Unit,
4347
modifier: Modifier = Modifier,
4448
) {
4549
Column(
@@ -64,7 +68,8 @@ internal fun DrawTree(
6468
isAffected = isAffected,
6569
isExpanded = isExpanded,
6670
onNodeSelect = onNodeSelect,
67-
onExpandToggle = { expandedNodes[node.id] = !expandedNodes[node.id]!! }
71+
onExpandToggle = { expandedNodes[node.id] = !expandedNodes[node.id]!! },
72+
storeNodeLocation = storeNodeLocation
6873
)
6974

7075
if (isExpanded) {
@@ -77,15 +82,17 @@ internal fun DrawTree(
7782
previousFrameNode = previousFrameNode,
7883
affectedNodes = affectedNodes,
7984
expandedNodes = expandedNodes,
80-
onNodeSelect = onNodeSelect
85+
onNodeSelect = onNodeSelect,
86+
storeNodeLocation = storeNodeLocation
8187
)
8288

8389
AffectedChildrenGroup(
8490
children = affectedChildren,
8591
previousFrameNode = previousFrameNode,
8692
affectedNodes = affectedNodes,
8793
expandedNodes = expandedNodes,
88-
onNodeSelect = onNodeSelect
94+
onNodeSelect = onNodeSelect,
95+
storeNodeLocation = storeNodeLocation
8996
)
9097
}
9198
}
@@ -106,7 +113,8 @@ private fun UnaffectedChildrenGroup(
106113
previousFrameNode: Node?,
107114
affectedNodes: Set<Node>,
108115
expandedNodes: MutableMap<String, Boolean>,
109-
onNodeSelect: (NodeUpdate) -> Unit
116+
onNodeSelect: (NodeUpdate) -> Unit,
117+
storeNodeLocation: (Node, Offset) -> Unit
110118
) {
111119
if (children.isEmpty()) return
112120

@@ -152,7 +160,8 @@ private fun UnaffectedChildrenGroup(
152160
affectedNodes = affectedNodes,
153161
expandedNodes = expandedNodes,
154162
unaffected = true,
155-
onNodeSelect = onNodeSelect
163+
onNodeSelect = onNodeSelect,
164+
storeNodeLocation = storeNodeLocation
156165
)
157166
}
158167
}
@@ -168,7 +177,8 @@ private fun AffectedChildrenGroup(
168177
previousFrameNode: Node?,
169178
affectedNodes: Set<Node>,
170179
expandedNodes: MutableMap<String, Boolean>,
171-
onNodeSelect: (NodeUpdate) -> Unit
180+
onNodeSelect: (NodeUpdate) -> Unit,
181+
storeNodeLocation: (Node, Offset) -> Unit
172182
) {
173183
if (children.isEmpty()) return
174184

@@ -177,7 +187,8 @@ private fun AffectedChildrenGroup(
177187
previousFrameNode = previousFrameNode,
178188
affectedNodes = affectedNodes,
179189
expandedNodes = expandedNodes,
180-
onNodeSelect = onNodeSelect
190+
onNodeSelect = onNodeSelect,
191+
storeNodeLocation = storeNodeLocation
181192
)
182193
}
183194

@@ -193,8 +204,9 @@ private fun DrawChildrenInGroups(
193204
previousFrameNode: Node?,
194205
affectedNodes: Set<Node>,
195206
expandedNodes: MutableMap<String, Boolean>,
207+
onNodeSelect: (NodeUpdate) -> Unit,
208+
storeNodeLocation: (Node, Offset) -> Unit,
196209
unaffected: Boolean = false,
197-
onNodeSelect: (NodeUpdate) -> Unit
198210
) {
199211
// Split children into those with children (nested) and those without
200212
var (nestedChildren, simpleChildren) = children.partition { it.children.isNotEmpty() }
@@ -227,7 +239,8 @@ private fun DrawChildrenInGroups(
227239
previousFrameNode = previousFrameNode?.children?.get(childNode.id),
228240
affectedNodes = affectedNodes,
229241
expandedNodes = expandedNodes,
230-
onNodeSelect = onNodeSelect
242+
onNodeSelect = onNodeSelect,
243+
storeNodeLocation = storeNodeLocation
231244
)
232245
}
233246
}
@@ -250,7 +263,8 @@ private fun DrawChildrenInGroups(
250263
previousFrameNode = previousFrameNode?.children?.get(childNode.id),
251264
affectedNodes = affectedNodes,
252265
expandedNodes = expandedNodes,
253-
onNodeSelect = onNodeSelect
266+
onNodeSelect = onNodeSelect,
267+
storeNodeLocation = storeNodeLocation
254268
)
255269
}
256270
}
@@ -270,6 +284,7 @@ private fun DrawNode(
270284
isExpanded: Boolean,
271285
onNodeSelect: (NodeUpdate) -> Unit,
272286
onExpandToggle: (Node) -> Unit,
287+
storeNodeLocation: (Node, Offset) -> Unit
273288
) {
274289
val nodeUpdate = NodeUpdate.create(
275290
current = node,
@@ -289,6 +304,10 @@ private fun DrawNode(
289304
}
290305
}
291306
.padding(16.dp)
307+
.onGloballyPositioned { coords ->
308+
val offset = coords.positionInParent()
309+
storeNodeLocation(node, offset)
310+
}
292311
) {
293312
Column(horizontalAlignment = Alignment.CenterHorizontally) {
294313
Row(

workflow-trace-viewer/src/jvmMain/kotlin/com/squareup/workflow1/traceviewer/util/TraceParser.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import androidx.compose.runtime.mutableStateOf
1010
import androidx.compose.runtime.remember
1111
import androidx.compose.runtime.setValue
1212
import androidx.compose.ui.Modifier
13+
import androidx.compose.ui.geometry.Offset
1314
import com.squareup.moshi.JsonAdapter
1415
import com.squareup.workflow1.traceviewer.TraceMode
1516
import com.squareup.workflow1.traceviewer.model.Node
@@ -30,6 +31,7 @@ internal fun RenderTrace(
3031
onNodeSelect: (NodeUpdate) -> Unit,
3132
onNewFrame: () -> Unit,
3233
onNewData: (String) -> Unit,
34+
storeNodeLocation: (Node, Offset) -> Unit,
3335
modifier: Modifier = Modifier
3436
) {
3537
var isLoading by remember(traceSource) { mutableStateOf(true) }
@@ -110,6 +112,7 @@ internal fun RenderTrace(
110112
affectedNodes = affectedNodes[frameInd],
111113
expandedNodes = remember(frameInd) { mutableStateMapOf() },
112114
onNodeSelect = onNodeSelect,
115+
storeNodeLocation = storeNodeLocation
113116
)
114117
}
115118
}

0 commit comments

Comments
 (0)