From 45f52709a6b448221fa94c8dab424f4d5bb6ce27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20N=C3=BCsse?= Date: Sun, 14 Jul 2024 00:54:19 +0200 Subject: [PATCH] android: add quicksave hotkeys (#181) --- .../java/org/citra/citra_emu/NativeLibrary.kt | 18 +++++++- .../citra_emu/activities/EmulationActivity.kt | 2 +- .../citra_emu/features/hotkeys/Hotkey.kt | 4 +- .../features/hotkeys/HotkeyUtility.kt | 23 +++++++++- .../features/settings/model/Settings.kt | 10 ++++- .../model/view/InputBindingSetting.kt | 2 + .../citra_emu/fragments/EmulationFragment.kt | 42 ++++++++++++++----- .../app/src/main/res/values/strings.xml | 9 ++++ src/core/savestate.cpp | 2 +- src/core/savestate.h | 2 +- 10 files changed, 96 insertions(+), 18 deletions(-) diff --git a/src/android/app/src/main/java/org/citra/citra_emu/NativeLibrary.kt b/src/android/app/src/main/java/org/citra/citra_emu/NativeLibrary.kt index 84a62d898..135794d65 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/NativeLibrary.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/NativeLibrary.kt @@ -527,12 +527,28 @@ object NativeLibrary { external fun removeAmiibo() - const val SAVESTATE_SLOT_COUNT = 10 + const val SAVESTATE_SLOT_COUNT = 11 + const val QUICKSAVE_SLOT = 0 external fun getSavestateInfo(): Array? external fun saveState(slot: Int) + fun loadStateIfAvailable(slot: Int): Boolean { + var available = false + getSavestateInfo()?.forEach { + if (it.slot == slot){ + available = true + return@forEach + } + } + if (available) { + loadState(slot) + return true + } + return false + } + external fun loadState(slot: Int) /** 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 a911b0c31..6b9c89c9d 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 @@ -66,7 +66,7 @@ class EmulationActivity : AppCompatActivity() { binding = ActivityEmulationBinding.inflate(layoutInflater) screenAdjustmentUtil = ScreenAdjustmentUtil(windowManager, settingsViewModel.settings) - hotkeyUtility = HotkeyUtility(screenAdjustmentUtil) + hotkeyUtility = HotkeyUtility(screenAdjustmentUtil, this) setContentView(binding.root) val navHostFragment = 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 index b19cb03da..db99abf67 100644 --- 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 @@ -8,5 +8,7 @@ enum class Hotkey(val button: Int) { SWAP_SCREEN(10001), CYCLE_LAYOUT(10002), CLOSE_GAME(10003), - PAUSE_OR_RESUME(10004); + PAUSE_OR_RESUME(10004), + QUICKSAVE(10005), + QUICKLOAD(10006); } 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 index 830b57b29..25f6a493b 100644 --- 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 @@ -4,10 +4,14 @@ package org.citra.citra_emu.features.hotkeys +import android.content.Context +import android.widget.Toast +import org.citra.citra_emu.NativeLibrary +import org.citra.citra_emu.R import org.citra.citra_emu.utils.EmulationLifecycleUtil import org.citra.citra_emu.display.ScreenAdjustmentUtil -class HotkeyUtility(private val screenAdjustmentUtil: ScreenAdjustmentUtil) { +class HotkeyUtility(private val screenAdjustmentUtil: ScreenAdjustmentUtil, private val context: Context) { val hotkeyButtons = Hotkey.entries.map { it.button } @@ -18,6 +22,23 @@ class HotkeyUtility(private val screenAdjustmentUtil: ScreenAdjustmentUtil) { Hotkey.CYCLE_LAYOUT.button -> screenAdjustmentUtil.cycleLayouts() Hotkey.CLOSE_GAME.button -> EmulationLifecycleUtil.closeGame() Hotkey.PAUSE_OR_RESUME.button -> EmulationLifecycleUtil.pauseOrResume() + Hotkey.QUICKSAVE.button -> { + NativeLibrary.saveState(NativeLibrary.QUICKSAVE_SLOT) + Toast.makeText(context, + context.getString(R.string.quicksave_saving), + Toast.LENGTH_SHORT).show() + } + Hotkey.QUICKLOAD.button -> { + val wasLoaded = NativeLibrary.loadStateIfAvailable(NativeLibrary.QUICKSAVE_SLOT) + val stringRes = if(wasLoaded) { + R.string.quickload_loading + } else { + R.string.quickload_not_found + } + Toast.makeText(context, + context.getString(stringRes), + Toast.LENGTH_SHORT).show() + } else -> {} } return true 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 d94489021..0e92138cb 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 @@ -136,6 +136,8 @@ class Settings { 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" + const val HOTKEY_QUICKSAVE = "hotkey_quickload" + const val HOTKEY_QUICKlOAD = "hotkey_quickpause" val buttonKeys = listOf( KEY_BUTTON_A, @@ -187,13 +189,17 @@ class Settings { HOTKEY_SCREEN_SWAP, HOTKEY_CYCLE_LAYOUT, HOTKEY_CLOSE_GAME, - HOTKEY_PAUSE_OR_RESUME + HOTKEY_PAUSE_OR_RESUME, + HOTKEY_QUICKSAVE, + HOTKEY_QUICKlOAD ) val hotkeyTitles = listOf( R.string.emulation_swap_screens, R.string.emulation_cycle_landscape_layouts, R.string.emulation_close_game, - R.string.emulation_toggle_pause + R.string.emulation_toggle_pause, + R.string.emulation_quicksave, + R.string.emulation_quickload, ) const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch" 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 55b454093..8165830f6 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 @@ -133,6 +133,8 @@ class InputBindingSetting( 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 + Settings.HOTKEY_QUICKSAVE -> Hotkey.QUICKSAVE.button + Settings.HOTKEY_QUICKlOAD -> Hotkey.QUICKLOAD.button else -> -1 } 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 eeff4ff1b..7aa8ce7b7 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 @@ -481,12 +481,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram popupMenu.setOnMenuItemClickListener { when (it.itemId) { R.id.menu_emulation_save_state -> { - showSaveStateSubmenu() + showStateSubmenu(true) true } R.id.menu_emulation_load_state -> { - showLoadStateSubmenu() + showStateSubmenu(false) true } @@ -497,7 +497,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram popupMenu.show() } - private fun showSaveStateSubmenu() { + private fun showStateSubmenu(isSaving: Boolean) { + val savestates = NativeLibrary.getSavestateInfo() val popupMenu = PopupMenu( @@ -507,19 +508,40 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram popupMenu.menu.apply { for (i in 0 until NativeLibrary.SAVESTATE_SLOT_COUNT) { - val slot = i + 1 - val text = getString(R.string.emulation_empty_state_slot, slot) - add(text).setEnabled(true).setOnMenuItemClickListener { - displaySavestateWarning() - NativeLibrary.saveState(slot) + val slot = i + var enableClick = isSaving + val text = if (slot == NativeLibrary.QUICKSAVE_SLOT) { + enableClick = false + getString(R.string.emulation_quicksave_slot) + } else { + getString(R.string.emulation_empty_state_slot, slot) + } + + add(text).setEnabled(enableClick).setOnMenuItemClickListener { + if(isSaving) { + NativeLibrary.saveState(slot) + } else { + NativeLibrary.loadState(slot) + binding.drawerLayout.close() + Toast.makeText(context, + getString(R.string.quickload_loading), + Toast.LENGTH_SHORT).show() + } true } } } savestates?.forEach { - val text = getString(R.string.emulation_occupied_state_slot, it.slot, it.time) - popupMenu.menu.getItem(it.slot - 1).setTitle(text) + var enableClick = true + val text = if(it.slot == NativeLibrary.QUICKSAVE_SLOT) { + // do not allow saving in quicksave slot + enableClick = !isSaving + getString(R.string.emulation_occupied_quicksave_slot, it.time) + } else{ + getString(R.string.emulation_occupied_state_slot, it.slot, it.time) + } + popupMenu.menu.getItem(it.slot).setTitle(text).setEnabled(enableClick) } popupMenu.show() diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index 4de0ba43b..a01418870 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -692,4 +692,13 @@ Delay game render thread Delay the game render thread when it submits data to the GPU. Helps with performance issues in the (very few) dynamic-fps games. + + Quicksave + Quicksave + Quickload + Quicksave - %1$tF %1$tR + Saving… + Loading… + No Quicksave available. + diff --git a/src/core/savestate.cpp b/src/core/savestate.cpp index ad8e57ffb..3390af273 100644 --- a/src/core/savestate.cpp +++ b/src/core/savestate.cpp @@ -90,7 +90,7 @@ static bool ValidateSaveState(const CSTHeader& header, SaveStateInfo& info, u64 std::vector ListSaveStates(u64 program_id, u64 movie_id) { std::vector result; result.reserve(SaveStateSlotCount); - for (u32 slot = 1; slot <= SaveStateSlotCount; ++slot) { + for (u32 slot = 0; slot <= SaveStateSlotCount; ++slot) { const auto path = GetSaveStatePath(program_id, movie_id, slot); if (!FileUtil::Exists(path)) { continue; diff --git a/src/core/savestate.h b/src/core/savestate.h index 2962cce86..5232e98c0 100644 --- a/src/core/savestate.h +++ b/src/core/savestate.h @@ -20,7 +20,7 @@ struct SaveStateInfo { std::string build_name; }; -constexpr u32 SaveStateSlotCount = 10; // Maximum count of savestate slots +constexpr u32 SaveStateSlotCount = 11; // Maximum count of savestate slots std::vector ListSaveStates(u64 program_id, u64 movie_id);