mirror of
https://github.com/ARMSX2/ARMSX2.git
synced 2026-08-24 16:50:16 -07:00
Android: draw the pause-menu highlight from the registry
The pause menu painted one selection and moved another. Its action rows took their tint from selectedAction, an index in the view model that reset on tab change and advanced only when a row was ACTIVATED, while the D-pad moved the nav registry, which drew its own focus ring. Two selections, two highlights, and the row you were pointing at was not the one lit up — which is the symptom this branch was opened for. The registry was already the real one: every grid row has registered a controllerFocusable id all along. So the fix is to read the highlight from it and delete the other model outright — the state field, both places that reset it, moveSelection, selectAction, activateSelection, and the hardcoded per-tab count table. None of them had callers left; only the highlight had survived, which is exactly why the two could disagree without anything failing loudly. Deleting that count table is worth it on its own. It duplicated each pane's row count as a literal, and its own comment records the last time they drifted: it read 4 against a list of 5, so the pad could not reach Close at all. Nothing derives a count now. One intended visible change: no row is tinted while focus is on the tab column. Previously row 0 was always tinted regardless of where the pad actually was, which is the same lie in a quieter form.
This commit is contained in:
@@ -639,8 +639,6 @@ private fun SessionPane(state: EmulationMenuUiState, viewModel: EmulationMenuVie
|
||||
MainActivityRuntime.closeGame()
|
||||
},
|
||||
),
|
||||
selected = state.selectedAction,
|
||||
onSelect = viewModel::selectAction,
|
||||
)
|
||||
// On-screen display — a single universal on/off (old-UI style); the per-stat
|
||||
// toggles live in All Settings. Plus a frame-limit switch so fast-forward is one
|
||||
@@ -692,8 +690,9 @@ private fun SessionPane(state: EmulationMenuUiState, viewModel: EmulationMenuVie
|
||||
Spacer(Modifier.height(6.dp))
|
||||
// OSD colour, cycled in place. Shares the palette with the All Settings picker rather
|
||||
// than carrying its own copy. Safe to add here: this card's rows are plain switches with
|
||||
// their own callbacks — SessionPane's selectedAction indexes the action GRID above, not
|
||||
// these, so inserting a row can't shift the controller dispatch.
|
||||
// their own callbacks, and every control on this pane — grid rows included — now
|
||||
// registers its own id with the nav registry, so inserting a row cannot shift what any
|
||||
// other row does.
|
||||
val osdColorIndex = com.armsx2.ui.settings.OSD_COLORS
|
||||
.indexOf(state.settings.osdColor).coerceAtLeast(0)
|
||||
MenuCycleRow(
|
||||
@@ -1340,15 +1339,20 @@ private data class MenuAction(
|
||||
)
|
||||
|
||||
@Composable
|
||||
private fun ActionGrid(actions: List<MenuAction>, selected: Int, onSelect: (Int) -> Unit) {
|
||||
private fun ActionGrid(actions: List<MenuAction>) {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
actions.forEachIndexed { index, item ->
|
||||
val active = index == selected
|
||||
val id = "pause.action.$index"
|
||||
// The registry is the ONE source of truth for which row is selected. The tint used
|
||||
// to come from a separate index in the view model that advanced only when a row was
|
||||
// ACTIVATED, while the D-pad moved the registry — so the menu drew one selection and
|
||||
// moved another, and the row you were pointing at was never the one lit up.
|
||||
val active = com.armsx2.ui.settings.SettingsControllerNav.isSelected(id)
|
||||
Surface(
|
||||
onClick = { onSelect(index); item.action() },
|
||||
onClick = item.action,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.controllerFocusable("pause.action.$index", onConfirm = { onSelect(index); item.action() }),
|
||||
.controllerFocusable(id, onConfirm = item.action),
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
color = if (active) MaterialTheme.colorScheme.primary.copy(alpha = 0.14f)
|
||||
else MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.48f),
|
||||
|
||||
+1
-66
@@ -25,7 +25,6 @@ enum class EmulationMenuTab(val titleKey: String) {
|
||||
|
||||
data class EmulationMenuUiState(
|
||||
val tab: EmulationMenuTab = EmulationMenuTab.Session,
|
||||
val selectedAction: Int = 0,
|
||||
val saveSlot: Int = 0,
|
||||
val settings: Settings = Settings(),
|
||||
val touchControlsVisible: Boolean = true,
|
||||
@@ -76,7 +75,6 @@ class EmulationMenuViewModel(application: Application) : AndroidViewModel(applic
|
||||
}
|
||||
state.value = state.value.copy(
|
||||
tab = initialTab ?: state.value.tab,
|
||||
selectedAction = 0,
|
||||
saveSlot = MainActivityRuntime.currentSaveSlot.value,
|
||||
settings = settings,
|
||||
touchControlsVisible = com.armsx2.ui.touch.TouchControls.visible.value,
|
||||
@@ -97,7 +95,7 @@ class EmulationMenuViewModel(application: Application) : AndroidViewModel(applic
|
||||
fun selectTab(tab: EmulationMenuTab) {
|
||||
// Nav tick when flipping to a different in-game menu tab (bumpers via cycleTab, or a tap).
|
||||
if (tab != state.value.tab) com.armsx2.MenuSfx.play(com.armsx2.MenuSfx.Event.NAV)
|
||||
state.value = state.value.copy(tab = tab, selectedAction = 0)
|
||||
state.value = state.value.copy(tab = tab)
|
||||
}
|
||||
|
||||
fun cycleTab(delta: Int) {
|
||||
@@ -106,57 +104,6 @@ class EmulationMenuViewModel(application: Application) : AndroidViewModel(applic
|
||||
selectTab(tabs[(current + delta).floorMod(tabs.size)])
|
||||
}
|
||||
|
||||
fun moveSelection(delta: Int) {
|
||||
val max = actionCount(state.value.tab) - 1
|
||||
val before = state.value.selectedAction
|
||||
val next = (before + delta).coerceIn(0, max.coerceAtLeast(0))
|
||||
if (next != before) com.armsx2.MenuSfx.play(com.armsx2.MenuSfx.Event.NAV)
|
||||
state.value = state.value.copy(selectedAction = next)
|
||||
}
|
||||
|
||||
fun selectAction(index: Int) {
|
||||
state.value = state.value.copy(selectedAction = index)
|
||||
}
|
||||
|
||||
fun activateSelection() {
|
||||
when (state.value.tab) {
|
||||
EmulationMenuTab.Session -> when (state.value.selectedAction) {
|
||||
0 -> resume()
|
||||
1 -> MainActivityRuntime.restart()
|
||||
2 -> MainActivityRuntime.promptSwapDisc()
|
||||
3 -> MainActivityRuntime.closeGame()
|
||||
}
|
||||
EmulationMenuTab.Graphics -> when (state.value.selectedAction) {
|
||||
0 -> setRenderer("auto")
|
||||
1 -> setRenderer("vulkan")
|
||||
2 -> setRenderer("opengl")
|
||||
3 -> setRenderer("software")
|
||||
}
|
||||
// Fixes is a registry-driven pane (its controls self-navigate), so it has
|
||||
// no discrete action grid — nothing to activate here.
|
||||
EmulationMenuTab.Fixes -> Unit
|
||||
EmulationMenuTab.Performance -> when (state.value.selectedAction) {
|
||||
0 -> updateSettings { it.copy(frameLimitEnable = !it.frameLimitEnable) }
|
||||
1 -> setSpeed(it = state.value.settings.nominalSpeedPercent + 5)
|
||||
2 -> setFrameSkip((state.value.settings.frameSkip + 1) % 6)
|
||||
}
|
||||
EmulationMenuTab.Controls -> when (state.value.selectedAction) {
|
||||
0 -> editTouchControls()
|
||||
1 -> toggleTouchControls()
|
||||
}
|
||||
EmulationMenuTab.Options -> when (state.value.selectedAction) {
|
||||
0 -> updateSettings { it.copy(enablePatches = !it.enablePatches) }
|
||||
1 -> updateSettings { it.copy(enableCheats = !it.enableCheats) }
|
||||
2 -> updateSettings { it.copy(enableWideScreenPatches = !it.enableWideScreenPatches) }
|
||||
3 -> updateSettings { it.copy(enableNoInterlacingPatches = !it.enableNoInterlacingPatches) }
|
||||
}
|
||||
EmulationMenuTab.Achievements -> when (state.value.selectedAction) {
|
||||
0 -> requestToggleHardcore()
|
||||
1 -> openAchievements()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun resume() {
|
||||
dismissHandler?.invoke() ?: resumeImmediately()
|
||||
}
|
||||
@@ -352,18 +299,6 @@ class EmulationMenuViewModel(application: Application) : AndroidViewModel(applic
|
||||
state.value = state.value.copy(settings = updated)
|
||||
}
|
||||
|
||||
private fun actionCount(tab: EmulationMenuTab): Int = when (tab) {
|
||||
// MUST match SessionPane's action list length. This was 4 against a list of 5, so the pad
|
||||
// could never reach Close at all.
|
||||
EmulationMenuTab.Session -> 5
|
||||
EmulationMenuTab.Graphics -> 4
|
||||
EmulationMenuTab.Fixes -> 0
|
||||
EmulationMenuTab.Performance -> 3
|
||||
EmulationMenuTab.Controls -> 2
|
||||
EmulationMenuTab.Options -> 5
|
||||
EmulationMenuTab.Achievements -> 2
|
||||
}
|
||||
|
||||
private fun Int.floorMod(modulus: Int): Int = ((this % modulus) + modulus) % modulus
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user