From 60a280af24a201895d25ee6b25f304ec8ab07076 Mon Sep 17 00:00:00 2001 From: James Forward <33285502+jamfor352@users.noreply.github.com> Date: Sat, 23 Dec 2023 03:52:12 +0000 Subject: [PATCH] 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 --- .../citra_emu/activities/EmulationActivity.kt | 20 +++--- .../citra_emu/display/ScreenAdjustmentUtil.kt | 42 +++++++++++++ .../citra/citra_emu/display/ScreenLayout.kt | 22 +++++++ .../citra_emu/features/hotkeys/Hotkey.kt | 12 ++++ .../features/hotkeys/HotkeyUtility.kt | 27 ++++++++ .../features/settings/model/BooleanSetting.kt | 3 +- .../features/settings/model/IntSetting.kt | 1 + .../features/settings/model/Settings.kt | 21 +++++++ .../model/view/InputBindingSetting.kt | 8 ++- .../settings/ui/SettingsFragmentPresenter.kt | 8 ++- .../features/settings/utils/SettingsFile.kt | 29 +++++++-- .../citra_emu/fragments/EmulationFragment.kt | 61 +++++++++++-------- .../citra_emu/utils/EmulationLifecycleUtil.kt | 32 ++++++++++ .../citra_emu/utils/EmulationMenuSettings.kt | 11 +--- .../app/src/main/res/menu/menu_emulation.xml | 4 ++ .../res/menu/menu_landscape_screen_layout.xml | 4 ++ .../app/src/main/res/values/strings.xml | 4 ++ 17 files changed, 259 insertions(+), 50 deletions(-) create mode 100644 src/android/app/src/main/java/org/citra/citra_emu/display/ScreenAdjustmentUtil.kt create mode 100644 src/android/app/src/main/java/org/citra/citra_emu/display/ScreenLayout.kt create mode 100644 src/android/app/src/main/java/org/citra/citra_emu/features/hotkeys/Hotkey.kt create mode 100644 src/android/app/src/main/java/org/citra/citra_emu/features/hotkeys/HotkeyUtility.kt create mode 100644 src/android/app/src/main/java/org/citra/citra_emu/utils/EmulationLifecycleUtil.kt diff --git a/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.kt b/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.kt index 587233732..10b17a067 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.kt @@ -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 } diff --git a/src/android/app/src/main/java/org/citra/citra_emu/display/ScreenAdjustmentUtil.kt b/src/android/app/src/main/java/org/citra/citra_emu/display/ScreenAdjustmentUtil.kt new file mode 100644 index 000000000..e56007cc8 --- /dev/null +++ b/src/android/app/src/main/java/org/citra/citra_emu/display/ScreenAdjustmentUtil.kt @@ -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) + } +} diff --git a/src/android/app/src/main/java/org/citra/citra_emu/display/ScreenLayout.kt b/src/android/app/src/main/java/org/citra/citra_emu/display/ScreenLayout.kt new file mode 100644 index 000000000..85fdc28b5 --- /dev/null +++ b/src/android/app/src/main/java/org/citra/citra_emu/display/ScreenLayout.kt @@ -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 + } + } +} diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/hotkeys/Hotkey.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/hotkeys/Hotkey.kt new file mode 100644 index 000000000..b19cb03da --- /dev/null +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/hotkeys/Hotkey.kt @@ -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); +} diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/hotkeys/HotkeyUtility.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/hotkeys/HotkeyUtility.kt new file mode 100644 index 000000000..830b57b29 --- /dev/null +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/hotkeys/HotkeyUtility.kt @@ -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 + } +} diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/BooleanSetting.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/BooleanSetting.kt index 28b026f91..0a1cf915a 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/BooleanSetting.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/BooleanSetting.kt @@ -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 diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/IntSetting.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/IntSetting.kt index 12d0b4ae4..cc64d29a6 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/IntSetting.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/IntSetting.kt @@ -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), diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/Settings.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/Settings.kt index 926f669cc..d94489021 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/Settings.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/Settings.kt @@ -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" diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/InputBindingSetting.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/InputBindingSetting.kt index a6ae5e31c..55b454093 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/InputBindingSetting.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/InputBindingSetting.kt @@ -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 } diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt index fd6eda28c..efaf83540 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt @@ -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])) + } } } diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/utils/SettingsFile.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/utils/SettingsFile.kt index 83b5da972..dec3e4e0a 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/utils/SettingsFile.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/utils/SettingsFile.kt @@ -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) diff --git a/src/android/app/src/main/java/org/citra/citra_emu/fragments/EmulationFragment.kt b/src/android/app/src/main/java/org/citra/citra_emu/fragments/EmulationFragment.kt index 66ec4cfbc..eeff4ff1b 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/fragments/EmulationFragment.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/fragments/EmulationFragment.kt @@ -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() 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 diff --git a/src/android/app/src/main/java/org/citra/citra_emu/utils/EmulationLifecycleUtil.kt b/src/android/app/src/main/java/org/citra/citra_emu/utils/EmulationLifecycleUtil.kt new file mode 100644 index 000000000..8f3b5dc07 --- /dev/null +++ b/src/android/app/src/main/java/org/citra/citra_emu/utils/EmulationLifecycleUtil.kt @@ -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 = ArrayList() + private var pauseResumeHooks: MutableList = 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() + } +} diff --git a/src/android/app/src/main/java/org/citra/citra_emu/utils/EmulationMenuSettings.kt b/src/android/app/src/main/java/org/citra/citra_emu/utils/EmulationMenuSettings.kt index c07636dbb..05804551e 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/utils/EmulationMenuSettings.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/utils/EmulationMenuSettings.kt @@ -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() diff --git a/src/android/app/src/main/res/menu/menu_emulation.xml b/src/android/app/src/main/res/menu/menu_emulation.xml index 8492dfdb9..5e7d8dfb8 100644 --- a/src/android/app/src/main/res/menu/menu_emulation.xml +++ b/src/android/app/src/main/res/menu/menu_emulation.xml @@ -83,6 +83,10 @@ + + diff --git a/src/android/app/src/main/res/menu/menu_landscape_screen_layout.xml b/src/android/app/src/main/res/menu/menu_landscape_screen_layout.xml index 2b9e6f805..b02ef22f6 100644 --- a/src/android/app/src/main/res/menu/menu_landscape_screen_layout.xml +++ b/src/android/app/src/main/res/menu/menu_landscape_screen_layout.xml @@ -19,6 +19,10 @@ android:id="@+id/menu_screen_layout_sidebyside" android:title="@string/emulation_screen_layout_sidebyside" /> + + diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index 62a69b1d3..a7caab91f 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -104,6 +104,7 @@ Circle Pad C-Stick + Hotkeys Triggers Trigger D-Pad @@ -336,10 +337,13 @@ Portrait Single Screen Side by Side Screens + Hybrid Screens + Cycle Landscape Layouts Swap Screens Reset Overlay Show Overlay Close Game + Toggle Pause Are you sure that you would like to close the current game? Amiibo Load