From 4bb80d6501ebac85160339e698c8e8612fa8954b Mon Sep 17 00:00:00 2001 From: Matt Hills Date: Sun, 12 Apr 2026 16:26:22 -0400 Subject: [PATCH] Replace button chip grid with controller diagram in test screen Render pressed buttons as a visual gamepad layout instead of a flat grid of chips. Fix stale overlay visibility preference in GameScreen. --- .../controller/ControllerTestScreen.kt | 162 ++++++++++++------ .../emulate/ui/screens/game/GameScreen.kt | 2 +- 2 files changed, 108 insertions(+), 56 deletions(-) diff --git a/app/src/main/java/com/lazy/emulate/ui/screens/controller/ControllerTestScreen.kt b/app/src/main/java/com/lazy/emulate/ui/screens/controller/ControllerTestScreen.kt index bffcaf8..374eca7 100644 --- a/app/src/main/java/com/lazy/emulate/ui/screens/controller/ControllerTestScreen.kt +++ b/app/src/main/java/com/lazy/emulate/ui/screens/controller/ControllerTestScreen.kt @@ -4,6 +4,7 @@ import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -11,13 +12,16 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack @@ -41,6 +45,9 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.lazy.emulate.input.ControllerManager @@ -154,7 +161,7 @@ fun ControllerTestScreen( // Live button indicators Text("Pressed Buttons", style = MaterialTheme.typography.titleMedium) Spacer(Modifier.height(6.dp)) - ButtonGrid(pressed = pressed) + ControllerDiagram(pressed = pressed) Spacer(Modifier.height(16.dp)) @@ -221,54 +228,120 @@ fun ControllerTestScreen( } @Composable -private fun ButtonGrid(pressed: Set) { - val rows = listOf( - listOf(GamepadButton.DPAD_UP, GamepadButton.DPAD_DOWN, GamepadButton.DPAD_LEFT, GamepadButton.DPAD_RIGHT), - listOf(GamepadButton.FACE_TOP, GamepadButton.FACE_BOTTOM, GamepadButton.FACE_LEFT, GamepadButton.FACE_RIGHT), - listOf(GamepadButton.C_BUTTON, GamepadButton.Z_BUTTON, GamepadButton.MODE), - listOf(GamepadButton.L1, GamepadButton.R1, GamepadButton.L2, GamepadButton.R2), - listOf(GamepadButton.L3, GamepadButton.R3, GamepadButton.START, GamepadButton.SELECT) - ) +private fun ControllerDiagram(pressed: Set) { + val btnSize = 34.dp + val smallBtn = 28.dp + val pillW = 38.dp + val pillH = 22.dp + Card(modifier = Modifier.fillMaxWidth()) { - Column(modifier = Modifier.padding(8.dp)) { - rows.forEach { row -> - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(6.dp) - ) { - row.forEach { btn -> - ButtonChip( - label = shortLabel(btn), - active = btn in pressed, - modifier = Modifier.weight(1f) - ) - } - } - Spacer(Modifier.height(6.dp)) + BoxWithConstraints( + modifier = Modifier + .fillMaxWidth() + .height(200.dp) + .padding(8.dp) + ) { + val w = maxWidth + val h = maxHeight + + // --- Shoulder buttons --- + // L2 / R2 (top row) + ControllerButton("L2", GamepadButton.L2 in pressed, pillW, pillH, RoundedCornerShape(4.dp), + x = w * 0.08f, y = h * 0.02f) + ControllerButton("R2", GamepadButton.R2 in pressed, pillW, pillH, RoundedCornerShape(4.dp), + x = w - w * 0.08f - pillW, y = h * 0.02f) + // L1 / R1 + ControllerButton("L1", GamepadButton.L1 in pressed, pillW, pillH, RoundedCornerShape(4.dp), + x = w * 0.08f, y = h * 0.15f) + ControllerButton("R1", GamepadButton.R1 in pressed, pillW, pillH, RoundedCornerShape(4.dp), + x = w - w * 0.08f - pillW, y = h * 0.15f) + + // --- D-Pad (left side) --- + val dpadCx = w * 0.18f + val dpadCy = h * 0.50f + val dpadOff = btnSize * 0.85f + ControllerButton("↑", GamepadButton.DPAD_UP in pressed, smallBtn, smallBtn, CircleShape, + x = dpadCx - smallBtn / 2, y = dpadCy - dpadOff - smallBtn / 2) + ControllerButton("↓", GamepadButton.DPAD_DOWN in pressed, smallBtn, smallBtn, CircleShape, + x = dpadCx - smallBtn / 2, y = dpadCy + dpadOff - smallBtn / 2) + ControllerButton("←", GamepadButton.DPAD_LEFT in pressed, smallBtn, smallBtn, CircleShape, + x = dpadCx - dpadOff - smallBtn / 2, y = dpadCy - smallBtn / 2) + ControllerButton("→", GamepadButton.DPAD_RIGHT in pressed, smallBtn, smallBtn, CircleShape, + x = dpadCx + dpadOff - smallBtn / 2, y = dpadCy - smallBtn / 2) + + // --- Face buttons (right side, diamond) --- + val faceCx = w * 0.82f + val faceCy = h * 0.50f + val faceOff = btnSize * 0.85f + ControllerButton("△", GamepadButton.FACE_TOP in pressed, btnSize, btnSize, CircleShape, + x = faceCx - btnSize / 2, y = faceCy - faceOff - btnSize / 2) + ControllerButton("✕", GamepadButton.FACE_BOTTOM in pressed, btnSize, btnSize, CircleShape, + x = faceCx - btnSize / 2, y = faceCy + faceOff - btnSize / 2) + ControllerButton("□", GamepadButton.FACE_LEFT in pressed, btnSize, btnSize, CircleShape, + x = faceCx - faceOff - btnSize / 2, y = faceCy - btnSize / 2) + ControllerButton("○", GamepadButton.FACE_RIGHT in pressed, btnSize, btnSize, CircleShape, + x = faceCx + faceOff - btnSize / 2, y = faceCy - btnSize / 2) + + // --- Center buttons (Select / Start) --- + val centerY = h * 0.38f + ControllerButton("SL", GamepadButton.SELECT in pressed, pillW, pillH, RoundedCornerShape(4.dp), + x = w * 0.5f - pillW - 4.dp, y = centerY) + ControllerButton("ST", GamepadButton.START in pressed, pillW, pillH, RoundedCornerShape(4.dp), + x = w * 0.5f + 4.dp, y = centerY) + + // --- Stick clicks (L3 / R3) --- + val stickY = h * 0.78f + ControllerButton("L3", GamepadButton.L3 in pressed, btnSize, btnSize, CircleShape, + x = w * 0.33f - btnSize / 2, y = stickY - btnSize / 2) + ControllerButton("R3", GamepadButton.R3 in pressed, btnSize, btnSize, CircleShape, + x = w * 0.67f - btnSize / 2, y = stickY - btnSize / 2) + + // --- Genesis extras (bottom center, only visible when pressed) --- + val extraY = h * 0.62f + if (GamepadButton.MODE in pressed || GamepadButton.Z_BUTTON in pressed || GamepadButton.C_BUTTON in pressed) { + ControllerButton("MD", GamepadButton.MODE in pressed, smallBtn, smallBtn, CircleShape, + x = w * 0.42f - smallBtn / 2, y = extraY) + ControllerButton("Z", GamepadButton.Z_BUTTON in pressed, smallBtn, smallBtn, CircleShape, + x = w * 0.50f - smallBtn / 2, y = extraY) + ControllerButton("C", GamepadButton.C_BUTTON in pressed, smallBtn, smallBtn, CircleShape, + x = w * 0.58f - smallBtn / 2, y = extraY) } } } } @Composable -private fun ButtonChip(label: String, active: Boolean, modifier: Modifier = Modifier) { +private fun ControllerButton( + label: String, + active: Boolean, + width: Dp, + height: Dp, + shape: androidx.compose.ui.graphics.Shape, + x: Dp, + y: Dp +) { + val density = LocalDensity.current val bg = if (active) MaterialTheme.colorScheme.primary else Color.Transparent val fg = if (active) MaterialTheme.colorScheme.onPrimary else MaterialTheme.colorScheme.onSurfaceVariant + val borderColor = if (active) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.outlineVariant + Box( - modifier = modifier - .height(36.dp) - .border( - width = 1.dp, - color = MaterialTheme.colorScheme.outline, - shape = RoundedCornerShape(6.dp) - ) - .background(bg, RoundedCornerShape(6.dp)), + modifier = Modifier + .offset { + IntOffset( + with(density) { x.roundToPx() }, + with(density) { y.roundToPx() } + ) + } + .size(width, height) + .border(1.5.dp, borderColor, shape) + .background(bg, shape), contentAlignment = Alignment.Center ) { Text( text = label, color = fg, - fontSize = 12.sp, + fontSize = 10.sp, fontWeight = if (active) FontWeight.Bold else FontWeight.Normal ) } @@ -341,24 +414,3 @@ private fun AxisRow(label: String, x: Float, y: Float) { } } -private fun shortLabel(button: GamepadButton): String = when (button) { - GamepadButton.DPAD_UP -> "↑" - GamepadButton.DPAD_DOWN -> "↓" - GamepadButton.DPAD_LEFT -> "←" - GamepadButton.DPAD_RIGHT -> "→" - GamepadButton.FACE_TOP -> "△/Y/X" - GamepadButton.FACE_BOTTOM -> "✕/B/A" - GamepadButton.FACE_LEFT -> "□/X/Y" - GamepadButton.FACE_RIGHT -> "○/A/B" - GamepadButton.L1 -> "L1" - GamepadButton.R1 -> "R1" - GamepadButton.L2 -> "L2" - GamepadButton.R2 -> "R2" - GamepadButton.L3 -> "L3" - GamepadButton.R3 -> "R3" - GamepadButton.START -> "START" - GamepadButton.SELECT -> "SELECT" - GamepadButton.MODE -> "MODE" - GamepadButton.Z_BUTTON -> "Z" - GamepadButton.C_BUTTON -> "C" -} diff --git a/app/src/main/java/com/lazy/emulate/ui/screens/game/GameScreen.kt b/app/src/main/java/com/lazy/emulate/ui/screens/game/GameScreen.kt index 6fa4ba4..d9a2e77 100644 --- a/app/src/main/java/com/lazy/emulate/ui/screens/game/GameScreen.kt +++ b/app/src/main/java/com/lazy/emulate/ui/screens/game/GameScreen.kt @@ -100,7 +100,7 @@ fun GameScreen( var saveSlots by remember { mutableStateOf>(emptyList()) } val activeController by controllerManager.activeController.collectAsState() - val overlayVisibility = remember { preferencesManager.overlayVisibility } + val overlayVisibility = preferencesManager.overlayVisibility val showTouchControls = when (overlayVisibility) { OverlayVisibility.ALWAYS_SHOW -> true OverlayVisibility.ALWAYS_HIDE -> false