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:
James Forward 2023-12-23 03:52:12 +00:00 committed by GitHub
parent 178e602589
commit 60a280af24
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 259 additions and 50 deletions

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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
}
}
}

View File

@ -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);
}

View File

@ -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
}
}

View File

@ -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

View File

@ -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),

View File

@ -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"

View File

@ -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
}

View File

@ -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]))
}
}
}

View File

@ -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)

View File

@ -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

View File

@ -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()
}
}

View File

@ -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()

View File

@ -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>

View File

@ -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>

View File

@ -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>