summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGravatar t8952023-12-24 15:42:28 -0500
committerGravatar t8952023-12-30 15:11:36 -0500
commitd163b182081401dbdcc5fc92c1692ff3209f2353 (patch)
treee5ed8e3c24c03966f4544d5433a7f4c553c28b40 /src
parentfrontend_common: config: Refactor WriteSetting to stricter types (diff)
downloadyuzu-d163b182081401dbdcc5fc92c1692ff3209f2353.tar.gz
yuzu-d163b182081401dbdcc5fc92c1692ff3209f2353.tar.xz
yuzu-d163b182081401dbdcc5fc92c1692ff3209f2353.zip
android: Migrate in-game overlay settings to ini
Diffstat (limited to 'src')
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt6
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt8
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt4
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt39
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt89
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt984
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableButton.kt3
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt4
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/model/OverlayControl.kt188
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/model/OverlayControlData.kt19
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/model/OverlayControlDefault.kt13
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/model/OverlayLayout.kt10
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt142
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/EmulationMenuSettings.kt50
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt18
-rw-r--r--src/android/app/src/main/jni/android_common/android_common.cpp9
-rw-r--r--src/android/app/src/main/jni/android_common/android_common.h3
-rw-r--r--src/android/app/src/main/jni/android_config.cpp62
-rw-r--r--src/android/app/src/main/jni/android_config.h2
-rw-r--r--src/android/app/src/main/jni/android_settings.h25
-rw-r--r--src/android/app/src/main/jni/id_cache.cpp79
-rw-r--r--src/android/app/src/main/jni/id_cache.h12
-rw-r--r--src/android/app/src/main/jni/native_config.cpp70
-rw-r--r--src/android/app/src/main/res/values/arrays.xml10
-rw-r--r--src/android/app/src/main/res/values/integers.xml204
-rw-r--r--src/common/settings.cpp2
-rw-r--r--src/common/settings_common.h1
27 files changed, 1209 insertions, 847 deletions
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
index 9b08f008d..93c8ce922 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
@@ -49,6 +49,7 @@ import org.yuzu.yuzu_emu.utils.ForegroundService
49import org.yuzu.yuzu_emu.utils.InputHandler 49import org.yuzu.yuzu_emu.utils.InputHandler
50import org.yuzu.yuzu_emu.utils.Log 50import org.yuzu.yuzu_emu.utils.Log
51import org.yuzu.yuzu_emu.utils.MemoryUtil 51import org.yuzu.yuzu_emu.utils.MemoryUtil
52import org.yuzu.yuzu_emu.utils.NativeConfig
52import org.yuzu.yuzu_emu.utils.NfcReader 53import org.yuzu.yuzu_emu.utils.NfcReader
53import org.yuzu.yuzu_emu.utils.ThemeHelper 54import org.yuzu.yuzu_emu.utils.ThemeHelper
54import java.text.NumberFormat 55import java.text.NumberFormat
@@ -170,6 +171,11 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
170 stopMotionSensorListener() 171 stopMotionSensorListener()
171 } 172 }
172 173
174 override fun onStop() {
175 super.onStop()
176 NativeConfig.saveGlobalConfig()
177 }
178
173 override fun onUserLeaveHint() { 179 override fun onUserLeaveHint() {
174 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { 180 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
175 if (BooleanSetting.PICTURE_IN_PICTURE.getBoolean() && !isInPictureInPictureMode) { 181 if (BooleanSetting.PICTURE_IN_PICTURE.getBoolean() && !isInPictureInPictureMode) {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt
index 110d15f1c..86bd33672 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt
@@ -19,7 +19,13 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
19 RENDERER_DEBUG("debug"), 19 RENDERER_DEBUG("debug"),
20 PICTURE_IN_PICTURE("picture_in_picture"), 20 PICTURE_IN_PICTURE("picture_in_picture"),
21 USE_CUSTOM_RTC("custom_rtc_enabled"), 21 USE_CUSTOM_RTC("custom_rtc_enabled"),
22 BLACK_BACKGROUNDS("black_backgrounds"); 22 BLACK_BACKGROUNDS("black_backgrounds"),
23 JOYSTICK_REL_CENTER("joystick_rel_center"),
24 DPAD_SLIDE("dpad_slide"),
25 HAPTIC_FEEDBACK("haptic_feedback"),
26 SHOW_PERFORMANCE_OVERLAY("show_performance_overlay"),
27 SHOW_INPUT_OVERLAY("show_input_overlay"),
28 TOUCHSCREEN("touchscreen");
23 29
24 override fun getBoolean(needsGlobal: Boolean): Boolean = 30 override fun getBoolean(needsGlobal: Boolean): Boolean =
25 NativeConfig.getBoolean(key, needsGlobal) 31 NativeConfig.getBoolean(key, needsGlobal)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt
index b0193d83e..16fb87614 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt
@@ -21,7 +21,9 @@ enum class IntSetting(override val key: String) : AbstractIntSetting {
21 AUDIO_OUTPUT_ENGINE("output_engine"), 21 AUDIO_OUTPUT_ENGINE("output_engine"),
22 MAX_ANISOTROPY("max_anisotropy"), 22 MAX_ANISOTROPY("max_anisotropy"),
23 THEME("theme"), 23 THEME("theme"),
24 THEME_MODE("theme_mode"); 24 THEME_MODE("theme_mode"),
25 OVERLAY_SCALE("control_scale"),
26 OVERLAY_OPACITY("control_opacity");
25 27
26 override fun getInt(needsGlobal: Boolean): Int = NativeConfig.getInt(key, needsGlobal) 28 override fun getInt(needsGlobal: Boolean): Int = NativeConfig.getInt(key, needsGlobal)
27 29
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt
index 360bdaf3e..43caac989 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt
@@ -15,18 +15,10 @@ object Settings {
15 SECTION_DEBUG(R.string.preferences_debug); 15 SECTION_DEBUG(R.string.preferences_debug);
16 } 16 }
17 17
18 const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch"
18 const val PREF_MEMORY_WARNING_SHOWN = "MemoryWarningShown" 19 const val PREF_MEMORY_WARNING_SHOWN = "MemoryWarningShown"
19 20
20 const val PREF_OVERLAY_VERSION = "OverlayVersion" 21 // Deprecated input overlay preference keys
21 const val PREF_LANDSCAPE_OVERLAY_VERSION = "LandscapeOverlayVersion"
22 const val PREF_PORTRAIT_OVERLAY_VERSION = "PortraitOverlayVersion"
23 const val PREF_FOLDABLE_OVERLAY_VERSION = "FoldableOverlayVersion"
24 val overlayLayoutPrefs = listOf(
25 PREF_LANDSCAPE_OVERLAY_VERSION,
26 PREF_PORTRAIT_OVERLAY_VERSION,
27 PREF_FOLDABLE_OVERLAY_VERSION
28 )
29
30 const val PREF_CONTROL_SCALE = "controlScale" 22 const val PREF_CONTROL_SCALE = "controlScale"
31 const val PREF_CONTROL_OPACITY = "controlOpacity" 23 const val PREF_CONTROL_OPACITY = "controlOpacity"
32 const val PREF_TOUCH_ENABLED = "isTouchEnabled" 24 const val PREF_TOUCH_ENABLED = "isTouchEnabled"
@@ -47,24 +39,12 @@ object Settings {
47 const val PREF_BUTTON_STICK_R = "buttonToggle14" 39 const val PREF_BUTTON_STICK_R = "buttonToggle14"
48 const val PREF_BUTTON_HOME = "buttonToggle15" 40 const val PREF_BUTTON_HOME = "buttonToggle15"
49 const val PREF_BUTTON_SCREENSHOT = "buttonToggle16" 41 const val PREF_BUTTON_SCREENSHOT = "buttonToggle16"
50
51 const val PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER = "EmulationMenuSettings_JoystickRelCenter" 42 const val PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER = "EmulationMenuSettings_JoystickRelCenter"
52 const val PREF_MENU_SETTINGS_DPAD_SLIDE = "EmulationMenuSettings_DpadSlideEnable" 43 const val PREF_MENU_SETTINGS_DPAD_SLIDE = "EmulationMenuSettings_DpadSlideEnable"
53 const val PREF_MENU_SETTINGS_HAPTICS = "EmulationMenuSettings_Haptics" 44 const val PREF_MENU_SETTINGS_HAPTICS = "EmulationMenuSettings_Haptics"
54 const val PREF_MENU_SETTINGS_SHOW_FPS = "EmulationMenuSettings_ShowFps" 45 const val PREF_MENU_SETTINGS_SHOW_FPS = "EmulationMenuSettings_ShowFps"
55 const val PREF_MENU_SETTINGS_SHOW_OVERLAY = "EmulationMenuSettings_ShowOverlay" 46 const val PREF_MENU_SETTINGS_SHOW_OVERLAY = "EmulationMenuSettings_ShowOverlay"
56
57 // Deprecated theme preference keys
58 const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch"
59 const val PREF_THEME = "Theme"
60 const val PREF_THEME_MODE = "ThemeMode"
61 const val PREF_BLACK_BACKGROUNDS = "BlackBackgrounds"
62
63 val overlayPreferences = listOf( 47 val overlayPreferences = listOf(
64 PREF_OVERLAY_VERSION,
65 PREF_CONTROL_SCALE,
66 PREF_CONTROL_OPACITY,
67 PREF_TOUCH_ENABLED,
68 PREF_BUTTON_A, 48 PREF_BUTTON_A,
69 PREF_BUTTON_B, 49 PREF_BUTTON_B,
70 PREF_BUTTON_X, 50 PREF_BUTTON_X,
@@ -84,6 +64,21 @@ object Settings {
84 PREF_BUTTON_STICK_R 64 PREF_BUTTON_STICK_R
85 ) 65 )
86 66
67 // Deprecated layout preference keys
68 const val PREF_LANDSCAPE_SUFFIX = "_Landscape"
69 const val PREF_PORTRAIT_SUFFIX = "_Portrait"
70 const val PREF_FOLDABLE_SUFFIX = "_Foldable"
71 val overlayLayoutSuffixes = listOf(
72 PREF_LANDSCAPE_SUFFIX,
73 PREF_PORTRAIT_SUFFIX,
74 PREF_FOLDABLE_SUFFIX
75 )
76
77 // Deprecated theme preference keys
78 const val PREF_THEME = "Theme"
79 const val PREF_THEME_MODE = "ThemeMode"
80 const val PREF_BLACK_BACKGROUNDS = "BlackBackgrounds"
81
87 const val LayoutOption_Unspecified = 0 82 const val LayoutOption_Unspecified = 0
88 const val LayoutOption_MobilePortrait = 4 83 const val LayoutOption_MobilePortrait = 4
89 const val LayoutOption_MobileLandscape = 5 84 const val LayoutOption_MobileLandscape = 5
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
index d7b38f62d..6e5dd1dba 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
@@ -7,7 +7,6 @@ import android.annotation.SuppressLint
7import android.app.AlertDialog 7import android.app.AlertDialog
8import android.content.Context 8import android.content.Context
9import android.content.DialogInterface 9import android.content.DialogInterface
10import android.content.SharedPreferences
11import android.content.pm.ActivityInfo 10import android.content.pm.ActivityInfo
12import android.content.res.Configuration 11import android.content.res.Configuration
13import android.net.Uri 12import android.net.Uri
@@ -33,7 +32,6 @@ import androidx.lifecycle.lifecycleScope
33import androidx.lifecycle.repeatOnLifecycle 32import androidx.lifecycle.repeatOnLifecycle
34import androidx.navigation.findNavController 33import androidx.navigation.findNavController
35import androidx.navigation.fragment.navArgs 34import androidx.navigation.fragment.navArgs
36import androidx.preference.PreferenceManager
37import androidx.window.layout.FoldingFeature 35import androidx.window.layout.FoldingFeature
38import androidx.window.layout.WindowInfoTracker 36import androidx.window.layout.WindowInfoTracker
39import androidx.window.layout.WindowLayoutInfo 37import androidx.window.layout.WindowLayoutInfo
@@ -46,22 +44,22 @@ import kotlinx.coroutines.launch
46import org.yuzu.yuzu_emu.HomeNavigationDirections 44import org.yuzu.yuzu_emu.HomeNavigationDirections
47import org.yuzu.yuzu_emu.NativeLibrary 45import org.yuzu.yuzu_emu.NativeLibrary
48import org.yuzu.yuzu_emu.R 46import org.yuzu.yuzu_emu.R
49import org.yuzu.yuzu_emu.YuzuApplication
50import org.yuzu.yuzu_emu.activities.EmulationActivity 47import org.yuzu.yuzu_emu.activities.EmulationActivity
51import org.yuzu.yuzu_emu.databinding.DialogOverlayAdjustBinding 48import org.yuzu.yuzu_emu.databinding.DialogOverlayAdjustBinding
52import org.yuzu.yuzu_emu.databinding.FragmentEmulationBinding 49import org.yuzu.yuzu_emu.databinding.FragmentEmulationBinding
50import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
53import org.yuzu.yuzu_emu.features.settings.model.IntSetting 51import org.yuzu.yuzu_emu.features.settings.model.IntSetting
54import org.yuzu.yuzu_emu.features.settings.model.Settings 52import org.yuzu.yuzu_emu.features.settings.model.Settings
55import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile 53import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
56import org.yuzu.yuzu_emu.model.DriverViewModel 54import org.yuzu.yuzu_emu.model.DriverViewModel
57import org.yuzu.yuzu_emu.model.Game 55import org.yuzu.yuzu_emu.model.Game
58import org.yuzu.yuzu_emu.model.EmulationViewModel 56import org.yuzu.yuzu_emu.model.EmulationViewModel
59import org.yuzu.yuzu_emu.overlay.InputOverlay 57import org.yuzu.yuzu_emu.overlay.model.OverlayControl
58import org.yuzu.yuzu_emu.overlay.model.OverlayLayout
60import org.yuzu.yuzu_emu.utils.* 59import org.yuzu.yuzu_emu.utils.*
61import java.lang.NullPointerException 60import java.lang.NullPointerException
62 61
63class EmulationFragment : Fragment(), SurfaceHolder.Callback { 62class EmulationFragment : Fragment(), SurfaceHolder.Callback {
64 private lateinit var preferences: SharedPreferences
65 private lateinit var emulationState: EmulationState 63 private lateinit var emulationState: EmulationState
66 private var emulationActivity: EmulationActivity? = null 64 private var emulationActivity: EmulationActivity? = null
67 private var perfStatsUpdater: (() -> Unit)? = null 65 private var perfStatsUpdater: (() -> Unit)? = null
@@ -141,7 +139,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
141 139
142 // So this fragment doesn't restart on configuration changes; i.e. rotation. 140 // So this fragment doesn't restart on configuration changes; i.e. rotation.
143 retainInstance = true 141 retainInstance = true
144 preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
145 emulationState = EmulationState(game.path) 142 emulationState = EmulationState(game.path)
146 } 143 }
147 144
@@ -382,24 +379,25 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
382 } 379 }
383 380
384 updateScreenLayout() 381 updateScreenLayout()
382 val showInputOverlay = BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean()
385 if (emulationActivity?.isInPictureInPictureMode == true) { 383 if (emulationActivity?.isInPictureInPictureMode == true) {
386 if (binding.drawerLayout.isOpen) { 384 if (binding.drawerLayout.isOpen) {
387 binding.drawerLayout.close() 385 binding.drawerLayout.close()
388 } 386 }
389 if (EmulationMenuSettings.showOverlay) { 387 if (showInputOverlay) {
390 binding.surfaceInputOverlay.visibility = View.INVISIBLE 388 binding.surfaceInputOverlay.visibility = View.INVISIBLE
391 } 389 }
392 } else { 390 } else {
393 if (EmulationMenuSettings.showOverlay && emulationViewModel.emulationStarted.value) { 391 if (showInputOverlay && emulationViewModel.emulationStarted.value) {
394 binding.surfaceInputOverlay.visibility = View.VISIBLE 392 binding.surfaceInputOverlay.visibility = View.VISIBLE
395 } else { 393 } else {
396 binding.surfaceInputOverlay.visibility = View.INVISIBLE 394 binding.surfaceInputOverlay.visibility = View.INVISIBLE
397 } 395 }
398 if (!isInFoldableLayout) { 396 if (!isInFoldableLayout) {
399 if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) { 397 if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
400 binding.surfaceInputOverlay.layout = InputOverlay.PORTRAIT 398 binding.surfaceInputOverlay.layout = OverlayLayout.Portrait
401 } else { 399 } else {
402 binding.surfaceInputOverlay.layout = InputOverlay.LANDSCAPE 400 binding.surfaceInputOverlay.layout = OverlayLayout.Landscape
403 } 401 }
404 } 402 }
405 } 403 }
@@ -423,17 +421,15 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
423 } 421 }
424 422
425 private fun resetInputOverlay() { 423 private fun resetInputOverlay() {
426 preferences.edit() 424 IntSetting.OVERLAY_SCALE.reset()
427 .remove(Settings.PREF_CONTROL_SCALE) 425 IntSetting.OVERLAY_OPACITY.reset()
428 .remove(Settings.PREF_CONTROL_OPACITY)
429 .apply()
430 binding.surfaceInputOverlay.post { 426 binding.surfaceInputOverlay.post {
431 binding.surfaceInputOverlay.resetLayoutVisibilityAndPlacement() 427 binding.surfaceInputOverlay.resetLayoutVisibilityAndPlacement()
432 } 428 }
433 } 429 }
434 430
435 private fun updateShowFpsOverlay() { 431 private fun updateShowFpsOverlay() {
436 if (EmulationMenuSettings.showFps) { 432 if (BooleanSetting.SHOW_PERFORMANCE_OVERLAY.getBoolean()) {
437 val SYSTEM_FPS = 0 433 val SYSTEM_FPS = 0
438 val FPS = 1 434 val FPS = 1
439 val FRAMETIME = 2 435 val FRAMETIME = 2
@@ -496,7 +492,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
496 binding.inGameMenu.layoutParams.height = it.bounds.bottom 492 binding.inGameMenu.layoutParams.height = it.bounds.bottom
497 493
498 isInFoldableLayout = true 494 isInFoldableLayout = true
499 binding.surfaceInputOverlay.layout = InputOverlay.FOLDABLE 495 binding.surfaceInputOverlay.layout = OverlayLayout.Foldable
500 } 496 }
501 } 497 }
502 it.isSeparating 498 it.isSeparating
@@ -535,18 +531,21 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
535 popup.menuInflater.inflate(R.menu.menu_overlay_options, popup.menu) 531 popup.menuInflater.inflate(R.menu.menu_overlay_options, popup.menu)
536 532
537 popup.menu.apply { 533 popup.menu.apply {
538 findItem(R.id.menu_toggle_fps).isChecked = EmulationMenuSettings.showFps 534 findItem(R.id.menu_toggle_fps).isChecked =
539 findItem(R.id.menu_rel_stick_center).isChecked = EmulationMenuSettings.joystickRelCenter 535 BooleanSetting.SHOW_PERFORMANCE_OVERLAY.getBoolean()
540 findItem(R.id.menu_dpad_slide).isChecked = EmulationMenuSettings.dpadSlide 536 findItem(R.id.menu_rel_stick_center).isChecked =
541 findItem(R.id.menu_show_overlay).isChecked = EmulationMenuSettings.showOverlay 537 BooleanSetting.JOYSTICK_REL_CENTER.getBoolean()
542 findItem(R.id.menu_haptics).isChecked = EmulationMenuSettings.hapticFeedback 538 findItem(R.id.menu_dpad_slide).isChecked = BooleanSetting.DPAD_SLIDE.getBoolean()
539 findItem(R.id.menu_show_overlay).isChecked =
540 BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean()
541 findItem(R.id.menu_haptics).isChecked = BooleanSetting.HAPTIC_FEEDBACK.getBoolean()
543 } 542 }
544 543
545 popup.setOnMenuItemClickListener { 544 popup.setOnMenuItemClickListener {
546 when (it.itemId) { 545 when (it.itemId) {
547 R.id.menu_toggle_fps -> { 546 R.id.menu_toggle_fps -> {
548 it.isChecked = !it.isChecked 547 it.isChecked = !it.isChecked
549 EmulationMenuSettings.showFps = it.isChecked 548 BooleanSetting.SHOW_PERFORMANCE_OVERLAY.setBoolean(it.isChecked)
550 updateShowFpsOverlay() 549 updateShowFpsOverlay()
551 true 550 true
552 } 551 }
@@ -564,11 +563,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
564 } 563 }
565 564
566 R.id.menu_toggle_controls -> { 565 R.id.menu_toggle_controls -> {
567 val preferences = 566 val overlayControlData = NativeConfig.getOverlayControlData()
568 PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) 567 val optionsArray = BooleanArray(overlayControlData.size)
569 val optionsArray = BooleanArray(Settings.overlayPreferences.size) 568 overlayControlData.forEachIndexed { i, _ ->
570 Settings.overlayPreferences.forEachIndexed { i, _ -> 569 optionsArray[i] = overlayControlData.firstOrNull { data ->
571 optionsArray[i] = preferences.getBoolean("buttonToggle$i", i < 15) 570 OverlayControl.entries[i].id == data.id
571 }?.enabled == true
572 } 572 }
573 573
574 val dialog = MaterialAlertDialogBuilder(requireContext()) 574 val dialog = MaterialAlertDialogBuilder(requireContext())
@@ -577,11 +577,13 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
577 R.array.gamepadButtons, 577 R.array.gamepadButtons,
578 optionsArray 578 optionsArray
579 ) { _, indexSelected, isChecked -> 579 ) { _, indexSelected, isChecked ->
580 preferences.edit() 580 overlayControlData.firstOrNull { data ->
581 .putBoolean("buttonToggle$indexSelected", isChecked) 581 OverlayControl.entries[indexSelected].id == data.id
582 .apply() 582 }?.enabled = isChecked
583 } 583 }
584 .setPositiveButton(android.R.string.ok) { _, _ -> 584 .setPositiveButton(android.R.string.ok) { _, _ ->
585 NativeConfig.setOverlayControlData(overlayControlData)
586 NativeConfig.saveGlobalConfig()
585 binding.surfaceInputOverlay.refreshControls() 587 binding.surfaceInputOverlay.refreshControls()
586 } 588 }
587 .setNegativeButton(android.R.string.cancel, null) 589 .setNegativeButton(android.R.string.cancel, null)
@@ -592,12 +594,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
592 dialog.getButton(AlertDialog.BUTTON_NEUTRAL) 594 dialog.getButton(AlertDialog.BUTTON_NEUTRAL)
593 .setOnClickListener { 595 .setOnClickListener {
594 val isChecked = !optionsArray[0] 596 val isChecked = !optionsArray[0]
595 Settings.overlayPreferences.forEachIndexed { i, _ -> 597 overlayControlData.forEachIndexed { i, _ ->
596 optionsArray[i] = isChecked 598 optionsArray[i] = isChecked
597 dialog.listView.setItemChecked(i, isChecked) 599 dialog.listView.setItemChecked(i, isChecked)
598 preferences.edit() 600 overlayControlData[i].enabled = isChecked
599 .putBoolean("buttonToggle$i", isChecked)
600 .apply()
601 } 601 }
602 } 602 }
603 true 603 true
@@ -605,26 +605,26 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
605 605
606 R.id.menu_show_overlay -> { 606 R.id.menu_show_overlay -> {
607 it.isChecked = !it.isChecked 607 it.isChecked = !it.isChecked
608 EmulationMenuSettings.showOverlay = it.isChecked 608 BooleanSetting.SHOW_INPUT_OVERLAY.setBoolean(it.isChecked)
609 binding.surfaceInputOverlay.refreshControls() 609 binding.surfaceInputOverlay.refreshControls()
610 true 610 true
611 } 611 }
612 612
613 R.id.menu_rel_stick_center -> { 613 R.id.menu_rel_stick_center -> {
614 it.isChecked = !it.isChecked 614 it.isChecked = !it.isChecked
615 EmulationMenuSettings.joystickRelCenter = it.isChecked 615 BooleanSetting.JOYSTICK_REL_CENTER.setBoolean(it.isChecked)
616 true 616 true
617 } 617 }
618 618
619 R.id.menu_dpad_slide -> { 619 R.id.menu_dpad_slide -> {
620 it.isChecked = !it.isChecked 620 it.isChecked = !it.isChecked
621 EmulationMenuSettings.dpadSlide = it.isChecked 621 BooleanSetting.DPAD_SLIDE.setBoolean(it.isChecked)
622 true 622 true
623 } 623 }
624 624
625 R.id.menu_haptics -> { 625 R.id.menu_haptics -> {
626 it.isChecked = !it.isChecked 626 it.isChecked = !it.isChecked
627 EmulationMenuSettings.hapticFeedback = it.isChecked 627 BooleanSetting.HAPTIC_FEEDBACK.setBoolean(it.isChecked)
628 true 628 true
629 } 629 }
630 630
@@ -667,6 +667,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
667 it.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED 667 it.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
668 } 668 }
669 } 669 }
670 NativeConfig.saveGlobalConfig()
670 } 671 }
671 672
672 @SuppressLint("SetTextI18n") 673 @SuppressLint("SetTextI18n")
@@ -675,7 +676,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
675 adjustBinding.apply { 676 adjustBinding.apply {
676 inputScaleSlider.apply { 677 inputScaleSlider.apply {
677 valueTo = 150F 678 valueTo = 150F
678 value = preferences.getInt(Settings.PREF_CONTROL_SCALE, 50).toFloat() 679 value = IntSetting.OVERLAY_SCALE.getInt().toFloat()
679 addOnChangeListener( 680 addOnChangeListener(
680 Slider.OnChangeListener { _, value, _ -> 681 Slider.OnChangeListener { _, value, _ ->
681 inputScaleValue.text = "${value.toInt()}%" 682 inputScaleValue.text = "${value.toInt()}%"
@@ -685,7 +686,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
685 } 686 }
686 inputOpacitySlider.apply { 687 inputOpacitySlider.apply {
687 valueTo = 100F 688 valueTo = 100F
688 value = preferences.getInt(Settings.PREF_CONTROL_OPACITY, 100).toFloat() 689 value = IntSetting.OVERLAY_OPACITY.getInt().toFloat()
689 addOnChangeListener( 690 addOnChangeListener(
690 Slider.OnChangeListener { _, value, _ -> 691 Slider.OnChangeListener { _, value, _ ->
691 inputOpacityValue.text = "${value.toInt()}%" 692 inputOpacityValue.text = "${value.toInt()}%"
@@ -709,16 +710,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
709 } 710 }
710 711
711 private fun setControlScale(scale: Int) { 712 private fun setControlScale(scale: Int) {
712 preferences.edit() 713 IntSetting.OVERLAY_SCALE.setInt(scale)
713 .putInt(Settings.PREF_CONTROL_SCALE, scale)
714 .apply()
715 binding.surfaceInputOverlay.refreshControls() 714 binding.surfaceInputOverlay.refreshControls()
716 } 715 }
717 716
718 private fun setControlOpacity(opacity: Int) { 717 private fun setControlOpacity(opacity: Int) {
719 preferences.edit() 718 IntSetting.OVERLAY_OPACITY.setInt(opacity)
720 .putInt(Settings.PREF_CONTROL_OPACITY, opacity)
721 .apply()
722 binding.surfaceInputOverlay.refreshControls() 719 binding.surfaceInputOverlay.refreshControls()
723 } 720 }
724 721
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt
index a13faf3c7..bb69b8bd5 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt
@@ -21,7 +21,6 @@ import android.view.View
21import android.view.View.OnTouchListener 21import android.view.View.OnTouchListener
22import android.view.WindowInsets 22import android.view.WindowInsets
23import androidx.core.content.ContextCompat 23import androidx.core.content.ContextCompat
24import androidx.preference.PreferenceManager
25import androidx.window.layout.WindowMetricsCalculator 24import androidx.window.layout.WindowMetricsCalculator
26import kotlin.math.max 25import kotlin.math.max
27import kotlin.math.min 26import kotlin.math.min
@@ -29,9 +28,13 @@ import org.yuzu.yuzu_emu.NativeLibrary
29import org.yuzu.yuzu_emu.NativeLibrary.ButtonType 28import org.yuzu.yuzu_emu.NativeLibrary.ButtonType
30import org.yuzu.yuzu_emu.NativeLibrary.StickType 29import org.yuzu.yuzu_emu.NativeLibrary.StickType
31import org.yuzu.yuzu_emu.R 30import org.yuzu.yuzu_emu.R
32import org.yuzu.yuzu_emu.YuzuApplication 31import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
32import org.yuzu.yuzu_emu.features.settings.model.IntSetting
33import org.yuzu.yuzu_emu.features.settings.model.Settings 33import org.yuzu.yuzu_emu.features.settings.model.Settings
34import org.yuzu.yuzu_emu.utils.EmulationMenuSettings 34import org.yuzu.yuzu_emu.overlay.model.OverlayControl
35import org.yuzu.yuzu_emu.overlay.model.OverlayControlData
36import org.yuzu.yuzu_emu.overlay.model.OverlayLayout
37import org.yuzu.yuzu_emu.utils.NativeConfig
35 38
36/** 39/**
37 * Draws the interactive input overlay on top of the 40 * Draws the interactive input overlay on top of the
@@ -51,23 +54,18 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
51 54
52 private lateinit var windowInsets: WindowInsets 55 private lateinit var windowInsets: WindowInsets
53 56
54 var layout = LANDSCAPE 57 var layout = OverlayLayout.Landscape
55 58
56 override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { 59 override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
57 super.onLayout(changed, left, top, right, bottom) 60 super.onLayout(changed, left, top, right, bottom)
58 61
59 windowInsets = rootWindowInsets 62 windowInsets = rootWindowInsets
60 63
61 val overlayVersion = preferences.getInt(Settings.PREF_OVERLAY_VERSION, 0) 64 val overlayControlData = NativeConfig.getOverlayControlData()
62 if (overlayVersion != OVERLAY_VERSION) { 65 if (overlayControlData.isEmpty()) {
63 resetAllLayouts() 66 populateDefaultConfig()
64 } else { 67 } else {
65 val layoutIndex = overlayLayouts.indexOf(layout) 68 checkForNewControls(overlayControlData)
66 val currentLayoutVersion =
67 preferences.getInt(Settings.overlayLayoutPrefs[layoutIndex], 0)
68 if (currentLayoutVersion != overlayLayoutVersions[layoutIndex]) {
69 resetCurrentLayout()
70 }
71 } 69 }
72 70
73 // Load the controls. 71 // Load the controls.
@@ -123,7 +121,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
123 } 121 }
124 122
125 for (dpad in overlayDpads) { 123 for (dpad in overlayDpads) {
126 if (!dpad.updateStatus(event, EmulationMenuSettings.dpadSlide)) { 124 if (!dpad.updateStatus(event, BooleanSetting.DPAD_SLIDE.getBoolean())) {
127 continue 125 continue
128 } 126 }
129 NativeLibrary.onGamePadButtonEvent( 127 NativeLibrary.onGamePadButtonEvent(
@@ -174,7 +172,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
174 invalidate() 172 invalidate()
175 } 173 }
176 174
177 if (!preferences.getBoolean(Settings.PREF_TOUCH_ENABLED, true)) { 175 if (!BooleanSetting.TOUCHSCREEN.getBoolean()) {
178 return true 176 return true
179 } 177 }
180 178
@@ -211,7 +209,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
211 } 209 }
212 210
213 private fun playHaptics(event: MotionEvent) { 211 private fun playHaptics(event: MotionEvent) {
214 if (EmulationMenuSettings.hapticFeedback) { 212 if (BooleanSetting.HAPTIC_FEEDBACK.getBoolean()) {
215 when (event.actionMasked) { 213 when (event.actionMasked) {
216 MotionEvent.ACTION_DOWN, 214 MotionEvent.ACTION_DOWN,
217 MotionEvent.ACTION_POINTER_DOWN -> 215 MotionEvent.ACTION_POINTER_DOWN ->
@@ -255,10 +253,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
255 MotionEvent.ACTION_POINTER_DOWN -> 253 MotionEvent.ACTION_POINTER_DOWN ->
256 // If no button is being moved now, remember the currently touched button to move. 254 // If no button is being moved now, remember the currently touched button to move.
257 if (buttonBeingConfigured == null && 255 if (buttonBeingConfigured == null &&
258 button.bounds.contains( 256 button.bounds.contains(fingerPositionX, fingerPositionY)
259 fingerPositionX,
260 fingerPositionY
261 )
262 ) { 257 ) {
263 buttonBeingConfigured = button 258 buttonBeingConfigured = button
264 buttonBeingConfigured!!.onConfigureTouch(event) 259 buttonBeingConfigured!!.onConfigureTouch(event)
@@ -274,7 +269,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
274 MotionEvent.ACTION_POINTER_UP -> if (buttonBeingConfigured === button) { 269 MotionEvent.ACTION_POINTER_UP -> if (buttonBeingConfigured === button) {
275 // Persist button position by saving new place. 270 // Persist button position by saving new place.
276 saveControlPosition( 271 saveControlPosition(
277 buttonBeingConfigured!!.prefId, 272 buttonBeingConfigured!!.overlayControlData.id,
278 buttonBeingConfigured!!.bounds.centerX(), 273 buttonBeingConfigured!!.bounds.centerX(),
279 buttonBeingConfigured!!.bounds.centerY(), 274 buttonBeingConfigured!!.bounds.centerY(),
280 layout 275 layout
@@ -321,10 +316,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
321 when (event.action) { 316 when (event.action) {
322 MotionEvent.ACTION_DOWN, 317 MotionEvent.ACTION_DOWN,
323 MotionEvent.ACTION_POINTER_DOWN -> if (joystickBeingConfigured == null && 318 MotionEvent.ACTION_POINTER_DOWN -> if (joystickBeingConfigured == null &&
324 joystick.bounds.contains( 319 joystick.bounds.contains(fingerPositionX, fingerPositionY)
325 fingerPositionX,
326 fingerPositionY
327 )
328 ) { 320 ) {
329 joystickBeingConfigured = joystick 321 joystickBeingConfigured = joystick
330 joystickBeingConfigured!!.onConfigureTouch(event) 322 joystickBeingConfigured!!.onConfigureTouch(event)
@@ -351,231 +343,257 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
351 return true 343 return true
352 } 344 }
353 345
354 private fun addOverlayControls(layout: String) { 346 private fun addOverlayControls(layout: OverlayLayout) {
355 val windowSize = getSafeScreenSize(context, Pair(measuredWidth, measuredHeight)) 347 val windowSize = getSafeScreenSize(context, Pair(measuredWidth, measuredHeight))
356 if (preferences.getBoolean(Settings.PREF_BUTTON_A, true)) { 348 val overlayControlData = NativeConfig.getOverlayControlData()
357 overlayButtons.add( 349 for (data in overlayControlData) {
358 initializeOverlayButton( 350 if (!data.enabled) {
359 context, 351 continue
360 windowSize, 352 }
361 R.drawable.facebutton_a, 353
362 R.drawable.facebutton_a_depressed, 354 val position = data.positionFromLayout(layout)
363 ButtonType.BUTTON_A, 355 when (data.id) {
364 Settings.PREF_BUTTON_A, 356 OverlayControl.BUTTON_A.id -> {
365 layout 357 overlayButtons.add(
366 ) 358 initializeOverlayButton(
367 ) 359 context,
368 } 360 windowSize,
369 if (preferences.getBoolean(Settings.PREF_BUTTON_B, true)) { 361 R.drawable.facebutton_a,
370 overlayButtons.add( 362 R.drawable.facebutton_a_depressed,
371 initializeOverlayButton( 363 ButtonType.BUTTON_A,
372 context, 364 data,
373 windowSize, 365 position
374 R.drawable.facebutton_b, 366 )
375 R.drawable.facebutton_b_depressed, 367 )
376 ButtonType.BUTTON_B, 368 }
377 Settings.PREF_BUTTON_B, 369
378 layout 370 OverlayControl.BUTTON_B.id -> {
379 ) 371 overlayButtons.add(
380 ) 372 initializeOverlayButton(
381 } 373 context,
382 if (preferences.getBoolean(Settings.PREF_BUTTON_X, true)) { 374 windowSize,
383 overlayButtons.add( 375 R.drawable.facebutton_b,
384 initializeOverlayButton( 376 R.drawable.facebutton_b_depressed,
385 context, 377 ButtonType.BUTTON_B,
386 windowSize, 378 data,
387 R.drawable.facebutton_x, 379 position
388 R.drawable.facebutton_x_depressed, 380 )
389 ButtonType.BUTTON_X, 381 )
390 Settings.PREF_BUTTON_X, 382 }
391 layout 383
392 ) 384 OverlayControl.BUTTON_X.id -> {
393 ) 385 overlayButtons.add(
394 } 386 initializeOverlayButton(
395 if (preferences.getBoolean(Settings.PREF_BUTTON_Y, true)) { 387 context,
396 overlayButtons.add( 388 windowSize,
397 initializeOverlayButton( 389 R.drawable.facebutton_x,
398 context, 390 R.drawable.facebutton_x_depressed,
399 windowSize, 391 ButtonType.BUTTON_X,
400 R.drawable.facebutton_y, 392 data,
401 R.drawable.facebutton_y_depressed, 393 position
402 ButtonType.BUTTON_Y, 394 )
403 Settings.PREF_BUTTON_Y, 395 )
404 layout 396 }
405 ) 397
406 ) 398 OverlayControl.BUTTON_Y.id -> {
407 } 399 overlayButtons.add(
408 if (preferences.getBoolean(Settings.PREF_BUTTON_L, true)) { 400 initializeOverlayButton(
409 overlayButtons.add( 401 context,
410 initializeOverlayButton( 402 windowSize,
411 context, 403 R.drawable.facebutton_y,
412 windowSize, 404 R.drawable.facebutton_y_depressed,
413 R.drawable.l_shoulder, 405 ButtonType.BUTTON_Y,
414 R.drawable.l_shoulder_depressed, 406 data,
415 ButtonType.TRIGGER_L, 407 position
416 Settings.PREF_BUTTON_L, 408 )
417 layout 409 )
418 ) 410 }
419 ) 411
420 } 412 OverlayControl.BUTTON_PLUS.id -> {
421 if (preferences.getBoolean(Settings.PREF_BUTTON_R, true)) { 413 overlayButtons.add(
422 overlayButtons.add( 414 initializeOverlayButton(
423 initializeOverlayButton( 415 context,
424 context, 416 windowSize,
425 windowSize, 417 R.drawable.facebutton_plus,
426 R.drawable.r_shoulder, 418 R.drawable.facebutton_plus_depressed,
427 R.drawable.r_shoulder_depressed, 419 ButtonType.BUTTON_PLUS,
428 ButtonType.TRIGGER_R, 420 data,
429 Settings.PREF_BUTTON_R, 421 position
430 layout 422 )
431 ) 423 )
432 ) 424 }
433 } 425
434 if (preferences.getBoolean(Settings.PREF_BUTTON_ZL, true)) { 426 OverlayControl.BUTTON_MINUS.id -> {
435 overlayButtons.add( 427 overlayButtons.add(
436 initializeOverlayButton( 428 initializeOverlayButton(
437 context, 429 context,
438 windowSize, 430 windowSize,
439 R.drawable.zl_trigger, 431 R.drawable.facebutton_minus,
440 R.drawable.zl_trigger_depressed, 432 R.drawable.facebutton_minus_depressed,
441 ButtonType.TRIGGER_ZL, 433 ButtonType.BUTTON_MINUS,
442 Settings.PREF_BUTTON_ZL, 434 data,
443 layout 435 position
444 ) 436 )
445 ) 437 )
446 } 438 }
447 if (preferences.getBoolean(Settings.PREF_BUTTON_ZR, true)) { 439
448 overlayButtons.add( 440 OverlayControl.BUTTON_HOME.id -> {
449 initializeOverlayButton( 441 overlayButtons.add(
450 context, 442 initializeOverlayButton(
451 windowSize, 443 context,
452 R.drawable.zr_trigger, 444 windowSize,
453 R.drawable.zr_trigger_depressed, 445 R.drawable.facebutton_home,
454 ButtonType.TRIGGER_ZR, 446 R.drawable.facebutton_home_depressed,
455 Settings.PREF_BUTTON_ZR, 447 ButtonType.BUTTON_HOME,
456 layout 448 data,
457 ) 449 position
458 ) 450 )
459 } 451 )
460 if (preferences.getBoolean(Settings.PREF_BUTTON_PLUS, true)) { 452 }
461 overlayButtons.add( 453
462 initializeOverlayButton( 454 OverlayControl.BUTTON_CAPTURE.id -> {
463 context, 455 overlayButtons.add(
464 windowSize, 456 initializeOverlayButton(
465 R.drawable.facebutton_plus, 457 context,
466 R.drawable.facebutton_plus_depressed, 458 windowSize,
467 ButtonType.BUTTON_PLUS, 459 R.drawable.facebutton_screenshot,
468 Settings.PREF_BUTTON_PLUS, 460 R.drawable.facebutton_screenshot_depressed,
469 layout 461 ButtonType.BUTTON_CAPTURE,
470 ) 462 data,
471 ) 463 position
472 } 464 )
473 if (preferences.getBoolean(Settings.PREF_BUTTON_MINUS, true)) { 465 )
474 overlayButtons.add( 466 }
475 initializeOverlayButton( 467
476 context, 468 OverlayControl.BUTTON_L.id -> {
477 windowSize, 469 overlayButtons.add(
478 R.drawable.facebutton_minus, 470 initializeOverlayButton(
479 R.drawable.facebutton_minus_depressed, 471 context,
480 ButtonType.BUTTON_MINUS, 472 windowSize,
481 Settings.PREF_BUTTON_MINUS, 473 R.drawable.l_shoulder,
482 layout 474 R.drawable.l_shoulder_depressed,
483 ) 475 ButtonType.TRIGGER_L,
484 ) 476 data,
485 } 477 position
486 if (preferences.getBoolean(Settings.PREF_BUTTON_DPAD, true)) { 478 )
487 overlayDpads.add( 479 )
488 initializeOverlayDpad( 480 }
489 context, 481
490 windowSize, 482 OverlayControl.BUTTON_R.id -> {
491 R.drawable.dpad_standard, 483 overlayButtons.add(
492 R.drawable.dpad_standard_cardinal_depressed, 484 initializeOverlayButton(
493 R.drawable.dpad_standard_diagonal_depressed, 485 context,
494 layout 486 windowSize,
495 ) 487 R.drawable.r_shoulder,
496 ) 488 R.drawable.r_shoulder_depressed,
497 } 489 ButtonType.TRIGGER_R,
498 if (preferences.getBoolean(Settings.PREF_STICK_L, true)) { 490 data,
499 overlayJoysticks.add( 491 position
500 initializeOverlayJoystick( 492 )
501 context, 493 )
502 windowSize, 494 }
503 R.drawable.joystick_range, 495
504 R.drawable.joystick, 496 OverlayControl.BUTTON_ZL.id -> {
505 R.drawable.joystick_depressed, 497 overlayButtons.add(
506 StickType.STICK_L, 498 initializeOverlayButton(
507 ButtonType.STICK_L, 499 context,
508 Settings.PREF_STICK_L, 500 windowSize,
509 layout 501 R.drawable.zl_trigger,
510 ) 502 R.drawable.zl_trigger_depressed,
511 ) 503 ButtonType.TRIGGER_ZL,
512 } 504 data,
513 if (preferences.getBoolean(Settings.PREF_STICK_R, true)) { 505 position
514 overlayJoysticks.add( 506 )
515 initializeOverlayJoystick( 507 )
516 context, 508 }
517 windowSize, 509
518 R.drawable.joystick_range, 510 OverlayControl.BUTTON_ZR.id -> {
519 R.drawable.joystick, 511 overlayButtons.add(
520 R.drawable.joystick_depressed, 512 initializeOverlayButton(
521 StickType.STICK_R, 513 context,
522 ButtonType.STICK_R, 514 windowSize,
523 Settings.PREF_STICK_R, 515 R.drawable.zr_trigger,
524 layout 516 R.drawable.zr_trigger_depressed,
525 ) 517 ButtonType.TRIGGER_ZR,
526 ) 518 data,
527 } 519 position
528 if (preferences.getBoolean(Settings.PREF_BUTTON_HOME, false)) { 520 )
529 overlayButtons.add( 521 )
530 initializeOverlayButton( 522 }
531 context, 523
532 windowSize, 524 OverlayControl.BUTTON_STICK_L.id -> {
533 R.drawable.facebutton_home, 525 overlayButtons.add(
534 R.drawable.facebutton_home_depressed, 526 initializeOverlayButton(
535 ButtonType.BUTTON_HOME, 527 context,
536 Settings.PREF_BUTTON_HOME, 528 windowSize,
537 layout 529 R.drawable.button_l3,
538 ) 530 R.drawable.button_l3_depressed,
539 ) 531 ButtonType.STICK_L,
540 } 532 data,
541 if (preferences.getBoolean(Settings.PREF_BUTTON_SCREENSHOT, false)) { 533 position
542 overlayButtons.add( 534 )
543 initializeOverlayButton( 535 )
544 context, 536 }
545 windowSize, 537
546 R.drawable.facebutton_screenshot, 538 OverlayControl.BUTTON_STICK_R.id -> {
547 R.drawable.facebutton_screenshot_depressed, 539 overlayButtons.add(
548 ButtonType.BUTTON_CAPTURE, 540 initializeOverlayButton(
549 Settings.PREF_BUTTON_SCREENSHOT, 541 context,
550 layout 542 windowSize,
551 ) 543 R.drawable.button_r3,
552 ) 544 R.drawable.button_r3_depressed,
553 } 545 ButtonType.STICK_R,
554 if (preferences.getBoolean(Settings.PREF_BUTTON_STICK_L, true)) { 546 data,
555 overlayButtons.add( 547 position
556 initializeOverlayButton( 548 )
557 context, 549 )
558 windowSize, 550 }
559 R.drawable.button_l3, 551
560 R.drawable.button_l3_depressed, 552 OverlayControl.STICK_L.id -> {
561 ButtonType.STICK_L, 553 overlayJoysticks.add(
562 Settings.PREF_BUTTON_STICK_L, 554 initializeOverlayJoystick(
563 layout 555 context,
564 ) 556 windowSize,
565 ) 557 R.drawable.joystick_range,
566 } 558 R.drawable.joystick,
567 if (preferences.getBoolean(Settings.PREF_BUTTON_STICK_R, true)) { 559 R.drawable.joystick_depressed,
568 overlayButtons.add( 560 StickType.STICK_L,
569 initializeOverlayButton( 561 ButtonType.STICK_L,
570 context, 562 data,
571 windowSize, 563 position
572 R.drawable.button_r3, 564 )
573 R.drawable.button_r3_depressed, 565 )
574 ButtonType.STICK_R, 566 }
575 Settings.PREF_BUTTON_STICK_R, 567
576 layout 568 OverlayControl.STICK_R.id -> {
577 ) 569 overlayJoysticks.add(
578 ) 570 initializeOverlayJoystick(
571 context,
572 windowSize,
573 R.drawable.joystick_range,
574 R.drawable.joystick,
575 R.drawable.joystick_depressed,
576 StickType.STICK_R,
577 ButtonType.STICK_R,
578 data,
579 position
580 )
581 )
582 }
583
584 OverlayControl.COMBINED_DPAD.id -> {
585 overlayDpads.add(
586 initializeOverlayDpad(
587 context,
588 windowSize,
589 R.drawable.dpad_standard,
590 R.drawable.dpad_standard_cardinal_depressed,
591 R.drawable.dpad_standard_diagonal_depressed,
592 position
593 )
594 )
595 }
596 }
579 } 597 }
580 } 598 }
581 599
@@ -586,313 +604,87 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
586 overlayJoysticks.clear() 604 overlayJoysticks.clear()
587 605
588 // Add all the enabled overlay items back to the HashSet. 606 // Add all the enabled overlay items back to the HashSet.
589 if (EmulationMenuSettings.showOverlay) { 607 if (BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean()) {
590 addOverlayControls(layout) 608 addOverlayControls(layout)
591 } 609 }
592 invalidate() 610 invalidate()
593 } 611 }
594 612
595 private fun saveControlPosition(prefId: String, x: Int, y: Int, layout: String) { 613 private fun saveControlPosition(id: String, x: Int, y: Int, layout: OverlayLayout) {
596 val windowSize = getSafeScreenSize(context, Pair(measuredWidth, measuredHeight)) 614 val windowSize = getSafeScreenSize(context, Pair(measuredWidth, measuredHeight))
597 val min = windowSize.first 615 val min = windowSize.first
598 val max = windowSize.second 616 val max = windowSize.second
599 PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext).edit() 617 val overlayControlData = NativeConfig.getOverlayControlData()
600 .putFloat("$prefId-X$layout", (x - min.x).toFloat() / max.x) 618 val data = overlayControlData.firstOrNull { it.id == id }
601 .putFloat("$prefId-Y$layout", (y - min.y).toFloat() / max.y) 619 val newPosition = Pair((x - min.x).toDouble() / max.x, (y - min.y).toDouble() / max.y)
602 .apply() 620 when (layout) {
621 OverlayLayout.Landscape -> data?.landscapePosition = newPosition
622 OverlayLayout.Portrait -> data?.portraitPosition = newPosition
623 OverlayLayout.Foldable -> data?.foldablePosition = newPosition
624 }
625 NativeConfig.setOverlayControlData(overlayControlData)
603 } 626 }
604 627
605 fun setIsInEditMode(editMode: Boolean) { 628 fun setIsInEditMode(editMode: Boolean) {
606 inEditMode = editMode 629 inEditMode = editMode
607 } 630 }
608 631
609 private fun resetCurrentLayout() { 632 /**
610 defaultOverlayByLayout(layout) 633 * Applies and saves all default values for the overlay
611 val layoutIndex = overlayLayouts.indexOf(layout) 634 */
612 preferences.edit() 635 private fun populateDefaultConfig() {
613 .putInt(Settings.overlayLayoutPrefs[layoutIndex], overlayLayoutVersions[layoutIndex]) 636 val newConfig = OverlayControl.entries.map { it.toOverlayControlData() }
614 .apply() 637 NativeConfig.setOverlayControlData(newConfig.toTypedArray())
638 NativeConfig.saveGlobalConfig()
615 } 639 }
616 640
617 private fun resetAllLayouts() { 641 /**
618 val editor = preferences.edit() 642 * Checks if any new controls were added to OverlayControl that do not exist within deserialized
619 overlayLayouts.forEachIndexed { i, layout -> 643 * config and adds / saves them if necessary
620 defaultOverlayByLayout(layout) 644 *
621 editor.putInt(Settings.overlayLayoutPrefs[i], overlayLayoutVersions[i]) 645 * @param overlayControlData Overlay control data from [NativeConfig.getOverlayControlData]
646 */
647 private fun checkForNewControls(overlayControlData: Array<OverlayControlData>) {
648 val missingControls = mutableListOf<OverlayControlData>()
649 OverlayControl.entries.forEach { defaultControl ->
650 val controlData = overlayControlData.firstOrNull { it.id == defaultControl.id }
651 if (controlData == null) {
652 missingControls.add(defaultControl.toOverlayControlData())
653 }
654 }
655
656 if (missingControls.isNotEmpty()) {
657 NativeConfig.setOverlayControlData(
658 arrayOf(*overlayControlData, *(missingControls.toTypedArray()))
659 )
660 NativeConfig.saveGlobalConfig()
622 } 661 }
623 editor.putInt(Settings.PREF_OVERLAY_VERSION, OVERLAY_VERSION)
624 editor.apply()
625 } 662 }
626 663
627 fun resetLayoutVisibilityAndPlacement() { 664 fun resetLayoutVisibilityAndPlacement() {
628 defaultOverlayByLayout(layout) 665 defaultOverlayPositionByLayout(layout)
629 val editor = preferences.edit() 666
630 Settings.overlayPreferences.forEachIndexed { _, pref -> 667 val overlayControlData = NativeConfig.getOverlayControlData()
631 editor.remove(pref) 668 overlayControlData.forEach {
669 it.enabled = OverlayControl.from(it.id)?.defaultVisibility == false
632 } 670 }
633 editor.apply() 671 NativeConfig.setOverlayControlData(overlayControlData)
672
634 refreshControls() 673 refreshControls()
635 } 674 }
636 675
637 private val landscapeResources = arrayOf( 676 private fun defaultOverlayPositionByLayout(layout: OverlayLayout) {
638 R.integer.SWITCH_BUTTON_A_X, 677 val overlayControlData = NativeConfig.getOverlayControlData()
639 R.integer.SWITCH_BUTTON_A_Y, 678 for (data in overlayControlData) {
640 R.integer.SWITCH_BUTTON_B_X, 679 val defaultControlData = OverlayControl.from(data.id) ?: continue
641 R.integer.SWITCH_BUTTON_B_Y, 680 val position = defaultControlData.getDefaultPositionForLayout(layout)
642 R.integer.SWITCH_BUTTON_X_X, 681 when (layout) {
643 R.integer.SWITCH_BUTTON_X_Y, 682 OverlayLayout.Landscape -> data.landscapePosition = position
644 R.integer.SWITCH_BUTTON_Y_X, 683 OverlayLayout.Portrait -> data.portraitPosition = position
645 R.integer.SWITCH_BUTTON_Y_Y, 684 OverlayLayout.Foldable -> data.foldablePosition = position
646 R.integer.SWITCH_TRIGGER_ZL_X, 685 }
647 R.integer.SWITCH_TRIGGER_ZL_Y,
648 R.integer.SWITCH_TRIGGER_ZR_X,
649 R.integer.SWITCH_TRIGGER_ZR_Y,
650 R.integer.SWITCH_BUTTON_DPAD_X,
651 R.integer.SWITCH_BUTTON_DPAD_Y,
652 R.integer.SWITCH_TRIGGER_L_X,
653 R.integer.SWITCH_TRIGGER_L_Y,
654 R.integer.SWITCH_TRIGGER_R_X,
655 R.integer.SWITCH_TRIGGER_R_Y,
656 R.integer.SWITCH_BUTTON_PLUS_X,
657 R.integer.SWITCH_BUTTON_PLUS_Y,
658 R.integer.SWITCH_BUTTON_MINUS_X,
659 R.integer.SWITCH_BUTTON_MINUS_Y,
660 R.integer.SWITCH_BUTTON_HOME_X,
661 R.integer.SWITCH_BUTTON_HOME_Y,
662 R.integer.SWITCH_BUTTON_CAPTURE_X,
663 R.integer.SWITCH_BUTTON_CAPTURE_Y,
664 R.integer.SWITCH_STICK_R_X,
665 R.integer.SWITCH_STICK_R_Y,
666 R.integer.SWITCH_STICK_L_X,
667 R.integer.SWITCH_STICK_L_Y,
668 R.integer.SWITCH_BUTTON_STICK_L_X,
669 R.integer.SWITCH_BUTTON_STICK_L_Y,
670 R.integer.SWITCH_BUTTON_STICK_R_X,
671 R.integer.SWITCH_BUTTON_STICK_R_Y
672 )
673
674 private val portraitResources = arrayOf(
675 R.integer.SWITCH_BUTTON_A_X_PORTRAIT,
676 R.integer.SWITCH_BUTTON_A_Y_PORTRAIT,
677 R.integer.SWITCH_BUTTON_B_X_PORTRAIT,
678 R.integer.SWITCH_BUTTON_B_Y_PORTRAIT,
679 R.integer.SWITCH_BUTTON_X_X_PORTRAIT,
680 R.integer.SWITCH_BUTTON_X_Y_PORTRAIT,
681 R.integer.SWITCH_BUTTON_Y_X_PORTRAIT,
682 R.integer.SWITCH_BUTTON_Y_Y_PORTRAIT,
683 R.integer.SWITCH_TRIGGER_ZL_X_PORTRAIT,
684 R.integer.SWITCH_TRIGGER_ZL_Y_PORTRAIT,
685 R.integer.SWITCH_TRIGGER_ZR_X_PORTRAIT,
686 R.integer.SWITCH_TRIGGER_ZR_Y_PORTRAIT,
687 R.integer.SWITCH_BUTTON_DPAD_X_PORTRAIT,
688 R.integer.SWITCH_BUTTON_DPAD_Y_PORTRAIT,
689 R.integer.SWITCH_TRIGGER_L_X_PORTRAIT,
690 R.integer.SWITCH_TRIGGER_L_Y_PORTRAIT,
691 R.integer.SWITCH_TRIGGER_R_X_PORTRAIT,
692 R.integer.SWITCH_TRIGGER_R_Y_PORTRAIT,
693 R.integer.SWITCH_BUTTON_PLUS_X_PORTRAIT,
694 R.integer.SWITCH_BUTTON_PLUS_Y_PORTRAIT,
695 R.integer.SWITCH_BUTTON_MINUS_X_PORTRAIT,
696 R.integer.SWITCH_BUTTON_MINUS_Y_PORTRAIT,
697 R.integer.SWITCH_BUTTON_HOME_X_PORTRAIT,
698 R.integer.SWITCH_BUTTON_HOME_Y_PORTRAIT,
699 R.integer.SWITCH_BUTTON_CAPTURE_X_PORTRAIT,
700 R.integer.SWITCH_BUTTON_CAPTURE_Y_PORTRAIT,
701 R.integer.SWITCH_STICK_R_X_PORTRAIT,
702 R.integer.SWITCH_STICK_R_Y_PORTRAIT,
703 R.integer.SWITCH_STICK_L_X_PORTRAIT,
704 R.integer.SWITCH_STICK_L_Y_PORTRAIT,
705 R.integer.SWITCH_BUTTON_STICK_L_X_PORTRAIT,
706 R.integer.SWITCH_BUTTON_STICK_L_Y_PORTRAIT,
707 R.integer.SWITCH_BUTTON_STICK_R_X_PORTRAIT,
708 R.integer.SWITCH_BUTTON_STICK_R_Y_PORTRAIT
709 )
710
711 private val foldableResources = arrayOf(
712 R.integer.SWITCH_BUTTON_A_X_FOLDABLE,
713 R.integer.SWITCH_BUTTON_A_Y_FOLDABLE,
714 R.integer.SWITCH_BUTTON_B_X_FOLDABLE,
715 R.integer.SWITCH_BUTTON_B_Y_FOLDABLE,
716 R.integer.SWITCH_BUTTON_X_X_FOLDABLE,
717 R.integer.SWITCH_BUTTON_X_Y_FOLDABLE,
718 R.integer.SWITCH_BUTTON_Y_X_FOLDABLE,
719 R.integer.SWITCH_BUTTON_Y_Y_FOLDABLE,
720 R.integer.SWITCH_TRIGGER_ZL_X_FOLDABLE,
721 R.integer.SWITCH_TRIGGER_ZL_Y_FOLDABLE,
722 R.integer.SWITCH_TRIGGER_ZR_X_FOLDABLE,
723 R.integer.SWITCH_TRIGGER_ZR_Y_FOLDABLE,
724 R.integer.SWITCH_BUTTON_DPAD_X_FOLDABLE,
725 R.integer.SWITCH_BUTTON_DPAD_Y_FOLDABLE,
726 R.integer.SWITCH_TRIGGER_L_X_FOLDABLE,
727 R.integer.SWITCH_TRIGGER_L_Y_FOLDABLE,
728 R.integer.SWITCH_TRIGGER_R_X_FOLDABLE,
729 R.integer.SWITCH_TRIGGER_R_Y_FOLDABLE,
730 R.integer.SWITCH_BUTTON_PLUS_X_FOLDABLE,
731 R.integer.SWITCH_BUTTON_PLUS_Y_FOLDABLE,
732 R.integer.SWITCH_BUTTON_MINUS_X_FOLDABLE,
733 R.integer.SWITCH_BUTTON_MINUS_Y_FOLDABLE,
734 R.integer.SWITCH_BUTTON_HOME_X_FOLDABLE,
735 R.integer.SWITCH_BUTTON_HOME_Y_FOLDABLE,
736 R.integer.SWITCH_BUTTON_CAPTURE_X_FOLDABLE,
737 R.integer.SWITCH_BUTTON_CAPTURE_Y_FOLDABLE,
738 R.integer.SWITCH_STICK_R_X_FOLDABLE,
739 R.integer.SWITCH_STICK_R_Y_FOLDABLE,
740 R.integer.SWITCH_STICK_L_X_FOLDABLE,
741 R.integer.SWITCH_STICK_L_Y_FOLDABLE,
742 R.integer.SWITCH_BUTTON_STICK_L_X_FOLDABLE,
743 R.integer.SWITCH_BUTTON_STICK_L_Y_FOLDABLE,
744 R.integer.SWITCH_BUTTON_STICK_R_X_FOLDABLE,
745 R.integer.SWITCH_BUTTON_STICK_R_Y_FOLDABLE
746 )
747
748 private fun getResourceValue(layout: String, position: Int): Float {
749 return when (layout) {
750 PORTRAIT -> resources.getInteger(portraitResources[position]).toFloat() / 1000
751 FOLDABLE -> resources.getInteger(foldableResources[position]).toFloat() / 1000
752 else -> resources.getInteger(landscapeResources[position]).toFloat() / 1000
753 } 686 }
754 } 687 NativeConfig.setOverlayControlData(overlayControlData)
755
756 private fun defaultOverlayByLayout(layout: String) {
757 // Each value represents the position of the button in relation to the screen size without insets.
758 preferences.edit()
759 .putFloat(
760 "${Settings.PREF_BUTTON_A}-X$layout",
761 getResourceValue(layout, 0)
762 )
763 .putFloat(
764 "${Settings.PREF_BUTTON_A}-Y$layout",
765 getResourceValue(layout, 1)
766 )
767 .putFloat(
768 "${Settings.PREF_BUTTON_B}-X$layout",
769 getResourceValue(layout, 2)
770 )
771 .putFloat(
772 "${Settings.PREF_BUTTON_B}-Y$layout",
773 getResourceValue(layout, 3)
774 )
775 .putFloat(
776 "${Settings.PREF_BUTTON_X}-X$layout",
777 getResourceValue(layout, 4)
778 )
779 .putFloat(
780 "${Settings.PREF_BUTTON_X}-Y$layout",
781 getResourceValue(layout, 5)
782 )
783 .putFloat(
784 "${Settings.PREF_BUTTON_Y}-X$layout",
785 getResourceValue(layout, 6)
786 )
787 .putFloat(
788 "${Settings.PREF_BUTTON_Y}-Y$layout",
789 getResourceValue(layout, 7)
790 )
791 .putFloat(
792 "${Settings.PREF_BUTTON_ZL}-X$layout",
793 getResourceValue(layout, 8)
794 )
795 .putFloat(
796 "${Settings.PREF_BUTTON_ZL}-Y$layout",
797 getResourceValue(layout, 9)
798 )
799 .putFloat(
800 "${Settings.PREF_BUTTON_ZR}-X$layout",
801 getResourceValue(layout, 10)
802 )
803 .putFloat(
804 "${Settings.PREF_BUTTON_ZR}-Y$layout",
805 getResourceValue(layout, 11)
806 )
807 .putFloat(
808 "${Settings.PREF_BUTTON_DPAD}-X$layout",
809 getResourceValue(layout, 12)
810 )
811 .putFloat(
812 "${Settings.PREF_BUTTON_DPAD}-Y$layout",
813 getResourceValue(layout, 13)
814 )
815 .putFloat(
816 "${Settings.PREF_BUTTON_L}-X$layout",
817 getResourceValue(layout, 14)
818 )
819 .putFloat(
820 "${Settings.PREF_BUTTON_L}-Y$layout",
821 getResourceValue(layout, 15)
822 )
823 .putFloat(
824 "${Settings.PREF_BUTTON_R}-X$layout",
825 getResourceValue(layout, 16)
826 )
827 .putFloat(
828 "${Settings.PREF_BUTTON_R}-Y$layout",
829 getResourceValue(layout, 17)
830 )
831 .putFloat(
832 "${Settings.PREF_BUTTON_PLUS}-X$layout",
833 getResourceValue(layout, 18)
834 )
835 .putFloat(
836 "${Settings.PREF_BUTTON_PLUS}-Y$layout",
837 getResourceValue(layout, 19)
838 )
839 .putFloat(
840 "${Settings.PREF_BUTTON_MINUS}-X$layout",
841 getResourceValue(layout, 20)
842 )
843 .putFloat(
844 "${Settings.PREF_BUTTON_MINUS}-Y$layout",
845 getResourceValue(layout, 21)
846 )
847 .putFloat(
848 "${Settings.PREF_BUTTON_HOME}-X$layout",
849 getResourceValue(layout, 22)
850 )
851 .putFloat(
852 "${Settings.PREF_BUTTON_HOME}-Y$layout",
853 getResourceValue(layout, 23)
854 )
855 .putFloat(
856 "${Settings.PREF_BUTTON_SCREENSHOT}-X$layout",
857 getResourceValue(layout, 24)
858 )
859 .putFloat(
860 "${Settings.PREF_BUTTON_SCREENSHOT}-Y$layout",
861 getResourceValue(layout, 25)
862 )
863 .putFloat(
864 "${Settings.PREF_STICK_R}-X$layout",
865 getResourceValue(layout, 26)
866 )
867 .putFloat(
868 "${Settings.PREF_STICK_R}-Y$layout",
869 getResourceValue(layout, 27)
870 )
871 .putFloat(
872 "${Settings.PREF_STICK_L}-X$layout",
873 getResourceValue(layout, 28)
874 )
875 .putFloat(
876 "${Settings.PREF_STICK_L}-Y$layout",
877 getResourceValue(layout, 29)
878 )
879 .putFloat(
880 "${Settings.PREF_BUTTON_STICK_L}-X$layout",
881 getResourceValue(layout, 30)
882 )
883 .putFloat(
884 "${Settings.PREF_BUTTON_STICK_L}-Y$layout",
885 getResourceValue(layout, 31)
886 )
887 .putFloat(
888 "${Settings.PREF_BUTTON_STICK_R}-X$layout",
889 getResourceValue(layout, 32)
890 )
891 .putFloat(
892 "${Settings.PREF_BUTTON_STICK_R}-Y$layout",
893 getResourceValue(layout, 33)
894 )
895 .apply()
896 } 688 }
897 689
898 override fun isInEditMode(): Boolean { 690 override fun isInEditMode(): Boolean {
@@ -913,18 +705,6 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
913 FOLDABLE_OVERLAY_VERSION 705 FOLDABLE_OVERLAY_VERSION
914 ) 706 )
915 707
916 private val preferences: SharedPreferences =
917 PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
918
919 const val LANDSCAPE = "_Landscape"
920 const val PORTRAIT = "_Portrait"
921 const val FOLDABLE = "_Foldable"
922 val overlayLayouts = listOf(
923 LANDSCAPE,
924 PORTRAIT,
925 FOLDABLE
926 )
927
928 /** 708 /**
929 * Resizes a [Bitmap] by a given scale factor 709 * Resizes a [Bitmap] by a given scale factor
930 * 710 *
@@ -1036,29 +816,19 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
1036 * In the input overlay configuration menu, 816 * In the input overlay configuration menu,
1037 * once a touch event begins and then ends (ie. Organizing the buttons to one's own liking for the overlay). 817 * once a touch event begins and then ends (ie. Organizing the buttons to one's own liking for the overlay).
1038 * the X and Y coordinates of the button at the END of its touch event 818 * the X and Y coordinates of the button at the END of its touch event
1039 * (when you remove your finger/stylus from the touchscreen) are then stored 819 * (when you remove your finger/stylus from the touchscreen) are then stored in a native .
1040 * within a SharedPreferences instance so that those values can be retrieved here.
1041 *
1042 *
1043 * This has a few benefits over the conventional way of storing the values
1044 * (ie. within the yuzu ini file).
1045 *
1046 * * No native calls
1047 * * Keeps Android-only values inside the Android environment
1048 *
1049 *
1050 * 820 *
1051 * Technically no modifications should need to be performed on the returned 821 * Technically no modifications should need to be performed on the returned
1052 * InputOverlayDrawableButton. Simply add it to the HashSet of overlay items and wait 822 * InputOverlayDrawableButton. Simply add it to the HashSet of overlay items and wait
1053 * for Android to call the onDraw method. 823 * for Android to call the onDraw method.
1054 * 824 *
1055 * @param context The current [Context]. 825 * @param context The current [Context].
1056 * @param windowSize The size of the window to draw the overlay on. 826 * @param windowSize The size of the window to draw the overlay on.
1057 * @param defaultResId The resource ID of the [Drawable] to get the [Bitmap] of (Default State). 827 * @param defaultResId The resource ID of the [Drawable] to get the [Bitmap] of (Default State).
1058 * @param pressedResId The resource ID of the [Drawable] to get the [Bitmap] of (Pressed State). 828 * @param pressedResId The resource ID of the [Drawable] to get the [Bitmap] of (Pressed State).
1059 * @param buttonId Identifier for determining what type of button the initialized InputOverlayDrawableButton represents. 829 * @param buttonId Identifier for determining what type of button the initialized InputOverlayDrawableButton represents.
1060 * @param prefId Identifier for determining where a button appears on screen. 830 * @param overlayControlData Identifier for determining where a button appears on screen.
1061 * @param layout The current screen layout as determined by [LANDSCAPE], [PORTRAIT], or [FOLDABLE]. 831 * @param position The position on screen as represented by an x and y value between 0 and 1.
1062 * @return An [InputOverlayDrawableButton] with the correct drawing bounds set. 832 * @return An [InputOverlayDrawableButton] with the correct drawing bounds set.
1063 */ 833 */
1064 private fun initializeOverlayButton( 834 private fun initializeOverlayButton(
@@ -1067,33 +837,30 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
1067 defaultResId: Int, 837 defaultResId: Int,
1068 pressedResId: Int, 838 pressedResId: Int,
1069 buttonId: Int, 839 buttonId: Int,
1070 prefId: String, 840 overlayControlData: OverlayControlData,
1071 layout: String 841 position: Pair<Double, Double>
1072 ): InputOverlayDrawableButton { 842 ): InputOverlayDrawableButton {
1073 // Resources handle for fetching the initial Drawable resource. 843 // Resources handle for fetching the initial Drawable resource.
1074 val res = context.resources 844 val res = context.resources
1075 845
1076 // SharedPreference to retrieve the X and Y coordinates for the InputOverlayDrawableButton.
1077 val sPrefs = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
1078
1079 // Decide scale based on button preference ID and user preference 846 // Decide scale based on button preference ID and user preference
1080 var scale: Float = when (prefId) { 847 var scale: Float = when (overlayControlData.id) {
1081 Settings.PREF_BUTTON_HOME, 848 OverlayControl.BUTTON_HOME.id,
1082 Settings.PREF_BUTTON_SCREENSHOT, 849 OverlayControl.BUTTON_CAPTURE.id,
1083 Settings.PREF_BUTTON_PLUS, 850 OverlayControl.BUTTON_PLUS.id,
1084 Settings.PREF_BUTTON_MINUS -> 0.07f 851 OverlayControl.BUTTON_MINUS.id -> 0.07f
1085 852
1086 Settings.PREF_BUTTON_L, 853 OverlayControl.BUTTON_L.id,
1087 Settings.PREF_BUTTON_R, 854 OverlayControl.BUTTON_R.id,
1088 Settings.PREF_BUTTON_ZL, 855 OverlayControl.BUTTON_ZL.id,
1089 Settings.PREF_BUTTON_ZR -> 0.26f 856 OverlayControl.BUTTON_ZR.id -> 0.26f
1090 857
1091 Settings.PREF_BUTTON_STICK_L, 858 OverlayControl.BUTTON_STICK_L.id,
1092 Settings.PREF_BUTTON_STICK_R -> 0.155f 859 OverlayControl.BUTTON_STICK_R.id -> 0.155f
1093 860
1094 else -> 0.11f 861 else -> 0.11f
1095 } 862 }
1096 scale *= (sPrefs.getInt(Settings.PREF_CONTROL_SCALE, 50) + 50).toFloat() 863 scale *= (IntSetting.OVERLAY_SCALE.getInt() + 50).toFloat()
1097 scale /= 100f 864 scale /= 100f
1098 865
1099 // Initialize the InputOverlayDrawableButton. 866 // Initialize the InputOverlayDrawableButton.
@@ -1104,7 +871,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
1104 defaultStateBitmap, 871 defaultStateBitmap,
1105 pressedStateBitmap, 872 pressedStateBitmap,
1106 buttonId, 873 buttonId,
1107 prefId 874 overlayControlData
1108 ) 875 )
1109 876
1110 // Get the minimum and maximum coordinates of the screen where the button can be placed. 877 // Get the minimum and maximum coordinates of the screen where the button can be placed.
@@ -1113,12 +880,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
1113 880
1114 // The X and Y coordinates of the InputOverlayDrawableButton on the InputOverlay. 881 // The X and Y coordinates of the InputOverlayDrawableButton on the InputOverlay.
1115 // These were set in the input overlay configuration menu. 882 // These were set in the input overlay configuration menu.
1116 val xKey = "$prefId-X$layout" 883 val drawableX = (position.first * max.x + min.x).toInt()
1117 val yKey = "$prefId-Y$layout" 884 val drawableY = (position.second * max.y + min.y).toInt()
1118 val drawableXPercent = sPrefs.getFloat(xKey, 0f)
1119 val drawableYPercent = sPrefs.getFloat(yKey, 0f)
1120 val drawableX = (drawableXPercent * max.x + min.x).toInt()
1121 val drawableY = (drawableYPercent * max.y + min.y).toInt()
1122 val width = overlayDrawable.width 885 val width = overlayDrawable.width
1123 val height = overlayDrawable.height 886 val height = overlayDrawable.height
1124 887
@@ -1136,8 +899,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
1136 drawableX - (width / 2), 899 drawableX - (width / 2),
1137 drawableY - (height / 2) 900 drawableY - (height / 2)
1138 ) 901 )
1139 val savedOpacity = preferences.getInt(Settings.PREF_CONTROL_OPACITY, 100) 902 overlayDrawable.setOpacity(IntSetting.OVERLAY_OPACITY.getInt() * 255 / 100)
1140 overlayDrawable.setOpacity(savedOpacity * 255 / 100)
1141 return overlayDrawable 903 return overlayDrawable
1142 } 904 }
1143 905
@@ -1149,7 +911,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
1149 * @param defaultResId The [Bitmap] resource ID of the default state. 911 * @param defaultResId The [Bitmap] resource ID of the default state.
1150 * @param pressedOneDirectionResId The [Bitmap] resource ID of the pressed state in one direction. 912 * @param pressedOneDirectionResId The [Bitmap] resource ID of the pressed state in one direction.
1151 * @param pressedTwoDirectionsResId The [Bitmap] resource ID of the pressed state in two directions. 913 * @param pressedTwoDirectionsResId The [Bitmap] resource ID of the pressed state in two directions.
1152 * @param layout The current screen layout as determined by [LANDSCAPE], [PORTRAIT], or [FOLDABLE]. 914 * @param position The position on screen as represented by an x and y value between 0 and 1.
1153 * @return The initialized [InputOverlayDrawableDpad] 915 * @return The initialized [InputOverlayDrawableDpad]
1154 */ 916 */
1155 private fun initializeOverlayDpad( 917 private fun initializeOverlayDpad(
@@ -1158,17 +920,14 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
1158 defaultResId: Int, 920 defaultResId: Int,
1159 pressedOneDirectionResId: Int, 921 pressedOneDirectionResId: Int,
1160 pressedTwoDirectionsResId: Int, 922 pressedTwoDirectionsResId: Int,
1161 layout: String 923 position: Pair<Double, Double>
1162 ): InputOverlayDrawableDpad { 924 ): InputOverlayDrawableDpad {
1163 // Resources handle for fetching the initial Drawable resource. 925 // Resources handle for fetching the initial Drawable resource.
1164 val res = context.resources 926 val res = context.resources
1165 927
1166 // SharedPreference to retrieve the X and Y coordinates for the InputOverlayDrawableDpad.
1167 val sPrefs = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
1168
1169 // Decide scale based on button ID and user preference 928 // Decide scale based on button ID and user preference
1170 var scale = 0.25f 929 var scale = 0.25f
1171 scale *= (sPrefs.getInt(Settings.PREF_CONTROL_SCALE, 50) + 50).toFloat() 930 scale *= (IntSetting.OVERLAY_SCALE.getInt() + 50).toFloat()
1172 scale /= 100f 931 scale /= 100f
1173 932
1174 // Initialize the InputOverlayDrawableDpad. 933 // Initialize the InputOverlayDrawableDpad.
@@ -1195,10 +954,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
1195 954
1196 // The X and Y coordinates of the InputOverlayDrawableDpad on the InputOverlay. 955 // The X and Y coordinates of the InputOverlayDrawableDpad on the InputOverlay.
1197 // These were set in the input overlay configuration menu. 956 // These were set in the input overlay configuration menu.
1198 val drawableXPercent = sPrefs.getFloat("${Settings.PREF_BUTTON_DPAD}-X$layout", 0f) 957 val drawableX = (position.first * max.x + min.x).toInt()
1199 val drawableYPercent = sPrefs.getFloat("${Settings.PREF_BUTTON_DPAD}-Y$layout", 0f) 958 val drawableY = (position.second * max.y + min.y).toInt()
1200 val drawableX = (drawableXPercent * max.x + min.x).toInt()
1201 val drawableY = (drawableYPercent * max.y + min.y).toInt()
1202 val width = overlayDrawable.width 959 val width = overlayDrawable.width
1203 val height = overlayDrawable.height 960 val height = overlayDrawable.height
1204 961
@@ -1213,8 +970,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
1213 970
1214 // Need to set the image's position 971 // Need to set the image's position
1215 overlayDrawable.setPosition(drawableX - (width / 2), drawableY - (height / 2)) 972 overlayDrawable.setPosition(drawableX - (width / 2), drawableY - (height / 2))
1216 val savedOpacity = preferences.getInt(Settings.PREF_CONTROL_OPACITY, 100) 973 overlayDrawable.setOpacity(IntSetting.OVERLAY_OPACITY.getInt() * 255 / 100)
1217 overlayDrawable.setOpacity(savedOpacity * 255 / 100)
1218 return overlayDrawable 974 return overlayDrawable
1219 } 975 }
1220 976
@@ -1227,9 +983,9 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
1227 * @param defaultResInner Resource ID for the default inner image of the joystick (the one you actually move around). 983 * @param defaultResInner Resource ID for the default inner image of the joystick (the one you actually move around).
1228 * @param pressedResInner Resource ID for the pressed inner image of the joystick. 984 * @param pressedResInner Resource ID for the pressed inner image of the joystick.
1229 * @param joystick Identifier for which joystick this is. 985 * @param joystick Identifier for which joystick this is.
1230 * @param button Identifier for which joystick button this is. 986 * @param buttonId Identifier for which joystick button this is.
1231 * @param prefId Identifier for determining where a button appears on screen. 987 * @param overlayControlData Identifier for determining where a button appears on screen.
1232 * @param layout The current screen layout as determined by [LANDSCAPE], [PORTRAIT], or [FOLDABLE]. 988 * @param position The position on screen as represented by an x and y value between 0 and 1.
1233 * @return The initialized [InputOverlayDrawableJoystick]. 989 * @return The initialized [InputOverlayDrawableJoystick].
1234 */ 990 */
1235 private fun initializeOverlayJoystick( 991 private fun initializeOverlayJoystick(
@@ -1239,19 +995,16 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
1239 defaultResInner: Int, 995 defaultResInner: Int,
1240 pressedResInner: Int, 996 pressedResInner: Int,
1241 joystick: Int, 997 joystick: Int,
1242 button: Int, 998 buttonId: Int,
1243 prefId: String, 999 overlayControlData: OverlayControlData,
1244 layout: String 1000 position: Pair<Double, Double>
1245 ): InputOverlayDrawableJoystick { 1001 ): InputOverlayDrawableJoystick {
1246 // Resources handle for fetching the initial Drawable resource. 1002 // Resources handle for fetching the initial Drawable resource.
1247 val res = context.resources 1003 val res = context.resources
1248 1004
1249 // SharedPreference to retrieve the X and Y coordinates for the InputOverlayDrawableJoystick.
1250 val sPrefs = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
1251
1252 // Decide scale based on user preference 1005 // Decide scale based on user preference
1253 var scale = 0.3f 1006 var scale = 0.3f
1254 scale *= (sPrefs.getInt(Settings.PREF_CONTROL_SCALE, 50) + 50).toFloat() 1007 scale *= (IntSetting.OVERLAY_SCALE.getInt() + 50).toFloat()
1255 scale /= 100f 1008 scale /= 100f
1256 1009
1257 // Initialize the InputOverlayDrawableJoystick. 1010 // Initialize the InputOverlayDrawableJoystick.
@@ -1265,10 +1018,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
1265 1018
1266 // The X and Y coordinates of the InputOverlayDrawableButton on the InputOverlay. 1019 // The X and Y coordinates of the InputOverlayDrawableButton on the InputOverlay.
1267 // These were set in the input overlay configuration menu. 1020 // These were set in the input overlay configuration menu.
1268 val drawableXPercent = sPrefs.getFloat("$prefId-X$layout", 0f) 1021 val drawableX = (position.first * max.x + min.x).toInt()
1269 val drawableYPercent = sPrefs.getFloat("$prefId-Y$layout", 0f) 1022 val drawableY = (position.second * max.y + min.y).toInt()
1270 val drawableX = (drawableXPercent * max.x + min.x).toInt()
1271 val drawableY = (drawableYPercent * max.y + min.y).toInt()
1272 val outerScale = 1.66f 1023 val outerScale = 1.66f
1273 1024
1274 // Now set the bounds for the InputOverlayDrawableJoystick. 1025 // Now set the bounds for the InputOverlayDrawableJoystick.
@@ -1292,14 +1043,13 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
1292 outerRect, 1043 outerRect,
1293 innerRect, 1044 innerRect,
1294 joystick, 1045 joystick,
1295 button, 1046 buttonId,
1296 prefId 1047 overlayControlData.id
1297 ) 1048 )
1298 1049
1299 // Need to set the image's position 1050 // Need to set the image's position
1300 overlayDrawable.setPosition(drawableX, drawableY) 1051 overlayDrawable.setPosition(drawableX, drawableY)
1301 val savedOpacity = preferences.getInt(Settings.PREF_CONTROL_OPACITY, 100) 1052 overlayDrawable.setOpacity(IntSetting.OVERLAY_OPACITY.getInt() * 255 / 100)
1302 overlayDrawable.setOpacity(savedOpacity * 255 / 100)
1303 return overlayDrawable 1053 return overlayDrawable
1304 } 1054 }
1305 } 1055 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableButton.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableButton.kt
index 2c28dda88..b14a4f96e 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableButton.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableButton.kt
@@ -10,6 +10,7 @@ import android.graphics.Rect
10import android.graphics.drawable.BitmapDrawable 10import android.graphics.drawable.BitmapDrawable
11import android.view.MotionEvent 11import android.view.MotionEvent
12import org.yuzu.yuzu_emu.NativeLibrary.ButtonState 12import org.yuzu.yuzu_emu.NativeLibrary.ButtonState
13import org.yuzu.yuzu_emu.overlay.model.OverlayControlData
13 14
14/** 15/**
15 * Custom [BitmapDrawable] that is capable 16 * Custom [BitmapDrawable] that is capable
@@ -25,7 +26,7 @@ class InputOverlayDrawableButton(
25 defaultStateBitmap: Bitmap, 26 defaultStateBitmap: Bitmap,
26 pressedStateBitmap: Bitmap, 27 pressedStateBitmap: Bitmap,
27 val buttonId: Int, 28 val buttonId: Int,
28 val prefId: String 29 val overlayControlData: OverlayControlData
29) { 30) {
30 // The ID value what motion event is tracking 31 // The ID value what motion event is tracking
31 var trackId: Int 32 var trackId: Int
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt
index 518b1e783..113bf7c24 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt
@@ -14,7 +14,7 @@ import kotlin.math.cos
14import kotlin.math.sin 14import kotlin.math.sin
15import kotlin.math.sqrt 15import kotlin.math.sqrt
16import org.yuzu.yuzu_emu.NativeLibrary 16import org.yuzu.yuzu_emu.NativeLibrary
17import org.yuzu.yuzu_emu.utils.EmulationMenuSettings 17import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
18 18
19/** 19/**
20 * Custom [BitmapDrawable] that is capable 20 * Custom [BitmapDrawable] that is capable
@@ -125,7 +125,7 @@ class InputOverlayDrawableJoystick(
125 pressedState = true 125 pressedState = true
126 outerBitmap.alpha = 0 126 outerBitmap.alpha = 0
127 boundsBoxBitmap.alpha = opacity 127 boundsBoxBitmap.alpha = opacity
128 if (EmulationMenuSettings.joystickRelCenter) { 128 if (BooleanSetting.JOYSTICK_REL_CENTER.getBoolean()) {
129 virtBounds.offset( 129 virtBounds.offset(
130 xPosition - virtBounds.centerX(), 130 xPosition - virtBounds.centerX(),
131 yPosition - virtBounds.centerY() 131 yPosition - virtBounds.centerY()
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/model/OverlayControl.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/model/OverlayControl.kt
new file mode 100644
index 000000000..a0eeadf4b
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/model/OverlayControl.kt
@@ -0,0 +1,188 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.overlay.model
5
6import androidx.annotation.IntegerRes
7import org.yuzu.yuzu_emu.R
8import org.yuzu.yuzu_emu.YuzuApplication
9
10enum class OverlayControl(
11 val id: String,
12 val defaultVisibility: Boolean,
13 @IntegerRes val defaultLandscapePositionResources: Pair<Int, Int>,
14 @IntegerRes val defaultPortraitPositionResources: Pair<Int, Int>,
15 @IntegerRes val defaultFoldablePositionResources: Pair<Int, Int>
16) {
17 BUTTON_A(
18 "button_a",
19 true,
20 Pair(R.integer.BUTTON_A_X, R.integer.BUTTON_A_Y),
21 Pair(R.integer.BUTTON_A_X_PORTRAIT, R.integer.BUTTON_A_Y_PORTRAIT),
22 Pair(R.integer.BUTTON_A_X_FOLDABLE, R.integer.BUTTON_A_Y_FOLDABLE)
23 ),
24 BUTTON_B(
25 "button_b",
26 true,
27 Pair(R.integer.BUTTON_B_X, R.integer.BUTTON_B_Y),
28 Pair(R.integer.BUTTON_B_X_PORTRAIT, R.integer.BUTTON_B_Y_PORTRAIT),
29 Pair(R.integer.BUTTON_B_X_FOLDABLE, R.integer.BUTTON_B_Y_FOLDABLE)
30 ),
31 BUTTON_X(
32 "button_x",
33 true,
34 Pair(R.integer.BUTTON_X_X, R.integer.BUTTON_X_Y),
35 Pair(R.integer.BUTTON_X_X_PORTRAIT, R.integer.BUTTON_X_Y_PORTRAIT),
36 Pair(R.integer.BUTTON_X_X_FOLDABLE, R.integer.BUTTON_X_Y_FOLDABLE)
37 ),
38 BUTTON_Y(
39 "button_y",
40 true,
41 Pair(R.integer.BUTTON_Y_X, R.integer.BUTTON_Y_Y),
42 Pair(R.integer.BUTTON_Y_X_PORTRAIT, R.integer.BUTTON_Y_Y_PORTRAIT),
43 Pair(R.integer.BUTTON_Y_X_FOLDABLE, R.integer.BUTTON_Y_Y_FOLDABLE)
44 ),
45 BUTTON_PLUS(
46 "button_plus",
47 true,
48 Pair(R.integer.BUTTON_PLUS_X, R.integer.BUTTON_PLUS_Y),
49 Pair(R.integer.BUTTON_PLUS_X_PORTRAIT, R.integer.BUTTON_PLUS_Y_PORTRAIT),
50 Pair(R.integer.BUTTON_PLUS_X_FOLDABLE, R.integer.BUTTON_PLUS_Y_FOLDABLE)
51 ),
52 BUTTON_MINUS(
53 "button_minus",
54 true,
55 Pair(R.integer.BUTTON_MINUS_X, R.integer.BUTTON_MINUS_Y),
56 Pair(R.integer.BUTTON_MINUS_X_PORTRAIT, R.integer.BUTTON_MINUS_Y_PORTRAIT),
57 Pair(R.integer.BUTTON_MINUS_X_FOLDABLE, R.integer.BUTTON_MINUS_Y_FOLDABLE)
58 ),
59 BUTTON_HOME(
60 "button_home",
61 false,
62 Pair(R.integer.BUTTON_HOME_X, R.integer.BUTTON_HOME_Y),
63 Pair(R.integer.BUTTON_HOME_X_PORTRAIT, R.integer.BUTTON_HOME_Y_PORTRAIT),
64 Pair(R.integer.BUTTON_HOME_X_FOLDABLE, R.integer.BUTTON_HOME_Y_FOLDABLE)
65 ),
66 BUTTON_CAPTURE(
67 "button_capture",
68 false,
69 Pair(R.integer.BUTTON_CAPTURE_X, R.integer.BUTTON_CAPTURE_Y),
70 Pair(R.integer.BUTTON_CAPTURE_X_PORTRAIT, R.integer.BUTTON_CAPTURE_Y_PORTRAIT),
71 Pair(R.integer.BUTTON_CAPTURE_X_FOLDABLE, R.integer.BUTTON_CAPTURE_Y_FOLDABLE)
72 ),
73 BUTTON_L(
74 "button_l",
75 true,
76 Pair(R.integer.BUTTON_L_X, R.integer.BUTTON_L_Y),
77 Pair(R.integer.BUTTON_L_X_PORTRAIT, R.integer.BUTTON_L_Y_PORTRAIT),
78 Pair(R.integer.BUTTON_L_X_FOLDABLE, R.integer.BUTTON_L_Y_FOLDABLE)
79 ),
80 BUTTON_R(
81 "button_r",
82 true,
83 Pair(R.integer.BUTTON_R_X, R.integer.BUTTON_R_Y),
84 Pair(R.integer.BUTTON_R_X_PORTRAIT, R.integer.BUTTON_R_Y_PORTRAIT),
85 Pair(R.integer.BUTTON_R_X_FOLDABLE, R.integer.BUTTON_R_Y_FOLDABLE)
86 ),
87 BUTTON_ZL(
88 "button_zl",
89 true,
90 Pair(R.integer.BUTTON_ZL_X, R.integer.BUTTON_ZL_Y),
91 Pair(R.integer.BUTTON_ZL_X_PORTRAIT, R.integer.BUTTON_ZL_Y_PORTRAIT),
92 Pair(R.integer.BUTTON_ZL_X_FOLDABLE, R.integer.BUTTON_ZL_Y_FOLDABLE)
93 ),
94 BUTTON_ZR(
95 "button_zr",
96 true,
97 Pair(R.integer.BUTTON_ZR_X, R.integer.BUTTON_ZR_Y),
98 Pair(R.integer.BUTTON_ZR_X_PORTRAIT, R.integer.BUTTON_ZR_Y_PORTRAIT),
99 Pair(R.integer.BUTTON_ZR_X_FOLDABLE, R.integer.BUTTON_ZR_Y_FOLDABLE)
100 ),
101 BUTTON_STICK_L(
102 "button_stick_l",
103 true,
104 Pair(R.integer.BUTTON_STICK_L_X, R.integer.BUTTON_STICK_L_Y),
105 Pair(R.integer.BUTTON_STICK_L_X_PORTRAIT, R.integer.BUTTON_STICK_L_Y_PORTRAIT),
106 Pair(R.integer.BUTTON_STICK_L_X_FOLDABLE, R.integer.BUTTON_STICK_L_Y_FOLDABLE)
107 ),
108 BUTTON_STICK_R(
109 "button_stick_r",
110 true,
111 Pair(R.integer.BUTTON_STICK_R_X, R.integer.BUTTON_STICK_R_Y),
112 Pair(R.integer.BUTTON_STICK_R_X_PORTRAIT, R.integer.BUTTON_STICK_R_Y_PORTRAIT),
113 Pair(R.integer.BUTTON_STICK_R_X_FOLDABLE, R.integer.BUTTON_STICK_R_Y_FOLDABLE)
114 ),
115 STICK_L(
116 "stick_l",
117 true,
118 Pair(R.integer.STICK_L_X, R.integer.STICK_L_Y),
119 Pair(R.integer.STICK_L_X_PORTRAIT, R.integer.STICK_L_Y_PORTRAIT),
120 Pair(R.integer.STICK_L_X_FOLDABLE, R.integer.STICK_L_Y_FOLDABLE)
121 ),
122 STICK_R(
123 "stick_r",
124 true,
125 Pair(R.integer.STICK_R_X, R.integer.STICK_R_Y),
126 Pair(R.integer.STICK_R_X_PORTRAIT, R.integer.STICK_R_Y_PORTRAIT),
127 Pair(R.integer.STICK_R_X_FOLDABLE, R.integer.STICK_R_Y_FOLDABLE)
128 ),
129 COMBINED_DPAD(
130 "combined_dpad",
131 true,
132 Pair(R.integer.COMBINED_DPAD_X, R.integer.COMBINED_DPAD_Y),
133 Pair(R.integer.COMBINED_DPAD_X_PORTRAIT, R.integer.COMBINED_DPAD_Y_PORTRAIT),
134 Pair(R.integer.COMBINED_DPAD_X_FOLDABLE, R.integer.COMBINED_DPAD_Y_FOLDABLE)
135 );
136
137 fun getDefaultPositionForLayout(layout: OverlayLayout): Pair<Double, Double> {
138 val rawResourcePair: Pair<Int, Int>
139 YuzuApplication.appContext.resources.apply {
140 rawResourcePair = when (layout) {
141 OverlayLayout.Landscape -> {
142 Pair(
143 getInteger(this@OverlayControl.defaultLandscapePositionResources.first),
144 getInteger(this@OverlayControl.defaultLandscapePositionResources.second)
145 )
146 }
147
148 OverlayLayout.Portrait -> {
149 Pair(
150 getInteger(this@OverlayControl.defaultPortraitPositionResources.first),
151 getInteger(this@OverlayControl.defaultPortraitPositionResources.second)
152 )
153 }
154
155 OverlayLayout.Foldable -> {
156 Pair(
157 getInteger(this@OverlayControl.defaultFoldablePositionResources.first),
158 getInteger(this@OverlayControl.defaultFoldablePositionResources.second)
159 )
160 }
161 }
162 }
163
164 return Pair(
165 rawResourcePair.first.toDouble() / 1000,
166 rawResourcePair.second.toDouble() / 1000
167 )
168 }
169
170 fun toOverlayControlData(): OverlayControlData =
171 OverlayControlData(
172 id,
173 defaultVisibility,
174 getDefaultPositionForLayout(OverlayLayout.Landscape),
175 getDefaultPositionForLayout(OverlayLayout.Portrait),
176 getDefaultPositionForLayout(OverlayLayout.Foldable)
177 )
178
179 companion object {
180 val map: HashMap<String, OverlayControl> by lazy {
181 val hashMap = hashMapOf<String, OverlayControl>()
182 entries.forEach { hashMap[it.id] = it }
183 hashMap
184 }
185
186 fun from(id: String): OverlayControl? = map[id]
187 }
188}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/model/OverlayControlData.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/model/OverlayControlData.kt
new file mode 100644
index 000000000..26cfeb1db
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/model/OverlayControlData.kt
@@ -0,0 +1,19 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.overlay.model
5
6data class OverlayControlData(
7 val id: String,
8 var enabled: Boolean,
9 var landscapePosition: Pair<Double, Double>,
10 var portraitPosition: Pair<Double, Double>,
11 var foldablePosition: Pair<Double, Double>
12) {
13 fun positionFromLayout(layout: OverlayLayout): Pair<Double, Double> =
14 when (layout) {
15 OverlayLayout.Landscape -> landscapePosition
16 OverlayLayout.Portrait -> portraitPosition
17 OverlayLayout.Foldable -> foldablePosition
18 }
19}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/model/OverlayControlDefault.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/model/OverlayControlDefault.kt
new file mode 100644
index 000000000..6bd74c82f
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/model/OverlayControlDefault.kt
@@ -0,0 +1,13 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.overlay.model
5
6import androidx.annotation.IntegerRes
7
8data class OverlayControlDefault(
9 val buttonId: String,
10 @IntegerRes val landscapePositionResource: Pair<Int, Int>,
11 @IntegerRes val portraitPositionResource: Pair<Int, Int>,
12 @IntegerRes val foldablePositionResource: Pair<Int, Int>
13)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/model/OverlayLayout.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/model/OverlayLayout.kt
new file mode 100644
index 000000000..d728164e5
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/model/OverlayLayout.kt
@@ -0,0 +1,10 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.overlay.model
5
6enum class OverlayLayout(val id: String) {
7 Landscape("Landscape"),
8 Portrait("Portrait"),
9 Foldable("Foldable")
10}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt
index d4a9da06f..de0794a17 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt
@@ -10,6 +10,9 @@ import org.yuzu.yuzu_emu.YuzuApplication
10import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting 10import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
11import org.yuzu.yuzu_emu.features.settings.model.IntSetting 11import org.yuzu.yuzu_emu.features.settings.model.IntSetting
12import org.yuzu.yuzu_emu.features.settings.model.Settings 12import org.yuzu.yuzu_emu.features.settings.model.Settings
13import org.yuzu.yuzu_emu.overlay.model.OverlayControlData
14import org.yuzu.yuzu_emu.overlay.model.OverlayControl
15import org.yuzu.yuzu_emu.overlay.model.OverlayLayout
13import org.yuzu.yuzu_emu.utils.PreferenceUtil.migratePreference 16import org.yuzu.yuzu_emu.utils.PreferenceUtil.migratePreference
14 17
15object DirectoryInitialization { 18object DirectoryInitialization {
@@ -64,8 +67,147 @@ object DirectoryInitialization {
64 saveConfig = true 67 saveConfig = true
65 } 68 }
66 69
70 val joystickRelCenter =
71 preferences.migratePreference<Boolean>(Settings.PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER)
72 if (joystickRelCenter != null) {
73 BooleanSetting.JOYSTICK_REL_CENTER.setBoolean(joystickRelCenter)
74 saveConfig = true
75 }
76
77 val dpadSlide =
78 preferences.migratePreference<Boolean>(Settings.PREF_MENU_SETTINGS_DPAD_SLIDE)
79 if (dpadSlide != null) {
80 BooleanSetting.DPAD_SLIDE.setBoolean(dpadSlide)
81 saveConfig = true
82 }
83
84 val hapticFeedback =
85 preferences.migratePreference<Boolean>(Settings.PREF_MENU_SETTINGS_HAPTICS)
86 if (hapticFeedback != null) {
87 BooleanSetting.HAPTIC_FEEDBACK.setBoolean(hapticFeedback)
88 saveConfig = true
89 }
90
91 val showPerformanceOverlay =
92 preferences.migratePreference<Boolean>(Settings.PREF_MENU_SETTINGS_SHOW_FPS)
93 if (showPerformanceOverlay != null) {
94 BooleanSetting.SHOW_PERFORMANCE_OVERLAY.setBoolean(showPerformanceOverlay)
95 saveConfig = true
96 }
97
98 val showInputOverlay =
99 preferences.migratePreference<Boolean>(Settings.PREF_MENU_SETTINGS_SHOW_OVERLAY)
100 if (showInputOverlay != null) {
101 BooleanSetting.SHOW_INPUT_OVERLAY.setBoolean(showInputOverlay)
102 saveConfig = true
103 }
104
105 val overlayOpacity = preferences.migratePreference<Int>(Settings.PREF_CONTROL_OPACITY)
106 if (overlayOpacity != null) {
107 IntSetting.OVERLAY_OPACITY.setInt(overlayOpacity)
108 saveConfig = true
109 }
110
111 val overlayScale = preferences.migratePreference<Int>(Settings.PREF_CONTROL_SCALE)
112 if (overlayScale != null) {
113 IntSetting.OVERLAY_SCALE.setInt(overlayScale)
114 saveConfig = true
115 }
116
117 var setOverlayData = false
118 val overlayControlData = NativeConfig.getOverlayControlData()
119 if (overlayControlData.isEmpty()) {
120 val overlayControlDataMap =
121 NativeConfig.getOverlayControlData().associateBy { it.id }.toMutableMap()
122 for (button in Settings.overlayPreferences) {
123 val buttonId = convertButtonId(button)
124 var buttonEnabled = preferences.migratePreference<Boolean>(button)
125 if (buttonEnabled == null) {
126 buttonEnabled = OverlayControl.map[buttonId]?.defaultVisibility == true
127 }
128
129 var landscapeXPosition = preferences.migratePreference<Float>(
130 "$button-X${Settings.PREF_LANDSCAPE_SUFFIX}"
131 )?.toDouble()
132 var landscapeYPosition = preferences.migratePreference<Float>(
133 "$button-Y${Settings.PREF_LANDSCAPE_SUFFIX}"
134 )?.toDouble()
135 if (landscapeXPosition == null || landscapeYPosition == null) {
136 val landscapePosition = OverlayControl.map[buttonId]
137 ?.getDefaultPositionForLayout(OverlayLayout.Landscape) ?: Pair(0.0, 0.0)
138 landscapeXPosition = landscapePosition.first
139 landscapeYPosition = landscapePosition.second
140 }
141
142 var portraitXPosition = preferences.migratePreference<Float>(
143 "$button-X${Settings.PREF_PORTRAIT_SUFFIX}"
144 )?.toDouble()
145 var portraitYPosition = preferences.migratePreference<Float>(
146 "$button-Y${Settings.PREF_PORTRAIT_SUFFIX}"
147 )?.toDouble()
148 if (portraitXPosition == null || portraitYPosition == null) {
149 val portraitPosition = OverlayControl.map[buttonId]
150 ?.getDefaultPositionForLayout(OverlayLayout.Portrait) ?: Pair(0.0, 0.0)
151 portraitXPosition = portraitPosition.first
152 portraitYPosition = portraitPosition.second
153 }
154
155 var foldableXPosition = preferences.migratePreference<Float>(
156 "$button-X${Settings.PREF_FOLDABLE_SUFFIX}"
157 )?.toDouble()
158 var foldableYPosition = preferences.migratePreference<Float>(
159 "$button-Y${Settings.PREF_FOLDABLE_SUFFIX}"
160 )?.toDouble()
161 if (foldableXPosition == null || foldableYPosition == null) {
162 val foldablePosition = OverlayControl.map[buttonId]
163 ?.getDefaultPositionForLayout(OverlayLayout.Foldable) ?: Pair(0.0, 0.0)
164 foldableXPosition = foldablePosition.first
165 foldableYPosition = foldablePosition.second
166 }
167
168 val controlData = OverlayControlData(
169 buttonId,
170 buttonEnabled,
171 Pair(landscapeXPosition, landscapeYPosition),
172 Pair(portraitXPosition, portraitYPosition),
173 Pair(foldableXPosition, foldableYPosition)
174 )
175 overlayControlDataMap[buttonId] = controlData
176 setOverlayData = true
177 }
178
179 if (setOverlayData) {
180 NativeConfig.setOverlayControlData(
181 overlayControlDataMap.map { it.value }.toTypedArray()
182 )
183 saveConfig = true
184 }
185 }
186
67 if (saveConfig) { 187 if (saveConfig) {
68 NativeConfig.saveGlobalConfig() 188 NativeConfig.saveGlobalConfig()
69 } 189 }
70 } 190 }
191
192 private fun convertButtonId(buttonId: String): String =
193 when (buttonId) {
194 Settings.PREF_BUTTON_A -> OverlayControl.BUTTON_A.id
195 Settings.PREF_BUTTON_B -> OverlayControl.BUTTON_B.id
196 Settings.PREF_BUTTON_X -> OverlayControl.BUTTON_X.id
197 Settings.PREF_BUTTON_Y -> OverlayControl.BUTTON_Y.id
198 Settings.PREF_BUTTON_L -> OverlayControl.BUTTON_L.id
199 Settings.PREF_BUTTON_R -> OverlayControl.BUTTON_R.id
200 Settings.PREF_BUTTON_ZL -> OverlayControl.BUTTON_ZL.id
201 Settings.PREF_BUTTON_ZR -> OverlayControl.BUTTON_ZR.id
202 Settings.PREF_BUTTON_PLUS -> OverlayControl.BUTTON_PLUS.id
203 Settings.PREF_BUTTON_MINUS -> OverlayControl.BUTTON_MINUS.id
204 Settings.PREF_BUTTON_DPAD -> OverlayControl.COMBINED_DPAD.id
205 Settings.PREF_STICK_L -> OverlayControl.STICK_L.id
206 Settings.PREF_STICK_R -> OverlayControl.STICK_R.id
207 Settings.PREF_BUTTON_HOME -> OverlayControl.BUTTON_HOME.id
208 Settings.PREF_BUTTON_SCREENSHOT -> OverlayControl.BUTTON_CAPTURE.id
209 Settings.PREF_BUTTON_STICK_L -> OverlayControl.BUTTON_STICK_L.id
210 Settings.PREF_BUTTON_STICK_R -> OverlayControl.BUTTON_STICK_R.id
211 else -> ""
212 }
71} 213}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/EmulationMenuSettings.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/EmulationMenuSettings.kt
deleted file mode 100644
index 7e8f058c1..000000000
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/EmulationMenuSettings.kt
+++ /dev/null
@@ -1,50 +0,0 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.utils
5
6import androidx.preference.PreferenceManager
7import org.yuzu.yuzu_emu.YuzuApplication
8import org.yuzu.yuzu_emu.features.settings.model.Settings
9
10object EmulationMenuSettings {
11 private val preferences =
12 PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
13
14 var joystickRelCenter: Boolean
15 get() = preferences.getBoolean(Settings.PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER, true)
16 set(value) {
17 preferences.edit()
18 .putBoolean(Settings.PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER, value)
19 .apply()
20 }
21 var dpadSlide: Boolean
22 get() = preferences.getBoolean(Settings.PREF_MENU_SETTINGS_DPAD_SLIDE, true)
23 set(value) {
24 preferences.edit()
25 .putBoolean(Settings.PREF_MENU_SETTINGS_DPAD_SLIDE, value)
26 .apply()
27 }
28 var hapticFeedback: Boolean
29 get() = preferences.getBoolean(Settings.PREF_MENU_SETTINGS_HAPTICS, false)
30 set(value) {
31 preferences.edit()
32 .putBoolean(Settings.PREF_MENU_SETTINGS_HAPTICS, value)
33 .apply()
34 }
35
36 var showFps: Boolean
37 get() = preferences.getBoolean(Settings.PREF_MENU_SETTINGS_SHOW_FPS, false)
38 set(value) {
39 preferences.edit()
40 .putBoolean(Settings.PREF_MENU_SETTINGS_SHOW_FPS, value)
41 .apply()
42 }
43 var showOverlay: Boolean
44 get() = preferences.getBoolean(Settings.PREF_MENU_SETTINGS_SHOW_OVERLAY, true)
45 set(value) {
46 preferences.edit()
47 .putBoolean(Settings.PREF_MENU_SETTINGS_SHOW_OVERLAY, value)
48 .apply()
49 }
50}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt
index 7512d5eed..a4c14b3a7 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt
@@ -4,6 +4,7 @@
4package org.yuzu.yuzu_emu.utils 4package org.yuzu.yuzu_emu.utils
5 5
6import org.yuzu.yuzu_emu.model.GameDir 6import org.yuzu.yuzu_emu.model.GameDir
7import org.yuzu.yuzu_emu.overlay.model.OverlayControlData
7 8
8object NativeConfig { 9object NativeConfig {
9 /** 10 /**
@@ -150,4 +151,21 @@ object NativeConfig {
150 */ 151 */
151 @Synchronized 152 @Synchronized
152 external fun setDisabledAddons(programId: String, disabledAddons: Array<String>) 153 external fun setDisabledAddons(programId: String, disabledAddons: Array<String>)
154
155 /**
156 * Gets an array of [OverlayControlData] from settings
157 *
158 * @return An array of [OverlayControlData]
159 */
160 @Synchronized
161 external fun getOverlayControlData(): Array<OverlayControlData>
162
163 /**
164 * Clears the AndroidSettings::values.overlay_control_data array and replaces its values
165 * with [overlayControlData]
166 *
167 * @param overlayControlData Replacement array of [OverlayControlData]
168 */
169 @Synchronized
170 external fun setOverlayControlData(overlayControlData: Array<OverlayControlData>)
153} 171}
diff --git a/src/android/app/src/main/jni/android_common/android_common.cpp b/src/android/app/src/main/jni/android_common/android_common.cpp
index 52d8ecfeb..1e884ffdd 100644
--- a/src/android/app/src/main/jni/android_common/android_common.cpp
+++ b/src/android/app/src/main/jni/android_common/android_common.cpp
@@ -9,6 +9,7 @@
9#include <jni.h> 9#include <jni.h>
10 10
11#include "common/string_util.h" 11#include "common/string_util.h"
12#include "jni/id_cache.h"
12 13
13std::string GetJString(JNIEnv* env, jstring jstr) { 14std::string GetJString(JNIEnv* env, jstring jstr) {
14 if (!jstr) { 15 if (!jstr) {
@@ -33,3 +34,11 @@ jstring ToJString(JNIEnv* env, std::string_view str) {
33jstring ToJString(JNIEnv* env, std::u16string_view str) { 34jstring ToJString(JNIEnv* env, std::u16string_view str) {
34 return ToJString(env, Common::UTF16ToUTF8(str)); 35 return ToJString(env, Common::UTF16ToUTF8(str));
35} 36}
37
38double GetJDouble(JNIEnv* env, jobject jdouble) {
39 return env->GetDoubleField(jdouble, IDCache::GetDoubleValueField());
40}
41
42jobject ToJDouble(JNIEnv* env, double value) {
43 return env->NewObject(IDCache::GetDoubleClass(), IDCache::GetDoubleConstructor(), value);
44}
diff --git a/src/android/app/src/main/jni/android_common/android_common.h b/src/android/app/src/main/jni/android_common/android_common.h
index ccb0c06f7..8eb803e1b 100644
--- a/src/android/app/src/main/jni/android_common/android_common.h
+++ b/src/android/app/src/main/jni/android_common/android_common.h
@@ -10,3 +10,6 @@
10std::string GetJString(JNIEnv* env, jstring jstr); 10std::string GetJString(JNIEnv* env, jstring jstr);
11jstring ToJString(JNIEnv* env, std::string_view str); 11jstring ToJString(JNIEnv* env, std::string_view str);
12jstring ToJString(JNIEnv* env, std::u16string_view str); 12jstring ToJString(JNIEnv* env, std::u16string_view str);
13
14double GetJDouble(JNIEnv* env, jobject jdouble);
15jobject ToJDouble(JNIEnv* env, double value);
diff --git a/src/android/app/src/main/jni/android_config.cpp b/src/android/app/src/main/jni/android_config.cpp
index fb17ab6f6..c86aa1c39 100644
--- a/src/android/app/src/main/jni/android_config.cpp
+++ b/src/android/app/src/main/jni/android_config.cpp
@@ -35,6 +35,7 @@ void AndroidConfig::ReadAndroidValues() {
35 if (global) { 35 if (global) {
36 ReadAndroidUIValues(); 36 ReadAndroidUIValues();
37 ReadUIValues(); 37 ReadUIValues();
38 ReadOverlayValues();
38 } 39 }
39 ReadDriverValues(); 40 ReadDriverValues();
40} 41}
@@ -81,10 +82,42 @@ void AndroidConfig::ReadDriverValues() {
81 EndGroup(); 82 EndGroup();
82} 83}
83 84
85void AndroidConfig::ReadOverlayValues() {
86 BeginGroup(Settings::TranslateCategory(Settings::Category::Overlay));
87
88 ReadCategory(Settings::Category::Overlay);
89
90 AndroidSettings::values.overlay_control_data.clear();
91 const int control_data_size = BeginArray("control_data");
92 for (int i = 0; i < control_data_size; ++i) {
93 SetArrayIndex(i);
94 AndroidSettings::OverlayControlData control_data;
95 control_data.id = ReadStringSetting(std::string("id"));
96 control_data.enabled = ReadBooleanSetting(std::string("enabled"));
97 control_data.landscape_position.first =
98 ReadDoubleSetting(std::string("landscape\\x_position"));
99 control_data.landscape_position.second =
100 ReadDoubleSetting(std::string("landscape\\y_position"));
101 control_data.portrait_position.first =
102 ReadDoubleSetting(std::string("portrait\\x_position"));
103 control_data.portrait_position.second =
104 ReadDoubleSetting(std::string("portrait\\y_position"));
105 control_data.foldable_position.first =
106 ReadDoubleSetting(std::string("foldable\\x_position"));
107 control_data.foldable_position.second =
108 ReadDoubleSetting(std::string("foldable\\y_position"));
109 AndroidSettings::values.overlay_control_data.push_back(control_data);
110 }
111 EndArray();
112
113 EndGroup();
114}
115
84void AndroidConfig::SaveAndroidValues() { 116void AndroidConfig::SaveAndroidValues() {
85 if (global) { 117 if (global) {
86 SaveAndroidUIValues(); 118 SaveAndroidUIValues();
87 SaveUIValues(); 119 SaveUIValues();
120 SaveOverlayValues();
88 } 121 }
89 SaveDriverValues(); 122 SaveDriverValues();
90 123
@@ -131,6 +164,35 @@ void AndroidConfig::SaveDriverValues() {
131 EndGroup(); 164 EndGroup();
132} 165}
133 166
167void AndroidConfig::SaveOverlayValues() {
168 BeginGroup(Settings::TranslateCategory(Settings::Category::Overlay));
169
170 WriteCategory(Settings::Category::Overlay);
171
172 BeginArray("control_data");
173 for (size_t i = 0; i < AndroidSettings::values.overlay_control_data.size(); ++i) {
174 SetArrayIndex(i);
175 const auto& control_data = AndroidSettings::values.overlay_control_data[i];
176 WriteStringSetting(std::string("id"), control_data.id);
177 WriteBooleanSetting(std::string("enabled"), control_data.enabled);
178 WriteDoubleSetting(std::string("landscape\\x_position"),
179 control_data.landscape_position.first);
180 WriteDoubleSetting(std::string("landscape\\y_position"),
181 control_data.landscape_position.second);
182 WriteDoubleSetting(std::string("portrait\\x_position"),
183 control_data.portrait_position.first);
184 WriteDoubleSetting(std::string("portrait\\y_position"),
185 control_data.portrait_position.second);
186 WriteDoubleSetting(std::string("foldable\\x_position"),
187 control_data.foldable_position.first);
188 WriteDoubleSetting(std::string("foldable\\y_position"),
189 control_data.foldable_position.second);
190 }
191 EndArray();
192
193 EndGroup();
194}
195
134std::vector<Settings::BasicSetting*>& AndroidConfig::FindRelevantList(Settings::Category category) { 196std::vector<Settings::BasicSetting*>& AndroidConfig::FindRelevantList(Settings::Category category) {
135 auto& map = Settings::values.linkage.by_category; 197 auto& map = Settings::values.linkage.by_category;
136 if (map.contains(category)) { 198 if (map.contains(category)) {
diff --git a/src/android/app/src/main/jni/android_config.h b/src/android/app/src/main/jni/android_config.h
index 2c12874e1..d83852de9 100644
--- a/src/android/app/src/main/jni/android_config.h
+++ b/src/android/app/src/main/jni/android_config.h
@@ -18,6 +18,7 @@ protected:
18 void ReadAndroidValues(); 18 void ReadAndroidValues();
19 void ReadAndroidUIValues(); 19 void ReadAndroidUIValues();
20 void ReadDriverValues(); 20 void ReadDriverValues();
21 void ReadOverlayValues();
21 void ReadHidbusValues() override {} 22 void ReadHidbusValues() override {}
22 void ReadDebugControlValues() override {} 23 void ReadDebugControlValues() override {}
23 void ReadPathValues() override; 24 void ReadPathValues() override;
@@ -30,6 +31,7 @@ protected:
30 void SaveAndroidValues(); 31 void SaveAndroidValues();
31 void SaveAndroidUIValues(); 32 void SaveAndroidUIValues();
32 void SaveDriverValues(); 33 void SaveDriverValues();
34 void SaveOverlayValues();
33 void SaveHidbusValues() override {} 35 void SaveHidbusValues() override {}
34 void SaveDebugControlValues() override {} 36 void SaveDebugControlValues() override {}
35 void SavePathValues() override; 37 void SavePathValues() override;
diff --git a/src/android/app/src/main/jni/android_settings.h b/src/android/app/src/main/jni/android_settings.h
index 1e4906b9a..559ae83eb 100644
--- a/src/android/app/src/main/jni/android_settings.h
+++ b/src/android/app/src/main/jni/android_settings.h
@@ -14,6 +14,14 @@ struct GameDir {
14 bool deep_scan = false; 14 bool deep_scan = false;
15}; 15};
16 16
17struct OverlayControlData {
18 std::string id;
19 bool enabled;
20 std::pair<double, double> landscape_position;
21 std::pair<double, double> portrait_position;
22 std::pair<double, double> foldable_position;
23};
24
17struct Values { 25struct Values {
18 Settings::Linkage linkage; 26 Settings::Linkage linkage;
19 27
@@ -38,6 +46,23 @@ struct Values {
38 Settings::Setting<s32> theme_mode{linkage, -1, "theme_mode", Settings::Category::Android}; 46 Settings::Setting<s32> theme_mode{linkage, -1, "theme_mode", Settings::Category::Android};
39 Settings::Setting<bool> black_backgrounds{linkage, false, "black_backgrounds", 47 Settings::Setting<bool> black_backgrounds{linkage, false, "black_backgrounds",
40 Settings::Category::Android}; 48 Settings::Category::Android};
49
50 // Input/performance overlay settings
51 std::vector<OverlayControlData> overlay_control_data;
52 Settings::Setting<s32> overlay_scale{linkage, 50, "control_scale", Settings::Category::Overlay};
53 Settings::Setting<s32> overlay_opacity{linkage, 100, "control_opacity",
54 Settings::Category::Overlay};
55
56 Settings::Setting<bool> joystick_rel_center{linkage, true, "joystick_rel_center",
57 Settings::Category::Overlay};
58 Settings::Setting<bool> dpad_slide{linkage, true, "dpad_slide", Settings::Category::Overlay};
59 Settings::Setting<bool> haptic_feedback{linkage, true, "haptic_feedback",
60 Settings::Category::Overlay};
61 Settings::Setting<bool> show_performance_overlay{linkage, true, "show_performance_overlay",
62 Settings::Category::Overlay};
63 Settings::Setting<bool> show_input_overlay{linkage, true, "show_input_overlay",
64 Settings::Category::Overlay};
65 Settings::Setting<bool> touchscreen{linkage, true, "touchscreen", Settings::Category::Overlay};
41}; 66};
42 67
43extern Values values; 68extern Values values;
diff --git a/src/android/app/src/main/jni/id_cache.cpp b/src/android/app/src/main/jni/id_cache.cpp
index e7a86d3fd..c79ad7d76 100644
--- a/src/android/app/src/main/jni/id_cache.cpp
+++ b/src/android/app/src/main/jni/id_cache.cpp
@@ -35,6 +35,18 @@ static jmethodID s_pair_constructor;
35static jfieldID s_pair_first_field; 35static jfieldID s_pair_first_field;
36static jfieldID s_pair_second_field; 36static jfieldID s_pair_second_field;
37 37
38static jclass s_overlay_control_data_class;
39static jmethodID s_overlay_control_data_constructor;
40static jfieldID s_overlay_control_data_id_field;
41static jfieldID s_overlay_control_data_enabled_field;
42static jfieldID s_overlay_control_data_landscape_position_field;
43static jfieldID s_overlay_control_data_portrait_position_field;
44static jfieldID s_overlay_control_data_foldable_position_field;
45
46static jclass s_double_class;
47static jmethodID s_double_constructor;
48static jfieldID s_double_value_field;
49
38static constexpr jint JNI_VERSION = JNI_VERSION_1_6; 50static constexpr jint JNI_VERSION = JNI_VERSION_1_6;
39 51
40namespace IDCache { 52namespace IDCache {
@@ -146,6 +158,46 @@ jfieldID GetPairSecondField() {
146 return s_pair_second_field; 158 return s_pair_second_field;
147} 159}
148 160
161jclass GetOverlayControlDataClass() {
162 return s_overlay_control_data_class;
163}
164
165jmethodID GetOverlayControlDataConstructor() {
166 return s_overlay_control_data_constructor;
167}
168
169jfieldID GetOverlayControlDataIdField() {
170 return s_overlay_control_data_id_field;
171}
172
173jfieldID GetOverlayControlDataEnabledField() {
174 return s_overlay_control_data_enabled_field;
175}
176
177jfieldID GetOverlayControlDataLandscapePositionField() {
178 return s_overlay_control_data_landscape_position_field;
179}
180
181jfieldID GetOverlayControlDataPortraitPositionField() {
182 return s_overlay_control_data_portrait_position_field;
183}
184
185jfieldID GetOverlayControlDataFoldablePositionField() {
186 return s_overlay_control_data_foldable_position_field;
187}
188
189jclass GetDoubleClass() {
190 return s_double_class;
191}
192
193jmethodID GetDoubleConstructor() {
194 return s_double_constructor;
195}
196
197jfieldID GetDoubleValueField() {
198 return s_double_value_field;
199}
200
149} // namespace IDCache 201} // namespace IDCache
150 202
151#ifdef __cplusplus 203#ifdef __cplusplus
@@ -207,6 +259,31 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) {
207 s_pair_second_field = env->GetFieldID(pair_class, "second", "Ljava/lang/Object;"); 259 s_pair_second_field = env->GetFieldID(pair_class, "second", "Ljava/lang/Object;");
208 env->DeleteLocalRef(pair_class); 260 env->DeleteLocalRef(pair_class);
209 261
262 const jclass overlay_control_data_class =
263 env->FindClass("org/yuzu/yuzu_emu/overlay/model/OverlayControlData");
264 s_overlay_control_data_class =
265 reinterpret_cast<jclass>(env->NewGlobalRef(overlay_control_data_class));
266 s_overlay_control_data_constructor =
267 env->GetMethodID(overlay_control_data_class, "<init>",
268 "(Ljava/lang/String;ZLkotlin/Pair;Lkotlin/Pair;Lkotlin/Pair;)V");
269 s_overlay_control_data_id_field =
270 env->GetFieldID(overlay_control_data_class, "id", "Ljava/lang/String;");
271 s_overlay_control_data_enabled_field =
272 env->GetFieldID(overlay_control_data_class, "enabled", "Z");
273 s_overlay_control_data_landscape_position_field =
274 env->GetFieldID(overlay_control_data_class, "landscapePosition", "Lkotlin/Pair;");
275 s_overlay_control_data_portrait_position_field =
276 env->GetFieldID(overlay_control_data_class, "portraitPosition", "Lkotlin/Pair;");
277 s_overlay_control_data_foldable_position_field =
278 env->GetFieldID(overlay_control_data_class, "foldablePosition", "Lkotlin/Pair;");
279 env->DeleteLocalRef(overlay_control_data_class);
280
281 const jclass double_class = env->FindClass("java/lang/Double");
282 s_double_class = reinterpret_cast<jclass>(env->NewGlobalRef(double_class));
283 s_double_constructor = env->GetMethodID(double_class, "<init>", "(D)V");
284 s_double_value_field = env->GetFieldID(double_class, "value", "D");
285 env->DeleteLocalRef(double_class);
286
210 // Initialize Android Storage 287 // Initialize Android Storage
211 Common::FS::Android::RegisterCallbacks(env, s_native_library_class); 288 Common::FS::Android::RegisterCallbacks(env, s_native_library_class);
212 289
@@ -231,6 +308,8 @@ void JNI_OnUnload(JavaVM* vm, void* reserved) {
231 env->DeleteGlobalRef(s_game_class); 308 env->DeleteGlobalRef(s_game_class);
232 env->DeleteGlobalRef(s_string_class); 309 env->DeleteGlobalRef(s_string_class);
233 env->DeleteGlobalRef(s_pair_class); 310 env->DeleteGlobalRef(s_pair_class);
311 env->DeleteGlobalRef(s_overlay_control_data_class);
312 env->DeleteGlobalRef(s_double_class);
234 313
235 // UnInitialize applets 314 // UnInitialize applets
236 SoftwareKeyboard::CleanupJNI(env); 315 SoftwareKeyboard::CleanupJNI(env);
diff --git a/src/android/app/src/main/jni/id_cache.h b/src/android/app/src/main/jni/id_cache.h
index 24030be42..784d1412f 100644
--- a/src/android/app/src/main/jni/id_cache.h
+++ b/src/android/app/src/main/jni/id_cache.h
@@ -35,4 +35,16 @@ jmethodID GetPairConstructor();
35jfieldID GetPairFirstField(); 35jfieldID GetPairFirstField();
36jfieldID GetPairSecondField(); 36jfieldID GetPairSecondField();
37 37
38jclass GetOverlayControlDataClass();
39jmethodID GetOverlayControlDataConstructor();
40jfieldID GetOverlayControlDataIdField();
41jfieldID GetOverlayControlDataEnabledField();
42jfieldID GetOverlayControlDataLandscapePositionField();
43jfieldID GetOverlayControlDataPortraitPositionField();
44jfieldID GetOverlayControlDataFoldablePositionField();
45
46jclass GetDoubleClass();
47jmethodID GetDoubleConstructor();
48jfieldID GetDoubleValueField();
49
38} // namespace IDCache 50} // namespace IDCache
diff --git a/src/android/app/src/main/jni/native_config.cpp b/src/android/app/src/main/jni/native_config.cpp
index 324d9e9cd..535902483 100644
--- a/src/android/app/src/main/jni/native_config.cpp
+++ b/src/android/app/src/main/jni/native_config.cpp
@@ -344,4 +344,74 @@ void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setDisabledAddons(JNIEnv* env, j
344 Settings::values.disabled_addons[program_id] = disabled_addons; 344 Settings::values.disabled_addons[program_id] = disabled_addons;
345} 345}
346 346
347jobjectArray Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getOverlayControlData(JNIEnv* env,
348 jobject obj) {
349 jobjectArray joverlayControlDataArray =
350 env->NewObjectArray(AndroidSettings::values.overlay_control_data.size(),
351 IDCache::GetOverlayControlDataClass(), nullptr);
352 for (size_t i = 0; i < AndroidSettings::values.overlay_control_data.size(); ++i) {
353 const auto& control_data = AndroidSettings::values.overlay_control_data[i];
354 jobject jlandscapePosition =
355 env->NewObject(IDCache::GetPairClass(), IDCache::GetPairConstructor(),
356 ToJDouble(env, control_data.landscape_position.first),
357 ToJDouble(env, control_data.landscape_position.second));
358 jobject jportraitPosition =
359 env->NewObject(IDCache::GetPairClass(), IDCache::GetPairConstructor(),
360 ToJDouble(env, control_data.portrait_position.first),
361 ToJDouble(env, control_data.portrait_position.second));
362 jobject jfoldablePosition =
363 env->NewObject(IDCache::GetPairClass(), IDCache::GetPairConstructor(),
364 ToJDouble(env, control_data.foldable_position.first),
365 ToJDouble(env, control_data.foldable_position.second));
366
367 jobject jcontrolData = env->NewObject(
368 IDCache::GetOverlayControlDataClass(), IDCache::GetOverlayControlDataConstructor(),
369 ToJString(env, control_data.id), control_data.enabled, jlandscapePosition,
370 jportraitPosition, jfoldablePosition);
371 env->SetObjectArrayElement(joverlayControlDataArray, i, jcontrolData);
372 }
373 return joverlayControlDataArray;
374}
375
376void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setOverlayControlData(
377 JNIEnv* env, jobject obj, jobjectArray joverlayControlDataArray) {
378 AndroidSettings::values.overlay_control_data.clear();
379 int size = env->GetArrayLength(joverlayControlDataArray);
380
381 if (size == 0) {
382 return;
383 }
384
385 for (int i = 0; i < size; ++i) {
386 jobject joverlayControlData = env->GetObjectArrayElement(joverlayControlDataArray, i);
387 jstring jidString = static_cast<jstring>(
388 env->GetObjectField(joverlayControlData, IDCache::GetOverlayControlDataIdField()));
389 bool enabled = static_cast<bool>(env->GetBooleanField(
390 joverlayControlData, IDCache::GetOverlayControlDataEnabledField()));
391
392 jobject jlandscapePosition = env->GetObjectField(
393 joverlayControlData, IDCache::GetOverlayControlDataLandscapePositionField());
394 std::pair<double, double> landscape_position = std::make_pair(
395 GetJDouble(env, env->GetObjectField(jlandscapePosition, IDCache::GetPairFirstField())),
396 GetJDouble(env,
397 env->GetObjectField(jlandscapePosition, IDCache::GetPairSecondField())));
398
399 jobject jportraitPosition = env->GetObjectField(
400 joverlayControlData, IDCache::GetOverlayControlDataPortraitPositionField());
401 std::pair<double, double> portrait_position = std::make_pair(
402 GetJDouble(env, env->GetObjectField(jportraitPosition, IDCache::GetPairFirstField())),
403 GetJDouble(env, env->GetObjectField(jportraitPosition, IDCache::GetPairSecondField())));
404
405 jobject jfoldablePosition = env->GetObjectField(
406 joverlayControlData, IDCache::GetOverlayControlDataFoldablePositionField());
407 std::pair<double, double> foldable_position = std::make_pair(
408 GetJDouble(env, env->GetObjectField(jfoldablePosition, IDCache::GetPairFirstField())),
409 GetJDouble(env, env->GetObjectField(jfoldablePosition, IDCache::GetPairSecondField())));
410
411 AndroidSettings::values.overlay_control_data.push_back(AndroidSettings::OverlayControlData{
412 GetJString(env, jidString), enabled, landscape_position, portrait_position,
413 foldable_position});
414 }
415}
416
347} // extern "C" 417} // extern "C"
diff --git a/src/android/app/src/main/res/values/arrays.xml b/src/android/app/src/main/res/values/arrays.xml
index c882a8e62..45d57c3ea 100644
--- a/src/android/app/src/main/res/values/arrays.xml
+++ b/src/android/app/src/main/res/values/arrays.xml
@@ -212,19 +212,19 @@
212 <item>B</item> 212 <item>B</item>
213 <item>X</item> 213 <item>X</item>
214 <item>Y</item> 214 <item>Y</item>
215 <item>+</item>
216 <item>-</item>
217 <item>@string/gamepad_home</item>
218 <item>@string/gamepad_screenshot</item>
215 <item>L</item> 219 <item>L</item>
216 <item>R</item> 220 <item>R</item>
217 <item>ZL</item> 221 <item>ZL</item>
218 <item>ZR</item> 222 <item>ZR</item>
219 <item>+</item>
220 <item>-</item>
221 <item>@string/gamepad_d_pad</item>
222 <item>@string/gamepad_left_stick</item> 223 <item>@string/gamepad_left_stick</item>
223 <item>@string/gamepad_right_stick</item> 224 <item>@string/gamepad_right_stick</item>
224 <item>L3</item> 225 <item>L3</item>
225 <item>R3</item> 226 <item>R3</item>
226 <item>@string/gamepad_home</item> 227 <item>@string/gamepad_d_pad</item>
227 <item>@string/gamepad_screenshot</item>
228 </string-array> 228 </string-array>
229 229
230 <string-array name="themeEntries"> 230 <string-array name="themeEntries">
diff --git a/src/android/app/src/main/res/values/integers.xml b/src/android/app/src/main/res/values/integers.xml
index dc527965c..1c6f5db93 100644
--- a/src/android/app/src/main/res/values/integers.xml
+++ b/src/android/app/src/main/res/values/integers.xml
@@ -3,111 +3,111 @@
3 <integer name="grid_columns">1</integer> 3 <integer name="grid_columns">1</integer>
4 4
5 <!-- Default SWITCH landscape layout --> 5 <!-- Default SWITCH landscape layout -->
6 <integer name="SWITCH_BUTTON_A_X">760</integer> 6 <integer name="BUTTON_A_X">760</integer>
7 <integer name="SWITCH_BUTTON_A_Y">790</integer> 7 <integer name="BUTTON_A_Y">790</integer>
8 <integer name="SWITCH_BUTTON_B_X">710</integer> 8 <integer name="BUTTON_B_X">710</integer>
9 <integer name="SWITCH_BUTTON_B_Y">900</integer> 9 <integer name="BUTTON_B_Y">900</integer>
10 <integer name="SWITCH_BUTTON_X_X">710</integer> 10 <integer name="BUTTON_X_X">710</integer>
11 <integer name="SWITCH_BUTTON_X_Y">680</integer> 11 <integer name="BUTTON_X_Y">680</integer>
12 <integer name="SWITCH_BUTTON_Y_X">660</integer> 12 <integer name="BUTTON_Y_X">660</integer>
13 <integer name="SWITCH_BUTTON_Y_Y">790</integer> 13 <integer name="BUTTON_Y_Y">790</integer>
14 <integer name="SWITCH_STICK_L_X">100</integer> 14 <integer name="BUTTON_PLUS_X">540</integer>
15 <integer name="SWITCH_STICK_L_Y">670</integer> 15 <integer name="BUTTON_PLUS_Y">950</integer>
16 <integer name="SWITCH_STICK_R_X">900</integer> 16 <integer name="BUTTON_MINUS_X">460</integer>
17 <integer name="SWITCH_STICK_R_Y">670</integer> 17 <integer name="BUTTON_MINUS_Y">950</integer>
18 <integer name="SWITCH_TRIGGER_L_X">70</integer> 18 <integer name="BUTTON_HOME_X">600</integer>
19 <integer name="SWITCH_TRIGGER_L_Y">220</integer> 19 <integer name="BUTTON_HOME_Y">950</integer>
20 <integer name="SWITCH_TRIGGER_R_X">930</integer> 20 <integer name="BUTTON_CAPTURE_X">400</integer>
21 <integer name="SWITCH_TRIGGER_R_Y">220</integer> 21 <integer name="BUTTON_CAPTURE_Y">950</integer>
22 <integer name="SWITCH_TRIGGER_ZL_X">70</integer> 22 <integer name="BUTTON_L_X">70</integer>
23 <integer name="SWITCH_TRIGGER_ZL_Y">90</integer> 23 <integer name="BUTTON_L_Y">220</integer>
24 <integer name="SWITCH_TRIGGER_ZR_X">930</integer> 24 <integer name="BUTTON_R_X">930</integer>
25 <integer name="SWITCH_TRIGGER_ZR_Y">90</integer> 25 <integer name="BUTTON_R_Y">220</integer>
26 <integer name="SWITCH_BUTTON_MINUS_X">460</integer> 26 <integer name="BUTTON_ZL_X">70</integer>
27 <integer name="SWITCH_BUTTON_MINUS_Y">950</integer> 27 <integer name="BUTTON_ZL_Y">90</integer>
28 <integer name="SWITCH_BUTTON_PLUS_X">540</integer> 28 <integer name="BUTTON_ZR_X">930</integer>
29 <integer name="SWITCH_BUTTON_PLUS_Y">950</integer> 29 <integer name="BUTTON_ZR_Y">90</integer>
30 <integer name="SWITCH_BUTTON_HOME_X">600</integer> 30 <integer name="BUTTON_STICK_L_X">870</integer>
31 <integer name="SWITCH_BUTTON_HOME_Y">950</integer> 31 <integer name="BUTTON_STICK_L_Y">400</integer>
32 <integer name="SWITCH_BUTTON_CAPTURE_X">400</integer> 32 <integer name="BUTTON_STICK_R_X">960</integer>
33 <integer name="SWITCH_BUTTON_CAPTURE_Y">950</integer> 33 <integer name="BUTTON_STICK_R_Y">430</integer>
34 <integer name="SWITCH_BUTTON_DPAD_X">260</integer> 34 <integer name="STICK_L_X">100</integer>
35 <integer name="SWITCH_BUTTON_DPAD_Y">790</integer> 35 <integer name="STICK_L_Y">670</integer>
36 <integer name="SWITCH_BUTTON_STICK_L_X">870</integer> 36 <integer name="STICK_R_X">900</integer>
37 <integer name="SWITCH_BUTTON_STICK_L_Y">400</integer> 37 <integer name="STICK_R_Y">670</integer>
38 <integer name="SWITCH_BUTTON_STICK_R_X">960</integer> 38 <integer name="COMBINED_DPAD_X">260</integer>
39 <integer name="SWITCH_BUTTON_STICK_R_Y">430</integer> 39 <integer name="COMBINED_DPAD_Y">790</integer>
40 40
41 <!-- Default SWITCH portrait layout --> 41 <!-- Default SWITCH portrait layout -->
42 <integer name="SWITCH_BUTTON_A_X_PORTRAIT">840</integer> 42 <integer name="BUTTON_A_X_PORTRAIT">840</integer>
43 <integer name="SWITCH_BUTTON_A_Y_PORTRAIT">840</integer> 43 <integer name="BUTTON_A_Y_PORTRAIT">840</integer>
44 <integer name="SWITCH_BUTTON_B_X_PORTRAIT">740</integer> 44 <integer name="BUTTON_B_X_PORTRAIT">740</integer>
45 <integer name="SWITCH_BUTTON_B_Y_PORTRAIT">880</integer> 45 <integer name="BUTTON_B_Y_PORTRAIT">880</integer>
46 <integer name="SWITCH_BUTTON_X_X_PORTRAIT">740</integer> 46 <integer name="BUTTON_X_X_PORTRAIT">740</integer>
47 <integer name="SWITCH_BUTTON_X_Y_PORTRAIT">800</integer> 47 <integer name="BUTTON_X_Y_PORTRAIT">800</integer>
48 <integer name="SWITCH_BUTTON_Y_X_PORTRAIT">640</integer> 48 <integer name="BUTTON_Y_X_PORTRAIT">640</integer>
49 <integer name="SWITCH_BUTTON_Y_Y_PORTRAIT">840</integer> 49 <integer name="BUTTON_Y_Y_PORTRAIT">840</integer>
50 <integer name="SWITCH_STICK_L_X_PORTRAIT">180</integer> 50 <integer name="BUTTON_PLUS_Y_PORTRAIT">950</integer>
51 <integer name="SWITCH_STICK_L_Y_PORTRAIT">660</integer> 51 <integer name="BUTTON_MINUS_X_PORTRAIT">440</integer>
52 <integer name="SWITCH_STICK_R_X_PORTRAIT">820</integer> 52 <integer name="BUTTON_MINUS_Y_PORTRAIT">950</integer>
53 <integer name="SWITCH_STICK_R_Y_PORTRAIT">660</integer> 53 <integer name="BUTTON_HOME_X_PORTRAIT">680</integer>
54 <integer name="SWITCH_TRIGGER_L_X_PORTRAIT">140</integer> 54 <integer name="BUTTON_HOME_Y_PORTRAIT">950</integer>
55 <integer name="SWITCH_TRIGGER_L_Y_PORTRAIT">260</integer> 55 <integer name="BUTTON_CAPTURE_X_PORTRAIT">320</integer>
56 <integer name="SWITCH_TRIGGER_R_X_PORTRAIT">860</integer> 56 <integer name="BUTTON_CAPTURE_Y_PORTRAIT">950</integer>
57 <integer name="SWITCH_TRIGGER_R_Y_PORTRAIT">260</integer> 57 <integer name="BUTTON_L_X_PORTRAIT">140</integer>
58 <integer name="SWITCH_TRIGGER_ZL_X_PORTRAIT">140</integer> 58 <integer name="BUTTON_L_Y_PORTRAIT">260</integer>
59 <integer name="SWITCH_TRIGGER_ZL_Y_PORTRAIT">200</integer> 59 <integer name="BUTTON_R_X_PORTRAIT">860</integer>
60 <integer name="SWITCH_TRIGGER_ZR_X_PORTRAIT">860</integer> 60 <integer name="BUTTON_R_Y_PORTRAIT">260</integer>
61 <integer name="SWITCH_TRIGGER_ZR_Y_PORTRAIT">200</integer> 61 <integer name="BUTTON_ZL_X_PORTRAIT">140</integer>
62 <integer name="SWITCH_BUTTON_MINUS_X_PORTRAIT">440</integer> 62 <integer name="BUTTON_ZL_Y_PORTRAIT">200</integer>
63 <integer name="SWITCH_BUTTON_MINUS_Y_PORTRAIT">950</integer> 63 <integer name="BUTTON_ZR_X_PORTRAIT">860</integer>
64 <integer name="SWITCH_BUTTON_PLUS_X_PORTRAIT">560</integer> 64 <integer name="BUTTON_ZR_Y_PORTRAIT">200</integer>
65 <integer name="SWITCH_BUTTON_PLUS_Y_PORTRAIT">950</integer> 65 <integer name="BUTTON_PLUS_X_PORTRAIT">560</integer>
66 <integer name="SWITCH_BUTTON_HOME_X_PORTRAIT">680</integer> 66 <integer name="BUTTON_STICK_L_X_PORTRAIT">730</integer>
67 <integer name="SWITCH_BUTTON_HOME_Y_PORTRAIT">950</integer> 67 <integer name="BUTTON_STICK_L_Y_PORTRAIT">510</integer>
68 <integer name="SWITCH_BUTTON_CAPTURE_X_PORTRAIT">320</integer> 68 <integer name="BUTTON_STICK_R_X_PORTRAIT">900</integer>
69 <integer name="SWITCH_BUTTON_CAPTURE_Y_PORTRAIT">950</integer> 69 <integer name="BUTTON_STICK_R_Y_PORTRAIT">540</integer>
70 <integer name="SWITCH_BUTTON_DPAD_X_PORTRAIT">240</integer> 70 <integer name="STICK_L_X_PORTRAIT">180</integer>
71 <integer name="SWITCH_BUTTON_DPAD_Y_PORTRAIT">840</integer> 71 <integer name="STICK_L_Y_PORTRAIT">660</integer>
72 <integer name="SWITCH_BUTTON_STICK_L_X_PORTRAIT">730</integer> 72 <integer name="STICK_R_X_PORTRAIT">820</integer>
73 <integer name="SWITCH_BUTTON_STICK_L_Y_PORTRAIT">510</integer> 73 <integer name="STICK_R_Y_PORTRAIT">660</integer>
74 <integer name="SWITCH_BUTTON_STICK_R_X_PORTRAIT">900</integer> 74 <integer name="COMBINED_DPAD_X_PORTRAIT">240</integer>
75 <integer name="SWITCH_BUTTON_STICK_R_Y_PORTRAIT">540</integer> 75 <integer name="COMBINED_DPAD_Y_PORTRAIT">840</integer>
76 76
77 <!-- Default SWITCH foldable layout --> 77 <!-- Default SWITCH foldable layout -->
78 <integer name="SWITCH_BUTTON_A_X_FOLDABLE">840</integer> 78 <integer name="BUTTON_A_X_FOLDABLE">840</integer>
79 <integer name="SWITCH_BUTTON_A_Y_FOLDABLE">390</integer> 79 <integer name="BUTTON_A_Y_FOLDABLE">390</integer>
80 <integer name="SWITCH_BUTTON_B_X_FOLDABLE">740</integer> 80 <integer name="BUTTON_B_X_FOLDABLE">740</integer>
81 <integer name="SWITCH_BUTTON_B_Y_FOLDABLE">430</integer> 81 <integer name="BUTTON_B_Y_FOLDABLE">430</integer>
82 <integer name="SWITCH_BUTTON_X_X_FOLDABLE">740</integer> 82 <integer name="BUTTON_X_X_FOLDABLE">740</integer>
83 <integer name="SWITCH_BUTTON_X_Y_FOLDABLE">350</integer> 83 <integer name="BUTTON_X_Y_FOLDABLE">350</integer>
84 <integer name="SWITCH_BUTTON_Y_X_FOLDABLE">640</integer> 84 <integer name="BUTTON_Y_X_FOLDABLE">640</integer>
85 <integer name="SWITCH_BUTTON_Y_Y_FOLDABLE">390</integer> 85 <integer name="BUTTON_Y_Y_FOLDABLE">390</integer>
86 <integer name="SWITCH_STICK_L_X_FOLDABLE">180</integer> 86 <integer name="BUTTON_PLUS_X_FOLDABLE">560</integer>
87 <integer name="SWITCH_STICK_L_Y_FOLDABLE">250</integer> 87 <integer name="BUTTON_PLUS_Y_FOLDABLE">470</integer>
88 <integer name="SWITCH_STICK_R_X_FOLDABLE">820</integer> 88 <integer name="BUTTON_MINUS_X_FOLDABLE">440</integer>
89 <integer name="SWITCH_STICK_R_Y_FOLDABLE">250</integer> 89 <integer name="BUTTON_MINUS_Y_FOLDABLE">470</integer>
90 <integer name="SWITCH_TRIGGER_L_X_FOLDABLE">140</integer> 90 <integer name="BUTTON_HOME_X_FOLDABLE">680</integer>
91 <integer name="SWITCH_TRIGGER_L_Y_FOLDABLE">130</integer> 91 <integer name="BUTTON_HOME_Y_FOLDABLE">470</integer>
92 <integer name="SWITCH_TRIGGER_R_X_FOLDABLE">860</integer> 92 <integer name="BUTTON_CAPTURE_X_FOLDABLE">320</integer>
93 <integer name="SWITCH_TRIGGER_R_Y_FOLDABLE">130</integer> 93 <integer name="BUTTON_CAPTURE_Y_FOLDABLE">470</integer>
94 <integer name="SWITCH_TRIGGER_ZL_X_FOLDABLE">140</integer> 94 <integer name="BUTTON_L_X_FOLDABLE">140</integer>
95 <integer name="SWITCH_TRIGGER_ZL_Y_FOLDABLE">70</integer> 95 <integer name="BUTTON_L_Y_FOLDABLE">130</integer>
96 <integer name="SWITCH_TRIGGER_ZR_X_FOLDABLE">860</integer> 96 <integer name="BUTTON_R_X_FOLDABLE">860</integer>
97 <integer name="SWITCH_TRIGGER_ZR_Y_FOLDABLE">70</integer> 97 <integer name="BUTTON_R_Y_FOLDABLE">130</integer>
98 <integer name="SWITCH_BUTTON_MINUS_X_FOLDABLE">440</integer> 98 <integer name="BUTTON_ZL_X_FOLDABLE">140</integer>
99 <integer name="SWITCH_BUTTON_MINUS_Y_FOLDABLE">470</integer> 99 <integer name="BUTTON_ZL_Y_FOLDABLE">70</integer>
100 <integer name="SWITCH_BUTTON_PLUS_X_FOLDABLE">560</integer> 100 <integer name="BUTTON_ZR_X_FOLDABLE">860</integer>
101 <integer name="SWITCH_BUTTON_PLUS_Y_FOLDABLE">470</integer> 101 <integer name="BUTTON_ZR_Y_FOLDABLE">70</integer>
102 <integer name="SWITCH_BUTTON_HOME_X_FOLDABLE">680</integer> 102 <integer name="BUTTON_STICK_L_X_FOLDABLE">550</integer>
103 <integer name="SWITCH_BUTTON_HOME_Y_FOLDABLE">470</integer> 103 <integer name="BUTTON_STICK_L_Y_FOLDABLE">210</integer>
104 <integer name="SWITCH_BUTTON_CAPTURE_X_FOLDABLE">320</integer> 104 <integer name="BUTTON_STICK_R_X_FOLDABLE">550</integer>
105 <integer name="SWITCH_BUTTON_CAPTURE_Y_FOLDABLE">470</integer> 105 <integer name="BUTTON_STICK_R_Y_FOLDABLE">280</integer>
106 <integer name="SWITCH_BUTTON_DPAD_X_FOLDABLE">240</integer> 106 <integer name="STICK_L_X_FOLDABLE">180</integer>
107 <integer name="SWITCH_BUTTON_DPAD_Y_FOLDABLE">390</integer> 107 <integer name="STICK_L_Y_FOLDABLE">250</integer>
108 <integer name="SWITCH_BUTTON_STICK_L_X_FOLDABLE">550</integer> 108 <integer name="STICK_R_X_FOLDABLE">820</integer>
109 <integer name="SWITCH_BUTTON_STICK_L_Y_FOLDABLE">210</integer> 109 <integer name="STICK_R_Y_FOLDABLE">250</integer>
110 <integer name="SWITCH_BUTTON_STICK_R_X_FOLDABLE">550</integer> 110 <integer name="COMBINED_DPAD_X_FOLDABLE">240</integer>
111 <integer name="SWITCH_BUTTON_STICK_R_Y_FOLDABLE">280</integer> 111 <integer name="COMBINED_DPAD_Y_FOLDABLE">390</integer>
112 112
113</resources> 113</resources>
diff --git a/src/common/settings.cpp b/src/common/settings.cpp
index ea52bbfa6..07709d4e5 100644
--- a/src/common/settings.cpp
+++ b/src/common/settings.cpp
@@ -199,6 +199,8 @@ const char* TranslateCategory(Category category) {
199 case Category::CpuDebug: 199 case Category::CpuDebug:
200 case Category::CpuUnsafe: 200 case Category::CpuUnsafe:
201 return "Cpu"; 201 return "Cpu";
202 case Category::Overlay:
203 return "Overlay";
202 case Category::Renderer: 204 case Category::Renderer:
203 case Category::RendererAdvanced: 205 case Category::RendererAdvanced:
204 case Category::RendererDebug: 206 case Category::RendererDebug:
diff --git a/src/common/settings_common.h b/src/common/settings_common.h
index c82e17495..1a290ad77 100644
--- a/src/common/settings_common.h
+++ b/src/common/settings_common.h
@@ -18,6 +18,7 @@ enum class Category : u32 {
18 Cpu, 18 Cpu,
19 CpuDebug, 19 CpuDebug,
20 CpuUnsafe, 20 CpuUnsafe,
21 Overlay,
21 Renderer, 22 Renderer,
22 RendererAdvanced, 23 RendererAdvanced,
23 RendererDebug, 24 RendererDebug,