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
@ -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…
Reference in New Issue
Block a user