From 9d5d07d4c611bad6128acc5e997ba034b90a4fb1 Mon Sep 17 00:00:00 2001 From: James Rich <2199651+jamesarich@users.noreply.github.com> Date: Mon, 31 Aug 2026 11:34:34 -0500 Subject: [PATCH] fix(debug): keep the Mirror tab's D-pad above the fold Cap the mirror canvas at 512.dp (4x for a 128px OLED) instead of filling the window width, fold the frame info into the toggle row, and pair Down/Back so the controls fit without scrolling on desktop. Co-Authored-By: Claude Fable 5 --- .../settings/debugging/DisplayMirror.kt | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/debugging/DisplayMirror.kt b/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/debugging/DisplayMirror.kt index f3774f0f0a..6428e86040 100644 --- a/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/debugging/DisplayMirror.kt +++ b/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/debugging/DisplayMirror.kt @@ -25,6 +25,7 @@ import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material3.FilledTonalButton @@ -52,6 +53,9 @@ import org.meshtastic.core.repository.DisplayMirrorManager import org.meshtastic.core.repository.MirrorFrame import org.meshtastic.core.repository.RadioController +// 4x scale for the common 128px-wide OLED; caps the canvas so the D-pad stays above the fold on desktop. +private val MAX_CANVAS_WIDTH = 512.dp + // Firmware input_broker_event codes (src/input/InputBroker.h). private const val INPUT_SELECT = 10 private const val INPUT_UP = 17 @@ -94,18 +98,20 @@ fun DisplayMirrorContent(modifier: Modifier = Modifier, viewModel: DisplayMirror horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(12.dp), ) { + val currentFrame = frame Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) { Switch(checked = mirroring, onCheckedChange = viewModel::setMirror) Text(text = if (mirroring) "Mirroring" else "Mirror off", style = MaterialTheme.typography.titleMedium) + if (currentFrame != null) { + Text( + text = "${currentFrame.width}x${currentFrame.height} frame #${currentFrame.frameId}", + style = MaterialTheme.typography.labelSmall, + ) + } } - val currentFrame = frame if (currentFrame != null) { MirrorFrameCanvas(currentFrame) - Text( - text = "${currentFrame.width}x${currentFrame.height} frame #${currentFrame.frameId}", - style = MaterialTheme.typography.labelSmall, - ) } else { Text(text = "No frame received yet — enable mirroring above.") } @@ -121,18 +127,18 @@ fun DisplayMirrorContent(modifier: Modifier = Modifier, viewModel: DisplayMirror } Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { FilledTonalButton(onClick = { viewModel.sendKey(INPUT_DOWN) }) { Text("Down") } - } - Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { FilledTonalButton(onClick = { viewModel.sendKey(INPUT_BACK) }) { Text("Back") } } } } -/** Draws a MONO_VLSB 1bpp framebuffer scaled to the available width, one filled rect per lit pixel. */ +/** Draws a MONO_VLSB 1bpp framebuffer scaled up, one filled rect per lit pixel. Width-capped so the controls stay in view on desktop. */ @Composable private fun MirrorFrameCanvas(frame: MirrorFrame) { val aspect = frame.width.toFloat() / frame.height.toFloat() - Canvas(modifier = Modifier.fillMaxWidth().aspectRatio(aspect).background(Color.Black)) { + Canvas( + modifier = Modifier.widthIn(max = MAX_CANVAS_WIDTH).fillMaxWidth().aspectRatio(aspect).background(Color.Black), + ) { val scale = size.width / frame.width val pixel = Size(scale, scale) for (y in 0 until frame.height) {