Просмотр исходного кода

android: Migrate in-game overlay settings to ini

t895 2 лет назад
Родитель
Сommit
d163b18208
27 измененных файлов с 1173 добавлено и 791 удалено
  1. 6 0
      src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
  2. 7 1
      src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt
  3. 3 1
      src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt
  4. 17 22
      src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt
  5. 43 46
      src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
  6. 331 561
      src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt
  7. 2 1
      src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableButton.kt
  8. 2 2
      src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt
  9. 188 0
      src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/model/OverlayControl.kt
  10. 19 0
      src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/model/OverlayControlData.kt
  11. 13 0
      src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/model/OverlayControlDefault.kt
  12. 10 0
      src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/model/OverlayLayout.kt
  13. 142 0
      src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt
  14. 0 50
      src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/EmulationMenuSettings.kt
  15. 18 0
      src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt
  16. 9 0
      src/android/app/src/main/jni/android_common/android_common.cpp
  17. 3 0
      src/android/app/src/main/jni/android_common/android_common.h
  18. 62 0
      src/android/app/src/main/jni/android_config.cpp
  19. 2 0
      src/android/app/src/main/jni/android_config.h
  20. 25 0
      src/android/app/src/main/jni/android_settings.h
  21. 79 0
      src/android/app/src/main/jni/id_cache.cpp
  22. 12 0
      src/android/app/src/main/jni/id_cache.h
  23. 70 0
      src/android/app/src/main/jni/native_config.cpp
  24. 5 5
      src/android/app/src/main/res/values/arrays.xml
  25. 102 102
      src/android/app/src/main/res/values/integers.xml
  26. 2 0
      src/common/settings.cpp
  27. 1 0
      src/common/settings_common.h

+ 6 - 0
src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt

@@ -49,6 +49,7 @@ import org.yuzu.yuzu_emu.utils.ForegroundService
 import org.yuzu.yuzu_emu.utils.InputHandler
 import org.yuzu.yuzu_emu.utils.InputHandler
 import org.yuzu.yuzu_emu.utils.Log
 import org.yuzu.yuzu_emu.utils.Log
 import org.yuzu.yuzu_emu.utils.MemoryUtil
 import org.yuzu.yuzu_emu.utils.MemoryUtil
+import org.yuzu.yuzu_emu.utils.NativeConfig
 import org.yuzu.yuzu_emu.utils.NfcReader
 import org.yuzu.yuzu_emu.utils.NfcReader
 import org.yuzu.yuzu_emu.utils.ThemeHelper
 import org.yuzu.yuzu_emu.utils.ThemeHelper
 import java.text.NumberFormat
 import java.text.NumberFormat
@@ -170,6 +171,11 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
         stopMotionSensorListener()
         stopMotionSensorListener()
     }
     }
 
 
+    override fun onStop() {
+        super.onStop()
+        NativeConfig.saveGlobalConfig()
+    }
+
     override fun onUserLeaveHint() {
     override fun onUserLeaveHint() {
         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
             if (BooleanSetting.PICTURE_IN_PICTURE.getBoolean() && !isInPictureInPictureMode) {
             if (BooleanSetting.PICTURE_IN_PICTURE.getBoolean() && !isInPictureInPictureMode) {

+ 7 - 1
src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt

@@ -19,7 +19,13 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
     RENDERER_DEBUG("debug"),
     RENDERER_DEBUG("debug"),
     PICTURE_IN_PICTURE("picture_in_picture"),
     PICTURE_IN_PICTURE("picture_in_picture"),
     USE_CUSTOM_RTC("custom_rtc_enabled"),
     USE_CUSTOM_RTC("custom_rtc_enabled"),
-    BLACK_BACKGROUNDS("black_backgrounds");
+    BLACK_BACKGROUNDS("black_backgrounds"),
+    JOYSTICK_REL_CENTER("joystick_rel_center"),
+    DPAD_SLIDE("dpad_slide"),
+    HAPTIC_FEEDBACK("haptic_feedback"),
+    SHOW_PERFORMANCE_OVERLAY("show_performance_overlay"),
+    SHOW_INPUT_OVERLAY("show_input_overlay"),
+    TOUCHSCREEN("touchscreen");
 
 
     override fun getBoolean(needsGlobal: Boolean): Boolean =
     override fun getBoolean(needsGlobal: Boolean): Boolean =
         NativeConfig.getBoolean(key, needsGlobal)
         NativeConfig.getBoolean(key, needsGlobal)

+ 3 - 1
src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt

@@ -21,7 +21,9 @@ enum class IntSetting(override val key: String) : AbstractIntSetting {
     AUDIO_OUTPUT_ENGINE("output_engine"),
     AUDIO_OUTPUT_ENGINE("output_engine"),
     MAX_ANISOTROPY("max_anisotropy"),
     MAX_ANISOTROPY("max_anisotropy"),
     THEME("theme"),
     THEME("theme"),
-    THEME_MODE("theme_mode");
+    THEME_MODE("theme_mode"),
+    OVERLAY_SCALE("control_scale"),
+    OVERLAY_OPACITY("control_opacity");
 
 
     override fun getInt(needsGlobal: Boolean): Int = NativeConfig.getInt(key, needsGlobal)
     override fun getInt(needsGlobal: Boolean): Int = NativeConfig.getInt(key, needsGlobal)
 
 

+ 17 - 22
src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt

@@ -15,18 +15,10 @@ object Settings {
         SECTION_DEBUG(R.string.preferences_debug);
         SECTION_DEBUG(R.string.preferences_debug);
     }
     }
 
 
+    const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch"
     const val PREF_MEMORY_WARNING_SHOWN = "MemoryWarningShown"
     const val PREF_MEMORY_WARNING_SHOWN = "MemoryWarningShown"
 
 
-    const val PREF_OVERLAY_VERSION = "OverlayVersion"
-    const val PREF_LANDSCAPE_OVERLAY_VERSION = "LandscapeOverlayVersion"
-    const val PREF_PORTRAIT_OVERLAY_VERSION = "PortraitOverlayVersion"
-    const val PREF_FOLDABLE_OVERLAY_VERSION = "FoldableOverlayVersion"
-    val overlayLayoutPrefs = listOf(
-        PREF_LANDSCAPE_OVERLAY_VERSION,
-        PREF_PORTRAIT_OVERLAY_VERSION,
-        PREF_FOLDABLE_OVERLAY_VERSION
-    )
-
+    // Deprecated input overlay preference keys
     const val PREF_CONTROL_SCALE = "controlScale"
     const val PREF_CONTROL_SCALE = "controlScale"
     const val PREF_CONTROL_OPACITY = "controlOpacity"
     const val PREF_CONTROL_OPACITY = "controlOpacity"
     const val PREF_TOUCH_ENABLED = "isTouchEnabled"
     const val PREF_TOUCH_ENABLED = "isTouchEnabled"
@@ -47,24 +39,12 @@ object Settings {
     const val PREF_BUTTON_STICK_R = "buttonToggle14"
     const val PREF_BUTTON_STICK_R = "buttonToggle14"
     const val PREF_BUTTON_HOME = "buttonToggle15"
     const val PREF_BUTTON_HOME = "buttonToggle15"
     const val PREF_BUTTON_SCREENSHOT = "buttonToggle16"
     const val PREF_BUTTON_SCREENSHOT = "buttonToggle16"
-
     const val PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER = "EmulationMenuSettings_JoystickRelCenter"
     const val PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER = "EmulationMenuSettings_JoystickRelCenter"
     const val PREF_MENU_SETTINGS_DPAD_SLIDE = "EmulationMenuSettings_DpadSlideEnable"
     const val PREF_MENU_SETTINGS_DPAD_SLIDE = "EmulationMenuSettings_DpadSlideEnable"
     const val PREF_MENU_SETTINGS_HAPTICS = "EmulationMenuSettings_Haptics"
     const val PREF_MENU_SETTINGS_HAPTICS = "EmulationMenuSettings_Haptics"
     const val PREF_MENU_SETTINGS_SHOW_FPS = "EmulationMenuSettings_ShowFps"
     const val PREF_MENU_SETTINGS_SHOW_FPS = "EmulationMenuSettings_ShowFps"
     const val PREF_MENU_SETTINGS_SHOW_OVERLAY = "EmulationMenuSettings_ShowOverlay"
     const val PREF_MENU_SETTINGS_SHOW_OVERLAY = "EmulationMenuSettings_ShowOverlay"
-
-    // Deprecated theme preference keys
-    const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch"
-    const val PREF_THEME = "Theme"
-    const val PREF_THEME_MODE = "ThemeMode"
-    const val PREF_BLACK_BACKGROUNDS = "BlackBackgrounds"
-
     val overlayPreferences = listOf(
     val overlayPreferences = listOf(
-        PREF_OVERLAY_VERSION,
-        PREF_CONTROL_SCALE,
-        PREF_CONTROL_OPACITY,
-        PREF_TOUCH_ENABLED,
         PREF_BUTTON_A,
         PREF_BUTTON_A,
         PREF_BUTTON_B,
         PREF_BUTTON_B,
         PREF_BUTTON_X,
         PREF_BUTTON_X,
@@ -84,6 +64,21 @@ object Settings {
         PREF_BUTTON_STICK_R
         PREF_BUTTON_STICK_R
     )
     )
 
 
+    // Deprecated layout preference keys
+    const val PREF_LANDSCAPE_SUFFIX = "_Landscape"
+    const val PREF_PORTRAIT_SUFFIX = "_Portrait"
+    const val PREF_FOLDABLE_SUFFIX = "_Foldable"
+    val overlayLayoutSuffixes = listOf(
+        PREF_LANDSCAPE_SUFFIX,
+        PREF_PORTRAIT_SUFFIX,
+        PREF_FOLDABLE_SUFFIX
+    )
+
+    // Deprecated theme preference keys
+    const val PREF_THEME = "Theme"
+    const val PREF_THEME_MODE = "ThemeMode"
+    const val PREF_BLACK_BACKGROUNDS = "BlackBackgrounds"
+
     const val LayoutOption_Unspecified = 0
     const val LayoutOption_Unspecified = 0
     const val LayoutOption_MobilePortrait = 4
     const val LayoutOption_MobilePortrait = 4
     const val LayoutOption_MobileLandscape = 5
     const val LayoutOption_MobileLandscape = 5

+ 43 - 46
src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt

@@ -7,7 +7,6 @@ import android.annotation.SuppressLint
 import android.app.AlertDialog
 import android.app.AlertDialog
 import android.content.Context
 import android.content.Context
 import android.content.DialogInterface
 import android.content.DialogInterface
-import android.content.SharedPreferences
 import android.content.pm.ActivityInfo
 import android.content.pm.ActivityInfo
 import android.content.res.Configuration
 import android.content.res.Configuration
 import android.net.Uri
 import android.net.Uri
@@ -33,7 +32,6 @@ import androidx.lifecycle.lifecycleScope
 import androidx.lifecycle.repeatOnLifecycle
 import androidx.lifecycle.repeatOnLifecycle
 import androidx.navigation.findNavController
 import androidx.navigation.findNavController
 import androidx.navigation.fragment.navArgs
 import androidx.navigation.fragment.navArgs
-import androidx.preference.PreferenceManager
 import androidx.window.layout.FoldingFeature
 import androidx.window.layout.FoldingFeature
 import androidx.window.layout.WindowInfoTracker
 import androidx.window.layout.WindowInfoTracker
 import androidx.window.layout.WindowLayoutInfo
 import androidx.window.layout.WindowLayoutInfo
@@ -46,22 +44,22 @@ import kotlinx.coroutines.launch
 import org.yuzu.yuzu_emu.HomeNavigationDirections
 import org.yuzu.yuzu_emu.HomeNavigationDirections
 import org.yuzu.yuzu_emu.NativeLibrary
 import org.yuzu.yuzu_emu.NativeLibrary
 import org.yuzu.yuzu_emu.R
 import org.yuzu.yuzu_emu.R
-import org.yuzu.yuzu_emu.YuzuApplication
 import org.yuzu.yuzu_emu.activities.EmulationActivity
 import org.yuzu.yuzu_emu.activities.EmulationActivity
 import org.yuzu.yuzu_emu.databinding.DialogOverlayAdjustBinding
 import org.yuzu.yuzu_emu.databinding.DialogOverlayAdjustBinding
 import org.yuzu.yuzu_emu.databinding.FragmentEmulationBinding
 import org.yuzu.yuzu_emu.databinding.FragmentEmulationBinding
+import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
 import org.yuzu.yuzu_emu.features.settings.model.IntSetting
 import org.yuzu.yuzu_emu.features.settings.model.IntSetting
 import org.yuzu.yuzu_emu.features.settings.model.Settings
 import org.yuzu.yuzu_emu.features.settings.model.Settings
 import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
 import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
 import org.yuzu.yuzu_emu.model.DriverViewModel
 import org.yuzu.yuzu_emu.model.DriverViewModel
 import org.yuzu.yuzu_emu.model.Game
 import org.yuzu.yuzu_emu.model.Game
 import org.yuzu.yuzu_emu.model.EmulationViewModel
 import org.yuzu.yuzu_emu.model.EmulationViewModel
-import org.yuzu.yuzu_emu.overlay.InputOverlay
+import org.yuzu.yuzu_emu.overlay.model.OverlayControl
+import org.yuzu.yuzu_emu.overlay.model.OverlayLayout
 import org.yuzu.yuzu_emu.utils.*
 import org.yuzu.yuzu_emu.utils.*
 import java.lang.NullPointerException
 import java.lang.NullPointerException
 
 
 class EmulationFragment : Fragment(), SurfaceHolder.Callback {
 class EmulationFragment : Fragment(), SurfaceHolder.Callback {
-    private lateinit var preferences: SharedPreferences
     private lateinit var emulationState: EmulationState
     private lateinit var emulationState: EmulationState
     private var emulationActivity: EmulationActivity? = null
     private var emulationActivity: EmulationActivity? = null
     private var perfStatsUpdater: (() -> Unit)? = null
     private var perfStatsUpdater: (() -> Unit)? = null
@@ -141,7 +139,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
 
 
         // So this fragment doesn't restart on configuration changes; i.e. rotation.
         // So this fragment doesn't restart on configuration changes; i.e. rotation.
         retainInstance = true
         retainInstance = true
-        preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
         emulationState = EmulationState(game.path)
         emulationState = EmulationState(game.path)
     }
     }
 
 
@@ -382,24 +379,25 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
         }
         }
 
 
         updateScreenLayout()
         updateScreenLayout()
+        val showInputOverlay = BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean()
         if (emulationActivity?.isInPictureInPictureMode == true) {
         if (emulationActivity?.isInPictureInPictureMode == true) {
             if (binding.drawerLayout.isOpen) {
             if (binding.drawerLayout.isOpen) {
                 binding.drawerLayout.close()
                 binding.drawerLayout.close()
             }
             }
-            if (EmulationMenuSettings.showOverlay) {
+            if (showInputOverlay) {
                 binding.surfaceInputOverlay.visibility = View.INVISIBLE
                 binding.surfaceInputOverlay.visibility = View.INVISIBLE
             }
             }
         } else {
         } else {
-            if (EmulationMenuSettings.showOverlay && emulationViewModel.emulationStarted.value) {
+            if (showInputOverlay && emulationViewModel.emulationStarted.value) {
                 binding.surfaceInputOverlay.visibility = View.VISIBLE
                 binding.surfaceInputOverlay.visibility = View.VISIBLE
             } else {
             } else {
                 binding.surfaceInputOverlay.visibility = View.INVISIBLE
                 binding.surfaceInputOverlay.visibility = View.INVISIBLE
             }
             }
             if (!isInFoldableLayout) {
             if (!isInFoldableLayout) {
                 if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
                 if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
-                    binding.surfaceInputOverlay.layout = InputOverlay.PORTRAIT
+                    binding.surfaceInputOverlay.layout = OverlayLayout.Portrait
                 } else {
                 } else {
-                    binding.surfaceInputOverlay.layout = InputOverlay.LANDSCAPE
+                    binding.surfaceInputOverlay.layout = OverlayLayout.Landscape
                 }
                 }
             }
             }
         }
         }
@@ -423,17 +421,15 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
     }
     }
 
 
     private fun resetInputOverlay() {
     private fun resetInputOverlay() {
-        preferences.edit()
-            .remove(Settings.PREF_CONTROL_SCALE)
-            .remove(Settings.PREF_CONTROL_OPACITY)
-            .apply()
+        IntSetting.OVERLAY_SCALE.reset()
+        IntSetting.OVERLAY_OPACITY.reset()
         binding.surfaceInputOverlay.post {
         binding.surfaceInputOverlay.post {
             binding.surfaceInputOverlay.resetLayoutVisibilityAndPlacement()
             binding.surfaceInputOverlay.resetLayoutVisibilityAndPlacement()
         }
         }
     }
     }
 
 
     private fun updateShowFpsOverlay() {
     private fun updateShowFpsOverlay() {
-        if (EmulationMenuSettings.showFps) {
+        if (BooleanSetting.SHOW_PERFORMANCE_OVERLAY.getBoolean()) {
             val SYSTEM_FPS = 0
             val SYSTEM_FPS = 0
             val FPS = 1
             val FPS = 1
             val FRAMETIME = 2
             val FRAMETIME = 2
@@ -496,7 +492,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
                         binding.inGameMenu.layoutParams.height = it.bounds.bottom
                         binding.inGameMenu.layoutParams.height = it.bounds.bottom
 
 
                         isInFoldableLayout = true
                         isInFoldableLayout = true
-                        binding.surfaceInputOverlay.layout = InputOverlay.FOLDABLE
+                        binding.surfaceInputOverlay.layout = OverlayLayout.Foldable
                     }
                     }
                 }
                 }
                 it.isSeparating
                 it.isSeparating
