diff --git a/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.kt b/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.kt index 5f5215876..c9d6c9e3f 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.kt @@ -38,7 +38,7 @@ import org.citra.citra_emu.display.SecondaryDisplay import org.citra.citra_emu.features.hotkeys.HotkeyUtility import org.citra.citra_emu.features.settings.model.BooleanSetting import org.citra.citra_emu.features.settings.model.IntSetting -import org.citra.citra_emu.features.settings.model.SettingsViewModel +import org.citra.citra_emu.features.settings.model.Settings import org.citra.citra_emu.features.settings.model.view.InputBindingSetting import org.citra.citra_emu.fragments.EmulationFragment import org.citra.citra_emu.fragments.MessageDialogFragment @@ -52,14 +52,13 @@ import org.citra.citra_emu.utils.Log import org.citra.citra_emu.utils.RefreshRateUtil import org.citra.citra_emu.utils.ThemeUtil import org.citra.citra_emu.viewmodel.EmulationViewModel +import org.citra.citra_emu.features.settings.utils.SettingsFile class EmulationActivity : AppCompatActivity() { private val preferences: SharedPreferences get() = PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext) var isActivityRecreated = false - private val emulationViewModel: EmulationViewModel by viewModels() - val settingsViewModel: SettingsViewModel by viewModels() - + val emulationViewModel: EmulationViewModel by viewModels() private lateinit var binding: ActivityEmulationBinding private lateinit var screenAdjustmentUtil: ScreenAdjustmentUtil private lateinit var hotkeyUtility: HotkeyUtility @@ -88,14 +87,22 @@ class EmulationActivity : AppCompatActivity() { RefreshRateUtil.enforceRefreshRate(this, sixtyHz = true) ThemeUtil.setTheme(this) - settingsViewModel.settings.loadSettings() + + super.onCreate(savedInstanceState) - secondaryDisplay = SecondaryDisplay(this) + + // load global settings if for some reason they aren't (should be loaded in MainActivity) + if (Settings.settings.getAllGlobal().isEmpty()) { + SettingsFile.loadSettings(Settings.settings) + } + // once per-game settings are added, load them here! + + secondaryDisplay = SecondaryDisplay(this, Settings.settings) secondaryDisplay.updateDisplay() binding = ActivityEmulationBinding.inflate(layoutInflater) - screenAdjustmentUtil = ScreenAdjustmentUtil(this, windowManager, settingsViewModel.settings) - hotkeyUtility = HotkeyUtility(screenAdjustmentUtil, this) + screenAdjustmentUtil = ScreenAdjustmentUtil(this, windowManager, Settings.settings) + hotkeyUtility = HotkeyUtility(screenAdjustmentUtil, this, Settings.settings) setContentView(binding.root) val navHostFragment = @@ -142,7 +149,7 @@ class EmulationActivity : AppCompatActivity() { override fun onResume() { super.onResume() enableFullscreenImmersive() - applyOrientationSettings() // Check for orientation settings changes on runtime + applyOrientationSettings() } override fun onStop() { @@ -179,6 +186,8 @@ class EmulationActivity : AppCompatActivity() { secondaryDisplay.releasePresentation() secondaryDisplay.releaseVD() + Settings.settings.removePerGameSettings() + super.onDestroy() } @@ -229,11 +238,11 @@ class EmulationActivity : AppCompatActivity() { ).show() } - private fun enableFullscreenImmersive() { + fun enableFullscreenImmersive() { val attributes = window.attributes attributes.layoutInDisplayCutoutMode = - if (BooleanSetting.EXPAND_TO_CUTOUT_AREA.boolean) { + if (Settings.settings.get(BooleanSetting.EXPAND_TO_CUTOUT_AREA)) { WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES } else { WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER @@ -250,8 +259,8 @@ class EmulationActivity : AppCompatActivity() { } } - private fun applyOrientationSettings() { - val orientationOption = IntSetting.ORIENTATION_OPTION.int + fun applyOrientationSettings() { + val orientationOption = Settings.settings.get(IntSetting.ORIENTATION_OPTION) screenAdjustmentUtil.changeActivityOrientation(orientationOption) } diff --git a/src/android/app/src/main/java/org/citra/citra_emu/display/ScreenAdjustmentUtil.kt b/src/android/app/src/main/java/org/citra/citra_emu/display/ScreenAdjustmentUtil.kt index e63960fa8..d6b477e4b 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/display/ScreenAdjustmentUtil.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/display/ScreenAdjustmentUtil.kt @@ -29,13 +29,13 @@ class ScreenAdjustmentUtil( isEnabled, windowManager.defaultDisplay.rotation ) - BooleanSetting.SWAP_SCREEN.boolean = isEnabled - settings.saveSetting(BooleanSetting.SWAP_SCREEN, SettingsFile.FILE_NAME_CONFIG) + settings.update(BooleanSetting.SWAP_SCREEN, isEnabled) + SettingsFile.saveSetting(BooleanSetting.SWAP_SCREEN, settings) } fun cycleLayouts() { - val landscapeLayoutsToCycle = IntListSetting.LAYOUTS_TO_CYCLE.list; + val landscapeLayoutsToCycle = settings.get(IntListSetting.LAYOUTS_TO_CYCLE) val landscapeValues = if (landscapeLayoutsToCycle.isNotEmpty()) landscapeLayoutsToCycle.toIntArray() @@ -45,12 +45,12 @@ class ScreenAdjustmentUtil( val portraitValues = context.resources.getIntArray(R.array.portraitValues) if (NativeLibrary.isPortraitMode) { - val currentLayout = IntSetting.PORTRAIT_SCREEN_LAYOUT.int + val currentLayout = settings.get(IntSetting.PORTRAIT_SCREEN_LAYOUT) val pos = portraitValues.indexOf(currentLayout) val layoutOption = portraitValues[(pos + 1) % portraitValues.size] changePortraitOrientation(layoutOption) } else { - val currentLayout = IntSetting.SCREEN_LAYOUT.int + val currentLayout = settings.get(IntSetting.SCREEN_LAYOUT) val pos = landscapeValues.indexOf(currentLayout) val layoutOption = landscapeValues[(pos + 1) % landscapeValues.size] changeScreenOrientation(layoutOption) @@ -58,30 +58,30 @@ class ScreenAdjustmentUtil( } fun changePortraitOrientation(layoutOption: Int) { - IntSetting.PORTRAIT_SCREEN_LAYOUT.int = layoutOption - settings.saveSetting(IntSetting.PORTRAIT_SCREEN_LAYOUT, SettingsFile.FILE_NAME_CONFIG) + settings.update(IntSetting.PORTRAIT_SCREEN_LAYOUT, layoutOption) + SettingsFile.saveSetting(IntSetting.PORTRAIT_SCREEN_LAYOUT, settings) NativeLibrary.reloadSettings() NativeLibrary.updateFramebuffer(NativeLibrary.isPortraitMode) } fun changeScreenOrientation(layoutOption: Int) { - IntSetting.SCREEN_LAYOUT.int = layoutOption - settings.saveSetting(IntSetting.SCREEN_LAYOUT, SettingsFile.FILE_NAME_CONFIG) + settings.update(IntSetting.SCREEN_LAYOUT, layoutOption) + SettingsFile.saveSetting(IntSetting.SCREEN_LAYOUT, settings) NativeLibrary.reloadSettings() NativeLibrary.updateFramebuffer(NativeLibrary.isPortraitMode) } fun changeActivityOrientation(orientationOption: Int) { val activity = context as? Activity ?: return - IntSetting.ORIENTATION_OPTION.int = orientationOption - settings.saveSetting(IntSetting.ORIENTATION_OPTION, SettingsFile.FILE_NAME_CONFIG) + settings.update(IntSetting.ORIENTATION_OPTION, orientationOption) + SettingsFile.saveSetting(IntSetting.ORIENTATION_OPTION, settings) activity.requestedOrientation = orientationOption } fun toggleScreenUpright() { - val uprightBoolean = BooleanSetting.UPRIGHT_SCREEN.boolean - BooleanSetting.UPRIGHT_SCREEN.boolean = !uprightBoolean - settings.saveSetting(BooleanSetting.UPRIGHT_SCREEN, SettingsFile.FILE_NAME_CONFIG) + val uprightBoolean = settings.get(BooleanSetting.UPRIGHT_SCREEN) + settings.update(BooleanSetting.UPRIGHT_SCREEN, !uprightBoolean) + SettingsFile.saveSetting(BooleanSetting.UPRIGHT_SCREEN, settings) NativeLibrary.reloadSettings() NativeLibrary.updateFramebuffer(NativeLibrary.isPortraitMode) diff --git a/src/android/app/src/main/java/org/citra/citra_emu/display/SecondaryDisplay.kt b/src/android/app/src/main/java/org/citra/citra_emu/display/SecondaryDisplay.kt index d09daab41..2fec60d2a 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/display/SecondaryDisplay.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/display/SecondaryDisplay.kt @@ -16,8 +16,9 @@ import android.view.SurfaceView import android.view.WindowManager import org.citra.citra_emu.features.settings.model.IntSetting import org.citra.citra_emu.NativeLibrary +import org.citra.citra_emu.features.settings.model.Settings -class SecondaryDisplay(val context: Context) : DisplayManager.DisplayListener { +class SecondaryDisplay(val context: Context, private val settings: Settings) : DisplayManager.DisplayListener { private var pres: SecondaryDisplayPresentation? = null private val displayManager = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager private val vd: VirtualDisplay @@ -70,8 +71,7 @@ class SecondaryDisplay(val context: Context) : DisplayManager.DisplayListener { // decide if we are going to the external display or the internal one var display = getExternalDisplay(context) - if (display == null || - IntSetting.SECONDARY_DISPLAY_LAYOUT.int == SecondaryDisplayLayout.NONE.int) { + if (display == null || settings.get(IntSetting.SECONDARY_DISPLAY_LAYOUT) == SecondaryDisplayLayout.NONE.int) { display = vd.display } diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/hotkeys/HotkeyUtility.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/hotkeys/HotkeyUtility.kt index d01d5f769..d57ff5e8b 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/hotkeys/HotkeyUtility.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/hotkeys/HotkeyUtility.kt @@ -19,7 +19,8 @@ import org.citra.citra_emu.features.settings.model.Settings class HotkeyUtility( private val screenAdjustmentUtil: ScreenAdjustmentUtil, - private val context: Context + private val context: Context, + private val settings: Settings ) { private val hotkeyButtons = Hotkey.entries.map { it.button } @@ -112,7 +113,7 @@ class HotkeyUtility( Hotkey.CYCLE_LAYOUT.button -> screenAdjustmentUtil.cycleLayouts() Hotkey.CLOSE_GAME.button -> EmulationLifecycleUtil.closeGame() Hotkey.PAUSE_OR_RESUME.button -> EmulationLifecycleUtil.pauseOrResume() - Hotkey.TURBO_LIMIT.button -> TurboHelper.toggleTurbo(true) + Hotkey.TURBO_LIMIT.button -> TurboHelper.toggleTurbo(true, settings) Hotkey.QUICKSAVE.button -> { NativeLibrary.saveState(NativeLibrary.QUICKSAVE_SLOT) Toast.makeText( diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/AbstractBooleanSetting.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/AbstractBooleanSetting.kt deleted file mode 100644 index e60b1ca36..000000000 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/AbstractBooleanSetting.kt +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright 2023 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -package org.citra.citra_emu.features.settings.model - -interface AbstractBooleanSetting : AbstractSetting { - var boolean: Boolean -} diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/AbstractFloatSetting.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/AbstractFloatSetting.kt deleted file mode 100644 index c3b2c8e2e..000000000 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/AbstractFloatSetting.kt +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright 2023 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -package org.citra.citra_emu.features.settings.model - -interface AbstractFloatSetting : AbstractSetting { - var float: Float -} diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/AbstractIntSetting.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/AbstractIntSetting.kt deleted file mode 100644 index 7c3660854..000000000 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/AbstractIntSetting.kt +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright 2023 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -package org.citra.citra_emu.features.settings.model - -interface AbstractIntSetting : AbstractSetting { - var int: Int -} diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/AbstractListSetting.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/AbstractListSetting.kt deleted file mode 100644 index d89db48af..000000000 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/AbstractListSetting.kt +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright Citra Emulator Project / Azahar Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -package org.citra.citra_emu.features.settings.model - -interface AbstractListSetting : AbstractSetting { - var list: List -} diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/AbstractSetting.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/AbstractSetting.kt index 54af79efb..1d9e2f456 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/AbstractSetting.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/AbstractSetting.kt @@ -1,13 +1,16 @@ -// Copyright 2023 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. package org.citra.citra_emu.features.settings.model -interface AbstractSetting { - val key: String? - val section: String? +interface AbstractSetting { + val key: String + val section: String + val defaultValue: T val isRuntimeEditable: Boolean - val valueAsString: String - val defaultValue: Any + + fun valueToString(value: T): String = value.toString() + + fun valueFromString(string: String): T? } diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/AbstractShortSetting.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/AbstractShortSetting.kt deleted file mode 100644 index 9fafc5410..000000000 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/AbstractShortSetting.kt +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright 2023 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -package org.citra.citra_emu.features.settings.model - -interface AbstractShortSetting : AbstractSetting { - var short: Short -} diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/AbstractStringSetting.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/AbstractStringSetting.kt deleted file mode 100644 index 41ecc5038..000000000 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/AbstractStringSetting.kt +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright 2023 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -package org.citra.citra_emu.features.settings.model - -interface AbstractStringSetting : AbstractSetting { - var string: String -} diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/BooleanSetting.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/BooleanSetting.kt index e12d87544..5c075bc3f 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/BooleanSetting.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/BooleanSetting.kt @@ -10,7 +10,7 @@ enum class BooleanSetting( override val key: String, override val section: String, override val defaultValue: Boolean -) : AbstractBooleanSetting { +) : AbstractSetting { EXPAND_TO_CUTOUT_AREA(SettingKeys.expand_to_cutout_area(), Settings.SECTION_LAYOUT, false), SPIRV_SHADER_GEN(SettingKeys.spirv_shader_gen(), Settings.SECTION_RENDERER, true), ASYNC_SHADERS(SettingKeys.async_shader_compilation(), Settings.SECTION_RENDERER, false), @@ -58,10 +58,13 @@ enum class BooleanSetting( APPLY_REGION_FREE_PATCH(SettingKeys.apply_region_free_patch(), Settings.SECTION_SYSTEM, true), USE_INTEGER_SCALING(SettingKeys.use_integer_scaling(), Settings.SECTION_RENDERER, false); - override var boolean: Boolean = defaultValue - - override val valueAsString: String - get() = boolean.toString() + override fun valueFromString(string: String): Boolean? { + return when (string.trim().lowercase()) { + "1", "true" -> true + "0", "false" -> false + else -> null + } + } override val isRuntimeEditable: Boolean get() { @@ -98,7 +101,5 @@ enum class BooleanSetting( fun from(key: String): BooleanSetting? = BooleanSetting.values().firstOrNull { it.key == key } - - fun clear() = BooleanSetting.values().forEach { it.boolean = it.defaultValue } - } + } } diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/FloatSetting.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/FloatSetting.kt index c192677bd..c221a28c4 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/FloatSetting.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/FloatSetting.kt @@ -9,18 +9,25 @@ import org.citra.citra_emu.features.settings.SettingKeys enum class FloatSetting( override val key: String, override val section: String, - override val defaultValue: Float -) : AbstractFloatSetting { + override val defaultValue: Float, + val scale:Int = 1 +) : AbstractSetting { LARGE_SCREEN_PROPORTION(SettingKeys.large_screen_proportion(),Settings.SECTION_LAYOUT,2.25f), SECOND_SCREEN_OPACITY(SettingKeys.custom_second_layer_opacity(), Settings.SECTION_RENDERER, 100f), - BACKGROUND_RED(SettingKeys.bg_red(), Settings.SECTION_RENDERER, 0f), - BACKGROUND_BLUE(SettingKeys.bg_blue(), Settings.SECTION_RENDERER, 0f), - BACKGROUND_GREEN(SettingKeys.bg_green(), Settings.SECTION_RENDERER, 0f); + BACKGROUND_RED(SettingKeys.bg_red(), Settings.SECTION_RENDERER, 0f, 255), + BACKGROUND_BLUE(SettingKeys.bg_blue(), Settings.SECTION_RENDERER, 0f, 255), + BACKGROUND_GREEN(SettingKeys.bg_green(), Settings.SECTION_RENDERER, 0f, 255), + AUDIO_VOLUME(SettingKeys.volume(), Settings.SECTION_AUDIO, 100f, 100); - override var float: Float = defaultValue + // valueFromString reads raw setting from file, scales up for UI + override fun valueFromString(string: String): Float? { + return string.toFloatOrNull()?.times(scale) + } - override val valueAsString: String - get() = float.toString() + // valueToString scales back down to raw for file + override fun valueToString(value: Float): String { + return (value / scale).toString() + } override val isRuntimeEditable: Boolean get() { @@ -36,7 +43,5 @@ enum class FloatSetting( private val NOT_RUNTIME_EDITABLE = emptyList() fun from(key: String): FloatSetting? = FloatSetting.values().firstOrNull { it.key == key } - - fun clear() = FloatSetting.values().forEach { it.float = it.defaultValue } } } diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/IntListSetting.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/IntListSetting.kt index 0de51acce..fbffc735c 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/IntListSetting.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/IntListSetting.kt @@ -9,28 +9,17 @@ enum class IntListSetting( override val section: String, override val defaultValue: List, val canBeEmpty: Boolean = true -) : AbstractListSetting { +) : AbstractSetting> { LAYOUTS_TO_CYCLE("layouts_to_cycle", Settings.SECTION_LAYOUT, listOf(0, 1, 2, 3, 4, 5), canBeEmpty = false); - private var backingList: List = defaultValue - private var lastValidList : List = defaultValue - - override var list: List - get() = backingList - set(value) { - if (!canBeEmpty && value.isEmpty()) { - backingList = lastValidList - } else { - backingList = value - lastValidList = value - } - } - - override val valueAsString: String - get() = list.joinToString() - + override fun valueToString(value: List): String = value.joinToString() + override fun valueFromString(string: String): List? { + return string.split(",") + .mapNotNull { it.trim().toIntOrNull() } + .takeIf { canBeEmpty || it.isNotEmpty() } + } override val isRuntimeEditable: Boolean get() { for (setting in NOT_RUNTIME_EDITABLE) { @@ -46,7 +35,5 @@ enum class IntListSetting( fun from(key: String): IntListSetting? = values().firstOrNull { it.key == key } - - fun clear() = values().forEach { it.list = it.defaultValue } } } diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/IntSetting.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/IntSetting.kt index 2c8cbf2a1..364a3d1db 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/IntSetting.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/IntSetting.kt @@ -10,7 +10,7 @@ enum class IntSetting( override val key: String, override val section: String, override val defaultValue: Int -) : AbstractIntSetting { +) : AbstractSetting { FRAME_LIMIT(SettingKeys.frame_limit(), Settings.SECTION_RENDERER, 100), EMULATED_REGION(SettingKeys.region_value(), Settings.SECTION_SYSTEM, -1), INIT_CLOCK(SettingKeys.init_clock(), Settings.SECTION_SYSTEM, 0), @@ -50,7 +50,6 @@ enum class IntSetting( CPU_CLOCK_SPEED(SettingKeys.cpu_clock_percentage(), Settings.SECTION_CORE, 100), TEXTURE_FILTER(SettingKeys.texture_filter(), Settings.SECTION_RENDERER, 0), TEXTURE_SAMPLING(SettingKeys.texture_sampling(), Settings.SECTION_RENDERER, 0), - USE_FRAME_LIMIT(SettingKeys.use_frame_limit(), Settings.SECTION_RENDERER, 1), DELAY_RENDER_THREAD_US(SettingKeys.delay_game_render_thread_us(), Settings.SECTION_RENDERER, 0), ORIENTATION_OPTION(SettingKeys.screen_orientation(), Settings.SECTION_LAYOUT, 2), TURBO_LIMIT(SettingKeys.turbo_limit(), Settings.SECTION_CORE, 200), @@ -58,10 +57,15 @@ enum class IntSetting( RENDER_3D_WHICH_DISPLAY(SettingKeys.render_3d_which_display(),Settings.SECTION_RENDERER,0), ASPECT_RATIO(SettingKeys.aspect_ratio(), Settings.SECTION_LAYOUT, 0); - override var int: Int = defaultValue - override val valueAsString: String - get() = int.toString() + override fun valueFromString(string: String): Int? { + return string.toIntOrNull() ?: when (string.trim().lowercase()) { + "true" -> 1 + "false" -> 0 + else -> null + } + } + override val isRuntimeEditable: Boolean get() { @@ -83,6 +87,5 @@ enum class IntSetting( fun from(key: String): IntSetting? = IntSetting.values().firstOrNull { it.key == key } - fun clear() = IntSetting.values().forEach { it.int = it.defaultValue } } -} +} \ No newline at end of file diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/ScaledFloatSetting.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/ScaledFloatSetting.kt deleted file mode 100644 index a37452f58..000000000 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/ScaledFloatSetting.kt +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright Citra Emulator Project / Azahar Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -package org.citra.citra_emu.features.settings.model - -import org.citra.citra_emu.features.settings.SettingKeys - -enum class ScaledFloatSetting( - override val key: String, - override val section: String, - override val defaultValue: Float, - val scale: Int -) : AbstractFloatSetting { - AUDIO_VOLUME(SettingKeys.volume(), Settings.SECTION_AUDIO, 1.0f, 100); - - override var float: Float = defaultValue - get() = field * scale - set(value) { - field = value / scale - } - - override val valueAsString: String get() = (float / scale).toString() - - override val isRuntimeEditable: Boolean - get() { - for (setting in NOT_RUNTIME_EDITABLE) { - if (setting == this) { - return false - } - } - return true - } - - companion object { - private val NOT_RUNTIME_EDITABLE = emptyList() - - fun from(key: String): ScaledFloatSetting? = - ScaledFloatSetting.values().firstOrNull { it.key == key } - - fun clear() = ScaledFloatSetting.values().forEach { it.float = it.defaultValue * it.scale } - } -} diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/SettingSection.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/SettingSection.kt deleted file mode 100644 index 02c5fa2d5..000000000 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/SettingSection.kt +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2023 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -package org.citra.citra_emu.features.settings.model - -/** - * A semantically-related group of Settings objects. These Settings are - * internally stored as a HashMap. - */ -class SettingSection(val name: String) { - val settings = HashMap() - - /** - * Convenience method; inserts a value directly into the backing HashMap. - * - * @param setting The Setting to be inserted. - */ - fun putSetting(setting: AbstractSetting) { - settings[setting.key!!] = setting - } - - /** - * Convenience method; gets a value directly from the backing HashMap. - * - * @param key Used to retrieve the Setting. - * @return A Setting object (you should probably cast this before using) - */ - fun getSetting(key: String): AbstractSetting? { - return settings[key] - } - - fun mergeSection(settingSection: SettingSection) { - for (setting in settingSection.settings.values) { - putSetting(setting) - } - } -} diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/Settings.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/Settings.kt index 547a53594..5ef881bfd 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/Settings.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/Settings.kt @@ -4,101 +4,94 @@ package org.citra.citra_emu.features.settings.model -import android.text.TextUtils -import org.citra.citra_emu.CitraApplication import org.citra.citra_emu.R -import org.citra.citra_emu.features.settings.ui.SettingsActivityView -import org.citra.citra_emu.features.settings.utils.SettingsFile -import java.util.TreeMap class Settings { - private var gameId: String? = null + private val globalValues = HashMap() + private val perGameOverrides = HashMap() - var isLoaded = false + var gameId: String? = null + + fun isPerGame(): Boolean = gameId != null && gameId != "" + + fun get(setting: AbstractSetting): T { + @Suppress("UNCHECKED_CAST") + return (perGameOverrides[setting.key] + ?: globalValues[setting.key] + ?: setting.defaultValue) as T + } + + fun getGlobal(setting: AbstractSetting): T { + @Suppress("UNCHECKED_CAST") + return (globalValues[setting.key] ?: setting.defaultValue) as T + } + + fun setGlobal(setting: AbstractSetting, value: T) { + globalValues[setting.key] = value as Any + } + + fun setOverride(setting: AbstractSetting, value: T) { + perGameOverrides[setting.key] = value as Any + } + + /** Sets the per-game or global setting based on whether this file has ANY per-game setting. + * This should be used, for example, by the Settings Activity + */ + fun set(setting: AbstractSetting, value: T) { + if (isPerGame()) setOverride(setting, value) else setGlobal(setting, value) + } /** - * A HashMap, SettingSection> that constructs a new SettingSection instead of returning null - * when getting a key not already in the map + * Updates an existing setting honoring whether it is *currently* global or local. This will + * be used by the Quick Menu */ - class SettingsSectionMap : HashMap() { - override operator fun get(key: String): SettingSection? { - if (!super.containsKey(key)) { - val section = SettingSection(key) - super.put(key, section) - return section - } - return super.get(key) + fun update(setting: AbstractSetting, value: T) { + if (hasOverride(setting)) setOverride(setting, value) else setGlobal(setting, value) + } + + /** Merge the globals from other into the current settings. Merge per-game if game id is the same. */ + fun mergeSettings(other: Settings) { + other.globalValues.forEach{ (key, value) -> + globalValues[key] = value + } + + if (gameId != other.gameId) return + + perGameOverrides.clear() + other.perGameOverrides.forEach{ (key, value) -> + perGameOverrides[key] = value } } - var sections: HashMap = SettingsSectionMap() - - fun getSection(sectionName: String): SettingSection? { - return sections[sectionName] + fun clearOverride(setting: AbstractSetting) { + perGameOverrides.remove(setting.key) } - val isEmpty: Boolean - get() = sections.isEmpty() - - fun loadSettings(view: SettingsActivityView? = null) { - sections = SettingsSectionMap() - loadCitraSettings(view) - if (!TextUtils.isEmpty(gameId)) { - loadCustomGameSettings(gameId!!, view) - } - isLoaded = true + fun hasOverride(setting: AbstractSetting<*>): Boolean { + return perGameOverrides.containsKey(setting.key) } - private fun loadCitraSettings(view: SettingsActivityView?) { - for ((fileName) in configFileSectionsMap) { - sections.putAll(SettingsFile.readFile(fileName, view)) - } + fun getAllOverrides(): Map = perGameOverrides.toMap() + + fun getAllGlobal(): Map = globalValues.toMap() + + fun clearAll() { + globalValues.clear() + perGameOverrides.clear() } - private fun loadCustomGameSettings(gameId: String, view: SettingsActivityView?) { - // Custom game settings - mergeSections(SettingsFile.readCustomGameSettings(gameId, view)) + fun clearOverrides() { + perGameOverrides.clear() } - private fun mergeSections(updatedSections: HashMap) { - for ((key, updatedSection) in updatedSections) { - if (sections.containsKey(key)) { - val originalSection = sections[key] - originalSection!!.mergeSection(updatedSection!!) - } else { - sections[key] = updatedSection - } - } + fun removePerGameSettings() { + clearOverrides() + gameId = null } - fun loadSettings(gameId: String, view: SettingsActivityView) { - this.gameId = gameId - loadSettings(view) - } - - fun saveSettings(view: SettingsActivityView) { - if (TextUtils.isEmpty(gameId)) { - view.showToastMessage( - CitraApplication.appContext.getString(R.string.ini_saved), - false - ) - for ((fileName, sectionNames) in configFileSectionsMap.entries) { - val iniSections = TreeMap() - for (section in sectionNames) { - iniSections[section] = sections[section] - } - SettingsFile.saveFile(fileName, iniSections, view) - } - } else { - // TODO: Implement per game settings - } - } - - fun saveSetting(setting: AbstractSetting, filename: String) { - SettingsFile.saveFile(filename, setting) - } companion object { + const val SECTION_CORE = "Core" const val SECTION_SYSTEM = "System" const val SECTION_CAMERA = "Camera" @@ -234,20 +227,7 @@ class Settings { private val configFileSectionsMap: MutableMap> = HashMap() - init { - configFileSectionsMap[SettingsFile.FILE_NAME_CONFIG] = - listOf( - SECTION_CORE, - SECTION_SYSTEM, - SECTION_CAMERA, - SECTION_CONTROLS, - SECTION_RENDERER, - SECTION_LAYOUT, - SECTION_STORAGE, - SECTION_UTILITY, - SECTION_AUDIO, - SECTION_DEBUG - ) - } + /** Stores the settings as a singleton available everywhere.*/ + val settings = Settings() } } \ No newline at end of file diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/SettingsViewModel.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/SettingsViewModel.kt index 3f9b4ad1f..166c74d16 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/SettingsViewModel.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/SettingsViewModel.kt @@ -1,4 +1,4 @@ -// Copyright 2023 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -7,5 +7,7 @@ package org.citra.citra_emu.features.settings.model import androidx.lifecycle.ViewModel class SettingsViewModel : ViewModel() { + // the Settings Activity will always work with a local copy of settings while + // editing, to avoid issues with conflicting active/edited settings val settings = Settings() } diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/StringSetting.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/StringSetting.kt index fe476a1fa..100cbd7ae 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/StringSetting.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/StringSetting.kt @@ -10,7 +10,7 @@ enum class StringSetting( override val key: String, override val section: String, override val defaultValue: String -) : AbstractStringSetting { +) : AbstractSetting{ INIT_TIME(SettingKeys.init_time(), Settings.SECTION_SYSTEM, "946731601"), CAMERA_INNER_NAME(SettingKeys.camera_inner_name(), Settings.SECTION_CAMERA, "ndk"), CAMERA_INNER_CONFIG(SettingKeys.camera_inner_config(), Settings.SECTION_CAMERA, "_front"), @@ -19,10 +19,7 @@ enum class StringSetting( CAMERA_OUTER_RIGHT_NAME(SettingKeys.camera_outer_right_name(), Settings.SECTION_CAMERA, "ndk"), CAMERA_OUTER_RIGHT_CONFIG(SettingKeys.camera_outer_right_config(), Settings.SECTION_CAMERA, "_back"); - override var string: String = defaultValue - - override val valueAsString: String - get() = string + override fun valueFromString(string: String) = string override val isRuntimeEditable: Boolean get() { @@ -47,6 +44,5 @@ enum class StringSetting( fun from(key: String): StringSetting? = StringSetting.values().firstOrNull { it.key == key } - fun clear() = StringSetting.values().forEach { it.string = it.defaultValue } } } diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/DateTimeSetting.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/DateTimeSetting.kt index f1752614e..90a9af41f 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/DateTimeSetting.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/DateTimeSetting.kt @@ -1,33 +1,41 @@ -// Copyright 2023 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. package org.citra.citra_emu.features.settings.model.view import org.citra.citra_emu.features.settings.model.AbstractSetting -import org.citra.citra_emu.features.settings.model.AbstractStringSetting +import org.citra.citra_emu.features.settings.model.Settings class DateTimeSetting( - setting: AbstractSetting?, + private val settings: Settings, + setting: AbstractSetting?, titleId: Int, descriptionId: Int, val key: String? = null, private val defaultValue: String? = null, - override var isEnabled: Boolean = true + override var isEnabled: Boolean = true, + private val getValue: (()->String)? = null, + private val setValue: ((String)-> Unit)? = null ) : SettingsItem(setting, titleId, descriptionId) { override val type = TYPE_DATETIME_SETTING + @Suppress("UNCHECKED_CAST") val value: String - get() = if (setting != null) { - val setting = setting as AbstractStringSetting - setting.string - } else { - defaultValue!! - } + get() = getValue?.invoke() + ?: if (setting != null) { + settings.get(setting as AbstractSetting) + } else { + defaultValue!! + } - fun setSelectedValue(datetime: String): AbstractStringSetting { - val stringSetting = setting as AbstractStringSetting - stringSetting.string = datetime - return stringSetting + @Suppress("UNCHECKED_CAST") + fun setSelectedValue(datetime: String) { + if (setValue != null) { + setValue(datetime) + }else { + val stringSetting = setting as AbstractSetting + settings.set(stringSetting, datetime) + } } } diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/HeaderSetting.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/HeaderSetting.kt index d2a50f648..a08f26d99 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/HeaderSetting.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/HeaderSetting.kt @@ -1,4 +1,4 @@ -// Copyright 2023 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/InputBindingSetting.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/InputBindingSetting.kt index 6ec851db1..aa67dd15b 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/InputBindingSetting.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/InputBindingSetting.kt @@ -17,11 +17,10 @@ import org.citra.citra_emu.NativeLibrary import org.citra.citra_emu.R import org.citra.citra_emu.features.hotkeys.Hotkey import org.citra.citra_emu.features.settings.model.AbstractSetting -import org.citra.citra_emu.features.settings.model.AbstractStringSetting import org.citra.citra_emu.features.settings.model.Settings class InputBindingSetting( - val abstractSetting: AbstractSetting, + val abstractSetting: AbstractSetting<*>, titleId: Int ) : SettingsItem(abstractSetting, titleId, 0) { private val context: Context get() = CitraApplication.appContext @@ -161,11 +160,12 @@ class InputBindingSetting( /** * Removes the old mapping for this key from the settings, e.g. on user clearing the setting. */ + @Suppress("UNCHECKED_CAST") fun removeOldMapping() { // Try remove all possible keys we wrote for this setting val oldKey = preferences.getString(reverseKey, "") if (oldKey != "") { - (setting as AbstractStringSetting).string = "" + //settings.set(setting as AbstractSetting,"") preferences.edit() .remove(abstractSetting.key) // Used for ui text .remove(oldKey + "_GuestOrientation") // Used for axis orientation diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/MultiChoiceSetting.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/MultiChoiceSetting.kt index d097696e0..1a4b95c93 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/MultiChoiceSetting.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/MultiChoiceSetting.kt @@ -5,26 +5,34 @@ package org.citra.citra_emu.features.settings.model.view import org.citra.citra_emu.features.settings.model.AbstractSetting import org.citra.citra_emu.features.settings.model.IntListSetting +import org.citra.citra_emu.features.settings.model.Settings + class MultiChoiceSetting( - setting: AbstractSetting?, + val settings: Settings, + setting: AbstractSetting>?, titleId: Int, descriptionId: Int, val choicesId: Int, val valuesId: Int, val key: String? = null, val defaultValue: List? = null, - override var isEnabled: Boolean = true + override var isEnabled: Boolean = true, + private val getValue: (()->List)? = null, + private val setValue: ((List)-> Unit)? = null ) : SettingsItem(setting, titleId, descriptionId) { override val type = TYPE_MULTI_CHOICE val selectedValues: List get() { + if (getValue != null) { + @Suppress("UNCHECKED_CAST") + return getValue.invoke() + } if (setting == null) { return defaultValue!! } try { - val setting = setting as IntListSetting - return setting.list + return settings.get(setting as IntListSetting) }catch (_: ClassCastException) { } return defaultValue!! @@ -35,12 +43,14 @@ class MultiChoiceSetting( * initializes a new one and returns it, so it can be added to the Hashmap. * * @param selection New value of the int. - * @return the existing setting with the new value applied. */ - fun setSelectedValue(selection: List): IntListSetting { - val intSetting = setting as IntListSetting - intSetting.list = selection - return intSetting + fun setSelectedValue(selection: List) { + if (setValue != null) { + setValue(selection) + }else { + val intSetting = setting as IntListSetting + settings.set(intSetting, selection) + } } } diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/SettingsItem.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/SettingsItem.kt index 066912dd9..6028a67fe 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/SettingsItem.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/SettingsItem.kt @@ -16,7 +16,7 @@ import org.citra.citra_emu.features.settings.model.AbstractSetting * file.) */ abstract class SettingsItem( - var setting: AbstractSetting?, + var setting: AbstractSetting<*>?, val nameId: Int, val descriptionId: Int ) { @@ -35,6 +35,8 @@ abstract class SettingsItem( return this.isEditable && this.isEnabled } + + companion object { const val TYPE_HEADER = 0 const val TYPE_SWITCH = 1 diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/SingleChoiceSetting.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/SingleChoiceSetting.kt index 9ba2fe8c5..708b4e1e0 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/SingleChoiceSetting.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/SingleChoiceSetting.kt @@ -1,62 +1,48 @@ -// Copyright 2023 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. package org.citra.citra_emu.features.settings.model.view -import org.citra.citra_emu.features.settings.model.AbstractIntSetting import org.citra.citra_emu.features.settings.model.AbstractSetting -import org.citra.citra_emu.features.settings.model.AbstractShortSetting +import org.citra.citra_emu.features.settings.model.Settings class SingleChoiceSetting( - setting: AbstractSetting?, + val settings: Settings, + setting: AbstractSetting<*>?, titleId: Int, descriptionId: Int, val choicesId: Int, val valuesId: Int, val key: String? = null, val defaultValue: Int? = null, - override var isEnabled: Boolean = true + override var isEnabled: Boolean = true, + private val getValue: (()->Int)? = null, + private val setValue: ((Int)-> Unit)? = null ) : SettingsItem(setting, titleId, descriptionId) { override val type = TYPE_SINGLE_CHOICE val selectedValue: Int get() { - if (setting == null) { - return defaultValue!! + if (getValue != null) { + return getValue.invoke() } - - try { - val setting = setting as AbstractIntSetting - return setting.int - } catch (_: ClassCastException) { - } - - try { - val setting = setting as AbstractShortSetting - return setting.short.toInt() - } catch (_: ClassCastException) { - } - - return defaultValue!! + @Suppress("UNCHECKED_CAST") + val s = (setting as? AbstractSetting) ?: return defaultValue!! + return settings.get(s) } /** - * Write a value to the backing int. If that int was previously null, - * initializes a new one and returns it, so it can be added to the Hashmap. - * + * Write a value to the backing int . * @param selection New value of the int. - * @return the existing setting with the new value applied. */ - fun setSelectedValue(selection: Int): AbstractIntSetting { - val intSetting = setting as AbstractIntSetting - intSetting.int = selection - return intSetting - } - - fun setSelectedValue(selection: Short): AbstractShortSetting { - val shortSetting = setting as AbstractShortSetting - shortSetting.short = selection - return shortSetting + fun setSelectedValue(selection: Int) { + if (setValue != null) { + setValue(selection) + }else { + @Suppress("UNCHECKED_CAST") + val backSetting = setting as AbstractSetting + settings.set(backSetting, selection) + } } } diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/SliderSetting.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/SliderSetting.kt index 46ed42905..cbb653823 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/SliderSetting.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/SliderSetting.kt @@ -4,15 +4,16 @@ package org.citra.citra_emu.features.settings.model.view -import org.citra.citra_emu.features.settings.model.AbstractFloatSetting -import org.citra.citra_emu.features.settings.model.AbstractIntSetting import org.citra.citra_emu.features.settings.model.AbstractSetting import org.citra.citra_emu.features.settings.model.FloatSetting -import org.citra.citra_emu.features.settings.model.ScaledFloatSetting +import org.citra.citra_emu.features.settings.model.Settings import org.citra.citra_emu.utils.Log +import kotlin.math.pow +import kotlin.math.roundToInt class SliderSetting( - setting: AbstractSetting?, + val settings: Settings, + setting: AbstractSetting<*>?, titleId: Int, descriptionId: Int, val min: Int, @@ -20,17 +21,28 @@ class SliderSetting( val units: String, val key: String? = null, val defaultValue: Float? = null, - override var isEnabled: Boolean = true + val rounding: Int = 2, + override var isEnabled: Boolean = true, + private val getValue: (()->Float)? = null, + private val setValue: ((Float)-> Unit)? = null ) : SettingsItem(setting, titleId, descriptionId) { override val type = TYPE_SLIDER val selectedFloat: Float get() { - val setting = setting ?: return defaultValue!!.toFloat() + if (getValue != null) return getValue.invoke() + val s = setting ?: return defaultValue!! + + val ret = when (s.defaultValue) { + is Int -> { + @Suppress("UNCHECKED_CAST") + settings.get(s as AbstractSetting).toFloat() + } + + is Float -> { + @Suppress("UNCHECKED_CAST") + settings.get(s as AbstractSetting) + } - val ret = when (setting) { - is AbstractIntSetting -> setting.int.toFloat() - is FloatSetting -> setting.float - is ScaledFloatSetting -> setting.float else -> { Log.error("[SliderSetting] Error casting setting type.") -1f @@ -38,16 +50,32 @@ class SliderSetting( } return ret.coerceIn(min.toFloat(), max.toFloat()) } - /** - * Write a value to the backing int. If that int was previously null, - * initializes a new one and returns it, so it can be added to the Hashmap. - * - * @param selection New value of the int. - * @return the existing setting with the new value applied. - */ - fun setSelectedValue(selection: Int): AbstractIntSetting { - val intSetting = setting as AbstractIntSetting - intSetting.int = selection + fun roundedFloat(value: Float): Float { + val factor = 10f.pow(rounding) + return (value * factor).roundToInt() / factor + } + + val valueAsString: String + get() = setting?.let { + when (it.defaultValue) { + is Int -> { + @Suppress("UNCHECKED_CAST") + settings.get(it as AbstractSetting).toString() + } + + is Float -> { + @Suppress("UNCHECKED_CAST") + roundedFloat(settings.get(it as AbstractSetting)).toString() + } + + else -> "" + } + } ?: defaultValue?.toString() ?: "" + + fun setSelectedValue(selection: Int): AbstractSetting { + @Suppress("UNCHECKED_CAST") + val intSetting = setting as AbstractSetting + settings.set(intSetting, selection) return intSetting } @@ -56,15 +84,14 @@ class SliderSetting( * initializes a new one and returns it, so it can be added to the Hashmap. * * @param selection New value of the float. - * @return the existing setting with the new value applied. */ - fun setSelectedValue(selection: Float): AbstractFloatSetting { - val floatSetting = setting as AbstractFloatSetting - if (floatSetting is ScaledFloatSetting) { - floatSetting.float = selection - } else { - floatSetting.float = selection + fun setSelectedValue(selection: Float) { + if (setValue != null) { + setValue(selection) + }else { + @Suppress("UNCHECKED_CAST") + val floatSetting = setting as AbstractSetting + settings.set(floatSetting, selection) } - return floatSetting } } diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/StringInputSetting.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/StringInputSetting.kt index f32c078a6..611851967 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/StringInputSetting.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/StringInputSetting.kt @@ -1,28 +1,40 @@ -// Copyright 2023 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. package org.citra.citra_emu.features.settings.model.view import org.citra.citra_emu.features.settings.model.AbstractSetting -import org.citra.citra_emu.features.settings.model.AbstractStringSetting +import org.citra.citra_emu.features.settings.model.Settings class StringInputSetting( - setting: AbstractSetting?, + val settings: Settings, + setting: AbstractSetting?, titleId: Int, descriptionId: Int, val defaultValue: String, val characterLimit: Int = 0, - override var isEnabled: Boolean = true + override var isEnabled: Boolean = true, + private val getValue: (()->String)? = null, + private val setValue: ((String)-> Unit)? = null ) : SettingsItem(setting, titleId, descriptionId) { override val type = TYPE_STRING_INPUT val selectedValue: String - get() = setting?.valueAsString ?: defaultValue + @Suppress("UNCHECKED_CAST") + get() { + if (getValue != null) return getValue.invoke() + setting ?: return defaultValue + return settings.get(setting as AbstractSetting) + } - fun setSelectedValue(selection: String): AbstractStringSetting { - val stringSetting = setting as AbstractStringSetting - stringSetting.string = selection - return stringSetting + fun setSelectedValue(selection: String) { + if (setValue != null) { + setValue.invoke(selection) + }else { + @Suppress("UNCHECKED_CAST") + val stringSetting = setting as AbstractSetting + settings.set(stringSetting, selection) + } } } diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/StringSingleChoiceSetting.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/StringSingleChoiceSetting.kt index 037a26ffc..f74140fd4 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/StringSingleChoiceSetting.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/StringSingleChoiceSetting.kt @@ -1,22 +1,24 @@ -// Copyright 2023 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. package org.citra.citra_emu.features.settings.model.view import org.citra.citra_emu.features.settings.model.AbstractSetting -import org.citra.citra_emu.features.settings.model.AbstractShortSetting -import org.citra.citra_emu.features.settings.model.AbstractStringSetting +import org.citra.citra_emu.features.settings.model.Settings class StringSingleChoiceSetting( - setting: AbstractSetting?, + val settings: Settings, + setting: AbstractSetting?, titleId: Int, descriptionId: Int, val choices: Array, val values: Array?, val key: String? = null, private val defaultValue: String? = null, - override var isEnabled: Boolean = true + override var isEnabled: Boolean = true, + private val getValue: (()->String)? = null, + private val setValue: ((String)-> Unit)? = null ) : SettingsItem(setting, titleId, descriptionId) { override val type = TYPE_STRING_SINGLE_CHOICE @@ -31,22 +33,12 @@ class StringSingleChoiceSetting( val selectedValue: String get() { + if (getValue != null) return getValue.invoke() if (setting == null) { return defaultValue!! } - - try { - val setting = setting as AbstractStringSetting - return setting.string - } catch (_: ClassCastException) { - } - - try { - val setting = setting as AbstractShortSetting - return setting.short.toString() - } catch (_: ClassCastException) { - } - return defaultValue!! + @Suppress("UNCHECKED_CAST") + return settings.get(setting as AbstractSetting) } val selectValueIndex: Int get() { @@ -64,17 +56,14 @@ class StringSingleChoiceSetting( * initializes a new one and returns it, so it can be added to the Hashmap. * * @param selection New value of the int. - * @return the existing setting with the new value applied. */ - fun setSelectedValue(selection: String): AbstractStringSetting { - val stringSetting = setting as AbstractStringSetting - stringSetting.string = selection - return stringSetting - } - - fun setSelectedValue(selection: Short): AbstractShortSetting { - val shortSetting = setting as AbstractShortSetting - shortSetting.short = selection - return shortSetting + fun setSelectedValue(selection: String) { + if (setValue != null) { + setValue(selection) + }else { + @Suppress("UNCHECKED_CAST") + val stringSetting = setting as AbstractSetting + settings.set(stringSetting, selection) + } } } diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/SwitchSetting.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/SwitchSetting.kt index b8badfd06..1e63f0a97 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/SwitchSetting.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/SwitchSetting.kt @@ -4,38 +4,45 @@ package org.citra.citra_emu.features.settings.model.view -import org.citra.citra_emu.features.settings.model.AbstractBooleanSetting -import org.citra.citra_emu.features.settings.model.AbstractIntSetting import org.citra.citra_emu.features.settings.model.AbstractSetting +import org.citra.citra_emu.features.settings.model.Settings class SwitchSetting( - setting: AbstractBooleanSetting, + val settings: Settings, + setting: AbstractSetting?, titleId: Int, descriptionId: Int, val key: String? = null, val defaultValue: Boolean = false, - override var isEnabled: Boolean = true + override var isEnabled: Boolean = true, + private val getValue: (() -> Boolean)? = null, + private val setValue: ((Boolean) -> Unit)? = null ) : SettingsItem(setting, titleId, descriptionId) { override val type = TYPE_SWITCH val isChecked: Boolean get() { + if (getValue != null) return getValue.invoke() if (setting == null) { return defaultValue } - val setting = setting as AbstractBooleanSetting - return setting.boolean + @Suppress("UNCHECKED_CAST") + val setting = setting as AbstractSetting + return settings.get(setting) } /** * Write a value to the backing boolean. * * @param checked Pretty self explanatory. - * @return the existing setting with the new value applied. */ - fun setChecked(checked: Boolean): AbstractBooleanSetting { - val setting = setting as AbstractBooleanSetting - setting.boolean = checked - return setting + fun setChecked(checked: Boolean) { + if (setValue != null) { + setValue(checked) + }else { + @Suppress("UNCHECKED_CAST") + val setting = setting as AbstractSetting + settings.set(setting, checked) + } } } diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsActivity.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsActivity.kt index 064fa700e..7366406df 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsActivity.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsActivity.kt @@ -7,7 +7,6 @@ package org.citra.citra_emu.features.settings.ui import android.content.Context import android.content.Intent import android.net.Uri -import android.os.Build import android.os.Bundle import android.view.View import android.view.ViewGroup.MarginLayoutParams @@ -27,13 +26,8 @@ import org.citra.citra_emu.NativeLibrary import org.citra.citra_emu.R import org.citra.citra_emu.databinding.ActivitySettingsBinding import java.io.IOException -import org.citra.citra_emu.features.settings.model.BooleanSetting -import org.citra.citra_emu.features.settings.model.FloatSetting -import org.citra.citra_emu.features.settings.model.IntSetting -import org.citra.citra_emu.features.settings.model.ScaledFloatSetting import org.citra.citra_emu.features.settings.model.Settings import org.citra.citra_emu.features.settings.model.SettingsViewModel -import org.citra.citra_emu.features.settings.model.StringSetting import org.citra.citra_emu.features.settings.utils.SettingsFile import org.citra.citra_emu.utils.SystemSaveGame import org.citra.citra_emu.utils.DirectoryInitialization @@ -42,12 +36,13 @@ import org.citra.citra_emu.utils.RefreshRateUtil import org.citra.citra_emu.utils.ThemeUtil class SettingsActivity : AppCompatActivity(), SettingsActivityView { - private val presenter = SettingsActivityPresenter(this) private lateinit var binding: ActivitySettingsBinding + val settingsViewModel: SettingsViewModel by viewModels() - private val settingsViewModel: SettingsViewModel by viewModels() + private val presenter by lazy { SettingsActivityPresenter(this, settingsViewModel) } + // the activity will work with the fresh Settings() object created and stored in the viewmodel override val settings: Settings get() = settingsViewModel.settings override fun onCreate(savedInstanceState: Bundle?) { @@ -63,9 +58,9 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView { WindowCompat.setDecorFitsSystemWindows(window, false) val launcher = intent - val gameID = launcher.getStringExtra(ARG_GAME_ID) - val menuTag = launcher.getStringExtra(ARG_MENU_TAG) - presenter.onCreate(savedInstanceState, menuTag!!, gameID!!) + val gameID = launcher.getStringExtra(ARG_GAME_ID) ?: "" + val menuTag = launcher.getStringExtra(ARG_MENU_TAG) ?: "" + presenter.onCreate(savedInstanceState, menuTag, gameID) // Show "Back" button in the action bar for navigation setSupportActionBar(binding.toolbarSettings) @@ -211,13 +206,6 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView { controllerKeys.forEach { editor.remove(it) } editor.apply() - // Reset the static memory representation of each setting - BooleanSetting.clear() - FloatSetting.clear() - ScaledFloatSetting.clear() - IntSetting.clear() - StringSetting.clear() - // Delete settings file because the user may have changed values that do not exist in the UI val settingsFile = SettingsFile.getSettingsFile(SettingsFile.FILE_NAME_CONFIG) if (!settingsFile.delete()) { diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsActivityPresenter.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsActivityPresenter.kt index 33aea46f9..1c2cbf144 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsActivityPresenter.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsActivityPresenter.kt @@ -12,6 +12,8 @@ import org.citra.citra_emu.CitraApplication import org.citra.citra_emu.NativeLibrary import org.citra.citra_emu.features.settings.model.BooleanSetting import org.citra.citra_emu.features.settings.model.Settings +import org.citra.citra_emu.features.settings.model.SettingsViewModel +import org.citra.citra_emu.features.settings.utils.SettingsFile import org.citra.citra_emu.utils.SystemSaveGame import org.citra.citra_emu.utils.DirectoryInitialization import org.citra.citra_emu.utils.FileUtil @@ -19,8 +21,8 @@ import org.citra.citra_emu.utils.Log import org.citra.citra_emu.utils.PermissionsHandler import org.citra.citra_emu.utils.TurboHelper -class SettingsActivityPresenter(private val activityView: SettingsActivityView) { - val settings: Settings get() = activityView.settings +class SettingsActivityPresenter(private val activityView: SettingsActivityView, private val viewModel: SettingsViewModel) { + val settings: Settings get() = viewModel.settings private var shouldSave = false private lateinit var menuTag: String @@ -29,6 +31,9 @@ class SettingsActivityPresenter(private val activityView: SettingsActivityView) fun onCreate(savedInstanceState: Bundle?, menuTag: String, gameId: String) { this.menuTag = menuTag this.gameId = gameId + // merge the active settings into the local settings activity instance + settings.mergeSettings(Settings.settings) + if (savedInstanceState != null) { shouldSave = savedInstanceState.getBoolean(KEY_SHOULD_SAVE) } @@ -47,13 +52,6 @@ class SettingsActivityPresenter(private val activityView: SettingsActivityView) } private fun loadSettingsUI() { - if (!settings.isLoaded) { - if (!TextUtils.isEmpty(gameId)) { - settings.loadSettings(gameId, activityView) - } else { - settings.loadSettings(activityView) - } - } activityView.showSettingsFragment(menuTag, false, gameId) activityView.onSettingsFileLoaded() } @@ -72,7 +70,8 @@ class SettingsActivityPresenter(private val activityView: SettingsActivityView) val nomediaFileExists: Boolean try { dataDirTreeUri = PermissionsHandler.citraDirectory - dataDirDocument = DocumentFile.fromTreeUri(CitraApplication.appContext, dataDirTreeUri)!! + dataDirDocument = + DocumentFile.fromTreeUri(CitraApplication.appContext, dataDirTreeUri)!! nomediaFileDocument = dataDirDocument.findFile(".nomedia") nomediaFileExists = (nomediaFileDocument != null) } catch (e: Exception) { @@ -80,7 +79,7 @@ class SettingsActivityPresenter(private val activityView: SettingsActivityView) return } - if (BooleanSetting.ANDROID_HIDE_IMAGES.boolean) { + if (settings.get(BooleanSetting.ANDROID_HIDE_IMAGES)) { if (!nomediaFileExists) { Log.info("[SettingsActivity]: Attempting to create .nomedia in user data directory") FileUtil.createFile(dataDirTreeUri.toString(), ".nomedia") @@ -94,14 +93,18 @@ class SettingsActivityPresenter(private val activityView: SettingsActivityView) fun onStop(finishing: Boolean) { if (finishing && shouldSave) { Log.debug("[SettingsActivity] Settings activity stopping. Saving settings to INI...") - settings.saveSettings(activityView) - //added to ensure that layout changes take effect as soon as settings window closes + if (settings.isPerGame()) { + SettingsFile.saveCustomFile(settings,activityView) + }else{ + SettingsFile.saveGlobalFile(settings,activityView) + } + // merge the edited settings back into the active settings + Settings.settings.mergeSettings(settings) NativeLibrary.reloadSettings() NativeLibrary.updateFramebuffer(NativeLibrary.isPortraitMode) updateAndroidImageVisibility() - TurboHelper.reloadTurbo(false) // TODO: Can this go somewhere else? -OS + TurboHelper.reloadTurbo(false, settings) // TODO: Can this go somewhere else? -OS } - NativeLibrary.reloadSettings() } fun onSettingChanged() { diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsAdapter.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsAdapter.kt index 43a1dcbbd..c124cf383 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsAdapter.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsAdapter.kt @@ -35,15 +35,8 @@ import org.citra.citra_emu.databinding.DialogSoftwareKeyboardBinding import org.citra.citra_emu.databinding.ListItemSettingBinding import org.citra.citra_emu.databinding.ListItemSettingSwitchBinding import org.citra.citra_emu.databinding.ListItemSettingsHeaderBinding -import org.citra.citra_emu.features.settings.model.AbstractBooleanSetting -import org.citra.citra_emu.features.settings.model.AbstractFloatSetting -import org.citra.citra_emu.features.settings.model.AbstractIntSetting import org.citra.citra_emu.features.settings.model.AbstractSetting -import org.citra.citra_emu.features.settings.model.AbstractStringSetting import org.citra.citra_emu.features.settings.model.FloatSetting -import org.citra.citra_emu.features.settings.model.IntListSetting -import org.citra.citra_emu.features.settings.model.ScaledFloatSetting -import org.citra.citra_emu.features.settings.model.AbstractShortSetting import org.citra.citra_emu.features.settings.model.view.DateTimeSetting import org.citra.citra_emu.features.settings.model.view.InputBindingSetting import org.citra.citra_emu.features.settings.model.view.SettingsItem @@ -74,9 +67,9 @@ import java.text.SimpleDateFormat import kotlin.math.roundToInt class SettingsAdapter( - private val fragmentView: SettingsFragmentView, - public val context: Context -) : RecyclerView.Adapter(), DialogInterface.OnClickListener, + val fragmentView: SettingsFragmentView, + val context: Context +) : RecyclerView.Adapter?>(), DialogInterface.OnClickListener, DialogInterface.OnMultiChoiceClickListener { private var settings: ArrayList? = null private var clickedItem: SettingsItem? = null @@ -94,7 +87,7 @@ class SettingsAdapter( clickedPosition = -1 } - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SettingViewHolder { + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SettingViewHolder<*> { val inflater = LayoutInflater.from(parent.context) return when (viewType) { SettingsItem.TYPE_HEADER -> { @@ -144,7 +137,7 @@ class SettingsAdapter( } } - override fun onBindViewHolder(holder: SettingViewHolder, position: Int) { + override fun onBindViewHolder(holder: SettingViewHolder, position: Int) { getItem(position)?.let { holder.bind(it) } } @@ -226,8 +219,7 @@ class SettingsAdapter( } fun onBooleanClick(item: SwitchSetting, position: Int, checked: Boolean) { - val setting = item.setChecked(checked) - fragmentView.putSetting(setting) + item.setChecked(checked) fragmentView.onSettingChanged() // If statement is required otherwise the app will crash on activity recreate ex. theme settings @@ -338,8 +330,7 @@ class SettingsAdapter( fragmentView.onSettingChanged() } notifyItemChanged(clickedPosition) - val setting = item.setSelectedValue(rtcString) - fragmentView.putSetting(setting) + item.setSelectedValue(rtcString) fragmentView.loadSettingsList() clickedItem = null } @@ -359,7 +350,7 @@ class SettingsAdapter( val sliderBinding = DialogSliderBinding.inflate(inflater) textInputLayout = sliderBinding.textInput textSliderValue = sliderBinding.textValue - if (item.setting is FloatSetting) { + if (item.setting?.defaultValue is Float) { textSliderValue?.let { it.inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_DECIMAL it.setText(sliderProgress.toString()) @@ -393,9 +384,9 @@ class SettingsAdapter( }) addOnChangeListener { _: Slider, value: Float, _: Boolean -> - sliderProgress = (value * 100).roundToInt().toFloat() / 100f + sliderProgress = item.roundedFloat(value) var sliderString = sliderProgress.toString() - if (item.setting !is FloatSetting) { + if (item.setting?.defaultValue !is Float) { sliderString = sliderProgress.roundToInt().toString() if (textSliderValue?.text.toString() != sliderString) { textSliderValue?.setText(sliderString) @@ -418,16 +409,12 @@ class SettingsAdapter( .setPositiveButton(android.R.string.ok, this) .setNegativeButton(android.R.string.cancel, defaultCancelListener) .setNeutralButton(R.string.slider_default) { dialog: DialogInterface, which: Int -> - sliderBinding.slider?.value = when (item.setting) { - is ScaledFloatSetting -> { - val scaledSetting = item.setting as ScaledFloatSetting - scaledSetting.defaultValue * scaledSetting.scale - } - - is FloatSetting -> (item.setting as FloatSetting).defaultValue - else -> item.defaultValue ?: 0f + sliderBinding.slider.value = when (item.setting?.defaultValue) { + is Float -> item.setting!!.defaultValue as Float + is Int -> (item.setting!!.defaultValue as Int).toFloat() + else -> 0f } - onClick(dialog, which) + onClick(dialog, which) } .show() } @@ -478,26 +465,9 @@ class SettingsAdapter( is SingleChoiceSetting -> { val scSetting = clickedItem as? SingleChoiceSetting scSetting?.let { - val setting = when (it.setting) { - is AbstractIntSetting -> { - val value = getValueForSingleChoiceSelection(it, which) - if (it.selectedValue != value) { - fragmentView?.onSettingChanged() - } - it.setSelectedValue(value) - } - - is AbstractShortSetting -> { - val value = getValueForSingleChoiceSelection(it, which).toShort() - if (it.selectedValue.toShort() != value) { - fragmentView?.onSettingChanged() - } - it.setSelectedValue(value) - } - - else -> throw IllegalStateException("Unrecognized type used for SingleChoiceSetting!") - } - fragmentView?.putSetting(setting) + val value = getValueForSingleChoiceSelection(it, which) + if (it.selectedValue != value) fragmentView?.onSettingChanged() + it.setSelectedValue(value) fragmentView.loadSettingsList() closeDialog() } @@ -506,22 +476,9 @@ class SettingsAdapter( is StringSingleChoiceSetting -> { val scSetting = clickedItem as? StringSingleChoiceSetting scSetting?.let { - val setting = when (it.setting) { - is AbstractStringSetting -> { - val value = it.getValueAt(which) - if (it.selectedValue != value) fragmentView?.onSettingChanged() - it.setSelectedValue(value ?: "") - } - - is AbstractShortSetting -> { - if (it.selectValueIndex != which) fragmentView?.onSettingChanged() - it.setSelectedValue(it.getValueAt(which)?.toShort() ?: 1) - } - - else -> throw IllegalStateException("Unrecognized type used for StringSingleChoiceSetting!") - } - - fragmentView?.putSetting(setting) + val value = it.getValueAt(which) ?: "" + if (it.selectedValue != value) fragmentView?.onSettingChanged() + it.setSelectedValue(value) fragmentView.loadSettingsList() closeDialog() } @@ -530,21 +487,12 @@ class SettingsAdapter( is SliderSetting -> { val sliderSetting = clickedItem as? SliderSetting sliderSetting?.let { - val sliderval = (it.selectedFloat * 100).roundToInt().toFloat() / 100 - if (sliderval != sliderProgress) { - fragmentView?.onSettingChanged() - } - when (it.setting) { - is AbstractIntSetting -> { - val value = sliderProgress.roundToInt() - val setting = it.setSelectedValue(value) - fragmentView?.putSetting(setting) - } - - else -> { - val setting = it.setSelectedValue(sliderProgress) - fragmentView?.putSetting(setting) - } + val sliderval = it.roundedFloat(sliderProgress) + if (sliderval != it.selectedFloat) fragmentView.onSettingChanged() + val s = it.setting + when { + it.setting?.defaultValue is Int -> it.setSelectedValue(sliderProgress.roundToInt()) + else -> it.setSelectedValue(sliderProgress) } fragmentView.loadSettingsList() closeDialog() @@ -557,8 +505,7 @@ class SettingsAdapter( if (it.selectedValue != textInputValue) { fragmentView?.onSettingChanged() } - val setting = it.setSelectedValue(textInputValue ?: "") - fragmentView?.putSetting(setting) + it.setSelectedValue(textInputValue) fragmentView.loadSettingsList() closeDialog() } @@ -575,36 +522,19 @@ class SettingsAdapter( mcsetting?.let { val value = getValueForMultiChoiceSelection(it, which) if (it.selectedValues.contains(value) != isChecked) { - val setting = it.setSelectedValue((if (isChecked) it.selectedValues + value else it.selectedValues - value).sorted()) - fragmentView?.putSetting(setting) - fragmentView?.onSettingChanged() + it.setSelectedValue((if (isChecked) it.selectedValues + value else it.selectedValues - value).sorted()) + fragmentView.onSettingChanged() } fragmentView.loadSettingsList() } } - fun onLongClick(setting: AbstractSetting, position: Int): Boolean { + fun onLongClick(setting: AbstractSetting<*>, position: Int): Boolean { MaterialAlertDialogBuilder(context) .setMessage(R.string.reset_setting_confirmation) .setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int -> - when (setting) { - is AbstractBooleanSetting -> setting.boolean = setting.defaultValue as Boolean - is AbstractFloatSetting -> { - if (setting is ScaledFloatSetting) { - setting.float = setting.defaultValue * setting.scale - } else { - setting.float = setting.defaultValue as Float - } - } - - is AbstractIntSetting -> setting.int = setting.defaultValue as Int - is AbstractStringSetting -> setting.string = setting.defaultValue as String - is AbstractShortSetting -> setting.short = setting.defaultValue as Short - } - notifyItemChanged(position) - fragmentView.onSettingChanged() - fragmentView.loadSettingsList() + resetSettingToDefault(setting, position) } .setNegativeButton(android.R.string.cancel, null) .show() @@ -612,6 +542,13 @@ class SettingsAdapter( return true } + fun resetSettingToDefault(setting: AbstractSetting, position: Int) { + fragmentView.activityView?.settings?.set(setting,setting.defaultValue) + notifyItemChanged(position) + fragmentView.onSettingChanged() + fragmentView.loadSettingsList() + } + fun onInputBindingLongClick(setting: InputBindingSetting, position: Int): Boolean { MaterialAlertDialogBuilder(context) .setMessage(R.string.reset_setting_confirmation) diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragment.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragment.kt index 96568a76a..4521bae95 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragment.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragment.kt @@ -13,16 +13,19 @@ import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import androidx.core.view.updatePadding import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels import androidx.recyclerview.widget.LinearLayoutManager -import com.google.android.material.divider.MaterialDividerItemDecoration import org.citra.citra_emu.databinding.FragmentSettingsBinding -import org.citra.citra_emu.features.settings.model.AbstractSetting +import org.citra.citra_emu.features.settings.model.SettingsViewModel import org.citra.citra_emu.features.settings.model.view.SettingsItem +import kotlin.getValue class SettingsFragment : Fragment(), SettingsFragmentView { override var activityView: SettingsActivityView? = null - private val fragmentPresenter = SettingsFragmentPresenter(this) + private val settingsViewModel: SettingsViewModel by activityViewModels() + + private val fragmentPresenter by lazy { SettingsFragmentPresenter(this) } private var settingsAdapter: SettingsAdapter? = null private var _binding: FragmentSettingsBinding? = null @@ -37,7 +40,7 @@ class SettingsFragment : Fragment(), SettingsFragmentView { super.onCreate(savedInstanceState) val menuTag = requireArguments().getString(ARGUMENT_MENU_TAG) val gameId = requireArguments().getString(ARGUMENT_GAME_ID) - fragmentPresenter.onCreate(menuTag!!, gameId!!) + fragmentPresenter.onCreate(menuTag!!, gameId!!, settingsViewModel.settings) } override fun onCreateView( @@ -88,10 +91,6 @@ class SettingsFragment : Fragment(), SettingsFragmentView { activityView!!.showToastMessage(message!!, is_long) } - override fun putSetting(setting: AbstractSetting) { - fragmentPresenter.putSetting(setting) - } - override fun onSettingChanged() { activityView!!.onSettingChanged() } diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt index 0143ac804..aaa067af2 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt @@ -14,22 +14,16 @@ import android.os.Build import android.text.TextUtils import androidx.preference.PreferenceManager import com.google.android.material.dialog.MaterialAlertDialogBuilder -import kotlinx.serialization.builtins.IntArraySerializer import org.citra.citra_emu.CitraApplication import org.citra.citra_emu.R import org.citra.citra_emu.display.ScreenLayout import org.citra.citra_emu.display.StereoMode import org.citra.citra_emu.display.StereoWhichDisplay -import org.citra.citra_emu.features.settings.model.AbstractBooleanSetting -import org.citra.citra_emu.features.settings.model.AbstractIntSetting import org.citra.citra_emu.features.settings.model.AbstractSetting -import org.citra.citra_emu.features.settings.model.AbstractShortSetting -import org.citra.citra_emu.features.settings.model.AbstractStringSetting import org.citra.citra_emu.features.settings.model.BooleanSetting import org.citra.citra_emu.features.settings.model.FloatSetting import org.citra.citra_emu.features.settings.model.IntSetting import org.citra.citra_emu.features.settings.model.IntListSetting -import org.citra.citra_emu.features.settings.model.ScaledFloatSetting import org.citra.citra_emu.features.settings.model.Settings import org.citra.citra_emu.features.settings.model.StringSetting import org.citra.citra_emu.features.settings.model.view.DateTimeSetting @@ -50,6 +44,7 @@ import org.citra.citra_emu.utils.BirthdayMonth import org.citra.citra_emu.utils.Log import org.citra.citra_emu.utils.SystemSaveGame import org.citra.citra_emu.utils.ThemeUtil +import kotlin.math.roundToInt class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) { private var menuTag: String? = null @@ -57,14 +52,15 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) private var settingsList: ArrayList? = null private val settingsActivity get() = fragmentView.activityView as SettingsActivity - private val settings get() = fragmentView.activityView!!.settings + private lateinit var settings: Settings private lateinit var settingsAdapter: SettingsAdapter private lateinit var preferences: SharedPreferences - fun onCreate(menuTag: String, gameId: String) { + fun onCreate(menuTag: String, gameId: String, settings: Settings) { this.gameId = gameId this.menuTag = menuTag + this.settings = settings } fun onViewCreated(settingsAdapter: SettingsAdapter) { @@ -73,16 +69,6 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) loadSettingsList() } - fun putSetting(setting: AbstractSetting) { - if (setting.section == null || setting.key == null) { - return - } - - val section = settings.getSection(setting.section!!)!! - if (section.getSetting(setting.key!!) == null) { - section.putSetting(setting) - } - } fun loadSettingsList() { if (!TextUtils.isEmpty(gameId)) { @@ -226,6 +212,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) sl.apply { add( SwitchSetting( + settings, BooleanSetting.USE_FRAME_LIMIT, R.string.frame_limit_enable, R.string.frame_limit_enable_description, @@ -235,6 +222,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) ) add( SliderSetting( + settings, IntSetting.FRAME_LIMIT, R.string.frame_limit_slider, R.string.frame_limit_slider_description, @@ -247,6 +235,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) ) add( SliderSetting( + settings, IntSetting.TURBO_LIMIT, R.string.turbo_limit, R.string.turbo_limit_description, @@ -259,6 +248,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) ) add( SwitchSetting( + settings, BooleanSetting.ANDROID_HIDE_IMAGES, R.string.android_hide_images, R.string.android_hide_images_description, @@ -274,7 +264,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) private fun checkCountryCompatibility() { if (countryCompatibilityChanged) { countryCompatibilityChanged = false - val compatFlags = SystemSaveGame.getCountryCompatibility(IntSetting.EMULATED_REGION.int) + val compatFlags = SystemSaveGame.getCountryCompatibility(settings.get(IntSetting.EMULATED_REGION)) if (compatFlags != 0) { var message = "" if (compatFlags and 1 != 0) { @@ -297,19 +287,10 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) private fun addSystemSettings(sl: ArrayList) { settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_system)) sl.apply { - val usernameSetting = object : AbstractStringSetting { - override var string: String - get() = SystemSaveGame.getUsername() - set(value) = SystemSaveGame.setUsername(value) - override val key = null - override val section = null - override val isRuntimeEditable = false - override val valueAsString get() = string - override val defaultValue = "AZAHAR" - } add(HeaderSetting(R.string.emulation_settings)) add( SwitchSetting( + settings, BooleanSetting.NEW_3DS, R.string.new_3ds, 0, @@ -319,6 +300,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) ) add( SwitchSetting( + settings, BooleanSetting.LLE_APPLETS, R.string.lle_applets, 0, @@ -328,6 +310,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) ) add( SwitchSetting( + settings, BooleanSetting.REQUIRED_ONLINE_LLE_MODULES, R.string.enable_required_online_lle_modules, R.string.enable_required_online_lle_modules_desc, @@ -336,35 +319,29 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) ) ) add(HeaderSetting(R.string.profile_settings)) - val regionSetting = object : AbstractIntSetting { - override var int: Int - get() { - val ret = IntSetting.EMULATED_REGION.int - checkCountryCompatibility() - return ret - } - set(value) { - IntSetting.EMULATED_REGION.int = value - countryCompatibilityChanged = true - checkCountryCompatibility() - } - override val key = IntSetting.EMULATED_REGION.key - override val section = null - override val isRuntimeEditable = false - override val valueAsString get() = int.toString() - override val defaultValue = IntSetting.EMULATED_REGION.defaultValue - } add( SingleChoiceSetting( - regionSetting, + settings, + null, R.string.emulated_region, 0, R.array.regionNames, R.array.regionValues, + getValue = { + val ret = settings.get(IntSetting.EMULATED_REGION) + checkCountryCompatibility() + ret + }, + setValue = { + settings.set(IntSetting.EMULATED_REGION, it) + countryCompatibilityChanged = true + checkCountryCompatibility() + } ) ) add( SwitchSetting( + settings, BooleanSetting.APPLY_REGION_FREE_PATCH, R.string.apply_region_free_patch, R.string.apply_region_free_patch_desc, @@ -372,24 +349,6 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) BooleanSetting.APPLY_REGION_FREE_PATCH.defaultValue ) ) - val systemCountrySetting = object : AbstractShortSetting { - override var short: Short - get() { - val ret = SystemSaveGame.getCountryCode() - checkCountryCompatibility() - return ret; - } - set(value) { - SystemSaveGame.setCountryCode(value) - countryCompatibilityChanged = true - checkCountryCompatibility() - } - override val key = null - override val section = null - override val isRuntimeEditable = false - override val valueAsString = short.toString() - override val defaultValue: Short = 49 - } var index = -1 val countries = settingsActivity.resources.getStringArray(R.array.countries) .mapNotNull { @@ -398,63 +357,64 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) } add( StringSingleChoiceSetting( - systemCountrySetting, + settings, + null, R.string.country, 0, countries.map { it.first }.toTypedArray(), - countries.map { it.second }.toTypedArray() + countries.map { it.second }.toTypedArray(), + getValue = { + val ret = SystemSaveGame.getCountryCode() + checkCountryCompatibility() + ret.toString() + }, + setValue = { + SystemSaveGame.setCountryCode(it.toShort()) + countryCompatibilityChanged = true + checkCountryCompatibility() + } ) ) - val systemLanguageSetting = object : AbstractIntSetting { - override var int: Int - get() = SystemSaveGame.getSystemLanguage() - set(value) = SystemSaveGame.setSystemLanguage(value) - override val key = null - override val section = null - override val isRuntimeEditable = false - override val valueAsString get() = int.toString() - override val defaultValue = 1 - } add( SingleChoiceSetting( - systemLanguageSetting, + settings, + null, R.string.emulated_language, 0, R.array.languageNames, - R.array.languageValues + R.array.languageValues, + getValue = { SystemSaveGame.getSystemLanguage() }, + setValue = { SystemSaveGame.setSystemLanguage(it) } ) ) add( StringInputSetting( - usernameSetting, + settings, + null, R.string.username, 0, "AZAHAR", - 10 + 10, + getValue = { SystemSaveGame.getUsername() }, + setValue = { SystemSaveGame.setUsername(it) } ) ) - val playCoinSettings = object : AbstractIntSetting { - override var int: Int - get() = SystemSaveGame.getPlayCoins() - set(value) = SystemSaveGame.setPlayCoins(value) - override val key = null - override val section = null - override val isRuntimeEditable = false - override val valueAsString = int.toString() - override val defaultValue = 42 - } add( SliderSetting( - playCoinSettings, + settings, + null, R.string.play_coins, 0, 0, 300, - "" + "", + getValue = { SystemSaveGame.getPlayCoins().toFloat() }, + setValue = { SystemSaveGame.setPlayCoins(it.roundToInt()) } ) ) add( SliderSetting( + settings, IntSetting.STEPS_PER_HOUR, R.string.steps_per_hour, R.string.steps_per_hour_description, @@ -487,10 +447,17 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) ) add(HeaderSetting(R.string.birthday)) - val systemBirthdayMonthSetting = object : AbstractShortSetting { - override var short: Short - get() = SystemSaveGame.getBirthday()[0] - set(value) { + add( + SingleChoiceSetting( + settings, + null, + R.string.birthday_month, + 0, + R.array.months, + R.array.monthValues, + getValue = { SystemSaveGame.getBirthday()[0].toInt() }, + setValue = { + val value = it.toShort() val birthdayDay = SystemSaveGame.getBirthday()[1] val daysInNewMonth = BirthdayMonth.getMonthFromCode(value)?.days ?: 31 if (daysInNewMonth < birthdayDay) { @@ -500,26 +467,23 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) SystemSaveGame.setBirthday(value, birthdayDay) } } - override val key = null - override val section = null - override val isRuntimeEditable = false - override val valueAsString get() = short.toString() - override val defaultValue: Short = 11 - } - add( - SingleChoiceSetting( - systemBirthdayMonthSetting, - R.string.birthday_month, - 0, - R.array.months, - R.array.monthValues ) ) - val systemBirthdayDaySetting = object : AbstractShortSetting { - override var short: Short - get() = SystemSaveGame.getBirthday()[1] - set(value) { + val birthdayMonth = SystemSaveGame.getBirthday()[0] + val daysInMonth = BirthdayMonth.getMonthFromCode(birthdayMonth)?.days ?: 31 + val dayArray = Array(daysInMonth) { "${it + 1}" } + add( + StringSingleChoiceSetting( + settings, + null, + R.string.birthday_day, + 0, + dayArray, + dayArray, + getValue = { SystemSaveGame.getBirthday()[1].toString() }, + setValue = { + val value = it.toShort() val birthdayMonth = SystemSaveGame.getBirthday()[0] val daysInNewMonth = BirthdayMonth.getMonthFromCode(birthdayMonth)?.days ?: 31 @@ -529,28 +493,14 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) SystemSaveGame.setBirthday(birthdayMonth, value) } } - override val key = null - override val section = null - override val isRuntimeEditable = false - override val valueAsString get() = short.toString() - override val defaultValue: Short = 7 - } - val birthdayMonth = SystemSaveGame.getBirthday()[0] - val daysInMonth = BirthdayMonth.getMonthFromCode(birthdayMonth)?.days ?: 31 - val dayArray = Array(daysInMonth) { "${it + 1}" } - add( - StringSingleChoiceSetting( - systemBirthdayDaySetting, - R.string.birthday_day, - 0, - dayArray, - dayArray + ) ) add(HeaderSetting(R.string.clock)) add( SingleChoiceSetting( + settings, IntSetting.INIT_CLOCK, R.string.init_clock, R.string.init_clock_description, @@ -562,6 +512,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) ) add( DateTimeSetting( + settings, StringSetting.INIT_TIME, R.string.simulated_clock, R.string.simulated_clock_description, @@ -573,6 +524,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) add(HeaderSetting(R.string.plugin_loader)) add( SwitchSetting( + settings, BooleanSetting.PLUGIN_LOADER, R.string.plugin_loader, R.string.plugin_loader_description, @@ -582,6 +534,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) ) add( SwitchSetting( + settings, BooleanSetting.ALLOW_PLUGIN_LOADER, R.string.allow_plugin_loader, R.string.allow_plugin_loader_description, @@ -592,6 +545,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) add(HeaderSetting(R.string.storage)) add( SwitchSetting( + settings, BooleanSetting.COMPRESS_INSTALLED_CIA_CONTENT, R.string.compress_cia_installs, R.string.compress_cia_installs_description, @@ -666,6 +620,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) add(HeaderSetting(R.string.inner_camera)) add( StringSingleChoiceSetting( + settings, StringSetting.CAMERA_INNER_NAME, R.string.image_source, R.string.image_source_description, @@ -678,6 +633,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) if (haveCameraDevices) { add( StringSingleChoiceSetting( + settings, StringSetting.CAMERA_INNER_CONFIG, R.string.camera_device, R.string.camera_device_description, @@ -690,6 +646,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) } add( SingleChoiceSetting( + settings, IntSetting.CAMERA_INNER_FLIP, R.string.image_flip, 0, @@ -703,6 +660,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) add(HeaderSetting(R.string.outer_left_camera)) add( StringSingleChoiceSetting( + settings, StringSetting.CAMERA_OUTER_LEFT_NAME, R.string.image_source, R.string.image_source_description, @@ -715,6 +673,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) if (haveCameraDevices) { add( StringSingleChoiceSetting( + settings, StringSetting.CAMERA_OUTER_LEFT_CONFIG, R.string.camera_device, R.string.camera_device_description, @@ -727,6 +686,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) } add( SingleChoiceSetting( + settings, IntSetting.CAMERA_OUTER_LEFT_FLIP, R.string.image_flip, 0, @@ -740,6 +700,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) add(HeaderSetting(R.string.outer_right_camera)) add( StringSingleChoiceSetting( + settings, StringSetting.CAMERA_OUTER_RIGHT_NAME, R.string.image_source, R.string.image_source_description, @@ -752,6 +713,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) if (haveCameraDevices) { add( StringSingleChoiceSetting( + settings, StringSetting.CAMERA_OUTER_RIGHT_CONFIG, R.string.camera_device, R.string.camera_device_description, @@ -764,6 +726,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) } add( SingleChoiceSetting( + settings, IntSetting.CAMERA_OUTER_RIGHT_FLIP, R.string.image_flip, 0, @@ -832,6 +795,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) add(HeaderSetting(R.string.miscellaneous)) add( SwitchSetting( + settings, BooleanSetting.USE_ARTIC_BASE_CONTROLLER, R.string.use_artic_base_controller, R.string.use_artic_base_controller_description, @@ -842,20 +806,16 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) } } - private fun getInputObject(key: String): AbstractStringSetting { - return object : AbstractStringSetting { - override var string: String - get() = preferences.getString(key, "")!! - set(value) { - preferences.edit() - .putString(key, value) - .apply() - } + private fun getInputObject(key: String): AbstractSetting { + return object : AbstractSetting { override val key = key override val section = Settings.SECTION_CONTROLS override val isRuntimeEditable = true - override val valueAsString = preferences.getString(key, "")!! override val defaultValue = "" + override fun valueFromString(string: String): String = string + override fun valueToString(value: String): String = value + // TODO: make input mappings also work per-game, which will be easy if we move + // them to config files } } @@ -865,6 +825,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) add(HeaderSetting(R.string.renderer)) add( SingleChoiceSetting( + settings, IntSetting.GRAPHICS_API, R.string.graphics_api, 0, @@ -876,6 +837,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) ) add( SwitchSetting( + settings, BooleanSetting.SPIRV_SHADER_GEN, R.string.spirv_shader_gen, R.string.spirv_shader_gen_description, @@ -885,6 +847,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) ) add( SwitchSetting( + settings, BooleanSetting.DISABLE_SPIRV_OPTIMIZER, R.string.disable_spirv_optimizer, R.string.disable_spirv_optimizer_description, @@ -894,6 +857,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) ) add( SwitchSetting( + settings, BooleanSetting.ASYNC_SHADERS, R.string.async_shaders, R.string.async_shaders_description, @@ -903,6 +867,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) ) add( SingleChoiceSetting( + settings, IntSetting.RESOLUTION_FACTOR, R.string.internal_resolution, R.string.internal_resolution_description, @@ -914,6 +879,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) ) add( SwitchSetting( + settings, BooleanSetting.USE_INTEGER_SCALING, R.string.use_integer_scaling, R.string.use_integer_scaling_description, @@ -923,6 +889,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) ) add( SwitchSetting( + settings, BooleanSetting.LINEAR_FILTERING, R.string.linear_filtering, R.string.linear_filtering_description, @@ -932,6 +899,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) ) add( SwitchSetting( + settings, BooleanSetting.SHADERS_ACCURATE_MUL, R.string.shaders_accurate_mul, R.string.shaders_accurate_mul_description, @@ -941,6 +909,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) ) add( SwitchSetting( + settings, BooleanSetting.DISK_SHADER_CACHE, R.string.use_disk_shader_cache, R.string.use_disk_shader_cache_description, @@ -950,6 +919,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) ) add( SingleChoiceSetting( + settings, IntSetting.TEXTURE_FILTER, R.string.texture_filter_name, R.string.texture_filter_description, @@ -961,6 +931,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) ) add( SliderSetting( + settings, IntSetting.DELAY_RENDER_THREAD_US, R.string.delay_render_thread, R.string.delay_render_thread_description, @@ -975,6 +946,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) add(HeaderSetting(R.string.stereoscopy)) add( SingleChoiceSetting( + settings, IntSetting.RENDER_3D_WHICH_DISPLAY, R.string.render_3d_which_display, R.string.render_3d_which_display_description, @@ -986,6 +958,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) ) add( SingleChoiceSetting( + settings, IntSetting.STEREOSCOPIC_3D_MODE, R.string.render3d, R.string.render3d_description, @@ -993,12 +966,13 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) R.array.render3dValues, IntSetting.STEREOSCOPIC_3D_MODE.key, IntSetting.STEREOSCOPIC_3D_MODE.defaultValue, - isEnabled = IntSetting.RENDER_3D_WHICH_DISPLAY.int != StereoWhichDisplay.NONE.int + isEnabled = settings.get(IntSetting.RENDER_3D_WHICH_DISPLAY) != StereoWhichDisplay.NONE.int ) ) add( SliderSetting( + settings, IntSetting.STEREOSCOPIC_3D_DEPTH, R.string.factor3d, R.string.factor3d_description, @@ -1011,6 +985,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) ) add( SwitchSetting( + settings, BooleanSetting.DISABLE_RIGHT_EYE_RENDER, R.string.disable_right_eye_render, R.string.disable_right_eye_render_description, @@ -1021,18 +996,20 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) add( SwitchSetting( + settings, BooleanSetting.SWAP_EYES_3D, R.string.swap_eyes_3d, R.string.swap_eyes_3d_description, BooleanSetting.SWAP_EYES_3D.key, BooleanSetting.SWAP_EYES_3D.defaultValue, - isEnabled = IntSetting.RENDER_3D_WHICH_DISPLAY.int != StereoWhichDisplay.NONE.int + isEnabled = settings.get(IntSetting.RENDER_3D_WHICH_DISPLAY) != StereoWhichDisplay.NONE.int ) ) add(HeaderSetting(R.string.cardboard_vr)) add( SliderSetting( + settings, IntSetting.CARDBOARD_SCREEN_SIZE, R.string.cardboard_screen_size, R.string.cardboard_screen_size_description, @@ -1041,11 +1018,12 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) "%", IntSetting.CARDBOARD_SCREEN_SIZE.key, IntSetting.CARDBOARD_SCREEN_SIZE.defaultValue.toFloat(), - isEnabled = IntSetting.STEREOSCOPIC_3D_MODE.int == StereoMode.CARDBOARD_VR.int + isEnabled = settings.get(IntSetting.STEREOSCOPIC_3D_MODE) == StereoMode.CARDBOARD_VR.int ) ) add( SliderSetting( + settings, IntSetting.CARDBOARD_X_SHIFT, R.string.cardboard_x_shift, R.string.cardboard_x_shift_description, @@ -1054,11 +1032,12 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) "%", IntSetting.CARDBOARD_X_SHIFT.key, IntSetting.CARDBOARD_X_SHIFT.defaultValue.toFloat(), - isEnabled = IntSetting.STEREOSCOPIC_3D_MODE.int == StereoMode.CARDBOARD_VR.int + isEnabled = settings.get(IntSetting.STEREOSCOPIC_3D_MODE) == StereoMode.CARDBOARD_VR.int ) ) add( SliderSetting( + settings, IntSetting.CARDBOARD_Y_SHIFT, R.string.cardboard_y_shift, R.string.cardboard_y_shift_description, @@ -1067,13 +1046,14 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) "%", IntSetting.CARDBOARD_Y_SHIFT.key, IntSetting.CARDBOARD_Y_SHIFT.defaultValue.toFloat(), - isEnabled = IntSetting.STEREOSCOPIC_3D_MODE.int == StereoMode.CARDBOARD_VR.int + isEnabled = settings.get(IntSetting.STEREOSCOPIC_3D_MODE) == StereoMode.CARDBOARD_VR.int ) ) add(HeaderSetting(R.string.utility)) add( SwitchSetting( + settings, BooleanSetting.DUMP_TEXTURES, R.string.dump_textures, R.string.dump_textures_description, @@ -1083,6 +1063,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) ) add( SwitchSetting( + settings, BooleanSetting.CUSTOM_TEXTURES, R.string.custom_textures, R.string.custom_textures_description, @@ -1092,6 +1073,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) ) add( SwitchSetting( + settings, BooleanSetting.ASYNC_CUSTOM_LOADING, R.string.async_custom_loading, R.string.async_custom_loading_description, @@ -1103,6 +1085,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) add(HeaderSetting(R.string.advanced)) add( SingleChoiceSetting( + settings, IntSetting.TEXTURE_SAMPLING, R.string.texture_sampling_name, R.string.texture_sampling_description, @@ -1132,6 +1115,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) sl.apply { add( SingleChoiceSetting( + settings, IntSetting.ORIENTATION_OPTION, R.string.layout_screen_orientation, 0, @@ -1143,6 +1127,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) ) add( SwitchSetting( + settings, BooleanSetting.EXPAND_TO_CUTOUT_AREA, R.string.expand_to_cutout_area, R.string.expand_to_cutout_area_description, @@ -1152,6 +1137,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) ) add( SingleChoiceSetting( + settings, IntSetting.SCREEN_LAYOUT, R.string.emulation_switch_screen_layout, 0, @@ -1163,6 +1149,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) ) add( SwitchSetting( + settings, BooleanSetting.UPRIGHT_SCREEN, R.string.emulation_rotate_upright, 0, @@ -1172,6 +1159,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) ) add( MultiChoiceSetting( + settings, IntListSetting.LAYOUTS_TO_CYCLE, R.string.layouts_to_cycle, R.string.layouts_to_cycle_description, @@ -1183,6 +1171,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) ) add( SingleChoiceSetting( + settings, IntSetting.PORTRAIT_SCREEN_LAYOUT, R.string.emulation_switch_portrait_layout, 0, @@ -1194,6 +1183,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) ) add( SingleChoiceSetting( + settings, IntSetting.SECONDARY_DISPLAY_LAYOUT, R.string.emulation_switch_secondary_layout, R.string.emulation_switch_secondary_layout_description, @@ -1205,6 +1195,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) ) add( SingleChoiceSetting( + settings, IntSetting.ASPECT_RATIO, R.string.emulation_aspect_ratio, 0, @@ -1212,11 +1203,12 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) R.array.aspectRatioValues, IntSetting.ASPECT_RATIO.key, IntSetting.ASPECT_RATIO.defaultValue, - isEnabled = IntSetting.SCREEN_LAYOUT.int == ScreenLayout.SINGLE_SCREEN.int, + isEnabled = settings.get(IntSetting.SCREEN_LAYOUT) == ScreenLayout.SINGLE_SCREEN.int, ) ) add( SingleChoiceSetting( + settings, IntSetting.SMALL_SCREEN_POSITION, R.string.emulation_small_screen_position, R.string.small_screen_position_description, @@ -1228,6 +1220,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) ) add( SliderSetting( + settings, IntSetting.SCREEN_GAP, R.string.screen_gap, R.string.screen_gap_description, @@ -1240,6 +1233,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) ) add( SliderSetting( + settings, FloatSetting.LARGE_SCREEN_PROPORTION, R.string.large_screen_proportion, R.string.large_screen_proportion_description, @@ -1252,6 +1246,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) ) add( SliderSetting( + settings, FloatSetting.SECOND_SCREEN_OPACITY, R.string.second_screen_opacity, R.string.second_screen_opacity_description, @@ -1260,77 +1255,47 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) "%", FloatSetting.SECOND_SCREEN_OPACITY.key, FloatSetting.SECOND_SCREEN_OPACITY.defaultValue, - isEnabled = IntSetting.SCREEN_LAYOUT.int == ScreenLayout.CUSTOM_LAYOUT.int + 0, + isEnabled = settings.get(IntSetting.SCREEN_LAYOUT) == ScreenLayout.CUSTOM_LAYOUT.int ) ) add(HeaderSetting(R.string.bg_color, R.string.bg_color_description)) - val bgRedSetting = object : AbstractIntSetting { - override var int: Int - get() = (FloatSetting.BACKGROUND_RED.float * 255).toInt() - set(value) { - FloatSetting.BACKGROUND_RED.float = value.toFloat() / 255 - settings.saveSetting(FloatSetting.BACKGROUND_RED, SettingsFile.FILE_NAME_CONFIG) - } - override val key = null - override val section = null - override val isRuntimeEditable = false - override val valueAsString = int.toString() - override val defaultValue = FloatSetting.BACKGROUND_RED.defaultValue - } add( SliderSetting( - bgRedSetting, + settings, + FloatSetting.BACKGROUND_RED, R.string.bg_red, 0, 0, 255, - "" + "", + rounding = 0 ) ) - val bgGreenSetting = object : AbstractIntSetting { - override var int: Int - get() = (FloatSetting.BACKGROUND_GREEN.float * 255).toInt() - set(value) { - FloatSetting.BACKGROUND_GREEN.float = value.toFloat() / 255 - settings.saveSetting(FloatSetting.BACKGROUND_GREEN, SettingsFile.FILE_NAME_CONFIG) - } - override val key = null - override val section = null - override val isRuntimeEditable = false - override val valueAsString = int.toString() - override val defaultValue = FloatSetting.BACKGROUND_GREEN.defaultValue - } + add( SliderSetting( - bgGreenSetting, + settings, + FloatSetting.BACKGROUND_GREEN, R.string.bg_green, 0, 0, 255, - "" + "", + rounding = 0 ) ) - val bgBlueSetting = object : AbstractIntSetting { - override var int: Int - get() = (FloatSetting.BACKGROUND_BLUE.float * 255).toInt() - set(value) { - FloatSetting.BACKGROUND_BLUE.float = value.toFloat() / 255 - settings.saveSetting(FloatSetting.BACKGROUND_BLUE, SettingsFile.FILE_NAME_CONFIG) - } - override val key = null - override val section = null - override val isRuntimeEditable = false - override val valueAsString = int.toString() - override val defaultValue = FloatSetting.BACKGROUND_BLUE.defaultValue - } + add( SliderSetting( - bgBlueSetting, + settings, + FloatSetting.BACKGROUND_BLUE, R.string.bg_blue, 0, 0, 255, - "" + "", + rounding = 0 ) ) add( @@ -1368,6 +1333,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) add( SwitchSetting( + settings, BooleanSetting.PERF_OVERLAY_ENABLE, R.string.performance_overlay_enable, 0, @@ -1378,6 +1344,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) add( SwitchSetting( + settings, BooleanSetting.PERF_OVERLAY_BACKGROUND, R.string.performance_overlay_background, R.string.performance_overlay_background_description, @@ -1388,6 +1355,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) add( SingleChoiceSetting( + settings, IntSetting.PERFORMANCE_OVERLAY_POSITION, R.string.performance_overlay_position, R.string.performance_overlay_position_description, @@ -1401,6 +1369,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) add( SwitchSetting( + settings, BooleanSetting.PERF_OVERLAY_SHOW_FPS, R.string.performance_overlay_show_fps, R.string.performance_overlay_show_fps_description, @@ -1411,6 +1380,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) add( SwitchSetting( + settings, BooleanSetting.PERF_OVERLAY_SHOW_FRAMETIME, R.string.performance_overlay_show_frametime, R.string.performance_overlay_show_frametime_description, @@ -1421,6 +1391,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) add( SwitchSetting( + settings, BooleanSetting.PERF_OVERLAY_SHOW_SPEED, R.string.performance_overlay_show_speed, R.string.performance_overlay_show_speed_description, @@ -1431,6 +1402,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) add( SwitchSetting( + settings, BooleanSetting.PERF_OVERLAY_SHOW_APP_RAM_USAGE, R.string.performance_overlay_show_app_ram_usage, R.string.performance_overlay_show_app_ram_usage_description, @@ -1441,6 +1413,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) add( SwitchSetting( + settings, BooleanSetting.PERF_OVERLAY_SHOW_AVAILABLE_RAM, R.string.performance_overlay_show_available_ram, R.string.performance_overlay_show_available_ram_description, @@ -1451,6 +1424,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) add( SwitchSetting( + settings, BooleanSetting.PERF_OVERLAY_SHOW_BATTERY_TEMP, R.string.performance_overlay_show_battery_temp, R.string.performance_overlay_show_battery_temp_description, @@ -1467,6 +1441,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) add(HeaderSetting(R.string.emulation_top_screen)) add( SliderSetting( + settings, IntSetting.LANDSCAPE_TOP_X, R.string.emulation_custom_layout_x, 0, @@ -1479,6 +1454,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) ) add( SliderSetting( + settings, IntSetting.LANDSCAPE_TOP_Y, R.string.emulation_custom_layout_y, 0, @@ -1491,6 +1467,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) ) add( SliderSetting( + settings, IntSetting.LANDSCAPE_TOP_WIDTH, R.string.emulation_custom_layout_width, 0, @@ -1503,6 +1480,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) ) add( SliderSetting( + settings, IntSetting.LANDSCAPE_TOP_HEIGHT, R.string.emulation_custom_layout_height, 0, @@ -1516,6 +1494,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) add(HeaderSetting(R.string.emulation_bottom_screen)) add( SliderSetting( + settings, IntSetting.LANDSCAPE_BOTTOM_X, R.string.emulation_custom_layout_x, 0, @@ -1528,6 +1507,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) ) add( SliderSetting( + settings, IntSetting.LANDSCAPE_BOTTOM_Y, R.string.emulation_custom_layout_y, 0, @@ -1540,6 +1520,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) ) add( SliderSetting( + settings, IntSetting.LANDSCAPE_BOTTOM_WIDTH, R.string.emulation_custom_layout_width, 0, @@ -1552,6 +1533,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) ) add( SliderSetting( + settings, IntSetting.LANDSCAPE_BOTTOM_HEIGHT, R.string.emulation_custom_layout_height, 0, @@ -1572,6 +1554,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) add(HeaderSetting(R.string.emulation_top_screen)) add( SliderSetting( + settings, IntSetting.PORTRAIT_TOP_X, R.string.emulation_custom_layout_x, 0, @@ -1584,6 +1567,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) ) add( SliderSetting( + settings, IntSetting.PORTRAIT_TOP_Y, R.string.emulation_custom_layout_y, 0, @@ -1596,6 +1580,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) ) add( SliderSetting( + settings, IntSetting.PORTRAIT_TOP_WIDTH, R.string.emulation_custom_layout_width, 0, @@ -1608,6 +1593,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) ) add( SliderSetting( + settings, IntSetting.PORTRAIT_TOP_HEIGHT, R.string.emulation_custom_layout_height, 0, @@ -1621,6 +1607,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) add(HeaderSetting(R.string.emulation_bottom_screen)) add( SliderSetting( + settings, IntSetting.PORTRAIT_BOTTOM_X, R.string.emulation_custom_layout_x, 0, @@ -1633,6 +1620,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) ) add( SliderSetting( + settings, IntSetting.PORTRAIT_BOTTOM_Y, R.string.emulation_custom_layout_y, 0, @@ -1645,6 +1633,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) ) add( SliderSetting( + settings, IntSetting.PORTRAIT_BOTTOM_WIDTH, R.string.emulation_custom_layout_width, 0, @@ -1657,6 +1646,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) ) add( SliderSetting( + settings, IntSetting.PORTRAIT_BOTTOM_HEIGHT, R.string.emulation_custom_layout_height, 0, @@ -1676,18 +1666,21 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) sl.apply { add( SliderSetting( - ScaledFloatSetting.AUDIO_VOLUME, + settings, + FloatSetting.AUDIO_VOLUME, R.string.audio_volume, 0, 0, 100, "%", - ScaledFloatSetting.AUDIO_VOLUME.key, - ScaledFloatSetting.AUDIO_VOLUME.defaultValue + FloatSetting.AUDIO_VOLUME.key, + FloatSetting.AUDIO_VOLUME.defaultValue, + rounding = 0 ) ) add( SwitchSetting( + settings, BooleanSetting.ENABLE_AUDIO_STRETCHING, R.string.audio_stretch, R.string.audio_stretch_description, @@ -1697,6 +1690,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) ) add( SwitchSetting( + settings, BooleanSetting.ENABLE_REALTIME_AUDIO, R.string.realtime_audio, R.string.realtime_audio_description, @@ -1706,6 +1700,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) ) add( SingleChoiceSetting( + settings, IntSetting.AUDIO_INPUT_TYPE, R.string.audio_input_type, 0, @@ -1715,24 +1710,16 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) IntSetting.AUDIO_INPUT_TYPE.defaultValue ) ) - - val soundOutputModeSetting = object : AbstractIntSetting { - override var int: Int - get() = SystemSaveGame.getSoundOutputMode() - set(value) = SystemSaveGame.setSoundOutputMode(value) - override val key = null - override val section = null - override val isRuntimeEditable = false - override val valueAsString = int.toString() - override val defaultValue = 1 - } add( SingleChoiceSetting( - soundOutputModeSetting, + settings, + null, R.string.sound_output_mode, 0, R.array.soundOutputModes, - R.array.soundOutputModeValues + R.array.soundOutputModeValues, + getValue = { SystemSaveGame.getSoundOutputMode() }, + setValue = { SystemSaveGame.setSoundOutputMode(it) } ) ) } @@ -1744,6 +1731,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) add(HeaderSetting(R.string.debug_warning)) add( SliderSetting( + settings, IntSetting.CPU_CLOCK_SPEED, R.string.cpu_clock_speed, 0, @@ -1756,6 +1744,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) ) add( SwitchSetting( + settings, BooleanSetting.CPU_JIT, R.string.cpu_jit, R.string.cpu_jit_description, @@ -1765,6 +1754,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) ) add( SwitchSetting( + settings, BooleanSetting.HW_SHADER, R.string.hw_shaders, R.string.hw_shaders_description, @@ -1774,6 +1764,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) ) add( SwitchSetting( + settings, BooleanSetting.SHADER_JIT, R.string.shader_jit, R.string.shader_jit_description, @@ -1783,6 +1774,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) ) add( SwitchSetting( + settings, BooleanSetting.VSYNC, R.string.vsync, R.string.vsync_description, @@ -1792,6 +1784,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) ) add( SwitchSetting( + settings, BooleanSetting.DEBUG_RENDERER, R.string.renderer_debug, R.string.renderer_debug_description, @@ -1801,6 +1794,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) ) add( SwitchSetting( + settings, BooleanSetting.INSTANT_DEBUG_LOG, R.string.instant_debug_log, R.string.instant_debug_log_description, @@ -1810,6 +1804,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) ) add( SwitchSetting( + settings, BooleanSetting.ENABLE_RPC_SERVER, R.string.enable_rpc_server, R.string.enable_rpc_server_desc, @@ -1819,6 +1814,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) ) add( SwitchSetting( + settings, BooleanSetting.TOGGLE_UNIQUE_DATA_CONSOLE_TYPE, R.string.toggle_unique_data_console_type, R.string.toggle_unique_data_console_type_desc, @@ -1828,6 +1824,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) ) add( SwitchSetting( + settings, BooleanSetting.DELAY_START_LLE_MODULES, R.string.delay_start_lle_modules, R.string.delay_start_lle_modules_description, @@ -1837,6 +1834,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) ) add( SwitchSetting( + settings, BooleanSetting.DETERMINISTIC_ASYNC_OPERATIONS, R.string.deterministic_async_operations, R.string.deterministic_async_operations_description, @@ -1851,111 +1849,77 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) private fun addThemeSettings(sl: ArrayList) { settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_theme)) sl.apply { - val theme: AbstractBooleanSetting = object : AbstractBooleanSetting { - override var boolean: Boolean - get() = preferences.getBoolean(Settings.PREF_MATERIAL_YOU, false) - set(value) { - preferences.edit() - .putBoolean(Settings.PREF_MATERIAL_YOU, value) - .apply() - settingsActivity.recreate() - } - override val key: String? = null - override val section: String? = null - override val isRuntimeEditable: Boolean = false - override val valueAsString: String - get() = preferences.getBoolean(Settings.PREF_MATERIAL_YOU, false).toString() - override val defaultValue = false - } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { add( SwitchSetting( - theme, + settings, + null, R.string.material_you, - R.string.material_you_description + R.string.material_you_description, + getValue = { + preferences.getBoolean(Settings.PREF_MATERIAL_YOU, false) + }, + setValue = { + preferences.edit() + .putBoolean(Settings.PREF_MATERIAL_YOU, it) + .apply() + settingsActivity.recreate() + } ) ) } - - val staticThemeColor: AbstractIntSetting = object : AbstractIntSetting { - override var int: Int - get() = preferences.getInt(Settings.PREF_STATIC_THEME_COLOR, 0) - set(value) { - preferences.edit() - .putInt(Settings.PREF_STATIC_THEME_COLOR, value) - .apply() - settingsActivity.recreate() - } - override val key: String? = null - override val section: String? = null - override val isRuntimeEditable: Boolean = false - override val valueAsString: String - get() = preferences.getInt(Settings.PREF_STATIC_THEME_COLOR, 0).toString() - override val defaultValue: Any = 0 - } - add( SingleChoiceSetting( - staticThemeColor, + settings, + null, R.string.static_theme_color, R.string.static_theme_color_description, R.array.staticThemeNames, - R.array.staticThemeValues + R.array.staticThemeValues, + getValue = { + preferences.getInt(Settings.PREF_STATIC_THEME_COLOR, 0) + }, + setValue = { + preferences.edit() + .putInt(Settings.PREF_STATIC_THEME_COLOR, it) + .apply() + settingsActivity.recreate() + } ) ) - - val themeMode: AbstractIntSetting = object : AbstractIntSetting { - override var int: Int - get() = preferences.getInt(Settings.PREF_THEME_MODE, -1) - set(value) { + add( + SingleChoiceSetting( + settings, + null, + R.string.change_theme_mode, + 0, + R.array.themeModeEntries, + R.array.themeModeValues, + getValue = { + preferences.getInt(Settings.PREF_THEME_MODE, -1) + }, + setValue = { preferences.edit() - .putInt(Settings.PREF_THEME_MODE, value) + .putInt(Settings.PREF_THEME_MODE, it) .apply() ThemeUtil.setThemeMode(settingsActivity) settingsActivity.recreate() } - override val key: String? = null - override val section: String? = null - override val isRuntimeEditable: Boolean = false - override val valueAsString: String - get() = preferences.getInt(Settings.PREF_THEME_MODE, -1).toString() - override val defaultValue: Any = -1 - } - - add( - SingleChoiceSetting( - themeMode, - R.string.change_theme_mode, - 0, - R.array.themeModeEntries, - R.array.themeModeValues ) ) - - val blackBackgrounds: AbstractBooleanSetting = object : AbstractBooleanSetting { - override var boolean: Boolean - get() = preferences.getBoolean(Settings.PREF_BLACK_BACKGROUNDS, false) - set(value) { + add( + SwitchSetting( + settings, + null, + R.string.use_black_backgrounds, + R.string.use_black_backgrounds_description, + getValue = { preferences.getBoolean(Settings.PREF_BLACK_BACKGROUNDS, false) }, + setValue = { preferences.edit() - .putBoolean(Settings.PREF_BLACK_BACKGROUNDS, value) + .putBoolean(Settings.PREF_BLACK_BACKGROUNDS, it) .apply() settingsActivity.recreate() } - override val key: String? = null - override val section: String? = null - override val isRuntimeEditable: Boolean = false - override val valueAsString: String - get() = preferences.getBoolean(Settings.PREF_BLACK_BACKGROUNDS, false) - .toString() - override val defaultValue: Any = false - } - - add( - SwitchSetting( - blackBackgrounds, - R.string.use_black_backgrounds, - R.string.use_black_backgrounds_description ) ) } diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentView.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentView.kt index e1bb25230..95b8efe12 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentView.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentView.kt @@ -1,10 +1,9 @@ -// Copyright 2023 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. package org.citra.citra_emu.features.settings.ui -import org.citra.citra_emu.features.settings.model.AbstractSetting import org.citra.citra_emu.features.settings.model.view.SettingsItem /** @@ -45,13 +44,6 @@ interface SettingsFragmentView { */ fun showToastMessage(message: String?, is_long: Boolean) - /** - * Have the fragment add a setting to the HashMap. - * - * @param setting The (possibly previously missing) new setting. - */ - fun putSetting(setting: AbstractSetting) - /** * Have the fragment tell the containing Activity that a setting was modified. */ diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt index 7eb2dae97..69dd3fc0b 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt @@ -18,12 +18,11 @@ import org.citra.citra_emu.features.settings.ui.SettingsAdapter import java.text.SimpleDateFormat class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) : - SettingViewHolder(binding.root, adapter) { - private lateinit var setting: DateTimeSetting - + SettingViewHolder(binding.root, adapter) { + override lateinit var setting: DateTimeSetting @SuppressLint("SimpleDateFormat") override fun bind(item: SettingsItem) { - setting = item as DateTimeSetting + setting = item as? DateTimeSetting ?: return binding.textSettingName.setText(item.nameId) if (item.descriptionId != 0) { binding.textSettingDescription.visibility = View.VISIBLE diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/viewholder/HeaderViewHolder.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/viewholder/HeaderViewHolder.kt index ed794fcfb..6b34bf5e3 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/viewholder/HeaderViewHolder.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/viewholder/HeaderViewHolder.kt @@ -1,4 +1,4 @@ -// Copyright 2023 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -10,8 +10,8 @@ import org.citra.citra_emu.features.settings.model.view.SettingsItem import org.citra.citra_emu.features.settings.ui.SettingsAdapter class HeaderViewHolder(val binding: ListItemSettingsHeaderBinding, adapter: SettingsAdapter) : - SettingViewHolder(binding.root, adapter) { - + SettingViewHolder(binding.root, adapter) { + override var setting = null init { itemView.setOnClickListener(null) } diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/viewholder/InputBindingSettingViewHolder.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/viewholder/InputBindingSettingViewHolder.kt index 5d2a812e0..44e26758e 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/viewholder/InputBindingSettingViewHolder.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/viewholder/InputBindingSettingViewHolder.kt @@ -13,8 +13,8 @@ import org.citra.citra_emu.features.settings.model.view.SettingsItem import org.citra.citra_emu.features.settings.ui.SettingsAdapter class InputBindingSettingViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) : - SettingViewHolder(binding.root, adapter) { - private lateinit var setting: InputBindingSetting + SettingViewHolder(binding.root, adapter) { + override lateinit var setting: InputBindingSetting override fun bind(item: SettingsItem) { val preferences = PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext) diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/viewholder/MultiChoiceViewHolder.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/viewholder/MultiChoiceViewHolder.kt index 8493115a4..e94932b42 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/viewholder/MultiChoiceViewHolder.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/viewholder/MultiChoiceViewHolder.kt @@ -11,9 +11,8 @@ import org.citra.citra_emu.features.settings.model.view.MultiChoiceSetting import org.citra.citra_emu.features.settings.ui.SettingsAdapter class MultiChoiceViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) : - SettingViewHolder(binding.root, adapter) { - private lateinit var setting: SettingsItem - + SettingViewHolder(binding.root, adapter) { + override lateinit var setting: SettingsItem override fun bind(item: SettingsItem) { setting = item binding.textSettingName.setText(item.nameId) diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/viewholder/RunnableViewHolder.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/viewholder/RunnableViewHolder.kt index d75368598..c836bdfc7 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/viewholder/RunnableViewHolder.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/viewholder/RunnableViewHolder.kt @@ -14,8 +14,8 @@ import org.citra.citra_emu.features.settings.model.view.SettingsItem import org.citra.citra_emu.features.settings.ui.SettingsAdapter class RunnableViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) : - SettingViewHolder(binding.root, adapter) { - private lateinit var setting: RunnableSetting + SettingViewHolder(binding.root, adapter) { + override lateinit var setting: RunnableSetting override fun bind(item: SettingsItem) { setting = item as RunnableSetting diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/viewholder/SettingViewHolder.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/viewholder/SettingViewHolder.kt index 5b4d39cf4..2b408abe0 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/viewholder/SettingViewHolder.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/viewholder/SettingViewHolder.kt @@ -9,14 +9,17 @@ import androidx.recyclerview.widget.RecyclerView import org.citra.citra_emu.features.settings.model.view.SettingsItem import org.citra.citra_emu.features.settings.ui.SettingsAdapter -abstract class SettingViewHolder(itemView: View, protected val adapter: SettingsAdapter) : +abstract class SettingViewHolder(itemView: View, protected val adapter: SettingsAdapter) : RecyclerView.ViewHolder(itemView), View.OnClickListener, View.OnLongClickListener { - init { itemView.setOnClickListener(this) itemView.setOnLongClickListener(this) } + /** + * The SettingsItem we are holding + */ + abstract val setting: T? /** * Called by the adapter to set this ViewHolder's child views to display the list item * it must now represent. diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt index 6e899d946..8c764b617 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt @@ -12,8 +12,8 @@ import org.citra.citra_emu.features.settings.model.view.StringSingleChoiceSettin import org.citra.citra_emu.features.settings.ui.SettingsAdapter class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) : - SettingViewHolder(binding.root, adapter) { - private lateinit var setting: SettingsItem + SettingViewHolder(binding.root, adapter) { + override lateinit var setting: SettingsItem override fun bind(item: SettingsItem) { setting = item diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/viewholder/SliderViewHolder.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/viewholder/SliderViewHolder.kt index 726d8c96f..1d651046e 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/viewholder/SliderViewHolder.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/viewholder/SliderViewHolder.kt @@ -6,17 +6,14 @@ package org.citra.citra_emu.features.settings.ui.viewholder import android.view.View import org.citra.citra_emu.databinding.ListItemSettingBinding -import org.citra.citra_emu.features.settings.model.AbstractFloatSetting -import org.citra.citra_emu.features.settings.model.AbstractIntSetting import org.citra.citra_emu.features.settings.model.FloatSetting -import org.citra.citra_emu.features.settings.model.ScaledFloatSetting import org.citra.citra_emu.features.settings.model.view.SettingsItem import org.citra.citra_emu.features.settings.model.view.SliderSetting import org.citra.citra_emu.features.settings.ui.SettingsAdapter class SliderViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) : - SettingViewHolder(binding.root, adapter) { - private lateinit var setting: SliderSetting + SettingViewHolder(binding.root, adapter) { + override lateinit var setting: SliderSetting override fun bind(item: SettingsItem) { setting = item as SliderSetting @@ -28,12 +25,7 @@ class SliderViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAda binding.textSettingDescription.visibility = View.GONE } binding.textSettingValue.visibility = View.VISIBLE - binding.textSettingValue.text = when (setting.setting) { - is ScaledFloatSetting -> - "${(setting.setting as ScaledFloatSetting).float.toInt()}${setting.units}" - is FloatSetting -> "${(setting.setting as AbstractFloatSetting).float}${setting.units}" - else -> "${(setting.setting as AbstractIntSetting).int}${setting.units}" - } + binding.textSettingValue.text = "${setting.valueAsString}${setting.units}" if (setting.isActive) { binding.textSettingName.alpha = 1f diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/viewholder/StringInputViewHolder.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/viewholder/StringInputViewHolder.kt index 09bc206d3..75981f538 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/viewholder/StringInputViewHolder.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/viewholder/StringInputViewHolder.kt @@ -11,11 +11,10 @@ import org.citra.citra_emu.features.settings.model.view.StringInputSetting import org.citra.citra_emu.features.settings.ui.SettingsAdapter class StringInputViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) : - SettingViewHolder(binding.root, adapter) { - private lateinit var setting: SettingsItem - + SettingViewHolder(binding.root, adapter) { + override lateinit var setting: StringInputSetting override fun bind(item: SettingsItem) { - setting = item + setting = item as StringInputSetting binding.textSettingName.setText(item.nameId) if (item.descriptionId != 0) { binding.textSettingDescription.visibility = View.VISIBLE @@ -24,7 +23,7 @@ class StringInputViewHolder(val binding: ListItemSettingBinding, adapter: Settin binding.textSettingDescription.visibility = View.GONE } binding.textSettingValue.visibility = View.VISIBLE - binding.textSettingValue.text = setting.setting?.valueAsString + binding.textSettingValue.text = setting.selectedValue if (setting.isActive) { binding.textSettingName.alpha = 1f @@ -42,7 +41,7 @@ class StringInputViewHolder(val binding: ListItemSettingBinding, adapter: Settin adapter.onClickDisabledSetting(!setting.isEditable) return } - adapter.onStringInputClick((setting as StringInputSetting), bindingAdapterPosition) + adapter.onStringInputClick(setting, bindingAdapterPosition) } override fun onLongClick(clicked: View): Boolean { diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/viewholder/SubmenuViewHolder.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/viewholder/SubmenuViewHolder.kt index 461221763..8e2cc27e9 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/viewholder/SubmenuViewHolder.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/viewholder/SubmenuViewHolder.kt @@ -12,11 +12,11 @@ import org.citra.citra_emu.features.settings.model.view.SubmenuSetting import org.citra.citra_emu.features.settings.ui.SettingsAdapter class SubmenuViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) : - SettingViewHolder(binding.root, adapter) { - private lateinit var item: SubmenuSetting + SettingViewHolder(binding.root, adapter) { + override lateinit var setting: SubmenuSetting override fun bind(item: SettingsItem) { - this.item = item as SubmenuSetting + setting = item as SubmenuSetting if (item.iconId == 0) { binding.icon.visibility = View.GONE } else { @@ -40,7 +40,7 @@ class SubmenuViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAd } override fun onClick(clicked: View) { - adapter.onSubmenuClick(item) + adapter.onSubmenuClick(setting) } override fun onLongClick(clicked: View): Boolean { diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt index 72ebd8d0b..c92b7c6b7 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt @@ -12,9 +12,9 @@ import org.citra.citra_emu.features.settings.model.view.SwitchSetting import org.citra.citra_emu.features.settings.ui.SettingsAdapter class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter: SettingsAdapter) : - SettingViewHolder(binding.root, adapter) { + SettingViewHolder(binding.root, adapter) { - private lateinit var setting: SwitchSetting + override lateinit var setting: SwitchSetting override fun bind(item: SettingsItem) { setting = item as SwitchSetting diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/utils/SettingsFile.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/utils/SettingsFile.kt index a9e1d4743..2e03fc722 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/utils/SettingsFile.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/utils/SettingsFile.kt @@ -14,12 +14,9 @@ import org.citra.citra_emu.features.settings.model.BooleanSetting import org.citra.citra_emu.features.settings.model.FloatSetting import org.citra.citra_emu.features.settings.model.IntListSetting import org.citra.citra_emu.features.settings.model.IntSetting -import org.citra.citra_emu.features.settings.model.ScaledFloatSetting -import org.citra.citra_emu.features.settings.model.SettingSection -import org.citra.citra_emu.features.settings.model.Settings.SettingsSectionMap +import org.citra.citra_emu.features.settings.model.Settings import org.citra.citra_emu.features.settings.model.StringSetting import org.citra.citra_emu.features.settings.ui.SettingsActivityView -import org.citra.citra_emu.utils.BiMap import org.citra.citra_emu.utils.DirectoryInitialization.userDirectory import org.citra.citra_emu.utils.Log import org.ini4j.Wini @@ -27,7 +24,6 @@ import java.io.BufferedReader import java.io.FileNotFoundException import java.io.IOException import java.io.InputStreamReader -import java.util.TreeMap /** @@ -36,40 +32,46 @@ import java.util.TreeMap object SettingsFile { const val FILE_NAME_CONFIG = "config" - private var sectionsMap = BiMap() + private val allSettings: List> by lazy { + BooleanSetting.values().toList() + + IntSetting.values().toList() + + FloatSetting.values().toList() + + StringSetting.values().toList() + + IntListSetting.values().toList() + } + private fun findSettingByKey(key: String): AbstractSetting<*>? = + allSettings.firstOrNull { it.key == key } /** - * Reads a given .ini file from disk and returns it as a HashMap of Settings, themselves - * effectively a HashMap of key/value settings. If unsuccessful, outputs an error telling why it - * failed. + * Reads a given .ini file from disk and updates a instance of the Settings class appropriately * * @param ini The ini file to load the settings from + * @param settings The Settings instance to edit * @param isCustomGame * @param view The current view. * @return An Observable that emits a HashMap of the file's contents, then completes. */ fun readFile( ini: DocumentFile, + settings: Settings, isCustomGame: Boolean, view: SettingsActivityView? - ): HashMap { - val sections: HashMap = SettingsSectionMap() + ) { var reader: BufferedReader? = null try { val context: Context = CitraApplication.appContext val inputStream = context.contentResolver.openInputStream(ini.uri) reader = BufferedReader(InputStreamReader(inputStream)) - var current: SettingSection? = null + var currentSection: String? = null var line: String? while (reader.readLine().also { line = it } != null) { - if (line!!.startsWith("[") && line!!.endsWith("]")) { - current = sectionFromLine(line!!, isCustomGame) - sections[current.name] = current - } else if (current != null) { - val setting = settingFromLine(line!!) - if (setting != null) { - current.putSetting(setting) - } + if (line!!.startsWith("[") && line.endsWith("]")) { + currentSection = line.substring(1, line.length-1) + } else if (currentSection != null) { + val pair = parseLineToKeyValuePair(line) ?: continue + val (key, rawValue) = pair + val descriptor = findSettingByKey(key) ?: continue + loadSettingInto(settings, descriptor, rawValue, isCustomGame) } } } catch (e: FileNotFoundException) { @@ -87,102 +89,152 @@ object SettingsFile { } } } - return sections } - fun readFile(fileName: String, view: SettingsActivityView?): HashMap { - return readFile(getSettingsFile(fileName), false, view) - } - - fun readFile(fileName: String): HashMap = readFile(fileName, null) - /** - * Reads a given .ini file from disk and returns it as a HashMap of SettingSections, themselves - * effectively a HashMap of key/value settings. If unsuccessful, outputs an error telling why it - * failed. - * - * @param gameId the id of the game to load it's settings. - * @param view The current view. + * Load global settings from the config file into the settings instance */ - fun readCustomGameSettings( - gameId: String, - view: SettingsActivityView? - ): HashMap { - return readFile(getCustomGameSettingsFile(gameId), true, view) + fun loadSettings(settings: Settings, view: SettingsActivityView? = null) { + readFile(getSettingsFile(FILE_NAME_CONFIG),settings,false,view) } /** - * Saves a Settings HashMap to a given .ini file on disk. If unsuccessful, outputs an error + * Load global settings AND custom settings into the settings instance, sets gameId + */ + fun loadSettings(settings: Settings, gameId: String, view: SettingsActivityView? = null) { + settings.gameId = gameId + loadSettings(settings, view) + val file = findCustomGameSettingsFile(gameId) ?: return + readFile(file, settings, true, view) + } + + /** + * Uses the settings object to parse the raw string and store it in the correct map + */ + @Suppress("UNCHECKED_CAST") + private fun loadSettingInto( + settings: Settings, + setting: AbstractSetting, + rawValue: String, + isCustomGame: Boolean + ) { + val value = setting.valueFromString(rawValue) ?: return + if (isCustomGame) { + settings.setOverride(setting, value) + } else { + settings.setGlobal(setting, value) + } + } + + /** + * Saves a the global settings from a Settings instance + * to the global .ini file on disk. If unsuccessful, outputs an error * telling why it failed. * - * @param fileName The target filename without a path or extension. - * @param sections The HashMap containing the Settings we want to serialize. + * @param settings The Settings instance we are saving * @param view The current view. */ - fun saveFile( - fileName: String, - sections: TreeMap, - view: SettingsActivityView + fun saveGlobalFile( + settings: Settings, + view: SettingsActivityView? = null ) { - val ini = getSettingsFile(fileName) + val ini = getSettingsFile(FILE_NAME_CONFIG) try { val context: Context = CitraApplication.appContext val inputStream = context.contentResolver.openInputStream(ini.uri) val writer = Wini(inputStream) - val keySet: Set = sections.keys - for (key in keySet) { - val section = sections[key] - writeSection(writer, section!!) - } inputStream!!.close() + + for (setting in allSettings) { + val value = settings.getGlobal(setting) ?: continue + writeSettingToWini(writer, setting, value) + } + val outputStream = context.contentResolver.openOutputStream(ini.uri, "wt") writer.store(outputStream) outputStream!!.flush() outputStream.close() } catch (e: Exception) { - Log.error("[SettingsFile] File not found: $fileName.ini: ${e.message}") - view.showToastMessage( + Log.error("[SettingsFile] File not found: $FILE_NAME_CONFIG.ini: ${e.message}") + view?.showToastMessage( CitraApplication.appContext - .getString(R.string.error_saving, fileName, e.message), false + .getString(R.string.error_saving, FILE_NAME_CONFIG, e.message), false ) } } - fun saveFile( - fileName: String, - setting: AbstractSetting + /** + * Save the per-game overrides to a per-game config file + */ + + fun saveCustomFile( + settings: Settings, + view: SettingsActivityView? = null ) { - val ini = getSettingsFile(fileName) + if (!settings.isPerGame()) return + val ini = getOrCreateCustomGameSettingsFile(settings.gameId!!) try { val context: Context = CitraApplication.appContext + val writer = Wini() + + val overrides = settings.getAllOverrides() + for (descriptor in allSettings) { + val value = overrides[descriptor.key] ?: continue + writeSettingToWini(writer, descriptor, value) + } + + val outputStream = context.contentResolver.openOutputStream(ini.uri, "wt") + writer.store(outputStream) + outputStream?.flush() + outputStream?.close() + } catch (e: Exception) { + Log.error("[SettingsFile] Error saving custom file for ${settings.gameId}: ${e.message}") + view?.onSettingsFileNotFound() + } + } + + fun saveSetting(setting: AbstractSetting, settings: Settings) { + if (settings.hasOverride(setting)) { + // Currently a per-game setting, keep it that way + val ini = getOrCreateCustomGameSettingsFile(settings.gameId!!) + writeSingleSettingToFile(ini, setting, settings.get(setting)) + } else { + // Currently global, save to global file + val ini = getSettingsFile(FILE_NAME_CONFIG) + writeSingleSettingToFile(ini, setting, settings.getGlobal(setting)) + } + } + + private fun writeSingleSettingToFile(ini: DocumentFile, setting: AbstractSetting, value: T) { + try { + val context = CitraApplication.appContext val inputStream = context.contentResolver.openInputStream(ini.uri) - val writer = Wini(inputStream) - writer.put(setting.section, setting.key, setting.valueAsString) - inputStream!!.close() + val writer = if (inputStream != null) Wini(inputStream) else Wini() + inputStream?.close() + writeSettingToWini(writer, setting, value as Any) val outputStream = context.contentResolver.openOutputStream(ini.uri, "wt") writer.store(outputStream) outputStream!!.flush() outputStream.close() } catch (e: Exception) { - Log.error("[SettingsFile] File not found: $fileName.ini: ${e.message}") + Log.error("[SettingsFile] Error saving setting ${setting.key}: ${e.message}") } } + @Suppress("UNCHECKED_CAST") + private fun writeSettingToWini(writer: Wini, descriptor: AbstractSetting, value: Any) { + val typedValue = value as T + writer.put(descriptor.section, descriptor.key, descriptor.valueToString(typedValue)) + } - private fun mapSectionNameFromIni(generalSectionName: String): String? { - return if (sectionsMap.getForward(generalSectionName) != null) { - sectionsMap.getForward(generalSectionName) - } else { - generalSectionName - } + private fun parseLineToKeyValuePair(line: String): Pair? { + val splitLine = line.split("=".toRegex(), limit = 2) + if (splitLine.size != 2) return null + val key = splitLine[0].trim() + val value = splitLine[1].trim() + if (value.isEmpty()) return null + return Pair(key, value) } - private fun mapSectionNameToIni(generalSectionName: String): String { - return if (sectionsMap.getBackward(generalSectionName) != null) { - sectionsMap.getBackward(generalSectionName).toString() - } else { - generalSectionName - } - } fun getSettingsFile(fileName: String): DocumentFile { val root = DocumentFile.fromTreeUri(CitraApplication.appContext, Uri.parse(userDirectory)) @@ -190,96 +242,20 @@ object SettingsFile { return configDirectory!!.findFile("$fileName.ini")!! } - private fun getCustomGameSettingsFile(gameId: String): DocumentFile { + fun customExists(gameId: String): Boolean = findCustomGameSettingsFile(gameId) != null + + private fun findCustomGameSettingsFile(gameId: String): DocumentFile? { val root = DocumentFile.fromTreeUri(CitraApplication.appContext, Uri.parse(userDirectory)) - val configDirectory = root!!.findFile("GameSettings") - return configDirectory!!.findFile("$gameId.ini")!! + val configDir = root?.findFile("config") ?: return null + val customDir = configDir.findFile("custom") ?: return null + return customDir.findFile("$gameId.ini") } - private fun sectionFromLine(line: String, isCustomGame: Boolean): SettingSection { - var sectionName: String = line.substring(1, line.length - 1) - if (isCustomGame) { - sectionName = mapSectionNameToIni(sectionName) - } - return SettingSection(sectionName) - } - - /** - * For a line of text, determines what type of data is being represented, and returns - * a Setting object containing this data. - * - * @param line The line of text being parsed. - * @return A typed Setting containing the key/value contained in the line. - */ - private fun settingFromLine(line: String): AbstractSetting? { - val splitLine = line.split("=".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() - if (splitLine.size != 2) { - return null - } - val key = splitLine[0].trim { it <= ' ' } - val value = splitLine[1].trim { it <= ' ' } - if (value.isEmpty()) { - return null - } - - val booleanSetting = BooleanSetting.from(key) - if (booleanSetting != null) { - booleanSetting.boolean = value.toBoolean() - return booleanSetting - } - - val intSetting = IntSetting.from(key) - if (intSetting != null) { - try { - intSetting.int = value.toInt() - } catch (e: NumberFormatException) { - intSetting.int = if (value.toBoolean()) 1 else 0 - } - return intSetting - } - - val scaledFloatSetting = ScaledFloatSetting.from(key) - if (scaledFloatSetting != null) { - scaledFloatSetting.float = value.toFloat() * scaledFloatSetting.scale - return scaledFloatSetting - } - - val floatSetting = FloatSetting.from(key) - if (floatSetting != null) { - floatSetting.float = value.toFloat() - return floatSetting - } - - val stringSetting = StringSetting.from(key) - if (stringSetting != null) { - stringSetting.string = value - return stringSetting - } - - val intListSetting = IntListSetting.from(key) - if (intListSetting != null) { - intListSetting.list = value.split(", ").map { it.toInt() } - } - - return null - } - - /** - * Writes the contents of a Section HashMap to disk. - * - * @param parser A Wini pointed at a file on disk. - * @param section A section containing settings to be written to the file. - */ - private fun writeSection(parser: Wini, section: SettingSection) { - // Write the section header. - val header = section.name - - // Write this section's values. - val settings = section.settings - val keySet: Set = settings.keys - for (key in keySet) { - val setting = settings[key] - parser.put(header, setting!!.key, setting.valueAsString) - } + private fun getOrCreateCustomGameSettingsFile(gameId: String): DocumentFile { + val root = DocumentFile.fromTreeUri(CitraApplication.appContext, Uri.parse(userDirectory))!! + val configDir = root.findFile("config") ?: root.createDirectory("config")!! + val customDir = configDir.findFile("custom") ?: configDir.createDirectory("custom")!! + return customDir.findFile("$gameId.ini") + ?: customDir.createFile("*/*", "$gameId.ini")!! } } diff --git a/src/android/app/src/main/java/org/citra/citra_emu/fragments/EmulationFragment.kt b/src/android/app/src/main/java/org/citra/citra_emu/fragments/EmulationFragment.kt index 785438526..a1538076c 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/fragments/EmulationFragment.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/fragments/EmulationFragment.kt @@ -35,6 +35,7 @@ import android.widget.PopupMenu import android.widget.TextView import android.widget.Toast import androidx.activity.OnBackPressedCallback +import androidx.activity.result.contract.ActivityResultContracts import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.core.content.res.ResourcesCompat import androidx.core.graphics.Insets @@ -44,7 +45,6 @@ import androidx.drawerlayout.widget.DrawerLayout import androidx.drawerlayout.widget.DrawerLayout.DrawerListener import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels -import androidx.fragment.app.viewModels import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle @@ -70,7 +70,6 @@ import org.citra.citra_emu.display.ScreenLayout import org.citra.citra_emu.features.settings.model.BooleanSetting import org.citra.citra_emu.features.settings.model.IntSetting import org.citra.citra_emu.features.settings.model.Settings -import org.citra.citra_emu.features.settings.model.SettingsViewModel import org.citra.citra_emu.features.settings.ui.SettingsActivity import org.citra.citra_emu.features.settings.utils.SettingsFile import org.citra.citra_emu.model.Game @@ -78,7 +77,6 @@ import org.citra.citra_emu.utils.BuildUtil import org.citra.citra_emu.utils.DirectoryInitialization import org.citra.citra_emu.utils.DirectoryInitialization.DirectoryInitializationState import org.citra.citra_emu.utils.EmulationMenuSettings -import org.citra.citra_emu.utils.FileUtil import org.citra.citra_emu.utils.GameHelper import org.citra.citra_emu.utils.GameIconUtils import org.citra.citra_emu.utils.EmulationLifecycleUtil @@ -104,8 +102,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram private lateinit var screenAdjustmentUtil: ScreenAdjustmentUtil private val emulationViewModel: EmulationViewModel by activityViewModels() - private val settingsViewModel: SettingsViewModel by viewModels() - private val settings get() = settingsViewModel.settings private val onPause = Runnable{ togglePause() } private val onShutdown = Runnable{ emulationState.stop() } @@ -184,7 +180,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram retainInstance = true emulationState = EmulationState(game.path) emulationActivity = requireActivity() as EmulationActivity - screenAdjustmentUtil = ScreenAdjustmentUtil(requireContext(), requireActivity().windowManager, settings) + screenAdjustmentUtil = ScreenAdjustmentUtil(requireContext(), requireActivity().windowManager, Settings.settings) EmulationLifecycleUtil.addPauseResumeHook(onPause) EmulationLifecycleUtil.addShutdownHook(onShutdown) } @@ -211,7 +207,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram if (requireActivity().isFinishing) { return } - + binding.surfaceInputOverlay.initializeSettings(Settings.settings) binding.surfaceEmulation.holder.addCallback(this) binding.doneControlConfig.setOnClickListener { binding.doneControlConfig.visibility = View.GONE @@ -221,7 +217,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram // Show/hide the "Stats" overlay updateShowPerformanceOverlay() - val position = IntSetting.PERFORMANCE_OVERLAY_POSITION.int + val position = Settings.settings.get(IntSetting.PERFORMANCE_OVERLAY_POSITION) updateStatsPosition(position) binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED) @@ -382,10 +378,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram R.id.menu_settings -> { SettingsActivity.launch( requireContext(), - SettingsFile.FILE_NAME_CONFIG, - "" + SettingsFile.FILE_NAME_CONFIG, null ) - true } @@ -508,7 +502,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram emulationState.unpause() // If the overlay is enabled, we need to update the position if changed - val position = IntSetting.PERFORMANCE_OVERLAY_POSITION.int + val position = Settings.settings.get(IntSetting.PERFORMANCE_OVERLAY_POSITION) updateStatsPosition(position) binding.inGameMenu.menu.findItem(R.id.menu_emulation_pause)?.let { menuItem -> @@ -707,7 +701,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram popupMenu.menu.apply { findItem(R.id.menu_show_overlay).isChecked = EmulationMenuSettings.showOverlay findItem(R.id.menu_performance_overlay_show).isChecked = - BooleanSetting.PERF_OVERLAY_ENABLE.boolean + Settings.settings.get(BooleanSetting.PERF_OVERLAY_ENABLE) findItem(R.id.menu_haptic_feedback).isChecked = EmulationMenuSettings.hapticFeedback findItem(R.id.menu_emulation_joystick_rel_center).isChecked = EmulationMenuSettings.joystickRelCenter @@ -724,8 +718,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram } R.id.menu_performance_overlay_show -> { - BooleanSetting.PERF_OVERLAY_ENABLE.boolean = !BooleanSetting.PERF_OVERLAY_ENABLE.boolean - settings.saveSetting(BooleanSetting.PERF_OVERLAY_ENABLE, SettingsFile.FILE_NAME_CONFIG) + Settings.settings.update(BooleanSetting.PERF_OVERLAY_ENABLE, + Settings.settings.get(BooleanSetting.PERF_OVERLAY_ENABLE)) + SettingsFile.saveSetting(BooleanSetting.PERF_OVERLAY_ENABLE, Settings.settings) updateShowPerformanceOverlay() true } @@ -921,7 +916,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram popupMenu.menuInflater.inflate(R.menu.menu_landscape_screen_layout, popupMenu.menu) - val layoutOptionMenuItem = when (IntSetting.SCREEN_LAYOUT.int) { + val layoutOptionMenuItem = when (Settings.settings.get(IntSetting.SCREEN_LAYOUT)) { ScreenLayout.ORIGINAL.int -> R.id.menu_screen_layout_original @@ -993,7 +988,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram popupMenu.menuInflater.inflate(R.menu.menu_portrait_screen_layout, popupMenu.menu) - val layoutOptionMenuItem = when (IntSetting.PORTRAIT_SCREEN_LAYOUT.int) { + val layoutOptionMenuItem = when (Settings.settings.get(IntSetting.PORTRAIT_SCREEN_LAYOUT)) { PortraitScreenLayout.TOP_FULL_WIDTH.int -> R.id.menu_portrait_layout_top_full PortraitScreenLayout.ORIGINAL.int -> @@ -1260,7 +1255,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram perfStatsUpdateHandler.removeCallbacks(perfStatsUpdater!!) } - if (BooleanSetting.PERF_OVERLAY_ENABLE.boolean) { + if (Settings.settings.get(BooleanSetting.PERF_OVERLAY_ENABLE)) { val SYSTEM_FPS = 0 val FPS = 1 val SPEED = 2 @@ -1275,11 +1270,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram val perfStats = NativeLibrary.getPerfStats() val dividerString = "\u00A0\u2502 " if (perfStats[FPS] > 0) { - if (BooleanSetting.PERF_OVERLAY_SHOW_FPS.boolean) { + if (Settings.settings.get(BooleanSetting.PERF_OVERLAY_SHOW_FPS)) { sb.append(String.format("FPS:\u00A0%d", (perfStats[FPS] + 0.5).toInt())) } - if (BooleanSetting.PERF_OVERLAY_SHOW_FRAMETIME.boolean) { + if (Settings.settings.get(BooleanSetting.PERF_OVERLAY_SHOW_FRAMETIME)) { if (sb.isNotEmpty()) sb.append(dividerString) sb.append( String.format( @@ -1294,7 +1289,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram ) } - if (BooleanSetting.PERF_OVERLAY_SHOW_SPEED.boolean) { + if (Settings.settings.get(BooleanSetting.PERF_OVERLAY_SHOW_SPEED)) { if (sb.isNotEmpty()) sb.append(dividerString) sb.append( String.format( @@ -1304,14 +1299,14 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram ) } - if (BooleanSetting.PERF_OVERLAY_SHOW_APP_RAM_USAGE.boolean) { + if (Settings.settings.get(BooleanSetting.PERF_OVERLAY_SHOW_APP_RAM_USAGE)) { if (sb.isNotEmpty()) sb.append(dividerString) val appRamUsage = File("/proc/self/statm").readLines()[0].split(' ')[1].toLong() * 4096 / 1000000 sb.append("Process\u00A0RAM:\u00A0$appRamUsage\u00A0MB") } - if (BooleanSetting.PERF_OVERLAY_SHOW_AVAILABLE_RAM.boolean) { + if (Settings.settings.get(BooleanSetting.PERF_OVERLAY_SHOW_AVAILABLE_RAM)) { if (sb.isNotEmpty()) sb.append(dividerString) context?.let { ctx -> val activityManager = @@ -1324,14 +1319,14 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram } } - if (BooleanSetting.PERF_OVERLAY_SHOW_BATTERY_TEMP.boolean) { + if (Settings.settings.get(BooleanSetting.PERF_OVERLAY_SHOW_BATTERY_TEMP)) { if (sb.isNotEmpty()) sb.append(dividerString) val batteryTemp = getBatteryTemperature() val tempF = celsiusToFahrenheit(batteryTemp) sb.append(String.format("%.1f°C/%.1f°F", batteryTemp, tempF)) } - if (BooleanSetting.PERF_OVERLAY_BACKGROUND.boolean) { + if (Settings.settings.get(BooleanSetting.PERF_OVERLAY_BACKGROUND)) { binding.performanceOverlayShowText.setBackgroundResource(R.color.citra_transparent_black) } else { binding.performanceOverlayShowText.setBackgroundResource(0) diff --git a/src/android/app/src/main/java/org/citra/citra_emu/overlay/InputOverlay.kt b/src/android/app/src/main/java/org/citra/citra_emu/overlay/InputOverlay.kt index f7519bb81..c50b7d8f7 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/overlay/InputOverlay.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/overlay/InputOverlay.kt @@ -24,6 +24,7 @@ import androidx.preference.PreferenceManager import org.citra.citra_emu.CitraApplication import org.citra.citra_emu.NativeLibrary import org.citra.citra_emu.R +import org.citra.citra_emu.features.settings.model.Settings import org.citra.citra_emu.utils.EmulationMenuSettings import org.citra.citra_emu.utils.TurboHelper import java.lang.NullPointerException @@ -45,7 +46,7 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex private var buttonBeingConfigured: InputOverlayDrawableButton? = null private var dpadBeingConfigured: InputOverlayDrawableDpad? = null private var joystickBeingConfigured: InputOverlayDrawableJoystick? = null - private val settingsViewModel = NativeLibrary.sEmulationActivity.get()!!.settingsViewModel + private lateinit var settings: Settings // Stores the ID of the pointer that interacted with the 3DS touchscreen. private var touchscreenPointerId = -1 @@ -71,6 +72,10 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex requestFocus() } + fun initializeSettings(settings: Settings) { + this.settings = settings + } + override fun draw(canvas: Canvas) { super.draw(canvas) overlayButtons.forEach { it.draw(canvas) } @@ -173,7 +178,7 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex swapScreen() } else if (button.id == NativeLibrary.ButtonType.BUTTON_TURBO && button.status == NativeLibrary.ButtonState.PRESSED) { - TurboHelper.toggleTurbo(true) + TurboHelper.toggleTurbo(true, settings) } NativeLibrary.onGamePadEvent( diff --git a/src/android/app/src/main/java/org/citra/citra_emu/ui/main/MainActivity.kt b/src/android/app/src/main/java/org/citra/citra_emu/ui/main/MainActivity.kt index 91df60632..97f1eaf28 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/ui/main/MainActivity.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/ui/main/MainActivity.kt @@ -48,7 +48,6 @@ import org.citra.citra_emu.R import org.citra.citra_emu.contracts.OpenFileResultContract import org.citra.citra_emu.databinding.ActivityMainBinding import org.citra.citra_emu.features.settings.model.Settings -import org.citra.citra_emu.features.settings.model.SettingsViewModel import org.citra.citra_emu.features.settings.ui.SettingsActivity import org.citra.citra_emu.features.settings.utils.SettingsFile import org.citra.citra_emu.fragments.GrantMissingFilesystemPermissionFragment @@ -72,7 +71,6 @@ class MainActivity : AppCompatActivity(), ThemeProvider { private val homeViewModel: HomeViewModel by viewModels() private val gamesViewModel: GamesViewModel by viewModels() - private val settingsViewModel: SettingsViewModel by viewModels() override var themeId: Int = 0 @@ -95,7 +93,8 @@ class MainActivity : AppCompatActivity(), ThemeProvider { if (PermissionsHandler.hasWriteAccess(applicationContext) && DirectoryInitialization.areCitraDirectoriesReady() && !CitraDirectoryUtils.needToUpdateManually()) { - settingsViewModel.settings.loadSettings() + // load the global settings from the config file at program launch + SettingsFile.loadSettings(Settings.settings) } ThemeUtil.ThemeChangeListener(this) diff --git a/src/android/app/src/main/java/org/citra/citra_emu/utils/TurboHelper.kt b/src/android/app/src/main/java/org/citra/citra_emu/utils/TurboHelper.kt index 26ac69a75..4ffbcc72b 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/utils/TurboHelper.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/utils/TurboHelper.kt @@ -9,6 +9,7 @@ import org.citra.citra_emu.CitraApplication import org.citra.citra_emu.NativeLibrary import org.citra.citra_emu.R import org.citra.citra_emu.features.settings.model.IntSetting +import org.citra.citra_emu.features.settings.model.Settings object TurboHelper { private var turboSpeedEnabled = false @@ -17,12 +18,12 @@ object TurboHelper { return turboSpeedEnabled } - fun reloadTurbo(showToast: Boolean) { + fun reloadTurbo(showToast: Boolean, settings: Settings) { val context = CitraApplication.appContext val toastMessage: String if (turboSpeedEnabled) { - NativeLibrary.setTemporaryFrameLimit(IntSetting.TURBO_LIMIT.int.toDouble()) + NativeLibrary.setTemporaryFrameLimit(settings.get(IntSetting.TURBO_LIMIT).toDouble()) toastMessage = context.getString(R.string.turbo_enabled_toast) } else { NativeLibrary.disableTemporaryFrameLimit() @@ -34,12 +35,12 @@ object TurboHelper { } } - fun setTurboEnabled(state: Boolean, showToast: Boolean) { + fun setTurboEnabled(state: Boolean, showToast: Boolean, settings: Settings) { turboSpeedEnabled = state - reloadTurbo(showToast) + reloadTurbo(showToast, settings) } - fun toggleTurbo(showToast: Boolean) { - setTurboEnabled(!TurboHelper.isTurboSpeedEnabled(), showToast) + fun toggleTurbo(showToast: Boolean, settings: Settings) { + setTurboEnabled(!TurboHelper.isTurboSpeedEnabled(), showToast, settings) } } diff --git a/src/android/app/src/main/java/org/citra/citra_emu/viewmodel/EmulationViewModel.kt b/src/android/app/src/main/java/org/citra/citra_emu/viewmodel/EmulationViewModel.kt index 3a5571e9b..68458254e 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/viewmodel/EmulationViewModel.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/viewmodel/EmulationViewModel.kt @@ -1,4 +1,4 @@ -// Copyright 2023 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -7,9 +7,13 @@ package org.citra.citra_emu.viewmodel import androidx.lifecycle.ViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow +import org.citra.citra_emu.features.settings.model.Settings class EmulationViewModel : ViewModel() { val emulationStarted get() = _emulationStarted.asStateFlow() + // convenience shortcut for + val settings = Settings.settings + private val _emulationStarted = MutableStateFlow(false) val shaderProgress get() = _shaderProgress.asStateFlow() @@ -21,6 +25,7 @@ class EmulationViewModel : ViewModel() { val shaderMessage get() = _shaderMessage.asStateFlow() private val _shaderMessage = MutableStateFlow("") + fun setShaderProgress(progress: Int) { _shaderProgress.value = progress }