From f4f40c4c34e4b02aef64108a5fa77eb189ff9941 Mon Sep 17 00:00:00 2001 From: cmclark00 Date: Mon, 31 Mar 2025 07:52:01 -0400 Subject: [PATCH] Implement full gamepad menu support and title screen start --- app/src/main/java/com/mintris/MainActivity.kt | 115 +++++++++++++++++- .../com/mintris/game/GamepadController.kt | 43 ++++++- 2 files changed, 155 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/mintris/MainActivity.kt b/app/src/main/java/com/mintris/MainActivity.kt index 9d8e35d..916e837 100644 --- a/app/src/main/java/com/mintris/MainActivity.kt +++ b/app/src/main/java/com/mintris/MainActivity.kt @@ -41,7 +41,8 @@ import android.app.AlertDialog class MainActivity : AppCompatActivity(), GamepadController.GamepadConnectionListener, - GamepadController.GamepadMenuListener { + GamepadController.GamepadMenuListener, + GamepadController.GamepadNavigationListener { companion object { private const val TAG = "MainActivity" @@ -93,6 +94,10 @@ class MainActivity : AppCompatActivity(), // Track connected gamepads to detect changes private val connectedGamepads = mutableSetOf() + // Track currently selected menu item in pause menu for gamepad navigation + private var currentMenuSelection = 0 + private val pauseMenuItems = mutableListOf() + override fun onCreate(savedInstanceState: Bundle?) { // Register activity result launcher for high score entry highScoreEntryLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> @@ -124,6 +129,7 @@ class MainActivity : AppCompatActivity(), gamepadController = GamepadController(gameView) gamepadController.setGamepadConnectionListener(this) gamepadController.setGamepadMenuListener(this) + gamepadController.setGamepadNavigationListener(this) // Set up touch event forwarding binding.touchInterceptor?.setOnTouchListener { _, event -> @@ -395,6 +401,9 @@ class MainActivity : AppCompatActivity(), if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { window.setDecorFitsSystemWindows(false) } + + // Initialize pause menu items for gamepad navigation + initPauseMenuNavigation() } /** @@ -576,7 +585,7 @@ class MainActivity : AppCompatActivity(), unlockedThemes = progressionManager.getUnlockedThemes(), currentTheme = currentTheme ) - + // Update block skin selector - handle both standard and landscape versions blockSkinSelector.updateBlockSkins( progressionManager.getUnlockedBlocks(), @@ -591,6 +600,15 @@ class MainActivity : AppCompatActivity(), gameView.getCurrentBlockSkin(), progressionManager.getPlayerLevel() ) + + // Initialize pause menu navigation + initPauseMenuNavigation() + + // Reset and highlight first selectable menu item + if (pauseMenuItems.isNotEmpty()) { + currentMenuSelection = if (binding.resumeButton.visibility == View.VISIBLE) 0 else 1 + highlightMenuItem(currentMenuSelection) + } } /** @@ -1013,6 +1031,13 @@ class MainActivity : AppCompatActivity(), // First check if it's a gamepad input if (GamepadController.isGamepad(event.device)) { + // Handle title screen start with gamepad + if (titleScreen.visibility == View.VISIBLE && + (keyCode == KeyEvent.KEYCODE_BUTTON_A || keyCode == KeyEvent.KEYCODE_BUTTON_START)) { + titleScreen.performClick() + return true + } + // If gamepad input was handled by the controller, consume the event if (gamepadController.handleKeyEvent(keyCode, event)) { return true @@ -1086,7 +1111,93 @@ class MainActivity : AppCompatActivity(), // If pause menu is showing, handle as a resume hidePauseMenu() resumeGame() + } else if (titleScreen.visibility == View.VISIBLE) { + // If title screen is showing, start the game + titleScreen.performClick() } } } + + /** + * Implements GamepadNavigationListener to handle menu navigation + */ + override fun onMenuUp() { + runOnUiThread { + if (binding.pauseContainer.visibility == View.VISIBLE) { + moveMenuSelectionUp() + } + } + } + + override fun onMenuDown() { + runOnUiThread { + if (binding.pauseContainer.visibility == View.VISIBLE) { + moveMenuSelectionDown() + } + } + } + + override fun onMenuSelect() { + runOnUiThread { + if (binding.pauseContainer.visibility == View.VISIBLE) { + activateSelectedMenuItem() + } else if (titleScreen.visibility == View.VISIBLE) { + titleScreen.performClick() + } + } + } + + /** + * Initialize pause menu items for gamepad navigation + */ + private fun initPauseMenuNavigation() { + pauseMenuItems.clear() + pauseMenuItems.add(binding.resumeButton) + pauseMenuItems.add(binding.pauseStartButton) + pauseMenuItems.add(binding.pauseRestartButton) + pauseMenuItems.add(binding.highScoresButton) + pauseMenuItems.add(binding.statsButton) + pauseMenuItems.add(binding.pauseLevelUpButton) + pauseMenuItems.add(binding.pauseLevelDownButton) + } + + /** + * Highlight the currently selected menu item + */ + private fun highlightMenuItem(index: Int) { + // Reset all items to normal state + pauseMenuItems.forEachIndexed { i, item -> + item.alpha = if (i == index) 1.0f else 0.7f + item.scaleX = if (i == index) 1.2f else 1.0f + item.scaleY = if (i == index) 1.2f else 1.0f + } + } + + /** + * Move menu selection up + */ + private fun moveMenuSelectionUp() { + if (pauseMenuItems.isEmpty()) return + currentMenuSelection = (currentMenuSelection - 1 + pauseMenuItems.size) % pauseMenuItems.size + highlightMenuItem(currentMenuSelection) + gameHaptics.vibrateForPieceMove() + } + + /** + * Move menu selection down + */ + private fun moveMenuSelectionDown() { + if (pauseMenuItems.isEmpty()) return + currentMenuSelection = (currentMenuSelection + 1) % pauseMenuItems.size + highlightMenuItem(currentMenuSelection) + gameHaptics.vibrateForPieceMove() + } + + /** + * Activate the currently selected menu item + */ + private fun activateSelectedMenuItem() { + if (pauseMenuItems.isEmpty() || currentMenuSelection < 0 || currentMenuSelection >= pauseMenuItems.size) return + pauseMenuItems[currentMenuSelection].performClick() + } } \ No newline at end of file diff --git a/app/src/main/java/com/mintris/game/GamepadController.kt b/app/src/main/java/com/mintris/game/GamepadController.kt index 949fca3..2d12bee 100644 --- a/app/src/main/java/com/mintris/game/GamepadController.kt +++ b/app/src/main/java/com/mintris/game/GamepadController.kt @@ -149,9 +149,16 @@ class GamepadController( fun onPauseRequested() } + interface GamepadNavigationListener { + fun onMenuUp() + fun onMenuDown() + fun onMenuSelect() + } + // Listeners private var connectionListener: GamepadConnectionListener? = null private var menuListener: GamepadMenuListener? = null + private var navigationListener: GamepadNavigationListener? = null // Currently active gamepad for rumble private var activeGamepad: InputDevice? = null @@ -170,6 +177,13 @@ class GamepadController( menuListener = listener } + /** + * Set a listener for gamepad navigation events + */ + fun setGamepadNavigationListener(listener: GamepadNavigationListener) { + navigationListener = listener + } + /** * Function to check for newly connected gamepads. * Call this periodically from the activity to detect connection changes. @@ -253,7 +267,34 @@ class GamepadController( */ fun handleKeyEvent(keyCode: Int, event: KeyEvent): Boolean { // Skip if game is not active - if (!gameView.isActive()) return false + if (!gameView.isActive()) { + // Handle menu navigation even when game is not active + if (event.action == KeyEvent.ACTION_DOWN) { + when (keyCode) { + KeyEvent.KEYCODE_DPAD_UP -> { + navigationListener?.onMenuUp() + return true + } + KeyEvent.KEYCODE_DPAD_DOWN -> { + navigationListener?.onMenuDown() + return true + } + KeyEvent.KEYCODE_BUTTON_A, + KeyEvent.KEYCODE_DPAD_CENTER -> { + navigationListener?.onMenuSelect() + return true + } + } + } + + // Handle menu/start button for pause menu + if (event.action == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_BUTTON_START) { + menuListener?.onPauseRequested() + return true + } + + return false + } val device = event.device if (!isGamepad(device)) return false