From 6c8f2b355ace41e33e8a1ad2f95d2816893a953b Mon Sep 17 00:00:00 2001 From: Charles Lombardo Date: Wed, 16 Aug 2023 02:36:56 -0400 Subject: android: Expose interface for getting settings from native code Completely removes code related to parsing the settings file on the java side. Now all settings are accessed via NativeConfig.kt and config.cpp has been modified to be closer to the core counterpart. Since the core currently uses QSettings, we can't remove reliance from Wini yet. This also includes simplifications to each settings interface to get closer to native code and prepare for per-game settings. --- .../main/java/org/yuzu/yuzu_emu/NativeLibrary.kt | 8 +- .../yuzu/yuzu_emu/activities/EmulationActivity.kt | 6 - .../settings/model/AbstractBooleanSetting.kt | 4 +- .../features/settings/model/AbstractByteSetting.kt | 10 + .../settings/model/AbstractFloatSetting.kt | 4 +- .../features/settings/model/AbstractIntSetting.kt | 4 +- .../features/settings/model/AbstractLongSetting.kt | 10 + .../features/settings/model/AbstractSetting.kt | 13 +- .../settings/model/AbstractShortSetting.kt | 10 + .../settings/model/AbstractStringSetting.kt | 4 +- .../features/settings/model/BooleanSetting.kt | 57 ++-- .../features/settings/model/ByteSetting.kt | 25 ++ .../features/settings/model/FloatSetting.kt | 32 +-- .../yuzu_emu/features/settings/model/IntSetting.kt | 151 ++-------- .../features/settings/model/LongSetting.kt | 25 ++ .../features/settings/model/SettingSection.kt | 37 --- .../yuzu_emu/features/settings/model/Settings.kt | 316 +++++++++------------ .../features/settings/model/SettingsViewModel.kt | 10 - .../features/settings/model/ShortSetting.kt | 25 ++ .../features/settings/model/StringSetting.kt | 36 +-- .../settings/model/view/DateTimeSetting.kt | 18 +- .../features/settings/model/view/SettingsItem.kt | 2 +- .../settings/model/view/SingleChoiceSetting.kt | 2 +- .../features/settings/model/view/SliderSetting.kt | 24 +- .../model/view/StringSingleChoiceSetting.kt | 2 +- .../features/settings/model/view/SwitchSetting.kt | 4 +- .../features/settings/ui/SettingsActivity.kt | 30 +- .../settings/ui/SettingsActivityPresenter.kt | 15 +- .../features/settings/ui/SettingsActivityView.kt | 19 -- .../features/settings/ui/SettingsAdapter.kt | 62 ++-- .../features/settings/ui/SettingsFragment.kt | 9 - .../settings/ui/SettingsFragmentPresenter.kt | 181 ++++++------ .../features/settings/ui/SettingsFragmentView.kt | 16 -- .../settings/ui/viewholder/DateTimeViewHolder.kt | 2 +- .../features/settings/utils/SettingsFile.kt | 255 ++--------------- .../java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt | 4 - .../src/main/java/org/yuzu/yuzu_emu/utils/BiMap.kt | 25 -- .../java/org/yuzu/yuzu_emu/utils/NativeConfig.kt | 31 ++ src/android/app/src/main/jni/CMakeLists.txt | 2 + src/android/app/src/main/jni/config.cpp | 31 +- src/android/app/src/main/jni/config.h | 24 +- src/android/app/src/main/jni/native.cpp | 28 -- src/android/app/src/main/jni/native_config.cpp | 224 +++++++++++++++ src/android/app/src/main/jni/uisettings.cpp | 10 + src/android/app/src/main/jni/uisettings.h | 29 ++ src/android/app/src/main/res/values/arrays.xml | 10 +- src/android/app/src/main/res/values/strings.xml | 1 + src/common/settings.cpp | 2 + src/common/settings_common.cpp | 1 + src/common/settings_common.h | 2 + 50 files changed, 876 insertions(+), 976 deletions(-) create mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractByteSetting.kt create mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractLongSetting.kt create mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractShortSetting.kt create mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ByteSetting.kt create mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/LongSetting.kt delete mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/SettingSection.kt delete mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/SettingsViewModel.kt create mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ShortSetting.kt delete mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/BiMap.kt create mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt create mode 100644 src/android/app/src/main/jni/native_config.cpp create mode 100644 src/android/app/src/main/jni/uisettings.cpp create mode 100644 src/android/app/src/main/jni/uisettings.h (limited to 'src') diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt index 9c32e044c..5a7cf4ed7 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt @@ -219,10 +219,6 @@ object NativeLibrary { external fun reloadSettings() - external fun getUserSetting(gameID: String?, Section: String?, Key: String?): String? - - external fun setUserSetting(gameID: String?, Section: String?, Key: String?, Value: String?) - external fun initGameIni(gameID: String?) /** @@ -413,14 +409,17 @@ object NativeLibrary { details.ifEmpty { emulationActivity.getString(R.string.system_archive_general) } ) } + CoreError.ErrorSavestate -> { title = emulationActivity.getString(R.string.save_load_error) message = details } + CoreError.ErrorUnknown -> { title = emulationActivity.getString(R.string.fatal_error) message = emulationActivity.getString(R.string.fatal_error_message) } + else -> { return true } @@ -454,6 +453,7 @@ object NativeLibrary { captionId = R.string.loader_error_video_core descriptionId = R.string.loader_error_video_core_description } + else -> { captionId = R.string.loader_error_encrypted descriptionId = R.string.loader_error_encrypted_roms_description diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt index 7461fb093..6f52a7a8d 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt @@ -28,7 +28,6 @@ import android.view.Surface import android.view.View import android.view.inputmethod.InputMethodManager import android.widget.Toast -import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity import androidx.core.view.WindowCompat import androidx.core.view.WindowInsetsCompat @@ -42,7 +41,6 @@ import org.yuzu.yuzu_emu.databinding.ActivityEmulationBinding import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting import org.yuzu.yuzu_emu.features.settings.model.IntSetting import org.yuzu.yuzu_emu.features.settings.model.Settings -import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel import org.yuzu.yuzu_emu.model.Game import org.yuzu.yuzu_emu.utils.ControllerMappingHelper import org.yuzu.yuzu_emu.utils.ForegroundService @@ -72,8 +70,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener { private val actionMute = "ACTION_EMULATOR_MUTE" private val actionUnmute = "ACTION_EMULATOR_UNMUTE" - private val settingsViewModel: SettingsViewModel by viewModels() - override fun onDestroy() { stopForegroundService(this) super.onDestroy() @@ -82,8 +78,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener { override fun onCreate(savedInstanceState: Bundle?) { ThemeHelper.setTheme(this) - settingsViewModel.settings.loadSettings() - super.onCreate(savedInstanceState) binding = ActivityEmulationBinding.inflate(layoutInflater) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractBooleanSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractBooleanSetting.kt index a6e9833ee..aeda8d222 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractBooleanSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractBooleanSetting.kt @@ -4,5 +4,7 @@ package org.yuzu.yuzu_emu.features.settings.model interface AbstractBooleanSetting : AbstractSetting { - var boolean: Boolean + val boolean: Boolean + + fun setBoolean(value: Boolean) } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractByteSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractByteSetting.kt new file mode 100644 index 000000000..606519ad8 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractByteSetting.kt @@ -0,0 +1,10 @@ +// SPDX-FileCopyrightText: 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.yuzu.yuzu_emu.features.settings.model + +interface AbstractByteSetting : AbstractSetting { + val byte: Byte + + fun setByte(value: Byte) +} diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractFloatSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractFloatSetting.kt index 6fe4bc263..974925eed 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractFloatSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractFloatSetting.kt @@ -4,5 +4,7 @@ package org.yuzu.yuzu_emu.features.settings.model interface AbstractFloatSetting : AbstractSetting { - var float: Float + val float: Float + + fun setFloat(value: Float) } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractIntSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractIntSetting.kt index 892b7dcfe..89b285b10 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractIntSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractIntSetting.kt @@ -4,5 +4,7 @@ package org.yuzu.yuzu_emu.features.settings.model interface AbstractIntSetting : AbstractSetting { - var int: Int + val int: Int + + fun setInt(value: Int) } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractLongSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractLongSetting.kt new file mode 100644 index 000000000..4873942db --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractLongSetting.kt @@ -0,0 +1,10 @@ +// SPDX-FileCopyrightText: 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.yuzu.yuzu_emu.features.settings.model + +interface AbstractLongSetting : AbstractSetting { + val long: Long + + fun setLong(value: Long) +} diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractSetting.kt index 258580209..7afed95ad 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractSetting.kt @@ -3,10 +3,17 @@ package org.yuzu.yuzu_emu.features.settings.model +import org.yuzu.yuzu_emu.utils.NativeConfig + interface AbstractSetting { val key: String? - val section: String? - val isRuntimeEditable: Boolean - val valueAsString: String + val category: Settings.Category val defaultValue: Any + val valueAsString: String + get() = "" + + val isRuntimeModifiable: Boolean + get() = NativeConfig.getIsRuntimeModifiable(key!!) + + fun reset() = run { } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractShortSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractShortSetting.kt new file mode 100644 index 000000000..91407ccbb --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractShortSetting.kt @@ -0,0 +1,10 @@ +// SPDX-FileCopyrightText: 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.yuzu.yuzu_emu.features.settings.model + +interface AbstractShortSetting : AbstractSetting { + val short: Short + + fun setShort(value: Short) +} diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractStringSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractStringSetting.kt index 0d02c5997..c8935cc48 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractStringSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractStringSetting.kt @@ -4,5 +4,7 @@ package org.yuzu.yuzu_emu.features.settings.model interface AbstractStringSetting : AbstractSetting { - var string: String + val string: String + + fun setString(value: String) } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt index d41933766..f7528642e 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt @@ -3,41 +3,34 @@ package org.yuzu.yuzu_emu.features.settings.model +import org.yuzu.yuzu_emu.utils.NativeConfig + enum class BooleanSetting( override val key: String, - override val section: String, - override val defaultValue: Boolean + override val category: Settings.Category ) : AbstractBooleanSetting { - CPU_DEBUG_MODE("cpu_debug_mode", Settings.SECTION_CPU, false), - FASTMEM("cpuopt_fastmem", Settings.SECTION_CPU, true), - FASTMEM_EXCLUSIVES("cpuopt_fastmem_exclusives", Settings.SECTION_CPU, true), - PICTURE_IN_PICTURE("picture_in_picture", Settings.SECTION_GENERAL, true), - USE_CUSTOM_RTC("custom_rtc_enabled", Settings.SECTION_SYSTEM, false); - - override var boolean: Boolean = defaultValue + CPU_DEBUG_MODE("cpu_debug_mode", Settings.Category.Cpu), + FASTMEM("cpuopt_fastmem", Settings.Category.Cpu), + FASTMEM_EXCLUSIVES("cpuopt_fastmem_exclusives", Settings.Category.Cpu), + RENDERER_USE_SPEED_LIMIT("use_speed_limit", Settings.Category.Core), + USE_DOCKED_MODE("use_docked_mode", Settings.Category.System), + RENDERER_USE_DISK_SHADER_CACHE("use_disk_shader_cache", Settings.Category.Renderer), + RENDERER_FORCE_MAX_CLOCK("force_max_clock", Settings.Category.Renderer), + RENDERER_ASYNCHRONOUS_SHADERS("use_asynchronous_shaders", Settings.Category.Renderer), + RENDERER_REACTIVE_FLUSHING("use_reactive_flushing", Settings.Category.Renderer), + RENDERER_DEBUG("debug", Settings.Category.Renderer), + PICTURE_IN_PICTURE("picture_in_picture", Settings.Category.Android), + USE_CUSTOM_RTC("custom_rtc_enabled", Settings.Category.System); + + override val boolean: Boolean + get() = NativeConfig.getBoolean(key, false) + + override fun setBoolean(value: Boolean) = NativeConfig.setBoolean(key, value) + + override val defaultValue: Boolean by lazy { NativeConfig.getBoolean(key, true) } override val valueAsString: String - get() = boolean.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 = listOf( - PICTURE_IN_PICTURE, - USE_CUSTOM_RTC - ) - - fun from(key: String): BooleanSetting? = - BooleanSetting.values().firstOrNull { it.key == key } - - fun clear() = BooleanSetting.values().forEach { it.boolean = it.defaultValue } - } + get() = if (boolean) "1" else "0" + + override fun reset() = NativeConfig.setBoolean(key, defaultValue) } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ByteSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ByteSetting.kt new file mode 100644 index 000000000..6ec0a765e --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ByteSetting.kt @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.yuzu.yuzu_emu.features.settings.model + +import org.yuzu.yuzu_emu.utils.NativeConfig + +enum class ByteSetting( + override val key: String, + override val category: Settings.Category +) : AbstractByteSetting { + AUDIO_VOLUME("volume", Settings.Category.Audio); + + override val byte: Byte + get() = NativeConfig.getByte(key, false) + + override fun setByte(value: Byte) = NativeConfig.setByte(key, value) + + override val defaultValue: Byte by lazy { NativeConfig.getByte(key, true) } + + override val valueAsString: String + get() = byte.toString() + + override fun reset() = NativeConfig.setByte(key, defaultValue) +} diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/FloatSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/FloatSetting.kt index e5545a916..0181d06f2 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/FloatSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/FloatSetting.kt @@ -3,34 +3,24 @@ package org.yuzu.yuzu_emu.features.settings.model +import org.yuzu.yuzu_emu.utils.NativeConfig + enum class FloatSetting( override val key: String, - override val section: String, - override val defaultValue: Float + override val category: Settings.Category ) : AbstractFloatSetting { // No float settings currently exist - EMPTY_SETTING("", "", 0f); - - override var float: Float = defaultValue + EMPTY_SETTING("", Settings.Category.UiGeneral); - override val valueAsString: String - get() = float.toString() + override val float: Float + get() = NativeConfig.getFloat(key, false) - override val isRuntimeEditable: Boolean - get() { - for (setting in NOT_RUNTIME_EDITABLE) { - if (setting == this) { - return false - } - } - return true - } + override fun setFloat(value: Float) = NativeConfig.setFloat(key, value) - companion object { - private val NOT_RUNTIME_EDITABLE = emptyList() + override val defaultValue: Float by lazy { NativeConfig.getFloat(key, true) } - fun from(key: String): FloatSetting? = FloatSetting.values().firstOrNull { it.key == key } + override val valueAsString: String + get() = float.toString() - fun clear() = FloatSetting.values().forEach { it.float = it.defaultValue } - } + override fun reset() = NativeConfig.setFloat(key, defaultValue) } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt index 4427a7d9d..a64757207 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt @@ -3,139 +3,34 @@ package org.yuzu.yuzu_emu.features.settings.model +import org.yuzu.yuzu_emu.utils.NativeConfig + enum class IntSetting( override val key: String, - override val section: String, - override val defaultValue: Int + override val category: Settings.Category ) : AbstractIntSetting { - RENDERER_USE_SPEED_LIMIT( - "use_speed_limit", - Settings.SECTION_RENDERER, - 1 - ), - USE_DOCKED_MODE( - "use_docked_mode", - Settings.SECTION_SYSTEM, - 0 - ), - RENDERER_USE_DISK_SHADER_CACHE( - "use_disk_shader_cache", - Settings.SECTION_RENDERER, - 1 - ), - RENDERER_FORCE_MAX_CLOCK( - "force_max_clock", - Settings.SECTION_RENDERER, - 0 - ), - RENDERER_ASYNCHRONOUS_SHADERS( - "use_asynchronous_shaders", - Settings.SECTION_RENDERER, - 0 - ), - RENDERER_REACTIVE_FLUSHING( - "use_reactive_flushing", - Settings.SECTION_RENDERER, - 0 - ), - RENDERER_DEBUG( - "debug", - Settings.SECTION_RENDERER, - 0 - ), - RENDERER_SPEED_LIMIT( - "speed_limit", - Settings.SECTION_RENDERER, - 100 - ), - CPU_ACCURACY( - "cpu_accuracy", - Settings.SECTION_CPU, - 0 - ), - REGION_INDEX( - "region_index", - Settings.SECTION_SYSTEM, - -1 - ), - LANGUAGE_INDEX( - "language_index", - Settings.SECTION_SYSTEM, - 1 - ), - RENDERER_BACKEND( - "backend", - Settings.SECTION_RENDERER, - 1 - ), - RENDERER_ACCURACY( - "gpu_accuracy", - Settings.SECTION_RENDERER, - 0 - ), - RENDERER_RESOLUTION( - "resolution_setup", - Settings.SECTION_RENDERER, - 2 - ), - RENDERER_VSYNC( - "use_vsync", - Settings.SECTION_RENDERER, - 0 - ), - RENDERER_SCALING_FILTER( - "scaling_filter", - Settings.SECTION_RENDERER, - 1 - ), - RENDERER_ANTI_ALIASING( - "anti_aliasing", - Settings.SECTION_RENDERER, - 0 - ), - RENDERER_SCREEN_LAYOUT( - "screen_layout", - Settings.SECTION_RENDERER, - Settings.LayoutOption_MobileLandscape - ), - RENDERER_ASPECT_RATIO( - "aspect_ratio", - Settings.SECTION_RENDERER, - 0 - ), - AUDIO_VOLUME( - "volume", - Settings.SECTION_AUDIO, - 100 - ); - - override var int: Int = defaultValue + CPU_ACCURACY("cpu_accuracy", Settings.Category.Cpu), + REGION_INDEX("region_index", Settings.Category.System), + LANGUAGE_INDEX("language_index", Settings.Category.System), + RENDERER_BACKEND("backend", Settings.Category.Renderer), + RENDERER_ACCURACY("gpu_accuracy", Settings.Category.Renderer), + RENDERER_RESOLUTION("resolution_setup", Settings.Category.Renderer), + RENDERER_VSYNC("use_vsync", Settings.Category.Renderer), + RENDERER_SCALING_FILTER("scaling_filter", Settings.Category.Renderer), + RENDERER_ANTI_ALIASING("anti_aliasing", Settings.Category.Renderer), + RENDERER_SCREEN_LAYOUT("screen_layout", Settings.Category.Android), + RENDERER_ASPECT_RATIO("aspect_ratio", Settings.Category.Renderer), + AUDIO_OUTPUT_ENGINE("output_engine", Settings.Category.Audio); + + override val int: Int + get() = NativeConfig.getInt(key, false) + + override fun setInt(value: Int) = NativeConfig.setInt(key, value) + + override val defaultValue: Int by lazy { NativeConfig.getInt(key, true) } override val valueAsString: String get() = int.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 = listOf( - RENDERER_USE_DISK_SHADER_CACHE, - RENDERER_ASYNCHRONOUS_SHADERS, - RENDERER_DEBUG, - RENDERER_BACKEND, - RENDERER_RESOLUTION, - RENDERER_VSYNC - ) - - fun from(key: String): IntSetting? = IntSetting.values().firstOrNull { it.key == key } - - fun clear() = IntSetting.values().forEach { it.int = it.defaultValue } - } + override fun reset() = NativeConfig.setInt(key, defaultValue) } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/LongSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/LongSetting.kt new file mode 100644 index 000000000..c526fc4cf --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/LongSetting.kt @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.yuzu.yuzu_emu.features.settings.model + +import org.yuzu.yuzu_emu.utils.NativeConfig + +enum class LongSetting( + override val key: String, + override val category: Settings.Category +) : AbstractLongSetting { + CUSTOM_RTC("custom_rtc", Settings.Category.System); + + override val long: Long + get() = NativeConfig.getLong(key, false) + + override fun setLong(value: Long) = NativeConfig.setLong(key, value) + + override val defaultValue: Long by lazy { NativeConfig.getLong(key, true) } + + override val valueAsString: String + get() = long.toString() + + override fun reset() = NativeConfig.setLong(key, defaultValue) +} diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/SettingSection.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/SettingSection.kt deleted file mode 100644 index 474f598a9..000000000 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/SettingSection.kt +++ /dev/null @@ -1,37 +0,0 @@ -// SPDX-FileCopyrightText: 2023 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.yuzu.yuzu_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/yuzu/yuzu_emu/features/settings/model/Settings.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt index a6251bafd..0702236e8 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt @@ -4,195 +4,151 @@ package org.yuzu.yuzu_emu.features.settings.model import android.text.TextUtils -import java.util.* +import android.widget.Toast import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.YuzuApplication -import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivityView import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile -class Settings { - private var gameId: String? = null +object Settings { + private val context get() = YuzuApplication.appContext - var isLoaded = false - - /** - * A HashMap, SettingSection> that constructs a new SettingSection instead of returning null - * when getting a key not already in the map - */ - 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) - } - } - - var sections: HashMap = SettingsSectionMap() - - fun getSection(sectionName: String): SettingSection? { - return sections[sectionName] - } - - val isEmpty: Boolean - get() = sections.isEmpty() - - fun loadSettings(view: SettingsActivityView? = null) { - sections = SettingsSectionMap() - loadYuzuSettings(view) - if (!TextUtils.isEmpty(gameId)) { - loadCustomGameSettings(gameId!!, view) - } - isLoaded = true - } - - private fun loadYuzuSettings(view: SettingsActivityView?) { - for ((fileName) in configFileSectionsMap) { - sections.putAll(SettingsFile.readFile(fileName, view)) - } - } - - private fun loadCustomGameSettings(gameId: String, view: SettingsActivityView?) { - // Custom game settings - mergeSections(SettingsFile.readCustomGameSettings(gameId, view)) - } - - 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 loadSettings(gameId: String, view: SettingsActivityView) { - this.gameId = gameId - loadSettings(view) - } - - fun saveSettings(view: SettingsActivityView) { + fun saveSettings(gameId: String = "") { if (TextUtils.isEmpty(gameId)) { - view.showToastMessage( - YuzuApplication.appContext.getString(R.string.ini_saved), - false - ) - - for ((fileName, sectionNames) in configFileSectionsMap) { - val iniSections = TreeMap() - for (section in sectionNames) { - iniSections[section] = sections[section]!! - } - - SettingsFile.saveFile(fileName, iniSections, view) - } + Toast.makeText( + context, + context.getString(R.string.ini_saved), + Toast.LENGTH_SHORT + ).show() + SettingsFile.saveFile(SettingsFile.FILE_NAME_CONFIG) } else { - // Custom game settings - view.showToastMessage( - YuzuApplication.appContext.getString(R.string.gameid_saved, gameId), - false - ) - - SettingsFile.saveCustomGameSettings(gameId, sections) + // TODO: Save custom game settings + Toast.makeText( + context, + context.getString(R.string.gameid_saved, gameId), + Toast.LENGTH_SHORT + ).show() } } - companion object { - const val SECTION_GENERAL = "General" - const val SECTION_SYSTEM = "System" - const val SECTION_RENDERER = "Renderer" - const val SECTION_AUDIO = "Audio" - const val SECTION_CPU = "Cpu" - const val SECTION_THEME = "Theme" - const val SECTION_DEBUG = "Debug" - - const val PREF_MEMORY_WARNING_SHOWN = "MemoryWarningShown" - - const val PREF_OVERLAY_VERSION = "OverlayVersion" - const val PREF_LANDSCAPE_OVERLAY_VERSION = "LandscapeOverlayVersion" - const val PREF_PORTRAIT_OVERLAY_VERSION = "PortraitOverlayVersion" - const val PREF_FOLDABLE_OVERLAY_VERSION = "FoldableOverlayVersion" - val overlayLayoutPrefs = listOf( - PREF_LANDSCAPE_OVERLAY_VERSION, - PREF_PORTRAIT_OVERLAY_VERSION, - PREF_FOLDABLE_OVERLAY_VERSION - ) - - const val PREF_CONTROL_SCALE = "controlScale" - const val PREF_CONTROL_OPACITY = "controlOpacity" - const val PREF_TOUCH_ENABLED = "isTouchEnabled" - const val PREF_BUTTON_A = "buttonToggle0" - const val PREF_BUTTON_B = "buttonToggle1" - const val PREF_BUTTON_X = "buttonToggle2" - const val PREF_BUTTON_Y = "buttonToggle3" - const val PREF_BUTTON_L = "buttonToggle4" - const val PREF_BUTTON_R = "buttonToggle5" - const val PREF_BUTTON_ZL = "buttonToggle6" - const val PREF_BUTTON_ZR = "buttonToggle7" - const val PREF_BUTTON_PLUS = "buttonToggle8" - const val PREF_BUTTON_MINUS = "buttonToggle9" - const val PREF_BUTTON_DPAD = "buttonToggle10" - const val PREF_STICK_L = "buttonToggle11" - const val PREF_STICK_R = "buttonToggle12" - const val PREF_BUTTON_STICK_L = "buttonToggle13" - const val PREF_BUTTON_STICK_R = "buttonToggle14" - const val PREF_BUTTON_HOME = "buttonToggle15" - const val PREF_BUTTON_SCREENSHOT = "buttonToggle16" - - const val PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER = "EmulationMenuSettings_JoystickRelCenter" - const val PREF_MENU_SETTINGS_DPAD_SLIDE = "EmulationMenuSettings_DpadSlideEnable" - const val PREF_MENU_SETTINGS_HAPTICS = "EmulationMenuSettings_Haptics" - const val PREF_MENU_SETTINGS_SHOW_FPS = "EmulationMenuSettings_ShowFps" - const val PREF_MENU_SETTINGS_SHOW_OVERLAY = "EmulationMenuSettings_ShowOverlay" - - const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch" - const val PREF_THEME = "Theme" - const val PREF_THEME_MODE = "ThemeMode" - const val PREF_BLACK_BACKGROUNDS = "BlackBackgrounds" - - private val configFileSectionsMap: MutableMap> = HashMap() - - val overlayPreferences = listOf( - PREF_OVERLAY_VERSION, - PREF_CONTROL_SCALE, - PREF_CONTROL_OPACITY, - PREF_TOUCH_ENABLED, - PREF_BUTTON_A, - PREF_BUTTON_B, - PREF_BUTTON_X, - PREF_BUTTON_Y, - PREF_BUTTON_L, - PREF_BUTTON_R, - PREF_BUTTON_ZL, - PREF_BUTTON_ZR, - PREF_BUTTON_PLUS, - PREF_BUTTON_MINUS, - PREF_BUTTON_DPAD, - PREF_STICK_L, - PREF_STICK_R, - PREF_BUTTON_HOME, - PREF_BUTTON_SCREENSHOT, - PREF_BUTTON_STICK_L, - PREF_BUTTON_STICK_R - ) - - const val LayoutOption_Unspecified = 0 - const val LayoutOption_MobilePortrait = 4 - const val LayoutOption_MobileLandscape = 5 - - init { - configFileSectionsMap[SettingsFile.FILE_NAME_CONFIG] = - listOf( - SECTION_GENERAL, - SECTION_SYSTEM, - SECTION_RENDERER, - SECTION_AUDIO, - SECTION_CPU - ) - } + enum class Category { + Android, + Audio, + Core, + Cpu, + CpuDebug, + CpuUnsafe, + Renderer, + RendererAdvanced, + RendererDebug, + System, + SystemAudio, + DataStorage, + Debugging, + DebuggingGraphics, + Miscellaneous, + Network, + WebService, + AddOns, + Controls, + Ui, + UiGeneral, + UiLayout, + UiGameList, + Screenshots, + Shortcuts, + Multiplayer, + Services, + Paths, + MaxEnum } + + val settingsList = listOf( + *BooleanSetting.values(), + *ByteSetting.values(), + *ShortSetting.values(), + *IntSetting.values(), + *FloatSetting.values(), + *LongSetting.values(), + *StringSetting.values() + ) + + const val SECTION_GENERAL = "General" + const val SECTION_SYSTEM = "System" + const val SECTION_RENDERER = "Renderer" + const val SECTION_AUDIO = "Audio" + const val SECTION_CPU = "Cpu" + const val SECTION_THEME = "Theme" + const val SECTION_DEBUG = "Debug" + + const val PREF_MEMORY_WARNING_SHOWN = "MemoryWarningShown" + + const val PREF_OVERLAY_VERSION = "OverlayVersion" + const val PREF_LANDSCAPE_OVERLAY_VERSION = "LandscapeOverlayVersion" + const val PREF_PORTRAIT_OVERLAY_VERSION = "PortraitOverlayVersion" + const val PREF_FOLDABLE_OVERLAY_VERSION = "FoldableOverlayVersion" + val overlayLayoutPrefs = listOf( + PREF_LANDSCAPE_OVERLAY_VERSION, + PREF_PORTRAIT_OVERLAY_VERSION, + PREF_FOLDABLE_OVERLAY_VERSION + ) + + const val PREF_CONTROL_SCALE = "controlScale" + const val PREF_CONTROL_OPACITY = "controlOpacity" + const val PREF_TOUCH_ENABLED = "isTouchEnabled" + const val PREF_BUTTON_A = "buttonToggle0" + const val PREF_BUTTON_B = "buttonToggle1" + const val PREF_BUTTON_X = "buttonToggle2" + const val PREF_BUTTON_Y = "buttonToggle3" + const val PREF_BUTTON_L = "buttonToggle4" + const val PREF_BUTTON_R = "buttonToggle5" + const val PREF_BUTTON_ZL = "buttonToggle6" + const val PREF_BUTTON_ZR = "buttonToggle7" + const val PREF_BUTTON_PLUS = "buttonToggle8" + const val PREF_BUTTON_MINUS = "buttonToggle9" + const val PREF_BUTTON_DPAD = "buttonToggle10" + const val PREF_STICK_L = "buttonToggle11" + const val PREF_STICK_R = "buttonToggle12" + const val PREF_BUTTON_STICK_L = "buttonToggle13" + const val PREF_BUTTON_STICK_R = "buttonToggle14" + const val PREF_BUTTON_HOME = "buttonToggle15" + const val PREF_BUTTON_SCREENSHOT = "buttonToggle16" + + const val PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER = "EmulationMenuSettings_JoystickRelCenter" + const val PREF_MENU_SETTINGS_DPAD_SLIDE = "EmulationMenuSettings_DpadSlideEnable" + const val PREF_MENU_SETTINGS_HAPTICS = "EmulationMenuSettings_Haptics" + const val PREF_MENU_SETTINGS_SHOW_FPS = "EmulationMenuSettings_ShowFps" + const val PREF_MENU_SETTINGS_SHOW_OVERLAY = "EmulationMenuSettings_ShowOverlay" + + const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch" + const val PREF_THEME = "Theme" + const val PREF_THEME_MODE = "ThemeMode" + const val PREF_BLACK_BACKGROUNDS = "BlackBackgrounds" + + val overlayPreferences = listOf( + PREF_OVERLAY_VERSION, + PREF_CONTROL_SCALE, + PREF_CONTROL_OPACITY, + PREF_TOUCH_ENABLED, + PREF_BUTTON_A, + PREF_BUTTON_B, + PREF_BUTTON_X, + PREF_BUTTON_Y, + PREF_BUTTON_L, + PREF_BUTTON_R, + PREF_BUTTON_ZL, + PREF_BUTTON_ZR, + PREF_BUTTON_PLUS, + PREF_BUTTON_MINUS, + PREF_BUTTON_DPAD, + PREF_STICK_L, + PREF_STICK_R, + PREF_BUTTON_HOME, + PREF_BUTTON_SCREENSHOT, + PREF_BUTTON_STICK_L, + PREF_BUTTON_STICK_R + ) + + const val LayoutOption_Unspecified = 0 + const val LayoutOption_MobilePortrait = 4 + const val LayoutOption_MobileLandscape = 5 } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/SettingsViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/SettingsViewModel.kt deleted file mode 100644 index bd9233d62..000000000 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/SettingsViewModel.kt +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-FileCopyrightText: 2023 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.yuzu.yuzu_emu.features.settings.model - -import androidx.lifecycle.ViewModel - -class SettingsViewModel : ViewModel() { - val settings = Settings() -} diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ShortSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ShortSetting.kt new file mode 100644 index 000000000..c9a0c664c --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ShortSetting.kt @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.yuzu.yuzu_emu.features.settings.model + +import org.yuzu.yuzu_emu.utils.NativeConfig + +enum class ShortSetting( + override val key: String, + override val category: Settings.Category +) : AbstractShortSetting { + RENDERER_SPEED_LIMIT("speed_limit", Settings.Category.Core); + + override val short: Short + get() = NativeConfig.getShort(key, false) + + override fun setShort(value: Short) = NativeConfig.setShort(key, value) + + override val defaultValue: Short by lazy { NativeConfig.getShort(key, true) } + + override val valueAsString: String + get() = short.toString() + + override fun reset() = NativeConfig.setShort(key, defaultValue) +} diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/StringSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/StringSetting.kt index 6621289fd..9bb3e66d4 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/StringSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/StringSetting.kt @@ -3,36 +3,24 @@ package org.yuzu.yuzu_emu.features.settings.model +import org.yuzu.yuzu_emu.utils.NativeConfig + enum class StringSetting( override val key: String, - override val section: String, - override val defaultValue: String + override val category: Settings.Category ) : AbstractStringSetting { - AUDIO_OUTPUT_ENGINE("output_engine", Settings.SECTION_AUDIO, "auto"), - CUSTOM_RTC("custom_rtc", Settings.SECTION_SYSTEM, "0"); + // No string settings currently exist + EMPTY_SETTING("", Settings.Category.UiGeneral); + + override val string: String + get() = NativeConfig.getString(key, false) + + override fun setString(value: String) = NativeConfig.setString(key, value) - override var string: String = defaultValue + override val defaultValue: String by lazy { NativeConfig.getString(key, true) } override val valueAsString: String get() = string - 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 = listOf( - CUSTOM_RTC - ) - - fun from(key: String): StringSetting? = StringSetting.values().firstOrNull { it.key == key } - - fun clear() = StringSetting.values().forEach { it.string = it.defaultValue } - } + override fun reset() = NativeConfig.setString(key, defaultValue) } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/DateTimeSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/DateTimeSetting.kt index bc0bf7788..7c858916e 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/DateTimeSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/DateTimeSetting.kt @@ -3,29 +3,29 @@ package org.yuzu.yuzu_emu.features.settings.model.view +import org.yuzu.yuzu_emu.features.settings.model.AbstractLongSetting import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting -import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting class DateTimeSetting( setting: AbstractSetting?, titleId: Int, descriptionId: Int, val key: String? = null, - private val defaultValue: String? = null + private val defaultValue: Long? = null ) : SettingsItem(setting, titleId, descriptionId) { override val type = TYPE_DATETIME_SETTING - val value: String + val value: Long get() = if (setting != null) { - val setting = setting as AbstractStringSetting - setting.string + val setting = setting as AbstractLongSetting + setting.long } else { defaultValue!! } - fun setSelectedValue(datetime: String): AbstractStringSetting { - val stringSetting = setting as AbstractStringSetting - stringSetting.string = datetime - return stringSetting + fun setSelectedValue(datetime: Long): AbstractLongSetting { + val longSetting = setting as AbstractLongSetting + longSetting.setLong(datetime) + return longSetting } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt index 07520849e..a6cba977c 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt @@ -23,7 +23,7 @@ abstract class SettingsItem( val isEditable: Boolean get() { if (!NativeLibrary.isRunning()) return true - return setting?.isRuntimeEditable ?: false + return setting?.isRuntimeModifiable ?: false } companion object { diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SingleChoiceSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SingleChoiceSetting.kt index 7306ec458..b6a8c4612 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SingleChoiceSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SingleChoiceSetting.kt @@ -33,7 +33,7 @@ class SingleChoiceSetting( */ fun setSelectedValue(selection: Int): AbstractIntSetting { val intSetting = setting as AbstractIntSetting - intSetting.int = selection + intSetting.setInt(selection) return intSetting } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SliderSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SliderSetting.kt index 92d0167ae..e71a29e35 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SliderSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SliderSetting.kt @@ -3,10 +3,12 @@ package org.yuzu.yuzu_emu.features.settings.model.view +import org.yuzu.yuzu_emu.features.settings.model.AbstractByteSetting import kotlin.math.roundToInt import org.yuzu.yuzu_emu.features.settings.model.AbstractFloatSetting import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting +import org.yuzu.yuzu_emu.features.settings.model.AbstractShortSetting import org.yuzu.yuzu_emu.utils.Log class SliderSetting( @@ -17,14 +19,16 @@ class SliderSetting( val max: Int, val units: String, val key: String? = null, - val defaultValue: Int? = null + val defaultValue: Any? = null ) : SettingsItem(setting, titleId, descriptionId) { override val type = TYPE_SLIDER - val selectedValue: Int + val selectedValue: Any get() { val setting = setting ?: return defaultValue!! return when (setting) { + is AbstractByteSetting -> setting.byte.toInt() + is AbstractShortSetting -> setting.short.toInt() is AbstractIntSetting -> setting.int is AbstractFloatSetting -> setting.float.roundToInt() else -> { @@ -43,7 +47,7 @@ class SliderSetting( */ fun setSelectedValue(selection: Int): AbstractIntSetting { val intSetting = setting as AbstractIntSetting - intSetting.int = selection + intSetting.setInt(selection) return intSetting } @@ -56,7 +60,19 @@ class SliderSetting( */ fun setSelectedValue(selection: Float): AbstractFloatSetting { val floatSetting = setting as AbstractFloatSetting - floatSetting.float = selection + floatSetting.setFloat(selection) return floatSetting } + + fun setSelectedValue(selection: Short): AbstractShortSetting { + val shortSetting = setting as AbstractShortSetting + shortSetting.setShort(selection) + return shortSetting + } + + fun setSelectedValue(selection: Byte): AbstractByteSetting { + val byteSetting = setting as AbstractByteSetting + byteSetting.setByte(selection) + return byteSetting + } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/StringSingleChoiceSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/StringSingleChoiceSetting.kt index 3b6731dcd..2195641e3 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/StringSingleChoiceSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/StringSingleChoiceSetting.kt @@ -53,7 +53,7 @@ class StringSingleChoiceSetting( */ fun setSelectedValue(selection: String): AbstractStringSetting { val stringSetting = setting as AbstractStringSetting - stringSetting.string = selection + stringSetting.setString(selection) return stringSetting } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SwitchSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SwitchSetting.kt index 90b198718..4ed8070e5 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SwitchSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SwitchSetting.kt @@ -49,14 +49,14 @@ class SwitchSetting( // Try integer setting try { val setting = setting as AbstractIntSetting - setting.int = if (checked) 1 else 0 + setting.setInt(if (checked) 1 else 0) return setting } catch (_: ClassCastException) { } // Try boolean setting val setting = setting as AbstractBooleanSetting - setting.boolean = checked + setting.setBoolean(checked) return setting } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt index e6fffc832..733a53c8c 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt @@ -21,12 +21,7 @@ import com.google.android.material.color.MaterialColors import java.io.IOException import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding -import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting -import org.yuzu.yuzu_emu.features.settings.model.FloatSetting -import org.yuzu.yuzu_emu.features.settings.model.IntSetting import org.yuzu.yuzu_emu.features.settings.model.Settings -import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel -import org.yuzu.yuzu_emu.features.settings.model.StringSetting import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile import org.yuzu.yuzu_emu.utils.* @@ -35,10 +30,6 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView { private lateinit var binding: ActivitySettingsBinding - private val settingsViewModel: SettingsViewModel by viewModels() - - override val settings: Settings get() = settingsViewModel.settings - override fun onCreate(savedInstanceState: Bundle?) { ThemeHelper.setTheme(this) @@ -171,14 +162,6 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView { fragment?.loadSettingsList() } - override fun showToastMessage(message: String, is_long: Boolean) { - Toast.makeText( - this, - message, - if (is_long) Toast.LENGTH_LONG else Toast.LENGTH_SHORT - ).show() - } - override fun onSettingChanged() { presenter.onSettingChanged() } @@ -187,19 +170,18 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView { // Prevents saving to a non-existent settings file presenter.onSettingsReset() - // Reset the static memory representation of each setting - BooleanSetting.clear() - FloatSetting.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()) { throw IOException("Failed to delete $settingsFile") } + Settings.settingsList.forEach { it.reset() } - showToastMessage(getString(R.string.settings_reset), true) + Toast.makeText( + applicationContext, + getString(R.string.settings_reset), + Toast.LENGTH_LONG + ).show() finish() } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityPresenter.kt index 93e677b21..fdbad32bf 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityPresenter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityPresenter.kt @@ -5,7 +5,6 @@ package org.yuzu.yuzu_emu.features.settings.ui import android.content.Context import android.os.Bundle -import android.text.TextUtils import java.io.File import org.yuzu.yuzu_emu.NativeLibrary import org.yuzu.yuzu_emu.features.settings.model.Settings @@ -14,8 +13,6 @@ import org.yuzu.yuzu_emu.utils.DirectoryInitialization import org.yuzu.yuzu_emu.utils.Log class SettingsActivityPresenter(private val activityView: SettingsActivityView) { - val settings: Settings get() = activityView.settings - private var shouldSave = false private lateinit var menuTag: String private lateinit var gameId: String @@ -33,13 +30,7 @@ class SettingsActivityPresenter(private val activityView: SettingsActivityView) } private fun loadSettingsUI() { - if (!settings.isLoaded) { - if (!TextUtils.isEmpty(gameId)) { - settings.loadSettings(gameId, activityView) - } else { - settings.loadSettings(activityView) - } - } + // TODO: Load custom settings contextually activityView.showSettingsFragment(menuTag, false, gameId) activityView.onSettingsFileLoaded() } @@ -67,9 +58,9 @@ 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) + Settings.saveSettings() + NativeLibrary.reloadSettings() } - NativeLibrary.reloadSettings() } fun onSettingChanged() { diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityView.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityView.kt index c186fc388..07a58b4ea 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityView.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityView.kt @@ -3,8 +3,6 @@ package org.yuzu.yuzu_emu.features.settings.ui -import org.yuzu.yuzu_emu.features.settings.model.Settings - /** * Abstraction for the Activity that manages SettingsFragments. */ @@ -17,15 +15,6 @@ interface SettingsActivityView { */ fun showSettingsFragment(menuTag: String, addToStack: Boolean, gameId: String) - /** - * Called by a contained Fragment to get access to the Setting HashMap - * loaded from disk, so that each Fragment doesn't need to perform its own - * read operation. - * - * @return A HashMap of Settings. - */ - val settings: Settings - /** * Called when a load operation completes. */ @@ -36,14 +25,6 @@ interface SettingsActivityView { */ fun onSettingsFileNotFound() - /** - * Display a popup text message on screen. - * - * @param message The contents of the onscreen message. - * @param is_long Whether this should be a long Toast or short one. - */ - fun showToastMessage(message: String, is_long: Boolean) - /** * End the activity. */ diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt index 9711e2c51..e2e8d8bec 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt @@ -24,12 +24,10 @@ import org.yuzu.yuzu_emu.databinding.DialogSliderBinding import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding import org.yuzu.yuzu_emu.databinding.ListItemSettingSwitchBinding import org.yuzu.yuzu_emu.databinding.ListItemSettingsHeaderBinding -import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting -import org.yuzu.yuzu_emu.features.settings.model.AbstractFloatSetting -import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting -import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting +import org.yuzu.yuzu_emu.features.settings.model.ByteSetting import org.yuzu.yuzu_emu.features.settings.model.FloatSetting +import org.yuzu.yuzu_emu.features.settings.model.ShortSetting import org.yuzu.yuzu_emu.features.settings.model.view.* import org.yuzu.yuzu_emu.features.settings.ui.viewholder.* @@ -115,8 +113,7 @@ class SettingsAdapter( } fun onBooleanClick(item: SwitchSetting, position: Int, checked: Boolean) { - val setting = item.setChecked(checked) - fragmentView.putSetting(setting) + item.setChecked(checked) fragmentView.onSettingChanged() } @@ -150,7 +147,7 @@ class SettingsAdapter( fun onDateTimeClick(item: DateTimeSetting, position: Int) { clickedItem = item clickedPosition = position - val storedTime = java.lang.Long.decode(item.value) * 1000 + val storedTime = item.value * 1000 // Helper to extract hour and minute from epoch time val calendar: Calendar = Calendar.getInstance() @@ -183,13 +180,11 @@ class SettingsAdapter( var epochTime: Long = datePicker.selection!! / 1000 epochTime += timePicker.hour.toLong() * 60 * 60 epochTime += timePicker.minute.toLong() * 60 - val rtcString = epochTime.toString() - if (item.value != rtcString) { + if (item.value != epochTime) { fragmentView.onSettingChanged() + notifyItemChanged(clickedPosition) + item.setSelectedValue(epochTime) } - notifyItemChanged(clickedPosition) - val setting = item.setSelectedValue(rtcString) - fragmentView.putSetting(setting) clickedItem = null } datePicker.show( @@ -201,7 +196,7 @@ class SettingsAdapter( fun onSliderClick(item: SliderSetting, position: Int) { clickedItem = item clickedPosition = position - sliderProgress = item.selectedValue + sliderProgress = item.selectedValue as Int val inflater = LayoutInflater.from(context) val sliderBinding = DialogSliderBinding.inflate(inflater) @@ -249,8 +244,7 @@ class SettingsAdapter( } // Get the backing Setting, which may be null (if for example it was missing from the file) - val setting = scSetting.setSelectedValue(value) - fragmentView.putSetting(setting) + scSetting.setSelectedValue(value) closeDialog() } @@ -258,8 +252,7 @@ class SettingsAdapter( val scSetting = clickedItem as StringSingleChoiceSetting val value = scSetting.getValueAt(which) if (scSetting.selectedValue != value) fragmentView.onSettingChanged() - val setting = scSetting.setSelectedValue(value!!) - fragmentView.putSetting(setting) + scSetting.setSelectedValue(value!!) closeDialog() } @@ -268,13 +261,25 @@ class SettingsAdapter( if (sliderSetting.selectedValue != sliderProgress) { fragmentView.onSettingChanged() } - if (sliderSetting.setting is FloatSetting) { - val value = sliderProgress.toFloat() - val setting = sliderSetting.setSelectedValue(value) - fragmentView.putSetting(setting) - } else { - val setting = sliderSetting.setSelectedValue(sliderProgress) - fragmentView.putSetting(setting) + when (sliderSetting.setting) { + is ByteSetting -> { + val value = sliderProgress.toByte() + sliderSetting.setSelectedValue(value) + } + + is ShortSetting -> { + val value = sliderProgress.toShort() + sliderSetting.setSelectedValue(value) + } + + is FloatSetting -> { + val value = sliderProgress.toFloat() + sliderSetting.setSelectedValue(value) + } + + else -> { + sliderSetting.setSelectedValue(sliderProgress) + } } closeDialog() } @@ -286,13 +291,8 @@ class SettingsAdapter( fun onLongClick(setting: AbstractSetting, position: Int): Boolean { MaterialAlertDialogBuilder(context) .setMessage(R.string.reset_setting_confirmation) - .setPositiveButton(android.R.string.ok) { dialog: DialogInterface, which: Int -> - when (setting) { - is AbstractBooleanSetting -> setting.boolean = setting.defaultValue as Boolean - is AbstractFloatSetting -> setting.float = setting.defaultValue as Float - is AbstractIntSetting -> setting.int = setting.defaultValue as Int - is AbstractStringSetting -> setting.string = setting.defaultValue as String - } + .setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int -> + setting.reset() notifyItemChanged(position) fragmentView.onSettingChanged() } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt index 70a74c4dd..dc1bf6eb1 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt @@ -15,7 +15,6 @@ import androidx.fragment.app.Fragment import androidx.recyclerview.widget.LinearLayoutManager import com.google.android.material.divider.MaterialDividerItemDecoration import org.yuzu.yuzu_emu.databinding.FragmentSettingsBinding -import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem class SettingsFragment : Fragment(), SettingsFragmentView { @@ -89,14 +88,6 @@ class SettingsFragment : Fragment(), SettingsFragmentView { ) } - override fun showToastMessage(message: String?, is_long: Boolean) { - 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/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt index 59c1d9d54..2bab9e542 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt @@ -6,16 +6,18 @@ package org.yuzu.yuzu_emu.features.settings.ui import android.content.SharedPreferences import android.os.Build import android.text.TextUtils +import android.widget.Toast import androidx.preference.PreferenceManager import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.YuzuApplication import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting -import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting +import org.yuzu.yuzu_emu.features.settings.model.ByteSetting import org.yuzu.yuzu_emu.features.settings.model.IntSetting +import org.yuzu.yuzu_emu.features.settings.model.LongSetting import org.yuzu.yuzu_emu.features.settings.model.Settings -import org.yuzu.yuzu_emu.features.settings.model.StringSetting +import org.yuzu.yuzu_emu.features.settings.model.ShortSetting import org.yuzu.yuzu_emu.features.settings.model.view.* import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile import org.yuzu.yuzu_emu.fragments.ResetSettingsDialogFragment @@ -27,7 +29,6 @@ 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 preferences: SharedPreferences @@ -41,17 +42,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)) { settingsActivity.setToolbarTitle("Game Settings: $gameId") @@ -69,7 +59,12 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) Settings.SECTION_THEME -> addThemeSettings(sl) Settings.SECTION_DEBUG -> addDebugSettings(sl) else -> { - fragmentView.showToastMessage("Unimplemented menu", false) + val context = YuzuApplication.appContext + Toast.makeText( + context, + context.getString(R.string.unimplemented_menu), + Toast.LENGTH_SHORT + ).show() return } } @@ -135,23 +130,23 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) sl.apply { add( SwitchSetting( - IntSetting.RENDERER_USE_SPEED_LIMIT, + BooleanSetting.RENDERER_USE_SPEED_LIMIT, R.string.frame_limit_enable, R.string.frame_limit_enable_description, - IntSetting.RENDERER_USE_SPEED_LIMIT.key, - IntSetting.RENDERER_USE_SPEED_LIMIT.defaultValue + BooleanSetting.RENDERER_USE_SPEED_LIMIT.key, + BooleanSetting.RENDERER_USE_SPEED_LIMIT.defaultValue ) ) add( SliderSetting( - IntSetting.RENDERER_SPEED_LIMIT, + ShortSetting.RENDERER_SPEED_LIMIT, R.string.frame_limit_slider, R.string.frame_limit_slider_description, 1, 200, "%", - IntSetting.RENDERER_SPEED_LIMIT.key, - IntSetting.RENDERER_SPEED_LIMIT.defaultValue + ShortSetting.RENDERER_SPEED_LIMIT.key, + ShortSetting.RENDERER_SPEED_LIMIT.defaultValue ) ) add( @@ -182,11 +177,11 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) sl.apply { add( SwitchSetting( - IntSetting.USE_DOCKED_MODE, + BooleanSetting.USE_DOCKED_MODE, R.string.use_docked_mode, R.string.use_docked_mode_description, - IntSetting.USE_DOCKED_MODE.key, - IntSetting.USE_DOCKED_MODE.defaultValue + BooleanSetting.USE_DOCKED_MODE.key, + BooleanSetting.USE_DOCKED_MODE.defaultValue ) ) add( @@ -222,11 +217,11 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) ) add( DateTimeSetting( - StringSetting.CUSTOM_RTC, + LongSetting.CUSTOM_RTC, R.string.set_custom_rtc, 0, - StringSetting.CUSTOM_RTC.key, - StringSetting.CUSTOM_RTC.defaultValue + LongSetting.CUSTOM_RTC.key, + LongSetting.CUSTOM_RTC.defaultValue ) ) } @@ -314,38 +309,38 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) ) add( SwitchSetting( - IntSetting.RENDERER_USE_DISK_SHADER_CACHE, + BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE, R.string.use_disk_shader_cache, R.string.use_disk_shader_cache_description, - IntSetting.RENDERER_USE_DISK_SHADER_CACHE.key, - IntSetting.RENDERER_USE_DISK_SHADER_CACHE.defaultValue + BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE.key, + BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE.defaultValue ) ) add( SwitchSetting( - IntSetting.RENDERER_FORCE_MAX_CLOCK, + BooleanSetting.RENDERER_FORCE_MAX_CLOCK, R.string.renderer_force_max_clock, R.string.renderer_force_max_clock_description, - IntSetting.RENDERER_FORCE_MAX_CLOCK.key, - IntSetting.RENDERER_FORCE_MAX_CLOCK.defaultValue + BooleanSetting.RENDERER_FORCE_MAX_CLOCK.key, + BooleanSetting.RENDERER_FORCE_MAX_CLOCK.defaultValue ) ) add( SwitchSetting( - IntSetting.RENDERER_ASYNCHRONOUS_SHADERS, + BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS, R.string.renderer_asynchronous_shaders, R.string.renderer_asynchronous_shaders_description, - IntSetting.RENDERER_ASYNCHRONOUS_SHADERS.key, - IntSetting.RENDERER_ASYNCHRONOUS_SHADERS.defaultValue + BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS.key, + BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS.defaultValue ) ) add( SwitchSetting( - IntSetting.RENDERER_REACTIVE_FLUSHING, + BooleanSetting.RENDERER_REACTIVE_FLUSHING, R.string.renderer_reactive_flushing, R.string.renderer_reactive_flushing_description, - IntSetting.RENDERER_REACTIVE_FLUSHING.key, - IntSetting.RENDERER_REACTIVE_FLUSHING.defaultValue + BooleanSetting.RENDERER_REACTIVE_FLUSHING.key, + BooleanSetting.RENDERER_REACTIVE_FLUSHING.defaultValue ) ) } @@ -355,26 +350,26 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_audio)) sl.apply { add( - StringSingleChoiceSetting( - StringSetting.AUDIO_OUTPUT_ENGINE, + SingleChoiceSetting( + IntSetting.AUDIO_OUTPUT_ENGINE, R.string.audio_output_engine, 0, - settingsActivity.resources.getStringArray(R.array.outputEngineEntries), - settingsActivity.resources.getStringArray(R.array.outputEngineValues), - StringSetting.AUDIO_OUTPUT_ENGINE.key, - StringSetting.AUDIO_OUTPUT_ENGINE.defaultValue + R.array.outputEngineEntries, + R.array.outputEngineValues, + IntSetting.AUDIO_OUTPUT_ENGINE.key, + IntSetting.AUDIO_OUTPUT_ENGINE.defaultValue ) ) add( SliderSetting( - IntSetting.AUDIO_VOLUME, + ByteSetting.AUDIO_VOLUME, R.string.audio_volume, R.string.audio_volume_description, 0, 100, "%", - IntSetting.AUDIO_VOLUME.key, - IntSetting.AUDIO_VOLUME.defaultValue + ByteSetting.AUDIO_VOLUME.key, + ByteSetting.AUDIO_VOLUME.defaultValue ) ) } @@ -384,19 +379,19 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_theme)) sl.apply { val theme: AbstractIntSetting = object : AbstractIntSetting { - override var int: Int + override val int: Int get() = preferences.getInt(Settings.PREF_THEME, 0) - set(value) { - preferences.edit() - .putInt(Settings.PREF_THEME, value) - .apply() - settingsActivity.recreate() - } + + override fun setInt(value: Int) { + preferences.edit() + .putInt(Settings.PREF_THEME, 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_THEME, 0).toString() + override val category = Settings.Category.UiGeneral + override val isRuntimeModifiable: Boolean = false override val defaultValue: Any = 0 } @@ -423,19 +418,19 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) } val themeMode: AbstractIntSetting = object : AbstractIntSetting { - override var int: Int + override val int: Int get() = preferences.getInt(Settings.PREF_THEME_MODE, -1) - set(value) { - preferences.edit() - .putInt(Settings.PREF_THEME_MODE, value) - .apply() - ThemeHelper.setThemeMode(settingsActivity) - } + + override fun setInt(value: Int) { + preferences.edit() + .putInt(Settings.PREF_THEME_MODE, value) + .apply() + ThemeHelper.setThemeMode(settingsActivity) + } + 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 category = Settings.Category.UiGeneral + override val isRuntimeModifiable: Boolean = false override val defaultValue: Any = -1 } @@ -450,20 +445,19 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) ) val blackBackgrounds: AbstractBooleanSetting = object : AbstractBooleanSetting { - override var boolean: Boolean + override val boolean: Boolean get() = preferences.getBoolean(Settings.PREF_BLACK_BACKGROUNDS, false) - set(value) { - preferences.edit() - .putBoolean(Settings.PREF_BLACK_BACKGROUNDS, value) - .apply() - settingsActivity.recreate() - } + + override fun setBoolean(value: Boolean) { + preferences.edit() + .putBoolean(Settings.PREF_BLACK_BACKGROUNDS, 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_BLACK_BACKGROUNDS, false) - .toString() + override val category = Settings.Category.UiGeneral + override val isRuntimeModifiable: Boolean = false override val defaultValue: Any = false } @@ -494,11 +488,11 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) ) add( SwitchSetting( - IntSetting.RENDERER_DEBUG, + BooleanSetting.RENDERER_DEBUG, R.string.renderer_debug, R.string.renderer_debug_description, - IntSetting.RENDERER_DEBUG.key, - IntSetting.RENDERER_DEBUG.defaultValue + BooleanSetting.RENDERER_DEBUG.key, + BooleanSetting.RENDERER_DEBUG.defaultValue ) ) @@ -514,17 +508,18 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) ) val fastmem = object : AbstractBooleanSetting { - override var boolean: Boolean + override val boolean: Boolean get() = BooleanSetting.FASTMEM.boolean && BooleanSetting.FASTMEM_EXCLUSIVES.boolean - set(value) { - BooleanSetting.FASTMEM.boolean = value - BooleanSetting.FASTMEM_EXCLUSIVES.boolean = value - } + + override fun setBoolean(value: Boolean) { + BooleanSetting.FASTMEM.setBoolean(value) + BooleanSetting.FASTMEM_EXCLUSIVES.setBoolean(value) + } + override val key: String? = null - override val section: String = Settings.SECTION_CPU - override val isRuntimeEditable: Boolean = false - override val valueAsString: String = "" + override val category = Settings.Category.Cpu + override val isRuntimeModifiable: Boolean = false override val defaultValue: Any = true } add( diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentView.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentView.kt index 1ebe35eaa..a4d7a80aa 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentView.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentView.kt @@ -3,7 +3,6 @@ package org.yuzu.yuzu_emu.features.settings.ui -import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem /** @@ -36,21 +35,6 @@ interface SettingsFragmentView { */ fun loadSubMenu(menuKey: String) - /** - * Tell the Fragment to tell the containing activity to display a toast message. - * - * @param message Text to be shown in the Toast - * @param is_long Whether this should be a long Toast or short one. - */ - 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/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt index 79572fc06..eb25ea4fb 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt @@ -29,7 +29,7 @@ class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA } binding.textSettingValue.visibility = View.VISIBLE - val epochTime = setting.value.toLong() + val epochTime = setting.value val instant = Instant.ofEpochMilli(epochTime * 1000) val zonedTime = ZonedDateTime.ofInstant(instant, ZoneId.of("UTC")) val dateFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.kt index 70a52df5d..2b04d666a 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.kt @@ -3,18 +3,15 @@ package org.yuzu.yuzu_emu.features.settings.utils +import android.widget.Toast import java.io.* -import java.util.* import org.ini4j.Wini -import org.yuzu.yuzu_emu.NativeLibrary import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.YuzuApplication import org.yuzu.yuzu_emu.features.settings.model.* -import org.yuzu.yuzu_emu.features.settings.model.Settings.SettingsSectionMap -import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivityView -import org.yuzu.yuzu_emu.utils.BiMap import org.yuzu.yuzu_emu.utils.DirectoryInitialization import org.yuzu.yuzu_emu.utils.Log +import org.yuzu.yuzu_emu.utils.NativeConfig /** * Contains static methods for interacting with .ini files in which settings are stored. @@ -22,243 +19,41 @@ import org.yuzu.yuzu_emu.utils.Log object SettingsFile { const val FILE_NAME_CONFIG = "config" - private var sectionsMap = BiMap() - - /** - * 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. - * - * @param ini The ini file to load the settings from - * @param isCustomGame - * @param view The current view. - * @return An Observable that emits a HashMap of the file's contents, then completes. - */ - private fun readFile( - ini: File?, - isCustomGame: Boolean, - view: SettingsActivityView? = null - ): HashMap { - val sections: HashMap = SettingsSectionMap() - var reader: BufferedReader? = null - try { - reader = BufferedReader(FileReader(ini)) - var current: SettingSection? = 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) - } - } - } - } catch (e: FileNotFoundException) { - Log.error("[SettingsFile] File not found: " + e.message) - view?.onSettingsFileNotFound() - } catch (e: IOException) { - Log.error("[SettingsFile] Error reading from: " + e.message) - view?.onSettingsFileNotFound() - } finally { - if (reader != null) { - try { - reader.close() - } catch (e: IOException) { - Log.error("[SettingsFile] Error closing: " + e.message) - } - } - } - return sections - } - - fun readFile(fileName: String, view: SettingsActivityView?): HashMap { - return readFile(getSettingsFile(fileName), false, view) - } - - fun readFile(fileName: String): HashMap = - readFile(getSettingsFile(fileName), false) - - /** - * 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. - */ - fun readCustomGameSettings( - gameId: String, - view: SettingsActivityView? - ): HashMap { - return readFile(getCustomGameSettingsFile(gameId), true, view) - } - /** * Saves a Settings HashMap to a given .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 view The current view. */ - fun saveFile( - fileName: String, - sections: TreeMap, - view: SettingsActivityView - ) { + fun saveFile(fileName: String) { val ini = getSettingsFile(fileName) try { - val writer = Wini(ini) - val keySet: Set = sections.keys - for (key in keySet) { - val section = sections[key] - writeSection(writer, section!!) + val wini = Wini(ini) + for (specificCategory in Settings.Category.values()) { + val categoryHeader = NativeConfig.getConfigHeader(specificCategory.ordinal) + for (setting in Settings.settingsList) { + if (setting.key!!.isEmpty()) continue + + val settingCategoryHeader = + NativeConfig.getConfigHeader(setting.category.ordinal) + val iniSetting: String? = wini.get(categoryHeader, setting.key) + if (iniSetting != null || settingCategoryHeader == categoryHeader) { + wini.put(settingCategoryHeader, setting.key, setting.valueAsString) + } + } } - writer.store() + wini.store() } catch (e: IOException) { Log.error("[SettingsFile] File not found: " + fileName + ".ini: " + e.message) - view.showToastMessage( - YuzuApplication.appContext - .getString(R.string.error_saving, fileName, e.message), - false - ) - } - } - - fun saveCustomGameSettings(gameId: String?, sections: HashMap) { - val sortedSections: Set = TreeSet(sections.keys) - for (sectionKey in sortedSections) { - val section = sections[sectionKey] - val settings = section!!.settings - val sortedKeySet: Set = TreeSet(settings.keys) - for (settingKey in sortedKeySet) { - val setting = settings[settingKey] - NativeLibrary.setUserSetting( - gameId, - mapSectionNameFromIni( - section.name - ), - setting!!.key, - setting.valueAsString - ) - } - } - } - - private fun mapSectionNameFromIni(generalSectionName: String): String? { - return if (sectionsMap.getForward(generalSectionName) != null) { - sectionsMap.getForward(generalSectionName) - } else { - generalSectionName - } - } - - private fun mapSectionNameToIni(generalSectionName: String): String { - return if (sectionsMap.getBackward(generalSectionName) != null) { - sectionsMap.getBackward(generalSectionName).toString() - } else { - generalSectionName - } - } - - fun getSettingsFile(fileName: String): File { - return File( - DirectoryInitialization.userDirectory + "/config/" + fileName + ".ini" - ) - } - - private fun getCustomGameSettingsFile(gameId: String): File { - return File(DirectoryInitialization.userDirectory + "/GameSettings/" + gameId + ".ini") - } - - private fun sectionFromLine(line: String, isCustomGame: Boolean): SettingSection { - var sectionName: String = line.substring(1, line.length - 1) - if (isCustomGame) { - sectionName = mapSectionNameToIni(sectionName) + val context = YuzuApplication.appContext + Toast.makeText( + context, + context.getString(R.string.error_saving, fileName, e.message), + Toast.LENGTH_SHORT + ).show() } - 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) { - intSetting.int = value.toInt() - return intSetting - } - - 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 - } - - 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) - } - - BooleanSetting.values().forEach { - if (!keySet.contains(it.key)) { - parser.put(header, it.key, it.valueAsString) - } - } - IntSetting.values().forEach { - if (!keySet.contains(it.key)) { - parser.put(header, it.key, it.valueAsString) - } - } - StringSetting.values().forEach { - if (!keySet.contains(it.key)) { - parser.put(header, it.key, it.valueAsString) - } - } - } + fun getSettingsFile(fileName: String): File = + File(DirectoryInitialization.userDirectory + "/config/" + fileName + ".ini") } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt index aaf3a0ec1..d8dbf1f45 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt @@ -39,7 +39,6 @@ import org.yuzu.yuzu_emu.activities.EmulationActivity import org.yuzu.yuzu_emu.databinding.ActivityMainBinding import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding import org.yuzu.yuzu_emu.features.settings.model.Settings -import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment @@ -54,7 +53,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 @@ -62,8 +60,6 @@ class MainActivity : AppCompatActivity(), ThemeProvider { val splashScreen = installSplashScreen() splashScreen.setKeepOnScreenCondition { !DirectoryInitialization.areDirectoriesReady } - settingsViewModel.settings.loadSettings() - ThemeHelper.setTheme(this) super.onCreate(savedInstanceState) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/BiMap.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/BiMap.kt deleted file mode 100644 index 9cfda74ee..000000000 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/BiMap.kt +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-FileCopyrightText: 2023 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.yuzu.yuzu_emu.utils - -class BiMap { - private val forward: MutableMap = HashMap() - private val backward: MutableMap = HashMap() - - @Synchronized - fun add(key: K, value: V) { - forward[key] = value - backward[value] = key - } - - @Synchronized - fun getForward(key: K): V? { - return forward[key] - } - - @Synchronized - fun getBackward(key: V): K? { - return backward[key] - } -} diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt new file mode 100644 index 000000000..d4d981f9e --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.yuzu.yuzu_emu.utils + +object NativeConfig { + external fun getBoolean(key: String, getDefault: Boolean): Boolean + external fun setBoolean(key: String, value: Boolean) + + external fun getByte(key: String, getDefault: Boolean): Byte + external fun setByte(key: String, value: Byte) + + external fun getShort(key: String, getDefault: Boolean): Short + external fun setShort(key: String, value: Short) + + external fun getInt(key: String, getDefault: Boolean): Int + external fun setInt(key: String, value: Int) + + external fun getFloat(key: String, getDefault: Boolean): Float + external fun setFloat(key: String, value: Float) + + external fun getLong(key: String, getDefault: Boolean): Long + external fun setLong(key: String, value: Long) + + external fun getString(key: String, getDefault: Boolean): String + external fun setString(key: String, value: String) + + external fun getIsRuntimeModifiable(key: String): Boolean + + external fun getConfigHeader(category: Int): String +} diff --git a/src/android/app/src/main/jni/CMakeLists.txt b/src/android/app/src/main/jni/CMakeLists.txt index e2ed08e9f..e15d1480b 100644 --- a/src/android/app/src/main/jni/CMakeLists.txt +++ b/src/android/app/src/main/jni/CMakeLists.txt @@ -14,6 +14,8 @@ add_library(yuzu-android SHARED id_cache.cpp id_cache.h native.cpp + native_config.cpp + uisettings.cpp ) set_property(TARGET yuzu-android PROPERTY IMPORTED_LOCATION ${FFmpeg_LIBRARY_DIR}) diff --git a/src/android/app/src/main/jni/config.cpp b/src/android/app/src/main/jni/config.cpp index 9de9bd93e..34b425cb4 100644 --- a/src/android/app/src/main/jni/config.cpp +++ b/src/android/app/src/main/jni/config.cpp @@ -16,18 +16,20 @@ #include "input_common/main.h" #include "jni/config.h" #include "jni/default_ini.h" +#include "uisettings.h" namespace FS = Common::FS; -Config::Config(std::optional config_path) - : config_loc{config_path.value_or(FS::GetYuzuPath(FS::YuzuPath::ConfigDir) / "config.ini")}, - config{std::make_unique(FS::PathToUTF8String(config_loc))} { - Reload(); +Config::Config(const std::string& config_name, ConfigType config_type) + : type(config_type), global{config_type == ConfigType::GlobalConfig} { + Initialize(config_name); } Config::~Config() = default; bool Config::LoadINI(const std::string& default_contents, bool retry) { + void(FS::CreateParentDir(config_loc)); + config = std::make_unique(FS::PathToUTF8String(config_loc)); const auto config_loc_str = FS::PathToUTF8String(config_loc); if (config->ParseError() < 0) { if (retry) { @@ -301,9 +303,28 @@ void Config::ReadValues() { // Network ReadSetting("Network", Settings::values.network_interface); + + // Android + ReadSetting("Android", AndroidSettings::values.picture_in_picture); + ReadSetting("Android", AndroidSettings::values.screen_layout); } -void Config::Reload() { +void Config::Initialize(const std::string& config_name) { + const auto fs_config_loc = FS::GetYuzuPath(FS::YuzuPath::ConfigDir); + const auto config_file = fmt::format("{}.ini", config_name); + + switch (type) { + case ConfigType::GlobalConfig: + config_loc = FS::PathToUTF8String(fs_config_loc / config_file); + break; + case ConfigType::PerGameConfig: + config_loc = FS::PathToUTF8String(fs_config_loc / "custom" / FS::ToU8String(config_file)); + break; + case ConfigType::InputProfile: + config_loc = FS::PathToUTF8String(fs_config_loc / "input" / config_file); + LoadINI(DefaultINI::android_config_file); + return; + } LoadINI(DefaultINI::android_config_file); ReadValues(); } diff --git a/src/android/app/src/main/jni/config.h b/src/android/app/src/main/jni/config.h index 0d7d6e94d..e1e8f47ed 100644 --- a/src/android/app/src/main/jni/config.h +++ b/src/android/app/src/main/jni/config.h @@ -13,25 +13,35 @@ class INIReader; class Config { - std::filesystem::path config_loc; - std::unique_ptr config; - bool LoadINI(const std::string& default_contents = "", bool retry = true); - void ReadValues(); public: - explicit Config(std::optional config_path = std::nullopt); + enum class ConfigType { + GlobalConfig, + PerGameConfig, + InputProfile, + }; + + explicit Config(const std::string& config_name = "config", + ConfigType config_type = ConfigType::GlobalConfig); ~Config(); - void Reload(); + void Initialize(const std::string& config_name); private: /** - * Applies a value read from the sdl2_config to a Setting. + * Applies a value read from the config to a Setting. * * @param group The name of the INI group * @param setting The yuzu setting to modify */ template void ReadSetting(const std::string& group, Settings::Setting& setting); + + void ReadValues(); + + const ConfigType type; + std::unique_ptr config; + std::string config_loc; + const bool global; }; diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index 7e17833a0..b2adfdeda 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -824,34 +824,6 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_reloadSettings(JNIEnv* env, jclass cl Config{}; } -jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getUserSetting(JNIEnv* env, jclass clazz, - jstring j_game_id, jstring j_section, - jstring j_key) { - std::string_view game_id = env->GetStringUTFChars(j_game_id, 0); - std::string_view section = env->GetStringUTFChars(j_section, 0); - std::string_view key = env->GetStringUTFChars(j_key, 0); - - env->ReleaseStringUTFChars(j_game_id, game_id.data()); - env->ReleaseStringUTFChars(j_section, section.data()); - env->ReleaseStringUTFChars(j_key, key.data()); - - return env->NewStringUTF(""); -} - -void Java_org_yuzu_yuzu_1emu_NativeLibrary_setUserSetting(JNIEnv* env, jclass clazz, - jstring j_game_id, jstring j_section, - jstring j_key, jstring j_value) { - std::string_view game_id = env->GetStringUTFChars(j_game_id, 0); - std::string_view section = env->GetStringUTFChars(j_section, 0); - std::string_view key = env->GetStringUTFChars(j_key, 0); - std::string_view value = env->GetStringUTFChars(j_value, 0); - - env->ReleaseStringUTFChars(j_game_id, game_id.data()); - env->ReleaseStringUTFChars(j_section, section.data()); - env->ReleaseStringUTFChars(j_key, key.data()); - env->ReleaseStringUTFChars(j_value, value.data()); -} - void Java_org_yuzu_yuzu_1emu_NativeLibrary_initGameIni(JNIEnv* env, jclass clazz, jstring j_game_id) { std::string_view game_id = env->GetStringUTFChars(j_game_id, 0); diff --git a/src/android/app/src/main/jni/native_config.cpp b/src/android/app/src/main/jni/native_config.cpp new file mode 100644 index 000000000..6123b3d08 --- /dev/null +++ b/src/android/app/src/main/jni/native_config.cpp @@ -0,0 +1,224 @@ +// SPDX-FileCopyrightText: 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include + +#include + +#include "common/logging/log.h" +#include "common/settings.h" +#include "jni/android_common/android_common.h" +#include "jni/config.h" +#include "uisettings.h" + +template +Settings::Setting* getSetting(JNIEnv* env, jstring jkey) { + auto key = GetJString(env, jkey); + auto basicSetting = Settings::values.linkage.by_key[key]; + auto basicAndroidSetting = AndroidSettings::values.linkage.by_key[key]; + if (basicSetting != 0) { + return static_cast*>(basicSetting); + } + if (basicAndroidSetting != 0) { + return static_cast*>(basicAndroidSetting); + } + LOG_ERROR(Frontend, "[Android Native] Could not find setting - {}", key); + return nullptr; +} + +extern "C" { + +jboolean Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getBoolean(JNIEnv* env, jobject obj, + jstring jkey, jboolean getDefault) { + auto setting = getSetting(env, jkey); + if (setting == nullptr) { + return false; + } + setting->SetGlobal(true); + + if (static_cast(getDefault)) { + return setting->GetDefault(); + } + + return setting->GetValue(); +} + +void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setBoolean(JNIEnv* env, jobject obj, jstring jkey, + jboolean value) { + auto setting = getSetting(env, jkey); + if (setting == nullptr) { + return; + } + setting->SetGlobal(true); + setting->SetValue(static_cast(value)); +} + +jbyte Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getByte(JNIEnv* env, jobject obj, jstring jkey, + jboolean getDefault) { + auto setting = getSetting(env, jkey); + if (setting == nullptr) { + return -1; + } + setting->SetGlobal(true); + + if (static_cast(getDefault)) { + return setting->GetDefault(); + } + + return setting->GetValue(); +} + +void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setByte(JNIEnv* env, jobject obj, jstring jkey, + jbyte value) { + auto setting = getSetting(env, jkey); + if (setting == nullptr) { + return; + } + setting->SetGlobal(true); + setting->SetValue(value); +} + +jshort Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getShort(JNIEnv* env, jobject obj, jstring jkey, + jboolean getDefault) { + auto setting = getSetting(env, jkey); + if (setting == nullptr) { + return -1; + } + setting->SetGlobal(true); + + if (static_cast(getDefault)) { + return setting->GetDefault(); + } + + return setting->GetValue(); +} + +void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setShort(JNIEnv* env, jobject obj, jstring jkey, + jshort value) { + auto setting = getSetting(env, jkey); + if (setting == nullptr) { + return; + } + setting->SetGlobal(true); + setting->SetValue(value); +} + +jint Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getInt(JNIEnv* env, jobject obj, jstring jkey, + jboolean getDefault) { + auto setting = getSetting(env, jkey); + if (setting == nullptr) { + return -1; + } + setting->SetGlobal(true); + + if (static_cast(getDefault)) { + return setting->GetDefault(); + } + + return setting->GetValue(); +} + +void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setInt(JNIEnv* env, jobject obj, jstring jkey, + jint value) { + auto setting = getSetting(env, jkey); + if (setting == nullptr) { + return; + } + setting->SetGlobal(true); + setting->SetValue(value); +} + +jfloat Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getFloat(JNIEnv* env, jobject obj, jstring jkey, + jboolean getDefault) { + auto setting = getSetting(env, jkey); + if (setting == nullptr) { + return -1; + } + setting->SetGlobal(true); + + if (static_cast(getDefault)) { + return setting->GetDefault(); + } + + return setting->GetValue(); +} + +void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setFloat(JNIEnv* env, jobject obj, jstring jkey, + jfloat value) { + auto setting = getSetting(env, jkey); + if (setting == nullptr) { + return; + } + setting->SetGlobal(true); + setting->SetValue(value); +} + +jlong Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getLong(JNIEnv* env, jobject obj, jstring jkey, + jboolean getDefault) { + auto setting = getSetting(env, jkey); + if (setting == nullptr) { + return -1; + } + setting->SetGlobal(true); + + if (static_cast(getDefault)) { + return setting->GetDefault(); + } + + return setting->GetValue(); +} + +void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setLong(JNIEnv* env, jobject obj, jstring jkey, + jlong value) { + auto setting = getSetting(env, jkey); + if (setting == nullptr) { + return; + } + setting->SetGlobal(true); + setting->SetValue(value); +} + +jstring Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getString(JNIEnv* env, jobject obj, jstring jkey, + jboolean getDefault) { + auto setting = getSetting(env, jkey); + if (setting == nullptr) { + return ToJString(env, ""); + } + setting->SetGlobal(true); + + if (static_cast(getDefault)) { + return ToJString(env, setting->GetDefault()); + } + + return ToJString(env, setting->GetValue()); +} + +void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setString(JNIEnv* env, jobject obj, jstring jkey, + jstring value) { + auto setting = getSetting(env, jkey); + if (setting == nullptr) { + return; + } + + setting->SetGlobal(true); + setting->SetValue(GetJString(env, value)); +} + +jboolean Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getIsRuntimeModifiable(JNIEnv* env, jobject obj, + jstring jkey) { + auto key = GetJString(env, jkey); + auto setting = Settings::values.linkage.by_key[key]; + if (setting != 0) { + return setting->RuntimeModfiable(); + } + LOG_ERROR(Frontend, "[Android Native] Could not find setting - {}", key); + return true; +} + +jstring Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getConfigHeader(JNIEnv* env, jobject obj, + jint jcategory) { + auto category = static_cast(jcategory); + return ToJString(env, Settings::TranslateCategory(category)); +} + +} // extern "C" diff --git a/src/android/app/src/main/jni/uisettings.cpp b/src/android/app/src/main/jni/uisettings.cpp new file mode 100644 index 000000000..f2f0bad50 --- /dev/null +++ b/src/android/app/src/main/jni/uisettings.cpp @@ -0,0 +1,10 @@ +// SPDX-FileCopyrightText: 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "uisettings.h" + +namespace AndroidSettings { + +Values values; + +} // namespace AndroidSettings diff --git a/src/android/app/src/main/jni/uisettings.h b/src/android/app/src/main/jni/uisettings.h new file mode 100644 index 000000000..494654af7 --- /dev/null +++ b/src/android/app/src/main/jni/uisettings.h @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include "common/common_types.h" +#include "common/settings_setting.h" + +namespace AndroidSettings { + +struct Values { + Settings::Linkage linkage; + + // Android + Settings::Setting picture_in_picture{linkage, true, "picture_in_picture", + Settings::Category::Android}; + Settings::Setting screen_layout{linkage, + 5, + "screen_layout", + Settings::Category::Android, + Settings::Specialization::Default, + true, + true}; +}; + +extern Values values; + +} // namespace AndroidSettings diff --git a/src/android/app/src/main/res/values/arrays.xml b/src/android/app/src/main/res/values/arrays.xml index 200b99185..dc10159c9 100644 --- a/src/android/app/src/main/res/values/arrays.xml +++ b/src/android/app/src/main/res/values/arrays.xml @@ -243,10 +243,10 @@ @string/cubeb @string/string_null - - auto - cubeb - null - + + 0 + 1 + 3 + diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index de1b2909b..df76563fc 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -200,6 +200,7 @@ Saved settings Saved settings for %1$s Error saving %1$s.ini: %2$s + Unimplemented Menu Loading… Do you want to reset this setting back to its default value? Reset to default diff --git a/src/common/settings.cpp b/src/common/settings.cpp index 524056841..4ecaf550b 100644 --- a/src/common/settings.cpp +++ b/src/common/settings.cpp @@ -159,6 +159,8 @@ float Volume() { const char* TranslateCategory(Category category) { switch (category) { + case Category::Android: + return "Android"; case Category::Audio: return "Audio"; case Category::Core: diff --git a/src/common/settings_common.cpp b/src/common/settings_common.cpp index 137b65d5f..5960b78aa 100644 --- a/src/common/settings_common.cpp +++ b/src/common/settings_common.cpp @@ -14,6 +14,7 @@ BasicSetting::BasicSetting(Linkage& linkage, const std::string& name, enum Categ : label{name}, category{category_}, id{linkage.count}, save{save_}, runtime_modifiable{runtime_modifiable_}, specialization{specialization_}, other_setting{other_setting_} { + linkage.by_key.insert({name, this}); linkage.by_category[category].push_back(this); linkage.count++; } diff --git a/src/common/settings_common.h b/src/common/settings_common.h index 3082e0ce1..5b170dfd5 100644 --- a/src/common/settings_common.h +++ b/src/common/settings_common.h @@ -12,6 +12,7 @@ namespace Settings { enum class Category : u32 { + Android, Audio, Core, Cpu, @@ -68,6 +69,7 @@ public: explicit Linkage(u32 initial_count = 0); ~Linkage(); std::map> by_category{}; + std::map by_key{}; std::vector> restore_functions{}; u32 count; }; -- cgit v1.2.3 From f5e6b12c74243736a1dc29d297dd2eb1d0c3a4b2 Mon Sep 17 00:00:00 2001 From: Charles Lombardo Date: Mon, 21 Aug 2023 21:48:21 -0400 Subject: android: Trim settings enums and items Take advantage of the new settings interface to reduce the amount of code we need for each setting item. Additionally make all settings items non-null to improve brevity. --- .../features/settings/model/AbstractSetting.kt | 2 +- .../settings/model/view/DateTimeSetting.kt | 25 +-- .../features/settings/model/view/HeaderSetting.kt | 2 +- .../settings/model/view/RunnableSetting.kt | 2 +- .../features/settings/model/view/SettingsItem.kt | 12 +- .../settings/model/view/SingleChoiceSetting.kt | 37 ++--- .../features/settings/model/view/SliderSetting.kt | 63 ++----- .../model/view/StringSingleChoiceSetting.kt | 46 ++---- .../features/settings/model/view/SubmenuSetting.kt | 2 +- .../features/settings/model/view/SwitchSetting.kt | 53 ++---- .../features/settings/ui/SettingsAdapter.kt | 16 +- .../settings/ui/SettingsFragmentPresenter.kt | 184 ++++++--------------- .../settings/ui/viewholder/DateTimeViewHolder.kt | 2 +- .../ui/viewholder/SingleChoiceViewHolder.kt | 4 +- .../settings/ui/viewholder/SliderViewHolder.kt | 2 +- .../ui/viewholder/SwitchSettingViewHolder.kt | 2 +- 16 files changed, 133 insertions(+), 321 deletions(-) (limited to 'src') diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractSetting.kt index 7afed95ad..724a2ecb8 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractSetting.kt @@ -15,5 +15,5 @@ interface AbstractSetting { val isRuntimeModifiable: Boolean get() = NativeConfig.getIsRuntimeModifiable(key!!) - fun reset() = run { } + fun reset() } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/DateTimeSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/DateTimeSetting.kt index 7c858916e..8bc164197 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/DateTimeSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/DateTimeSetting.kt @@ -4,28 +4,15 @@ package org.yuzu.yuzu_emu.features.settings.model.view import org.yuzu.yuzu_emu.features.settings.model.AbstractLongSetting -import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting class DateTimeSetting( - setting: AbstractSetting?, + private val longSetting: AbstractLongSetting, titleId: Int, - descriptionId: Int, - val key: String? = null, - private val defaultValue: Long? = null -) : SettingsItem(setting, titleId, descriptionId) { + descriptionId: Int +) : SettingsItem(longSetting, titleId, descriptionId) { override val type = TYPE_DATETIME_SETTING - val value: Long - get() = if (setting != null) { - val setting = setting as AbstractLongSetting - setting.long - } else { - defaultValue!! - } - - fun setSelectedValue(datetime: Long): AbstractLongSetting { - val longSetting = setting as AbstractLongSetting - longSetting.setLong(datetime) - return longSetting - } + var value: Long + get() = longSetting.long + set(value) = (setting as AbstractLongSetting).setLong(value) } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/HeaderSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/HeaderSetting.kt index a67001311..d31ce1c31 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/HeaderSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/HeaderSetting.kt @@ -5,6 +5,6 @@ package org.yuzu.yuzu_emu.features.settings.model.view class HeaderSetting( titleId: Int -) : SettingsItem(null, titleId, 0) { +) : SettingsItem(emptySetting, titleId, 0) { override val type = TYPE_HEADER } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/RunnableSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/RunnableSetting.kt index caaab50d8..522cc49df 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/RunnableSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/RunnableSetting.kt @@ -8,6 +8,6 @@ class RunnableSetting( descriptionId: Int, val isRuntimeRunnable: Boolean, val runnable: () -> Unit -) : SettingsItem(null, titleId, descriptionId) { +) : SettingsItem(emptySetting, titleId, descriptionId) { override val type = TYPE_RUNNABLE } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt index a6cba977c..3bdcc5bca 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt @@ -5,6 +5,7 @@ package org.yuzu.yuzu_emu.features.settings.model.view import org.yuzu.yuzu_emu.NativeLibrary import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting +import org.yuzu.yuzu_emu.features.settings.model.Settings /** * ViewModel abstraction for an Item in the RecyclerView powering SettingsFragments. @@ -14,7 +15,7 @@ import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting * file.) */ abstract class SettingsItem( - var setting: AbstractSetting?, + val setting: AbstractSetting, val nameId: Int, val descriptionId: Int ) { @@ -23,7 +24,7 @@ abstract class SettingsItem( val isEditable: Boolean get() { if (!NativeLibrary.isRunning()) return true - return setting?.isRuntimeModifiable ?: false + return setting.isRuntimeModifiable } companion object { @@ -35,5 +36,12 @@ abstract class SettingsItem( const val TYPE_STRING_SINGLE_CHOICE = 5 const val TYPE_DATETIME_SETTING = 6 const val TYPE_RUNNABLE = 7 + + val emptySetting = object : AbstractSetting { + override val key: String = "" + override val category: Settings.Category = Settings.Category.Ui + override val defaultValue: Any = false + override fun reset() {} + } } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SingleChoiceSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SingleChoiceSetting.kt index b6a8c4612..705527a73 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SingleChoiceSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SingleChoiceSetting.kt @@ -4,36 +4,27 @@ package org.yuzu.yuzu_emu.features.settings.model.view import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting +import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting class SingleChoiceSetting( - setting: AbstractIntSetting?, + setting: AbstractSetting, titleId: Int, descriptionId: Int, val choicesId: Int, - val valuesId: Int, - val key: String? = null, - val defaultValue: Int? = null + val valuesId: Int ) : SettingsItem(setting, titleId, descriptionId) { override val type = TYPE_SINGLE_CHOICE - val selectedValue: Int - get() = if (setting != null) { - val setting = setting as AbstractIntSetting - setting.int - } else { - defaultValue!! + var selectedValue: Int + get() { + return when (setting) { + is AbstractIntSetting -> setting.int + else -> -1 + } + } + set(value) { + when (setting) { + is AbstractIntSetting -> setting.setInt(value) + } } - - /** - * 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.setInt(selection) - return intSetting - } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SliderSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SliderSetting.kt index e71a29e35..c3b5df02c 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SliderSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SliderSetting.kt @@ -4,75 +4,38 @@ package org.yuzu.yuzu_emu.features.settings.model.view import org.yuzu.yuzu_emu.features.settings.model.AbstractByteSetting -import kotlin.math.roundToInt import org.yuzu.yuzu_emu.features.settings.model.AbstractFloatSetting import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting import org.yuzu.yuzu_emu.features.settings.model.AbstractShortSetting -import org.yuzu.yuzu_emu.utils.Log +import kotlin.math.roundToInt class SliderSetting( - setting: AbstractSetting?, + setting: AbstractSetting, titleId: Int, descriptionId: Int, val min: Int, val max: Int, - val units: String, - val key: String? = null, - val defaultValue: Any? = null + val units: String ) : SettingsItem(setting, titleId, descriptionId) { override val type = TYPE_SLIDER - val selectedValue: Any + var selectedValue: Int get() { - val setting = setting ?: return defaultValue!! return when (setting) { is AbstractByteSetting -> setting.byte.toInt() is AbstractShortSetting -> setting.short.toInt() is AbstractIntSetting -> setting.int is AbstractFloatSetting -> setting.float.roundToInt() - else -> { - Log.error("[SliderSetting] Error casting setting type.") - -1 - } + else -> -1 + } + } + set(value) { + when (setting) { + is AbstractByteSetting -> setting.setByte(value.toByte()) + is AbstractShortSetting -> setting.setShort(value.toShort()) + is AbstractIntSetting -> setting.setInt(value) + is AbstractFloatSetting -> setting.setFloat(value.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.setInt(selection) - return intSetting - } - - /** - * Write a value to the backing float. If that float was previously null, - * 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 - floatSetting.setFloat(selection) - return floatSetting - } - - fun setSelectedValue(selection: Short): AbstractShortSetting { - val shortSetting = setting as AbstractShortSetting - shortSetting.setShort(selection) - return shortSetting - } - - fun setSelectedValue(selection: Byte): AbstractByteSetting { - val byteSetting = setting as AbstractByteSetting - byteSetting.setByte(selection) - return byteSetting - } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/StringSingleChoiceSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/StringSingleChoiceSetting.kt index 2195641e3..871dab4f3 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/StringSingleChoiceSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/StringSingleChoiceSetting.kt @@ -3,57 +3,31 @@ package org.yuzu.yuzu_emu.features.settings.model.view -import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting class StringSingleChoiceSetting( - setting: AbstractSetting?, + private val stringSetting: AbstractStringSetting, titleId: Int, descriptionId: Int, val choices: Array, - val values: Array?, - val key: String? = null, - private val defaultValue: String? = null -) : SettingsItem(setting, titleId, descriptionId) { + val values: Array +) : SettingsItem(stringSetting, titleId, descriptionId) { override val type = TYPE_STRING_SINGLE_CHOICE - fun getValueAt(index: Int): String? { - if (values == null) return null - return if (index >= 0 && index < values.size) { - values[index] - } else { - "" - } - } + fun getValueAt(index: Int): String = + if (index >= 0 && index < values.size) values[index] else "" + + var selectedValue: String + get() = stringSetting.string + set(value) = stringSetting.setString(value) - val selectedValue: String - get() = if (setting != null) { - val setting = setting as AbstractStringSetting - setting.string - } else { - defaultValue!! - } val selectValueIndex: Int get() { - val selectedValue = selectedValue - for (i in values!!.indices) { + for (i in values.indices) { if (values[i] == selectedValue) { return i } } return -1 } - - /** - * 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: String): AbstractStringSetting { - val stringSetting = setting as AbstractStringSetting - stringSetting.setString(selection) - return stringSetting - } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt index 8a9d13a92..91c273964 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt @@ -7,6 +7,6 @@ class SubmenuSetting( titleId: Int, descriptionId: Int, val menuKey: String -) : SettingsItem(null, titleId, descriptionId) { +) : SettingsItem(emptySetting, titleId, descriptionId) { override val type = TYPE_SUBMENU } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SwitchSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SwitchSetting.kt index 4ed8070e5..416967e64 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SwitchSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SwitchSetting.kt @@ -10,53 +10,22 @@ import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting class SwitchSetting( setting: AbstractSetting, titleId: Int, - descriptionId: Int, - val key: String? = null, - val defaultValue: Any? = null + descriptionId: Int ) : SettingsItem(setting, titleId, descriptionId) { override val type = TYPE_SWITCH - val isChecked: Boolean + var checked: Boolean get() { - if (setting == null) { - return defaultValue as Boolean + return when (setting) { + is AbstractIntSetting -> setting.int == 1 + is AbstractBooleanSetting -> setting.boolean + else -> false } - - // Try integer setting - try { - val setting = setting as AbstractIntSetting - return setting.int == 1 - } catch (_: ClassCastException) { - } - - // Try boolean setting - try { - val setting = setting as AbstractBooleanSetting - return setting.boolean - } catch (_: ClassCastException) { - } - return defaultValue as Boolean } - - /** - * Write a value to the backing boolean. If that boolean was previously null, - * initializes a new one and returns it, so it can be added to the Hashmap. - * - * @param checked Pretty self explanatory. - * @return the existing setting with the new value applied. - */ - fun setChecked(checked: Boolean): AbstractSetting { - // Try integer setting - try { - val setting = setting as AbstractIntSetting - setting.setInt(if (checked) 1 else 0) - return setting - } catch (_: ClassCastException) { + set(value) { + when (setting) { + is AbstractIntSetting -> setting.setInt(if (value) 1 else 0) + is AbstractBooleanSetting -> setting.setBoolean(value) + } } - - // Try boolean setting - val setting = setting as AbstractBooleanSetting - setting.setBoolean(checked) - return setting - } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt index e2e8d8bec..27eaaa576 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt @@ -113,7 +113,7 @@ class SettingsAdapter( } fun onBooleanClick(item: SwitchSetting, position: Int, checked: Boolean) { - item.setChecked(checked) + item.checked = checked fragmentView.onSettingChanged() } @@ -183,7 +183,7 @@ class SettingsAdapter( if (item.value != epochTime) { fragmentView.onSettingChanged() notifyItemChanged(clickedPosition) - item.setSelectedValue(epochTime) + item.value = epochTime } clickedItem = null } @@ -244,7 +244,7 @@ class SettingsAdapter( } // Get the backing Setting, which may be null (if for example it was missing from the file) - scSetting.setSelectedValue(value) + scSetting.selectedValue = value closeDialog() } @@ -252,7 +252,7 @@ class SettingsAdapter( val scSetting = clickedItem as StringSingleChoiceSetting val value = scSetting.getValueAt(which) if (scSetting.selectedValue != value) fragmentView.onSettingChanged() - scSetting.setSelectedValue(value!!) + scSetting.selectedValue = value!! closeDialog() } @@ -264,21 +264,21 @@ class SettingsAdapter( when (sliderSetting.setting) { is ByteSetting -> { val value = sliderProgress.toByte() - sliderSetting.setSelectedValue(value) + sliderSetting.selectedValue = value.toInt() } is ShortSetting -> { val value = sliderProgress.toShort() - sliderSetting.setSelectedValue(value) + sliderSetting.selectedValue = value.toInt() } is FloatSetting -> { val value = sliderProgress.toFloat() - sliderSetting.setSelectedValue(value) + sliderSetting.selectedValue = value.toInt() } else -> { - sliderSetting.setSelectedValue(sliderProgress) + sliderSetting.selectedValue = sliderProgress } } closeDialog() diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt index 2bab9e542..dddbf65bb 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt @@ -75,47 +75,13 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) private fun addConfigSettings(sl: ArrayList) { settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.advanced_settings)) sl.apply { + add(SubmenuSetting(R.string.preferences_general, 0, Settings.SECTION_GENERAL)) + add(SubmenuSetting(R.string.preferences_system, 0, Settings.SECTION_SYSTEM)) + add(SubmenuSetting(R.string.preferences_graphics, 0, Settings.SECTION_RENDERER)) + add(SubmenuSetting(R.string.preferences_audio, 0, Settings.SECTION_AUDIO)) + add(SubmenuSetting(R.string.preferences_debug, 0, Settings.SECTION_DEBUG)) add( - SubmenuSetting( - R.string.preferences_general, - 0, - Settings.SECTION_GENERAL - ) - ) - add( - SubmenuSetting( - R.string.preferences_system, - 0, - Settings.SECTION_SYSTEM - ) - ) - add( - SubmenuSetting( - R.string.preferences_graphics, - 0, - Settings.SECTION_RENDERER - ) - ) - add( - SubmenuSetting( - R.string.preferences_audio, - 0, - Settings.SECTION_AUDIO - ) - ) - add( - SubmenuSetting( - R.string.preferences_debug, - 0, - Settings.SECTION_DEBUG - ) - ) - add( - RunnableSetting( - R.string.reset_to_default, - 0, - false - ) { + RunnableSetting(R.string.reset_to_default, 0, false) { ResetSettingsDialogFragment().show( settingsActivity.supportFragmentManager, ResetSettingsDialogFragment.TAG @@ -132,9 +98,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) SwitchSetting( BooleanSetting.RENDERER_USE_SPEED_LIMIT, R.string.frame_limit_enable, - R.string.frame_limit_enable_description, - BooleanSetting.RENDERER_USE_SPEED_LIMIT.key, - BooleanSetting.RENDERER_USE_SPEED_LIMIT.defaultValue + R.string.frame_limit_enable_description ) ) add( @@ -144,9 +108,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) R.string.frame_limit_slider_description, 1, 200, - "%", - ShortSetting.RENDERER_SPEED_LIMIT.key, - ShortSetting.RENDERER_SPEED_LIMIT.defaultValue + "%" ) ) add( @@ -155,18 +117,14 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) R.string.cpu_accuracy, 0, R.array.cpuAccuracyNames, - R.array.cpuAccuracyValues, - IntSetting.CPU_ACCURACY.key, - IntSetting.CPU_ACCURACY.defaultValue + R.array.cpuAccuracyValues ) ) add( SwitchSetting( BooleanSetting.PICTURE_IN_PICTURE, R.string.picture_in_picture, - R.string.picture_in_picture_description, - BooleanSetting.PICTURE_IN_PICTURE.key, - BooleanSetting.PICTURE_IN_PICTURE.defaultValue + R.string.picture_in_picture_description ) ) } @@ -179,9 +137,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) SwitchSetting( BooleanSetting.USE_DOCKED_MODE, R.string.use_docked_mode, - R.string.use_docked_mode_description, - BooleanSetting.USE_DOCKED_MODE.key, - BooleanSetting.USE_DOCKED_MODE.defaultValue + R.string.use_docked_mode_description ) ) add( @@ -190,9 +146,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) R.string.emulated_region, 0, R.array.regionNames, - R.array.regionValues, - IntSetting.REGION_INDEX.key, - IntSetting.REGION_INDEX.defaultValue + R.array.regionValues ) ) add( @@ -201,29 +155,17 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) R.string.emulated_language, 0, R.array.languageNames, - R.array.languageValues, - IntSetting.LANGUAGE_INDEX.key, - IntSetting.LANGUAGE_INDEX.defaultValue + R.array.languageValues ) ) add( SwitchSetting( BooleanSetting.USE_CUSTOM_RTC, R.string.use_custom_rtc, - R.string.use_custom_rtc_description, - BooleanSetting.USE_CUSTOM_RTC.key, - BooleanSetting.USE_CUSTOM_RTC.defaultValue - ) - ) - add( - DateTimeSetting( - LongSetting.CUSTOM_RTC, - R.string.set_custom_rtc, - 0, - LongSetting.CUSTOM_RTC.key, - LongSetting.CUSTOM_RTC.defaultValue + R.string.use_custom_rtc_description ) ) + add(DateTimeSetting(LongSetting.CUSTOM_RTC, R.string.set_custom_rtc, 0)) } } @@ -236,9 +178,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) R.string.renderer_accuracy, 0, R.array.rendererAccuracyNames, - R.array.rendererAccuracyValues, - IntSetting.RENDERER_ACCURACY.key, - IntSetting.RENDERER_ACCURACY.defaultValue + R.array.rendererAccuracyValues ) ) add( @@ -247,9 +187,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) R.string.renderer_resolution, 0, R.array.rendererResolutionNames, - R.array.rendererResolutionValues, - IntSetting.RENDERER_RESOLUTION.key, - IntSetting.RENDERER_RESOLUTION.defaultValue + R.array.rendererResolutionValues ) ) add( @@ -258,9 +196,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) R.string.renderer_vsync, 0, R.array.rendererVSyncNames, - R.array.rendererVSyncValues, - IntSetting.RENDERER_VSYNC.key, - IntSetting.RENDERER_VSYNC.defaultValue + R.array.rendererVSyncValues ) ) add( @@ -269,9 +205,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) R.string.renderer_scaling_filter, 0, R.array.rendererScalingFilterNames, - R.array.rendererScalingFilterValues, - IntSetting.RENDERER_SCALING_FILTER.key, - IntSetting.RENDERER_SCALING_FILTER.defaultValue + R.array.rendererScalingFilterValues ) ) add( @@ -280,9 +214,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) R.string.renderer_anti_aliasing, 0, R.array.rendererAntiAliasingNames, - R.array.rendererAntiAliasingValues, - IntSetting.RENDERER_ANTI_ALIASING.key, - IntSetting.RENDERER_ANTI_ALIASING.defaultValue + R.array.rendererAntiAliasingValues ) ) add( @@ -291,9 +223,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) R.string.renderer_screen_layout, 0, R.array.rendererScreenLayoutNames, - R.array.rendererScreenLayoutValues, - IntSetting.RENDERER_SCREEN_LAYOUT.key, - IntSetting.RENDERER_SCREEN_LAYOUT.defaultValue + R.array.rendererScreenLayoutValues ) ) add( @@ -302,45 +232,35 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) R.string.renderer_aspect_ratio, 0, R.array.rendererAspectRatioNames, - R.array.rendererAspectRatioValues, - IntSetting.RENDERER_ASPECT_RATIO.key, - IntSetting.RENDERER_ASPECT_RATIO.defaultValue + R.array.rendererAspectRatioValues ) ) add( SwitchSetting( BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE, R.string.use_disk_shader_cache, - R.string.use_disk_shader_cache_description, - BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE.key, - BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE.defaultValue + R.string.use_disk_shader_cache_description ) ) add( SwitchSetting( BooleanSetting.RENDERER_FORCE_MAX_CLOCK, R.string.renderer_force_max_clock, - R.string.renderer_force_max_clock_description, - BooleanSetting.RENDERER_FORCE_MAX_CLOCK.key, - BooleanSetting.RENDERER_FORCE_MAX_CLOCK.defaultValue + R.string.renderer_force_max_clock_description ) ) add( SwitchSetting( BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS, R.string.renderer_asynchronous_shaders, - R.string.renderer_asynchronous_shaders_description, - BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS.key, - BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS.defaultValue + R.string.renderer_asynchronous_shaders_description ) ) add( SwitchSetting( BooleanSetting.RENDERER_REACTIVE_FLUSHING, R.string.renderer_reactive_flushing, - R.string.renderer_reactive_flushing_description, - BooleanSetting.RENDERER_REACTIVE_FLUSHING.key, - BooleanSetting.RENDERER_REACTIVE_FLUSHING.defaultValue + R.string.renderer_reactive_flushing_description ) ) } @@ -355,9 +275,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) R.string.audio_output_engine, 0, R.array.outputEngineEntries, - R.array.outputEngineValues, - IntSetting.AUDIO_OUTPUT_ENGINE.key, - IntSetting.AUDIO_OUTPUT_ENGINE.defaultValue + R.array.outputEngineValues ) ) add( @@ -367,9 +285,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) R.string.audio_volume_description, 0, 100, - "%", - ByteSetting.AUDIO_VOLUME.key, - ByteSetting.AUDIO_VOLUME.defaultValue + "%" ) ) } @@ -392,7 +308,12 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) override val key: String? = null override val category = Settings.Category.UiGeneral override val isRuntimeModifiable: Boolean = false - override val defaultValue: Any = 0 + override val defaultValue: Int = 0 + override fun reset() { + preferences.edit() + .putInt(Settings.PREF_THEME, defaultValue) + .apply() + } } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { @@ -431,7 +352,12 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) override val key: String? = null override val category = Settings.Category.UiGeneral override val isRuntimeModifiable: Boolean = false - override val defaultValue: Any = -1 + override val defaultValue: Int = -1 + override fun reset() { + preferences.edit() + .putInt(Settings.PREF_BLACK_BACKGROUNDS, defaultValue) + .apply() + } } add( @@ -458,7 +384,12 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) override val key: String? = null override val category = Settings.Category.UiGeneral override val isRuntimeModifiable: Boolean = false - override val defaultValue: Any = false + override val defaultValue: Boolean = false + override fun reset() { + preferences.edit() + .putBoolean(Settings.PREF_BLACK_BACKGROUNDS, defaultValue) + .apply() + } } add( @@ -481,18 +412,14 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) R.string.renderer_api, 0, R.array.rendererApiNames, - R.array.rendererApiValues, - IntSetting.RENDERER_BACKEND.key, - IntSetting.RENDERER_BACKEND.defaultValue + R.array.rendererApiValues ) ) add( SwitchSetting( BooleanSetting.RENDERER_DEBUG, R.string.renderer_debug, - R.string.renderer_debug_description, - BooleanSetting.RENDERER_DEBUG.key, - BooleanSetting.RENDERER_DEBUG.defaultValue + R.string.renderer_debug_description ) ) @@ -501,9 +428,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) SwitchSetting( BooleanSetting.CPU_DEBUG_MODE, R.string.cpu_debug_mode, - R.string.cpu_debug_mode_description, - BooleanSetting.CPU_DEBUG_MODE.key, - BooleanSetting.CPU_DEBUG_MODE.defaultValue + R.string.cpu_debug_mode_description ) ) @@ -520,15 +445,10 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) override val key: String? = null override val category = Settings.Category.Cpu override val isRuntimeModifiable: Boolean = false - override val defaultValue: Any = true + override val defaultValue: Boolean = true + override fun reset() = setBoolean(defaultValue) } - add( - SwitchSetting( - fastmem, - R.string.fastmem, - 0 - ) - ) + add(SwitchSetting(fastmem, R.string.fastmem, 0)) } } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt index eb25ea4fb..68c0b24d6 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt @@ -46,7 +46,7 @@ class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA override fun onLongClick(clicked: View): Boolean { if (setting.isEditable) { - return adapter.onLongClick(setting.setting!!, bindingAdapterPosition) + return adapter.onLongClick(setting.setting, bindingAdapterPosition) } return false } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt index b42d955aa..a582c425b 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt @@ -35,7 +35,7 @@ class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: Setti } } } else if (item is StringSingleChoiceSetting) { - for (i in item.values!!.indices) { + for (i in item.values.indices) { if (item.values[i] == item.selectedValue) { binding.textSettingValue.text = item.choices[i] break @@ -66,7 +66,7 @@ class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: Setti override fun onLongClick(clicked: View): Boolean { if (setting.isEditable) { - return adapter.onLongClick(setting.setting!!, bindingAdapterPosition) + return adapter.onLongClick(setting.setting, bindingAdapterPosition) } return false } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SliderViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SliderViewHolder.kt index a23b5d109..d94a46262 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SliderViewHolder.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SliderViewHolder.kt @@ -41,7 +41,7 @@ class SliderViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAda override fun onLongClick(clicked: View): Boolean { if (setting.isEditable) { - return adapter.onLongClick(setting.setting!!, bindingAdapterPosition) + return adapter.onLongClick(setting.setting, bindingAdapterPosition) } return false } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt index ef34bf5f4..200fbc473 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt @@ -28,7 +28,7 @@ class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter binding.switchWidget.setOnCheckedChangeListener { _: CompoundButton, _: Boolean -> adapter.onBooleanClick(item, bindingAdapterPosition, binding.switchWidget.isChecked) } - binding.switchWidget.isChecked = setting.isChecked + binding.switchWidget.isChecked = setting.checked setStyle(setting.isEditable, binding) } -- cgit v1.2.3 From 95a939a49f798c2b4b04d6f8eae33c1d093d43b4 Mon Sep 17 00:00:00 2001 From: Charles Lombardo Date: Tue, 22 Aug 2023 19:46:07 -0400 Subject: android: Migrate settings to navigation component Consolidates all of the settings components to the fragment and activity with no interfaces and only the settings fragment presenter. This also includes new material animations and new viewmodel usage to prevent the fragment and activity directly interacting with one another. --- .../main/java/org/yuzu/yuzu_emu/YuzuApplication.kt | 2 +- .../yuzu/yuzu_emu/activities/EmulationActivity.kt | 4 +- .../features/settings/ui/SettingsActivity.kt | 176 ++++++++------------- .../settings/ui/SettingsActivityPresenter.kt | 81 ---------- .../features/settings/ui/SettingsActivityView.kt | 38 ----- .../features/settings/ui/SettingsAdapter.kt | 47 +++--- .../features/settings/ui/SettingsFragment.kt | 112 +++++++------ .../settings/ui/SettingsFragmentPresenter.kt | 68 ++++---- .../features/settings/ui/SettingsFragmentView.kt | 42 ----- .../yuzu/yuzu_emu/fragments/EmulationFragment.kt | 11 +- .../yuzu_emu/fragments/HomeSettingsFragment.kt | 19 ++- .../org/yuzu/yuzu_emu/model/SettingsViewModel.kt | 47 ++++++ .../java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt | 14 +- .../yuzu/yuzu_emu/utils/DirectoryInitialization.kt | 10 +- .../anim-ldrtl/anim_pop_settings_fragment_out.xml | 16 -- .../res/anim-ldrtl/anim_settings_fragment_in.xml | 16 -- .../res/anim/anim_pop_settings_fragment_out.xml | 16 -- .../main/res/anim/anim_settings_fragment_in.xml | 16 -- .../main/res/anim/anim_settings_fragment_out.xml | 10 -- .../main/res/animator/menu_slide_in_from_start.xml | 20 --- .../main/res/animator/menu_slide_out_to_start.xml | 21 --- .../app/src/main/res/layout/activity_settings.xml | 52 +++--- .../app/src/main/res/layout/fragment_settings.xml | 39 ++++- .../app/src/main/res/menu/menu_settings.xml | 2 - .../main/res/navigation/emulation_navigation.xml | 17 ++ .../src/main/res/navigation/home_navigation.xml | 17 ++ .../main/res/navigation/settings_navigation.xml | 24 +++ src/android/app/src/main/res/values/strings.xml | 1 + 28 files changed, 371 insertions(+), 567 deletions(-) delete mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityPresenter.kt delete mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityView.kt delete mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentView.kt create mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt delete mode 100644 src/android/app/src/main/res/anim-ldrtl/anim_pop_settings_fragment_out.xml delete mode 100644 src/android/app/src/main/res/anim-ldrtl/anim_settings_fragment_in.xml delete mode 100644 src/android/app/src/main/res/anim/anim_pop_settings_fragment_out.xml delete mode 100644 src/android/app/src/main/res/anim/anim_settings_fragment_in.xml delete mode 100644 src/android/app/src/main/res/anim/anim_settings_fragment_out.xml delete mode 100644 src/android/app/src/main/res/animator/menu_slide_in_from_start.xml delete mode 100644 src/android/app/src/main/res/animator/menu_slide_out_to_start.xml delete mode 100644 src/android/app/src/main/res/menu/menu_settings.xml create mode 100644 src/android/app/src/main/res/navigation/settings_navigation.xml (limited to 'src') diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt index 04ab6a220..9561748cb 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt @@ -46,7 +46,7 @@ class YuzuApplication : Application() { super.onCreate() application = this documentsTree = DocumentsTree() - DirectoryInitialization.start(applicationContext) + DirectoryInitialization.start() GpuDriverHelper.initializeDriverParameters(applicationContext) NativeLibrary.logDeviceInfo() diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt index 6f52a7a8d..dbd602a1d 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt @@ -85,9 +85,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener { val navHostFragment = supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment - val navController = navHostFragment.navController - navController - .setGraph(R.navigation.emulation_navigation, intent.extras) + navHostFragment.navController.setGraph(R.navigation.emulation_navigation, intent.extras) isActivityRecreated = savedInstanceState != null diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt index 733a53c8c..7fd83f5f7 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt @@ -3,10 +3,7 @@ package org.yuzu.yuzu_emu.features.settings.ui -import android.content.Context -import android.content.Intent import android.os.Bundle -import android.view.Menu import android.view.View import android.view.ViewGroup.MarginLayoutParams import android.widget.Toast @@ -16,20 +13,26 @@ import androidx.appcompat.app.AppCompatActivity import androidx.core.view.ViewCompat import androidx.core.view.WindowCompat import androidx.core.view.WindowInsetsCompat -import androidx.core.view.updatePadding +import androidx.navigation.fragment.NavHostFragment +import androidx.navigation.navArgs import com.google.android.material.color.MaterialColors +import org.yuzu.yuzu_emu.NativeLibrary import java.io.IOException import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding import org.yuzu.yuzu_emu.features.settings.model.Settings import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile +import org.yuzu.yuzu_emu.fragments.ResetSettingsDialogFragment +import org.yuzu.yuzu_emu.model.SettingsViewModel import org.yuzu.yuzu_emu.utils.* -class SettingsActivity : AppCompatActivity(), SettingsActivityView { - private val presenter = SettingsActivityPresenter(this) - +class SettingsActivity : AppCompatActivity() { private lateinit var binding: ActivitySettingsBinding + private val args by navArgs() + + private val settingsViewModel: SettingsViewModel by viewModels() + override fun onCreate(savedInstanceState: Bundle?) { ThemeHelper.setTheme(this) @@ -38,16 +41,17 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView { binding = ActivitySettingsBinding.inflate(layoutInflater) setContentView(binding.root) - WindowCompat.setDecorFitsSystemWindows(window, false) + settingsViewModel.game = args.game - val launcher = intent - val gameID = launcher.getStringExtra(ARG_GAME_ID) - val menuTag = launcher.getStringExtra(ARG_MENU_TAG) - presenter.onCreate(savedInstanceState, menuTag!!, gameID!!) + val navHostFragment = + supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment + navHostFragment.navController.setGraph(R.navigation.settings_navigation, intent.extras) - // Show "Back" button in the action bar for navigation - setSupportActionBar(binding.toolbarSettings) - supportActionBar!!.setDisplayHomeAsUpEnabled(true) + WindowCompat.setDecorFitsSystemWindows(window, false) + + if (savedInstanceState != null) { + settingsViewModel.shouldSave = savedInstanceState.getBoolean(KEY_SHOULD_SAVE) + } if (InsetsHelper.getSystemGestureType(applicationContext) != InsetsHelper.GESTURE_NAVIGATION @@ -63,6 +67,28 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView { ) } + settingsViewModel.shouldRecreate.observe(this) { + if (it) { + settingsViewModel.setShouldRecreate(false) + recreate() + } + } + settingsViewModel.shouldNavigateBack.observe(this) { + if (it) { + settingsViewModel.setShouldNavigateBack(false) + navigateBack() + } + } + settingsViewModel.shouldShowResetSettingsDialog.observe(this) { + if (it) { + settingsViewModel.setShouldShowResetSettingsDialog(false) + ResetSettingsDialogFragment().show( + supportFragmentManager, + ResetSettingsDialogFragment.TAG + ) + } + } + onBackPressedDispatcher.addCallback( this, object : OnBackPressedCallback(true) { @@ -73,34 +99,28 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView { setInsets() } - override fun onSupportNavigateUp(): Boolean { - navigateBack() - return true - } - - private fun navigateBack() { - if (supportFragmentManager.backStackEntryCount > 0) { - supportFragmentManager.popBackStack() + fun navigateBack() { + val navHostFragment = + supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment + if (navHostFragment.childFragmentManager.backStackEntryCount > 0) { + navHostFragment.navController.popBackStack() } else { finish() } } - override fun onCreateOptionsMenu(menu: Menu): Boolean { - val inflater = menuInflater - inflater.inflate(R.menu.menu_settings, menu) - return true - } - override fun onSaveInstanceState(outState: Bundle) { // Critical: If super method is not called, rotations will be busted. super.onSaveInstanceState(outState) - presenter.saveState(outState) + outState.putBoolean(KEY_SHOULD_SAVE, settingsViewModel.shouldSave) } override fun onStart() { super.onStart() - presenter.onStart() + // TODO: Load custom settings contextually + if (!DirectoryInitialization.areDirectoriesReady) { + DirectoryInitialization.start() + } } /** @@ -110,65 +130,21 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView { */ override fun onStop() { super.onStop() - presenter.onStop(isFinishing) - } - - override fun showSettingsFragment(menuTag: String, addToStack: Boolean, gameId: String) { - if (!addToStack && settingsFragment != null) { - return - } - - val transaction = supportFragmentManager.beginTransaction() - if (addToStack) { - if (areSystemAnimationsEnabled()) { - transaction.setCustomAnimations( - R.anim.anim_settings_fragment_in, - R.anim.anim_settings_fragment_out, - 0, - R.anim.anim_pop_settings_fragment_out - ) - } - transaction.addToBackStack(null) + if (isFinishing && settingsViewModel.shouldSave) { + Log.debug("[SettingsActivity] Settings activity stopping. Saving settings to INI...") + Settings.saveSettings() + NativeLibrary.reloadSettings() } - transaction.replace( - R.id.frame_content, - SettingsFragment.newInstance(menuTag, gameId), - FRAGMENT_TAG - ) - transaction.commit() - } - - private fun areSystemAnimationsEnabled(): Boolean { - val duration = android.provider.Settings.Global.getFloat( - contentResolver, - android.provider.Settings.Global.ANIMATOR_DURATION_SCALE, - 1f - ) - val transition = android.provider.Settings.Global.getFloat( - contentResolver, - android.provider.Settings.Global.TRANSITION_ANIMATION_SCALE, - 1f - ) - return duration != 0f && transition != 0f } - override fun onSettingsFileLoaded() { - val fragment: SettingsFragmentView? = settingsFragment - fragment?.loadSettingsList() - } - - override fun onSettingsFileNotFound() { - val fragment: SettingsFragmentView? = settingsFragment - fragment?.loadSettingsList() - } - - override fun onSettingChanged() { - presenter.onSettingChanged() + override fun onDestroy() { + settingsViewModel.clear() + super.onDestroy() } fun onSettingsReset() { // Prevents saving to a non-existent settings file - presenter.onSettingsReset() + settingsViewModel.shouldSave = false // 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) @@ -185,47 +161,21 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView { finish() } - fun setToolbarTitle(title: String) { - binding.toolbarSettingsLayout.title = title - } - - private val settingsFragment: SettingsFragment? - get() = supportFragmentManager.findFragmentByTag(FRAGMENT_TAG) as SettingsFragment? - private fun setInsets() { ViewCompat.setOnApplyWindowInsetsListener( - binding.frameContent + binding.navigationBarShade ) { view: View, windowInsets: WindowInsetsCompat -> val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) - val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) - view.updatePadding( - left = barInsets.left + cutoutInsets.left, - right = barInsets.right + cutoutInsets.right - ) - val mlpAppBar = binding.appbarSettings.layoutParams as MarginLayoutParams - mlpAppBar.leftMargin = barInsets.left + cutoutInsets.left - mlpAppBar.rightMargin = barInsets.right + cutoutInsets.right - binding.appbarSettings.layoutParams = mlpAppBar - - val mlpShade = binding.navigationBarShade.layoutParams as MarginLayoutParams + val mlpShade = view.layoutParams as MarginLayoutParams mlpShade.height = barInsets.bottom - binding.navigationBarShade.layoutParams = mlpShade + view.layoutParams = mlpShade windowInsets } } companion object { - private const val ARG_MENU_TAG = "menu_tag" - private const val ARG_GAME_ID = "game_id" - private const val FRAGMENT_TAG = "settings" - - fun launch(context: Context, menuTag: String?, gameId: String?) { - val settings = Intent(context, SettingsActivity::class.java) - settings.putExtra(ARG_MENU_TAG, menuTag) - settings.putExtra(ARG_GAME_ID, gameId) - context.startActivity(settings) - } + private const val KEY_SHOULD_SAVE = "should_save" } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityPresenter.kt deleted file mode 100644 index fdbad32bf..000000000 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityPresenter.kt +++ /dev/null @@ -1,81 +0,0 @@ -// SPDX-FileCopyrightText: 2023 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.yuzu.yuzu_emu.features.settings.ui - -import android.content.Context -import android.os.Bundle -import java.io.File -import org.yuzu.yuzu_emu.NativeLibrary -import org.yuzu.yuzu_emu.features.settings.model.Settings -import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile -import org.yuzu.yuzu_emu.utils.DirectoryInitialization -import org.yuzu.yuzu_emu.utils.Log - -class SettingsActivityPresenter(private val activityView: SettingsActivityView) { - private var shouldSave = false - private lateinit var menuTag: String - private lateinit var gameId: String - - fun onCreate(savedInstanceState: Bundle?, menuTag: String, gameId: String) { - this.menuTag = menuTag - this.gameId = gameId - if (savedInstanceState != null) { - shouldSave = savedInstanceState.getBoolean(KEY_SHOULD_SAVE) - } - } - - fun onStart() { - prepareDirectoriesIfNeeded() - } - - private fun loadSettingsUI() { - // TODO: Load custom settings contextually - activityView.showSettingsFragment(menuTag, false, gameId) - activityView.onSettingsFileLoaded() - } - - private fun prepareDirectoriesIfNeeded() { - val configFile = - File( - "${DirectoryInitialization.userDirectory}/config/" + - "${SettingsFile.FILE_NAME_CONFIG}.ini" - ) - if (!configFile.exists()) { - Log.error( - "${DirectoryInitialization.userDirectory}/config/" + - "${SettingsFile.FILE_NAME_CONFIG}.ini" - ) - Log.error("yuzu config file could not be found!") - } - - if (!DirectoryInitialization.areDirectoriesReady) { - DirectoryInitialization.start(activityView as Context) - } - loadSettingsUI() - } - - fun onStop(finishing: Boolean) { - if (finishing && shouldSave) { - Log.debug("[SettingsActivity] Settings activity stopping. Saving settings to INI...") - Settings.saveSettings() - NativeLibrary.reloadSettings() - } - } - - fun onSettingChanged() { - shouldSave = true - } - - fun onSettingsReset() { - shouldSave = false - } - - fun saveState(outState: Bundle) { - outState.putBoolean(KEY_SHOULD_SAVE, shouldSave) - } - - companion object { - private const val KEY_SHOULD_SAVE = "should_save" - } -} diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityView.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityView.kt deleted file mode 100644 index 07a58b4ea..000000000 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityView.kt +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-FileCopyrightText: 2023 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.yuzu.yuzu_emu.features.settings.ui - -/** - * Abstraction for the Activity that manages SettingsFragments. - */ -interface SettingsActivityView { - /** - * Show a new SettingsFragment. - * - * @param menuTag Identifier for the settings group that should be displayed. - * @param addToStack Whether or not this fragment should replace a previous one. - */ - fun showSettingsFragment(menuTag: String, addToStack: Boolean, gameId: String) - - /** - * Called when a load operation completes. - */ - fun onSettingsFileLoaded() - - /** - * Called when a load operation fails. - */ - fun onSettingsFileNotFound() - - /** - * End the activity. - */ - fun finish() - - /** - * Called by a containing Fragment to tell the Activity that a setting was changed; - * unless this has been called, the Activity will not save to disk. - */ - fun onSettingChanged() -} diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt index 27eaaa576..9883c2ec7 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt @@ -12,7 +12,8 @@ import android.view.LayoutInflater import android.view.ViewGroup import android.widget.TextView import androidx.appcompat.app.AlertDialog -import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.ViewModelProvider +import androidx.navigation.findNavController import androidx.recyclerview.widget.RecyclerView import com.google.android.material.datepicker.MaterialDatePicker import com.google.android.material.dialog.MaterialAlertDialogBuilder @@ -20,6 +21,7 @@ import com.google.android.material.slider.Slider import com.google.android.material.timepicker.MaterialTimePicker import com.google.android.material.timepicker.TimeFormat import org.yuzu.yuzu_emu.R +import org.yuzu.yuzu_emu.SettingsNavigationDirections import org.yuzu.yuzu_emu.databinding.DialogSliderBinding import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding import org.yuzu.yuzu_emu.databinding.ListItemSettingSwitchBinding @@ -30,18 +32,22 @@ import org.yuzu.yuzu_emu.features.settings.model.FloatSetting import org.yuzu.yuzu_emu.features.settings.model.ShortSetting import org.yuzu.yuzu_emu.features.settings.model.view.* import org.yuzu.yuzu_emu.features.settings.ui.viewholder.* +import org.yuzu.yuzu_emu.model.SettingsViewModel class SettingsAdapter( - private val fragmentView: SettingsFragmentView, + private val fragment: SettingsFragment, private val context: Context ) : RecyclerView.Adapter(), DialogInterface.OnClickListener { - private var settings: ArrayList? = null + private var settings = ArrayList() private var clickedItem: SettingsItem? = null private var clickedPosition: Int private var dialog: AlertDialog? = null private var sliderProgress = 0 private var textSliderValue: TextView? = null + private val settingsViewModel: SettingsViewModel + get() = ViewModelProvider(fragment.requireActivity())[SettingsViewModel::class.java] + private var defaultCancelListener = DialogInterface.OnClickListener { _: DialogInterface?, _: Int -> closeDialog() } @@ -91,30 +97,22 @@ class SettingsAdapter( holder.bind(getItem(position)) } - private fun getItem(position: Int): SettingsItem { - return settings!![position] - } + private fun getItem(position: Int): SettingsItem = settings[position] - override fun getItemCount(): Int { - return if (settings != null) { - settings!!.size - } else { - 0 - } - } + override fun getItemCount(): Int = settings.size override fun getItemViewType(position: Int): Int { return getItem(position).type } - fun setSettingsList(settings: ArrayList?) { + fun setSettingsList(settings: ArrayList) { this.settings = settings notifyDataSetChanged() } fun onBooleanClick(item: SwitchSetting, position: Int, checked: Boolean) { item.checked = checked - fragmentView.onSettingChanged() + settingsViewModel.shouldSave = true } private fun onSingleChoiceClick(item: SingleChoiceSetting) { @@ -155,7 +153,7 @@ class SettingsAdapter( calendar.timeZone = TimeZone.getTimeZone("UTC") var timeFormat: Int = TimeFormat.CLOCK_12H - if (DateFormat.is24HourFormat(fragmentView.activityView as AppCompatActivity)) { + if (DateFormat.is24HourFormat(context)) { timeFormat = TimeFormat.CLOCK_24H } @@ -172,7 +170,7 @@ class SettingsAdapter( datePicker.addOnPositiveButtonClickListener { timePicker.show( - (fragmentView.activityView as AppCompatActivity).supportFragmentManager, + fragment.childFragmentManager, "TimePicker" ) } @@ -181,14 +179,14 @@ class SettingsAdapter( epochTime += timePicker.hour.toLong() * 60 * 60 epochTime += timePicker.minute.toLong() * 60 if (item.value != epochTime) { - fragmentView.onSettingChanged() + settingsViewModel.shouldSave = true notifyItemChanged(clickedPosition) item.value = epochTime } clickedItem = null } datePicker.show( - (fragmentView.activityView as AppCompatActivity).supportFragmentManager, + fragment.childFragmentManager, "DatePicker" ) } @@ -231,7 +229,8 @@ class SettingsAdapter( } fun onSubmenuClick(item: SubmenuSetting) { - fragmentView.loadSubMenu(item.menuKey) + val action = SettingsNavigationDirections.actionGlobalSettingsFragment(item.menuKey, null) + fragment.view?.findNavController()?.navigate(action) } override fun onClick(dialog: DialogInterface, which: Int) { @@ -240,7 +239,7 @@ class SettingsAdapter( val scSetting = clickedItem as SingleChoiceSetting val value = getValueForSingleChoiceSelection(scSetting, which) if (scSetting.selectedValue != value) { - fragmentView.onSettingChanged() + settingsViewModel.shouldSave = true } // Get the backing Setting, which may be null (if for example it was missing from the file) @@ -251,7 +250,7 @@ class SettingsAdapter( is StringSingleChoiceSetting -> { val scSetting = clickedItem as StringSingleChoiceSetting val value = scSetting.getValueAt(which) - if (scSetting.selectedValue != value) fragmentView.onSettingChanged() + if (scSetting.selectedValue != value) settingsViewModel.shouldSave = true scSetting.selectedValue = value!! closeDialog() } @@ -259,7 +258,7 @@ class SettingsAdapter( is SliderSetting -> { val sliderSetting = clickedItem as SliderSetting if (sliderSetting.selectedValue != sliderProgress) { - fragmentView.onSettingChanged() + settingsViewModel.shouldSave = true } when (sliderSetting.setting) { is ByteSetting -> { @@ -294,7 +293,7 @@ class SettingsAdapter( .setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int -> setting.reset() notifyItemChanged(position) - fragmentView.onSettingChanged() + settingsViewModel.shouldSave = true } .setNegativeButton(android.R.string.cancel, null) .show() diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt index dc1bf6eb1..de6aebd9d 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt @@ -3,39 +3,41 @@ package org.yuzu.yuzu_emu.features.settings.ui -import android.content.Context import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.view.ViewGroup.MarginLayoutParams 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.navigation.fragment.navArgs import androidx.recyclerview.widget.LinearLayoutManager import com.google.android.material.divider.MaterialDividerItemDecoration +import com.google.android.material.transition.MaterialSharedAxis +import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.databinding.FragmentSettingsBinding -import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem +import org.yuzu.yuzu_emu.model.SettingsViewModel -class SettingsFragment : Fragment(), SettingsFragmentView { - override var activityView: SettingsActivityView? = null - - private val fragmentPresenter = SettingsFragmentPresenter(this) +class SettingsFragment : Fragment() { + private lateinit var presenter: SettingsFragmentPresenter private var settingsAdapter: SettingsAdapter? = null private var _binding: FragmentSettingsBinding? = null private val binding get() = _binding!! - override fun onAttach(context: Context) { - super.onAttach(context) - activityView = requireActivity() as SettingsActivityView - } + private val args by navArgs() + + private val settingsViewModel: SettingsViewModel by activityViewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - val menuTag = requireArguments().getString(ARGUMENT_MENU_TAG) - val gameId = requireArguments().getString(ARGUMENT_GAME_ID) - fragmentPresenter.onCreate(menuTag!!, gameId!!) + enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) + returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false) + reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false) + exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) } override fun onCreateView( @@ -48,7 +50,14 @@ class SettingsFragment : Fragment(), SettingsFragmentView { } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - settingsAdapter = SettingsAdapter(this, requireActivity()) + settingsAdapter = SettingsAdapter(this, requireContext()) + presenter = SettingsFragmentPresenter( + settingsViewModel, + settingsAdapter!!, + args.menuTag, + args.game?.gameId ?: "" + ) + val dividerDecoration = MaterialDividerItemDecoration( requireContext(), LinearLayoutManager.VERTICAL @@ -56,63 +65,52 @@ class SettingsFragment : Fragment(), SettingsFragmentView { dividerDecoration.isLastItemDecorated = false binding.listSettings.apply { adapter = settingsAdapter - layoutManager = LinearLayoutManager(activity) + layoutManager = LinearLayoutManager(requireContext()) addItemDecoration(dividerDecoration) } - fragmentPresenter.onViewCreated() - setInsets() - } - - override fun onDetach() { - super.onDetach() - activityView = null - if (settingsAdapter != null) { - settingsAdapter!!.closeDialog() + binding.toolbarSettings.setNavigationOnClickListener { + settingsViewModel.setShouldNavigateBack(true) } - } - override fun showSettingsList(settingsList: ArrayList) { - settingsAdapter!!.setSettingsList(settingsList) - } + settingsViewModel.toolbarTitle.observe(viewLifecycleOwner) { + if (it.isNotEmpty()) binding.toolbarSettingsLayout.title = it + } - override fun loadSettingsList() { - fragmentPresenter.loadSettingsList() - } + presenter.onViewCreated() - override fun loadSubMenu(menuKey: String) { - activityView!!.showSettingsFragment( - menuKey, - true, - requireArguments().getString(ARGUMENT_GAME_ID)!! - ) + setInsets() } - override fun onSettingChanged() { - activityView!!.onSettingChanged() + override fun onDetach() { + super.onDetach() + settingsAdapter?.closeDialog() } private fun setInsets() { ViewCompat.setOnApplyWindowInsetsListener( - binding.listSettings - ) { view: View, windowInsets: WindowInsetsCompat -> - val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) - view.updatePadding(bottom = insets.bottom) + binding.root + ) { _: View, windowInsets: WindowInsetsCompat -> + val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) + val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) + + val leftInsets = barInsets.left + cutoutInsets.left + val rightInsets = barInsets.right + cutoutInsets.right + + val sideMargin = resources.getDimensionPixelSize(R.dimen.spacing_medlarge) + val mlpSettingsList = binding.listSettings.layoutParams as MarginLayoutParams + mlpSettingsList.leftMargin = sideMargin + leftInsets + mlpSettingsList.rightMargin = sideMargin + rightInsets + binding.listSettings.layoutParams = mlpSettingsList + binding.listSettings.updatePadding( + bottom = barInsets.bottom + ) + + val mlpAppBar = binding.appbarSettings.layoutParams as MarginLayoutParams + mlpAppBar.leftMargin = leftInsets + mlpAppBar.rightMargin = rightInsets + binding.appbarSettings.layoutParams = mlpAppBar windowInsets } } - - companion object { - private const val ARGUMENT_MENU_TAG = "menu_tag" - private const val ARGUMENT_GAME_ID = "game_id" - - fun newInstance(menuTag: String?, gameId: String?): Fragment { - val fragment = SettingsFragment() - val arguments = Bundle() - arguments.putString(ARGUMENT_MENU_TAG, menuTag) - arguments.putString(ARGUMENT_GAME_ID, gameId) - fragment.arguments = arguments - return fragment - } - } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt index dddbf65bb..ba45c317d 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt @@ -3,6 +3,7 @@ package org.yuzu.yuzu_emu.features.settings.ui +import android.content.Context import android.content.SharedPreferences import android.os.Build import android.text.TextUtils @@ -20,36 +21,36 @@ import org.yuzu.yuzu_emu.features.settings.model.Settings import org.yuzu.yuzu_emu.features.settings.model.ShortSetting import org.yuzu.yuzu_emu.features.settings.model.view.* import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile -import org.yuzu.yuzu_emu.fragments.ResetSettingsDialogFragment -import org.yuzu.yuzu_emu.utils.ThemeHelper +import org.yuzu.yuzu_emu.model.SettingsViewModel -class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) { - private var menuTag: String? = null - private lateinit var gameId: String - private var settingsList: ArrayList? = null +class SettingsFragmentPresenter( + private val settingsViewModel: SettingsViewModel, + private val adapter: SettingsAdapter, + private var menuTag: String, + private var gameId: String +) { + private var settingsList = ArrayList() - private val settingsActivity get() = fragmentView.activityView as SettingsActivity + private val preferences: SharedPreferences + get() = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) - private lateinit var preferences: SharedPreferences - - fun onCreate(menuTag: String, gameId: String) { - this.gameId = gameId - this.menuTag = menuTag - } + private val context: Context get() = YuzuApplication.appContext fun onViewCreated() { - preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) loadSettingsList() } - fun loadSettingsList() { + private fun loadSettingsList() { if (!TextUtils.isEmpty(gameId)) { - settingsActivity.setToolbarTitle("Game Settings: $gameId") + settingsViewModel.setToolbarTitle( + context.getString( + R.string.advanced_settings_game, + gameId + ) + ) } + val sl = ArrayList() - if (menuTag == null) { - return - } when (menuTag) { SettingsFile.FILE_NAME_CONFIG -> addConfigSettings(sl) Settings.SECTION_GENERAL -> addGeneralSettings(sl) @@ -69,11 +70,11 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) } } settingsList = sl - fragmentView.showSettingsList(settingsList!!) + adapter.setSettingsList(settingsList) } private fun addConfigSettings(sl: ArrayList) { - settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.advanced_settings)) + settingsViewModel.setToolbarTitle(context.getString(R.string.advanced_settings)) sl.apply { add(SubmenuSetting(R.string.preferences_general, 0, Settings.SECTION_GENERAL)) add(SubmenuSetting(R.string.preferences_system, 0, Settings.SECTION_SYSTEM)) @@ -82,17 +83,14 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) add(SubmenuSetting(R.string.preferences_debug, 0, Settings.SECTION_DEBUG)) add( RunnableSetting(R.string.reset_to_default, 0, false) { - ResetSettingsDialogFragment().show( - settingsActivity.supportFragmentManager, - ResetSettingsDialogFragment.TAG - ) + settingsViewModel.setShouldShowResetSettingsDialog(true) } ) } } private fun addGeneralSettings(sl: ArrayList) { - settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_general)) + settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_general)) sl.apply { add( SwitchSetting( @@ -131,7 +129,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) } private fun addSystemSettings(sl: ArrayList) { - settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_system)) + settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_system)) sl.apply { add( SwitchSetting( @@ -170,7 +168,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) } private fun addGraphicsSettings(sl: ArrayList) { - settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_graphics)) + settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_graphics)) sl.apply { add( SingleChoiceSetting( @@ -267,7 +265,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) } private fun addAudioSettings(sl: ArrayList) { - settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_audio)) + settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_audio)) sl.apply { add( SingleChoiceSetting( @@ -292,7 +290,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) } private fun addThemeSettings(sl: ArrayList) { - settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_theme)) + settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_theme)) sl.apply { val theme: AbstractIntSetting = object : AbstractIntSetting { override val int: Int @@ -302,7 +300,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) preferences.edit() .putInt(Settings.PREF_THEME, value) .apply() - settingsActivity.recreate() + settingsViewModel.setShouldRecreate(true) } override val key: String? = null @@ -346,7 +344,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) preferences.edit() .putInt(Settings.PREF_THEME_MODE, value) .apply() - ThemeHelper.setThemeMode(settingsActivity) + settingsViewModel.setShouldRecreate(true) } override val key: String? = null @@ -357,6 +355,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) preferences.edit() .putInt(Settings.PREF_BLACK_BACKGROUNDS, defaultValue) .apply() + settingsViewModel.setShouldRecreate(true) } } @@ -378,7 +377,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) preferences.edit() .putBoolean(Settings.PREF_BLACK_BACKGROUNDS, value) .apply() - settingsActivity.recreate() + settingsViewModel.setShouldRecreate(true) } override val key: String? = null @@ -389,6 +388,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) preferences.edit() .putBoolean(Settings.PREF_BLACK_BACKGROUNDS, defaultValue) .apply() + settingsViewModel.setShouldRecreate(true) } } @@ -403,7 +403,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) } private fun addDebugSettings(sl: ArrayList) { - settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_debug)) + settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_debug)) sl.apply { add(HeaderSetting(R.string.gpu)) add( diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentView.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentView.kt deleted file mode 100644 index a4d7a80aa..000000000 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentView.kt +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-FileCopyrightText: 2023 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.yuzu.yuzu_emu.features.settings.ui - -import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem - -/** - * Abstraction for a screen showing a list of settings. Instances of - * this type of view will each display a layer of the setting hierarchy. - */ -interface SettingsFragmentView { - /** - * Pass an ArrayList to the View so that it can be displayed on screen. - * - * @param settingsList The result of converting the HashMap to an ArrayList - */ - fun showSettingsList(settingsList: ArrayList) - - /** - * Instructs the Fragment to load the settings screen. - */ - fun loadSettingsList() - - /** - * @return The Fragment's containing activity. - */ - val activityView: SettingsActivityView? - - /** - * Tell the Fragment to tell the containing Activity to show a new - * Fragment containing a submenu of settings. - * - * @param menuKey Identifier for the settings group that should be shown. - */ - fun loadSubMenu(menuKey: String) - - /** - * Have the fragment tell the containing Activity that a setting was modified. - */ - fun onSettingChanged() -} diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt index 09e93a017..70df3af80 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt @@ -28,6 +28,7 @@ import androidx.fragment.app.Fragment import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle +import androidx.navigation.findNavController import androidx.navigation.fragment.navArgs import androidx.preference.PreferenceManager import androidx.window.layout.FoldingFeature @@ -37,6 +38,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.slider.Slider import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import org.yuzu.yuzu_emu.HomeNavigationDirections import org.yuzu.yuzu_emu.NativeLibrary import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.YuzuApplication @@ -45,7 +47,6 @@ import org.yuzu.yuzu_emu.databinding.DialogOverlayAdjustBinding import org.yuzu.yuzu_emu.databinding.FragmentEmulationBinding import org.yuzu.yuzu_emu.features.settings.model.IntSetting import org.yuzu.yuzu_emu.features.settings.model.Settings -import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile import org.yuzu.yuzu_emu.overlay.InputOverlay import org.yuzu.yuzu_emu.utils.* @@ -139,7 +140,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { } R.id.menu_settings -> { - SettingsActivity.launch(requireContext(), SettingsFile.FILE_NAME_CONFIG, "") + val action = HomeNavigationDirections.actionGlobalSettingsActivity( + null, + SettingsFile.FILE_NAME_CONFIG + ) + binding.root.findNavController().navigate(action) true } @@ -211,7 +216,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { override fun onResume() { super.onResume() if (!DirectoryInitialization.areDirectoriesReady) { - DirectoryInitialization.start(requireContext()) + DirectoryInitialization.start() } updateScreenLayout() diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt index d5e793491..cbbe14d22 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt @@ -25,17 +25,18 @@ import androidx.core.view.updatePadding import androidx.documentfile.provider.DocumentFile import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels +import androidx.navigation.findNavController import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.LinearLayoutManager import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.transition.MaterialSharedAxis import org.yuzu.yuzu_emu.BuildConfig +import org.yuzu.yuzu_emu.HomeNavigationDirections import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.adapters.HomeSettingAdapter import org.yuzu.yuzu_emu.databinding.FragmentHomeSettingsBinding import org.yuzu.yuzu_emu.features.DocumentProvider import org.yuzu.yuzu_emu.features.settings.model.Settings -import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile import org.yuzu.yuzu_emu.model.HomeSetting import org.yuzu.yuzu_emu.model.HomeViewModel @@ -74,7 +75,13 @@ class HomeSettingsFragment : Fragment() { R.string.advanced_settings, R.string.settings_description, R.drawable.ic_settings, - { SettingsActivity.launch(requireContext(), SettingsFile.FILE_NAME_CONFIG, "") } + { + val action = HomeNavigationDirections.actionGlobalSettingsActivity( + null, + SettingsFile.FILE_NAME_CONFIG + ) + binding.root.findNavController().navigate(action) + } ) ) add( @@ -90,7 +97,13 @@ class HomeSettingsFragment : Fragment() { R.string.preferences_theme, R.string.theme_and_color_description, R.drawable.ic_palette, - { SettingsActivity.launch(requireContext(), Settings.SECTION_THEME, "") } + { + val action = HomeNavigationDirections.actionGlobalSettingsActivity( + null, + Settings.SECTION_THEME + ) + binding.root.findNavController().navigate(action) + } ) ) add( diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt new file mode 100644 index 000000000..1763341e2 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText: 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.yuzu.yuzu_emu.model + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel + +class SettingsViewModel : ViewModel() { + var game: Game? = null + + var shouldSave = false + + private val _toolbarTitle = MutableLiveData("") + val toolbarTitle: LiveData get() = _toolbarTitle + + private val _shouldRecreate = MutableLiveData(false) + val shouldRecreate: LiveData get() = _shouldRecreate + + private val _shouldNavigateBack = MutableLiveData(false) + val shouldNavigateBack: LiveData get() = _shouldNavigateBack + + private val _shouldShowResetSettingsDialog = MutableLiveData(false) + val shouldShowResetSettingsDialog: LiveData get() = _shouldShowResetSettingsDialog + + fun setToolbarTitle(value: String) { + _toolbarTitle.value = value + } + + fun setShouldRecreate(value: Boolean) { + _shouldRecreate.value = value + } + + fun setShouldNavigateBack(value: Boolean) { + _shouldNavigateBack.value = value + } + + fun setShouldShowResetSettingsDialog(value: Boolean) { + _shouldShowResetSettingsDialog.value = value + } + + fun clear() { + game = null + shouldSave = false + } +} diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt index d8dbf1f45..7735452e5 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt @@ -33,13 +33,13 @@ import java.io.IOException import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import org.yuzu.yuzu_emu.HomeNavigationDirections import org.yuzu.yuzu_emu.NativeLibrary import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.activities.EmulationActivity import org.yuzu.yuzu_emu.databinding.ActivityMainBinding import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding import org.yuzu.yuzu_emu.features.settings.model.Settings -import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment import org.yuzu.yuzu_emu.fragments.LongMessageDialogFragment @@ -105,11 +105,13 @@ class MainActivity : AppCompatActivity(), ThemeProvider { when (it.itemId) { R.id.gamesFragment -> gamesViewModel.setShouldScrollToTop(true) R.id.searchFragment -> gamesViewModel.setSearchFocused(true) - R.id.homeSettingsFragment -> SettingsActivity.launch( - this, - SettingsFile.FILE_NAME_CONFIG, - "" - ) + R.id.homeSettingsFragment -> { + val action = HomeNavigationDirections.actionGlobalSettingsActivity( + null, + SettingsFile.FILE_NAME_CONFIG + ) + navHostFragment.navController.navigate(action) + } } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt index 2ee63697e..3c9f6bad0 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt @@ -3,18 +3,18 @@ package org.yuzu.yuzu_emu.utils -import android.content.Context import java.io.IOException import org.yuzu.yuzu_emu.NativeLibrary +import org.yuzu.yuzu_emu.YuzuApplication object DirectoryInitialization { private var userPath: String? = null var areDirectoriesReady: Boolean = false - fun start(context: Context) { + fun start() { if (!areDirectoriesReady) { - initializeInternalStorage(context) + initializeInternalStorage() NativeLibrary.initializeEmulation() areDirectoriesReady = true } @@ -26,9 +26,9 @@ object DirectoryInitialization { return userPath } - private fun initializeInternalStorage(context: Context) { + private fun initializeInternalStorage() { try { - userPath = context.getExternalFilesDir(null)!!.canonicalPath + userPath = YuzuApplication.appContext.getExternalFilesDir(null)!!.canonicalPath NativeLibrary.setAppDirectory(userPath!!) } catch (e: IOException) { e.printStackTrace() diff --git a/src/android/app/src/main/res/anim-ldrtl/anim_pop_settings_fragment_out.xml b/src/android/app/src/main/res/anim-ldrtl/anim_pop_settings_fragment_out.xml deleted file mode 100644 index 9f49c133a..000000000 --- a/src/android/app/src/main/res/anim-ldrtl/anim_pop_settings_fragment_out.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - diff --git a/src/android/app/src/main/res/anim-ldrtl/anim_settings_fragment_in.xml b/src/android/app/src/main/res/anim-ldrtl/anim_settings_fragment_in.xml deleted file mode 100644 index 82fd719db..000000000 --- a/src/android/app/src/main/res/anim-ldrtl/anim_settings_fragment_in.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - diff --git a/src/android/app/src/main/res/anim/anim_pop_settings_fragment_out.xml b/src/android/app/src/main/res/anim/anim_pop_settings_fragment_out.xml deleted file mode 100644 index 5892128f1..000000000 --- a/src/android/app/src/main/res/anim/anim_pop_settings_fragment_out.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - diff --git a/src/android/app/src/main/res/anim/anim_settings_fragment_in.xml b/src/android/app/src/main/res/anim/anim_settings_fragment_in.xml deleted file mode 100644 index 98e0cf8bd..000000000 --- a/src/android/app/src/main/res/anim/anim_settings_fragment_in.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - diff --git a/src/android/app/src/main/res/anim/anim_settings_fragment_out.xml b/src/android/app/src/main/res/anim/anim_settings_fragment_out.xml deleted file mode 100644 index 77a40a4d1..000000000 --- a/src/android/app/src/main/res/anim/anim_settings_fragment_out.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - diff --git a/src/android/app/src/main/res/animator/menu_slide_in_from_start.xml b/src/android/app/src/main/res/animator/menu_slide_in_from_start.xml deleted file mode 100644 index 4612aee13..000000000 --- a/src/android/app/src/main/res/animator/menu_slide_in_from_start.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/src/android/app/src/main/res/animator/menu_slide_out_to_start.xml b/src/android/app/src/main/res/animator/menu_slide_out_to_start.xml deleted file mode 100644 index c00478946..000000000 --- a/src/android/app/src/main/res/animator/menu_slide_out_to_start.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/src/android/app/src/main/res/layout/activity_settings.xml b/src/android/app/src/main/res/layout/activity_settings.xml index 14ae83b04..8a026a30a 100644 --- a/src/android/app/src/main/res/layout/activity_settings.xml +++ b/src/android/app/src/main/res/layout/activity_settings.xml @@ -1,42 +1,24 @@ - - - - - - - - - - - - + + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" /> - + diff --git a/src/android/app/src/main/res/layout/fragment_settings.xml b/src/android/app/src/main/res/layout/fragment_settings.xml index 167720347..ebedbf1ec 100644 --- a/src/android/app/src/main/res/layout/fragment_settings.xml +++ b/src/android/app/src/main/res/layout/fragment_settings.xml @@ -1,14 +1,41 @@ - + android:layout_height="match_parent" + android:background="?attr/colorSurface"> + + + + + + + + + + + android:clipToPadding="false" + app:layout_behavior="@string/appbar_scrolling_view_behavior" /> - + diff --git a/src/android/app/src/main/res/menu/menu_settings.xml b/src/android/app/src/main/res/menu/menu_settings.xml deleted file mode 100644 index 1fe7aa6d4..000000000 --- a/src/android/app/src/main/res/menu/menu_settings.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/src/android/app/src/main/res/navigation/emulation_navigation.xml b/src/android/app/src/main/res/navigation/emulation_navigation.xml index 8208f4c2c..2a3c23d55 100644 --- a/src/android/app/src/main/res/navigation/emulation_navigation.xml +++ b/src/android/app/src/main/res/navigation/emulation_navigation.xml @@ -15,4 +15,21 @@ app:argType="org.yuzu.yuzu_emu.model.Game" /> + + + + + + + diff --git a/src/android/app/src/main/res/navigation/home_navigation.xml b/src/android/app/src/main/res/navigation/home_navigation.xml index fcebba726..fb8e14448 100644 --- a/src/android/app/src/main/res/navigation/home_navigation.xml +++ b/src/android/app/src/main/res/navigation/home_navigation.xml @@ -70,4 +70,21 @@ app:destination="@id/emulationActivity" app:launchSingleTop="true" /> + + + + + + + diff --git a/src/android/app/src/main/res/navigation/settings_navigation.xml b/src/android/app/src/main/res/navigation/settings_navigation.xml new file mode 100644 index 000000000..b36200c65 --- /dev/null +++ b/src/android/app/src/main/res/navigation/settings_navigation.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index df76563fc..6b782780a 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -74,6 +74,7 @@ Install GPU driver Install alternative drivers for potentially better performance or accuracy Advanced settings + Advanced settings: %1$s Configure emulator settings Recently played Recently added -- cgit v1.2.3 From 369d06292fae1ac37dfb2863deb16978f29094ad Mon Sep 17 00:00:00 2001 From: Charles Lombardo Date: Tue, 22 Aug 2023 19:49:02 -0400 Subject: android: Prevent infinite switch toggle loop If something like a lifecycle event happens when this switch is toggled (Ex. whenever the black backgrounds switch is toggled), this could move the switch from the default position and trigger the checked changed listener and restart the loop. Here I just removed the listener at the start so we recycle the view properly still, set the checked state and then add the new listener. --- .../features/settings/ui/viewholder/SwitchSettingViewHolder.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt index 200fbc473..25b689d66 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt @@ -25,10 +25,12 @@ class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter binding.textSettingDescription.text = "" binding.textSettingDescription.visibility = View.GONE } + + binding.switchWidget.setOnCheckedChangeListener(null) + binding.switchWidget.isChecked = setting.checked binding.switchWidget.setOnCheckedChangeListener { _: CompoundButton, _: Boolean -> adapter.onBooleanClick(item, bindingAdapterPosition, binding.switchWidget.isChecked) } - binding.switchWidget.isChecked = setting.checked setStyle(setting.isEditable, binding) } -- cgit v1.2.3 From d786d19880fbeddf4356d978ec858c4c4874935a Mon Sep 17 00:00:00 2001 From: Charles Lombardo Date: Tue, 22 Aug 2023 23:32:10 -0400 Subject: android: Implement paired settings Enables and disables editing on settings that rely on other boolean settings. --- .../features/settings/model/AbstractSetting.kt | 7 +- .../features/settings/model/view/SettingsItem.kt | 235 ++++++++++++++++++ .../features/settings/ui/SettingsAdapter.kt | 34 +-- .../features/settings/ui/SettingsFragment.kt | 7 + .../settings/ui/SettingsFragmentPresenter.kt | 265 ++++----------------- .../ui/viewholder/SwitchSettingViewHolder.kt | 4 +- .../org/yuzu/yuzu_emu/model/SettingsViewModel.kt | 7 + .../java/org/yuzu/yuzu_emu/utils/NativeConfig.kt | 2 + src/android/app/src/main/jni/native_config.cpp | 15 +- 9 files changed, 335 insertions(+), 241 deletions(-) (limited to 'src') diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractSetting.kt index 724a2ecb8..73215c247 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractSetting.kt @@ -6,14 +6,17 @@ package org.yuzu.yuzu_emu.features.settings.model import org.yuzu.yuzu_emu.utils.NativeConfig interface AbstractSetting { - val key: String? + val key: String val category: Settings.Category val defaultValue: Any val valueAsString: String get() = "" val isRuntimeModifiable: Boolean - get() = NativeConfig.getIsRuntimeModifiable(key!!) + get() = NativeConfig.getIsRuntimeModifiable(key) + + val pairedSettingKey: String + get() = NativeConfig.getPairedSettingKey(key) fun reset() } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt index 3bdcc5bca..b3b3fc209 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt @@ -4,8 +4,15 @@ package org.yuzu.yuzu_emu.features.settings.model.view import org.yuzu.yuzu_emu.NativeLibrary +import org.yuzu.yuzu_emu.R +import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting +import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting +import org.yuzu.yuzu_emu.features.settings.model.ByteSetting +import org.yuzu.yuzu_emu.features.settings.model.IntSetting +import org.yuzu.yuzu_emu.features.settings.model.LongSetting import org.yuzu.yuzu_emu.features.settings.model.Settings +import org.yuzu.yuzu_emu.features.settings.model.ShortSetting /** * ViewModel abstraction for an Item in the RecyclerView powering SettingsFragments. @@ -37,11 +44,239 @@ abstract class SettingsItem( const val TYPE_DATETIME_SETTING = 6 const val TYPE_RUNNABLE = 7 + const val FASTMEM_COMBINED = "fastmem_combined" + val emptySetting = object : AbstractSetting { override val key: String = "" override val category: Settings.Category = Settings.Category.Ui override val defaultValue: Any = false override fun reset() {} } + + // Extension for putting SettingsItems into a hashmap without repeating yourself + fun HashMap.put(item: SettingsItem) { + put(item.setting.key, item) + } + + // List of all general + val settingsItems = HashMap().apply { + put( + SwitchSetting( + BooleanSetting.RENDERER_USE_SPEED_LIMIT, + R.string.frame_limit_enable, + R.string.frame_limit_enable_description + ) + ) + put( + SliderSetting( + ShortSetting.RENDERER_SPEED_LIMIT, + R.string.frame_limit_slider, + R.string.frame_limit_slider_description, + 1, + 200, + "%" + ) + ) + put( + SingleChoiceSetting( + IntSetting.CPU_ACCURACY, + R.string.cpu_accuracy, + 0, + R.array.cpuAccuracyNames, + R.array.cpuAccuracyValues + ) + ) + put( + SwitchSetting( + BooleanSetting.PICTURE_IN_PICTURE, + R.string.picture_in_picture, + R.string.picture_in_picture_description + ) + ) + put( + SwitchSetting( + BooleanSetting.USE_DOCKED_MODE, + R.string.use_docked_mode, + R.string.use_docked_mode_description + ) + ) + put( + SingleChoiceSetting( + IntSetting.REGION_INDEX, + R.string.emulated_region, + 0, + R.array.regionNames, + R.array.regionValues + ) + ) + put( + SingleChoiceSetting( + IntSetting.LANGUAGE_INDEX, + R.string.emulated_language, + 0, + R.array.languageNames, + R.array.languageValues + ) + ) + put( + SwitchSetting( + BooleanSetting.USE_CUSTOM_RTC, + R.string.use_custom_rtc, + R.string.use_custom_rtc_description + ) + ) + put(DateTimeSetting(LongSetting.CUSTOM_RTC, R.string.set_custom_rtc, 0)) + put( + SingleChoiceSetting( + IntSetting.RENDERER_ACCURACY, + R.string.renderer_accuracy, + 0, + R.array.rendererAccuracyNames, + R.array.rendererAccuracyValues + ) + ) + put( + SingleChoiceSetting( + IntSetting.RENDERER_RESOLUTION, + R.string.renderer_resolution, + 0, + R.array.rendererResolutionNames, + R.array.rendererResolutionValues + ) + ) + put( + SingleChoiceSetting( + IntSetting.RENDERER_VSYNC, + R.string.renderer_vsync, + 0, + R.array.rendererVSyncNames, + R.array.rendererVSyncValues + ) + ) + put( + SingleChoiceSetting( + IntSetting.RENDERER_SCALING_FILTER, + R.string.renderer_scaling_filter, + 0, + R.array.rendererScalingFilterNames, + R.array.rendererScalingFilterValues + ) + ) + put( + SingleChoiceSetting( + IntSetting.RENDERER_ANTI_ALIASING, + R.string.renderer_anti_aliasing, + 0, + R.array.rendererAntiAliasingNames, + R.array.rendererAntiAliasingValues + ) + ) + put( + SingleChoiceSetting( + IntSetting.RENDERER_SCREEN_LAYOUT, + R.string.renderer_screen_layout, + 0, + R.array.rendererScreenLayoutNames, + R.array.rendererScreenLayoutValues + ) + ) + put( + SingleChoiceSetting( + IntSetting.RENDERER_ASPECT_RATIO, + R.string.renderer_aspect_ratio, + 0, + R.array.rendererAspectRatioNames, + R.array.rendererAspectRatioValues + ) + ) + put( + SwitchSetting( + BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE, + R.string.use_disk_shader_cache, + R.string.use_disk_shader_cache_description + ) + ) + put( + SwitchSetting( + BooleanSetting.RENDERER_FORCE_MAX_CLOCK, + R.string.renderer_force_max_clock, + R.string.renderer_force_max_clock_description + ) + ) + put( + SwitchSetting( + BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS, + R.string.renderer_asynchronous_shaders, + R.string.renderer_asynchronous_shaders_description + ) + ) + put( + SwitchSetting( + BooleanSetting.RENDERER_REACTIVE_FLUSHING, + R.string.renderer_reactive_flushing, + R.string.renderer_reactive_flushing_description + ) + ) + put( + SingleChoiceSetting( + IntSetting.AUDIO_OUTPUT_ENGINE, + R.string.audio_output_engine, + 0, + R.array.outputEngineEntries, + R.array.outputEngineValues + ) + ) + put( + SliderSetting( + ByteSetting.AUDIO_VOLUME, + R.string.audio_volume, + R.string.audio_volume_description, + 0, + 100, + "%" + ) + ) + put( + SingleChoiceSetting( + IntSetting.RENDERER_BACKEND, + R.string.renderer_api, + 0, + R.array.rendererApiNames, + R.array.rendererApiValues + ) + ) + put( + SwitchSetting( + BooleanSetting.RENDERER_DEBUG, + R.string.renderer_debug, + R.string.renderer_debug_description + ) + ) + put( + SwitchSetting( + BooleanSetting.CPU_DEBUG_MODE, + R.string.cpu_debug_mode, + R.string.cpu_debug_mode_description + ) + ) + + val fastmem = object : AbstractBooleanSetting { + override val boolean: Boolean + get() = + BooleanSetting.FASTMEM.boolean && BooleanSetting.FASTMEM_EXCLUSIVES.boolean + + override fun setBoolean(value: Boolean) { + BooleanSetting.FASTMEM.setBoolean(value) + BooleanSetting.FASTMEM_EXCLUSIVES.setBoolean(value) + } + + override val key: String = FASTMEM_COMBINED + override val category = Settings.Category.Cpu + override val isRuntimeModifiable: Boolean = false + override val defaultValue: Boolean = true + override fun reset() = setBoolean(defaultValue) + } + put(SwitchSetting(fastmem, R.string.fastmem, 0)) + } } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt index 9883c2ec7..ff1e64e0a 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt @@ -14,7 +14,9 @@ import android.widget.TextView import androidx.appcompat.app.AlertDialog import androidx.lifecycle.ViewModelProvider import androidx.navigation.findNavController -import androidx.recyclerview.widget.RecyclerView +import androidx.recyclerview.widget.AsyncDifferConfig +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter import com.google.android.material.datepicker.MaterialDatePicker import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.slider.Slider @@ -37,8 +39,8 @@ import org.yuzu.yuzu_emu.model.SettingsViewModel class SettingsAdapter( private val fragment: SettingsFragment, private val context: Context -) : RecyclerView.Adapter(), DialogInterface.OnClickListener { - private var settings = ArrayList() +) : ListAdapter(AsyncDifferConfig.Builder(DiffCallback()).build()), + DialogInterface.OnClickListener { private var clickedItem: SettingsItem? = null private var clickedPosition: Int private var dialog: AlertDialog? = null @@ -94,24 +96,18 @@ class SettingsAdapter( } override fun onBindViewHolder(holder: SettingViewHolder, position: Int) { - holder.bind(getItem(position)) + holder.bind(currentList[position]) } - private fun getItem(position: Int): SettingsItem = settings[position] - - override fun getItemCount(): Int = settings.size + override fun getItemCount(): Int = currentList.size override fun getItemViewType(position: Int): Int { - return getItem(position).type - } - - fun setSettingsList(settings: ArrayList) { - this.settings = settings - notifyDataSetChanged() + return currentList[position].type } - fun onBooleanClick(item: SwitchSetting, position: Int, checked: Boolean) { + fun onBooleanClick(item: SwitchSetting, checked: Boolean) { item.checked = checked + settingsViewModel.setShouldReloadSettingsList(true) settingsViewModel.shouldSave = true } @@ -338,4 +334,14 @@ class SettingsAdapter( } return -1 } + + private class DiffCallback : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: SettingsItem, newItem: SettingsItem): Boolean { + return oldItem.setting.key == newItem.setting.key + } + + override fun areContentsTheSame(oldItem: SettingsItem, newItem: SettingsItem): Boolean { + return oldItem.setting.key == newItem.setting.key + } + } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt index de6aebd9d..5890b0642 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt @@ -77,6 +77,13 @@ class SettingsFragment : Fragment() { if (it.isNotEmpty()) binding.toolbarSettingsLayout.title = it } + settingsViewModel.shouldReloadSettingsList.observe(viewLifecycleOwner) { + if (it) { + settingsViewModel.setShouldReloadSettingsList(false) + presenter.loadSettingsList() + } + } + presenter.onViewCreated() setInsets() diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt index ba45c317d..22a529b1b 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt @@ -22,6 +22,7 @@ import org.yuzu.yuzu_emu.features.settings.model.ShortSetting import org.yuzu.yuzu_emu.features.settings.model.view.* import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile import org.yuzu.yuzu_emu.model.SettingsViewModel +import org.yuzu.yuzu_emu.utils.NativeConfig class SettingsFragmentPresenter( private val settingsViewModel: SettingsViewModel, @@ -36,11 +37,22 @@ class SettingsFragmentPresenter( private val context: Context get() = YuzuApplication.appContext + // Extension for populating settings list based on paired settings + fun ArrayList.add(key: String) { + val item = SettingsItem.settingsItems[key]!! + val pairedSettingKey = item.setting.pairedSettingKey + if (pairedSettingKey.isNotEmpty()) { + val pairedSettingValue = NativeConfig.getBoolean(pairedSettingKey, false) + if (!pairedSettingValue) return + } + add(item) + } + fun onViewCreated() { loadSettingsList() } - private fun loadSettingsList() { + fun loadSettingsList() { if (!TextUtils.isEmpty(gameId)) { settingsViewModel.setToolbarTitle( context.getString( @@ -70,7 +82,7 @@ class SettingsFragmentPresenter( } } settingsList = sl - adapter.setSettingsList(settingsList) + adapter.submitList(settingsList) } private fun addConfigSettings(sl: ArrayList) { @@ -92,200 +104,46 @@ class SettingsFragmentPresenter( private fun addGeneralSettings(sl: ArrayList) { settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_general)) sl.apply { - add( - SwitchSetting( - BooleanSetting.RENDERER_USE_SPEED_LIMIT, - R.string.frame_limit_enable, - R.string.frame_limit_enable_description - ) - ) - add( - SliderSetting( - ShortSetting.RENDERER_SPEED_LIMIT, - R.string.frame_limit_slider, - R.string.frame_limit_slider_description, - 1, - 200, - "%" - ) - ) - add( - SingleChoiceSetting( - IntSetting.CPU_ACCURACY, - R.string.cpu_accuracy, - 0, - R.array.cpuAccuracyNames, - R.array.cpuAccuracyValues - ) - ) - add( - SwitchSetting( - BooleanSetting.PICTURE_IN_PICTURE, - R.string.picture_in_picture, - R.string.picture_in_picture_description - ) - ) + add(BooleanSetting.RENDERER_USE_SPEED_LIMIT.key) + add(ShortSetting.RENDERER_SPEED_LIMIT.key) + add(IntSetting.CPU_ACCURACY.key) + add(BooleanSetting.PICTURE_IN_PICTURE.key) } } private fun addSystemSettings(sl: ArrayList) { settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_system)) sl.apply { - add( - SwitchSetting( - BooleanSetting.USE_DOCKED_MODE, - R.string.use_docked_mode, - R.string.use_docked_mode_description - ) - ) - add( - SingleChoiceSetting( - IntSetting.REGION_INDEX, - R.string.emulated_region, - 0, - R.array.regionNames, - R.array.regionValues - ) - ) - add( - SingleChoiceSetting( - IntSetting.LANGUAGE_INDEX, - R.string.emulated_language, - 0, - R.array.languageNames, - R.array.languageValues - ) - ) - add( - SwitchSetting( - BooleanSetting.USE_CUSTOM_RTC, - R.string.use_custom_rtc, - R.string.use_custom_rtc_description - ) - ) - add(DateTimeSetting(LongSetting.CUSTOM_RTC, R.string.set_custom_rtc, 0)) + add(BooleanSetting.USE_DOCKED_MODE.key) + add(IntSetting.REGION_INDEX.key) + add(IntSetting.LANGUAGE_INDEX.key) + add(BooleanSetting.USE_CUSTOM_RTC.key) + add(LongSetting.CUSTOM_RTC.key) } } private fun addGraphicsSettings(sl: ArrayList) { settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_graphics)) sl.apply { - add( - SingleChoiceSetting( - IntSetting.RENDERER_ACCURACY, - R.string.renderer_accuracy, - 0, - R.array.rendererAccuracyNames, - R.array.rendererAccuracyValues - ) - ) - add( - SingleChoiceSetting( - IntSetting.RENDERER_RESOLUTION, - R.string.renderer_resolution, - 0, - R.array.rendererResolutionNames, - R.array.rendererResolutionValues - ) - ) - add( - SingleChoiceSetting( - IntSetting.RENDERER_VSYNC, - R.string.renderer_vsync, - 0, - R.array.rendererVSyncNames, - R.array.rendererVSyncValues - ) - ) - add( - SingleChoiceSetting( - IntSetting.RENDERER_SCALING_FILTER, - R.string.renderer_scaling_filter, - 0, - R.array.rendererScalingFilterNames, - R.array.rendererScalingFilterValues - ) - ) - add( - SingleChoiceSetting( - IntSetting.RENDERER_ANTI_ALIASING, - R.string.renderer_anti_aliasing, - 0, - R.array.rendererAntiAliasingNames, - R.array.rendererAntiAliasingValues - ) - ) - add( - SingleChoiceSetting( - IntSetting.RENDERER_SCREEN_LAYOUT, - R.string.renderer_screen_layout, - 0, - R.array.rendererScreenLayoutNames, - R.array.rendererScreenLayoutValues - ) - ) - add( - SingleChoiceSetting( - IntSetting.RENDERER_ASPECT_RATIO, - R.string.renderer_aspect_ratio, - 0, - R.array.rendererAspectRatioNames, - R.array.rendererAspectRatioValues - ) - ) - add( - SwitchSetting( - BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE, - R.string.use_disk_shader_cache, - R.string.use_disk_shader_cache_description - ) - ) - add( - SwitchSetting( - BooleanSetting.RENDERER_FORCE_MAX_CLOCK, - R.string.renderer_force_max_clock, - R.string.renderer_force_max_clock_description - ) - ) - add( - SwitchSetting( - BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS, - R.string.renderer_asynchronous_shaders, - R.string.renderer_asynchronous_shaders_description - ) - ) - add( - SwitchSetting( - BooleanSetting.RENDERER_REACTIVE_FLUSHING, - R.string.renderer_reactive_flushing, - R.string.renderer_reactive_flushing_description - ) - ) + add(IntSetting.RENDERER_ACCURACY.key) + add(IntSetting.RENDERER_RESOLUTION.key) + add(IntSetting.RENDERER_VSYNC.key) + add(IntSetting.RENDERER_SCALING_FILTER.key) + add(IntSetting.RENDERER_ANTI_ALIASING.key) + add(IntSetting.RENDERER_SCREEN_LAYOUT.key) + add(IntSetting.RENDERER_ASPECT_RATIO.key) + add(BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE.key) + add(BooleanSetting.RENDERER_FORCE_MAX_CLOCK.key) + add(BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS.key) + add(BooleanSetting.RENDERER_REACTIVE_FLUSHING.key) } } private fun addAudioSettings(sl: ArrayList) { settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_audio)) sl.apply { - add( - SingleChoiceSetting( - IntSetting.AUDIO_OUTPUT_ENGINE, - R.string.audio_output_engine, - 0, - R.array.outputEngineEntries, - R.array.outputEngineValues - ) - ) - add( - SliderSetting( - ByteSetting.AUDIO_VOLUME, - R.string.audio_volume, - R.string.audio_volume_description, - 0, - 100, - "%" - ) - ) + add(IntSetting.AUDIO_OUTPUT_ENGINE.key) + add(ByteSetting.AUDIO_VOLUME.key) } } @@ -303,7 +161,7 @@ class SettingsFragmentPresenter( settingsViewModel.setShouldRecreate(true) } - override val key: String? = null + override val key: String = Settings.PREF_THEME override val category = Settings.Category.UiGeneral override val isRuntimeModifiable: Boolean = false override val defaultValue: Int = 0 @@ -347,7 +205,7 @@ class SettingsFragmentPresenter( settingsViewModel.setShouldRecreate(true) } - override val key: String? = null + override val key: String = Settings.PREF_THEME_MODE override val category = Settings.Category.UiGeneral override val isRuntimeModifiable: Boolean = false override val defaultValue: Int = -1 @@ -380,7 +238,7 @@ class SettingsFragmentPresenter( settingsViewModel.setShouldRecreate(true) } - override val key: String? = null + override val key: String = Settings.PREF_BLACK_BACKGROUNDS override val category = Settings.Category.UiGeneral override val isRuntimeModifiable: Boolean = false override val defaultValue: Boolean = false @@ -406,49 +264,12 @@ class SettingsFragmentPresenter( settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_debug)) sl.apply { add(HeaderSetting(R.string.gpu)) - add( - SingleChoiceSetting( - IntSetting.RENDERER_BACKEND, - R.string.renderer_api, - 0, - R.array.rendererApiNames, - R.array.rendererApiValues - ) - ) - add( - SwitchSetting( - BooleanSetting.RENDERER_DEBUG, - R.string.renderer_debug, - R.string.renderer_debug_description - ) - ) + add(IntSetting.RENDERER_BACKEND.key) + add(BooleanSetting.RENDERER_DEBUG.key) add(HeaderSetting(R.string.cpu)) - add( - SwitchSetting( - BooleanSetting.CPU_DEBUG_MODE, - R.string.cpu_debug_mode, - R.string.cpu_debug_mode_description - ) - ) - - val fastmem = object : AbstractBooleanSetting { - override val boolean: Boolean - get() = - BooleanSetting.FASTMEM.boolean && BooleanSetting.FASTMEM_EXCLUSIVES.boolean - - override fun setBoolean(value: Boolean) { - BooleanSetting.FASTMEM.setBoolean(value) - BooleanSetting.FASTMEM_EXCLUSIVES.setBoolean(value) - } - - override val key: String? = null - override val category = Settings.Category.Cpu - override val isRuntimeModifiable: Boolean = false - override val defaultValue: Boolean = true - override fun reset() = setBoolean(defaultValue) - } - add(SwitchSetting(fastmem, R.string.fastmem, 0)) + add(BooleanSetting.CPU_DEBUG_MODE.key) + add(SettingsItem.FASTMEM_COMBINED) } } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt index 25b689d66..0a37d3624 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt @@ -29,7 +29,7 @@ class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter binding.switchWidget.setOnCheckedChangeListener(null) binding.switchWidget.isChecked = setting.checked binding.switchWidget.setOnCheckedChangeListener { _: CompoundButton, _: Boolean -> - adapter.onBooleanClick(item, bindingAdapterPosition, binding.switchWidget.isChecked) + adapter.onBooleanClick(item, binding.switchWidget.isChecked) } setStyle(setting.isEditable, binding) @@ -43,7 +43,7 @@ class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter override fun onLongClick(clicked: View): Boolean { if (setting.isEditable) { - return adapter.onLongClick(setting.setting!!, bindingAdapterPosition) + return adapter.onLongClick(setting.setting, bindingAdapterPosition) } return false } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt index 1763341e2..6f2276293 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt @@ -24,6 +24,9 @@ class SettingsViewModel : ViewModel() { private val _shouldShowResetSettingsDialog = MutableLiveData(false) val shouldShowResetSettingsDialog: LiveData get() = _shouldShowResetSettingsDialog + private val _shouldReloadSettingsList = MutableLiveData(false) + val shouldReloadSettingsList: LiveData get() = _shouldReloadSettingsList + fun setToolbarTitle(value: String) { _toolbarTitle.value = value } @@ -40,6 +43,10 @@ class SettingsViewModel : ViewModel() { _shouldShowResetSettingsDialog.value = value } + fun setShouldReloadSettingsList(value: Boolean) { + _shouldReloadSettingsList.value = value + } + fun clear() { game = null shouldSave = false diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt index d4d981f9e..9425f8b99 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt @@ -28,4 +28,6 @@ object NativeConfig { external fun getIsRuntimeModifiable(key: String): Boolean external fun getConfigHeader(category: Int): String + + external fun getPairedSettingKey(key: String): String } diff --git a/src/android/app/src/main/jni/native_config.cpp b/src/android/app/src/main/jni/native_config.cpp index 6123b3d08..8a704960c 100644 --- a/src/android/app/src/main/jni/native_config.cpp +++ b/src/android/app/src/main/jni/native_config.cpp @@ -216,9 +216,22 @@ jboolean Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getIsRuntimeModifiable(JNIEn } jstring Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getConfigHeader(JNIEnv* env, jobject obj, - jint jcategory) { + jint jcategory) { auto category = static_cast(jcategory); return ToJString(env, Settings::TranslateCategory(category)); } +jstring Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getPairedSettingKey(JNIEnv* env, jobject obj, + jstring jkey) { + auto setting = getSetting(env, jkey); + if (setting == nullptr) { + return ToJString(env, ""); + } + if (setting->PairedSetting() == nullptr) { + return ToJString(env, ""); + } + + return ToJString(env, setting->PairedSetting()->GetLabel()); +} + } // extern "C" -- cgit v1.2.3 From fd5c7b21ddc0e40df049773d9106e349072352f3 Mon Sep 17 00:00:00 2001 From: Charles Lombardo Date: Thu, 24 Aug 2023 16:11:08 -0400 Subject: android: Add search for settings --- .../features/settings/ui/SettingsAdapter.kt | 3 +- .../features/settings/ui/SettingsFragment.kt | 34 ++++ .../yuzu_emu/fragments/SettingsSearchFragment.kt | 189 +++++++++++++++++++++ .../org/yuzu/yuzu_emu/model/SettingsViewModel.kt | 7 + .../main/res/layout/fragment_settings_search.xml | 120 +++++++++++++ .../app/src/main/res/menu/menu_settings.xml | 11 ++ .../main/res/navigation/settings_navigation.xml | 8 + src/android/app/src/main/res/values/strings.xml | 1 + 8 files changed, 372 insertions(+), 1 deletion(-) create mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsSearchFragment.kt create mode 100644 src/android/app/src/main/res/layout/fragment_settings_search.xml create mode 100644 src/android/app/src/main/res/menu/menu_settings.xml (limited to 'src') diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt index ff1e64e0a..f5eba1222 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt @@ -12,6 +12,7 @@ import android.view.LayoutInflater import android.view.ViewGroup import android.widget.TextView import androidx.appcompat.app.AlertDialog +import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProvider import androidx.navigation.findNavController import androidx.recyclerview.widget.AsyncDifferConfig @@ -37,7 +38,7 @@ import org.yuzu.yuzu_emu.features.settings.ui.viewholder.* import org.yuzu.yuzu_emu.model.SettingsViewModel class SettingsAdapter( - private val fragment: SettingsFragment, + private val fragment: Fragment, private val context: Context ) : ListAdapter(AsyncDifferConfig.Builder(DiffCallback()).build()), DialogInterface.OnClickListener { diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt index 5890b0642..0ea587a88 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt @@ -13,12 +13,14 @@ import androidx.core.view.WindowInsetsCompat import androidx.core.view.updatePadding import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels +import androidx.navigation.findNavController import androidx.navigation.fragment.navArgs import androidx.recyclerview.widget.LinearLayoutManager import com.google.android.material.divider.MaterialDividerItemDecoration import com.google.android.material.transition.MaterialSharedAxis import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.databinding.FragmentSettingsBinding +import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile import org.yuzu.yuzu_emu.model.SettingsViewModel class SettingsFragment : Fragment() { @@ -84,11 +86,43 @@ class SettingsFragment : Fragment() { } } + settingsViewModel.isUsingSearch.observe(viewLifecycleOwner) { + if (it) { + reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true) + exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false) + } else { + reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false) + exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) + } + } + + if (args.menuTag == SettingsFile.FILE_NAME_CONFIG) { + binding.toolbarSettings.inflateMenu(R.menu.menu_settings) + binding.toolbarSettings.setOnMenuItemClickListener { + when (it.itemId) { + R.id.action_search -> { + reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true) + exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false) + view.findNavController() + .navigate(R.id.action_settingsFragment_to_settingsSearchFragment) + true + } + + else -> false + } + } + } + presenter.onViewCreated() setInsets() } + override fun onResume() { + super.onResume() + settingsViewModel.setIsUsingSearch(false) + } + override fun onDetach() { super.onDetach() settingsAdapter?.closeDialog() diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsSearchFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsSearchFragment.kt new file mode 100644 index 000000000..4f93db4ad --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsSearchFragment.kt @@ -0,0 +1,189 @@ +// SPDX-FileCopyrightText: 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.yuzu.yuzu_emu.fragments + +import android.content.Context +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.inputmethod.InputMethodManager +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat +import androidx.core.view.updatePadding +import androidx.core.widget.doOnTextChanged +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import androidx.recyclerview.widget.LinearLayoutManager +import com.google.android.material.divider.MaterialDividerItemDecoration +import com.google.android.material.transition.MaterialSharedAxis +import info.debatty.java.stringsimilarity.Cosine +import org.yuzu.yuzu_emu.R +import org.yuzu.yuzu_emu.databinding.FragmentSettingsSearchBinding +import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem +import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter +import org.yuzu.yuzu_emu.model.SettingsViewModel +import org.yuzu.yuzu_emu.utils.NativeConfig + +class SettingsSearchFragment : Fragment() { + private var _binding: FragmentSettingsSearchBinding? = null + private val binding get() = _binding!! + + private var settingsAdapter: SettingsAdapter? = null + + private val settingsViewModel: SettingsViewModel by activityViewModels() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + enterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false) + returnTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = FragmentSettingsSearchBinding.inflate(layoutInflater) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + settingsViewModel.setIsUsingSearch(true) + + if (savedInstanceState != null) { + binding.searchText.setText(savedInstanceState.getString(SEARCH_TEXT)) + } + + settingsAdapter = SettingsAdapter(this, requireContext()) + + val dividerDecoration = MaterialDividerItemDecoration( + requireContext(), + LinearLayoutManager.VERTICAL + ) + dividerDecoration.isLastItemDecorated = false + binding.settingsList.apply { + adapter = settingsAdapter + layoutManager = LinearLayoutManager(requireContext()) + addItemDecoration(dividerDecoration) + } + + focusSearch() + + binding.backButton.setOnClickListener { settingsViewModel.setShouldNavigateBack(true) } + binding.searchBackground.setOnClickListener { focusSearch() } + binding.clearButton.setOnClickListener { binding.searchText.setText("") } + binding.searchText.doOnTextChanged { _, _, _, _ -> + search() + binding.settingsList.smoothScrollToPosition(0) + } + settingsViewModel.shouldReloadSettingsList.observe(viewLifecycleOwner) { + if (it) { + settingsViewModel.setShouldReloadSettingsList(false) + search() + } + } + + search() + + setInsets() + } + + override fun onDetach() { + super.onDetach() + settingsAdapter?.closeDialog() + } + + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + outState.putString(SEARCH_TEXT, binding.searchText.text.toString()) + } + + private fun search() { + val searchTerm = binding.searchText.text.toString().lowercase() + binding.clearButton.visibility = + if (searchTerm.isEmpty()) View.INVISIBLE else View.VISIBLE + if (searchTerm.isEmpty()) { + binding.noResultsView.visibility = View.VISIBLE + settingsAdapter?.submitList(emptyList()) + return + } + + val baseList = SettingsItem.settingsItems + val similarityAlgorithm = if (searchTerm.length > 2) Cosine() else Cosine(1) + val sortedList: List = baseList.mapNotNull { item -> + val title = getString(item.value.nameId).lowercase() + val similarity = similarityAlgorithm.similarity(searchTerm, title) + if (similarity > 0.08) { + Pair(similarity, item) + } else { + null + } + }.sortedByDescending { it.first }.mapNotNull { + val item = it.second.value + val pairedSettingKey = item.setting.pairedSettingKey + val optionalSetting: SettingsItem? = if (pairedSettingKey.isNotEmpty()) { + val pairedSettingValue = NativeConfig.getBoolean(pairedSettingKey, false) + if (pairedSettingValue) it.second.value else null + } else { + it.second.value + } + optionalSetting + } + settingsAdapter?.submitList(sortedList) + binding.noResultsView.visibility = + if (sortedList.isEmpty()) View.VISIBLE else View.INVISIBLE + } + + private fun focusSearch() { + binding.searchText.requestFocus() + val imm = requireActivity() + .getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager? + imm?.showSoftInput(binding.searchText, InputMethodManager.SHOW_IMPLICIT) + } + + private fun setInsets() = + ViewCompat.setOnApplyWindowInsetsListener( + binding.root + ) { _: View, windowInsets: WindowInsetsCompat -> + val extraListSpacing = resources.getDimensionPixelSize(R.dimen.spacing_med) + val sideMargin = resources.getDimensionPixelSize(R.dimen.spacing_medlarge) + val topMargin = resources.getDimensionPixelSize(R.dimen.spacing_chip) + + val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) + val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) + + val leftInsets = barInsets.left + cutoutInsets.left + val rightInsets = barInsets.right + cutoutInsets.right + + binding.settingsList.updatePadding(bottom = barInsets.bottom + extraListSpacing) + binding.frameSearch.updatePadding( + left = leftInsets + sideMargin, + top = barInsets.top + topMargin, + right = rightInsets + sideMargin + ) + binding.noResultsView.updatePadding( + left = leftInsets, + right = rightInsets, + bottom = barInsets.bottom + ) + + val mlpSettingsList = binding.settingsList.layoutParams as ViewGroup.MarginLayoutParams + mlpSettingsList.leftMargin = leftInsets + sideMargin + mlpSettingsList.rightMargin = rightInsets + sideMargin + binding.settingsList.layoutParams = mlpSettingsList + + val mlpDivider = binding.divider.layoutParams as ViewGroup.MarginLayoutParams + mlpDivider.leftMargin = leftInsets + sideMargin + mlpDivider.rightMargin = rightInsets + sideMargin + binding.divider.layoutParams = mlpDivider + + windowInsets + } + + companion object { + const val SEARCH_TEXT = "SearchText" + } +} diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt index 6f2276293..a0cb7225f 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt @@ -27,6 +27,9 @@ class SettingsViewModel : ViewModel() { private val _shouldReloadSettingsList = MutableLiveData(false) val shouldReloadSettingsList: LiveData get() = _shouldReloadSettingsList + private val _isUsingSearch = MutableLiveData(false) + val isUsingSearch: LiveData get() = _isUsingSearch + fun setToolbarTitle(value: String) { _toolbarTitle.value = value } @@ -47,6 +50,10 @@ class SettingsViewModel : ViewModel() { _shouldReloadSettingsList.value = value } + fun setIsUsingSearch(value: Boolean) { + _isUsingSearch.value = value + } + fun clear() { game = null shouldSave = false diff --git a/src/android/app/src/main/res/layout/fragment_settings_search.xml b/src/android/app/src/main/res/layout/fragment_settings_search.xml new file mode 100644 index 000000000..c779ed2fc --- /dev/null +++ b/src/android/app/src/main/res/layout/fragment_settings_search.xml @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + +