@@ -81,6 +81,7 @@ import androidx.compose.runtime.saveable.rememberSaveable
8181import androidx.compose.runtime.setValue
8282import androidx.compose.ui.Alignment
8383import androidx.compose.ui.Modifier
84+ import androidx.compose.ui.draw.alpha
8485import androidx.compose.ui.graphics.Color
8586import androidx.compose.ui.platform.LocalContext
8687import androidx.compose.ui.text.font.FontFamily
@@ -706,6 +707,55 @@ private fun InspectTab(vm: WheelDiagnosticsViewModel) {
706707 return @Column
707708 }
708709
710+ // Range filter: hide bytes outside [min..max]. Empty input on either
711+ // side means "no bound on that side". Hidden cells render dimly so
712+ // the byte offsets stay aligned in the grid (otherwise tapping the
713+ // wrong cell to log would be too easy).
714+ var minFilterText by rememberSaveable { mutableStateOf(" " ) }
715+ var maxFilterText by rememberSaveable { mutableStateOf(" " ) }
716+ val minFilter = minFilterText.toIntOrNull()
717+ val maxFilter = maxFilterText.toIntOrNull()
718+ fun outOfRange (v : Int ): Boolean =
719+ (minFilter != null && v < minFilter) || (maxFilter != null && v > maxFilter)
720+ val hiddenCount = latestBytes.count { outOfRange(it) }
721+
722+ Row (
723+ verticalAlignment = Alignment .CenterVertically ,
724+ modifier = Modifier .fillMaxWidth().padding(bottom = 6 .dp)
725+ ) {
726+ androidx.compose.material3.OutlinedTextField (
727+ value = minFilterText,
728+ onValueChange = { v -> minFilterText = v.filter { it.isDigit() } },
729+ label = { Text (" min" , style = MaterialTheme .typography.labelSmall) },
730+ singleLine = true ,
731+ modifier = Modifier .width(80 .dp),
732+ textStyle = MaterialTheme .typography.bodySmall
733+ )
734+ Spacer (Modifier .width(6 .dp))
735+ androidx.compose.material3.OutlinedTextField (
736+ value = maxFilterText,
737+ onValueChange = { v -> maxFilterText = v.filter { it.isDigit() } },
738+ label = { Text (" max" , style = MaterialTheme .typography.labelSmall) },
739+ singleLine = true ,
740+ modifier = Modifier .width(80 .dp),
741+ textStyle = MaterialTheme .typography.bodySmall
742+ )
743+ Spacer (Modifier .width(8 .dp))
744+ if (hiddenCount > 0 ) {
745+ Text (
746+ " ($hiddenCount hidden)" ,
747+ style = MaterialTheme .typography.labelSmall,
748+ color = MaterialTheme .colorScheme.onSurfaceVariant
749+ )
750+ }
751+ Spacer (Modifier .weight(1f ))
752+ if (minFilter != null || maxFilter != null ) {
753+ IconButton (onClick = { minFilterText = " " ; maxFilterText = " " }) {
754+ Icon (Icons .Default .Close , contentDescription = " Clear filter" )
755+ }
756+ }
757+ }
758+
709759 // 6-column grid of byte cells. weight(1f) claims the leftover Column
710760 // height so the bottom row isn't clipped — the earlier fillMaxSize
711761 // approach was reading "as much as you want" rather than "exactly
@@ -717,7 +767,7 @@ private fun InspectTab(vm: WheelDiagnosticsViewModel) {
717767 ) {
718768 items(latestBytes.size) { off ->
719769 val v = latestBytes[off]
720- ByteInspectCell (off, v) {
770+ ByteInspectCell (off, v, dimmed = outOfRange(v) ) {
721771 vm.logByteInspection(selected, off, v)
722772 }
723773 }
@@ -726,11 +776,18 @@ private fun InspectTab(vm: WheelDiagnosticsViewModel) {
726776}
727777
728778@Composable
729- private fun ByteInspectCell (offset : Int , value : Int , onTap : () -> Unit ) {
779+ private fun ByteInspectCell (
780+ offset : Int ,
781+ value : Int ,
782+ dimmed : Boolean = false,
783+ onTap : () -> Unit
784+ ) {
730785 val hex = remember(value) { " %02x" .format(value) }
786+ val cellAlpha = if (dimmed) 0.18f else 1f
731787 Column (
732788 modifier = Modifier
733789 .padding(2 .dp)
790+ .alpha(cellAlpha)
734791 .border(
735792 1 .dp,
736793 MaterialTheme .colorScheme.outline.copy(alpha = 0.3f ),
0 commit comments