@@ -535,18 +531,21 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
         popup.menuInflater.inflate(R.menu.menu_overlay_options, popup.menu)
         popup.menuInflater.inflate(R.menu.menu_overlay_options, popup.menu)
 
 
         popup.menu.apply {
         popup.menu.apply {
-            findItem(R.id.menu_toggle_fps).isChecked = EmulationMenuSettings.showFps
-            findItem(R.id.menu_rel_stick_center).isChecked = EmulationMenuSettings.joystickRelCenter
-            findItem(R.id.menu_dpad_slide).isChecked = EmulationMenuSettings.dpadSlide
-            findItem(R.id.menu_show_overlay).isChecked = EmulationMenuSettings.showOverlay
-            findItem(R.id.menu_haptics).isChecked = EmulationMenuSettings.hapticFeedback
+            findItem(R.id.menu_toggle_fps).isChecked =
+                BooleanSetting.SHOW_PERFORMANCE_OVERLAY.getBoolean()
+            findItem(R.id.menu_rel_stick_center).isChecked =
+                BooleanSetting.JOYSTICK_REL_CENTER.getBoolean()
+            findItem(R.id.menu_dpad_slide).isChecked = BooleanSetting.DPAD_SLIDE.getBoolean()
+            findItem(R.id.menu_show_overlay).isChecked =
+                BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean()
+            findItem(R.id.menu_haptics).isChecked = BooleanSetting.HAPTIC_FEEDBACK.getBoolean()
         }
         }
 
 
         popup.setOnMenuItemClickListener {
         popup.setOnMenuItemClickListener {
             when (it.itemId) {
             when (it.itemId) {
                 R.id.menu_toggle_fps -> {
                 R.id.menu_toggle_fps -> {
                     it.isChecked = !it.isChecked
                     it.isChecked = !it.isChecked
-                    EmulationMenuSettings.showFps = it.isChecked
+                    BooleanSetting.SHOW_PERFORMANCE_OVERLAY.setBoolean(it.isChecked)
                     updateShowFpsOverlay()
                     updateShowFpsOverlay()
                     true
                     true
                 }
                 }
@@ -564,11 +563,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
                 }
                 }
 
 
                 R.id.menu_toggle_controls -> {
                 R.id.menu_toggle_controls -> {
-                    val preferences =
-                        PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
-                    val optionsArray = BooleanArray(Settings.overlayPreferences.size)
-                    Settings.overlayPreferences.forEachIndexed { i, _ ->
-                        optionsArray[i] = preferences.getBoolean("buttonToggle$i", i < 15)
+                    val overlayControlData = NativeConfig.getOverlayControlData()
+                    val optionsArray = BooleanArray(overlayControlData.size)
+                    overlayControlData.forEachIndexed { i, _ ->
+                        optionsArray[i] = overlayControlData.firstOrNull { data ->
+                            OverlayControl.entries[i].id == data.id
+                        }?.enabled == true
                     }
                     }
 
 
                     val dialog = MaterialAlertDialogBuilder(requireContext())
                     val dialog = MaterialAlertDialogBuilder(requireContext())
@@ -577,11 +577,13 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
                             R.array.gamepadButtons,
                             R.array.gamepadButtons,
                             optionsArray
                             optionsArray
                         ) { _, indexSelected, isChecked ->
                         ) { _, indexSelected, isChecked ->
-                            preferences.edit()
-                                .putBoolean("buttonToggle$indexSelected", isChecked)
-                                .apply()
+                            overlayControlData.firstOrNull { data ->
+                                OverlayControl.entries[indexSelected].id == data.id
+                            }?.enabled = isChecked
                         }
                         }
                         .setPositiveButton(android.R.string.ok) { _, _ ->
                         .setPositiveButton(android.R.string.ok) { _, _ ->
+                            NativeConfig.setOverlayControlData(overlayControlData)
+                            NativeConfig.saveGlobalConfig()
                             binding.surfaceInputOverlay.refreshControls()
                             binding.surfaceInputOverlay.refreshControls()
                         }
                         }
                         .setNegativeButton(android.R.string.cancel, null)
                         .setNegativeButton(android.R.string.cancel, null)
@@ -592,12 +594,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
                     dialog.getButton(AlertDialog.BUTTON_NEUTRAL)
                     dialog.getButton(AlertDialog.BUTTON_NEUTRAL)
                         .setOnClickListener {
                         .setOnClickListener {
                             val isChecked = !optionsArray[0]
                             val isChecked = !optionsArray[0]
-                            Settings.overlayPreferences.forEachIndexed { i, _ ->
+                            overlayControlData.forEachIndexed { i, _ ->
                                 optionsArray[i] = isChecked
                                 optionsArray[i] = isChecked
                                 dialog.listView.setItemChecked(i, isChecked)
                                 dialog.listView.setItemChecked(i, isChecked)
-                                preferences.edit()
-                                    .putBoolean("buttonToggle$i", isChecked)
-                                    .apply()
+                                overlayControlData[i].enabled = isChecked
                             }
                             }
                         }
                         }
                     true
                     true
@@ -605,26 +605,26 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
 
 
                 R.id.menu_show_overlay -> {
                 R.id.menu_show_overlay -> {
                     it.isChecked = !it.isChecked
                     it.isChecked = !it.isChecked
-                    EmulationMenuSettings.showOverlay = it.isChecked
+                    BooleanSetting.SHOW_INPUT_OVERLAY.setBoolean(it.isChecked)
                     binding.surfaceInputOverlay.refreshControls()
                     binding.surfaceInputOverlay.refreshControls()
                     true
                     true
                 }
                 }
 
 
                 R.id.menu_rel_stick_center -> {
                 R.id.menu_rel_stick_center -> {
                     it.isChecked = !it.isChecked
                     it.isChecked = !it.isChecked
-                    EmulationMenuSettings.joystickRelCenter = it.isChecked
+                    BooleanSetting.JOYSTICK_REL_CENTER.setBoolean(it.isChecked)
                     true
                     true
                 }
                 }
 
 
                 R.id.menu_dpad_slide -> {
                 R.id.menu_dpad_slide -> {
                     it.isChecked = !it.isChecked
                     it.isChecked = !it.isChecked
-                    EmulationMenuSettings.dpadSlide = it.isChecked
+                    BooleanSetting.DPAD_SLIDE.setBoolean(it.isChecked)
                     true
                     true
                 }
                 }
 
 
                 R.id.menu_haptics -> {
                 R.id.menu_haptics -> {
                     it.isChecked = !it.isChecked
                     it.isChecked = !it.isChecked
-                    EmulationMenuSettings.hapticFeedback = it.isChecked
+                    BooleanSetting.HAPTIC_FEEDBACK.setBoolean(it.isChecked)
                     true
                     true
                 }
                 }
 
 
@@ -667,6 +667,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
                 it.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
                 it.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
             }
             }
         }
         }
