summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/android/app/build.gradle.kts2
-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.kt9
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt6
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt38
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt74
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt96
-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.kt175
-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/java/org/yuzu/yuzu_emu/utils/PreferenceUtil.kt37
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ThemeHelper.kt38
-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.cpp67
-rw-r--r--src/android/app/src/main/jni/android_config.h2
-rw-r--r--src/android/app/src/main/jni/android_settings.h30
-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/menu/menu_overlay_options.xml5
-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/android/app/src/main/res/values/strings.xml1
-rw-r--r--src/android/build.gradle.kts2
-rw-r--r--src/common/settings.cpp2
-rw-r--r--src/common/settings_common.h1
-rw-r--r--src/frontend_common/config.cpp194
-rw-r--r--src/frontend_common/config.h33
-rw-r--r--src/yuzu/configuration/qt_config.cpp69
-rw-r--r--src/yuzu_cmd/sdl_config.cpp26
38 files changed, 1545 insertions, 1044 deletions
diff --git a/src/android/app/build.gradle.kts b/src/android/app/build.gradle.kts
index f763c657e..53aafa08c 100644
--- a/src/android/app/build.gradle.kts
+++ b/src/android/app/build.gradle.kts
@@ -10,7 +10,7 @@ plugins {
10 id("com.android.application") 10 id("com.android.application")
11 id("org.jetbrains.kotlin.android") 11 id("org.jetbrains.kotlin.android")
12 id("kotlin-parcelize") 12 id("kotlin-parcelize")
13 kotlin("plugin.serialization") version "1.8.21" 13 kotlin("plugin.serialization") version "1.9.20"
14 id("androidx.navigation.safeargs.kotlin") 14 id("androidx.navigation.safeargs.kotlin")
15 id("org.jlleitschuh.gradle.ktlint") version "11.4.0" 15 id("org.jlleitschuh.gradle.ktlint") version "11.4.0"
16} 16}
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 16f06cd0a..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
@@ -18,7 +18,14 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
18 RENDERER_REACTIVE_FLUSHING("use_reactive_flushing"), 18 RENDERER_REACTIVE_FLUSHING("use_reactive_flushing"),
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"),
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");
22 29
23 override fun getBoolean(needsGlobal: Boolean): Boolean = 30 override fun getBoolean(needsGlobal: Boolean): Boolean =
24 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 df760440f..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
@@ -19,7 +19,11 @@ enum class IntSetting(override val key: String) : AbstractIntSetting {
19 RENDERER_SCREEN_LAYOUT("screen_layout"), 19 RENDERER_SCREEN_LAYOUT("screen_layout"),
20 RENDERER_ASPECT_RATIO("aspect_ratio"), 20 RENDERER_ASPECT_RATIO("aspect_ratio"),
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"),
24 THEME_MODE("theme_mode"),
25 OVERLAY_SCALE("control_scale"),
26 OVERLAY_OPACITY("control_opacity");
23 27
24 override fun getInt(needsGlobal: Boolean): Int = NativeConfig.getInt(key, needsGlobal) 28 override fun getInt(needsGlobal: Boolean): Int = NativeConfig.getInt(key, needsGlobal)
25 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 9551fc05e..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,23 +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 const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch"
58 const val PREF_THEME = "Theme"
59 const val PREF_THEME_MODE = "ThemeMode"
60 const val PREF_BLACK_BACKGROUNDS = "BlackBackgrounds"
61
62 val overlayPreferences = listOf( 47 val overlayPreferences = listOf(
63 PREF_OVERLAY_VERSION,
64 PREF_CONTROL_SCALE,
65 PREF_CONTROL_OPACITY,
66 PREF_TOUCH_ENABLED,
67 PREF_BUTTON_A, 48 PREF_BUTTON_A,
68 PREF_BUTTON_B, 49 PREF_BUTTON_B,
69 PREF_BUTTON_X, 50 PREF_BUTTON_X,
@@ -83,6 +64,21 @@ object Settings {
83 PREF_BUTTON_STICK_R 64 PREF_BUTTON_STICK_R
84 ) 65 )
85 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
86 const val LayoutOption_Unspecified = 0 82 const val LayoutOption_Unspecified = 0
87 const val LayoutOption_MobilePortrait = 4 83 const val LayoutOption_MobilePortrait = 4
88 const val LayoutOption_MobileLandscape = 5 84 const val LayoutOption_MobileLandscape = 5
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt
index db1a1076c..2ad2f4966 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt
@@ -3,10 +3,8 @@
3 3
4package org.yuzu.yuzu_emu.features.settings.ui 4package org.yuzu.yuzu_emu.features.settings.ui
5 5
6import android.content.SharedPreferences
7import android.os.Build 6import android.os.Build
8import android.widget.Toast 7import android.widget.Toast
9import androidx.preference.PreferenceManager
10import org.yuzu.yuzu_emu.NativeLibrary 8import org.yuzu.yuzu_emu.NativeLibrary
11import org.yuzu.yuzu_emu.R 9import org.yuzu.yuzu_emu.R
12import org.yuzu.yuzu_emu.YuzuApplication 10import org.yuzu.yuzu_emu.YuzuApplication
@@ -29,9 +27,6 @@ class SettingsFragmentPresenter(
29) { 27) {
30 private var settingsList = ArrayList<SettingsItem>() 28 private var settingsList = ArrayList<SettingsItem>()
31 29
32 private val preferences: SharedPreferences
33 get() = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
34
35 // Extension for altering settings list based on each setting's properties 30 // Extension for altering settings list based on each setting's properties
36 fun ArrayList<SettingsItem>.add(key: String) { 31 fun ArrayList<SettingsItem>.add(key: String) {
37 val item = SettingsItem.settingsItems[key]!! 32 val item = SettingsItem.settingsItems[key]!!
@@ -170,25 +165,19 @@ class SettingsFragmentPresenter(
170 private fun addThemeSettings(sl: ArrayList<SettingsItem>) { 165 private fun addThemeSettings(sl: ArrayList<SettingsItem>) {
171 sl.apply { 166 sl.apply {
172 val theme: AbstractIntSetting = object : AbstractIntSetting { 167 val theme: AbstractIntSetting = object : AbstractIntSetting {
173 override fun getInt(needsGlobal: Boolean): Int = 168 override fun getInt(needsGlobal: Boolean): Int = IntSetting.THEME.getInt()
174 preferences.getInt(Settings.PREF_THEME, 0)
175
176 override fun setInt(value: Int) { 169 override fun setInt(value: Int) {
177 preferences.edit() 170 IntSetting.THEME.setInt(value)
178 .putInt(Settings.PREF_THEME, value)
179 .apply()
180 settingsViewModel.setShouldRecreate(true) 171 settingsViewModel.setShouldRecreate(true)
181 } 172 }
182 173
183 override val key: String = Settings.PREF_THEME 174 override val key: String = IntSetting.THEME.key
184 override val isRuntimeModifiable: Boolean = false 175 override val isRuntimeModifiable: Boolean = IntSetting.THEME.isRuntimeModifiable
185 override fun getValueAsString(needsGlobal: Boolean): String = getInt().toString() 176 override fun getValueAsString(needsGlobal: Boolean): String =
186 override val defaultValue: Int = 0 177 IntSetting.THEME.getValueAsString()
187 override fun reset() { 178
188 preferences.edit() 179 override val defaultValue: Int = IntSetting.THEME.defaultValue
189 .putInt(Settings.PREF_THEME, defaultValue) 180 override fun reset() = IntSetting.THEME.setInt(defaultValue)
190 .apply()
191 }
192 } 181 }
193 182
194 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { 183 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
@@ -214,24 +203,22 @@ class SettingsFragmentPresenter(
214 } 203 }
215 204
216 val themeMode: AbstractIntSetting = object : AbstractIntSetting { 205 val themeMode: AbstractIntSetting = object : AbstractIntSetting {
217 override fun getInt(needsGlobal: Boolean): Int = 206 override fun getInt(needsGlobal: Boolean): Int = IntSetting.THEME_MODE.getInt()
218 preferences.getInt(Settings.PREF_THEME_MODE, -1)
219
220 override fun setInt(value: Int) { 207 override fun setInt(value: Int) {
221 preferences.edit() 208 IntSetting.THEME_MODE.setInt(value)
222 .putInt(Settings.PREF_THEME_MODE, value)
223 .apply()
224 settingsViewModel.setShouldRecreate(true) 209 settingsViewModel.setShouldRecreate(true)
225 } 210 }
226 211
227 override val key: String = Settings.PREF_THEME_MODE 212 override val key: String = IntSetting.THEME_MODE.key
228 override val isRuntimeModifiable: Boolean = false 213 override val isRuntimeModifiable: Boolean =
229 override fun getValueAsString(needsGlobal: Boolean): String = getInt().toString() 214 IntSetting.THEME_MODE.isRuntimeModifiable
230 override val defaultValue: Int = -1 215
216 override fun getValueAsString(needsGlobal: Boolean): String =
217 IntSetting.THEME_MODE.getValueAsString()
218
219 override val defaultValue: Int = IntSetting.THEME_MODE.defaultValue
231 override fun reset() { 220 override fun reset() {
232 preferences.edit() 221 IntSetting.THEME_MODE.setInt(defaultValue)
233 .putInt(Settings.PREF_BLACK_BACKGROUNDS, defaultValue)
234 .apply()
235 settingsViewModel.setShouldRecreate(true) 222 settingsViewModel.setShouldRecreate(true)
236 } 223 }
237 } 224 }
@@ -248,25 +235,24 @@ class SettingsFragmentPresenter(
248 235
249 val blackBackgrounds: AbstractBooleanSetting = object : AbstractBooleanSetting { 236 val blackBackgrounds: AbstractBooleanSetting = object : AbstractBooleanSetting {
250 override fun getBoolean(needsGlobal: Boolean): Boolean = 237 override fun getBoolean(needsGlobal: Boolean): Boolean =
251 preferences.getBoolean(Settings.PREF_BLACK_BACKGROUNDS, false) 238 BooleanSetting.BLACK_BACKGROUNDS.getBoolean()
252 239
253 override fun setBoolean(value: Boolean) { 240 override fun setBoolean(value: Boolean) {
254 preferences.edit() 241 BooleanSetting.BLACK_BACKGROUNDS.setBoolean(value)
255 .putBoolean(Settings.PREF_BLACK_BACKGROUNDS, value)
256 .apply()
257 settingsViewModel.setShouldRecreate(true) 242 settingsViewModel.setShouldRecreate(true)
258 } 243 }
259 244
260 override val key: String = Settings.PREF_BLACK_BACKGROUNDS 245 override val key: String = BooleanSetting.BLACK_BACKGROUNDS.key
261 override val isRuntimeModifiable: Boolean = false 246 override val isRuntimeModifiable: Boolean =
247 BooleanSetting.BLACK_BACKGROUNDS.isRuntimeModifiable
248
262 override fun getValueAsString(needsGlobal: Boolean): String = 249 override fun getValueAsString(needsGlobal: Boolean): String =
263 getBoolean().toString() 250 BooleanSetting.BLACK_BACKGROUNDS.getValueAsString()
264 251
265 override val defaultValue: Boolean = false 252 override val defaultValue: Boolean = BooleanSetting.BLACK_BACKGROUNDS.defaultValue
266 override fun reset() { 253 override fun reset() {
267 preferences.edit() 254 BooleanSetting.BLACK_BACKGROUNDS
268 .putBoolean(Settings.PREF_BLACK_BACKGROUNDS, defaultValue) 255 .setBoolean(BooleanSetting.BLACK_BACKGROUNDS.defaultValue)
269 .apply()
270 settingsViewModel.setShouldRecreate(true) 256 settingsViewModel.setShouldRecreate(true)
271 } 257 }
272 } 258 }
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..510b2b5eb 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,22 @@ 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()
542 findItem(R.id.menu_touchscreen).isChecked = BooleanSetting.TOUCHSCREEN.getBoolean()
543 } 543 }
544 544
545 popup.setOnMenuItemClickListener { 545 popup.setOnMenuItemClickListener {
546 when (it.itemId) { 546 when (it.itemId) {
547 R.id.menu_toggle_fps -> { 547 R.id.menu_toggle_fps -> {
548 it.isChecked = !it.isChecked 548 it.isChecked = !it.isChecked
549 EmulationMenuSettings.showFps = it.isChecked 549 BooleanSetting.SHOW_PERFORMANCE_OVERLAY.setBoolean(it.isChecked)
550 updateShowFpsOverlay() 550 updateShowFpsOverlay()
551 true 551 true
552 } 552 }
@@ -564,11 +564,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
564 } 564 }
565 565
566 R.id.menu_toggle_controls -> { 566 R.id.menu_toggle_controls -> {
567 val preferences = 567 val overlayControlData = NativeConfig.getOverlayControlData()
568 PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) 568 val optionsArray = BooleanArray(overlayControlData.size)
569 val optionsArray = BooleanArray(Settings.overlayPreferences.size) 569 overlayControlData.forEachIndexed { i, _ ->
570 Settings.overlayPreferences.forEachIndexed { i, _ -> 570 optionsArray[i] = overlayControlData.firstOrNull { data ->
571 optionsArray[i] = preferences.getBoolean("buttonToggle$i", i < 15) 571 OverlayControl.entries[i].id == data.id
572 }?.enabled == true
572 } 573 }
573 574
574 val dialog = MaterialAlertDialogBuilder(requireContext()) 575 val dialog = MaterialAlertDialogBuilder(requireContext())
@@ -577,11 +578,13 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
577 R.array.gamepadButtons, 578 R.array.gamepadButtons,
578 optionsArray 579 optionsArray
579 ) { _, indexSelected, isChecked -> 580 ) { _, indexSelected, isChecked ->
580 preferences.edit() 581 overlayControlData.firstOrNull { data ->
581 .putBoolean("buttonToggle$indexSelected", isChecked) 582 OverlayControl.entries[indexSelected].id == data.id
582 .apply() 583 }?.enabled = isChecked
583 } 584 }
584 .setPositiveButton(android.R.string.ok) { _, _ -> 585 .setPositiveButton(android.R.string.ok) { _, _ ->
586 NativeConfig.setOverlayControlData(overlayControlData)
587 NativeConfig.saveGlobalConfig()
585 binding.surfaceInputOverlay.refreshControls() 588 binding.surfaceInputOverlay.refreshControls()
586 } 589 }
587 .setNegativeButton(android.R.string.cancel, null) 590 .setNegativeButton(android.R.string.cancel, null)
@@ -592,12 +595,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
592 dialog.getButton(AlertDialog.BUTTON_NEUTRAL) 595 dialog.getButton(AlertDialog.BUTTON_NEUTRAL)
593 .setOnClickListener { 596 .setOnClickListener {
594 val isChecked = !optionsArray[0] 597 val isChecked = !optionsArray[0]
595 Settings.overlayPreferences.forEachIndexed { i, _ -> 598 overlayControlData.forEachIndexed { i, _ ->
596 optionsArray[i] = isChecked 599 optionsArray[i] = isChecked
597 dialog.listView.setItemChecked(i, isChecked) 600 dialog.listView.setItemChecked(i, isChecked)
598 preferences.edit() 601 overlayControlData[i].enabled = isChecked
599 .putBoolean("buttonToggle$i", isChecked)
600 .apply()
601 } 602 }
602 } 603 }
603 true 604 true
@@ -605,26 +606,32 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
605 606
606 R.id.menu_show_overlay -> { 607 R.id.menu_show_overlay -> {
607 it.isChecked = !it.isChecked 608 it.isChecked = !it.isChecked
608 EmulationMenuSettings.showOverlay = it.isChecked 609 BooleanSetting.SHOW_INPUT_OVERLAY.setBoolean(it.isChecked)
609 binding.surfaceInputOverlay.refreshControls() 610 binding.surfaceInputOverlay.refreshControls()
610 true 611 true
611 } 612 }
612 613
613 R.id.menu_rel_stick_center -> { 614 R.id.menu_rel_stick_center -> {
614 it.isChecked = !it.isChecked 615 it.isChecked = !it.isChecked
615 EmulationMenuSettings.joystickRelCenter = it.isChecked 616 BooleanSetting.JOYSTICK_REL_CENTER.setBoolean(it.isChecked)
616 true 617 true
617 } 618 }
618 619
619 R.id.menu_dpad_slide -> { 620 R.id.menu_dpad_slide -> {
620 it.isChecked = !it.isChecked 621 it.isChecked = !it.isChecked
621 EmulationMenuSettings.dpadSlide = it.isChecked 622 BooleanSetting.DPAD_SLIDE.setBoolean(it.isChecked)
622 true 623 true
623 } 624 }
624 625
625 R.id.menu_haptics -> { 626 R.id.menu_haptics -> {
626 it.isChecked = !it.isChecked 627 it.isChecked = !it.isChecked
627 EmulationMenuSettings.hapticFeedback = it.isChecked 628 BooleanSetting.HAPTIC_FEEDBACK.setBoolean(it.isChecked)
629 true
630 }
631
632 R.id.menu_touchscreen -> {
633 it.isChecked = !it.isChecked
634 BooleanSetting.TOUCHSCREEN.setBoolean(it.isChecked)
628 true 635 true
629 } 636 }
630 637
@@ -667,6 +674,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
667 it.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED 674 it.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
668 } 675 }
669 } 676 }
677 NativeConfig.saveGlobalConfig()
670 } 678 }
671 679
672 @SuppressLint("SetTextI18n") 680 @SuppressLint("SetTextI18n")
@@ -675,7 +683,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
675 adjustBinding.apply { 683 adjustBinding.apply {
676 inputScaleSlider.apply { 684 inputScaleSlider.apply {
677 valueTo = 150F 685 valueTo = 150F
678 value = preferences.getInt(Settings.PREF_CONTROL_SCALE, 50).toFloat() 686 value = IntSetting.OVERLAY_SCALE.getInt().toFloat()
679 addOnChangeListener( 687 addOnChangeListener(
680 Slider.OnChangeListener { _, value, _ -> 688 Slider.OnChangeListener { _, value, _ ->
681 inputScaleValue.text = "${value.toInt()}%" 689 inputScaleValue.text = "${value.toInt()}%"
@@ -685,7 +693,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
685 } 693 }
686 inputOpacitySlider.apply { 694 inputOpacitySlider.apply {
687 valueTo = 100F 695 valueTo = 100F
688 value = preferences.getInt(Settings.PREF_CONTROL_OPACITY, 100).toFloat() 696 value = IntSetting.OVERLAY_OPACITY.getInt().toFloat()
689 addOnChangeListener( 697 addOnChangeListener(
690 Slider.OnChangeListener { _, value, _ -> 698 Slider.OnChangeListener { _, value, _ ->
691 inputOpacityValue.text = "${value.toInt()}%" 699 inputOpacityValue.text = "${value.toInt()}%"
@@ -709,16 +717,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
709 } 717 }
710 718
711 private fun setControlScale(scale: Int) { 719 private fun setControlScale(scale: Int) {
712 preferences.edit() 720 IntSetting.OVERLAY_SCALE.setInt(scale)
713 .putInt(Settings.PREF_CONTROL_SCALE, scale)
714 .apply()
715 binding.surfaceInputOverlay.refreshControls() 721 binding.surfaceInputOverlay.refreshControls()
716 } 722 }
717 723
718 private fun setControlOpacity(opacity: Int) { 724 private fun setControlOpacity(opacity: Int) {
719 preferences.edit() 725 IntSetting.OVERLAY_OPACITY.setInt(opacity)
720 .putInt(Settings.PREF_CONTROL_OPACITY, opacity)
721 .apply()
722 binding.surfaceInputOverlay.refreshControls() 726 binding.surfaceInputOverlay.refreshControls()
723 } 727 }
724 728
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 0197fd712..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
@@ -3,9 +3,17 @@
3 3
4package org.yuzu.yuzu_emu.utils 4package org.yuzu.yuzu_emu.utils
5 5
6import androidx.preference.PreferenceManager
6import java.io.IOException 7import java.io.IOException
7import org.yuzu.yuzu_emu.NativeLibrary 8import org.yuzu.yuzu_emu.NativeLibrary
8import org.yuzu.yuzu_emu.YuzuApplication 9import org.yuzu.yuzu_emu.YuzuApplication
10import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
11import org.yuzu.yuzu_emu.features.settings.model.IntSetting
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
16import org.yuzu.yuzu_emu.utils.PreferenceUtil.migratePreference
9 17
10object DirectoryInitialization { 18object DirectoryInitialization {
11 private var userPath: String? = null 19 private var userPath: String? = null
@@ -17,6 +25,7 @@ object DirectoryInitialization {
17 initializeInternalStorage() 25 initializeInternalStorage()
18 NativeLibrary.initializeSystem(false) 26 NativeLibrary.initializeSystem(false)
19 NativeConfig.initializeGlobalConfig() 27 NativeConfig.initializeGlobalConfig()
28 migrateSettings()
20 areDirectoriesReady = true 29 areDirectoriesReady = true
21 } 30 }
22 } 31 }
@@ -35,4 +44,170 @@ object DirectoryInitialization {
35 e.printStackTrace() 44 e.printStackTrace()
36 } 45 }
37 } 46 }
47
48 private fun migrateSettings() {
49 val preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
50 var saveConfig = false
51 val theme = preferences.migratePreference<Int>(Settings.PREF_THEME)
52 if (theme != null) {
53 IntSetting.THEME.setInt(theme)
54 saveConfig = true
55 }
56
57 val themeMode = preferences.migratePreference<Int>(Settings.PREF_THEME_MODE)
58 if (themeMode != null) {
59 IntSetting.THEME_MODE.setInt(themeMode)
60 saveConfig = true
61 }
62
63 val blackBackgrounds =
64 preferences.migratePreference<Boolean>(Settings.PREF_BLACK_BACKGROUNDS)
65 if (blackBackgrounds != null) {
66 BooleanSetting.BLACK_BACKGROUNDS.setBoolean(blackBackgrounds)
67 saveConfig = true
68 }
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
187 if (saveConfig) {
188 NativeConfig.saveGlobalConfig()
189 }
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 }
38} 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/java/org/yuzu/yuzu_emu/utils/PreferenceUtil.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/PreferenceUtil.kt
new file mode 100644
index 000000000..a233ba25c
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/PreferenceUtil.kt
@@ -0,0 +1,37 @@
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 android.content.SharedPreferences
7
8object PreferenceUtil {
9 /**
10 * Retrieves a shared preference value and then deletes the value in storage.
11 * @param key Associated key for the value in this preferences instance
12 * @return Typed value associated with [key]. Null if no such key exists.
13 */
14 inline fun <reified T> SharedPreferences.migratePreference(key: String): T? {
15 if (!this.contains(key)) {
16 return null
17 }
18
19 val value: Any = when (T::class) {
20 String::class -> this.getString(key, "")!!
21
22 Boolean::class -> this.getBoolean(key, false)
23
24 Int::class -> this.getInt(key, 0)
25
26 Float::class -> this.getFloat(key, 0f)
27
28 Long::class -> this.getLong(key, 0)
29
30 else -> throw IllegalStateException("Tried to migrate preference with invalid type!")
31 }
32 deletePreference(key)
33 return value as T
34 }
35
36 fun SharedPreferences.deletePreference(key: String) = this.edit().remove(key).apply()
37}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ThemeHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ThemeHelper.kt
index f312e24cf..6f7f40e43 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ThemeHelper.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ThemeHelper.kt
@@ -5,38 +5,38 @@ package org.yuzu.yuzu_emu.utils
5 5
6import android.content.res.Configuration 6import android.content.res.Configuration
7import android.graphics.Color 7import android.graphics.Color
8import android.os.Build
8import androidx.annotation.ColorInt 9import androidx.annotation.ColorInt
9import androidx.appcompat.app.AppCompatActivity 10import androidx.appcompat.app.AppCompatActivity
10import androidx.appcompat.app.AppCompatDelegate 11import androidx.appcompat.app.AppCompatDelegate
11import androidx.core.view.WindowCompat 12import androidx.core.view.WindowCompat
12import androidx.core.view.WindowInsetsControllerCompat 13import androidx.core.view.WindowInsetsControllerCompat
13import androidx.preference.PreferenceManager
14import kotlin.math.roundToInt 14import kotlin.math.roundToInt
15import org.yuzu.yuzu_emu.R 15import org.yuzu.yuzu_emu.R
16import org.yuzu.yuzu_emu.YuzuApplication 16import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
17import org.yuzu.yuzu_emu.features.settings.model.Settings 17import org.yuzu.yuzu_emu.features.settings.model.IntSetting
18import org.yuzu.yuzu_emu.ui.main.ThemeProvider 18import org.yuzu.yuzu_emu.ui.main.ThemeProvider
19 19
20object ThemeHelper { 20object ThemeHelper {
21 const val SYSTEM_BAR_ALPHA = 0.9f 21 const val SYSTEM_BAR_ALPHA = 0.9f
22 22
23 private const val DEFAULT = 0
24 private const val MATERIAL_YOU = 1
25
26 fun setTheme(activity: AppCompatActivity) { 23 fun setTheme(activity: AppCompatActivity) {
27 val preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
28 setThemeMode(activity) 24 setThemeMode(activity)
29 when (preferences.getInt(Settings.PREF_THEME, 0)) { 25 when (Theme.from(IntSetting.THEME.getInt())) {
30 DEFAULT -> activity.setTheme(R.style.Theme_Yuzu_Main) 26 Theme.Default -> activity.setTheme(R.style.Theme_Yuzu_Main)
31 MATERIAL_YOU -> activity.setTheme(R.style.Theme_Yuzu_Main_MaterialYou) 27 Theme.MaterialYou -> {
28 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
29 activity.setTheme(R.style.Theme_Yuzu_Main_MaterialYou)
30 } else {
31 activity.setTheme(R.style.Theme_Yuzu_Main)
32 }
33 }
32 } 34 }
33 35
34 // Using a specific night mode check because this could apply incorrectly when using the 36 // Using a specific night mode check because this could apply incorrectly when using the
35 // light app mode, dark system mode, and black backgrounds. Launching the settings activity 37 // light app mode, dark system mode, and black backgrounds. Launching the settings activity
36 // will then show light mode colors/navigation bars but with black backgrounds. 38 // will then show light mode colors/navigation bars but with black backgrounds.
37 if (preferences.getBoolean(Settings.PREF_BLACK_BACKGROUNDS, false) && 39 if (BooleanSetting.BLACK_BACKGROUNDS.getBoolean() && isNightMode(activity)) {
38 isNightMode(activity)
39 ) {
40 activity.setTheme(R.style.ThemeOverlay_Yuzu_Dark) 40 activity.setTheme(R.style.ThemeOverlay_Yuzu_Dark)
41 } 41 }
42 } 42 }
@@ -60,8 +60,7 @@ object ThemeHelper {
60 } 60 }
61 61
62 fun setThemeMode(activity: AppCompatActivity) { 62 fun setThemeMode(activity: AppCompatActivity) {
63 val themeMode = PreferenceManager.getDefaultSharedPreferences(activity.applicationContext) 63 val themeMode = IntSetting.THEME_MODE.getInt()
64 .getInt(Settings.PREF_THEME_MODE, AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
65 activity.delegate.localNightMode = themeMode 64 activity.delegate.localNightMode = themeMode
66 val windowController = WindowCompat.getInsetsController( 65 val windowController = WindowCompat.getInsetsController(
67 activity.window, 66 activity.window,
@@ -95,3 +94,12 @@ object ThemeHelper {
95 windowController.isAppearanceLightNavigationBars = false 94 windowController.isAppearanceLightNavigationBars = false
96 } 95 }
97} 96}
97
98enum class Theme(val int: Int) {
99 Default(0),
100 MaterialYou(1);
101
102 companion object {
103 fun from(int: Int): Theme = entries.firstOrNull { it.int == int } ?: Default
104 }
105}
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 9c3a5a9b2..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
@@ -114,8 +147,9 @@ void AndroidConfig::SavePathValues() {
114 for (size_t i = 0; i < AndroidSettings::values.game_dirs.size(); ++i) { 147 for (size_t i = 0; i < AndroidSettings::values.game_dirs.size(); ++i) {
115 SetArrayIndex(i); 148 SetArrayIndex(i);
116 const auto& game_dir = AndroidSettings::values.game_dirs[i]; 149 const auto& game_dir = AndroidSettings::values.game_dirs[i];
117 WriteSetting(std::string("path"), game_dir.path); 150 WriteStringSetting(std::string("path"), game_dir.path);
118 WriteSetting(std::string("deep_scan"), game_dir.deep_scan, std::make_optional(false)); 151 WriteBooleanSetting(std::string("deep_scan"), game_dir.deep_scan,
152 std::make_optional(false));
119 } 153 }
120 EndArray(); 154 EndArray();
121 155
@@ -130,6 +164,35 @@ void AndroidConfig::SaveDriverValues() {
130 EndGroup(); 164 EndGroup();
131} 165}
132 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
133std::vector<Settings::BasicSetting*>& AndroidConfig::FindRelevantList(Settings::Category category) { 196std::vector<Settings::BasicSetting*>& AndroidConfig::FindRelevantList(Settings::Category category) {
134 auto& map = Settings::values.linkage.by_category; 197 auto& map = Settings::values.linkage.by_category;
135 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 3733f5a3c..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
@@ -33,6 +41,28 @@ struct Values {
33 41
34 Settings::SwitchableSetting<std::string, false> driver_path{linkage, "", "driver_path", 42 Settings::SwitchableSetting<std::string, false> driver_path{linkage, "", "driver_path",
35 Settings::Category::GpuDriver}; 43 Settings::Category::GpuDriver};
44
45 Settings::Setting<s32> theme{linkage, 0, "theme", Settings::Category::Android};
46 Settings::Setting<s32> theme_mode{linkage, -1, "theme_mode", Settings::Category::Android};
47 Settings::Setting<bool> black_backgrounds{linkage, false, "black_backgrounds",
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};
36}; 66};
37 67
38extern 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/menu/menu_overlay_options.xml b/src/android/app/src/main/res/menu/menu_overlay_options.xml
index 4885b4f6f..363781652 100644
--- a/src/android/app/src/main/res/menu/menu_overlay_options.xml
+++ b/src/android/app/src/main/res/menu/menu_overlay_options.xml
@@ -39,6 +39,11 @@
39 android:checkable="true" /> 39 android:checkable="true" />
40 40
41 <item 41 <item
42 android:id="@+id/menu_touchscreen"
43 android:title="@string/touchscreen"
44 android:checkable="true" />
45
46 <item
42 android:id="@+id/menu_reset_overlay" 47 android:id="@+id/menu_reset_overlay"
43 android:title="@string/emulation_touch_overlay_reset" /> 48 android:title="@string/emulation_touch_overlay_reset" />
44 49
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/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml
index 4d5c268fe..1bedcb1ef 100644
--- a/src/android/app/src/main/res/values/strings.xml
+++ b/src/android/app/src/main/res/values/strings.xml
@@ -366,6 +366,7 @@
366 <string name="emulation_pause">Pause emulation</string> 366 <string name="emulation_pause">Pause emulation</string>
367 <string name="emulation_unpause">Unpause emulation</string> 367 <string name="emulation_unpause">Unpause emulation</string>
368 <string name="emulation_input_overlay">Overlay options</string> 368 <string name="emulation_input_overlay">Overlay options</string>
369 <string name="touchscreen">Touchscreen</string>
369 370
370 <string name="load_settings">Loading settings…</string> 371 <string name="load_settings">Loading settings…</string>
371 372
diff --git a/src/android/build.gradle.kts b/src/android/build.gradle.kts
index 51e559321..b77906ed6 100644
--- a/src/android/build.gradle.kts
+++ b/src/android/build.gradle.kts
@@ -5,7 +5,7 @@
5plugins { 5plugins {
6 id("com.android.application") version "8.1.2" apply false 6 id("com.android.application") version "8.1.2" apply false
7 id("com.android.library") version "8.1.2" apply false 7 id("com.android.library") version "8.1.2" apply false
8 id("org.jetbrains.kotlin.android") version "1.8.21" apply false 8 id("org.jetbrains.kotlin.android") version "1.9.20" apply false
9} 9}
10 10
11tasks.register("clean").configure { 11tasks.register("clean").configure {
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,
diff --git a/src/frontend_common/config.cpp b/src/frontend_common/config.cpp
index d9f99148b..51576b4ee 100644
--- a/src/frontend_common/config.cpp
+++ b/src/frontend_common/config.cpp
@@ -403,59 +403,63 @@ void Config::SavePlayerValues(const std::size_t player_index) {
403 // No custom profile selected 403 // No custom profile selected
404 return; 404 return;
405 } 405 }
406 WriteSetting(std::string(player_prefix).append("profile_name"), player.profile_name, 406 WriteStringSetting(std::string(player_prefix).append("profile_name"), player.profile_name,
407 std::make_optional(std::string(""))); 407 std::make_optional(std::string("")));
408 } 408 }
409 409
410 WriteSetting(std::string(player_prefix).append("type"), static_cast<u8>(player.controller_type), 410 WriteIntegerSetting(
411 std::make_optional(static_cast<u8>(Settings::ControllerType::ProController))); 411 std::string(player_prefix).append("type"), static_cast<u8>(player.controller_type),
412 std::make_optional(static_cast<u8>(Settings::ControllerType::ProController)));
412 413
413 if (!player_prefix.empty() || !Settings::IsConfiguringGlobal()) { 414 if (!player_prefix.empty() || !Settings::IsConfiguringGlobal()) {
414 WriteSetting(std::string(player_prefix).append("connected"), player.connected, 415 WriteBooleanSetting(std::string(player_prefix).append("connected"), player.connected,
415 std::make_optional(player_index == 0)); 416 std::make_optional(player_index == 0));
416 WriteSetting(std::string(player_prefix).append("vibration_enabled"), 417 WriteIntegerSetting(std::string(player_prefix).append("vibration_enabled"),
417 player.vibration_enabled, std::make_optional(true)); 418 player.vibration_enabled, std::make_optional(true));
418 WriteSetting(std::string(player_prefix).append("vibration_strength"), 419 WriteIntegerSetting(std::string(player_prefix).append("vibration_strength"),
419 player.vibration_strength, std::make_optional(100)); 420 player.vibration_strength, std::make_optional(100));
420 WriteSetting(std::string(player_prefix).append("body_color_left"), player.body_color_left, 421 WriteIntegerSetting(std::string(player_prefix).append("body_color_left"),
421 std::make_optional(Settings::JOYCON_BODY_NEON_BLUE)); 422 player.body_color_left,
422 WriteSetting(std::string(player_prefix).append("body_color_right"), player.body_color_right, 423 std::make_optional(Settings::JOYCON_BODY_NEON_BLUE));
423 std::make_optional(Settings::JOYCON_BODY_NEON_RED)); 424 WriteIntegerSetting(std::string(player_prefix).append("body_color_right"),
424 WriteSetting(std::string(player_prefix).append("button_color_left"), 425 player.body_color_right,
425 player.button_color_left, 426 std::make_optional(Settings::JOYCON_BODY_NEON_RED));
426 std::make_optional(Settings::JOYCON_BUTTONS_NEON_BLUE)); 427 WriteIntegerSetting(std::string(player_prefix).append("button_color_left"),
427 WriteSetting(std::string(player_prefix).append("button_color_right"), 428 player.button_color_left,
428 player.button_color_right, 429 std::make_optional(Settings::JOYCON_BUTTONS_NEON_BLUE));
429 std::make_optional(Settings::JOYCON_BUTTONS_NEON_RED)); 430 WriteIntegerSetting(std::string(player_prefix).append("button_color_right"),
431 player.button_color_right,
432 std::make_optional(Settings::JOYCON_BUTTONS_NEON_RED));
430 } 433 }
431} 434}
432 435
433void Config::SaveTouchscreenValues() { 436void Config::SaveTouchscreenValues() {
434 const auto& touchscreen = Settings::values.touchscreen; 437 const auto& touchscreen = Settings::values.touchscreen;
435 438
436 WriteSetting(std::string("touchscreen_enabled"), touchscreen.enabled, std::make_optional(true)); 439 WriteBooleanSetting(std::string("touchscreen_enabled"), touchscreen.enabled,
440 std::make_optional(true));
437 441
438 WriteSetting(std::string("touchscreen_angle"), touchscreen.rotation_angle, 442 WriteIntegerSetting(std::string("touchscreen_angle"), touchscreen.rotation_angle,
439 std::make_optional(static_cast<u32>(0))); 443 std::make_optional(static_cast<u32>(0)));
440 WriteSetting(std::string("touchscreen_diameter_x"), touchscreen.diameter_x, 444 WriteIntegerSetting(std::string("touchscreen_diameter_x"), touchscreen.diameter_x,
441 std::make_optional(static_cast<u32>(15))); 445 std::make_optional(static_cast<u32>(15)));
442 WriteSetting(std::string("touchscreen_diameter_y"), touchscreen.diameter_y, 446 WriteIntegerSetting(std::string("touchscreen_diameter_y"), touchscreen.diameter_y,
443 std::make_optional(static_cast<u32>(15))); 447 std::make_optional(static_cast<u32>(15)));
444} 448}
445 449
446void Config::SaveMotionTouchValues() { 450void Config::SaveMotionTouchValues() {
447 BeginArray(std::string("touch_from_button_maps")); 451 BeginArray(std::string("touch_from_button_maps"));
448 for (std::size_t p = 0; p < Settings::values.touch_from_button_maps.size(); ++p) { 452 for (std::size_t p = 0; p < Settings::values.touch_from_button_maps.size(); ++p) {
449 SetArrayIndex(static_cast<int>(p)); 453 SetArrayIndex(static_cast<int>(p));
450 WriteSetting(std::string("name"), Settings::values.touch_from_button_maps[p].name, 454 WriteStringSetting(std::string("name"), Settings::values.touch_from_button_maps[p].name,
451 std::make_optional(std::string("default"))); 455 std::make_optional(std::string("default")));
452 456
453 BeginArray(std::string("entries")); 457 BeginArray(std::string("entries"));
454 for (std::size_t q = 0; q < Settings::values.touch_from_button_maps[p].buttons.size(); 458 for (std::size_t q = 0; q < Settings::values.touch_from_button_maps[p].buttons.size();
455 ++q) { 459 ++q) {
456 SetArrayIndex(static_cast<int>(q)); 460 SetArrayIndex(static_cast<int>(q));
457 WriteSetting(std::string("bind"), 461 WriteStringSetting(std::string("bind"),
458 Settings::values.touch_from_button_maps[p].buttons[q]); 462 Settings::values.touch_from_button_maps[p].buttons[q]);
459 } 463 }
460 EndArray(); // entries 464 EndArray(); // entries
461 } 465 }
@@ -520,16 +524,16 @@ void Config::SaveCoreValues() {
520void Config::SaveDataStorageValues() { 524void Config::SaveDataStorageValues() {
521 BeginGroup(Settings::TranslateCategory(Settings::Category::DataStorage)); 525 BeginGroup(Settings::TranslateCategory(Settings::Category::DataStorage));
522 526
523 WriteSetting(std::string("nand_directory"), FS::GetYuzuPathString(FS::YuzuPath::NANDDir), 527 WriteStringSetting(std::string("nand_directory"), FS::GetYuzuPathString(FS::YuzuPath::NANDDir),
524 std::make_optional(FS::GetYuzuPathString(FS::YuzuPath::NANDDir))); 528 std::make_optional(FS::GetYuzuPathString(FS::YuzuPath::NANDDir)));
525 WriteSetting(std::string("sdmc_directory"), FS::GetYuzuPathString(FS::YuzuPath::SDMCDir), 529 WriteStringSetting(std::string("sdmc_directory"), FS::GetYuzuPathString(FS::YuzuPath::SDMCDir),
526 std::make_optional(FS::GetYuzuPathString(FS::YuzuPath::SDMCDir))); 530 std::make_optional(FS::GetYuzuPathString(FS::YuzuPath::SDMCDir)));
527 WriteSetting(std::string("load_directory"), FS::GetYuzuPathString(FS::YuzuPath::LoadDir), 531 WriteStringSetting(std::string("load_directory"), FS::GetYuzuPathString(FS::YuzuPath::LoadDir),
528 std::make_optional(FS::GetYuzuPathString(FS::YuzuPath::LoadDir))); 532 std::make_optional(FS::GetYuzuPathString(FS::YuzuPath::LoadDir)));
529 WriteSetting(std::string("dump_directory"), FS::GetYuzuPathString(FS::YuzuPath::DumpDir), 533 WriteStringSetting(std::string("dump_directory"), FS::GetYuzuPathString(FS::YuzuPath::DumpDir),
530 std::make_optional(FS::GetYuzuPathString(FS::YuzuPath::DumpDir))); 534 std::make_optional(FS::GetYuzuPathString(FS::YuzuPath::DumpDir)));
531 WriteSetting(std::string("tas_directory"), FS::GetYuzuPathString(FS::YuzuPath::TASDir), 535 WriteStringSetting(std::string("tas_directory"), FS::GetYuzuPathString(FS::YuzuPath::TASDir),
532 std::make_optional(FS::GetYuzuPathString(FS::YuzuPath::TASDir))); 536 std::make_optional(FS::GetYuzuPathString(FS::YuzuPath::TASDir)));
533 537
534 WriteCategory(Settings::Category::DataStorage); 538 WriteCategory(Settings::Category::DataStorage);
535 539
@@ -540,7 +544,7 @@ void Config::SaveDebuggingValues() {
540 BeginGroup(Settings::TranslateCategory(Settings::Category::Debugging)); 544 BeginGroup(Settings::TranslateCategory(Settings::Category::Debugging));
541 545
542 // Intentionally not using the QT default setting as this is intended to be changed in the ini 546 // Intentionally not using the QT default setting as this is intended to be changed in the ini
543 WriteSetting(std::string("record_frame_times"), Settings::values.record_frame_times); 547 WriteBooleanSetting(std::string("record_frame_times"), Settings::values.record_frame_times);
544 548
545 WriteCategory(Settings::Category::Debugging); 549 WriteCategory(Settings::Category::Debugging);
546 WriteCategory(Settings::Category::DebuggingGraphics); 550 WriteCategory(Settings::Category::DebuggingGraphics);
@@ -564,11 +568,13 @@ void Config::SaveDisabledAddOnValues() {
564 BeginArray(std::string("")); 568 BeginArray(std::string(""));
565 for (const auto& elem : Settings::values.disabled_addons) { 569 for (const auto& elem : Settings::values.disabled_addons) {
566 SetArrayIndex(i); 570 SetArrayIndex(i);
567 WriteSetting(std::string("title_id"), elem.first, std::make_optional(static_cast<u64>(0))); 571 WriteIntegerSetting(std::string("title_id"), elem.first,
572 std::make_optional(static_cast<u64>(0)));
568 BeginArray(std::string("disabled")); 573 BeginArray(std::string("disabled"));
569 for (std::size_t j = 0; j < elem.second.size(); ++j) { 574 for (std::size_t j = 0; j < elem.second.size(); ++j) {
570 SetArrayIndex(static_cast<int>(j)); 575 SetArrayIndex(static_cast<int>(j));
571 WriteSetting(std::string("d"), elem.second[j], std::make_optional(std::string(""))); 576 WriteStringSetting(std::string("d"), elem.second[j],
577 std::make_optional(std::string("")));
572 } 578 }
573 EndArray(); // disabled 579 EndArray(); // disabled
574 ++i; 580 ++i;
@@ -609,8 +615,8 @@ void Config::SaveRendererValues() {
609void Config::SaveScreenshotValues() { 615void Config::SaveScreenshotValues() {
610 BeginGroup(Settings::TranslateCategory(Settings::Category::Screenshots)); 616 BeginGroup(Settings::TranslateCategory(Settings::Category::Screenshots));
611 617
612 WriteSetting(std::string("screenshot_path"), 618 WriteStringSetting(std::string("screenshot_path"),
613 FS::GetYuzuPathString(FS::YuzuPath::ScreenshotsDir)); 619 FS::GetYuzuPathString(FS::YuzuPath::ScreenshotsDir));
614 WriteCategory(Settings::Category::Screenshots); 620 WriteCategory(Settings::Category::Screenshots);
615 621
616 EndGroup(); 622 EndGroup();
@@ -746,46 +752,70 @@ bool Config::Exists(const std::string& section, const std::string& key) const {
746 return !value.empty(); 752 return !value.empty();
747} 753}
748 754
749template <typename Type> 755void Config::WriteBooleanSetting(const std::string& key, const bool& value,
750void Config::WriteSetting(const std::string& key, const Type& value, 756 const std::optional<bool>& default_value,
751 const std::optional<Type>& default_value, 757 const std::optional<bool>& use_global) {
752 const std::optional<bool>& use_global) { 758 std::optional<std::string> string_default = std::nullopt;
753 std::string full_key = GetFullKey(key, false); 759 if (default_value.has_value()) {
760 string_default = std::make_optional(ToString(default_value.value()));
761 }
762 WritePreparedSetting(key, AdjustOutputString(ToString(value)), string_default, use_global);
763}
754 764
755 std::string saved_value; 765template <typename T>
756 std::string string_default; 766std::enable_if_t<std::is_integral_v<T>> Config::WriteIntegerSetting(
757 if constexpr (std::is_same_v<Type, std::string>) { 767 const std::string& key, const T& value, const std::optional<T>& default_value,
758 saved_value.append(AdjustOutputString(value)); 768 const std::optional<bool>& use_global) {
759 if (default_value.has_value()) { 769 std::optional<std::string> string_default = std::nullopt;
760 string_default.append(AdjustOutputString(default_value.value())); 770 if (default_value.has_value()) {
761 } 771 string_default = std::make_optional(ToString(default_value.value()));
762 } else { 772 }
763 saved_value.append(AdjustOutputString(ToString(value))); 773 WritePreparedSetting(key, AdjustOutputString(ToString(value)), string_default, use_global);
764 if (default_value.has_value()) { 774}
765 string_default.append(ToString(default_value.value())); 775
766 } 776void Config::WriteDoubleSetting(const std::string& key, const double& value,
777 const std::optional<double>& default_value,
778 const std::optional<bool>& use_global) {
779 std::optional<std::string> string_default = std::nullopt;
780 if (default_value.has_value()) {
781 string_default = std::make_optional(ToString(default_value.value()));
767 } 782 }
783 WritePreparedSetting(key, AdjustOutputString(ToString(value)), string_default, use_global);
784}
768 785
769 if (default_value.has_value() && use_global.has_value()) { 786void Config::WriteStringSetting(const std::string& key, const std::string& value,
787 const std::optional<std::string>& default_value,
788 const std::optional<bool>& use_global) {
789 std::optional string_default = default_value;
790 if (default_value.has_value()) {
791 string_default.value().append(AdjustOutputString(default_value.value()));
792 }
793 WritePreparedSetting(key, AdjustOutputString(value), string_default, use_global);
794}
795
796void Config::WritePreparedSetting(const std::string& key, const std::string& adjusted_value,
797 const std::optional<std::string>& adjusted_default_value,
798 const std::optional<bool>& use_global) {
799 std::string full_key = GetFullKey(key, false);
800 if (adjusted_default_value.has_value() && use_global.has_value()) {
770 if (!global) { 801 if (!global) {
771 WriteSettingInternal(std::string(full_key).append("\\global"), 802 WriteString(std::string(full_key).append("\\global"), ToString(use_global.value()));
772 ToString(use_global.value()));
773 } 803 }
774 if (global || use_global.value() == false) { 804 if (global || use_global.value() == false) {
775 WriteSettingInternal(std::string(full_key).append("\\default"), 805 WriteString(std::string(full_key).append("\\default"),
776 ToString(string_default == saved_value)); 806 ToString(adjusted_default_value == adjusted_value));
777 WriteSettingInternal(full_key, saved_value); 807 WriteString(full_key, adjusted_value);
778 } 808 }
779 } else if (default_value.has_value() && !use_global.has_value()) { 809 } else if (adjusted_default_value.has_value() && !use_global.has_value()) {
780 WriteSettingInternal(std::string(full_key).append("\\default"), 810 WriteString(std::string(full_key).append("\\default"),
781 ToString(string_default == saved_value)); 811 ToString(adjusted_default_value == adjusted_value));
782 WriteSettingInternal(full_key, saved_value); 812 WriteString(full_key, adjusted_value);
783 } else { 813 } else {
784 WriteSettingInternal(full_key, saved_value); 814 WriteString(full_key, adjusted_value);
785 } 815 }
786} 816}
787 817
788void Config::WriteSettingInternal(const std::string& key, const std::string& value) { 818void Config::WriteString(const std::string& key, const std::string& value) {
789 config->SetValue(GetSection().c_str(), key.c_str(), value.c_str()); 819 config->SetValue(GetSection().c_str(), key.c_str(), value.c_str());
790} 820}
791 821
@@ -861,17 +891,17 @@ void Config::WriteSettingGeneric(const Settings::BasicSetting* const setting) {
861 std::string key = AdjustKey(setting->GetLabel()); 891 std::string key = AdjustKey(setting->GetLabel());
862 if (setting->Switchable()) { 892 if (setting->Switchable()) {
863 if (!global) { 893 if (!global) {
864 WriteSetting(std::string(key).append("\\use_global"), setting->UsingGlobal()); 894 WriteBooleanSetting(std::string(key).append("\\use_global"), setting->UsingGlobal());
865 } 895 }
866 if (global || !setting->UsingGlobal()) { 896 if (global || !setting->UsingGlobal()) {
867 WriteSetting(std::string(key).append("\\default"), 897 WriteBooleanSetting(std::string(key).append("\\default"),
868 setting->ToString() == setting->DefaultToString()); 898 setting->ToString() == setting->DefaultToString());
869 WriteSetting(key, setting->ToString()); 899 WriteStringSetting(key, setting->ToString());
870 } 900 }
871 } else if (global) { 901 } else if (global) {
872 WriteSetting(std::string(key).append("\\default"), 902 WriteBooleanSetting(std::string(key).append("\\default"),
873 setting->ToString() == setting->DefaultToString()); 903 setting->ToString() == setting->DefaultToString());
874 WriteSetting(key, setting->ToString()); 904 WriteStringSetting(key, setting->ToString());
875 } 905 }
876} 906}
877 907
diff --git a/src/frontend_common/config.h b/src/frontend_common/config.h
index b3812af17..0c4d505b8 100644
--- a/src/frontend_common/config.h
+++ b/src/frontend_common/config.h
@@ -154,11 +154,20 @@ protected:
154 * @param use_global Specifies if the custom or global config should be in use, for custom 154 * @param use_global Specifies if the custom or global config should be in use, for custom
155 * configs 155 * configs
156 */ 156 */
157 template <typename Type = int> 157 void WriteBooleanSetting(const std::string& key, const bool& value,
158 void WriteSetting(const std::string& key, const Type& value, 158 const std::optional<bool>& default_value = std::nullopt,
159 const std::optional<Type>& default_value = std::nullopt, 159 const std::optional<bool>& use_global = std::nullopt);
160 const std::optional<bool>& use_global = std::nullopt); 160 template <typename T>
161 void WriteSettingInternal(const std::string& key, const std::string& value); 161 std::enable_if_t<std::is_integral_v<T>> WriteIntegerSetting(
162 const std::string& key, const T& value,
163 const std::optional<T>& default_value = std::nullopt,
164 const std::optional<bool>& use_global = std::nullopt);
165 void WriteDoubleSetting(const std::string& key, const double& value,
166 const std::optional<double>& default_value = std::nullopt,
167 const std::optional<bool>& use_global = std::nullopt);
168 void WriteStringSetting(const std::string& key, const std::string& value,
169 const std::optional<std::string>& default_value = std::nullopt,
170 const std::optional<bool>& use_global = std::nullopt);
162 171
163 void ReadCategory(Settings::Category category); 172 void ReadCategory(Settings::Category category);
164 void WriteCategory(Settings::Category category); 173 void WriteCategory(Settings::Category category);
@@ -175,8 +184,10 @@ protected:
175 return value_ ? "true" : "false"; 184 return value_ ? "true" : "false";
176 } else if constexpr (std::is_same_v<T, u64>) { 185 } else if constexpr (std::is_same_v<T, u64>) {
177 return std::to_string(static_cast<u64>(value_)); 186 return std::to_string(static_cast<u64>(value_));
178 } else { 187 } else if constexpr (std::is_same_v<T, s64>) {
179 return std::to_string(static_cast<s64>(value_)); 188 return std::to_string(static_cast<s64>(value_));
189 } else {
190 return std::to_string(value_);
180 } 191 }
181 } 192 }
182 193
@@ -197,9 +208,13 @@ protected:
197 const bool global; 208 const bool global;
198 209
199private: 210private:
200 inline static std::array<char, 19> special_characters = {'!', '#', '$', '%', '^', '&', '*', 211 void WritePreparedSetting(const std::string& key, const std::string& adjusted_value,
201 '|', ';', '\'', '\"', ',', '<', '.', 212 const std::optional<std::string>& adjusted_default_value,
202 '>', '?', '`', '~', '='}; 213 const std::optional<bool>& use_global);
214 void WriteString(const std::string& key, const std::string& value);
215
216 inline static std::array<char, 18> special_characters = {
217 '!', '#', '$', '%', '^', '&', '*', '|', ';', '\'', '\"', ',', '<', '>', '?', '`', '~', '='};
203 218
204 struct ConfigArray { 219 struct ConfigArray {
205 std::string name; 220 std::string name;
diff --git a/src/yuzu/configuration/qt_config.cpp b/src/yuzu/configuration/qt_config.cpp
index a71000b72..6aca71d7c 100644
--- a/src/yuzu/configuration/qt_config.cpp
+++ b/src/yuzu/configuration/qt_config.cpp
@@ -348,43 +348,45 @@ void QtConfig::SaveQtPlayerValues(const std::size_t player_index) {
348 348
349 for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { 349 for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
350 const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); 350 const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
351 WriteSetting(std::string(player_prefix).append(Settings::NativeButton::mapping[i]), 351 WriteStringSetting(std::string(player_prefix).append(Settings::NativeButton::mapping[i]),
352 player.buttons[i], std::make_optional(default_param)); 352 player.buttons[i], std::make_optional(default_param));
353 } 353 }
354 for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { 354 for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
355 const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( 355 const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
356 default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], 356 default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
357 default_analogs[i][3], default_stick_mod[i], 0.5f); 357 default_analogs[i][3], default_stick_mod[i], 0.5f);
358 WriteSetting(std::string(player_prefix).append(Settings::NativeAnalog::mapping[i]), 358 WriteStringSetting(std::string(player_prefix).append(Settings::NativeAnalog::mapping[i]),
359 player.analogs[i], std::make_optional(default_param)); 359 player.analogs[i], std::make_optional(default_param));
360 } 360 }
361 for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) { 361 for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) {
362 const std::string default_param = InputCommon::GenerateKeyboardParam(default_motions[i]); 362 const std::string default_param = InputCommon::GenerateKeyboardParam(default_motions[i]);
363 WriteSetting(std::string(player_prefix).append(Settings::NativeMotion::mapping[i]), 363 WriteStringSetting(std::string(player_prefix).append(Settings::NativeMotion::mapping[i]),
364 player.motions[i], std::make_optional(default_param)); 364 player.motions[i], std::make_optional(default_param));
365 } 365 }
366} 366}
367 367
368void QtConfig::SaveDebugControlValues() { 368void QtConfig::SaveDebugControlValues() {
369 for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { 369 for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
370 const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); 370 const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
371 WriteSetting(std::string("debug_pad_").append(Settings::NativeButton::mapping[i]), 371 WriteStringSetting(std::string("debug_pad_").append(Settings::NativeButton::mapping[i]),
372 Settings::values.debug_pad_buttons[i], std::make_optional(default_param)); 372 Settings::values.debug_pad_buttons[i],
373 std::make_optional(default_param));
373 } 374 }
374 for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { 375 for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
375 const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( 376 const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
376 default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], 377 default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
377 default_analogs[i][3], default_stick_mod[i], 0.5f); 378 default_analogs[i][3], default_stick_mod[i], 0.5f);
378 WriteSetting(std::string("debug_pad_").append(Settings::NativeAnalog::mapping[i]), 379 WriteStringSetting(std::string("debug_pad_").append(Settings::NativeAnalog::mapping[i]),
379 Settings::values.debug_pad_analogs[i], std::make_optional(default_param)); 380 Settings::values.debug_pad_analogs[i],
381 std::make_optional(default_param));
380 } 382 }
381} 383}
382 384
383void QtConfig::SaveHidbusValues() { 385void QtConfig::SaveHidbusValues() {
384 const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( 386 const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
385 0, 0, default_ringcon_analogs[0], default_ringcon_analogs[1], 0, 0.05f); 387 0, 0, default_ringcon_analogs[0], default_ringcon_analogs[1], 0, 0.05f);
386 WriteSetting(std::string("ring_controller"), Settings::values.ringcon_analogs, 388 WriteStringSetting(std::string("ring_controller"), Settings::values.ringcon_analogs,
387 std::make_optional(default_param)); 389 std::make_optional(default_param));
388} 390}
389 391
390void QtConfig::SaveQtControlValues() { 392void QtConfig::SaveQtControlValues() {
@@ -409,19 +411,20 @@ void QtConfig::SavePathValues() {
409 411
410 WriteCategory(Settings::Category::Paths); 412 WriteCategory(Settings::Category::Paths);
411 413
412 WriteSetting(std::string("romsPath"), UISettings::values.roms_path); 414 WriteStringSetting(std::string("romsPath"), UISettings::values.roms_path);
413 BeginArray(std::string("gamedirs")); 415 BeginArray(std::string("gamedirs"));
414 for (int i = 0; i < UISettings::values.game_dirs.size(); ++i) { 416 for (int i = 0; i < UISettings::values.game_dirs.size(); ++i) {
415 SetArrayIndex(i); 417 SetArrayIndex(i);
416 const auto& game_dir = UISettings::values.game_dirs[i]; 418 const auto& game_dir = UISettings::values.game_dirs[i];
417 WriteSetting(std::string("path"), game_dir.path); 419 WriteStringSetting(std::string("path"), game_dir.path);
418 WriteSetting(std::string("deep_scan"), game_dir.deep_scan, std::make_optional(false)); 420 WriteBooleanSetting(std::string("deep_scan"), game_dir.deep_scan,
419 WriteSetting(std::string("expanded"), game_dir.expanded, std::make_optional(true)); 421 std::make_optional(false));
422 WriteBooleanSetting(std::string("expanded"), game_dir.expanded, std::make_optional(true));
420 } 423 }
421 EndArray(); 424 EndArray();
422 425
423 WriteSetting(std::string("recentFiles"), 426 WriteStringSetting(std::string("recentFiles"),
424 UISettings::values.recent_files.join(QStringLiteral(", ")).toStdString()); 427 UISettings::values.recent_files.join(QStringLiteral(", ")).toStdString());
425 428
426 EndGroup(); 429 EndGroup();
427} 430}
@@ -438,14 +441,14 @@ void QtConfig::SaveShortcutValues() {
438 BeginGroup(group); 441 BeginGroup(group);
439 BeginGroup(name); 442 BeginGroup(name);
440 443
441 WriteSetting(std::string("KeySeq"), shortcut.keyseq, 444 WriteStringSetting(std::string("KeySeq"), shortcut.keyseq,
442 std::make_optional(default_hotkey.keyseq)); 445 std::make_optional(default_hotkey.keyseq));
443 WriteSetting(std::string("Controller_KeySeq"), shortcut.controller_keyseq, 446 WriteStringSetting(std::string("Controller_KeySeq"), shortcut.controller_keyseq,
444 std::make_optional(default_hotkey.controller_keyseq)); 447 std::make_optional(default_hotkey.controller_keyseq));
445 WriteSetting(std::string("Context"), shortcut.context, 448 WriteIntegerSetting(std::string("Context"), shortcut.context,
446 std::make_optional(default_hotkey.context)); 449 std::make_optional(default_hotkey.context));
447 WriteSetting(std::string("Repeat"), shortcut.repeat, 450 WriteBooleanSetting(std::string("Repeat"), shortcut.repeat,
448 std::make_optional(default_hotkey.repeat)); 451 std::make_optional(default_hotkey.repeat));
449 452
450 EndGroup(); // name 453 EndGroup(); // name
451 EndGroup(); // group 454 EndGroup(); // group
@@ -460,9 +463,10 @@ void QtConfig::SaveUIValues() {
460 WriteCategory(Settings::Category::Ui); 463 WriteCategory(Settings::Category::Ui);
461 WriteCategory(Settings::Category::UiGeneral); 464 WriteCategory(Settings::Category::UiGeneral);
462 465
463 WriteSetting(std::string("theme"), UISettings::values.theme, 466 WriteStringSetting(
464 std::make_optional(std::string( 467 std::string("theme"), UISettings::values.theme,
465 UISettings::themes[static_cast<size_t>(UISettings::default_theme)].second))); 468 std::make_optional(std::string(
469 UISettings::themes[static_cast<size_t>(UISettings::default_theme)].second)));
466 470
467 SaveUIGamelistValues(); 471 SaveUIGamelistValues();
468 SaveUILayoutValues(); 472 SaveUILayoutValues();
@@ -482,7 +486,7 @@ void QtConfig::SaveUIGamelistValues() {
482 BeginArray(std::string("favorites")); 486 BeginArray(std::string("favorites"));
483 for (int i = 0; i < UISettings::values.favorited_ids.size(); i++) { 487 for (int i = 0; i < UISettings::values.favorited_ids.size(); i++) {
484 SetArrayIndex(i); 488 SetArrayIndex(i);
485 WriteSetting(std::string("program_id"), UISettings::values.favorited_ids[i]); 489 WriteIntegerSetting(std::string("program_id"), UISettings::values.favorited_ids[i]);
486 } 490 }
487 EndArray(); // favorites 491 EndArray(); // favorites
488 492
@@ -506,14 +510,15 @@ void QtConfig::SaveMultiplayerValues() {
506 BeginArray(std::string("username_ban_list")); 510 BeginArray(std::string("username_ban_list"));
507 for (std::size_t i = 0; i < UISettings::values.multiplayer_ban_list.first.size(); ++i) { 511 for (std::size_t i = 0; i < UISettings::values.multiplayer_ban_list.first.size(); ++i) {
508 SetArrayIndex(static_cast<int>(i)); 512 SetArrayIndex(static_cast<int>(i));
509 WriteSetting(std::string("username"), UISettings::values.multiplayer_ban_list.first[i]); 513 WriteStringSetting(std::string("username"),
514 UISettings::values.multiplayer_ban_list.first[i]);
510 } 515 }
511 EndArray(); // username_ban_list 516 EndArray(); // username_ban_list
512 517
513 BeginArray(std::string("ip_ban_list")); 518 BeginArray(std::string("ip_ban_list"));
514 for (std::size_t i = 0; i < UISettings::values.multiplayer_ban_list.second.size(); ++i) { 519 for (std::size_t i = 0; i < UISettings::values.multiplayer_ban_list.second.size(); ++i) {
515 SetArrayIndex(static_cast<int>(i)); 520 SetArrayIndex(static_cast<int>(i));
516 WriteSetting(std::string("ip"), UISettings::values.multiplayer_ban_list.second[i]); 521 WriteStringSetting(std::string("ip"), UISettings::values.multiplayer_ban_list.second[i]);
517 } 522 }
518 EndArray(); // ip_ban_list 523 EndArray(); // ip_ban_list
519 524
diff --git a/src/yuzu_cmd/sdl_config.cpp b/src/yuzu_cmd/sdl_config.cpp
index 39fd8050c..e81bf5d45 100644
--- a/src/yuzu_cmd/sdl_config.cpp
+++ b/src/yuzu_cmd/sdl_config.cpp
@@ -213,43 +213,45 @@ void SdlConfig::SaveSdlPlayerValues(const std::size_t player_index) {
213 213
214 for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { 214 for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
215 const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); 215 const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
216 WriteSetting(std::string(player_prefix).append(Settings::NativeButton::mapping[i]), 216 WriteStringSetting(std::string(player_prefix).append(Settings::NativeButton::mapping[i]),
217 player.buttons[i], std::make_optional(default_param)); 217 player.buttons[i], std::make_optional(default_param));
218 } 218 }
219 for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { 219 for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
220 const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( 220 const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
221 default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], 221 default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
222 default_analogs[i][3], default_stick_mod[i], 0.5f); 222 default_analogs[i][3], default_stick_mod[i], 0.5f);
223 WriteSetting(std::string(player_prefix).append(Settings::NativeAnalog::mapping[i]), 223 WriteStringSetting(std::string(player_prefix).append(Settings::NativeAnalog::mapping[i]),
224 player.analogs[i], std::make_optional(default_param)); 224 player.analogs[i], std::make_optional(default_param));
225 } 225 }
226 for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) { 226 for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) {
227 const std::string default_param = InputCommon::GenerateKeyboardParam(default_motions[i]); 227 const std::string default_param = InputCommon::GenerateKeyboardParam(default_motions[i]);
228 WriteSetting(std::string(player_prefix).append(Settings::NativeMotion::mapping[i]), 228 WriteStringSetting(std::string(player_prefix).append(Settings::NativeMotion::mapping[i]),
229 player.motions[i], std::make_optional(default_param)); 229 player.motions[i], std::make_optional(default_param));
230 } 230 }
231} 231}
232 232
233void SdlConfig::SaveDebugControlValues() { 233void SdlConfig::SaveDebugControlValues() {
234 for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { 234 for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
235 const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); 235 const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
236 WriteSetting(std::string("debug_pad_").append(Settings::NativeButton::mapping[i]), 236 WriteStringSetting(std::string("debug_pad_").append(Settings::NativeButton::mapping[i]),
237 Settings::values.debug_pad_buttons[i], std::make_optional(default_param)); 237 Settings::values.debug_pad_buttons[i],
238 std::make_optional(default_param));
238 } 239 }
239 for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { 240 for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
240 const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( 241 const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
241 default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], 242 default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
242 default_analogs[i][3], default_stick_mod[i], 0.5f); 243 default_analogs[i][3], default_stick_mod[i], 0.5f);
243 WriteSetting(std::string("debug_pad_").append(Settings::NativeAnalog::mapping[i]), 244 WriteStringSetting(std::string("debug_pad_").append(Settings::NativeAnalog::mapping[i]),
244 Settings::values.debug_pad_analogs[i], std::make_optional(default_param)); 245 Settings::values.debug_pad_analogs[i],
246 std::make_optional(default_param));
245 } 247 }
246} 248}
247 249
248void SdlConfig::SaveHidbusValues() { 250void SdlConfig::SaveHidbusValues() {
249 const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( 251 const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
250 0, 0, default_ringcon_analogs[0], default_ringcon_analogs[1], 0, 0.05f); 252 0, 0, default_ringcon_analogs[0], default_ringcon_analogs[1], 0, 0.05f);
251 WriteSetting(std::string("ring_controller"), Settings::values.ringcon_analogs, 253 WriteStringSetting(std::string("ring_controller"), Settings::values.ringcon_analogs,
252 std::make_optional(default_param)); 254 std::make_optional(default_param));
253} 255}
254 256
255std::vector<Settings::BasicSetting*>& SdlConfig::FindRelevantList(Settings::Category category) { 257std::vector<Settings::BasicSetting*>& SdlConfig::FindRelevantList(Settings::Category category) {