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.
This commit is contained in:
2026-04-12 16:26:22 -04:00
parent f19aeffdce
commit 4bb80d6501
2 changed files with 108 additions and 56 deletions

View File

@@ -4,6 +4,7 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.border import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer 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.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack 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.graphics.Color
import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight 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.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import com.lazy.emulate.input.ControllerManager import com.lazy.emulate.input.ControllerManager
@@ -154,7 +161,7 @@ fun ControllerTestScreen(
// Live button indicators // Live button indicators
Text("Pressed Buttons", style = MaterialTheme.typography.titleMedium) Text("Pressed Buttons", style = MaterialTheme.typography.titleMedium)
Spacer(Modifier.height(6.dp)) Spacer(Modifier.height(6.dp))
ButtonGrid(pressed = pressed) ControllerDiagram(pressed = pressed)
Spacer(Modifier.height(16.dp)) Spacer(Modifier.height(16.dp))
@@ -221,54 +228,120 @@ fun ControllerTestScreen(
} }
@Composable @Composable
private fun ButtonGrid(pressed: Set<GamepadButton>) { private fun ControllerDiagram(pressed: Set<GamepadButton>) {
val rows = listOf( val btnSize = 34.dp
listOf(GamepadButton.DPAD_UP, GamepadButton.DPAD_DOWN, GamepadButton.DPAD_LEFT, GamepadButton.DPAD_RIGHT), val smallBtn = 28.dp
listOf(GamepadButton.FACE_TOP, GamepadButton.FACE_BOTTOM, GamepadButton.FACE_LEFT, GamepadButton.FACE_RIGHT), val pillW = 38.dp
listOf(GamepadButton.C_BUTTON, GamepadButton.Z_BUTTON, GamepadButton.MODE), val pillH = 22.dp
listOf(GamepadButton.L1, GamepadButton.R1, GamepadButton.L2, GamepadButton.R2),
listOf(GamepadButton.L3, GamepadButton.R3, GamepadButton.START, GamepadButton.SELECT)
)
Card(modifier = Modifier.fillMaxWidth()) { Card(modifier = Modifier.fillMaxWidth()) {
Column(modifier = Modifier.padding(8.dp)) { BoxWithConstraints(
rows.forEach { row -> modifier = Modifier
Row( .fillMaxWidth()
modifier = Modifier.fillMaxWidth(), .height(200.dp)
horizontalArrangement = Arrangement.spacedBy(6.dp) .padding(8.dp)
) { ) {
row.forEach { btn -> val w = maxWidth
ButtonChip( val h = maxHeight
label = shortLabel(btn),
active = btn in pressed, // --- Shoulder buttons ---
modifier = Modifier.weight(1f) // 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),
Spacer(Modifier.height(6.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 @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 bg = if (active) MaterialTheme.colorScheme.primary else Color.Transparent
val fg = if (active) MaterialTheme.colorScheme.onPrimary else MaterialTheme.colorScheme.onSurfaceVariant val fg = if (active) MaterialTheme.colorScheme.onPrimary else MaterialTheme.colorScheme.onSurfaceVariant
val borderColor = if (active) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.outlineVariant
Box( Box(
modifier = modifier modifier = Modifier
.height(36.dp) .offset {
.border( IntOffset(
width = 1.dp, with(density) { x.roundToPx() },
color = MaterialTheme.colorScheme.outline, with(density) { y.roundToPx() }
shape = RoundedCornerShape(6.dp)
) )
.background(bg, RoundedCornerShape(6.dp)), }
.size(width, height)
.border(1.5.dp, borderColor, shape)
.background(bg, shape),
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
Text( Text(
text = label, text = label,
color = fg, color = fg,
fontSize = 12.sp, fontSize = 10.sp,
fontWeight = if (active) FontWeight.Bold else FontWeight.Normal 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"
}

View File

@@ -100,7 +100,7 @@ fun GameScreen(
var saveSlots by remember { mutableStateOf<List<SaveSlotInfo>>(emptyList()) } var saveSlots by remember { mutableStateOf<List<SaveSlotInfo>>(emptyList()) }
val activeController by controllerManager.activeController.collectAsState() val activeController by controllerManager.activeController.collectAsState()
val overlayVisibility = remember { preferencesManager.overlayVisibility } val overlayVisibility = preferencesManager.overlayVisibility
val showTouchControls = when (overlayVisibility) { val showTouchControls = when (overlayVisibility) {
OverlayVisibility.ALWAYS_SHOW -> true OverlayVisibility.ALWAYS_SHOW -> true
OverlayVisibility.ALWAYS_HIDE -> false OverlayVisibility.ALWAYS_HIDE -> false