+        NativeConfig.saveGlobalConfig()
     }
     }
 
 
     @SuppressLint("SetTextI18n")
     @SuppressLint("SetTextI18n")
@@ -675,7 +676,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
         adjustBinding.apply {
         adjustBinding.apply {
             inputScaleSlider.apply {
             inputScaleSlider.apply {
                 valueTo = 150F
                 valueTo = 150F
-                value = preferences.getInt(Settings.PREF_CONTROL_SCALE, 50).toFloat()
+                value = IntSetting.OVERLAY_SCALE.getInt().toFloat()
                 addOnChangeListener(
                 addOnChangeListener(
                     Slider.OnChangeListener { _, value, _ ->
                     Slider.OnChangeListener { _, value, _ ->
                         inputScaleValue.text = "${value.toInt()}%"
                         inputScaleValue.text = "${value.toInt()}%"
@@ -685,7 +686,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
             }
             }
             inputOpacitySlider.apply {
             inputOpacitySlider.apply {
                 valueTo = 100F
                 valueTo = 100F
-                value = preferences.getInt(Settings.PREF_CONTROL_OPACITY, 100).toFloat()
+                value = IntSetting.OVERLAY_OPACITY.getInt().toFloat()
                 addOnChangeListener(
                 addOnChangeListener(
                     Slider.OnChangeListener { _, value, _ ->
                     Slider.OnChangeListener { _, value, _ ->
                         inputOpacityValue.text = "${value.toInt()}%"
                         inputOpacityValue.text = "${value.toInt()}%"
@@ -709,16 +710,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
     }
     }
 
 
     private fun setControlScale(scale: Int) {
     private fun setControlScale(scale: Int) {
-        preferences.edit()
-            .putInt(Settings.PREF_CONTROL_SCALE, scale)
-            .apply()
+        IntSetting.OVERLAY_SCALE.setInt(scale)
         binding.surfaceInputOverlay.refreshControls()
         binding.surfaceInputOverlay.refreshControls()
     }
     }
 
 
     private fun setControlOpacity(opacity: Int) {
     private fun setControlOpacity(opacity: Int) {
-        preferences.edit()
-            .putInt(Settings.PREF_CONTROL_OPACITY, opacity)
-            .apply()
+        IntSetting.OVERLAY_OPACITY.setInt(opacity)
         binding.surfaceInputOverlay.refreshControls()
         binding.surfaceInputOverlay.refreshControls()
     }
     }
 
 

Разница между файлами не показана из-за своего большого размера
+ 331 - 561
src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt


+ 2 - 1
src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableButton.kt

@@ -10,6 +10,7 @@ import android.graphics.Rect
 import android.graphics.drawable.BitmapDrawable
 import android.graphics.drawable.BitmapDrawable
 import android.view.MotionEvent
 import android.view.MotionEvent
 import org.yuzu.yuzu_emu.NativeLibrary.ButtonState
 import org.yuzu.yuzu_emu.NativeLibrary.ButtonState
+import org.yuzu.yuzu_emu.overlay.model.OverlayControlData
 
 
 /**
 /**
  * Custom [BitmapDrawable] that is capable
  * Custom [BitmapDrawable] that is capable
@@ -25,7 +26,7 @@ class InputOverlayDrawableButton(
     defaultStateBitmap: Bitmap,
     defaultStateBitmap: Bitmap,
     pressedStateBitmap: Bitmap,
     pressedStateBitmap: Bitmap,
     val buttonId: Int,
     val buttonId: Int,
-    val prefId: String
+    val overlayControlData: OverlayControlData
 ) {
 ) {
     // The ID value what motion event is tracking
     // The ID value what motion event is tracking
     var trackId: Int
     var trackId: Int

+ 2 - 2
src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt

@@ -14,7 +14,7 @@ import kotlin.math.cos
 import kotlin.math.sin
 import kotlin.math.sin
 import kotlin.math.sqrt
 import kotlin.math.sqrt
 import org.yuzu.yuzu_emu.NativeLibrary
 import org.yuzu.yuzu_emu.NativeLibrary
-import org.yuzu.yuzu_emu.utils.EmulationMenuSettings
+import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
 
 
 /**
 /**
  * Custom [BitmapDrawable] that is capable
  * Custom [BitmapDrawable] that is capable
@@ -125,7 +125,7 @@ class InputOverlayDrawableJoystick(
             pressedState = true
             pressedState = true
             outerBitmap.alpha = 0
             outerBitmap.alpha = 0
             boundsBoxBitmap.alpha = opacity
             boundsBoxBitmap.alpha = opacity
-            if (EmulationMenuSettings.joystickRelCenter) {
+            if (BooleanSetting.JOYSTICK_REL_CENTER.getBoolean()) {
                 virtBounds.offset(
                 virtBounds.offset(
                     xPosition - virtBounds.centerX(),
                     xPosition - virtBounds.centerX(),
                     yPosition - virtBounds.centerY()
                     yPosition - virtBounds.centerY()

+ 188 - 0
src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/model/OverlayControl.kt

@@ -0,0 +1,188 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.overlay.model
+
+import androidx.annotation.IntegerRes
+import org.yuzu.yuzu_emu.R
+import org.yuzu.yuzu_emu.YuzuApplication
+
+enum class OverlayControl(
+    val id: String,
+    val defaultVisibility: Boolean,
+    @IntegerRes val defaultLandscapePositionResources: Pair<Int, Int>,
+    @IntegerRes val defaultPortraitPositionResources: Pair<Int, Int>,
+    @IntegerRes val defaultFoldablePositionResources: Pair<Int, Int>
+) {
+    BUTTON_A(
+        "button_a",
+        true,
+        Pair(R.integer.BUTTON_A_X, R.integer.BUTTON_A_Y),
+        Pair(R.integer.BUTTON_A_X_PORTRAIT, R.integer.BUTTON_A_Y_PORTRAIT),
+        Pair(R.integer.BUTTON_A_X_FOLDABLE, R.integer.BUTTON_A_Y_FOLDABLE)
+    ),
+    BUTTON_B(
+        "button_b",
+        true,
+        Pair(R.integer.BUTTON_B_X, R.integer.BUTTON_B_Y),
+        Pair(R.integer.BUTTON_B_X_PORTRAIT, R.integer.BUTTON_B_Y_PORTRAIT),
+        Pair(R.integer.BUTTON_B_X_FOLDABLE, R.integer.BUTTON_B_Y_FOLDABLE)
+    ),
+    BUTTON_X(
+        "button_x",
+        true,
+        Pair(R.integer.BUTTON_X_X, R.integer.BUTTON_X_Y),
+        Pair(R.integer.BUTTON_X_X_PORTRAIT, R.integer.BUTTON_X_Y_PORTRAIT),
+        Pair(R.integer.BUTTON_X_X_FOLDABLE, R.integer.BUTTON_X_Y_FOLDABLE)
+    ),
+    BUTTON_Y(
+        "button_y",
+        true,
+        Pair(R.integer.BUTTON_Y_X, R.integer.BUTTON_Y_Y),
+        Pair(R.integer.BUTTON_Y_X_PORTRAIT, R.integer.BUTTON_Y_Y_PORTRAIT),
+        Pair(R.integer.BUTTON_Y_X_FOLDABLE, R.integer.BUTTON_Y_Y_FOLDABLE)
+    ),
+    BUTTON_PLUS(
+        "button_plus",
+        true,
+        Pair(R.integer.BUTTON_PLUS_X, R.integer.BUTTON_PLUS_Y),
+        Pair(R.integer.BUTTON_PLUS_X_PORTRAIT, R.integer.BUTTON_PLUS_Y_PORTRAIT),
+        Pair(R.integer.BUTTON_PLUS_X_FOLDABLE, R.integer.BUTTON_PLUS_Y_FOLDABLE)
+    ),
+    BUTTON_MINUS(
+        "button_minus",
+        true,
+        Pair(R.integer.BUTTON_MINUS_X, R.integer.BUTTON_MINUS_Y),
+        Pair(R.integer.BUTTON_MINUS_X_PORTRAIT, R.integer.BUTTON_MINUS_Y_PORTRAIT),
+        Pair(R.integer.BUTTON_MINUS_X_FOLDABLE, R.integer.BUTTON_MINUS_Y_FOLDABLE)
+    ),
+    BUTTON_HOME(
+        "button_home",
+        false,
+        Pair(R.integer.BUTTON_HOME_X, R.integer.BUTTON_HOME_Y),
+        Pair(R.integer.BUTTON_HOME_X_PORTRAIT, R.integer.BUTTON_HOME_Y_PORTRAIT),
+        Pair(R.integer.BUTTON_HOME_X_FOLDABLE, R.integer.BUTTON_HOME_Y_FOLDABLE)
+    ),
+    BUTTON_CAPTURE(
+        "button_capture",
+        false,
+        Pair(R.integer.BUTTON_CAPTURE_X, R.integer.BUTTON_CAPTURE_Y),
+        Pair(R.integer.BUTTON_CAPTURE_X_PORTRAIT, R.integer.BUTTON_CAPTURE_Y_PORTRAIT),
+        Pair(R.integer.BUTTON_CAPTURE_X_FOLDABLE, R.integer.BUTTON_CAPTURE_Y_FOLDABLE)
+    ),
+    BUTTON_L(
+        "button_l",
+        true,
+        Pair(R.integer.BUTTON_L_X, R.integer.BUTTON_L_Y),
+        Pair(R.integer.BUTTON_L_X_PORTRAIT, R.integer.BUTTON_L_Y_PORTRAIT),
+        Pair(R.integer.BUTTON_L_X_FOLDABLE, R.integer.BUTTON_L_Y_FOLDABLE)
+    ),
+    BUTTON_R(
+        "button_r",
+        true,
+        Pair(R.integer.BUTTON_R_X, R.integer.BUTTON_R_Y),
+        Pair(R.integer.BUTTON_R_X_PORTRAIT, R.integer.BUTTON_R_Y_PORTRAIT),
+        Pair(R.integer.BUTTON_R_X_FOLDABLE, R.integer.BUTTON_R_Y_FOLDABLE)
+    ),
+    BUTTON_ZL(
+        "button_zl",
+        true,
+        Pair(R.integer.BUTTON_ZL_X, R.integer.BUTTON_ZL_Y),
+        Pair(R.integer.BUTTON_ZL_X_PORTRAIT, R.integer.BUTTON_ZL_Y_PORTRAIT),
+        Pair(R.integer.BUTTON_ZL_X_FOLDABLE, R.integer.BUTTON_ZL_Y_FOLDABLE)
+    ),
+    BUTTON_ZR(
+        "button_zr",
+        true,
+        Pair(R.integer.BUTTON_ZR_X, R.integer.BUTTON_ZR_Y),
+        Pair(R.integer.BUTTON_ZR_X_PORTRAIT, R.integer.BUTTON_ZR_Y_PORTRAIT),
+        Pair(R.integer.BUTTON_ZR_X_FOLDABLE, R.integer.BUTTON_ZR_Y_FOLDABLE)
+    ),
+    BUTTON_STICK_L(
+        "button_stick_l",
+        true,
+        Pair(R.integer.BUTTON_STICK_L_X, R.integer.BUTTON_STICK_L_Y),
+        Pair(R.integer.BUTTON_STICK_L_X_PORTRAIT, R.integer.BUTTON_STICK_L_Y_PORTRAIT),
+        Pair(R.integer.BUTTON_STICK_L_X_FOLDABLE, R.integer.BUTTON_STICK_L_Y_FOLDABLE)
+    ),
+    BUTTON_STICK_R(
+        "button_stick_r",
+        true,
+        Pair(R.integer.BUTTON_STICK_R_X, R.integer.BUTTON_STICK_R_Y),
+        Pair(R.integer.BUTTON_STICK_R_X_PORTRAIT, R.integer.BUTTON_STICK_R_Y_PORTRAIT),
+        Pair(R.integer.BUTTON_STICK_R_X_FOLDABLE, R.integer.BUTTON_STICK_R_Y_FOLDABLE)
+    ),
+    STICK_L(
+        "stick_l",
+        true,
+        Pair(R.integer.STICK_L_X, R.integer.STICK_L_Y),
+        Pair(R.integer.STICK_L_X_PORTRAIT, R.integer.STICK_L_Y_PORTRAIT),
+        Pair(R.integer.STICK_L_X_FOLDABLE, R.integer.STICK_L_Y_FOLDABLE)
+    ),
+    STICK_R(
+        "stick_r",
+        true,
+        Pair(R.integer.STICK_R_X, R.integer.STICK_R_Y),
+        Pair(R.integer.STICK_R_X_PORTRAIT, R.integer.STICK_R_Y_PORTRAIT),
+        Pair(R.integer.STICK_R_X_FOLDABLE, R.integer.STICK_R_Y_FOLDABLE)
+    ),
+    COMBINED_DPAD(
+        "combined_dpad",
+        true,
+        Pair(R.integer.COMBINED_DPAD_X, R.integer.COMBINED_DPAD_Y),
+        Pair(R.integer.COMBINED_DPAD_X_PORTRAIT, R.integer.COMBINED_DPAD_Y_PORTRAIT),
+        Pair(R.integer.COMBINED_DPAD_X_FOLDABLE, R.integer.COMBINED_DPAD_Y_FOLDABLE)
+    );
+
+    fun getDefaultPositionForLayout(layout: OverlayLayout): Pair<Double, Double> {
+        val rawResourcePair: Pair<Int, Int>
+        YuzuApplication.appContext.resources.apply {
+            rawResourcePair = when (layout) {
+                OverlayLayout.Landscape -> {
+                    Pair(
+                        getInteger(this@OverlayControl.defaultLandscapePositionResources.first),
+                        getInteger(this@OverlayControl.defaultLandscapePositionResources.second)
+                    )
+                }
+
+                OverlayLayout.Portrait -> {
+                    Pair(
+                        getInteger(this@OverlayControl.defaultPortraitPositionResources.first),
+                        getInteger(this@OverlayControl.defaultPortraitPositionResources.second)
+                    )
+                }
+
+                OverlayLayout.Foldable -> {
+                    Pair(
+                        getInteger(this@OverlayControl.defaultFoldablePositionResources.first),
+                        getInteger(this@OverlayControl.defaultFoldablePositionResources.second)
+                    )
+                }
+            }
+        }
+
+        return Pair(
+            rawResourcePair.first.toDouble() / 1000,
+            rawResourcePair.second.toDouble() / 1000
+        )
+    }
+
+    fun toOverlayControlData(): OverlayControlData =
+        OverlayControlData(
+            id,
+            defaultVisibility,
+            getDefaultPositionForLayout(OverlayLayout.Landscape),
+            getDefaultPositionForLayout(OverlayLayout.Portrait),
+            getDefaultPositionForLayout(OverlayLayout.Foldable)
+        )
+
+    companion object {
+        val map: HashMap<String, OverlayControl> by lazy {
+            val hashMap = hashMapOf<String, OverlayControl>()
+            entries.forEach { hashMap[it.id] = it }
+            hashMap
+        }
+
+        fun from(id: String): OverlayControl? = map[id]
+    }
+}

+ 19 - 0
src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/model/OverlayControlData.kt

@@ -0,0 +1,19 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.overlay.model
+
+data class OverlayControlData(
+    val id: String,
+    var enabled: Boolean,
+    var landscapePosition: Pair<Double, Double>,
+    var portraitPosition: Pair<Double, Double>,
+    var foldablePosition: Pair<Double, Double>
+) {
+    fun positionFromLayout(layout: OverlayLayout): Pair<Double, Double> =
+        when (layout) {
+            OverlayLayout.Landscape -> landscapePosition
+            OverlayLayout.Portrait -> portraitPosition
+            OverlayLayout.Foldable -> foldablePosition
+        }
+}

+ 13 - 0
src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/model/OverlayControlDefault.kt

@@ -0,0 +1,13 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.overlay.model
+
+import androidx.annotation.IntegerRes
+
+data class OverlayControlDefault(
+    val buttonId: String,
+    @IntegerRes val landscapePositionResource: Pair<Int, Int>,
+    @IntegerRes val portraitPositionResource: Pair<Int, Int>,
+    @IntegerRes val foldablePositionResource: Pair<Int, Int>
+)

+ 10 - 0
src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/model/OverlayLayout.kt

@@ -0,0 +1,10 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.overlay.model
+
+enum class OverlayLayout(val id: String) {
+    Landscape("Landscape"),
+    Portrait("Portrait"),
+    Foldable("Foldable")
+}

+ 142 - 0
src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt

@@ -10,6 +10,9 @@ import org.yuzu.yuzu_emu.YuzuApplication
 import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
 import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
 import org.yuzu.yuzu_emu.features.settings.model.IntSetting
 import org.yuzu.yuzu_emu.features.settings.model.IntSetting
 import org.yuzu.yuzu_emu.features.settings.model.Settings
 import org.yuzu.yuzu_emu.features.settings.model.Settings
+import org.yuzu.yuzu_emu.overlay.model.OverlayControlData
+import org.yuzu.yuzu_emu.overlay.model.OverlayControl
+import org.yuzu.yuzu_emu.overlay.model.OverlayLayout
 import org.yuzu.yuzu_emu.utils.PreferenceUtil.migratePreference
 import org.yuzu.yuzu_emu.utils.PreferenceUtil.migratePreference
 
 
 object DirectoryInitialization {
 object DirectoryInitialization {
@@ -64,8 +67,147 @@ object DirectoryInitialization {
             saveConfig = true
             saveConfig = true
         }
         }
 
 
+        val joystickRelCenter =
+            preferences.migratePreference<Boolean>(Settings.PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER)
+        if (joystickRelCenter != null) {
+            BooleanSetting.JOYSTICK_REL_CENTER.setBoolean(joystickRelCenter)
+            saveConfig = true
+        }
+
+        val dpadSlide =
+            preferences.migratePreference<Boolean>(Settings.PREF_MENU_SETTINGS_DPAD_SLIDE)
+        if (dpadSlide != null) {
+            BooleanSetting.DPAD_SLIDE.setBoolean(dpadSlide)
+            saveConfig = true
+        }
+
+        val hapticFeedback =
+            preferences.migratePreference<Boolean>(Settings.PREF_MENU_SETTINGS_HAPTICS)
+        if (hapticFeedback != null) {
+            BooleanSetting.HAPTIC_FEEDBACK.setBoolean(hapticFeedback)
+            saveConfig = true
+        }
+
+        val showPerformanceOverlay =
+            preferences.migratePreference<Boolean>(Settings.PREF_MENU_SETTINGS_SHOW_FPS)
+        if (showPerformanceOverlay != null) {
+            BooleanSetting.SHOW_PERFORMANCE_OVERLAY.setBoolean(showPerformanceOverlay)
+            saveConfig = true
+        }
+
+        val showInputOverlay =
+            preferences.migratePreference<Boolean>(Settings.PREF_MENU_SETTINGS_SHOW_OVERLAY)
+        if (showInputOverlay != null) {
+            BooleanSetting.SHOW_INPUT_OVERLAY.setBoolean(showInputOverlay)
+            saveConfig = true
+        }
+
+        val overlayOpacity = preferences.migratePreference<Int>(Settings.PREF_CONTROL_OPACITY)
+        if (overlayOpacity != null) {
+            IntSetting.OVERLAY_OPACITY.setInt(overlayOpacity)
+            saveConfig = true
+        }
+
+        val overlayScale = preferences.migratePreference<Int>(Settings.PREF_CONTROL_SCALE)
+        if (overlayScale != null) {
+            IntSetting.OVERLAY_SCALE.setInt(overlayScale)
+            saveConfig = true
+        }
+
+        var setOverlayData = false
+        val overlayControlData = NativeConfig.getOverlayControlData()
+        if (overlayControlData.isEmpty()) {
+            val overlayControlDataMap =
+                NativeConfig.getOverlayControlData().associateBy { it.id }.toMutableMap()
+            for (button in Settings.overlayPreferences) {
+                val buttonId = convertButtonId(button)
+                var buttonEnabled = preferences.migratePreference<Boolean>(button)
+                if (buttonEnabled == null) {
+                    buttonEnabled = OverlayControl.map[buttonId]?.defaultVisibility == true
+                }
+
+                var landscapeXPosition = preferences.migratePreference<Float>(
+                    "$button-X${Settings.PREF_LANDSCAPE_SUFFIX}"
+                )?.toDouble()
+                var landscapeYPosition = preferences.migratePreference<Float>(
+                    "$button-Y${Settings.PREF_LANDSCAPE_SUFFIX}"
+                )?.toDouble()
+                if (landscapeXPosition == null || landscapeYPosition == null) {
+                    val landscapePosition = OverlayControl.map[buttonId]
+                        ?.getDefaultPositionForLayout(OverlayLayout.Landscape) ?: Pair(0.0, 0.0)
+                    landscapeXPosition = landscapePosition.first
+                    landscapeYPosition = landscapePosition.second
+                }
+
+                var portraitXPosition = preferences.migratePreference<Float>(
+                    "$button-X${Settings.PREF_PORTRAIT_SUFFIX}"
+                )?.toDouble()
+                var portraitYPosition = preferences.migratePreference<Float>(
+                    "$button-Y${Settings.PREF_PORTRAIT_SUFFIX}"
+                )?.toDouble()
+                if (portraitXPosition == null || portraitYPosition == null) {
+                    val portraitPosition = OverlayControl.map[buttonId]
+                        ?.getDefaultPositionForLayout(OverlayLayout.Portrait) ?: Pair(0.0, 0.0)
+                    portraitXPosition = portraitPosition.first
+                    portraitYPosition = portraitPosition.second
+                }
+
+                var foldableXPosition = preferences.migratePreference<Float>(
+                    "$button-X${Settings.PREF_FOLDABLE_SUFFIX}"
+                )?.toDouble()
+                var foldableYPosition = preferences.migratePreference<Float>(
+                    "$button-Y${Settings.PREF_FOLDABLE_SUFFIX}"
+                )?.toDouble()
+                if (foldableXPosition == null || foldableYPosition == null) {
+                    val foldablePosition = OverlayControl.map[buttonId]
+                        ?.getDefaultPositionForLayout(OverlayLayout.Foldable) ?: Pair(0.0, 0.0)
+                    foldableXPosition = foldablePosition.first
+                    foldableYPosition = foldablePosition.second
+                }
+
+                val controlData = OverlayControlData(
+                    buttonId,
+                    buttonEnabled,
+                    Pair(landscapeXPosition, landscapeYPosition),
+                    Pair(portraitXPosition, portraitYPosition),
+                    Pair(foldableXPosition, foldableYPosition)
+                )
+                overlayControlDataMap[buttonId] = controlData
+                setOverlayData = true
+            }
+
+            if (setOverlayData) {
+                NativeConfig.setOverlayControlData(
+                    overlayControlDataMap.map { it.value }.toTypedArray()
+                )
+                saveConfig = true
+            }
+        }
+
         if (saveConfig) {
         if (saveConfig) {
             NativeConfig.saveGlobalConfig()
             NativeConfig.saveGlobalConfig()
         }
         }
     }
     }
+
+    private fun convertButtonId(buttonId: String): String =
+        when (buttonId) {
+            Settings.PREF_BUTTON_A -> OverlayControl.BUTTON_A.id
+            Settings.PREF_BUTTON_B -> OverlayControl.BUTTON_B.id
+            Settings.PREF_BUTTON_X -> OverlayControl.BUTTON_X.id
+            Settings.PREF_BUTTON_Y -> OverlayControl.BUTTON_Y.id
+            Settings.PREF_BUTTON_L -> OverlayControl.BUTTON_L.id
+            Settings.PREF_BUTTON_R -> OverlayControl.BUTTON_R.id
+            Settings.PREF_BUTTON_ZL -> OverlayControl.BUTTON_ZL.id
+            Settings.PREF_BUTTON_ZR -> OverlayControl.BUTTON_ZR.id
+            Settings.PREF_BUTTON_PLUS -> OverlayControl.BUTTON_PLUS.id
+            Settings.PREF_BUTTON_MINUS -> OverlayControl.BUTTON_MINUS.id
+            Settings.PREF_BUTTON_DPAD -> OverlayControl.COMBINED_DPAD.id
+            Settings.PREF_STICK_L -> OverlayControl.STICK_L.id
+            Settings.PREF_STICK_R -> OverlayControl.STICK_R.id
+            Settings.PREF_BUTTON_HOME -> OverlayControl.BUTTON_HOME.id
+            Settings.PREF_BUTTON_SCREENSHOT -> OverlayControl.BUTTON_CAPTURE.id
+            Settings.PREF_BUTTON_STICK_L -> OverlayControl.BUTTON_STICK_L.id
+            Settings.PREF_BUTTON_STICK_R -> OverlayControl.BUTTON_STICK_R.id
+            else -> ""
+        }
 }
 }

+ 0 - 50
src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/EmulationMenuSettings.kt

@@ -1,50 +0,0 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-package org.yuzu.yuzu_emu.utils
-
-import androidx.preference.PreferenceManager
-import org.yuzu.yuzu_emu.YuzuApplication
-import org.yuzu.yuzu_emu.features.settings.model.Settings
-
-object EmulationMenuSettings {
-    private val preferences =
-        PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
-
-    var joystickRelCenter: Boolean
-        get() = preferences.getBoolean(Settings.PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER, true)
-        set(value) {
-            preferences.edit()
-                .putBoolean(Settings.PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER, value)
-                .apply()
-        }
-    var dpadSlide: Boolean
-        get() = preferences.getBoolean(Settings.PREF_MENU_SETTINGS_DPAD_SLIDE, true)
-        set(value) {
-            preferences.edit()
-                .putBoolean(Settings.PREF_MENU_SETTINGS_DPAD_SLIDE, value)
-                .apply()
-        }
-    var hapticFeedback: Boolean
-        get() = preferences.getBoolean(Settings.PREF_MENU_SETTINGS_HAPTICS, false)
-        set(value) {
-            preferences.edit()
-                .putBoolean(Settings.PREF_MENU_SETTINGS_HAPTICS, value)
-                .apply()
-        }
-
-    var showFps: Boolean
-        get() = preferences.getBoolean(Settings.PREF_MENU_SETTINGS_SHOW_FPS, false)
-        set(value) {
-            preferences.edit()
-                .putBoolean(Settings.PREF_MENU_SETTINGS_SHOW_FPS, value)
-                .apply()
-        }
-    var showOverlay: Boolean
-        get() = preferences.getBoolean(Settings.PREF_MENU_SETTINGS_SHOW_OVERLAY, true)
-        set(value) {
-            preferences.edit()
-                .putBoolean(Settings.PREF_MENU_SETTINGS_SHOW_OVERLAY, value)
-                .apply()
-        }
-}

+ 18 - 0
src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt

@@ -4,6 +4,7 @@
 package org.yuzu.yuzu_emu.utils
 package org.yuzu.yuzu_emu.utils
 
 
 import org.yuzu.yuzu_emu.model.GameDir
 import org.yuzu.yuzu_emu.model.GameDir
+import org.yuzu.yuzu_emu.overlay.model.OverlayControlData
 
 
 object NativeConfig {
 object NativeConfig {
     /**
     /**
@@ -150,4 +151,21 @@ object NativeConfig {
      */
      */
     @Synchronized
     @Synchronized
     external fun setDisabledAddons(programId: String, disabledAddons: Array<String>)
     external fun setDisabledAddons(programId: String, disabledAddons: Array<String>)
+
+    /**
+     * Gets an array of [OverlayControlData] from settings
+     *
+     * @return An array of [OverlayControlData]
+     */
+    @Synchronized
+    external fun getOverlayControlData(): Array<OverlayControlData>
+
+    /**
+     * Clears the AndroidSettings::values.overlay_control_data array and replaces its values
+     * with [overlayControlData]
+     *
+     * @param overlayControlData Replacement array of [OverlayControlData]
+     */
+    @Synchronized
+    external fun setOverlayControlData(overlayControlData: Array<OverlayControlData>)
 }
 }

+ 9 - 0
src/android/app/src/main/jni/android_common/android_common.cpp

@@ -9,6 +9,7 @@
 #include <jni.h>
 #include <jni.h>
 
 
 #include "common/string_util.h"
 #include "common/string_util.h"
+#include "jni/id_cache.h"
 
 
 std::string GetJString(JNIEnv* env, jstring jstr) {
 std::string GetJString(JNIEnv* env, jstring jstr) {
     if (!jstr) {
     if (!jstr) {
@@ -33,3 +34,11 @@ jstring ToJString(JNIEnv* env, std::string_view str) {
 jstring ToJString(JNIEnv* env, std::u16string_view str) {
 jstring ToJString(JNIEnv* env, std::u16string_view str) {
     return ToJString(env, Common::UTF16ToUTF8(str));
     return ToJString(env, Common::UTF16ToUTF8(str));
 }
 }
+
+double GetJDouble(JNIEnv* env, jobject jdouble) {
+    return env->GetDoubleField(jdouble, IDCache::GetDoubleValueField());
+}
+
+jobject ToJDouble(JNIEnv* env, double value) {
+    return env->NewObject(IDCache::GetDoubleClass(), IDCache::GetDoubleConstructor(), value);
+}

+ 3 - 0
src/android/app/src/main/jni/android_common/android_common.h

@@ -10,3 +10,6 @@
 std::string GetJString(JNIEnv* env, jstring jstr);
 std::string GetJString(JNIEnv* env, jstring jstr);
 jstring ToJString(JNIEnv* env, std::string_view str);
 jstring ToJString(JNIEnv* env, std::string_view str);
 jstring ToJString(JNIEnv* env, std::u16string_view str);
 jstring ToJString(JNIEnv* env, std::u16string_view str);
+
+double GetJDouble(JNIEnv* env, jobject jdouble);
+jobject ToJDouble(JNIEnv* env, double value);

+ 62 - 0
src/android/app/src/main/jni/android_config.cpp

@@ -35,6 +35,7 @@ void AndroidConfig::ReadAndroidValues() {
     if (global) {
     if (global) {
         ReadAndroidUIValues();
         ReadAndroidUIValues();
         ReadUIValues();
         ReadUIValues();
+        ReadOverlayValues();
     }
     }
     ReadDriverValues();
     ReadDriverValues();
 }
 }
@@ -81,10 +82,42 @@ void AndroidConfig::ReadDriverValues() {
     EndGroup();
     EndGroup();
 }
 }
 
 
+void AndroidConfig::ReadOverlayValues() {
+    BeginGroup(Settings::TranslateCategory(Settings::Category::Overlay));
+
+    ReadCategory(Settings::Category::Overlay);
+
+    AndroidSettings::values.overlay_control_data.clear();
+    const int control_data_size = BeginArray("control_data");
+    for (int i = 0; i < control_data_size; ++i) {
+        SetArrayIndex(i);
+        AndroidSettings::OverlayControlData control_data;
+        control_data.id = ReadStringSetting(std::string("id"));
+        control_data.enabled = ReadBooleanSetting(std::string("enabled"));
+        control_data.landscape_position.first =
+            ReadDoubleSetting(std::string("landscape\\x_position"));
+        control_data.landscape_position.second =
+            ReadDoubleSetting(std::string("landscape\\y_position"));
+        control_data.portrait_position.first =
+            ReadDoubleSetting(std::string("portrait\\x_position"));
+        control_data.portrait_position.second =
+            ReadDoubleSetting(std::string("portrait\\y_position"));
+        control_data.foldable_position.first =
+            ReadDoubleSetting(std::string("foldable\\x_position"));
+        control_data.foldable_position.second =
+            ReadDoubleSetting(std::string("foldable\\y_position"));
+        AndroidSettings::values.overlay_control_data.push_back(control_data);
+    }
+    EndArray();
+
+    EndGroup();
+}
+
 void AndroidConfig::SaveAndroidValues() {
 void AndroidConfig::SaveAndroidValues() {
     if (global) {
     if (global) {
         SaveAndroidUIValues();
         SaveAndroidUIValues();
         SaveUIValues();
         SaveUIValues();
+        SaveOverlayValues();
     }
     }
     SaveDriverValues();
     SaveDriverValues();
 
 
@@ -131,6 +164,35 @@ void AndroidConfig::SaveDriverValues() {
     EndGroup();
     EndGroup();
 }
 }
 
 
+void AndroidConfig::SaveOverlayValues() {
+    BeginGroup(Settings::TranslateCategory(Settings::Category::Overlay));
+
+    WriteCategory(Settings::Category::Overlay);
+
+    BeginArray("control_data");
+    for (size_t i = 0; i < AndroidSettings::values.overlay_control_data.size(); ++i) {
+        SetArrayIndex(i);
+        const auto& control_data = AndroidSettings::values.overlay_control_data[i];
+        WriteStringSetting(std::string("id"), control_data.id);
+        WriteBooleanSetting(std::string("enabled"), control_data.enabled);
+        WriteDoubleSetting(std::string("landscape\\x_position"),
+                           control_data.landscape_position.first);
+        WriteDoubleSetting(std::string("landscape\\y_position"),
+                           control_data.landscape_position.second);
+        WriteDoubleSetting(std::string("portrait\\x_position"),
+                           control_data.portrait_position.first);
+        WriteDoubleSetting(std::string("portrait\\y_position"),
+                           control_data.portrait_position.second);
+        WriteDoubleSetting(std::string("foldable\\x_position"),
+                           control_data.foldable_position.first);
+        WriteDoubleSetting(std::string("foldable\\y_position"),
+                           control_data.foldable_position.second);
+    }
+    EndArray();
+
+    EndGroup();
+}
+
 std::vector<Settings::BasicSetting*>& AndroidConfig::FindRelevantList(Settings::Category category) {
 std::vector<Settings::BasicSetting*>& AndroidConfig::FindRelevantList(Settings::Category category) {
     auto& map = Settings::values.linkage.by_category;
     auto& map = Settings::values.linkage.by_category;
     if (map.contains(category)) {
     if (map.contains(category)) {

+ 2 - 0
src/android/app/src/main/jni/android_config.h

@@ -18,6 +18,7 @@ protected:
     void ReadAndroidValues();
     void ReadAndroidValues();
     void ReadAndroidUIValues();
     void ReadAndroidUIValues();
     void ReadDriverValues();
     void ReadDriverValues();
+    void ReadOverlayValues();
     void ReadHidbusValues() override {}
     void ReadHidbusValues() override {}
     void ReadDebugControlValues() override {}
     void ReadDebugControlValues() override {}
     void ReadPathValues() override;
     void ReadPathValues() override;
@@ -30,6 +31,7 @@ protected:
     void SaveAndroidValues();
     void SaveAndroidValues();
     void SaveAndroidUIValues();
     void SaveAndroidUIValues();
     void SaveDriverValues();
     void SaveDriverValues();
+    void SaveOverlayValues();
     void SaveHidbusValues() override {}
     void SaveHidbusValues() override {}
     void SaveDebugControlValues() override {}
     void SaveDebugControlValues() override {}
     void SavePathValues() override;
     void SavePathValues() override;

+ 25 - 0
src/android/app/src/main/jni/android_settings.h

@@ -14,6 +14,14 @@ struct GameDir {
     bool deep_scan = false;
     bool deep_scan = false;
 };
 };
 
 
+struct OverlayControlData {
+    std::string id;
+    bool enabled;
+    std::pair<double, double> landscape_position;
+    std::pair<double, double> portrait_position;
+    std::pair<double, double> foldable_position;
+};
+
 struct Values {
 struct Values {
     Settings::Linkage linkage;
     Settings::Linkage linkage;
 
 
@@ -38,6 +46,23 @@ struct Values {
     Settings::Setting<s32> theme_mode{linkage, -1, "theme_mode", Settings::Category::Android};
     Settings::Setting<s32> theme_mode{linkage, -1, "theme_mode", Settings::Category::Android};
     Settings::Setting<bool> black_backgrounds{linkage, false, "black_backgrounds",
     Settings::Setting<bool> black_backgrounds{linkage, false, "black_backgrounds",
                                               Settings::Category::Android};
                                               Settings::Category::Android};
+
+    // Input/performance overlay settings
+    std::vector<OverlayControlData> overlay_control_data;
+    Settings::Setting<s32> overlay_scale{linkage, 50, "control_scale", Settings::Category::Overlay};
+    Settings::Setting<s32> overlay_opacity{linkage, 100, "control_opacity",
+                                           Settings::Category::Overlay};
+
+    Settings::Setting<bool> joystick_rel_center{linkage, true, "joystick_rel_center",
+                                                Settings::Category::Overlay};
+    Settings::Setting<bool> dpad_slide{linkage, true, "dpad_slide", Settings::Category::Overlay};
+    Settings::Setting<bool> haptic_feedback{linkage, true, "haptic_feedback",
+                                            Settings::Category::Overlay};
+    Settings::Setting<bool> show_performance_overlay{linkage, true, "show_performance_overlay",
+                                                     Settings::Category::Overlay};
+    Settings::Setting<bool> show_input_overlay{linkage, true, "show_input_overlay",
+                                               Settings::Category::Overlay};
+    Settings::Setting<bool> touchscreen{linkage, true, "touchscreen", Settings::Category::Overlay};
 };
 };
 
 
 extern Values values;
 extern Values values;

+ 79 - 0
src/android/app/src/main/jni/id_cache.cpp

@@ -35,6 +35,18 @@ static jmethodID s_pair_constructor;
 static jfieldID s_pair_first_field;
 static jfieldID s_pair_first_field;
 static jfieldID s_pair_second_field;
 static jfieldID s_pair_second_field;
 
 
+static jclass s_overlay_control_data_class;
+static jmethodID s_overlay_control_data_constructor;
+static jfieldID s_overlay_control_data_id_field;
+static jfieldID s_overlay_control_data_enabled_field;
+static jfieldID s_overlay_control_data_landscape_position_field;
+static jfieldID s_overlay_control_data_portrait_position_field;
+static jfieldID s_overlay_control_data_foldable_position_field;
+
+static jclass s_double_class;
+static jmethodID s_double_constructor;
+static jfieldID s_double_value_field;
+
 static constexpr jint JNI_VERSION = JNI_VERSION_1_6;
 static constexpr jint JNI_VERSION = JNI_VERSION_1_6;
 
 
 namespace IDCache {
 namespace IDCache {
@@ -146,6 +158,46 @@ jfieldID GetPairSecondField() {
     return s_pair_second_field;
     return s_pair_second_field;
 }
 }
 
 
+jclass GetOverlayControlDataClass() {
+    return s_overlay_control_data_class;
+}
+
+jmethodID GetOverlayControlDataConstructor() {
+    return s_overlay_control_data_constructor;
+}
+
+jfieldID GetOverlayControlDataIdField() {
+    return s_overlay_control_data_id_field;
+}
+
+jfieldID GetOverlayControlDataEnabledField() {
+    return s_overlay_control_data_enabled_field;
+}
+
+jfieldID GetOverlayControlDataLandscapePositionField() {
+    return s_overlay_control_data_landscape_position_field;
+}
+
+jfieldID GetOverlayControlDataPortraitPositionField() {
+    return s_overlay_control_data_portrait_position_field;
+}
+
+jfieldID GetOverlayControlDataFoldablePositionField() {
+    return s_overlay_control_data_foldable_position_field;
+}
+
+jclass GetDoubleClass() {
+    return s_double_class;
+}
+
+jmethodID GetDoubleConstructor() {
+    return s_double_constructor;
+}
+
+jfieldID GetDoubleValueField() {
+    return s_double_value_field;
+}
+
 } // namespace IDCache
 } // namespace IDCache
 
 
 #ifdef __cplusplus
 #ifdef __cplusplus
@@ -207,6 +259,31 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) {
     s_pair_second_field = env->GetFieldID(pair_class, "second", "Ljava/lang/Object;");
     s_pair_second_field = env->GetFieldID(pair_class, "second", "Ljava/lang/Object;");
     env->DeleteLocalRef(pair_class);
     env->DeleteLocalRef(pair_class);
 
 
+    const jclass overlay_control_data_class =
+        env->FindClass("org/yuzu/yuzu_emu/overlay/model/OverlayControlData");
+    s_overlay_control_data_class =
+        reinterpret_cast<jclass>(env->NewGlobalRef(overlay_control_data_class));
+    s_overlay_control_data_constructor =
+        env->GetMethodID(overlay_control_data_class, "<init>",
+                         "(Ljava/lang/String;ZLkotlin/Pair;Lkotlin/Pair;Lkotlin/Pair;)V");
+    s_overlay_control_data_id_field =
+        env->GetFieldID(overlay_control_data_class, "id", "Ljava/lang/String;");
+    s_overlay_control_data_enabled_field =
+        env->GetFieldID(overlay_control_data_class, "enabled", "Z");
+    s_overlay_control_data_landscape_position_field =
+        env->GetFieldID(overlay_control_data_class, "landscapePosition", "Lkotlin/Pair;");
+    s_overlay_control_data_portrait_position_field =
+        env->GetFieldID(overlay_control_data_class, "portraitPosition", "Lkotlin/Pair;");
+    s_overlay_control_data_foldable_position_field =
+        env->GetFieldID(overlay_control_data_class, "foldablePosition", "Lkotlin/Pair;");
+    env->DeleteLocalRef(overlay_control_data_class);
+
+    const jclass double_class = env->FindClass("java/lang/Double");
+    s_double_class = reinterpret_cast<jclass>(env->NewGlobalRef(double_class));
+    s_double_constructor = env->GetMethodID(double_class, "<init>", "(D)V");
+    s_double_value_field = env->GetFieldID(double_class, "value", "D");
+    env->DeleteLocalRef(double_class);
+
     // Initialize Android Storage
     // Initialize Android Storage
     Common::FS::Android::RegisterCallbacks(env, s_native_library_class);
     Common::FS::Android::RegisterCallbacks(env, s_native_library_class);
 
 
@@ -231,6 +308,8 @@ void JNI_OnUnload(JavaVM* vm, void* reserved) {
     env->DeleteGlobalRef(s_game_class);
     env->DeleteGlobalRef(s_game_class);
     env->DeleteGlobalRef(s_string_class);
     env->DeleteGlobalRef(s_string_class);
     env->DeleteGlobalRef(s_pair_class);
     env->DeleteGlobalRef(s_pair_class);
+    env->DeleteGlobalRef(s_overlay_control_data_class);
+    env->DeleteGlobalRef(s_double_class);
 
 
     // UnInitialize applets
     // UnInitialize applets
     SoftwareKeyboard::CleanupJNI(env);
     SoftwareKeyboard::CleanupJNI(env);

+ 12 - 0
src/android/app/src/main/jni/id_cache.h

@@ -35,4 +35,16 @@ jmethodID GetPairConstructor();
 jfieldID GetPairFirstField();
 jfieldID GetPairFirstField();
 jfieldID GetPairSecondField();
 jfieldID GetPairSecondField();
 
 
+jclass GetOverlayControlDataClass();
+jmethodID GetOverlayControlDataConstructor();
+jfieldID GetOverlayControlDataIdField();
+jfieldID GetOverlayControlDataEnabledField();
+jfieldID GetOverlayControlDataLandscapePositionField();
+jfieldID GetOverlayControlDataPortraitPositionField();
+jfieldID GetOverlayControlDataFoldablePositionField();
+
+jclass GetDoubleClass();
+jmethodID GetDoubleConstructor();
+jfieldID GetDoubleValueField();
+
 } // namespace IDCache
 } // namespace IDCache

+ 70 - 0
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
     Settings::values.disabled_addons[program_id] = disabled_addons;
     Settings::values.disabled_addons[program_id] = disabled_addons;
 }
 }
 
 
+jobjectArray Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getOverlayControlData(JNIEnv* env,
+                                                                              jobject obj) {
+    jobjectArray joverlayControlDataArray =
+        env->NewObjectArray(AndroidSettings::values.overlay_control_data.size(),
+                            IDCache::GetOverlayControlDataClass(), nullptr);
+    for (size_t i = 0; i < AndroidSettings::values.overlay_control_data.size(); ++i) {
+        const auto& control_data = AndroidSettings::values.overlay_control_data[i];
+        jobject jlandscapePosition =
+            env->NewObject(IDCache::GetPairClass(), IDCache::GetPairConstructor(),
+                           ToJDouble(env, control_data.landscape_position.first),
+                           ToJDouble(env, control_data.landscape_position.second));
+        jobject jportraitPosition =
+            env->NewObject(IDCache::GetPairClass(), IDCache::GetPairConstructor(),
+                           ToJDouble(env, control_data.portrait_position.first),
+                           ToJDouble(env, control_data.portrait_position.second));
+        jobject jfoldablePosition =
+            env->NewObject(IDCache::GetPairClass(), IDCache::GetPairConstructor(),
+                           ToJDouble(env, control_data.foldable_position.first),
+                           ToJDouble(env, control_data.foldable_position.second));
+
+        jobject jcontrolData = env->NewObject(
+            IDCache::GetOverlayControlDataClass(), IDCache::GetOverlayControlDataConstructor(),
+            ToJString(env, control_data.id), control_data.enabled, jlandscapePosition,
+            jportraitPosition, jfoldablePosition);
+        env->SetObjectArrayElement(joverlayControlDataArray, i, jcontrolData);
+    }
+    return joverlayControlDataArray;
+}
+
+void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setOverlayControlData(
+    JNIEnv* env, jobject obj, jobjectArray joverlayControlDataArray) {
+    AndroidSettings::values.overlay_control_data.clear();
+    int size = env->GetArrayLength(joverlayControlDataArray);
+
+    if (size == 0) {
+        return;
+    }
+
+    for (int i = 0; i < size; ++i) {
+        jobject joverlayControlData = env->GetObjectArrayElement(joverlayControlDataArray, i);
+        jstring jidString = static_cast<jstring>(
+            env->GetObjectField(joverlayControlData, IDCache::GetOverlayControlDataIdField()));
+        bool enabled = static_cast<bool>(env->GetBooleanField(
+            joverlayControlData, IDCache::GetOverlayControlDataEnabledField()));
+
+        jobject jlandscapePosition = env->GetObjectField(
+            joverlayControlData, IDCache::GetOverlayControlDataLandscapePositionField());
+        std::pair<double, double> landscape_position = std::make_pair(
+            GetJDouble(env, env->GetObjectField(jlandscapePosition, IDCache::GetPairFirstField())),
+            GetJDouble(env,
+                       env->GetObjectField(jlandscapePosition, IDCache::GetPairSecondField())));
+
+        jobject jportraitPosition = env->GetObjectField(
+            joverlayControlData, IDCache::GetOverlayControlDataPortraitPositionField());
+        std::pair<double, double> portrait_position = std::make_pair(
+            GetJDouble(env, env->GetObjectField(jportraitPosition, IDCache::GetPairFirstField())),
+            GetJDouble(env, env->GetObjectField(jportraitPosition, IDCache::GetPairSecondField())));
+
+        jobject jfoldablePosition = env->GetObjectField(
+            joverlayControlData, IDCache::GetOverlayControlDataFoldablePositionField());
+        std::pair<double, double> foldable_position = std::make_pair(
+            GetJDouble(env, env->GetObjectField(jfoldablePosition, IDCache::GetPairFirstField())),
+            GetJDouble(env, env->GetObjectField(jfoldablePosition, IDCache::GetPairSecondField())));
+
+        AndroidSettings::values.overlay_control_data.push_back(AndroidSettings::OverlayControlData{
+            GetJString(env, jidString), enabled, landscape_position, portrait_position,
+            foldable_position});
+    }
+}
+
 } // extern "C"
 } // extern "C"

+ 5 - 5
src/android/app/src/main/res/values/arrays.xml

@@ -212,19 +212,19 @@
         <item>B</item>
         <item>B</item>
         <item>X</item>
         <item>X</item>
         <item>Y</item>
         <item>Y</item>
+        <item>+</item>
+        <item>-</item>
+        <item>@string/gamepad_home</item>
+        <item>@string/gamepad_screenshot</item>
         <item>L</item>
         <item>L</item>
         <item>R</item>
         <item>R</item>
         <item>ZL</item>
         <item>ZL</item>
         <item>ZR</item>
         <item>ZR</item>
-        <item>+</item>
-        <item>-</item>
-        <item>@string/gamepad_d_pad</item>
         <item>@string/gamepad_left_stick</item>
         <item>@string/gamepad_left_stick</item>
         <item>@string/gamepad_right_stick</item>
         <item>@string/gamepad_right_stick</item>
         <item>L3</item>
         <item>L3</item>
         <item>R3</item>
         <item>R3</item>
-        <item>@string/gamepad_home</item>
-        <item>@string/gamepad_screenshot</item>
+        <item>@string/gamepad_d_pad</item>
     </string-array>
     </string-array>
 
 
     <string-array name="themeEntries">
     <string-array name="themeEntries">

+ 102 - 102
src/android/app/src/main/res/values/integers.xml

@@ -3,111 +3,111 @@
     <integer name="grid_columns">1</integer>
     <integer name="grid_columns">1</integer>
 
 
     <!-- Default SWITCH landscape layout -->
     <!-- Default SWITCH landscape layout -->
-    <integer name="SWITCH_BUTTON_A_X">760</integer>
-    <integer name="SWITCH_BUTTON_A_Y">790</integer>
-    <integer name="SWITCH_BUTTON_B_X">710</integer>
-    <integer name="SWITCH_BUTTON_B_Y">900</integer>
-    <integer name="SWITCH_BUTTON_X_X">710</integer>
-    <integer name="SWITCH_BUTTON_X_Y">680</integer>
-    <integer name="SWITCH_BUTTON_Y_X">660</integer>
-    <integer name="SWITCH_BUTTON_Y_Y">790</integer>
-    <integer name="SWITCH_STICK_L_X">100</integer>
-    <integer name="SWITCH_STICK_L_Y">670</integer>
-    <integer name="SWITCH_STICK_R_X">900</integer>
-    <integer name="SWITCH_STICK_R_Y">670</integer>
-    <integer name="SWITCH_TRIGGER_L_X">70</integer>
-    <integer name="SWITCH_TRIGGER_L_Y">220</integer>
-    <integer name="SWITCH_TRIGGER_R_X">930</integer>
-    <integer name="SWITCH_TRIGGER_R_Y">220</integer>
-    <integer name="SWITCH_TRIGGER_ZL_X">70</integer>
-    <integer name="SWITCH_TRIGGER_ZL_Y">90</integer>
-    <integer name="SWITCH_TRIGGER_ZR_X">930</integer>
-    <integer name="SWITCH_TRIGGER_ZR_Y">90</integer>
-    <integer name="SWITCH_BUTTON_MINUS_X">460</integer>
-    <integer name="SWITCH_BUTTON_MINUS_Y">950</integer>
-    <integer name="SWITCH_BUTTON_PLUS_X">540</integer>
-    <integer name="SWITCH_BUTTON_PLUS_Y">950</integer>
-    <integer name="SWITCH_BUTTON_HOME_X">600</integer>
-    <integer name="SWITCH_BUTTON_HOME_Y">950</integer>
-    <integer name="SWITCH_BUTTON_CAPTURE_X">400</integer>
-    <integer name="SWITCH_BUTTON_CAPTURE_Y">950</integer>
-    <integer name="SWITCH_BUTTON_DPAD_X">260</integer>
-    <integer name="SWITCH_BUTTON_DPAD_Y">790</integer>
-    <integer name="SWITCH_BUTTON_STICK_L_X">870</integer>
-    <integer name="SWITCH_BUTTON_STICK_L_Y">400</integer>
-    <integer name="SWITCH_BUTTON_STICK_R_X">960</integer>
-    <integer name="SWITCH_BUTTON_STICK_R_Y">430</integer>
+    <integer name="BUTTON_A_X">760</integer>
+    <integer name="BUTTON_A_Y">790</integer>
+    <integer name="BUTTON_B_X">710</integer>
+    <integer name="BUTTON_B_Y">900</integer>
+    <integer name="BUTTON_X_X">710</integer>
+    <integer name="BUTTON_X_Y">680</integer>
+    <integer name="BUTTON_Y_X">660</integer>
+    <integer name="BUTTON_Y_Y">790</integer>
+    <integer name="BUTTON_PLUS_X">540</integer>
+    <integer name="BUTTON_PLUS_Y">950</integer>
+    <integer name="BUTTON_MINUS_X">460</integer>
+    <integer name="BUTTON_MINUS_Y">950</integer>
+    <integer name="BUTTON_HOME_X">600</integer>
+    <integer name="BUTTON_HOME_Y">950</integer>
+    <integer name="BUTTON_CAPTURE_X">400</integer>
+    <integer name="BUTTON_CAPTURE_Y">950</integer>
+    <integer name="BUTTON_L_X">70</integer>
+    <integer name="BUTTON_L_Y">220</integer>
+    <integer name="BUTTON_R_X">930</integer>
+    <integer name="BUTTON_R_Y">220</integer>
+    <integer name="BUTTON_ZL_X">70</integer>
+    <integer name="BUTTON_ZL_Y">90</integer>
+    <integer name="BUTTON_ZR_X">930</integer>
+    <integer name="BUTTON_ZR_Y">90</integer>
+    <integer name="BUTTON_STICK_L_X">870</integer>
+    <integer name="BUTTON_STICK_L_Y">400</integer>
+    <integer name="BUTTON_STICK_R_X">960</integer>
+    <integer name="BUTTON_STICK_R_Y">430</integer>
+    <integer name="STICK_L_X">100</integer>
+    <integer name="STICK_L_Y">670</integer>
+    <integer name="STICK_R_X">900</integer>
+    <integer name="STICK_R_Y">670</integer>
+    <integer name="COMBINED_DPAD_X">260</integer>
+    <integer name="COMBINED_DPAD_Y">790</integer>
 
 
     <!-- Default SWITCH portrait layout -->
     <!-- Default SWITCH portrait layout -->
-    <integer name="SWITCH_BUTTON_A_X_PORTRAIT">840</integer>
-    <integer name="SWITCH_BUTTON_A_Y_PORTRAIT">840</integer>
-    <integer name="SWITCH_BUTTON_B_X_PORTRAIT">740</integer>
-    <integer name="SWITCH_BUTTON_B_Y_PORTRAIT">880</integer>
-    <integer name="SWITCH_BUTTON_X_X_PORTRAIT">740</integer>
-    <integer name="SWITCH_BUTTON_X_Y_PORTRAIT">800</integer>
-    <integer name="SWITCH_BUTTON_Y_X_PORTRAIT">640</integer>
-    <integer name="SWITCH_BUTTON_Y_Y_PORTRAIT">840</integer>
-    <integer name="SWITCH_STICK_L_X_PORTRAIT">180</integer>
-    <integer name="SWITCH_STICK_L_Y_PORTRAIT">660</integer>
-    <integer name="SWITCH_STICK_R_X_PORTRAIT">820</integer>
-    <integer name="SWITCH_STICK_R_Y_PORTRAIT">660</integer>
-    <integer name="SWITCH_TRIGGER_L_X_PORTRAIT">140</integer>
-    <integer name="SWITCH_TRIGGER_L_Y_PORTRAIT">260</integer>
-    <integer name="SWITCH_TRIGGER_R_X_PORTRAIT">860</integer>
-    <integer name="SWITCH_TRIGGER_R_Y_PORTRAIT">260</integer>
-    <integer name="SWITCH_TRIGGER_ZL_X_PORTRAIT">140</integer>
-    <integer name="SWITCH_TRIGGER_ZL_Y_PORTRAIT">200</integer>
-    <integer name="SWITCH_TRIGGER_ZR_X_PORTRAIT">860</integer>
-    <integer name="SWITCH_TRIGGER_ZR_Y_PORTRAIT">200</integer>
-    <integer name="SWITCH_BUTTON_MINUS_X_PORTRAIT">440</integer>
-    <integer name="SWITCH_BUTTON_MINUS_Y_PORTRAIT">950</integer>
-    <integer name="SWITCH_BUTTON_PLUS_X_PORTRAIT">560</integer>
-    <integer name="SWITCH_BUTTON_PLUS_Y_PORTRAIT">950</integer>
-    <integer name="SWITCH_BUTTON_HOME_X_PORTRAIT">680</integer>
-    <integer name="SWITCH_BUTTON_HOME_Y_PORTRAIT">950</integer>
-    <integer name="SWITCH_BUTTON_CAPTURE_X_PORTRAIT">320</integer>
-    <integer name="SWITCH_BUTTON_CAPTURE_Y_PORTRAIT">950</integer>
-    <integer name="SWITCH_BUTTON_DPAD_X_PORTRAIT">240</integer>
-    <integer name="SWITCH_BUTTON_DPAD_Y_PORTRAIT">840</integer>
-    <integer name="SWITCH_BUTTON_STICK_L_X_PORTRAIT">730</integer>
-    <integer name="SWITCH_BUTTON_STICK_L_Y_PORTRAIT">510</integer>
-    <integer name="SWITCH_BUTTON_STICK_R_X_PORTRAIT">900</integer>
-    <integer name="SWITCH_BUTTON_STICK_R_Y_PORTRAIT">540</integer>
+    <integer name="BUTTON_A_X_PORTRAIT">840</integer>
+    <integer name="BUTTON_A_Y_PORTRAIT">840</integer>
+    <integer name="BUTTON_B_X_PORTRAIT">740</integer>
+    <integer name="BUTTON_B_Y_PORTRAIT">880</integer>
+    <integer name="BUTTON_X_X_PORTRAIT">740</integer>
+    <integer name="BUTTON_X_Y_PORTRAIT">800</integer>
+    <integer name="BUTTON_Y_X_PORTRAIT">640</integer>
+    <integer name="BUTTON_Y_Y_PORTRAIT">840</integer>
+    <integer name="BUTTON_PLUS_Y_PORTRAIT">950</integer>
+    <integer name="BUTTON_MINUS_X_PORTRAIT">440</integer>
+    <integer name="BUTTON_MINUS_Y_PORTRAIT">950</integer>
+    <integer name="BUTTON_HOME_X_PORTRAIT">680</integer>
+    <integer name="BUTTON_HOME_Y_PORTRAIT">950</integer>
+    <integer name="BUTTON_CAPTURE_X_PORTRAIT">320</integer>
+    <integer name="BUTTON_CAPTURE_Y_PORTRAIT">950</integer>
+    <integer name="BUTTON_L_X_PORTRAIT">140</integer>
+    <integer name="BUTTON_L_Y_PORTRAIT">260</integer>
+    <integer name="BUTTON_R_X_PORTRAIT">860</integer>
+    <integer name="BUTTON_R_Y_PORTRAIT">260</integer>
+    <integer name="BUTTON_ZL_X_PORTRAIT">140</integer>
+    <integer name="BUTTON_ZL_Y_PORTRAIT">200</integer>
+    <integer name="BUTTON_ZR_X_PORTRAIT">860</integer>
+    <integer name="BUTTON_ZR_Y_PORTRAIT">200</integer>
+    <integer name="BUTTON_PLUS_X_PORTRAIT">560</integer>
+    <integer name="BUTTON_STICK_L_X_PORTRAIT">730</integer>
+    <integer name="BUTTON_STICK_L_Y_PORTRAIT">510</integer>
+    <integer name="BUTTON_STICK_R_X_PORTRAIT">900</integer>
+    <integer name="BUTTON_STICK_R_Y_PORTRAIT">540</integer>
+    <integer name="STICK_L_X_PORTRAIT">180</integer>
+    <integer name="STICK_L_Y_PORTRAIT">660</integer>
+    <integer name="STICK_R_X_PORTRAIT">820</integer>
+    <integer name="STICK_R_Y_PORTRAIT">660</integer>
+    <integer name="COMBINED_DPAD_X_PORTRAIT">240</integer>
+    <integer name="COMBINED_DPAD_Y_PORTRAIT">840</integer>
 
 
     <!-- Default SWITCH foldable layout -->
     <!-- Default SWITCH foldable layout -->
-    <integer name="SWITCH_BUTTON_A_X_FOLDABLE">840</integer>
-    <integer name="SWITCH_BUTTON_A_Y_FOLDABLE">390</integer>
-    <integer name="SWITCH_BUTTON_B_X_FOLDABLE">740</integer>
-    <integer name="SWITCH_BUTTON_B_Y_FOLDABLE">430</integer>
-    <integer name="SWITCH_BUTTON_X_X_FOLDABLE">740</integer>
-    <integer name="SWITCH_BUTTON_X_Y_FOLDABLE">350</integer>
-    <integer name="SWITCH_BUTTON_Y_X_FOLDABLE">640</integer>
-    <integer name="SWITCH_BUTTON_Y_Y_FOLDABLE">390</integer>
-    <integer name="SWITCH_STICK_L_X_FOLDABLE">180</integer>
-    <integer name="SWITCH_STICK_L_Y_FOLDABLE">250</integer>
-    <integer name="SWITCH_STICK_R_X_FOLDABLE">820</integer>
-    <integer name="SWITCH_STICK_R_Y_FOLDABLE">250</integer>
-    <integer name="SWITCH_TRIGGER_L_X_FOLDABLE">140</integer>
-    <integer name="SWITCH_TRIGGER_L_Y_FOLDABLE">130</integer>
-    <integer name="SWITCH_TRIGGER_R_X_FOLDABLE">860</integer>
-    <integer name="SWITCH_TRIGGER_R_Y_FOLDABLE">130</integer>
-    <integer name="SWITCH_TRIGGER_ZL_X_FOLDABLE">140</integer>
-    <integer name="SWITCH_TRIGGER_ZL_Y_FOLDABLE">70</integer>
-    <integer name="SWITCH_TRIGGER_ZR_X_FOLDABLE">860</integer>
-    <integer name="SWITCH_TRIGGER_ZR_Y_FOLDABLE">70</integer>
-    <integer name="SWITCH_BUTTON_MINUS_X_FOLDABLE">440</integer>
-    <integer name="SWITCH_BUTTON_MINUS_Y_FOLDABLE">470</integer>
-    <integer name="SWITCH_BUTTON_PLUS_X_FOLDABLE">560</integer>
-    <integer name="SWITCH_BUTTON_PLUS_Y_FOLDABLE">470</integer>
-    <integer name="SWITCH_BUTTON_HOME_X_FOLDABLE">680</integer>
-    <integer name="SWITCH_BUTTON_HOME_Y_FOLDABLE">470</integer>
-    <integer name="SWITCH_BUTTON_CAPTURE_X_FOLDABLE">320</integer>
-    <integer name="SWITCH_BUTTON_CAPTURE_Y_FOLDABLE">470</integer>
-    <integer name="SWITCH_BUTTON_DPAD_X_FOLDABLE">240</integer>
-    <integer name="SWITCH_BUTTON_DPAD_Y_FOLDABLE">390</integer>
-    <integer name="SWITCH_BUTTON_STICK_L_X_FOLDABLE">550</integer>
-    <integer name="SWITCH_BUTTON_STICK_L_Y_FOLDABLE">210</integer>
-    <integer name="SWITCH_BUTTON_STICK_R_X_FOLDABLE">550</integer>
-    <integer name="SWITCH_BUTTON_STICK_R_Y_FOLDABLE">280</integer>
+    <integer name="BUTTON_A_X_FOLDABLE">840</integer>
+    <integer name="BUTTON_A_Y_FOLDABLE">390</integer>
+    <integer name="BUTTON_B_X_FOLDABLE">740</integer>
+    <integer name="BUTTON_B_Y_FOLDABLE">430</integer>
+    <integer name="BUTTON_X_X_FOLDABLE">740</integer>
+    <integer name="BUTTON_X_Y_FOLDABLE">350</integer>
+    <integer name="BUTTON_Y_X_FOLDABLE">640</integer>
+    <integer name="BUTTON_Y_Y_FOLDABLE">390</integer>
+    <integer name="BUTTON_PLUS_X_FOLDABLE">560</integer>
+    <integer name="BUTTON_PLUS_Y_FOLDABLE">470</integer>
+    <integer name="BUTTON_MINUS_X_FOLDABLE">440</integer>
+    <integer name="BUTTON_MINUS_Y_FOLDABLE">470</integer>
+    <integer name="BUTTON_HOME_X_FOLDABLE">680</integer>
+    <integer name="BUTTON_HOME_Y_FOLDABLE">470</integer>
+    <integer name="BUTTON_CAPTURE_X_FOLDABLE">320</integer>
+    <integer name="BUTTON_CAPTURE_Y_FOLDABLE">470</integer>
+    <integer name="BUTTON_L_X_FOLDABLE">140</integer>
+    <integer name="BUTTON_L_Y_FOLDABLE">130</integer>
+    <integer name="BUTTON_R_X_FOLDABLE">860</integer>
+    <integer name="BUTTON_R_Y_FOLDABLE">130</integer>
+    <integer name="BUTTON_ZL_X_FOLDABLE">140</integer>
+    <integer name="BUTTON_ZL_Y_FOLDABLE">70</integer>
+    <integer name="BUTTON_ZR_X_FOLDABLE">860</integer>
+    <integer name="BUTTON_ZR_Y_FOLDABLE">70</integer>
+    <integer name="BUTTON_STICK_L_X_FOLDABLE">550</integer>
+    <integer name="BUTTON_STICK_L_Y_FOLDABLE">210</integer>
+    <integer name="BUTTON_STICK_R_X_FOLDABLE">550</integer>
+    <integer name="BUTTON_STICK_R_Y_FOLDABLE">280</integer>
+    <integer name="STICK_L_X_FOLDABLE">180</integer>
+    <integer name="STICK_L_Y_FOLDABLE">250</integer>
+    <integer name="STICK_R_X_FOLDABLE">820</integer>
+    <integer name="STICK_R_Y_FOLDABLE">250</integer>
+    <integer name="COMBINED_DPAD_X_FOLDABLE">240</integer>
+    <integer name="COMBINED_DPAD_Y_FOLDABLE">390</integer>
 
 
 </resources>
 </resources>

+ 2 - 0
src/common/settings.cpp

@@ -199,6 +199,8 @@ const char* TranslateCategory(Category category) {
     case Category::CpuDebug:
     case Category::CpuDebug:
     case Category::CpuUnsafe:
     case Category::CpuUnsafe:
         return "Cpu";
         return "Cpu";
+    case Category::Overlay:
+        return "Overlay";
     case Category::Renderer:
     case Category::Renderer:
     case Category::RendererAdvanced:
     case Category::RendererAdvanced:
     case Category::RendererDebug:
     case Category::RendererDebug:

+ 1 - 0
src/common/settings_common.h

@@ -18,6 +18,7 @@ enum class Category : u32 {
     Cpu,
     Cpu,
     CpuDebug,
     CpuDebug,
     CpuUnsafe,
     CpuUnsafe,
+    Overlay,
     Renderer,
     Renderer,
     RendererAdvanced,
     RendererAdvanced,
     RendererDebug,
     RendererDebug,

Некоторые файлы не были показаны из-за большого количества измененных файлов