mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-31 05:40:04 +00:00 
			
		
		
		
	feat(android-hotkeys): Introduce hotkey support for Android app and add missing hybrid layout (#7241)
* feat(android-hotkeys): Introduce hotkey support for Android app * android: Fix settings not saving for layout options - screen swap + layout. * android: Fix `from` method to default to "DEFAULT" if passed an invalid method (and also not be based on ordering) * android: PR response - name to togglePause
This commit is contained in:
		
							parent
							
								
									178e602589
								
							
						
					
					
						commit
						60a280af24
					
				
					 17 changed files with 259 additions and 50 deletions
				
			
		|  | @ -20,7 +20,6 @@ import android.widget.Toast | |||
| import androidx.activity.result.contract.ActivityResultContracts | ||||
| import androidx.activity.viewModels | ||||
| import androidx.appcompat.app.AppCompatActivity | ||||
| import androidx.core.app.NotificationManagerCompat | ||||
| import androidx.core.view.WindowCompat | ||||
| import androidx.core.view.WindowInsetsCompat | ||||
| import androidx.core.view.WindowInsetsControllerCompat | ||||
|  | @ -32,13 +31,15 @@ import org.citra.citra_emu.R | |||
| import org.citra.citra_emu.camera.StillImageCameraHelper.OnFilePickerResult | ||||
| import org.citra.citra_emu.contracts.OpenFileResultContract | ||||
| import org.citra.citra_emu.databinding.ActivityEmulationBinding | ||||
| import org.citra.citra_emu.display.ScreenAdjustmentUtil | ||||
| import org.citra.citra_emu.features.hotkeys.HotkeyUtility | ||||
| import org.citra.citra_emu.features.settings.model.SettingsViewModel | ||||
| import org.citra.citra_emu.features.settings.model.view.InputBindingSetting | ||||
| import org.citra.citra_emu.fragments.MessageDialogFragment | ||||
| import org.citra.citra_emu.utils.ControllerMappingHelper | ||||
| import org.citra.citra_emu.utils.EmulationMenuSettings | ||||
| import org.citra.citra_emu.utils.FileBrowserHelper | ||||
| import org.citra.citra_emu.utils.ForegroundService | ||||
| import org.citra.citra_emu.utils.EmulationLifecycleUtil | ||||
| import org.citra.citra_emu.utils.ThemeUtil | ||||
| import org.citra.citra_emu.viewmodel.EmulationViewModel | ||||
| 
 | ||||
|  | @ -52,6 +53,8 @@ class EmulationActivity : AppCompatActivity() { | |||
|     private val emulationViewModel: EmulationViewModel by viewModels() | ||||
| 
 | ||||
|     private lateinit var binding: ActivityEmulationBinding | ||||
|     private lateinit var screenAdjustmentUtil: ScreenAdjustmentUtil | ||||
|     private lateinit var hotkeyUtility: HotkeyUtility | ||||
| 
 | ||||
|     override fun onCreate(savedInstanceState: Bundle?) { | ||||
|         ThemeUtil.setTheme(this) | ||||
|  | @ -61,6 +64,8 @@ class EmulationActivity : AppCompatActivity() { | |||
|         super.onCreate(savedInstanceState) | ||||
| 
 | ||||
|         binding = ActivityEmulationBinding.inflate(layoutInflater) | ||||
|         screenAdjustmentUtil = ScreenAdjustmentUtil(windowManager, settingsViewModel.settings) | ||||
|         hotkeyUtility = HotkeyUtility(screenAdjustmentUtil) | ||||
|         setContentView(binding.root) | ||||
| 
 | ||||
|         val navHostFragment = | ||||
|  | @ -73,15 +78,11 @@ class EmulationActivity : AppCompatActivity() { | |||
|         // Set these options now so that the SurfaceView the game renders into is the right size. | ||||
|         enableFullscreenImmersive() | ||||
| 
 | ||||
|         // Override Citra core INI with the one set by our in game menu | ||||
|         NativeLibrary.swapScreens( | ||||
|             EmulationMenuSettings.swapScreens, | ||||
|             windowManager.defaultDisplay.rotation | ||||
|         ) | ||||
| 
 | ||||
|         // Start a foreground service to prevent the app from getting killed in the background | ||||
|         foregroundService = Intent(this, ForegroundService::class.java) | ||||
|         startForegroundService(foregroundService) | ||||
| 
 | ||||
|         EmulationLifecycleUtil.addShutdownHook(hook = { this.finish() }) | ||||
|     } | ||||
| 
 | ||||
|     // On some devices, the system bars will not disappear on first boot or after some | ||||
|  | @ -103,6 +104,7 @@ class EmulationActivity : AppCompatActivity() { | |||
|     } | ||||
| 
 | ||||
|     override fun onDestroy() { | ||||
|         EmulationLifecycleUtil.clear() | ||||
|         stopForegroundService(this) | ||||
|         super.onDestroy() | ||||
|     } | ||||
|  | @ -188,6 +190,8 @@ class EmulationActivity : AppCompatActivity() { | |||
|                     onBackPressed() | ||||
|                 } | ||||
| 
 | ||||
|                 hotkeyUtility.handleHotkey(button) | ||||
| 
 | ||||
|                 // Normal key events. | ||||
|                 NativeLibrary.ButtonState.PRESSED | ||||
|             } | ||||
|  |  | |||
|  | @ -0,0 +1,42 @@ | |||
| // Copyright 2023 Citra Emulator Project | ||||
| // Licensed under GPLv2 or any later version | ||||
| // Refer to the license.txt file included. | ||||
| 
 | ||||
| package org.citra.citra_emu.display | ||||
| 
 | ||||
| import android.view.WindowManager | ||||
| import org.citra.citra_emu.NativeLibrary | ||||
| import org.citra.citra_emu.features.settings.model.BooleanSetting | ||||
| import org.citra.citra_emu.features.settings.model.IntSetting | ||||
| import org.citra.citra_emu.features.settings.model.Settings | ||||
| import org.citra.citra_emu.features.settings.utils.SettingsFile | ||||
| import org.citra.citra_emu.utils.EmulationMenuSettings | ||||
| 
 | ||||
| class ScreenAdjustmentUtil(private val windowManager: WindowManager, | ||||
|                            private val settings: Settings) { | ||||
|     fun swapScreen() { | ||||
|         val isEnabled = !EmulationMenuSettings.swapScreens | ||||
|         EmulationMenuSettings.swapScreens = isEnabled | ||||
|         NativeLibrary.swapScreens( | ||||
|             isEnabled, | ||||
|             windowManager.defaultDisplay.rotation | ||||
|         ) | ||||
|         BooleanSetting.SWAP_SCREEN.boolean = isEnabled | ||||
|         settings.saveSetting(BooleanSetting.SWAP_SCREEN, SettingsFile.FILE_NAME_CONFIG) | ||||
|     } | ||||
| 
 | ||||
|     fun cycleLayouts() { | ||||
|         val nextLayout = (EmulationMenuSettings.landscapeScreenLayout + 1) % ScreenLayout.entries.size | ||||
|         changeScreenOrientation(ScreenLayout.from(nextLayout)) | ||||
|     } | ||||
| 
 | ||||
|     fun changeScreenOrientation(layoutOption: ScreenLayout) { | ||||
|         EmulationMenuSettings.landscapeScreenLayout = layoutOption.int | ||||
|         NativeLibrary.notifyOrientationChange( | ||||
|             EmulationMenuSettings.landscapeScreenLayout, | ||||
|             windowManager.defaultDisplay.rotation | ||||
|         ) | ||||
|         IntSetting.SCREEN_LAYOUT.int = layoutOption.int | ||||
|         settings.saveSetting(IntSetting.SCREEN_LAYOUT, SettingsFile.FILE_NAME_CONFIG) | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,22 @@ | |||
| // Copyright 2023 Citra Emulator Project | ||||
| // Licensed under GPLv2 or any later version | ||||
| // Refer to the license.txt file included. | ||||
| 
 | ||||
| package org.citra.citra_emu.display | ||||
| 
 | ||||
| enum class ScreenLayout(val int: Int) { | ||||
|     // These must match what is defined in src/common/settings.h | ||||
|     DEFAULT(0), | ||||
|     SINGLE_SCREEN(1), | ||||
|     LARGE_SCREEN(2), | ||||
|     SIDE_SCREEN(3), | ||||
|     HYBRID_SCREEN(4), | ||||
|     MOBILE_PORTRAIT(5), | ||||
|     MOBILE_LANDSCAPE(6); | ||||
| 
 | ||||
|     companion object { | ||||
|         fun from(int: Int): ScreenLayout { | ||||
|             return entries.firstOrNull { it.int == int } ?: DEFAULT | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,12 @@ | |||
| // Copyright 2023 Citra Emulator Project | ||||
| // Licensed under GPLv2 or any later version | ||||
| // Refer to the license.txt file included. | ||||
| 
 | ||||
| package org.citra.citra_emu.features.hotkeys | ||||
| 
 | ||||
| enum class Hotkey(val button: Int) { | ||||
|     SWAP_SCREEN(10001), | ||||
|     CYCLE_LAYOUT(10002), | ||||
|     CLOSE_GAME(10003), | ||||
|     PAUSE_OR_RESUME(10004); | ||||
| } | ||||
|  | @ -0,0 +1,27 @@ | |||
| // Copyright 2023 Citra Emulator Project | ||||
| // Licensed under GPLv2 or any later version | ||||
| // Refer to the license.txt file included. | ||||
| 
 | ||||
| package org.citra.citra_emu.features.hotkeys | ||||
| 
 | ||||
| import org.citra.citra_emu.utils.EmulationLifecycleUtil | ||||
| import org.citra.citra_emu.display.ScreenAdjustmentUtil | ||||
| 
 | ||||
| class HotkeyUtility(private val screenAdjustmentUtil: ScreenAdjustmentUtil) { | ||||
| 
 | ||||
|     val hotkeyButtons = Hotkey.entries.map { it.button } | ||||
| 
 | ||||
|     fun handleHotkey(bindedButton: Int): Boolean { | ||||
|         if(hotkeyButtons.contains(bindedButton)) { | ||||
|             when (bindedButton) { | ||||
|                 Hotkey.SWAP_SCREEN.button -> screenAdjustmentUtil.swapScreen() | ||||
|                 Hotkey.CYCLE_LAYOUT.button -> screenAdjustmentUtil.cycleLayouts() | ||||
|                 Hotkey.CLOSE_GAME.button -> EmulationLifecycleUtil.closeGame() | ||||
|                 Hotkey.PAUSE_OR_RESUME.button -> EmulationLifecycleUtil.pauseOrResume() | ||||
|                 else -> {} | ||||
|             } | ||||
|             return true | ||||
|         } | ||||
|         return false | ||||
|     } | ||||
| } | ||||
|  | @ -12,7 +12,8 @@ enum class BooleanSetting( | |||
|     SPIRV_SHADER_GEN("spirv_shader_gen", Settings.SECTION_RENDERER, true), | ||||
|     ASYNC_SHADERS("async_shader_compilation", Settings.SECTION_RENDERER, false), | ||||
|     PLUGIN_LOADER("plugin_loader", Settings.SECTION_SYSTEM, false), | ||||
|     ALLOW_PLUGIN_LOADER("allow_plugin_loader", Settings.SECTION_SYSTEM, true); | ||||
|     ALLOW_PLUGIN_LOADER("allow_plugin_loader", Settings.SECTION_SYSTEM, true), | ||||
|     SWAP_SCREEN("swap_screen", Settings.SECTION_LAYOUT, false); | ||||
| 
 | ||||
|     override var boolean: Boolean = defaultValue | ||||
| 
 | ||||
|  |  | |||
|  | @ -22,6 +22,7 @@ enum class IntSetting( | |||
|     CARDBOARD_SCREEN_SIZE("cardboard_screen_size", Settings.SECTION_LAYOUT, 85), | ||||
|     CARDBOARD_X_SHIFT("cardboard_x_shift", Settings.SECTION_LAYOUT, 0), | ||||
|     CARDBOARD_Y_SHIFT("cardboard_y_shift", Settings.SECTION_LAYOUT, 0), | ||||
|     SCREEN_LAYOUT("layout_option", Settings.SECTION_LAYOUT, 0), | ||||
|     AUDIO_INPUT_TYPE("output_type", Settings.SECTION_AUDIO, 0), | ||||
|     NEW_3DS("is_new_3ds", Settings.SECTION_SYSTEM, 1), | ||||
|     CPU_CLOCK_SPEED("cpu_clock_percentage", Settings.SECTION_CORE, 100), | ||||
|  |  | |||
|  | @ -94,6 +94,10 @@ class Settings { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fun saveSetting(setting: AbstractSetting, filename: String) { | ||||
|         SettingsFile.saveFile(filename, setting) | ||||
|     } | ||||
| 
 | ||||
|     companion object { | ||||
|         const val SECTION_CORE = "Core" | ||||
|         const val SECTION_SYSTEM = "System" | ||||
|  | @ -128,6 +132,11 @@ class Settings { | |||
|         const val KEY_DPAD_AXIS_VERTICAL = "dpad_axis_vertical" | ||||
|         const val KEY_DPAD_AXIS_HORIZONTAL = "dpad_axis_horizontal" | ||||
| 
 | ||||
|         const val HOTKEY_SCREEN_SWAP = "hotkey_screen_swap" | ||||
|         const val HOTKEY_CYCLE_LAYOUT = "hotkey_toggle_layout" | ||||
|         const val HOTKEY_CLOSE_GAME = "hotkey_close_game" | ||||
|         const val HOTKEY_PAUSE_OR_RESUME = "hotkey_pause_or_resume_game" | ||||
| 
 | ||||
|         val buttonKeys = listOf( | ||||
|             KEY_BUTTON_A, | ||||
|             KEY_BUTTON_B, | ||||
|  | @ -174,6 +183,18 @@ class Settings { | |||
|             R.string.button_zl, | ||||
|             R.string.button_zr | ||||
|         ) | ||||
|         val hotKeys = listOf( | ||||
|             HOTKEY_SCREEN_SWAP, | ||||
|             HOTKEY_CYCLE_LAYOUT, | ||||
|             HOTKEY_CLOSE_GAME, | ||||
|             HOTKEY_PAUSE_OR_RESUME | ||||
|         ) | ||||
|         val hotkeyTitles = listOf( | ||||
|             R.string.emulation_swap_screens, | ||||
|             R.string.emulation_cycle_landscape_layouts, | ||||
|             R.string.emulation_close_game, | ||||
|             R.string.emulation_toggle_pause | ||||
|         ) | ||||
| 
 | ||||
|         const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch" | ||||
|         const val PREF_MATERIAL_YOU = "MaterialYouTheme" | ||||
|  |  | |||
|  | @ -6,14 +6,15 @@ package org.citra.citra_emu.features.settings.model.view | |||
| 
 | ||||
| import android.content.Context | ||||
| import android.content.SharedPreferences | ||||
| import androidx.preference.PreferenceManager | ||||
| import android.view.InputDevice | ||||
| import android.view.InputDevice.MotionRange | ||||
| import android.view.KeyEvent | ||||
| import android.widget.Toast | ||||
| import androidx.preference.PreferenceManager | ||||
| import org.citra.citra_emu.CitraApplication | ||||
| import org.citra.citra_emu.NativeLibrary | ||||
| import org.citra.citra_emu.R | ||||
| import org.citra.citra_emu.features.hotkeys.Hotkey | ||||
| import org.citra.citra_emu.features.settings.model.AbstractSetting | ||||
| import org.citra.citra_emu.features.settings.model.Settings | ||||
| 
 | ||||
|  | @ -127,6 +128,11 @@ class InputBindingSetting( | |||
|                 Settings.KEY_BUTTON_DOWN -> NativeLibrary.ButtonType.DPAD_DOWN | ||||
|                 Settings.KEY_BUTTON_LEFT -> NativeLibrary.ButtonType.DPAD_LEFT | ||||
|                 Settings.KEY_BUTTON_RIGHT -> NativeLibrary.ButtonType.DPAD_RIGHT | ||||
| 
 | ||||
|                 Settings.HOTKEY_SCREEN_SWAP -> Hotkey.SWAP_SCREEN.button | ||||
|                 Settings.HOTKEY_CYCLE_LAYOUT -> Hotkey.CYCLE_LAYOUT.button | ||||
|                 Settings.HOTKEY_CLOSE_GAME -> Hotkey.CLOSE_GAME.button | ||||
|                 Settings.HOTKEY_PAUSE_OR_RESUME -> Hotkey.PAUSE_OR_RESUME.button | ||||
|                 else -> -1 | ||||
|             } | ||||
| 
 | ||||
|  |  | |||
|  | @ -38,8 +38,8 @@ import org.citra.citra_emu.features.settings.model.view.SwitchSetting | |||
| import org.citra.citra_emu.features.settings.utils.SettingsFile | ||||
| import org.citra.citra_emu.fragments.ResetSettingsDialogFragment | ||||
| import org.citra.citra_emu.utils.BirthdayMonth | ||||
| import org.citra.citra_emu.utils.SystemSaveGame | ||||
| import org.citra.citra_emu.utils.Log | ||||
| import org.citra.citra_emu.utils.SystemSaveGame | ||||
| import org.citra.citra_emu.utils.ThemeUtil | ||||
| 
 | ||||
| class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) { | ||||
|  | @ -620,6 +620,12 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) | |||
|                 val button = getInputObject(key) | ||||
|                 add(InputBindingSetting(button, Settings.triggerTitles[i])) | ||||
|             } | ||||
| 
 | ||||
|             add(HeaderSetting(R.string.controller_hotkeys)) | ||||
|             Settings.hotKeys.forEachIndexed { i: Int, key: String -> | ||||
|                 val button = getInputObject(key) | ||||
|                 add(InputBindingSetting(button, Settings.hotkeyTitles[i])) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -8,7 +8,6 @@ import android.content.Context | |||
| import android.net.Uri | ||||
| import androidx.documentfile.provider.DocumentFile | ||||
| import org.citra.citra_emu.CitraApplication | ||||
| import org.citra.citra_emu.NativeLibrary | ||||
| import org.citra.citra_emu.R | ||||
| import org.citra.citra_emu.features.settings.model.AbstractSetting | ||||
| import org.citra.citra_emu.features.settings.model.BooleanSetting | ||||
|  | @ -23,9 +22,11 @@ import org.citra.citra_emu.utils.BiMap | |||
| import org.citra.citra_emu.utils.DirectoryInitialization.userDirectory | ||||
| import org.citra.citra_emu.utils.Log | ||||
| import org.ini4j.Wini | ||||
| import java.io.* | ||||
| import java.lang.NumberFormatException | ||||
| import java.util.* | ||||
| import java.io.BufferedReader | ||||
| import java.io.FileNotFoundException | ||||
| import java.io.IOException | ||||
| import java.io.InputStreamReader | ||||
| import java.util.TreeMap | ||||
| 
 | ||||
| 
 | ||||
| /** | ||||
|  | @ -146,6 +147,26 @@ object SettingsFile { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fun saveFile( | ||||
|         fileName: String, | ||||
|         setting: AbstractSetting | ||||
|     ) { | ||||
|         val ini = getSettingsFile(fileName) | ||||
|         try { | ||||
|             val context: Context = CitraApplication.appContext | ||||
|             val inputStream = context.contentResolver.openInputStream(ini.uri) | ||||
|             val writer = Wini(inputStream) | ||||
|             writer.put(setting.section, setting.key, setting.valueAsString) | ||||
|             inputStream!!.close() | ||||
|             val outputStream = context.contentResolver.openOutputStream(ini.uri, "wt") | ||||
|             writer.store(outputStream) | ||||
|             outputStream!!.flush() | ||||
|             outputStream.close() | ||||
|         } catch (e: Exception) { | ||||
|             Log.error("[SettingsFile] File not found: $fileName.ini: ${e.message}") | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private fun mapSectionNameFromIni(generalSectionName: String): String? { | ||||
|         return if (sectionsMap.getForward(generalSectionName) != null) { | ||||
|             sectionsMap.getForward(generalSectionName) | ||||
|  |  | |||
|  | @ -15,7 +15,6 @@ import android.os.Looper | |||
| import android.os.SystemClock | ||||
| import android.view.Choreographer | ||||
| import android.view.LayoutInflater | ||||
| import android.view.MenuItem | ||||
| import android.view.MotionEvent | ||||
| import android.view.Surface | ||||
| import android.view.SurfaceHolder | ||||
|  | @ -33,6 +32,7 @@ import androidx.drawerlayout.widget.DrawerLayout | |||
| import androidx.drawerlayout.widget.DrawerLayout.DrawerListener | ||||
| import androidx.fragment.app.Fragment | ||||
| import androidx.fragment.app.activityViewModels | ||||
| import androidx.fragment.app.viewModels | ||||
| import androidx.lifecycle.Lifecycle | ||||
| import androidx.lifecycle.lifecycleScope | ||||
| import androidx.lifecycle.repeatOnLifecycle | ||||
|  | @ -51,6 +51,9 @@ import org.citra.citra_emu.activities.EmulationActivity | |||
| import org.citra.citra_emu.databinding.DialogCheckboxBinding | ||||
| import org.citra.citra_emu.databinding.DialogSliderBinding | ||||
| import org.citra.citra_emu.databinding.FragmentEmulationBinding | ||||
| import org.citra.citra_emu.display.ScreenAdjustmentUtil | ||||
| import org.citra.citra_emu.display.ScreenLayout | ||||
| import org.citra.citra_emu.features.settings.model.SettingsViewModel | ||||
| import org.citra.citra_emu.features.settings.ui.SettingsActivity | ||||
| import org.citra.citra_emu.features.settings.utils.SettingsFile | ||||
| import org.citra.citra_emu.model.Game | ||||
|  | @ -60,10 +63,10 @@ import org.citra.citra_emu.utils.EmulationMenuSettings | |||
| import org.citra.citra_emu.utils.FileUtil | ||||
| import org.citra.citra_emu.utils.GameHelper | ||||
| import org.citra.citra_emu.utils.GameIconUtils | ||||
| import org.citra.citra_emu.utils.EmulationLifecycleUtil | ||||
| import org.citra.citra_emu.utils.Log | ||||
| import org.citra.citra_emu.utils.ViewUtils | ||||
| import org.citra.citra_emu.viewmodel.EmulationViewModel | ||||
| import java.lang.NullPointerException | ||||
| 
 | ||||
| class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.FrameCallback { | ||||
|     private val preferences: SharedPreferences | ||||
|  | @ -80,8 +83,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram | |||
|     private val args by navArgs<EmulationFragmentArgs>() | ||||
| 
 | ||||
|     private lateinit var game: Game | ||||
|     private lateinit var screenAdjustmentUtil: ScreenAdjustmentUtil | ||||
| 
 | ||||
|     private val emulationViewModel: EmulationViewModel by activityViewModels() | ||||
|     private val settingsViewModel: SettingsViewModel by viewModels() | ||||
| 
 | ||||
|     override fun onAttach(context: Context) { | ||||
|         super.onAttach(context) | ||||
|  | @ -137,6 +142,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram | |||
|         retainInstance = true | ||||
|         emulationState = EmulationState(game.path) | ||||
|         emulationActivity = requireActivity() as EmulationActivity | ||||
|         screenAdjustmentUtil = ScreenAdjustmentUtil(emulationActivity.windowManager, settingsViewModel.settings) | ||||
|         EmulationLifecycleUtil.addShutdownHook(hook = { emulationState.stop() }) | ||||
|         EmulationLifecycleUtil.addPauseResumeHook(hook = { togglePause() }) | ||||
|     } | ||||
| 
 | ||||
|     override fun onCreateView( | ||||
|  | @ -258,12 +266,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram | |||
|                 } | ||||
| 
 | ||||
|                 R.id.menu_swap_screens -> { | ||||
|                     val isEnabled = !EmulationMenuSettings.swapScreens | ||||
|                     EmulationMenuSettings.swapScreens = isEnabled | ||||
|                     NativeLibrary.swapScreens( | ||||
|                         isEnabled, | ||||
|                         requireActivity().windowManager.defaultDisplay.rotation | ||||
|                     ) | ||||
|                     screenAdjustmentUtil.swapScreen() | ||||
|                     true | ||||
|                 } | ||||
| 
 | ||||
|  | @ -315,8 +318,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram | |||
|                         .setTitle(R.string.emulation_close_game) | ||||
|                         .setMessage(R.string.emulation_close_game_message) | ||||
|                         .setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int -> | ||||
|                             emulationState.stop() | ||||
|                             requireActivity().finish() | ||||
|                             EmulationLifecycleUtil.closeGame() | ||||
|                         } | ||||
|                         .setNegativeButton(android.R.string.cancel) { _: DialogInterface?, _: Int -> | ||||
|                             NativeLibrary.unPauseEmulation() | ||||
|  | @ -410,6 +412,14 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram | |||
|         setInsets() | ||||
|     } | ||||
| 
 | ||||
|     private fun togglePause() { | ||||
|         if(emulationState.isPaused) { | ||||
|             emulationState.unpause() | ||||
|         } else { | ||||
|             emulationState.pause() | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     override fun onResume() { | ||||
|         super.onResume() | ||||
|         Choreographer.getInstance().postFrameCallback(this) | ||||
|  | @ -666,15 +676,18 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram | |||
|         popupMenu.menuInflater.inflate(R.menu.menu_landscape_screen_layout, popupMenu.menu) | ||||
| 
 | ||||
|         val layoutOptionMenuItem = when (EmulationMenuSettings.landscapeScreenLayout) { | ||||
|             EmulationMenuSettings.LayoutOption_SingleScreen -> | ||||
|             ScreenLayout.SINGLE_SCREEN.int -> | ||||
|                 R.id.menu_screen_layout_single | ||||
| 
 | ||||
|             EmulationMenuSettings.LayoutOption_SideScreen -> | ||||
|             ScreenLayout.SIDE_SCREEN.int -> | ||||
|                 R.id.menu_screen_layout_sidebyside | ||||
| 
 | ||||
|             EmulationMenuSettings.LayoutOption_MobilePortrait -> | ||||
|             ScreenLayout.MOBILE_PORTRAIT.int -> | ||||
|                 R.id.menu_screen_layout_portrait | ||||
| 
 | ||||
|             ScreenLayout.HYBRID_SCREEN.int -> | ||||
|                 R.id.menu_screen_layout_hybrid | ||||
| 
 | ||||
|             else -> R.id.menu_screen_layout_landscape | ||||
|         } | ||||
|         popupMenu.menu.findItem(layoutOptionMenuItem).setChecked(true) | ||||
|  | @ -682,22 +695,27 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram | |||
|         popupMenu.setOnMenuItemClickListener { | ||||
|             when (it.itemId) { | ||||
|                 R.id.menu_screen_layout_landscape -> { | ||||
|                     changeScreenOrientation(EmulationMenuSettings.LayoutOption_MobileLandscape, it) | ||||
|                     screenAdjustmentUtil.changeScreenOrientation(ScreenLayout.MOBILE_LANDSCAPE) | ||||
|                     true | ||||
|                 } | ||||
| 
 | ||||
|                 R.id.menu_screen_layout_portrait -> { | ||||
|                     changeScreenOrientation(EmulationMenuSettings.LayoutOption_MobilePortrait, it) | ||||
|                     screenAdjustmentUtil.changeScreenOrientation(ScreenLayout.MOBILE_PORTRAIT) | ||||
|                     true | ||||
|                 } | ||||
| 
 | ||||
|                 R.id.menu_screen_layout_single -> { | ||||
|                     changeScreenOrientation(EmulationMenuSettings.LayoutOption_SingleScreen, it) | ||||
|                     screenAdjustmentUtil.changeScreenOrientation(ScreenLayout.SINGLE_SCREEN) | ||||
|                     true | ||||
|                 } | ||||
| 
 | ||||
|                 R.id.menu_screen_layout_sidebyside -> { | ||||
|                     changeScreenOrientation(EmulationMenuSettings.LayoutOption_SideScreen, it) | ||||
|                     screenAdjustmentUtil.changeScreenOrientation(ScreenLayout.SIDE_SCREEN) | ||||
|                     true | ||||
|                 } | ||||
| 
 | ||||
|                 R.id.menu_screen_layout_hybrid -> { | ||||
|                     screenAdjustmentUtil.changeScreenOrientation(ScreenLayout.HYBRID_SCREEN) | ||||
|                     true | ||||
|                 } | ||||
| 
 | ||||
|  | @ -708,15 +726,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram | |||
|         popupMenu.show() | ||||
|     } | ||||
| 
 | ||||
|     private fun changeScreenOrientation(layoutOption: Int, item: MenuItem) { | ||||
|         item.setChecked(true) | ||||
|         NativeLibrary.notifyOrientationChange( | ||||
|             layoutOption, | ||||
|             requireActivity().windowManager.defaultDisplay.rotation | ||||
|         ) | ||||
|         EmulationMenuSettings.landscapeScreenLayout = layoutOption | ||||
|     } | ||||
| 
 | ||||
|     private fun editControlsPlacement() { | ||||
|         if (binding.surfaceInputOverlay.isInEditMode) { | ||||
|             binding.doneControlConfig.visibility = View.GONE | ||||
|  |  | |||
|  | @ -0,0 +1,32 @@ | |||
| // Copyright 2023 Citra Emulator Project | ||||
| // Licensed under GPLv2 or any later version | ||||
| // Refer to the license.txt file included. | ||||
| 
 | ||||
| package org.citra.citra_emu.utils | ||||
| 
 | ||||
| object EmulationLifecycleUtil { | ||||
|     private var shutdownHooks: MutableList<Runnable> = ArrayList() | ||||
|     private var pauseResumeHooks: MutableList<Runnable> = ArrayList() | ||||
| 
 | ||||
| 
 | ||||
|     fun closeGame() { | ||||
|         shutdownHooks.forEach(Runnable::run) | ||||
|     } | ||||
| 
 | ||||
|     fun pauseOrResume() { | ||||
|         pauseResumeHooks.forEach(Runnable::run) | ||||
|     } | ||||
| 
 | ||||
|     fun addShutdownHook(hook: Runnable) { | ||||
|         shutdownHooks.add(hook) | ||||
|     } | ||||
| 
 | ||||
|     fun addPauseResumeHook(hook: Runnable) { | ||||
|         pauseResumeHooks.add(hook) | ||||
|     } | ||||
| 
 | ||||
|     fun clear() { | ||||
|         pauseResumeHooks.clear() | ||||
|         shutdownHooks.clear() | ||||
|     } | ||||
| } | ||||
|  | @ -7,19 +7,12 @@ package org.citra.citra_emu.utils | |||
| import androidx.drawerlayout.widget.DrawerLayout | ||||
| import androidx.preference.PreferenceManager | ||||
| import org.citra.citra_emu.CitraApplication | ||||
| import org.citra.citra_emu.display.ScreenLayout | ||||
| 
 | ||||
| object EmulationMenuSettings { | ||||
|     private val preferences = | ||||
|         PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext) | ||||
| 
 | ||||
|     // These must match what is defined in src/common/settings.h | ||||
|     const val LayoutOption_Default = 0 | ||||
|     const val LayoutOption_SingleScreen = 1 | ||||
|     const val LayoutOption_LargeScreen = 2 | ||||
|     const val LayoutOption_SideScreen = 3 | ||||
|     const val LayoutOption_MobilePortrait = 5 | ||||
|     const val LayoutOption_MobileLandscape = 6 | ||||
| 
 | ||||
|     var joystickRelCenter: Boolean | ||||
|         get() = preferences.getBoolean("EmulationMenuSettings_JoystickRelCenter", true) | ||||
|         set(value) { | ||||
|  | @ -37,7 +30,7 @@ object EmulationMenuSettings { | |||
|     var landscapeScreenLayout: Int | ||||
|         get() = preferences.getInt( | ||||
|             "EmulationMenuSettings_LandscapeScreenLayout", | ||||
|             LayoutOption_MobileLandscape | ||||
|             ScreenLayout.MOBILE_LANDSCAPE.int | ||||
|         ) | ||||
|         set(value) { | ||||
|             preferences.edit() | ||||
|  |  | |||
|  | @ -83,6 +83,10 @@ | |||
|                 <item | ||||
|                     android:id="@+id/menu_screen_layout_sidebyside" | ||||
|                     android:title="@string/emulation_screen_layout_sidebyside" /> | ||||
| 
 | ||||
|                 <item | ||||
|                     android:id="@+id/menu_screen_layout_hybrid" | ||||
|                     android:title="@string/emulation_screen_layout_hybrid" /> | ||||
|             </group> | ||||
|         </menu> | ||||
|     </item> | ||||
|  |  | |||
|  | @ -19,6 +19,10 @@ | |||
|             android:id="@+id/menu_screen_layout_sidebyside" | ||||
|             android:title="@string/emulation_screen_layout_sidebyside" /> | ||||
| 
 | ||||
|         <item | ||||
|             android:id="@+id/menu_screen_layout_hybrid" | ||||
|             android:title="@string/emulation_screen_layout_hybrid" /> | ||||
| 
 | ||||
|     </group> | ||||
| 
 | ||||
| </menu> | ||||
|  |  | |||
|  | @ -104,6 +104,7 @@ | |||
|     <!-- Input related strings --> | ||||
|     <string name="controller_circlepad">Circle Pad</string> | ||||
|     <string name="controller_c">C-Stick</string> | ||||
|     <string name="controller_hotkeys">Hotkeys</string> | ||||
|     <string name="controller_triggers">Triggers</string> | ||||
|     <string name="controller_trigger">Trigger</string> | ||||
|     <string name="controller_dpad">D-Pad</string> | ||||
|  | @ -336,10 +337,13 @@ | |||
|     <string name="emulation_screen_layout_portrait">Portrait</string> | ||||
|     <string name="emulation_screen_layout_single">Single Screen</string> | ||||
|     <string name="emulation_screen_layout_sidebyside">Side by Side Screens</string> | ||||
|     <string name="emulation_screen_layout_hybrid">Hybrid Screens</string> | ||||
|     <string name="emulation_cycle_landscape_layouts">Cycle Landscape Layouts</string> | ||||
|     <string name="emulation_swap_screens">Swap Screens</string> | ||||
|     <string name="emulation_touch_overlay_reset">Reset Overlay</string> | ||||
|     <string name="emulation_show_overlay">Show Overlay</string> | ||||
|     <string name="emulation_close_game">Close Game</string> | ||||
|     <string name="emulation_toggle_pause">Toggle Pause</string> | ||||
|     <string name="emulation_close_game_message">Are you sure that you would like to close the current game?</string> | ||||
|     <string name="menu_emulation_amiibo">Amiibo</string> | ||||
|     <string name="menu_emulation_amiibo_load">Load</string> | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue