diff options
Diffstat (limited to 'src')
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 { id("com.android.application") id("org.jetbrains.kotlin.android") id("kotlin-parcelize") - kotlin("plugin.serialization") version "1.8.21" + kotlin("plugin.serialization") version "1.9.20" id("androidx.navigation.safeargs.kotlin") id("org.jlleitschuh.gradle.ktlint") version "11.4.0" } 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 import org.yuzu.yuzu_emu.utils.InputHandler import org.yuzu.yuzu_emu.utils.Log 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.ThemeHelper import java.text.NumberFormat @@ -170,6 +171,11 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener { stopMotionSensorListener() } + override fun onStop() { + super.onStop() + NativeConfig.saveGlobalConfig() + } + override fun onUserLeaveHint() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { 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 { RENDERER_REACTIVE_FLUSHING("use_reactive_flushing"), RENDERER_DEBUG("debug"), PICTURE_IN_PICTURE("picture_in_picture"), - USE_CUSTOM_RTC("custom_rtc_enabled"); + USE_CUSTOM_RTC("custom_rtc_enabled"), + 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 = 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 { RENDERER_SCREEN_LAYOUT("screen_layout"), RENDERER_ASPECT_RATIO("aspect_ratio"), AUDIO_OUTPUT_ENGINE("output_engine"), - MAX_ANISOTROPY("max_anisotropy"); + MAX_ANISOTROPY("max_anisotropy"), + THEME("theme"), + THEME_MODE("theme_mode"), + OVERLAY_SCALE("control_scale"), + OVERLAY_OPACITY("control_opacity"); override fun getInt(needsGlobal: Boolean): Int = NativeConfig.getInt(key, needsGlobal) 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 { SECTION_DEBUG(R.string.preferences_debug); } + const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch" 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_OPACITY = "controlOpacity" const val PREF_TOUCH_ENABLED = "isTouchEnabled" @@ -47,23 +39,12 @@ object Settings { const val PREF_BUTTON_STICK_R = "buttonToggle14" const val PREF_BUTTON_HOME = "buttonToggle15" const val PREF_BUTTON_SCREENSHOT = "buttonToggle16" - const val PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER = "EmulationMenuSettings_JoystickRelCenter" const val PREF_MENU_SETTINGS_DPAD_SLIDE = "EmulationMenuSettings_DpadSlideEnable" const val PREF_MENU_SETTINGS_HAPTICS = "EmulationMenuSettings_Haptics" const val PREF_MENU_SETTINGS_SHOW_FPS = "EmulationMenuSettings_ShowFps" const val PREF_MENU_SETTINGS_SHOW_OVERLAY = "EmulationMenuSettings_ShowOverlay" - - 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( - PREF_OVERLAY_VERSION, - PREF_CONTROL_SCALE, - PREF_CONTROL_OPACITY, - PREF_TOUCH_ENABLED, PREF_BUTTON_A, PREF_BUTTON_B, PREF_BUTTON_X, @@ -83,6 +64,21 @@ object Settings { 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_MobilePortrait = 4 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 @@ package org.yuzu.yuzu_emu.features.settings.ui -import android.content.SharedPreferences import android.os.Build import android.widget.Toast -import androidx.preference.PreferenceManager import org.yuzu.yuzu_emu.NativeLibrary import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.YuzuApplication @@ -29,9 +27,6 @@ class SettingsFragmentPresenter( ) { private var settingsList = ArrayList<SettingsItem>() - private val preferences: SharedPreferences - get() = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) - // Extension for altering settings list based on each setting's properties fun ArrayList<SettingsItem>.add(key: String) { val item = SettingsItem.settingsItems[key]!! @@ -170,25 +165,19 @@ class SettingsFragmentPresenter( private fun addThemeSettings(sl: ArrayList<SettingsItem>) { sl.apply { val theme: AbstractIntSetting = object : AbstractIntSetting { - override fun getInt(needsGlobal: Boolean): Int = - preferences.getInt(Settings.PREF_THEME, 0) - + override fun getInt(needsGlobal: Boolean): Int = IntSetting.THEME.getInt() override fun setInt(value: Int) { - preferences.edit() - .putInt(Settings.PREF_THEME, value) - .apply() + IntSetting.THEME.setInt(value) settingsViewModel.setShouldRecreate(true) } - override val key: String = Settings.PREF_THEME - override val isRuntimeModifiable: Boolean = false - override fun getValueAsString(needsGlobal: Boolean): String = getInt().toString() - override val defaultValue: Int = 0 - override fun reset() { - preferences.edit() - .putInt(Settings.PREF_THEME, defaultValue) - .apply() - } + override val key: String = IntSetting.THEME.key + override val isRuntimeModifiable: Boolean = IntSetting.THEME.isRuntimeModifiable + override fun getValueAsString(needsGlobal: Boolean): String = + IntSetting.THEME.getValueAsString() + + override val defaultValue: Int = IntSetting.THEME.defaultValue + override fun reset() = IntSetting.THEME.setInt(defaultValue) } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { @@ -214,24 +203,22 @@ class SettingsFragmentPresenter( } val themeMode: AbstractIntSetting = object : AbstractIntSetting { - override fun getInt(needsGlobal: Boolean): Int = - preferences.getInt(Settings.PREF_THEME_MODE, -1) - + override fun getInt(needsGlobal: Boolean): Int = IntSetting.THEME_MODE.getInt() override fun setInt(value: Int) { - preferences.edit() - .putInt(Settings.PREF_THEME_MODE, value) - .apply() + IntSetting.THEME_MODE.setInt(value) settingsViewModel.setShouldRecreate(true) } - override val key: String = Settings.PREF_THEME_MODE - override val isRuntimeModifiable: Boolean = false - override fun getValueAsString(needsGlobal: Boolean): String = getInt().toString() - override val defaultValue: Int = -1 + override val key: String = IntSetting.THEME_MODE.key + override val isRuntimeModifiable: Boolean = + IntSetting.THEME_MODE.isRuntimeModifiable + + override fun getValueAsString(needsGlobal: Boolean): String = + IntSetting.THEME_MODE.getValueAsString() + + override val defaultValue: Int = IntSetting.THEME_MODE.defaultValue override fun reset() { - preferences.edit() - .putInt(Settings.PREF_BLACK_BACKGROUNDS, defaultValue) - .apply() + IntSetting.THEME_MODE.setInt(defaultValue) settingsViewModel.setShouldRecreate(true) } } @@ -248,25 +235,24 @@ class SettingsFragmentPresenter( val blackBackgrounds: AbstractBooleanSetting = object : AbstractBooleanSetting { override fun getBoolean(needsGlobal: Boolean): Boolean = - preferences.getBoolean(Settings.PREF_BLACK_BACKGROUNDS, false) + BooleanSetting.BLACK_BACKGROUNDS.getBoolean() override fun setBoolean(value: Boolean) { - preferences.edit() - .putBoolean(Settings.PREF_BLACK_BACKGROUNDS, value) - .apply() + BooleanSetting.BLACK_BACKGROUNDS.setBoolean(value) settingsViewModel.setShouldRecreate(true) } - override val key: String = Settings.PREF_BLACK_BACKGROUNDS - override val isRuntimeModifiable: Boolean = false + override val key: String = BooleanSetting.BLACK_BACKGROUNDS.key + override val isRuntimeModifiable: Boolean = + BooleanSetting.BLACK_BACKGROUNDS.isRuntimeModifiable + override fun getValueAsString(needsGlobal: Boolean): String = - getBoolean().toString() + BooleanSetting.BLACK_BACKGROUNDS.getValueAsString() - override val defaultValue: Boolean = false + override val defaultValue: Boolean = BooleanSetting.BLACK_BACKGROUNDS.defaultValue override fun reset() { - preferences.edit() - .putBoolean(Settings.PREF_BLACK_BACKGROUNDS, defaultValue) - .apply() + BooleanSetting.BLACK_BACKGROUNDS + .setBoolean(BooleanSetting.BLACK_BACKGROUNDS.defaultValue) settingsViewModel.setShouldRecreate(true) } } 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 import android.app.AlertDialog import android.content.Context import android.content.DialogInterface -import android.content.SharedPreferences import android.content.pm.ActivityInfo import android.content.res.Configuration import android.net.Uri @@ -33,7 +32,6 @@ import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import androidx.navigation.findNavController import androidx.navigation.fragment.navArgs -import androidx.preference.PreferenceManager import androidx.window.layout.FoldingFeature import androidx.window.layout.WindowInfoTracker import androidx.window.layout.WindowLayoutInfo @@ -46,22 +44,22 @@ import kotlinx.coroutines.launch import org.yuzu.yuzu_emu.HomeNavigationDirections import org.yuzu.yuzu_emu.NativeLibrary import org.yuzu.yuzu_emu.R -import org.yuzu.yuzu_emu.YuzuApplication import org.yuzu.yuzu_emu.activities.EmulationActivity import org.yuzu.yuzu_emu.databinding.DialogOverlayAdjustBinding 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.Settings import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile import org.yuzu.yuzu_emu.model.DriverViewModel import org.yuzu.yuzu_emu.model.Game 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 java.lang.NullPointerException class EmulationFragment : Fragment(), SurfaceHolder.Callback { - private lateinit var preferences: SharedPreferences private lateinit var emulationState: EmulationState private var emulationActivity: EmulationActivity? = 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. retainInstance = true - preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) emulationState = EmulationState(game.path) } @@ -382,24 +379,25 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { } updateScreenLayout() + val showInputOverlay = BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean() if (emulationActivity?.isInPictureInPictureMode == true) { if (binding.drawerLayout.isOpen) { binding.drawerLayout.close() } - if (EmulationMenuSettings.showOverlay) { + if (showInputOverlay) { binding.surfaceInputOverlay.visibility = View.INVISIBLE } } else { - if (EmulationMenuSettings.showOverlay && emulationViewModel.emulationStarted.value) { + if (showInputOverlay && emulationViewModel.emulationStarted.value) { binding.surfaceInputOverlay.visibility = View.VISIBLE } else { binding.surfaceInputOverlay.visibility = View.INVISIBLE } if (!isInFoldableLayout) { if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) { - binding.surfaceInputOverlay.layout = InputOverlay.PORTRAIT + binding.surfaceInputOverlay.layout = OverlayLayout.Portrait } else { - binding.surfaceInputOverlay.layout = InputOverlay.LANDSCAPE + binding.surfaceInputOverlay.layout = OverlayLayout.Landscape } } } @@ -423,17 +421,15 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { } 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.resetLayoutVisibilityAndPlacement() } } private fun updateShowFpsOverlay() { - if (EmulationMenuSettings.showFps) { + if (BooleanSetting.SHOW_PERFORMANCE_OVERLAY.getBoolean()) { val SYSTEM_FPS = 0 val FPS = 1 val FRAMETIME = 2 @@ -496,7 +492,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { binding.inGameMenu.layoutParams.height = it.bounds.bottom isInFoldableLayout = true - binding.surfaceInputOverlay.layout = InputOverlay.FOLDABLE + binding.surfaceInputOverlay.layout = OverlayLayout.Foldable } } it.isSeparating @@ -535,18 +531,22 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { popup.menuInflater.inflate(R.menu.menu_overlay_options, popup.menu) 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() + findItem(R.id.menu_touchscreen).isChecked = BooleanSetting.TOUCHSCREEN.getBoolean() } popup.setOnMenuItemClickListener { when (it.itemId) { R.id.menu_toggle_fps -> { it.isChecked = !it.isChecked - EmulationMenuSettings.showFps = it.isChecked + BooleanSetting.SHOW_PERFORMANCE_OVERLAY.setBoolean(it.isChecked) updateShowFpsOverlay() true } @@ -564,11 +564,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { } 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()) @@ -577,11 +578,13 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { R.array.gamepadButtons, optionsArray ) { _, 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) { _, _ -> + NativeConfig.setOverlayControlData(overlayControlData) + NativeConfig.saveGlobalConfig() binding.surfaceInputOverlay.refreshControls() } .setNegativeButton(android.R.string.cancel, null) @@ -592,12 +595,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { dialog.getButton(AlertDialog.BUTTON_NEUTRAL) .setOnClickListener { val isChecked = !optionsArray[0] - Settings.overlayPreferences.forEachIndexed { i, _ -> + overlayControlData.forEachIndexed { i, _ -> optionsArray[i] = isChecked dialog.listView.setItemChecked(i, isChecked) - preferences.edit() - .putBoolean("buttonToggle$i", isChecked) - .apply() + overlayControlData[i].enabled = isChecked } } true @@ -605,26 +606,32 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { R.id.menu_show_overlay -> { it.isChecked = !it.isChecked - EmulationMenuSettings.showOverlay = it.isChecked + BooleanSetting.SHOW_INPUT_OVERLAY.setBoolean(it.isChecked) binding.surfaceInputOverlay.refreshControls() true } R.id.menu_rel_stick_center -> { it.isChecked = !it.isChecked - EmulationMenuSettings.joystickRelCenter = it.isChecked + BooleanSetting.JOYSTICK_REL_CENTER.setBoolean(it.isChecked) true } R.id.menu_dpad_slide -> { it.isChecked = !it.isChecked - EmulationMenuSettings.dpadSlide = it.isChecked + BooleanSetting.DPAD_SLIDE.setBoolean(it.isChecked) true } R.id.menu_haptics -> { it.isChecked = !it.isChecked - EmulationMenuSettings.hapticFeedback = it.isChecked + BooleanSetting.HAPTIC_FEEDBACK.setBoolean(it.isChecked) + true + } + + R.id.menu_touchscreen -> { + it.isChecked = !it.isChecked + BooleanSetting.TOUCHSCREEN.setBoolean(it.isChecked) true } @@ -667,6 +674,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { it.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED } } + NativeConfig.saveGlobalConfig() } @SuppressLint("SetTextI18n") @@ -675,7 +683,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { adjustBinding.apply { inputScaleSlider.apply { valueTo = 150F - value = preferences.getInt(Settings.PREF_CONTROL_SCALE, 50).toFloat() + value = IntSetting.OVERLAY_SCALE.getInt().toFloat() addOnChangeListener( Slider.OnChangeListener { _, value, _ -> inputScaleValue.text = "${value.toInt()}%" @@ -685,7 +693,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { } inputOpacitySlider.apply { valueTo = 100F - value = preferences.getInt(Settings.PREF_CONTROL_OPACITY, 100).toFloat() + value = IntSetting.OVERLAY_OPACITY.getInt().toFloat() addOnChangeListener( Slider.OnChangeListener { _, value, _ -> inputOpacityValue.text = "${value.toInt()}%" @@ -709,16 +717,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { } private fun setControlScale(scale: Int) { - preferences.edit() - .putInt(Settings.PREF_CONTROL_SCALE, scale) - .apply() + IntSetting.OVERLAY_SCALE.setInt(scale) binding.surfaceInputOverlay.refreshControls() } private fun setControlOpacity(opacity: Int) { - preferences.edit() - .putInt(Settings.PREF_CONTROL_OPACITY, opacity) - .apply() + IntSetting.OVERLAY_OPACITY.setInt(opacity) binding.surfaceInputOverlay.refreshControls() } 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 import android.view.View.OnTouchListener import android.view.WindowInsets import androidx.core.content.ContextCompat -import androidx.preference.PreferenceManager import androidx.window.layout.WindowMetricsCalculator import kotlin.math.max import kotlin.math.min @@ -29,9 +28,13 @@ import org.yuzu.yuzu_emu.NativeLibrary import org.yuzu.yuzu_emu.NativeLibrary.ButtonType import org.yuzu.yuzu_emu.NativeLibrary.StickType import org.yuzu.yuzu_emu.R -import org.yuzu.yuzu_emu.YuzuApplication +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.Settings -import org.yuzu.yuzu_emu.utils.EmulationMenuSettings +import org.yuzu.yuzu_emu.overlay.model.OverlayControl +import org.yuzu.yuzu_emu.overlay.model.OverlayControlData +import org.yuzu.yuzu_emu.overlay.model.OverlayLayout +import org.yuzu.yuzu_emu.utils.NativeConfig /** * Draws the interactive input overlay on top of the @@ -51,23 +54,18 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : private lateinit var windowInsets: WindowInsets - var layout = LANDSCAPE + var layout = OverlayLayout.Landscape override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { super.onLayout(changed, left, top, right, bottom) windowInsets = rootWindowInsets - val overlayVersion = preferences.getInt(Settings.PREF_OVERLAY_VERSION, 0) - if (overlayVersion != OVERLAY_VERSION) { - resetAllLayouts() + val overlayControlData = NativeConfig.getOverlayControlData() + if (overlayControlData.isEmpty()) { + populateDefaultConfig() } else { - val layoutIndex = overlayLayouts.indexOf(layout) - val currentLayoutVersion = - preferences.getInt(Settings.overlayLayoutPrefs[layoutIndex], 0) - if (currentLayoutVersion != overlayLayoutVersions[layoutIndex]) { - resetCurrentLayout() - } + checkForNewControls(overlayControlData) } // Load the controls. @@ -123,7 +121,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : } for (dpad in overlayDpads) { - if (!dpad.updateStatus(event, EmulationMenuSettings.dpadSlide)) { + if (!dpad.updateStatus(event, BooleanSetting.DPAD_SLIDE.getBoolean())) { continue } NativeLibrary.onGamePadButtonEvent( @@ -174,7 +172,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : invalidate() } - if (!preferences.getBoolean(Settings.PREF_TOUCH_ENABLED, true)) { + if (!BooleanSetting.TOUCHSCREEN.getBoolean()) { return true } @@ -211,7 +209,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : } private fun playHaptics(event: MotionEvent) { - if (EmulationMenuSettings.hapticFeedback) { + if (BooleanSetting.HAPTIC_FEEDBACK.getBoolean()) { when (event.actionMasked) { MotionEvent.ACTION_DOWN, MotionEvent.ACTION_POINTER_DOWN -> @@ -255,10 +253,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : MotionEvent.ACTION_POINTER_DOWN -> // If no button is being moved now, remember the currently touched button to move. if (buttonBeingConfigured == null && - button.bounds.contains( - fingerPositionX, - fingerPositionY - ) + button.bounds.contains(fingerPositionX, fingerPositionY) ) { buttonBeingConfigured = button buttonBeingConfigured!!.onConfigureTouch(event) @@ -274,7 +269,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : MotionEvent.ACTION_POINTER_UP -> if (buttonBeingConfigured === button) { // Persist button position by saving new place. saveControlPosition( - buttonBeingConfigured!!.prefId, + buttonBeingConfigured!!.overlayControlData.id, buttonBeingConfigured!!.bounds.centerX(), buttonBeingConfigured!!.bounds.centerY(), layout @@ -321,10 +316,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : when (event.action) { MotionEvent.ACTION_DOWN, MotionEvent.ACTION_POINTER_DOWN -> if (joystickBeingConfigured == null && - joystick.bounds.contains( - fingerPositionX, - fingerPositionY - ) + joystick.bounds.contains(fingerPositionX, fingerPositionY) ) { joystickBeingConfigured = joystick joystickBeingConfigured!!.onConfigureTouch(event) @@ -351,231 +343,257 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : return true } - private fun addOverlayControls(layout: String) { + private fun addOverlayControls(layout: OverlayLayout) { val windowSize = getSafeScreenSize(context, Pair(measuredWidth, measuredHeight)) - if (preferences.getBoolean(Settings.PREF_BUTTON_A, true)) { - overlayButtons.add( - initializeOverlayButton( - context, - windowSize, - R.drawable.facebutton_a, - R.drawable.facebutton_a_depressed, - ButtonType.BUTTON_A, - Settings.PREF_BUTTON_A, - layout - ) - ) - } - if (preferences.getBoolean(Settings.PREF_BUTTON_B, true)) { - overlayButtons.add( - initializeOverlayButton( - context, - windowSize, - R.drawable.facebutton_b, - R.drawable.facebutton_b_depressed, - ButtonType.BUTTON_B, - Settings.PREF_BUTTON_B, - layout - ) - ) - } - if (preferences.getBoolean(Settings.PREF_BUTTON_X, true)) { - overlayButtons.add( - initializeOverlayButton( - context, - windowSize, - R.drawable.facebutton_x, - R.drawable.facebutton_x_depressed, - ButtonType.BUTTON_X, - Settings.PREF_BUTTON_X, - layout - ) - ) - } - if (preferences.getBoolean(Settings.PREF_BUTTON_Y, true)) { - overlayButtons.add( - initializeOverlayButton( - context, - windowSize, - R.drawable.facebutton_y, - R.drawable.facebutton_y_depressed, - ButtonType.BUTTON_Y, - Settings.PREF_BUTTON_Y, - layout - ) - ) - } - if (preferences.getBoolean(Settings.PREF_BUTTON_L, true)) { - overlayButtons.add( - initializeOverlayButton( - context, - windowSize, - R.drawable.l_shoulder, - R.drawable.l_shoulder_depressed, - ButtonType.TRIGGER_L, - Settings.PREF_BUTTON_L, - layout - ) - ) - } - if (preferences.getBoolean(Settings.PREF_BUTTON_R, true)) { - overlayButtons.add( - initializeOverlayButton( - context, - windowSize, - R.drawable.r_shoulder, - R.drawable.r_shoulder_depressed, - ButtonType.TRIGGER_R, - Settings.PREF_BUTTON_R, - layout - ) - ) - } - if (preferences.getBoolean(Settings.PREF_BUTTON_ZL, true)) { - overlayButtons.add( - initializeOverlayButton( - context, - windowSize, - R.drawable.zl_trigger, - R.drawable.zl_trigger_depressed, - ButtonType.TRIGGER_ZL, - Settings.PREF_BUTTON_ZL, - layout - ) - ) - } - if (preferences.getBoolean(Settings.PREF_BUTTON_ZR, true)) { - overlayButtons.add( - initializeOverlayButton( - context, - windowSize, - R.drawable.zr_trigger, - R.drawable.zr_trigger_depressed, - ButtonType.TRIGGER_ZR, - Settings.PREF_BUTTON_ZR, - layout - ) - ) - } - if (preferences.getBoolean(Settings.PREF_BUTTON_PLUS, true)) { - overlayButtons.add( - initializeOverlayButton( - context, - windowSize, - R.drawable.facebutton_plus, - R.drawable.facebutton_plus_depressed, - ButtonType.BUTTON_PLUS, - Settings.PREF_BUTTON_PLUS, - layout - ) - ) - } - if (preferences.getBoolean(Settings.PREF_BUTTON_MINUS, true)) { - overlayButtons.add( - initializeOverlayButton( - context, - windowSize, - R.drawable.facebutton_minus, - R.drawable.facebutton_minus_depressed, - ButtonType.BUTTON_MINUS, - Settings.PREF_BUTTON_MINUS, - layout - ) - ) - } - if (preferences.getBoolean(Settings.PREF_BUTTON_DPAD, true)) { - overlayDpads.add( - initializeOverlayDpad( - context, - windowSize, - R.drawable.dpad_standard, - R.drawable.dpad_standard_cardinal_depressed, - R.drawable.dpad_standard_diagonal_depressed, - layout - ) - ) - } - if (preferences.getBoolean(Settings.PREF_STICK_L, true)) { - overlayJoysticks.add( - initializeOverlayJoystick( - context, - windowSize, - R.drawable.joystick_range, - R.drawable.joystick, - R.drawable.joystick_depressed, - StickType.STICK_L, - ButtonType.STICK_L, - Settings.PREF_STICK_L, - layout - ) - ) - } - if (preferences.getBoolean(Settings.PREF_STICK_R, true)) { - overlayJoysticks.add( - initializeOverlayJoystick( - context, - windowSize, - R.drawable.joystick_range, - R.drawable.joystick, - R.drawable.joystick_depressed, - StickType.STICK_R, - ButtonType.STICK_R, - Settings.PREF_STICK_R, - layout - ) - ) - } - if (preferences.getBoolean(Settings.PREF_BUTTON_HOME, false)) { - overlayButtons.add( - initializeOverlayButton( - context, - windowSize, - R.drawable.facebutton_home, - R.drawable.facebutton_home_depressed, - ButtonType.BUTTON_HOME, - Settings.PREF_BUTTON_HOME, - layout - ) - ) - } - if (preferences.getBoolean(Settings.PREF_BUTTON_SCREENSHOT, false)) { - overlayButtons.add( - initializeOverlayButton( - context, - windowSize, - R.drawable.facebutton_screenshot, - R.drawable.facebutton_screenshot_depressed, - ButtonType.BUTTON_CAPTURE, - Settings.PREF_BUTTON_SCREENSHOT, - layout - ) - ) - } - if (preferences.getBoolean(Settings.PREF_BUTTON_STICK_L, true)) { - overlayButtons.add( - initializeOverlayButton( - context, - windowSize, - R.drawable.button_l3, - R.drawable.button_l3_depressed, - ButtonType.STICK_L, - Settings.PREF_BUTTON_STICK_L, - layout - ) - ) - } - if (preferences.getBoolean(Settings.PREF_BUTTON_STICK_R, true)) { - overlayButtons.add( - initializeOverlayButton( - context, - windowSize, - R.drawable.button_r3, - R.drawable.button_r3_depressed, - ButtonType.STICK_R, - Settings.PREF_BUTTON_STICK_R, - layout - ) - ) + val overlayControlData = NativeConfig.getOverlayControlData() + for (data in overlayControlData) { + if (!data.enabled) { + continue + } + + val position = data.positionFromLayout(layout) + when (data.id) { + OverlayControl.BUTTON_A.id -> { + overlayButtons.add( + initializeOverlayButton( + context, + windowSize, + R.drawable.facebutton_a, + R.drawable.facebutton_a_depressed, + ButtonType.BUTTON_A, + data, + position + ) + ) + } + + OverlayControl.BUTTON_B.id -> { + overlayButtons.add( + initializeOverlayButton( + context, + windowSize, + R.drawable.facebutton_b, + R.drawable.facebutton_b_depressed, + ButtonType.BUTTON_B, + data, + position + ) + ) + } + + OverlayControl.BUTTON_X.id -> { + overlayButtons.add( + initializeOverlayButton( + context, + windowSize, + R.drawable.facebutton_x, + R.drawable.facebutton_x_depressed, + ButtonType.BUTTON_X, + data, + position + ) + ) + } + + OverlayControl.BUTTON_Y.id -> { + overlayButtons.add( + initializeOverlayButton( + context, + windowSize, + R.drawable.facebutton_y, + R.drawable.facebutton_y_depressed, + ButtonType.BUTTON_Y, + data, + position + ) + ) + } + + OverlayControl.BUTTON_PLUS.id -> { + overlayButtons.add( + initializeOverlayButton( + context, + windowSize, + R.drawable.facebutton_plus, + R.drawable.facebutton_plus_depressed, + ButtonType.BUTTON_PLUS, + data, + position + ) + ) + } + + OverlayControl.BUTTON_MINUS.id -> { + overlayButtons.add( + initializeOverlayButton( + context, + windowSize, + R.drawable.facebutton_minus, + R.drawable.facebutton_minus_depressed, + ButtonType.BUTTON_MINUS, + data, + position + ) + ) + } + + OverlayControl.BUTTON_HOME.id -> { + overlayButtons.add( + initializeOverlayButton( + context, + windowSize, + R.drawable.facebutton_home, + R.drawable.facebutton_home_depressed, + ButtonType.BUTTON_HOME, + data, + position + ) + ) + } + + OverlayControl.BUTTON_CAPTURE.id -> { + overlayButtons.add( + initializeOverlayButton( + context, + windowSize, + R.drawable.facebutton_screenshot, + R.drawable.facebutton_screenshot_depressed, + ButtonType.BUTTON_CAPTURE, + data, + position + ) + ) + } + + OverlayControl.BUTTON_L.id -> { + overlayButtons.add( + initializeOverlayButton( + context, + windowSize, + R.drawable.l_shoulder, + R.drawable.l_shoulder_depressed, + ButtonType.TRIGGER_L, + data, + position + ) + ) + } + + OverlayControl.BUTTON_R.id -> { + overlayButtons.add( + initializeOverlayButton( + context, + windowSize, + R.drawable.r_shoulder, + R.drawable.r_shoulder_depressed, + ButtonType.TRIGGER_R, + data, + position + ) + ) + } + + OverlayControl.BUTTON_ZL.id -> { + overlayButtons.add( + initializeOverlayButton( + context, + windowSize, + R.drawable.zl_trigger, + R.drawable.zl_trigger_depressed, + ButtonType.TRIGGER_ZL, + data, + position + ) + ) + } + + OverlayControl.BUTTON_ZR.id -> { + overlayButtons.add( + initializeOverlayButton( + context, + windowSize, + R.drawable.zr_trigger, + R.drawable.zr_trigger_depressed, + ButtonType.TRIGGER_ZR, + data, + position + ) + ) + } + + OverlayControl.BUTTON_STICK_L.id -> { + overlayButtons.add( + initializeOverlayButton( + context, + windowSize, + R.drawable.button_l3, + R.drawable.button_l3_depressed, + ButtonType.STICK_L, + data, + position + ) + ) + } + + OverlayControl.BUTTON_STICK_R.id -> { + overlayButtons.add( + initializeOverlayButton( + context, + windowSize, + R.drawable.button_r3, + R.drawable.button_r3_depressed, + ButtonType.STICK_R, + data, + position + ) + ) + } + + OverlayControl.STICK_L.id -> { + overlayJoysticks.add( + initializeOverlayJoystick( + context, + windowSize, + R.drawable.joystick_range, + R.drawable.joystick, + R.drawable.joystick_depressed, + StickType.STICK_L, + ButtonType.STICK_L, + data, + position + ) + ) + } + + OverlayControl.STICK_R.id -> { + overlayJoysticks.add( + initializeOverlayJoystick( + context, + windowSize, + R.drawable.joystick_range, + R.drawable.joystick, + R.drawable.joystick_depressed, + StickType.STICK_R, + ButtonType.STICK_R, + data, + position + ) + ) + } + + OverlayControl.COMBINED_DPAD.id -> { + overlayDpads.add( + initializeOverlayDpad( + context, + windowSize, + R.drawable.dpad_standard, + R.drawable.dpad_standard_cardinal_depressed, + R.drawable.dpad_standard_diagonal_depressed, + position + ) + ) + } + } } } @@ -586,313 +604,87 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : overlayJoysticks.clear() // Add all the enabled overlay items back to the HashSet. - if (EmulationMenuSettings.showOverlay) { + if (BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean()) { addOverlayControls(layout) } invalidate() } - private fun saveControlPosition(prefId: String, x: Int, y: Int, layout: String) { + private fun saveControlPosition(id: String, x: Int, y: Int, layout: OverlayLayout) { val windowSize = getSafeScreenSize(context, Pair(measuredWidth, measuredHeight)) val min = windowSize.first val max = windowSize.second - PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext).edit() - .putFloat("$prefId-X$layout", (x - min.x).toFloat() / max.x) - .putFloat("$prefId-Y$layout", (y - min.y).toFloat() / max.y) - .apply() + val overlayControlData = NativeConfig.getOverlayControlData() + val data = overlayControlData.firstOrNull { it.id == id } + val newPosition = Pair((x - min.x).toDouble() / max.x, (y - min.y).toDouble() / max.y) + when (layout) { + OverlayLayout.Landscape -> data?.landscapePosition = newPosition + OverlayLayout.Portrait -> data?.portraitPosition = newPosition + OverlayLayout.Foldable -> data?.foldablePosition = newPosition + } + NativeConfig.setOverlayControlData(overlayControlData) } fun setIsInEditMode(editMode: Boolean) { inEditMode = editMode } - private fun resetCurrentLayout() { - defaultOverlayByLayout(layout) - val layoutIndex = overlayLayouts.indexOf(layout) - preferences.edit() - .putInt(Settings.overlayLayoutPrefs[layoutIndex], overlayLayoutVersions[layoutIndex]) - .apply() + /** + * Applies and saves all default values for the overlay + */ + private fun populateDefaultConfig() { + val newConfig = OverlayControl.entries.map { it.toOverlayControlData() } + NativeConfig.setOverlayControlData(newConfig.toTypedArray()) + NativeConfig.saveGlobalConfig() } - private fun resetAllLayouts() { - val editor = preferences.edit() - overlayLayouts.forEachIndexed { i, layout -> - defaultOverlayByLayout(layout) - editor.putInt(Settings.overlayLayoutPrefs[i], overlayLayoutVersions[i]) + /** + * Checks if any new controls were added to OverlayControl that do not exist within deserialized + * config and adds / saves them if necessary + * + * @param overlayControlData Overlay control data from [NativeConfig.getOverlayControlData] + */ + private fun checkForNewControls(overlayControlData: Array<OverlayControlData>) { + val missingControls = mutableListOf<OverlayControlData>() + OverlayControl.entries.forEach { defaultControl -> + val controlData = overlayControlData.firstOrNull { it.id == defaultControl.id } + if (controlData == null) { + missingControls.add(defaultControl.toOverlayControlData()) + } + } + + if (missingControls.isNotEmpty()) { + NativeConfig.setOverlayControlData( + arrayOf(*overlayControlData, *(missingControls.toTypedArray())) + ) + NativeConfig.saveGlobalConfig() } - editor.putInt(Settings.PREF_OVERLAY_VERSION, OVERLAY_VERSION) - editor.apply() } fun resetLayoutVisibilityAndPlacement() { - defaultOverlayByLayout(layout) - val editor = preferences.edit() - Settings.overlayPreferences.forEachIndexed { _, pref -> - editor.remove(pref) + defaultOverlayPositionByLayout(layout) + + val overlayControlData = NativeConfig.getOverlayControlData() + overlayControlData.forEach { + it.enabled = OverlayControl.from(it.id)?.defaultVisibility == false } - editor.apply() + NativeConfig.setOverlayControlData(overlayControlData) + refreshControls() } - private val landscapeResources = arrayOf( - R.integer.SWITCH_BUTTON_A_X, - R.integer.SWITCH_BUTTON_A_Y, - R.integer.SWITCH_BUTTON_B_X, - R.integer.SWITCH_BUTTON_B_Y, - R.integer.SWITCH_BUTTON_X_X, - R.integer.SWITCH_BUTTON_X_Y, - R.integer.SWITCH_BUTTON_Y_X, - R.integer.SWITCH_BUTTON_Y_Y, - R.integer.SWITCH_TRIGGER_ZL_X, - R.integer.SWITCH_TRIGGER_ZL_Y, - R.integer.SWITCH_TRIGGER_ZR_X, - R.integer.SWITCH_TRIGGER_ZR_Y, - R.integer.SWITCH_BUTTON_DPAD_X, - R.integer.SWITCH_BUTTON_DPAD_Y, - R.integer.SWITCH_TRIGGER_L_X, - R.integer.SWITCH_TRIGGER_L_Y, - R.integer.SWITCH_TRIGGER_R_X, - R.integer.SWITCH_TRIGGER_R_Y, - R.integer.SWITCH_BUTTON_PLUS_X, - R.integer.SWITCH_BUTTON_PLUS_Y, - R.integer.SWITCH_BUTTON_MINUS_X, - R.integer.SWITCH_BUTTON_MINUS_Y, - R.integer.SWITCH_BUTTON_HOME_X, - R.integer.SWITCH_BUTTON_HOME_Y, - R.integer.SWITCH_BUTTON_CAPTURE_X, - R.integer.SWITCH_BUTTON_CAPTURE_Y, - R.integer.SWITCH_STICK_R_X, - R.integer.SWITCH_STICK_R_Y, - R.integer.SWITCH_STICK_L_X, - R.integer.SWITCH_STICK_L_Y, - R.integer.SWITCH_BUTTON_STICK_L_X, - R.integer.SWITCH_BUTTON_STICK_L_Y, - R.integer.SWITCH_BUTTON_STICK_R_X, - R.integer.SWITCH_BUTTON_STICK_R_Y - ) - - private val portraitResources = arrayOf( - R.integer.SWITCH_BUTTON_A_X_PORTRAIT, - R.integer.SWITCH_BUTTON_A_Y_PORTRAIT, - R.integer.SWITCH_BUTTON_B_X_PORTRAIT, - R.integer.SWITCH_BUTTON_B_Y_PORTRAIT, - R.integer.SWITCH_BUTTON_X_X_PORTRAIT, - R.integer.SWITCH_BUTTON_X_Y_PORTRAIT, - R.integer.SWITCH_BUTTON_Y_X_PORTRAIT, - R.integer.SWITCH_BUTTON_Y_Y_PORTRAIT, - R.integer.SWITCH_TRIGGER_ZL_X_PORTRAIT, - R.integer.SWITCH_TRIGGER_ZL_Y_PORTRAIT, - R.integer.SWITCH_TRIGGER_ZR_X_PORTRAIT, - R.integer.SWITCH_TRIGGER_ZR_Y_PORTRAIT, - R.integer.SWITCH_BUTTON_DPAD_X_PORTRAIT, - R.integer.SWITCH_BUTTON_DPAD_Y_PORTRAIT, - R.integer.SWITCH_TRIGGER_L_X_PORTRAIT, - R.integer.SWITCH_TRIGGER_L_Y_PORTRAIT, - R.integer.SWITCH_TRIGGER_R_X_PORTRAIT, - R.integer.SWITCH_TRIGGER_R_Y_PORTRAIT, - R.integer.SWITCH_BUTTON_PLUS_X_PORTRAIT, - R.integer.SWITCH_BUTTON_PLUS_Y_PORTRAIT, - R.integer.SWITCH_BUTTON_MINUS_X_PORTRAIT, - R.integer.SWITCH_BUTTON_MINUS_Y_PORTRAIT, - R.integer.SWITCH_BUTTON_HOME_X_PORTRAIT, - R.integer.SWITCH_BUTTON_HOME_Y_PORTRAIT, - R.integer.SWITCH_BUTTON_CAPTURE_X_PORTRAIT, - R.integer.SWITCH_BUTTON_CAPTURE_Y_PORTRAIT, - R.integer.SWITCH_STICK_R_X_PORTRAIT, - R.integer.SWITCH_STICK_R_Y_PORTRAIT, - R.integer.SWITCH_STICK_L_X_PORTRAIT, - R.integer.SWITCH_STICK_L_Y_PORTRAIT, - R.integer.SWITCH_BUTTON_STICK_L_X_PORTRAIT, - R.integer.SWITCH_BUTTON_STICK_L_Y_PORTRAIT, - R.integer.SWITCH_BUTTON_STICK_R_X_PORTRAIT, - R.integer.SWITCH_BUTTON_STICK_R_Y_PORTRAIT - ) - - private val foldableResources = arrayOf( - R.integer.SWITCH_BUTTON_A_X_FOLDABLE, - R.integer.SWITCH_BUTTON_A_Y_FOLDABLE, - R.integer.SWITCH_BUTTON_B_X_FOLDABLE, - R.integer.SWITCH_BUTTON_B_Y_FOLDABLE, - R.integer.SWITCH_BUTTON_X_X_FOLDABLE, - R.integer.SWITCH_BUTTON_X_Y_FOLDABLE, - R.integer.SWITCH_BUTTON_Y_X_FOLDABLE, - R.integer.SWITCH_BUTTON_Y_Y_FOLDABLE, - R.integer.SWITCH_TRIGGER_ZL_X_FOLDABLE, - R.integer.SWITCH_TRIGGER_ZL_Y_FOLDABLE, - R.integer.SWITCH_TRIGGER_ZR_X_FOLDABLE, - R.integer.SWITCH_TRIGGER_ZR_Y_FOLDABLE, - R.integer.SWITCH_BUTTON_DPAD_X_FOLDABLE, - R.integer.SWITCH_BUTTON_DPAD_Y_FOLDABLE, - R.integer.SWITCH_TRIGGER_L_X_FOLDABLE, - R.integer.SWITCH_TRIGGER_L_Y_FOLDABLE, - R.integer.SWITCH_TRIGGER_R_X_FOLDABLE, - R.integer.SWITCH_TRIGGER_R_Y_FOLDABLE, - R.integer.SWITCH_BUTTON_PLUS_X_FOLDABLE, - R.integer.SWITCH_BUTTON_PLUS_Y_FOLDABLE, - R.integer.SWITCH_BUTTON_MINUS_X_FOLDABLE, - R.integer.SWITCH_BUTTON_MINUS_Y_FOLDABLE, - R.integer.SWITCH_BUTTON_HOME_X_FOLDABLE, - R.integer.SWITCH_BUTTON_HOME_Y_FOLDABLE, - R.integer.SWITCH_BUTTON_CAPTURE_X_FOLDABLE, - R.integer.SWITCH_BUTTON_CAPTURE_Y_FOLDABLE, - R.integer.SWITCH_STICK_R_X_FOLDABLE, - R.integer.SWITCH_STICK_R_Y_FOLDABLE, - R.integer.SWITCH_STICK_L_X_FOLDABLE, - R.integer.SWITCH_STICK_L_Y_FOLDABLE, - R.integer.SWITCH_BUTTON_STICK_L_X_FOLDABLE, - R.integer.SWITCH_BUTTON_STICK_L_Y_FOLDABLE, - R.integer.SWITCH_BUTTON_STICK_R_X_FOLDABLE, - R.integer.SWITCH_BUTTON_STICK_R_Y_FOLDABLE - ) - - private fun getResourceValue(layout: String, position: Int): Float { - return when (layout) { - PORTRAIT -> resources.getInteger(portraitResources[position]).toFloat() / 1000 - FOLDABLE -> resources.getInteger(foldableResources[position]).toFloat() / 1000 - else -> resources.getInteger(landscapeResources[position]).toFloat() / 1000 + private fun defaultOverlayPositionByLayout(layout: OverlayLayout) { + val overlayControlData = NativeConfig.getOverlayControlData() + for (data in overlayControlData) { + val defaultControlData = OverlayControl.from(data.id) ?: continue + val position = defaultControlData.getDefaultPositionForLayout(layout) + when (layout) { + OverlayLayout.Landscape -> data.landscapePosition = position + OverlayLayout.Portrait -> data.portraitPosition = position + OverlayLayout.Foldable -> data.foldablePosition = position + } } - } - - private fun defaultOverlayByLayout(layout: String) { - // Each value represents the position of the button in relation to the screen size without insets. - preferences.edit() - .putFloat( - "${Settings.PREF_BUTTON_A}-X$layout", - getResourceValue(layout, 0) - ) - .putFloat( - "${Settings.PREF_BUTTON_A}-Y$layout", - getResourceValue(layout, 1) - ) - .putFloat( - "${Settings.PREF_BUTTON_B}-X$layout", - getResourceValue(layout, 2) - ) - .putFloat( - "${Settings.PREF_BUTTON_B}-Y$layout", - getResourceValue(layout, 3) - ) - .putFloat( - "${Settings.PREF_BUTTON_X}-X$layout", - getResourceValue(layout, 4) - ) - .putFloat( - "${Settings.PREF_BUTTON_X}-Y$layout", - getResourceValue(layout, 5) - ) - .putFloat( - "${Settings.PREF_BUTTON_Y}-X$layout", - getResourceValue(layout, 6) - ) - .putFloat( - "${Settings.PREF_BUTTON_Y}-Y$layout", - getResourceValue(layout, 7) - ) - .putFloat( - "${Settings.PREF_BUTTON_ZL}-X$layout", - getResourceValue(layout, 8) - ) - .putFloat( - "${Settings.PREF_BUTTON_ZL}-Y$layout", - getResourceValue(layout, 9) - ) - .putFloat( - "${Settings.PREF_BUTTON_ZR}-X$layout", - getResourceValue(layout, 10) - ) - .putFloat( - "${Settings.PREF_BUTTON_ZR}-Y$layout", - getResourceValue(layout, 11) - ) - .putFloat( - "${Settings.PREF_BUTTON_DPAD}-X$layout", - getResourceValue(layout, 12) - ) - .putFloat( - "${Settings.PREF_BUTTON_DPAD}-Y$layout", - getResourceValue(layout, 13) - ) - .putFloat( - "${Settings.PREF_BUTTON_L}-X$layout", - getResourceValue(layout, 14) - ) - .putFloat( - "${Settings.PREF_BUTTON_L}-Y$layout", - getResourceValue(layout, 15) - ) - .putFloat( - "${Settings.PREF_BUTTON_R}-X$layout", - getResourceValue(layout, 16) - ) - .putFloat( - "${Settings.PREF_BUTTON_R}-Y$layout", - getResourceValue(layout, 17) - ) - .putFloat( - "${Settings.PREF_BUTTON_PLUS}-X$layout", - getResourceValue(layout, 18) - ) - .putFloat( - "${Settings.PREF_BUTTON_PLUS}-Y$layout", - getResourceValue(layout, 19) - ) - .putFloat( - "${Settings.PREF_BUTTON_MINUS}-X$layout", - getResourceValue(layout, 20) - ) - .putFloat( - "${Settings.PREF_BUTTON_MINUS}-Y$layout", - getResourceValue(layout, 21) - ) - .putFloat( - "${Settings.PREF_BUTTON_HOME}-X$layout", - getResourceValue(layout, 22) - ) - .putFloat( - "${Settings.PREF_BUTTON_HOME}-Y$layout", - getResourceValue(layout, 23) - ) - .putFloat( - "${Settings.PREF_BUTTON_SCREENSHOT}-X$layout", - getResourceValue(layout, 24) - ) - .putFloat( - "${Settings.PREF_BUTTON_SCREENSHOT}-Y$layout", - getResourceValue(layout, 25) - ) - .putFloat( - "${Settings.PREF_STICK_R}-X$layout", - getResourceValue(layout, 26) - ) - .putFloat( - "${Settings.PREF_STICK_R}-Y$layout", - getResourceValue(layout, 27) - ) - .putFloat( - "${Settings.PREF_STICK_L}-X$layout", - getResourceValue(layout, 28) - ) - .putFloat( - "${Settings.PREF_STICK_L}-Y$layout", - getResourceValue(layout, 29) - ) - .putFloat( - "${Settings.PREF_BUTTON_STICK_L}-X$layout", - getResourceValue(layout, 30) - ) - .putFloat( - "${Settings.PREF_BUTTON_STICK_L}-Y$layout", - getResourceValue(layout, 31) - ) - .putFloat( - "${Settings.PREF_BUTTON_STICK_R}-X$layout", - getResourceValue(layout, 32) - ) - .putFloat( - "${Settings.PREF_BUTTON_STICK_R}-Y$layout", - getResourceValue(layout, 33) - ) - .apply() + NativeConfig.setOverlayControlData(overlayControlData) } override fun isInEditMode(): Boolean { @@ -913,18 +705,6 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : FOLDABLE_OVERLAY_VERSION ) - private val preferences: SharedPreferences = - PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) - - const val LANDSCAPE = "_Landscape" - const val PORTRAIT = "_Portrait" - const val FOLDABLE = "_Foldable" - val overlayLayouts = listOf( - LANDSCAPE, - PORTRAIT, - FOLDABLE - ) - /** * Resizes a [Bitmap] by a given scale factor * @@ -1036,29 +816,19 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : * In the input overlay configuration menu, * once a touch event begins and then ends (ie. Organizing the buttons to one's own liking for the overlay). * the X and Y coordinates of the button at the END of its touch event - * (when you remove your finger/stylus from the touchscreen) are then stored - * within a SharedPreferences instance so that those values can be retrieved here. - * - * - * This has a few benefits over the conventional way of storing the values - * (ie. within the yuzu ini file). - * - * * No native calls - * * Keeps Android-only values inside the Android environment - * - * + * (when you remove your finger/stylus from the touchscreen) are then stored in a native . * * Technically no modifications should need to be performed on the returned * InputOverlayDrawableButton. Simply add it to the HashSet of overlay items and wait * for Android to call the onDraw method. * - * @param context The current [Context]. - * @param windowSize The size of the window to draw the overlay on. - * @param defaultResId The resource ID of the [Drawable] to get the [Bitmap] of (Default State). - * @param pressedResId The resource ID of the [Drawable] to get the [Bitmap] of (Pressed State). - * @param buttonId Identifier for determining what type of button the initialized InputOverlayDrawableButton represents. - * @param prefId Identifier for determining where a button appears on screen. - * @param layout The current screen layout as determined by [LANDSCAPE], [PORTRAIT], or [FOLDABLE]. + * @param context The current [Context]. + * @param windowSize The size of the window to draw the overlay on. + * @param defaultResId The resource ID of the [Drawable] to get the [Bitmap] of (Default State). + * @param pressedResId The resource ID of the [Drawable] to get the [Bitmap] of (Pressed State). + * @param buttonId Identifier for determining what type of button the initialized InputOverlayDrawableButton represents. + * @param overlayControlData Identifier for determining where a button appears on screen. + * @param position The position on screen as represented by an x and y value between 0 and 1. * @return An [InputOverlayDrawableButton] with the correct drawing bounds set. */ private fun initializeOverlayButton( @@ -1067,33 +837,30 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : defaultResId: Int, pressedResId: Int, buttonId: Int, - prefId: String, - layout: String + overlayControlData: OverlayControlData, + position: Pair<Double, Double> ): InputOverlayDrawableButton { // Resources handle for fetching the initial Drawable resource. val res = context.resources - // SharedPreference to retrieve the X and Y coordinates for the InputOverlayDrawableButton. - val sPrefs = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) - // Decide scale based on button preference ID and user preference - var scale: Float = when (prefId) { - Settings.PREF_BUTTON_HOME, - Settings.PREF_BUTTON_SCREENSHOT, - Settings.PREF_BUTTON_PLUS, - Settings.PREF_BUTTON_MINUS -> 0.07f + var scale: Float = when (overlayControlData.id) { + OverlayControl.BUTTON_HOME.id, + OverlayControl.BUTTON_CAPTURE.id, + OverlayControl.BUTTON_PLUS.id, + OverlayControl.BUTTON_MINUS.id -> 0.07f - Settings.PREF_BUTTON_L, - Settings.PREF_BUTTON_R, - Settings.PREF_BUTTON_ZL, - Settings.PREF_BUTTON_ZR -> 0.26f + OverlayControl.BUTTON_L.id, + OverlayControl.BUTTON_R.id, + OverlayControl.BUTTON_ZL.id, + OverlayControl.BUTTON_ZR.id -> 0.26f - Settings.PREF_BUTTON_STICK_L, - Settings.PREF_BUTTON_STICK_R -> 0.155f + OverlayControl.BUTTON_STICK_L.id, + OverlayControl.BUTTON_STICK_R.id -> 0.155f else -> 0.11f } - scale *= (sPrefs.getInt(Settings.PREF_CONTROL_SCALE, 50) + 50).toFloat() + scale *= (IntSetting.OVERLAY_SCALE.getInt() + 50).toFloat() scale /= 100f // Initialize the InputOverlayDrawableButton. @@ -1104,7 +871,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : defaultStateBitmap, pressedStateBitmap, buttonId, - prefId + overlayControlData ) // 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?) : // The X and Y coordinates of the InputOverlayDrawableButton on the InputOverlay. // These were set in the input overlay configuration menu. - val xKey = "$prefId-X$layout" - val yKey = "$prefId-Y$layout" - val drawableXPercent = sPrefs.getFloat(xKey, 0f) - val drawableYPercent = sPrefs.getFloat(yKey, 0f) - val drawableX = (drawableXPercent * max.x + min.x).toInt() - val drawableY = (drawableYPercent * max.y + min.y).toInt() + val drawableX = (position.first * max.x + min.x).toInt() + val drawableY = (position.second * max.y + min.y).toInt() val width = overlayDrawable.width val height = overlayDrawable.height @@ -1136,8 +899,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : drawableX - (width / 2), drawableY - (height / 2) ) - val savedOpacity = preferences.getInt(Settings.PREF_CONTROL_OPACITY, 100) - overlayDrawable.setOpacity(savedOpacity * 255 / 100) + overlayDrawable.setOpacity(IntSetting.OVERLAY_OPACITY.getInt() * 255 / 100) return overlayDrawable } @@ -1149,7 +911,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : * @param defaultResId The [Bitmap] resource ID of the default state. * @param pressedOneDirectionResId The [Bitmap] resource ID of the pressed state in one direction. * @param pressedTwoDirectionsResId The [Bitmap] resource ID of the pressed state in two directions. - * @param layout The current screen layout as determined by [LANDSCAPE], [PORTRAIT], or [FOLDABLE]. + * @param position The position on screen as represented by an x and y value between 0 and 1. * @return The initialized [InputOverlayDrawableDpad] */ private fun initializeOverlayDpad( @@ -1158,17 +920,14 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : defaultResId: Int, pressedOneDirectionResId: Int, pressedTwoDirectionsResId: Int, - layout: String + position: Pair<Double, Double> ): InputOverlayDrawableDpad { // Resources handle for fetching the initial Drawable resource. val res = context.resources - // SharedPreference to retrieve the X and Y coordinates for the InputOverlayDrawableDpad. - val sPrefs = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) - // Decide scale based on button ID and user preference var scale = 0.25f - scale *= (sPrefs.getInt(Settings.PREF_CONTROL_SCALE, 50) + 50).toFloat() + scale *= (IntSetting.OVERLAY_SCALE.getInt() + 50).toFloat() scale /= 100f // Initialize the InputOverlayDrawableDpad. @@ -1195,10 +954,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : // The X and Y coordinates of the InputOverlayDrawableDpad on the InputOverlay. // These were set in the input overlay configuration menu. - val drawableXPercent = sPrefs.getFloat("${Settings.PREF_BUTTON_DPAD}-X$layout", 0f) - val drawableYPercent = sPrefs.getFloat("${Settings.PREF_BUTTON_DPAD}-Y$layout", 0f) - val drawableX = (drawableXPercent * max.x + min.x).toInt() - val drawableY = (drawableYPercent * max.y + min.y).toInt() + val drawableX = (position.first * max.x + min.x).toInt() + val drawableY = (position.second * max.y + min.y).toInt() val width = overlayDrawable.width val height = overlayDrawable.height @@ -1213,8 +970,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : // Need to set the image's position overlayDrawable.setPosition(drawableX - (width / 2), drawableY - (height / 2)) - val savedOpacity = preferences.getInt(Settings.PREF_CONTROL_OPACITY, 100) - overlayDrawable.setOpacity(savedOpacity * 255 / 100) + overlayDrawable.setOpacity(IntSetting.OVERLAY_OPACITY.getInt() * 255 / 100) return overlayDrawable } @@ -1227,9 +983,9 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : * @param defaultResInner Resource ID for the default inner image of the joystick (the one you actually move around). * @param pressedResInner Resource ID for the pressed inner image of the joystick. * @param joystick Identifier for which joystick this is. - * @param button Identifier for which joystick button this is. - * @param prefId Identifier for determining where a button appears on screen. - * @param layout The current screen layout as determined by [LANDSCAPE], [PORTRAIT], or [FOLDABLE]. + * @param buttonId Identifier for which joystick button this is. + * @param overlayControlData Identifier for determining where a button appears on screen. + * @param position The position on screen as represented by an x and y value between 0 and 1. * @return The initialized [InputOverlayDrawableJoystick]. */ private fun initializeOverlayJoystick( @@ -1239,19 +995,16 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : defaultResInner: Int, pressedResInner: Int, joystick: Int, - button: Int, - prefId: String, - layout: String + buttonId: Int, + overlayControlData: OverlayControlData, + position: Pair<Double, Double> ): InputOverlayDrawableJoystick { // Resources handle for fetching the initial Drawable resource. val res = context.resources - // SharedPreference to retrieve the X and Y coordinates for the InputOverlayDrawableJoystick. - val sPrefs = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) - // Decide scale based on user preference var scale = 0.3f - scale *= (sPrefs.getInt(Settings.PREF_CONTROL_SCALE, 50) + 50).toFloat() + scale *= (IntSetting.OVERLAY_SCALE.getInt() + 50).toFloat() scale /= 100f // Initialize the InputOverlayDrawableJoystick. @@ -1265,10 +1018,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : // The X and Y coordinates of the InputOverlayDrawableButton on the InputOverlay. // These were set in the input overlay configuration menu. - val drawableXPercent = sPrefs.getFloat("$prefId-X$layout", 0f) - val drawableYPercent = sPrefs.getFloat("$prefId-Y$layout", 0f) - val drawableX = (drawableXPercent * max.x + min.x).toInt() - val drawableY = (drawableYPercent * max.y + min.y).toInt() + val drawableX = (position.first * max.x + min.x).toInt() + val drawableY = (position.second * max.y + min.y).toInt() val outerScale = 1.66f // Now set the bounds for the InputOverlayDrawableJoystick. @@ -1292,14 +1043,13 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : outerRect, innerRect, joystick, - button, - prefId + buttonId, + overlayControlData.id ) // Need to set the image's position overlayDrawable.setPosition(drawableX, drawableY) - val savedOpacity = preferences.getInt(Settings.PREF_CONTROL_OPACITY, 100) - overlayDrawable.setOpacity(savedOpacity * 255 / 100) + overlayDrawable.setOpacity(IntSetting.OVERLAY_OPACITY.getInt() * 255 / 100) return overlayDrawable } } 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 import android.graphics.drawable.BitmapDrawable import android.view.MotionEvent import org.yuzu.yuzu_emu.NativeLibrary.ButtonState +import org.yuzu.yuzu_emu.overlay.model.OverlayControlData /** * Custom [BitmapDrawable] that is capable @@ -25,7 +26,7 @@ class InputOverlayDrawableButton( defaultStateBitmap: Bitmap, pressedStateBitmap: Bitmap, val buttonId: Int, - val prefId: String + val overlayControlData: OverlayControlData ) { // The ID value what motion event is tracking 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 import kotlin.math.sin import kotlin.math.sqrt 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 @@ -125,7 +125,7 @@ class InputOverlayDrawableJoystick( pressedState = true outerBitmap.alpha = 0 boundsBoxBitmap.alpha = opacity - if (EmulationMenuSettings.joystickRelCenter) { + if (BooleanSetting.JOYSTICK_REL_CENTER.getBoolean()) { virtBounds.offset( xPosition - virtBounds.centerX(), 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 @@ +// 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] + } +} 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 @@ +// 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 + } +} 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 @@ +// 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> +) 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 @@ +// 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") +} 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 @@ package org.yuzu.yuzu_emu.utils +import androidx.preference.PreferenceManager import java.io.IOException import org.yuzu.yuzu_emu.NativeLibrary import org.yuzu.yuzu_emu.YuzuApplication +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.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 object DirectoryInitialization { private var userPath: String? = null @@ -17,6 +25,7 @@ object DirectoryInitialization { initializeInternalStorage() NativeLibrary.initializeSystem(false) NativeConfig.initializeGlobalConfig() + migrateSettings() areDirectoriesReady = true } } @@ -35,4 +44,170 @@ object DirectoryInitialization { e.printStackTrace() } } + + private fun migrateSettings() { + val preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) + var saveConfig = false + val theme = preferences.migratePreference<Int>(Settings.PREF_THEME) + if (theme != null) { + IntSetting.THEME.setInt(theme) + saveConfig = true + } + + val themeMode = preferences.migratePreference<Int>(Settings.PREF_THEME_MODE) + if (themeMode != null) { + IntSetting.THEME_MODE.setInt(themeMode) + saveConfig = true + } + + val blackBackgrounds = + preferences.migratePreference<Boolean>(Settings.PREF_BLACK_BACKGROUNDS) + if (blackBackgrounds != null) { + BooleanSetting.BLACK_BACKGROUNDS.setBoolean(blackBackgrounds) + 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) { + 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 -> "" + } } 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 @@ -// 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() - } -} 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 @@ package org.yuzu.yuzu_emu.utils import org.yuzu.yuzu_emu.model.GameDir +import org.yuzu.yuzu_emu.overlay.model.OverlayControlData object NativeConfig { /** @@ -150,4 +151,21 @@ object NativeConfig { */ @Synchronized 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>) } 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 @@ +// SPDX-FileCopyrightText: 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.yuzu.yuzu_emu.utils + +import android.content.SharedPreferences + +object PreferenceUtil { + /** + * Retrieves a shared preference value and then deletes the value in storage. + * @param key Associated key for the value in this preferences instance + * @return Typed value associated with [key]. Null if no such key exists. + */ + inline fun <reified T> SharedPreferences.migratePreference(key: String): T? { + if (!this.contains(key)) { + return null + } + + val value: Any = when (T::class) { + String::class -> this.getString(key, "")!! + + Boolean::class -> this.getBoolean(key, false) + + Int::class -> this.getInt(key, 0) + + Float::class -> this.getFloat(key, 0f) + + Long::class -> this.getLong(key, 0) + + else -> throw IllegalStateException("Tried to migrate preference with invalid type!") + } + deletePreference(key) + return value as T + } + + fun SharedPreferences.deletePreference(key: String) = this.edit().remove(key).apply() +} 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 import android.content.res.Configuration import android.graphics.Color +import android.os.Build import androidx.annotation.ColorInt import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatDelegate import androidx.core.view.WindowCompat import androidx.core.view.WindowInsetsControllerCompat -import androidx.preference.PreferenceManager import kotlin.math.roundToInt import org.yuzu.yuzu_emu.R -import org.yuzu.yuzu_emu.YuzuApplication -import org.yuzu.yuzu_emu.features.settings.model.Settings +import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting +import org.yuzu.yuzu_emu.features.settings.model.IntSetting import org.yuzu.yuzu_emu.ui.main.ThemeProvider object ThemeHelper { const val SYSTEM_BAR_ALPHA = 0.9f - private const val DEFAULT = 0 - private const val MATERIAL_YOU = 1 - fun setTheme(activity: AppCompatActivity) { - val preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) setThemeMode(activity) - when (preferences.getInt(Settings.PREF_THEME, 0)) { - DEFAULT -> activity.setTheme(R.style.Theme_Yuzu_Main) - MATERIAL_YOU -> activity.setTheme(R.style.Theme_Yuzu_Main_MaterialYou) + when (Theme.from(IntSetting.THEME.getInt())) { + Theme.Default -> activity.setTheme(R.style.Theme_Yuzu_Main) + Theme.MaterialYou -> { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + activity.setTheme(R.style.Theme_Yuzu_Main_MaterialYou) + } else { + activity.setTheme(R.style.Theme_Yuzu_Main) + } + } } // Using a specific night mode check because this could apply incorrectly when using the // light app mode, dark system mode, and black backgrounds. Launching the settings activity // will then show light mode colors/navigation bars but with black backgrounds. - if (preferences.getBoolean(Settings.PREF_BLACK_BACKGROUNDS, false) && - isNightMode(activity) - ) { + if (BooleanSetting.BLACK_BACKGROUNDS.getBoolean() && isNightMode(activity)) { activity.setTheme(R.style.ThemeOverlay_Yuzu_Dark) } } @@ -60,8 +60,7 @@ object ThemeHelper { } fun setThemeMode(activity: AppCompatActivity) { - val themeMode = PreferenceManager.getDefaultSharedPreferences(activity.applicationContext) - .getInt(Settings.PREF_THEME_MODE, AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM) + val themeMode = IntSetting.THEME_MODE.getInt() activity.delegate.localNightMode = themeMode val windowController = WindowCompat.getInsetsController( activity.window, @@ -95,3 +94,12 @@ object ThemeHelper { windowController.isAppearanceLightNavigationBars = false } } + +enum class Theme(val int: Int) { + Default(0), + MaterialYou(1); + + companion object { + fun from(int: Int): Theme = entries.firstOrNull { it.int == int } ?: Default + } +} 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 @@ #include <jni.h> #include "common/string_util.h" +#include "jni/id_cache.h" std::string GetJString(JNIEnv* env, jstring jstr) { if (!jstr) { @@ -33,3 +34,11 @@ jstring ToJString(JNIEnv* env, std::string_view str) { jstring ToJString(JNIEnv* env, std::u16string_view 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); +} 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 @@ std::string GetJString(JNIEnv* env, jstring jstr); jstring ToJString(JNIEnv* env, std::string_view str); jstring ToJString(JNIEnv* env, std::u16string_view str); + +double GetJDouble(JNIEnv* env, jobject jdouble); +jobject 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() { if (global) { ReadAndroidUIValues(); ReadUIValues(); + ReadOverlayValues(); } ReadDriverValues(); } @@ -81,10 +82,42 @@ void AndroidConfig::ReadDriverValues() { 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() { if (global) { SaveAndroidUIValues(); SaveUIValues(); + SaveOverlayValues(); } SaveDriverValues(); @@ -114,8 +147,9 @@ void AndroidConfig::SavePathValues() { for (size_t i = 0; i < AndroidSettings::values.game_dirs.size(); ++i) { SetArrayIndex(i); const auto& game_dir = AndroidSettings::values.game_dirs[i]; - WriteSetting(std::string("path"), game_dir.path); - WriteSetting(std::string("deep_scan"), game_dir.deep_scan, std::make_optional(false)); + WriteStringSetting(std::string("path"), game_dir.path); + WriteBooleanSetting(std::string("deep_scan"), game_dir.deep_scan, + std::make_optional(false)); } EndArray(); @@ -130,6 +164,35 @@ void AndroidConfig::SaveDriverValues() { 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) { auto& map = Settings::values.linkage.by_category; 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: void ReadAndroidValues(); void ReadAndroidUIValues(); void ReadDriverValues(); + void ReadOverlayValues(); void ReadHidbusValues() override {} void ReadDebugControlValues() override {} void ReadPathValues() override; @@ -30,6 +31,7 @@ protected: void SaveAndroidValues(); void SaveAndroidUIValues(); void SaveDriverValues(); + void SaveOverlayValues(); void SaveHidbusValues() override {} void SaveDebugControlValues() override {} 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 { 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 { Settings::Linkage linkage; @@ -33,6 +41,28 @@ struct Values { Settings::SwitchableSetting<std::string, false> driver_path{linkage, "", "driver_path", Settings::Category::GpuDriver}; + + Settings::Setting<s32> theme{linkage, 0, "theme", 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::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; 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; static jfieldID s_pair_first_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; namespace IDCache { @@ -146,6 +158,46 @@ jfieldID GetPairSecondField() { 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 #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;"); 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 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_string_class); env->DeleteGlobalRef(s_pair_class); + env->DeleteGlobalRef(s_overlay_control_data_class); + env->DeleteGlobalRef(s_double_class); // UnInitialize applets 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(); jfieldID GetPairFirstField(); jfieldID GetPairSecondField(); +jclass GetOverlayControlDataClass(); +jmethodID GetOverlayControlDataConstructor(); +jfieldID GetOverlayControlDataIdField(); +jfieldID GetOverlayControlDataEnabledField(); +jfieldID GetOverlayControlDataLandscapePositionField(); +jfieldID GetOverlayControlDataPortraitPositionField(); +jfieldID GetOverlayControlDataFoldablePositionField(); + +jclass GetDoubleClass(); +jmethodID GetDoubleConstructor(); +jfieldID GetDoubleValueField(); + } // 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 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" 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 @@ android:checkable="true" /> <item + android:id="@+id/menu_touchscreen" + android:title="@string/touchscreen" + android:checkable="true" /> + + <item android:id="@+id/menu_reset_overlay" android:title="@string/emulation_touch_overlay_reset" /> 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 @@ <item>B</item> <item>X</item> <item>Y</item> + <item>+</item> + <item>-</item> + <item>@string/gamepad_home</item> + <item>@string/gamepad_screenshot</item> <item>L</item> <item>R</item> <item>ZL</item> <item>ZR</item> - <item>+</item> - <item>-</item> - <item>@string/gamepad_d_pad</item> <item>@string/gamepad_left_stick</item> <item>@string/gamepad_right_stick</item> <item>L3</item> <item>R3</item> - <item>@string/gamepad_home</item> - <item>@string/gamepad_screenshot</item> + <item>@string/gamepad_d_pad</item> </string-array> <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 @@ <integer name="grid_columns">1</integer> <!-- 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 --> - <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 --> - <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> 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 @@ <string name="emulation_pause">Pause emulation</string> <string name="emulation_unpause">Unpause emulation</string> <string name="emulation_input_overlay">Overlay options</string> + <string name="touchscreen">Touchscreen</string> <string name="load_settings">Loading settings…</string> 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 @@ plugins { id("com.android.application") version "8.1.2" apply false id("com.android.library") version "8.1.2" apply false - id("org.jetbrains.kotlin.android") version "1.8.21" apply false + id("org.jetbrains.kotlin.android") version "1.9.20" apply false } tasks.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) { case Category::CpuDebug: case Category::CpuUnsafe: return "Cpu"; + case Category::Overlay: + return "Overlay"; case Category::Renderer: case Category::RendererAdvanced: 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 { Cpu, CpuDebug, CpuUnsafe, + Overlay, Renderer, RendererAdvanced, 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) { // No custom profile selected return; } - WriteSetting(std::string(player_prefix).append("profile_name"), player.profile_name, - std::make_optional(std::string(""))); + WriteStringSetting(std::string(player_prefix).append("profile_name"), player.profile_name, + std::make_optional(std::string(""))); } - WriteSetting(std::string(player_prefix).append("type"), static_cast<u8>(player.controller_type), - std::make_optional(static_cast<u8>(Settings::ControllerType::ProController))); + WriteIntegerSetting( + std::string(player_prefix).append("type"), static_cast<u8>(player.controller_type), + std::make_optional(static_cast<u8>(Settings::ControllerType::ProController))); if (!player_prefix.empty() || !Settings::IsConfiguringGlobal()) { - WriteSetting(std::string(player_prefix).append("connected"), player.connected, - std::make_optional(player_index == 0)); - WriteSetting(std::string(player_prefix).append("vibration_enabled"), - player.vibration_enabled, std::make_optional(true)); - WriteSetting(std::string(player_prefix).append("vibration_strength"), - player.vibration_strength, std::make_optional(100)); - WriteSetting(std::string(player_prefix).append("body_color_left"), player.body_color_left, - std::make_optional(Settings::JOYCON_BODY_NEON_BLUE)); - WriteSetting(std::string(player_prefix).append("body_color_right"), player.body_color_right, - std::make_optional(Settings::JOYCON_BODY_NEON_RED)); - WriteSetting(std::string(player_prefix).append("button_color_left"), - player.button_color_left, - std::make_optional(Settings::JOYCON_BUTTONS_NEON_BLUE)); - WriteSetting(std::string(player_prefix).append("button_color_right"), - player.button_color_right, - std::make_optional(Settings::JOYCON_BUTTONS_NEON_RED)); + WriteBooleanSetting(std::string(player_prefix).append("connected"), player.connected, + std::make_optional(player_index == 0)); + WriteIntegerSetting(std::string(player_prefix).append("vibration_enabled"), + player.vibration_enabled, std::make_optional(true)); + WriteIntegerSetting(std::string(player_prefix).append("vibration_strength"), + player.vibration_strength, std::make_optional(100)); + WriteIntegerSetting(std::string(player_prefix).append("body_color_left"), + player.body_color_left, + std::make_optional(Settings::JOYCON_BODY_NEON_BLUE)); + WriteIntegerSetting(std::string(player_prefix).append("body_color_right"), + player.body_color_right, + std::make_optional(Settings::JOYCON_BODY_NEON_RED)); + WriteIntegerSetting(std::string(player_prefix).append("button_color_left"), + player.button_color_left, + std::make_optional(Settings::JOYCON_BUTTONS_NEON_BLUE)); + WriteIntegerSetting(std::string(player_prefix).append("button_color_right"), + player.button_color_right, + std::make_optional(Settings::JOYCON_BUTTONS_NEON_RED)); } } void Config::SaveTouchscreenValues() { const auto& touchscreen = Settings::values.touchscreen; - WriteSetting(std::string("touchscreen_enabled"), touchscreen.enabled, std::make_optional(true)); + WriteBooleanSetting(std::string("touchscreen_enabled"), touchscreen.enabled, + std::make_optional(true)); - WriteSetting(std::string("touchscreen_angle"), touchscreen.rotation_angle, - std::make_optional(static_cast<u32>(0))); - WriteSetting(std::string("touchscreen_diameter_x"), touchscreen.diameter_x, - std::make_optional(static_cast<u32>(15))); - WriteSetting(std::string("touchscreen_diameter_y"), touchscreen.diameter_y, - std::make_optional(static_cast<u32>(15))); + WriteIntegerSetting(std::string("touchscreen_angle"), touchscreen.rotation_angle, + std::make_optional(static_cast<u32>(0))); + WriteIntegerSetting(std::string("touchscreen_diameter_x"), touchscreen.diameter_x, + std::make_optional(static_cast<u32>(15))); + WriteIntegerSetting(std::string("touchscreen_diameter_y"), touchscreen.diameter_y, + std::make_optional(static_cast<u32>(15))); } void Config::SaveMotionTouchValues() { BeginArray(std::string("touch_from_button_maps")); for (std::size_t p = 0; p < Settings::values.touch_from_button_maps.size(); ++p) { SetArrayIndex(static_cast<int>(p)); - WriteSetting(std::string("name"), Settings::values.touch_from_button_maps[p].name, - std::make_optional(std::string("default"))); + WriteStringSetting(std::string("name"), Settings::values.touch_from_button_maps[p].name, + std::make_optional(std::string("default"))); BeginArray(std::string("entries")); for (std::size_t q = 0; q < Settings::values.touch_from_button_maps[p].buttons.size(); ++q) { SetArrayIndex(static_cast<int>(q)); - WriteSetting(std::string("bind"), - Settings::values.touch_from_button_maps[p].buttons[q]); + WriteStringSetting(std::string("bind"), + Settings::values.touch_from_button_maps[p].buttons[q]); } EndArray(); // entries } @@ -520,16 +524,16 @@ void Config::SaveCoreValues() { void Config::SaveDataStorageValues() { BeginGroup(Settings::TranslateCategory(Settings::Category::DataStorage)); - WriteSetting(std::string("nand_directory"), FS::GetYuzuPathString(FS::YuzuPath::NANDDir), - std::make_optional(FS::GetYuzuPathString(FS::YuzuPath::NANDDir))); - WriteSetting(std::string("sdmc_directory"), FS::GetYuzuPathString(FS::YuzuPath::SDMCDir), - std::make_optional(FS::GetYuzuPathString(FS::YuzuPath::SDMCDir))); - WriteSetting(std::string("load_directory"), FS::GetYuzuPathString(FS::YuzuPath::LoadDir), - std::make_optional(FS::GetYuzuPathString(FS::YuzuPath::LoadDir))); - WriteSetting(std::string("dump_directory"), FS::GetYuzuPathString(FS::YuzuPath::DumpDir), - std::make_optional(FS::GetYuzuPathString(FS::YuzuPath::DumpDir))); - WriteSetting(std::string("tas_directory"), FS::GetYuzuPathString(FS::YuzuPath::TASDir), - std::make_optional(FS::GetYuzuPathString(FS::YuzuPath::TASDir))); + WriteStringSetting(std::string("nand_directory"), FS::GetYuzuPathString(FS::YuzuPath::NANDDir), + std::make_optional(FS::GetYuzuPathString(FS::YuzuPath::NANDDir))); + WriteStringSetting(std::string("sdmc_directory"), FS::GetYuzuPathString(FS::YuzuPath::SDMCDir), + std::make_optional(FS::GetYuzuPathString(FS::YuzuPath::SDMCDir))); + WriteStringSetting(std::string("load_directory"), FS::GetYuzuPathString(FS::YuzuPath::LoadDir), + std::make_optional(FS::GetYuzuPathString(FS::YuzuPath::LoadDir))); + WriteStringSetting(std::string("dump_directory"), FS::GetYuzuPathString(FS::YuzuPath::DumpDir), + std::make_optional(FS::GetYuzuPathString(FS::YuzuPath::DumpDir))); + WriteStringSetting(std::string("tas_directory"), FS::GetYuzuPathString(FS::YuzuPath::TASDir), + std::make_optional(FS::GetYuzuPathString(FS::YuzuPath::TASDir))); WriteCategory(Settings::Category::DataStorage); @@ -540,7 +544,7 @@ void Config::SaveDebuggingValues() { BeginGroup(Settings::TranslateCategory(Settings::Category::Debugging)); // Intentionally not using the QT default setting as this is intended to be changed in the ini - WriteSetting(std::string("record_frame_times"), Settings::values.record_frame_times); + WriteBooleanSetting(std::string("record_frame_times"), Settings::values.record_frame_times); WriteCategory(Settings::Category::Debugging); WriteCategory(Settings::Category::DebuggingGraphics); @@ -564,11 +568,13 @@ void Config::SaveDisabledAddOnValues() { BeginArray(std::string("")); for (const auto& elem : Settings::values.disabled_addons) { SetArrayIndex(i); - WriteSetting(std::string("title_id"), elem.first, std::make_optional(static_cast<u64>(0))); + WriteIntegerSetting(std::string("title_id"), elem.first, + std::make_optional(static_cast<u64>(0))); BeginArray(std::string("disabled")); for (std::size_t j = 0; j < elem.second.size(); ++j) { SetArrayIndex(static_cast<int>(j)); - WriteSetting(std::string("d"), elem.second[j], std::make_optional(std::string(""))); + WriteStringSetting(std::string("d"), elem.second[j], + std::make_optional(std::string(""))); } EndArray(); // disabled ++i; @@ -609,8 +615,8 @@ void Config::SaveRendererValues() { void Config::SaveScreenshotValues() { BeginGroup(Settings::TranslateCategory(Settings::Category::Screenshots)); - WriteSetting(std::string("screenshot_path"), - FS::GetYuzuPathString(FS::YuzuPath::ScreenshotsDir)); + WriteStringSetting(std::string("screenshot_path"), + FS::GetYuzuPathString(FS::YuzuPath::ScreenshotsDir)); WriteCategory(Settings::Category::Screenshots); EndGroup(); @@ -746,46 +752,70 @@ bool Config::Exists(const std::string& section, const std::string& key) const { return !value.empty(); } -template <typename Type> -void Config::WriteSetting(const std::string& key, const Type& value, - const std::optional<Type>& default_value, - const std::optional<bool>& use_global) { - std::string full_key = GetFullKey(key, false); +void Config::WriteBooleanSetting(const std::string& key, const bool& value, + const std::optional<bool>& default_value, + const std::optional<bool>& use_global) { + std::optional<std::string> string_default = std::nullopt; + if (default_value.has_value()) { + string_default = std::make_optional(ToString(default_value.value())); + } + WritePreparedSetting(key, AdjustOutputString(ToString(value)), string_default, use_global); +} - std::string saved_value; - std::string string_default; - if constexpr (std::is_same_v<Type, std::string>) { - saved_value.append(AdjustOutputString(value)); - if (default_value.has_value()) { - string_default.append(AdjustOutputString(default_value.value())); - } - } else { - saved_value.append(AdjustOutputString(ToString(value))); - if (default_value.has_value()) { - string_default.append(ToString(default_value.value())); - } +template <typename T> +std::enable_if_t<std::is_integral_v<T>> Config::WriteIntegerSetting( + const std::string& key, const T& value, const std::optional<T>& default_value, + const std::optional<bool>& use_global) { + std::optional<std::string> string_default = std::nullopt; + if (default_value.has_value()) { + string_default = std::make_optional(ToString(default_value.value())); + } + WritePreparedSetting(key, AdjustOutputString(ToString(value)), string_default, use_global); +} + +void Config::WriteDoubleSetting(const std::string& key, const double& value, + const std::optional<double>& default_value, + const std::optional<bool>& use_global) { + std::optional<std::string> string_default = std::nullopt; + if (default_value.has_value()) { + string_default = std::make_optional(ToString(default_value.value())); } + WritePreparedSetting(key, AdjustOutputString(ToString(value)), string_default, use_global); +} - if (default_value.has_value() && use_global.has_value()) { +void Config::WriteStringSetting(const std::string& key, const std::string& value, + const std::optional<std::string>& default_value, + const std::optional<bool>& use_global) { + std::optional string_default = default_value; + if (default_value.has_value()) { + string_default.value().append(AdjustOutputString(default_value.value())); + } + WritePreparedSetting(key, AdjustOutputString(value), string_default, use_global); +} + +void Config::WritePreparedSetting(const std::string& key, const std::string& adjusted_value, + const std::optional<std::string>& adjusted_default_value, + const std::optional<bool>& use_global) { + std::string full_key = GetFullKey(key, false); + if (adjusted_default_value.has_value() && use_global.has_value()) { if (!global) { - WriteSettingInternal(std::string(full_key).append("\\global"), - ToString(use_global.value())); + WriteString(std::string(full_key).append("\\global"), ToString(use_global.value())); } if (global || use_global.value() == false) { - WriteSettingInternal(std::string(full_key).append("\\default"), - ToString(string_default == saved_value)); - WriteSettingInternal(full_key, saved_value); + WriteString(std::string(full_key).append("\\default"), + ToString(adjusted_default_value == adjusted_value)); + WriteString(full_key, adjusted_value); } - } else if (default_value.has_value() && !use_global.has_value()) { - WriteSettingInternal(std::string(full_key).append("\\default"), - ToString(string_default == saved_value)); - WriteSettingInternal(full_key, saved_value); + } else if (adjusted_default_value.has_value() && !use_global.has_value()) { + WriteString(std::string(full_key).append("\\default"), + ToString(adjusted_default_value == adjusted_value)); + WriteString(full_key, adjusted_value); } else { - WriteSettingInternal(full_key, saved_value); + WriteString(full_key, adjusted_value); } } -void Config::WriteSettingInternal(const std::string& key, const std::string& value) { +void Config::WriteString(const std::string& key, const std::string& value) { config->SetValue(GetSection().c_str(), key.c_str(), value.c_str()); } @@ -861,17 +891,17 @@ void Config::WriteSettingGeneric(const Settings::BasicSetting* const setting) { std::string key = AdjustKey(setting->GetLabel()); if (setting->Switchable()) { if (!global) { - WriteSetting(std::string(key).append("\\use_global"), setting->UsingGlobal()); + WriteBooleanSetting(std::string(key).append("\\use_global"), setting->UsingGlobal()); } if (global || !setting->UsingGlobal()) { - WriteSetting(std::string(key).append("\\default"), - setting->ToString() == setting->DefaultToString()); - WriteSetting(key, setting->ToString()); + WriteBooleanSetting(std::string(key).append("\\default"), + setting->ToString() == setting->DefaultToString()); + WriteStringSetting(key, setting->ToString()); } } else if (global) { - WriteSetting(std::string(key).append("\\default"), - setting->ToString() == setting->DefaultToString()); - WriteSetting(key, setting->ToString()); + WriteBooleanSetting(std::string(key).append("\\default"), + setting->ToString() == setting->DefaultToString()); + WriteStringSetting(key, setting->ToString()); } } 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: * @param use_global Specifies if the custom or global config should be in use, for custom * configs */ - template <typename Type = int> - void WriteSetting(const std::string& key, const Type& value, - const std::optional<Type>& default_value = std::nullopt, - const std::optional<bool>& use_global = std::nullopt); - void WriteSettingInternal(const std::string& key, const std::string& value); + void WriteBooleanSetting(const std::string& key, const bool& value, + const std::optional<bool>& default_value = std::nullopt, + const std::optional<bool>& use_global = std::nullopt); + template <typename T> + std::enable_if_t<std::is_integral_v<T>> WriteIntegerSetting( + const std::string& key, const T& value, + const std::optional<T>& default_value = std::nullopt, + const std::optional<bool>& use_global = std::nullopt); + void WriteDoubleSetting(const std::string& key, const double& value, + const std::optional<double>& default_value = std::nullopt, + const std::optional<bool>& use_global = std::nullopt); + void WriteStringSetting(const std::string& key, const std::string& value, + const std::optional<std::string>& default_value = std::nullopt, + const std::optional<bool>& use_global = std::nullopt); void ReadCategory(Settings::Category category); void WriteCategory(Settings::Category category); @@ -175,8 +184,10 @@ protected: return value_ ? "true" : "false"; } else if constexpr (std::is_same_v<T, u64>) { return std::to_string(static_cast<u64>(value_)); - } else { + } else if constexpr (std::is_same_v<T, s64>) { return std::to_string(static_cast<s64>(value_)); + } else { + return std::to_string(value_); } } @@ -197,9 +208,13 @@ protected: const bool global; private: - inline static std::array<char, 19> special_characters = {'!', '#', '$', '%', '^', '&', '*', - '|', ';', '\'', '\"', ',', '<', '.', - '>', '?', '`', '~', '='}; + void WritePreparedSetting(const std::string& key, const std::string& adjusted_value, + const std::optional<std::string>& adjusted_default_value, + const std::optional<bool>& use_global); + void WriteString(const std::string& key, const std::string& value); + + inline static std::array<char, 18> special_characters = { + '!', '#', '$', '%', '^', '&', '*', '|', ';', '\'', '\"', ',', '<', '>', '?', '`', '~', '='}; struct ConfigArray { 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) { for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); - WriteSetting(std::string(player_prefix).append(Settings::NativeButton::mapping[i]), - player.buttons[i], std::make_optional(default_param)); + WriteStringSetting(std::string(player_prefix).append(Settings::NativeButton::mapping[i]), + player.buttons[i], std::make_optional(default_param)); } for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], default_analogs[i][3], default_stick_mod[i], 0.5f); - WriteSetting(std::string(player_prefix).append(Settings::NativeAnalog::mapping[i]), - player.analogs[i], std::make_optional(default_param)); + WriteStringSetting(std::string(player_prefix).append(Settings::NativeAnalog::mapping[i]), + player.analogs[i], std::make_optional(default_param)); } for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) { const std::string default_param = InputCommon::GenerateKeyboardParam(default_motions[i]); - WriteSetting(std::string(player_prefix).append(Settings::NativeMotion::mapping[i]), - player.motions[i], std::make_optional(default_param)); + WriteStringSetting(std::string(player_prefix).append(Settings::NativeMotion::mapping[i]), + player.motions[i], std::make_optional(default_param)); } } void QtConfig::SaveDebugControlValues() { for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); - WriteSetting(std::string("debug_pad_").append(Settings::NativeButton::mapping[i]), - Settings::values.debug_pad_buttons[i], std::make_optional(default_param)); + WriteStringSetting(std::string("debug_pad_").append(Settings::NativeButton::mapping[i]), + Settings::values.debug_pad_buttons[i], + std::make_optional(default_param)); } for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], default_analogs[i][3], default_stick_mod[i], 0.5f); - WriteSetting(std::string("debug_pad_").append(Settings::NativeAnalog::mapping[i]), - Settings::values.debug_pad_analogs[i], std::make_optional(default_param)); + WriteStringSetting(std::string("debug_pad_").append(Settings::NativeAnalog::mapping[i]), + Settings::values.debug_pad_analogs[i], + std::make_optional(default_param)); } } void QtConfig::SaveHidbusValues() { const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( 0, 0, default_ringcon_analogs[0], default_ringcon_analogs[1], 0, 0.05f); - WriteSetting(std::string("ring_controller"), Settings::values.ringcon_analogs, - std::make_optional(default_param)); + WriteStringSetting(std::string("ring_controller"), Settings::values.ringcon_analogs, + std::make_optional(default_param)); } void QtConfig::SaveQtControlValues() { @@ -409,19 +411,20 @@ void QtConfig::SavePathValues() { WriteCategory(Settings::Category::Paths); - WriteSetting(std::string("romsPath"), UISettings::values.roms_path); + WriteStringSetting(std::string("romsPath"), UISettings::values.roms_path); BeginArray(std::string("gamedirs")); for (int i = 0; i < UISettings::values.game_dirs.size(); ++i) { SetArrayIndex(i); const auto& game_dir = UISettings::values.game_dirs[i]; - WriteSetting(std::string("path"), game_dir.path); - WriteSetting(std::string("deep_scan"), game_dir.deep_scan, std::make_optional(false)); - WriteSetting(std::string("expanded"), game_dir.expanded, std::make_optional(true)); + WriteStringSetting(std::string("path"), game_dir.path); + WriteBooleanSetting(std::string("deep_scan"), game_dir.deep_scan, + std::make_optional(false)); + WriteBooleanSetting(std::string("expanded"), game_dir.expanded, std::make_optional(true)); } EndArray(); - WriteSetting(std::string("recentFiles"), - UISettings::values.recent_files.join(QStringLiteral(", ")).toStdString()); + WriteStringSetting(std::string("recentFiles"), + UISettings::values.recent_files.join(QStringLiteral(", ")).toStdString()); EndGroup(); } @@ -438,14 +441,14 @@ void QtConfig::SaveShortcutValues() { BeginGroup(group); BeginGroup(name); - WriteSetting(std::string("KeySeq"), shortcut.keyseq, - std::make_optional(default_hotkey.keyseq)); - WriteSetting(std::string("Controller_KeySeq"), shortcut.controller_keyseq, - std::make_optional(default_hotkey.controller_keyseq)); - WriteSetting(std::string("Context"), shortcut.context, - std::make_optional(default_hotkey.context)); - WriteSetting(std::string("Repeat"), shortcut.repeat, - std::make_optional(default_hotkey.repeat)); + WriteStringSetting(std::string("KeySeq"), shortcut.keyseq, + std::make_optional(default_hotkey.keyseq)); + WriteStringSetting(std::string("Controller_KeySeq"), shortcut.controller_keyseq, + std::make_optional(default_hotkey.controller_keyseq)); + WriteIntegerSetting(std::string("Context"), shortcut.context, + std::make_optional(default_hotkey.context)); + WriteBooleanSetting(std::string("Repeat"), shortcut.repeat, + std::make_optional(default_hotkey.repeat)); EndGroup(); // name EndGroup(); // group @@ -460,9 +463,10 @@ void QtConfig::SaveUIValues() { WriteCategory(Settings::Category::Ui); WriteCategory(Settings::Category::UiGeneral); - WriteSetting(std::string("theme"), UISettings::values.theme, - std::make_optional(std::string( - UISettings::themes[static_cast<size_t>(UISettings::default_theme)].second))); + WriteStringSetting( + std::string("theme"), UISettings::values.theme, + std::make_optional(std::string( + UISettings::themes[static_cast<size_t>(UISettings::default_theme)].second))); SaveUIGamelistValues(); SaveUILayoutValues(); @@ -482,7 +486,7 @@ void QtConfig::SaveUIGamelistValues() { BeginArray(std::string("favorites")); for (int i = 0; i < UISettings::values.favorited_ids.size(); i++) { SetArrayIndex(i); - WriteSetting(std::string("program_id"), UISettings::values.favorited_ids[i]); + WriteIntegerSetting(std::string("program_id"), UISettings::values.favorited_ids[i]); } EndArray(); // favorites @@ -506,14 +510,15 @@ void QtConfig::SaveMultiplayerValues() { BeginArray(std::string("username_ban_list")); for (std::size_t i = 0; i < UISettings::values.multiplayer_ban_list.first.size(); ++i) { SetArrayIndex(static_cast<int>(i)); - WriteSetting(std::string("username"), UISettings::values.multiplayer_ban_list.first[i]); + WriteStringSetting(std::string("username"), + UISettings::values.multiplayer_ban_list.first[i]); } EndArray(); // username_ban_list BeginArray(std::string("ip_ban_list")); for (std::size_t i = 0; i < UISettings::values.multiplayer_ban_list.second.size(); ++i) { SetArrayIndex(static_cast<int>(i)); - WriteSetting(std::string("ip"), UISettings::values.multiplayer_ban_list.second[i]); + WriteStringSetting(std::string("ip"), UISettings::values.multiplayer_ban_list.second[i]); } EndArray(); // ip_ban_list 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) { for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); - WriteSetting(std::string(player_prefix).append(Settings::NativeButton::mapping[i]), - player.buttons[i], std::make_optional(default_param)); + WriteStringSetting(std::string(player_prefix).append(Settings::NativeButton::mapping[i]), + player.buttons[i], std::make_optional(default_param)); } for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], default_analogs[i][3], default_stick_mod[i], 0.5f); - WriteSetting(std::string(player_prefix).append(Settings::NativeAnalog::mapping[i]), - player.analogs[i], std::make_optional(default_param)); + WriteStringSetting(std::string(player_prefix).append(Settings::NativeAnalog::mapping[i]), + player.analogs[i], std::make_optional(default_param)); } for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) { const std::string default_param = InputCommon::GenerateKeyboardParam(default_motions[i]); - WriteSetting(std::string(player_prefix).append(Settings::NativeMotion::mapping[i]), - player.motions[i], std::make_optional(default_param)); + WriteStringSetting(std::string(player_prefix).append(Settings::NativeMotion::mapping[i]), + player.motions[i], std::make_optional(default_param)); } } void SdlConfig::SaveDebugControlValues() { for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); - WriteSetting(std::string("debug_pad_").append(Settings::NativeButton::mapping[i]), - Settings::values.debug_pad_buttons[i], std::make_optional(default_param)); + WriteStringSetting(std::string("debug_pad_").append(Settings::NativeButton::mapping[i]), + Settings::values.debug_pad_buttons[i], + std::make_optional(default_param)); } for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], default_analogs[i][3], default_stick_mod[i], 0.5f); - WriteSetting(std::string("debug_pad_").append(Settings::NativeAnalog::mapping[i]), - Settings::values.debug_pad_analogs[i], std::make_optional(default_param)); + WriteStringSetting(std::string("debug_pad_").append(Settings::NativeAnalog::mapping[i]), + Settings::values.debug_pad_analogs[i], + std::make_optional(default_param)); } } void SdlConfig::SaveHidbusValues() { const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( 0, 0, default_ringcon_analogs[0], default_ringcon_analogs[1], 0, 0.05f); - WriteSetting(std::string("ring_controller"), Settings::values.ringcon_analogs, - std::make_optional(default_param)); + WriteStringSetting(std::string("ring_controller"), Settings::values.ringcon_analogs, + std::make_optional(default_param)); } std::vector<Settings::BasicSetting*>& SdlConfig::FindRelevantList(Settings::Category category) { |