From 73433005362dd7dcb84403d30cd0b531f350dd24 Mon Sep 17 00:00:00 2001 From: jay-tux Date: Wed, 26 Aug 2026 11:05:34 +0200 Subject: [PATCH] UI: filtering improvements --- .../phoebench/clients/ui/ProjectView.kt | 99 ++++++++++++++++--- 1 file changed, 84 insertions(+), 15 deletions(-) diff --git a/clients/compose/src/commonMain/kotlin/com/jaytux/phoebench/clients/ui/ProjectView.kt b/clients/compose/src/commonMain/kotlin/com/jaytux/phoebench/clients/ui/ProjectView.kt index 0219ae6..1c75d49 100644 --- a/clients/compose/src/commonMain/kotlin/com/jaytux/phoebench/clients/ui/ProjectView.kt +++ b/clients/compose/src/commonMain/kotlin/com/jaytux/phoebench/clients/ui/ProjectView.kt @@ -212,7 +212,7 @@ fun AddVersionDialog(onCancel: () -> Unit, onAdd: (name: String, color: Color, m CancelConfirmXRow(onCancel, { onAdd(name.trim(), color, meta.trim()) - }, confirmText = "Create", canConfirm = name.trim().isNotBlank() && meta.trim().isNotBlank()) + }, confirmText = "Create", canConfirm = name.trim().isNotBlank()) } } } @@ -303,7 +303,7 @@ fun AddEntryDialog( DropdownMenuItem({ Text( - "Create new label...", + "Create new version...", color = LocalContentColor.current.copy(alpha = 0.75f), fontStyle = FontStyle.Italic ) @@ -379,6 +379,34 @@ fun AddEntryDialog( } } +@Composable +fun EditVersionDialog(current: ProjectVM.Version, onCancel: () -> Unit, onUpdate: (id: Uuid, name: String?, color: Color?, meta: String?) -> Unit) { + Dialog(onDismissRequest = onCancel) { + var name by remember { mutableStateOf(current.name) } + var color by remember { mutableStateOf(current.uiColor) } + var meta by remember { mutableStateOf(current.meta) } + + Surface(Modifier.padding(15.dp).widthIn(400.dp), shape = MaterialTheme.shapes.medium) { + Column(Modifier.padding(8.dp).width(IntrinsicSize.Min)) { + Text("Create version", Modifier.align(Alignment.CenterHorizontally), style = MaterialTheme.typography.headlineMedium) + OutlinedTextField(name, { name = it }, Modifier.fillMaxWidth(), label = { Text("Name") }) + Spacer(Modifier.height(10.dp)) + KolorPicker(color, { color = it }, alphaTrackVisible = false, modifier = Modifier.aspectRatio(1f)) + Spacer(Modifier.height(10.dp)) + OutlinedTextField(meta, { meta = it }, Modifier.fillMaxWidth(), label = { Text("Additional information") }) + Spacer(Modifier.height(10.dp)) + + CancelConfirmXRow(onCancel, { + val useName = name.trim() nonEq current.name + val useColor = color nonEq current.uiColor + val useMeta = meta.trim() nonEq current.meta + onUpdate(current.id, useName, useColor, useMeta) + }, confirmText = "Update", canConfirm = name.trim().isNotBlank()) + } + } + } +} + @Composable fun ProjectPlotArea(vm: ProjectVM){ val entries by vm.entries @@ -393,25 +421,27 @@ fun ProjectPlotArea(vm: ProjectVM){ var timeFilter by remember { mutableStateOf(0f..1f) } var timeFilterString by remember { mutableStateOf("") } val versionFilter = remember(versions) { mutableStateSetFrom(versions.values) } - val labelVersionFilter by versionFilter.revision - val enabledInputs = remember { mutableStateSetOf() } - val enabledHardware = remember { mutableStateSetOf() } - var filterInput by remember { mutableStateOf(null) } + val inputFilter = remember(allInputs) { mutableStateSetFrom(allInputs) } var filterHardware by remember { mutableStateOf(null) } + val versionFilterRev by versionFilter.revision + val inputFilterRev by inputFilter.revision - LaunchedEffect(entries, allInputs, allHardware, timeFilter, timeMin, timeMax, labelVersionFilter, filterInput, filterHardware) { + val enabledHardware = remember(allHardware) { mutableStateSetOf() } + val enabledInputs = remember(allInputs) { mutableStateSetOf() } + + LaunchedEffect(entries, allInputs, allHardware, timeFilter, timeMin, timeMax, versionFilterRev, inputFilterRev, filterHardware) { val timeRange = dualLerp(timeMin, timeMax, timeFilter.start, timeFilter.endInclusive) - enabledInputs.clear() enabledHardware.clear() + enabledInputs.clear() filteredEntries = entries.filter { entry -> if(entry.label !in versionFilter) return@filter false if(!(entry.timeStamp inRange timeRange)) return@filter false - if(filterInput != null && entry.input != filterInput) return@filter false + if(entry.input !in inputFilter) return@filter false if(filterHardware != null && entry.hardware != filterHardware) return@filter false - enabledInputs.add(entry.input) enabledHardware.add(entry.hardware) + enabledInputs.add(entry.input) true } } @@ -446,6 +476,7 @@ fun ProjectPlotArea(vm: ProjectVM){ var deletingVersion by remember { mutableStateOf(null) } + var editingVersion by remember { mutableStateOf(null) } var baseline by remember { mutableStateOf(null) } var baselineComparison by remember { mutableStateOf>?>(null) } var baselineMax by remember { mutableStateOf(2.0f) } @@ -614,6 +645,12 @@ fun ProjectPlotArea(vm: ProjectVM){ } } } + if(editable) { + IconButton({ editingVersion = lbl }, Modifier.align(Alignment.CenterVertically)) { + Icon(Lucide.Pencil, "Edit version") + } + } + IconButton({ baseline = if(baseline == lbl) null else lbl }, Modifier.align(Alignment.CenterVertically), enabled = lbl in versionFilter) { if(baseline == lbl) { @@ -644,14 +681,39 @@ fun ProjectPlotArea(vm: ProjectVM){ item { Spacer(Modifier.height(10.dp)) } - items((allInputs intersect enabledInputs.toSet()).toList()) { - Row(Modifier.fillMaxWidth().clickable { filterInput = if(filterInput == null) it else null }) { - Text(it, Modifier.padding(start = 5.dp)) + items((allInputs intersect inputFilter.toSet()).toList()) { + Row(Modifier.fillMaxWidth().clickable { + if(inputFilter.size == allInputs.size) { + inputFilter.clear() + inputFilter.add(it) + } + else if(inputFilter.size == 1) { + inputFilter.addAll(allInputs.toSet()); inputFilter.remove(it) + } + else { + inputFilter.toggle(it) + if(inputFilter.isEmpty()) inputFilter.addAll(allInputs.toSet()) + } + }) { + Text(it, Modifier.padding(start = 5.dp), color = LocalContentColor.current.copy(alpha = if(it in enabledInputs) 1.0f else 0.9f)) + if(it !in enabledInputs) Text(" (no data)", Modifier.align(Alignment.Bottom), + style = MaterialTheme.typography.bodySmall, color = LocalContentColor.current.copy(0.9f)) } } - items((allInputs - enabledInputs.toSet()).toList()) { - Text(it, Modifier.padding(start = 5.dp, top = 2.dp, bottom = 2.dp), color = LocalContentColor.current.copy(0.66f)) + items((allInputs - inputFilter.toSet()).toList()) { + Row(Modifier.fillMaxWidth().clickable { + if(inputFilter.size == allInputs.size) { + inputFilter.clear() + inputFilter.add(it) + } + else { + inputFilter.toggle(it) + if(inputFilter.isEmpty()) inputFilter.addAll(allInputs.toSet()) + } + }) { + Text(it, Modifier.padding(start = 5.dp), color = LocalContentColor.current.copy(0.66f)) + } } item { Spacer(Modifier.height(10.dp)) } @@ -726,6 +788,13 @@ fun ProjectPlotArea(vm: ProjectVM){ vm.deleteVersion(rmV.id) } } + + editingVersion?.let { edV -> + EditVersionDialog(edV, { editingVersion = null }) { id, name, color, meta -> + vm.updateLabel(id, name, color, meta) + editingVersion = null + } + } } @Composable