diff --git a/app/src/main/app/shell/LibraryGameLaunchScreen.kt b/app/src/main/app/shell/LibraryGameLaunchScreen.kt index 68721386f..9e1085b70 100644 --- a/app/src/main/app/shell/LibraryGameLaunchScreen.kt +++ b/app/src/main/app/shell/LibraryGameLaunchScreen.kt @@ -118,6 +118,7 @@ import com.winlator.cmod.feature.library.LibraryStorageMove import com.winlator.cmod.shared.ui.layout.isPortraitLayout import com.winlator.cmod.shared.ui.layout.screenWidthDp import androidx.compose.runtime.CompositionLocalProvider +import com.winlator.cmod.shared.ui.focus.controllerConfirmOnA import com.winlator.cmod.shared.ui.focus.controllerFocusGlow import com.winlator.cmod.shared.ui.outlinedSwitchColors import com.winlator.cmod.shared.ui.nav.DialogPaneNav @@ -1275,6 +1276,7 @@ private fun LaunchSourceActionPopup( ), ) { Surface( + modifier = Modifier.controllerConfirmOnA(), color = LaunchBlack.copy(alpha = 0.78f), shape = RoundedCornerShape(12.dp), border = BorderStroke(1.dp, Color.White.copy(alpha = 0.22f)), diff --git a/app/src/main/app/shell/UnifiedActivityGameDialogs.kt b/app/src/main/app/shell/UnifiedActivityGameDialogs.kt index 37bf97be5..d1adc7534 100644 --- a/app/src/main/app/shell/UnifiedActivityGameDialogs.kt +++ b/app/src/main/app/shell/UnifiedActivityGameDialogs.kt @@ -233,6 +233,7 @@ import com.winlator.cmod.shared.ui.dialog.ContainerProgressPopup import com.winlator.cmod.shared.ui.dialog.PopupDialog import com.winlator.cmod.shared.ui.dialog.PopupTextAction import androidx.compose.foundation.focusGroup +import com.winlator.cmod.shared.ui.focus.controllerConfirmOnA import com.winlator.cmod.shared.ui.focus.controllerFocusGlow import com.winlator.cmod.shared.ui.focus.controllerMenuInput import com.winlator.cmod.shared.ui.focus.controllerTextFieldEscape @@ -2322,7 +2323,7 @@ internal fun UnifiedActivity.LibraryGameDetailDialog( ), ) { Surface( - modifier = Modifier.fillMaxSize(), + modifier = Modifier.fillMaxSize().controllerConfirmOnA(), shape = RectangleShape, color = Color.Black, ) { @@ -2541,6 +2542,7 @@ internal fun UnifiedActivity.LibraryGameDetailDialog( if (showSaveTransfer) { androidx.compose.material3.AlertDialog( onDismissRequest = { showSaveTransfer = false }, + modifier = Modifier.controllerConfirmOnA(), title = { androidx.compose.material3.Text(stringResource(R.string.retro_save_transfer_title)) }, text = { androidx.compose.material3.Text(stringResource(R.string.retro_save_transfer_message)) }, confirmButton = { diff --git a/app/src/main/shared/ui/focus/ControllerMenuModifiers.kt b/app/src/main/shared/ui/focus/ControllerMenuModifiers.kt index 214d3c937..916746274 100644 --- a/app/src/main/shared/ui/focus/ControllerMenuModifiers.kt +++ b/app/src/main/shared/ui/focus/ControllerMenuModifiers.kt @@ -28,6 +28,19 @@ import androidx.compose.ui.unit.dp private val AccentBorder = Color(0xFF00D7F5) +/** + * A gamepad button no control claims comes back as its Generic.kcm fallback key: Y as SPACE, Start/L3/R3 as + * DPAD_CENTER, which click whatever has focus. A (confirm), B (back) and the guide button keep theirs. + */ +internal fun isUnclaimedGamepadButton(keyCode: Int): Boolean = + android.view.KeyEvent.isGamepadButton(keyCode) && + keyCode != android.view.KeyEvent.KEYCODE_BUTTON_A && + keyCode != android.view.KeyEvent.KEYCODE_BUTTON_B && + keyCode != android.view.KeyEvent.KEYCODE_BUTTON_MODE + +/** For focus-navigated screens: only A presses the focused control. */ +fun Modifier.controllerConfirmOnA(): Modifier = onKeyEvent { isUnclaimedGamepadButton(it.nativeKeyEvent.keyCode) } + fun Modifier.controllerFocusBorder( cornerRadius: Dp = 10.dp, borderWidth: Dp = 2.dp, @@ -246,12 +259,7 @@ fun Modifier.controllerMenuInput( true } - android.view.KeyEvent.KEYCODE_BUTTON_Y, - android.view.KeyEvent.KEYCODE_BUTTON_L1, - android.view.KeyEvent.KEYCODE_BUTTON_R1, - -> true - - else -> false + else -> isUnclaimedGamepadButton(e.nativeKeyEvent.keyCode) } } } diff --git a/app/src/main/shared/ui/nav/DialogPaneNav.kt b/app/src/main/shared/ui/nav/DialogPaneNav.kt index 4e151bc55..2f66090b4 100644 --- a/app/src/main/shared/ui/nav/DialogPaneNav.kt +++ b/app/src/main/shared/ui/nav/DialogPaneNav.kt @@ -8,6 +8,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.ui.platform.LocalView import androidx.compose.ui.window.DialogWindowProvider +import com.winlator.cmod.shared.ui.focus.isUnclaimedGamepadButton import kotlin.math.abs private const val PANE_STICK_ENGAGE = 0.5f @@ -58,7 +59,12 @@ private class PaneNavWindowCallback( override fun dispatchKeyEvent(event: KeyEvent): Boolean { val keyCode = event.keyCode - if (!isOwnedKey(keyCode)) return base.dispatchKeyEvent(event) + if (!isOwnedKey(keyCode)) { + if (!isUnclaimedGamepadButton(keyCode)) return base.dispatchKeyEvent(event) + // Handled or not, the button is kept from falling back to DPAD_CENTER and activating. + base.dispatchKeyEvent(event) + return true + } if (event.action != KeyEvent.ACTION_DOWN) return true when (keyCode) { KeyEvent.KEYCODE_DPAD_LEFT -> handlers.onDir(PANE_DIR_LEFT)