From 6c8f2b355ace41e33e8a1ad2f95d2816893a953b Mon Sep 17 00:00:00 2001 From: Charles Lombardo Date: Wed, 16 Aug 2023 02:36:56 -0400 Subject: [PATCH 1/9] 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. --- .../java/org/yuzu/yuzu_emu/NativeLibrary.kt | 8 +- .../yuzu_emu/activities/EmulationActivity.kt | 6 - .../settings/model/AbstractBooleanSetting.kt | 4 +- ...ngsViewModel.kt => AbstractByteSetting.kt} | 6 +- .../settings/model/AbstractFloatSetting.kt | 4 +- .../settings/model/AbstractIntSetting.kt | 4 +- .../settings/model/AbstractLongSetting.kt | 10 + .../settings/model/AbstractSetting.kt | 13 +- .../settings/model/AbstractShortSetting.kt | 10 + .../settings/model/AbstractStringSetting.kt | 4 +- .../features/settings/model/BooleanSetting.kt | 53 ++- .../features/settings/model/ByteSetting.kt | 25 ++ .../features/settings/model/FloatSetting.kt | 32 +- .../features/settings/model/IntSetting.kt | 149 ++------- .../features/settings/model/LongSetting.kt | 25 ++ .../features/settings/model/SettingSection.kt | 37 -- .../features/settings/model/Settings.kt | 316 ++++++++---------- .../features/settings/model/ShortSetting.kt | 25 ++ .../features/settings/model/StringSetting.kt | 36 +- .../settings/model/view/DateTimeSetting.kt | 18 +- .../settings/model/view/SettingsItem.kt | 2 +- .../model/view/SingleChoiceSetting.kt | 2 +- .../settings/model/view/SliderSetting.kt | 24 +- .../model/view/StringSingleChoiceSetting.kt | 2 +- .../settings/model/view/SwitchSetting.kt | 4 +- .../features/settings/ui/SettingsActivity.kt | 30 +- .../settings/ui/SettingsActivityPresenter.kt | 15 +- .../settings/ui/SettingsActivityView.kt | 19 -- .../features/settings/ui/SettingsAdapter.kt | 62 ++-- .../features/settings/ui/SettingsFragment.kt | 9 - .../settings/ui/SettingsFragmentPresenter.kt | 181 +++++----- .../settings/ui/SettingsFragmentView.kt | 16 - .../ui/viewholder/DateTimeViewHolder.kt | 2 +- .../features/settings/utils/SettingsFile.kt | 255 ++------------ .../org/yuzu/yuzu_emu/ui/main/MainActivity.kt | 4 - .../java/org/yuzu/yuzu_emu/utils/BiMap.kt | 25 -- .../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 -- .../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 ++ .../app/src/main/res/values/arrays.xml | 10 +- .../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 + 49 files changed, 866 insertions(+), 966 deletions(-) rename src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/{SettingsViewModel.kt => AbstractByteSetting.kt} (59%) 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 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 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 9c32e044c0..5a7cf4ed73 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 7461fb093f..6f52a7a8da 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 a6e9833ee7..aeda8d2220 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/SettingsViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractByteSetting.kt similarity index 59% rename from src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/SettingsViewModel.kt rename to src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractByteSetting.kt index bd9233d625..606519ad84 100644 --- 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/AbstractByteSetting.kt @@ -3,8 +3,8 @@ package org.yuzu.yuzu_emu.features.settings.model -import androidx.lifecycle.ViewModel +interface AbstractByteSetting : AbstractSetting { + val byte: Byte -class SettingsViewModel : ViewModel() { - val settings = Settings() + 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 6fe4bc2635..974925eeda 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 892b7dcfe1..89b285b108 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 0000000000..4873942db7 --- /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 2585802092..7afed95adf 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 0000000000..91407ccbb4 --- /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 0d02c59973..c8935cc48c 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 d41933766d..f7528642e9 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); + 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 var boolean: Boolean = defaultValue + 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() + get() = if (boolean) "1" else "0" - 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 } - } + 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 0000000000..6ec0a765ef --- /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 e5545a916a..0181d06f21 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); + EMPTY_SETTING("", Settings.Category.UiGeneral); - override var float: Float = defaultValue + override val float: Float + get() = NativeConfig.getFloat(key, false) + + override fun setFloat(value: Float) = NativeConfig.setFloat(key, value) + + override val defaultValue: Float by lazy { NativeConfig.getFloat(key, true) } override val valueAsString: String get() = float.toString() - override val isRuntimeEditable: Boolean - get() { - for (setting in NOT_RUNTIME_EDITABLE) { - if (setting == this) { - return false - } - } - return true - } - - companion object { - private val NOT_RUNTIME_EDITABLE = emptyList() - - fun from(key: String): FloatSetting? = FloatSetting.values().firstOrNull { it.key == key } - - 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 4427a7d9dc..a647572071 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 - ); + 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 var int: Int = defaultValue + 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 0000000000..c526fc4cfa --- /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 474f598a91..0000000000 --- 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 a6251bafdd..0702236e81 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/ShortSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ShortSetting.kt new file mode 100644 index 0000000000..c9a0c664cb --- /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 6621289fd5..9bb3e66d4f 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 var string: String = defaultValue + override val string: String + get() = NativeConfig.getString(key, false) + + override fun setString(value: String) = NativeConfig.setString(key, value) + + 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 bc0bf77889..7c858916e8 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 07520849ec..a6cba977c6 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 7306ec458e..b6a8c46128 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 92d0167ae0..e71a29e354 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 3b6731dcd5..2195641e3f 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 90b198718a..4ed8070e5b 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 e6fffc8328..733a53c8cf 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 93e677b21b..fdbad32bf1 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 c186fc388c..07a58b4eaa 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 9711e2c51e..e2e8d8bece 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 70a74c4dda..dc1bf6eb1d 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 59c1d9d545..2bab9e5427 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 1ebe35eaad..a4d7a80aac 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 79572fc06b..eb25ea4fb7 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 70a52df5d4..2b04d666a9 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 - ) + val context = YuzuApplication.appContext + Toast.makeText( + context, + context.getString(R.string.error_saving, fileName, e.message), + Toast.LENGTH_SHORT + ).show() } } - 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) - } - 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 aaf3a0ec1e..d8dbf1f45f 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 9cfda74ee0..0000000000 --- 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 0000000000..d4d981f9ef --- /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 e2ed08e9fc..e15d1480b3 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 9de9bd93e2..34b425cb45 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 0d7d6e94df..e1e8f47ed2 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 7e17833a0c..b2adfdedaa 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 0000000000..6123b3d08d --- /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 0000000000..f2f0bad50b --- /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 0000000000..494654af75 --- /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 200b99185f..dc10159c9b 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 de1b2909b5..df76563fc2 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 5240568417..4ecaf550b1 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 137b65d5f7..5960b78aa5 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 3082e0ce1a..5b170dfd5d 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; }; From f5e6b12c74243736a1dc29d297dd2eb1d0c3a4b2 Mon Sep 17 00:00:00 2001 From: Charles Lombardo Date: Mon, 21 Aug 2023 21:48:21 -0400 Subject: [PATCH 2/9] 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. --- .../settings/model/AbstractSetting.kt | 2 +- .../settings/model/view/DateTimeSetting.kt | 25 +-- .../settings/model/view/HeaderSetting.kt | 2 +- .../settings/model/view/RunnableSetting.kt | 2 +- .../settings/model/view/SettingsItem.kt | 12 +- .../model/view/SingleChoiceSetting.kt | 37 ++-- .../settings/model/view/SliderSetting.kt | 63 ++---- .../model/view/StringSingleChoiceSetting.kt | 46 +---- .../settings/model/view/SubmenuSetting.kt | 2 +- .../settings/model/view/SwitchSetting.kt | 53 ++--- .../features/settings/ui/SettingsAdapter.kt | 16 +- .../settings/ui/SettingsFragmentPresenter.kt | 184 +++++------------- .../ui/viewholder/DateTimeViewHolder.kt | 2 +- .../ui/viewholder/SingleChoiceViewHolder.kt | 4 +- .../ui/viewholder/SliderViewHolder.kt | 2 +- .../ui/viewholder/SwitchSettingViewHolder.kt | 2 +- 16 files changed, 133 insertions(+), 321 deletions(-) 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 7afed95adf..724a2ecb84 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 7c858916e8..8bc1641978 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 a670013111..d31ce1c312 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 caaab50d81..522cc49df1 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 a6cba977c6..3bdcc5bca2 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 b6a8c46128..705527a733 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 e71a29e354..c3b5df02c9 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 2195641e3f..871dab4f3b 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 8a9d13a92e..91c273964a 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 4ed8070e5b..416967e645 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 e2e8d8bece..27eaaa576e 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 2bab9e5427..dddbf65bb7 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 eb25ea4fb7..68c0b24d6c 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 b42d955aa7..a582c425b7 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 a23b5d1099..d94a46262c 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 ef34bf5f44..200fbc4738 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) } From 95a939a49f798c2b4b04d6f8eae33c1d093d43b4 Mon Sep 17 00:00:00 2001 From: Charles Lombardo Date: Tue, 22 Aug 2023 19:46:07 -0400 Subject: [PATCH 3/9] 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. --- .../java/org/yuzu/yuzu_emu/YuzuApplication.kt | 2 +- .../yuzu_emu/activities/EmulationActivity.kt | 4 +- .../features/settings/ui/SettingsActivity.kt | 176 +++++++----------- .../settings/ui/SettingsActivityPresenter.kt | 81 -------- .../settings/ui/SettingsActivityView.kt | 38 ---- .../features/settings/ui/SettingsAdapter.kt | 47 +++-- .../features/settings/ui/SettingsFragment.kt | 114 ++++++------ .../settings/ui/SettingsFragmentPresenter.kt | 68 +++---- .../settings/ui/SettingsFragmentView.kt | 42 ----- .../yuzu_emu/fragments/EmulationFragment.kt | 11 +- .../fragments/HomeSettingsFragment.kt | 19 +- .../yuzu/yuzu_emu/model/SettingsViewModel.kt | 47 +++++ .../org/yuzu/yuzu_emu/ui/main/MainActivity.kt | 14 +- .../yuzu_emu/utils/DirectoryInitialization.kt | 10 +- .../anim_pop_settings_fragment_out.xml | 16 -- .../anim-ldrtl/anim_settings_fragment_in.xml | 16 -- .../anim/anim_pop_settings_fragment_out.xml | 16 -- .../res/anim/anim_settings_fragment_in.xml | 16 -- .../res/anim/anim_settings_fragment_out.xml | 10 - .../res/animator/menu_slide_in_from_start.xml | 20 -- .../res/animator/menu_slide_out_to_start.xml | 21 --- .../src/main/res/layout/activity_settings.xml | 52 ++---- .../src/main/res/layout/fragment_settings.xml | 39 +++- .../app/src/main/res/menu/menu_settings.xml | 2 - .../res/navigation/emulation_navigation.xml | 17 ++ .../main/res/navigation/home_navigation.xml | 17 ++ .../res/navigation/settings_navigation.xml | 24 +++ .../app/src/main/res/values/strings.xml | 1 + 28 files changed, 372 insertions(+), 568 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 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 04ab6a220a..9561748cbb 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 6f52a7a8da..dbd602a1d0 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 733a53c8cf..7fd83f5f7a 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) + settingsViewModel.game = args.game + + val navHostFragment = + supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment + navHostFragment.navController.setGraph(R.navigation.settings_navigation, intent.extras) + WindowCompat.setDecorFitsSystemWindows(window, false) - val launcher = intent - val gameID = launcher.getStringExtra(ARG_GAME_ID) - val menuTag = launcher.getStringExtra(ARG_MENU_TAG) - presenter.onCreate(savedInstanceState, menuTag!!, gameID!!) - - // Show "Back" button in the action bar for navigation - setSupportActionBar(binding.toolbarSettings) - supportActionBar!!.setDisplayHomeAsUpEnabled(true) + 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 + if (isFinishing && settingsViewModel.shouldSave) { + Log.debug("[SettingsActivity] Settings activity stopping. Saving settings to INI...") + Settings.saveSettings() + NativeLibrary.reloadSettings() } - - 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) - } - 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 fdbad32bf1..0000000000 --- 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 07a58b4eaa..0000000000 --- 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 27eaaa576e..9883c2ec70 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 dc1bf6eb1d..de6aebd9df 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() + + binding.toolbarSettings.setNavigationOnClickListener { + settingsViewModel.setShouldNavigateBack(true) + } + + settingsViewModel.toolbarTitle.observe(viewLifecycleOwner) { + if (it.isNotEmpty()) binding.toolbarSettingsLayout.title = it + } + + presenter.onViewCreated() setInsets() } override fun onDetach() { super.onDetach() - activityView = null - if (settingsAdapter != null) { - settingsAdapter!!.closeDialog() - } - } - - override fun showSettingsList(settingsList: ArrayList) { - settingsAdapter!!.setSettingsList(settingsList) - } - - override fun loadSettingsList() { - fragmentPresenter.loadSettingsList() - } - - override fun loadSubMenu(menuKey: String) { - activityView!!.showSettingsFragment( - menuKey, - true, - requireArguments().getString(ARGUMENT_GAME_ID)!! - ) - } - - override fun onSettingChanged() { - activityView!!.onSettingChanged() + 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 dddbf65bb7..ba45c317dd 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 a4d7a80aac..0000000000 --- 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 09e93a017e..70df3af802 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 d5e7934915..cbbe14d220 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 0000000000..1763341e20 --- /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 d8dbf1f45f..7735452e51 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 2ee63697eb..3c9f6bad0f 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 9f49c133a0..0000000000 --- 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 82fd719dba..0000000000 --- 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 5892128f18..0000000000 --- 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 98e0cf8bd6..0000000000 --- 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 77a40a4d1b..0000000000 --- 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 4612aee134..0000000000 --- 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 c004789466..0000000000 --- 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 14ae83b041..8a026a30ac 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 1677203476..ebedbf1ec4 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 1fe7aa6d4c..0000000000 --- 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 8208f4c2c4..2a3c23d554 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 fcebba7266..fb8e14448c 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 0000000000..b36200c655 --- /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 df76563fc2..6b782780a1 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 From 369d06292fae1ac37dfb2863deb16978f29094ad Mon Sep 17 00:00:00 2001 From: Charles Lombardo Date: Tue, 22 Aug 2023 19:49:02 -0400 Subject: [PATCH 4/9] 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. --- .../settings/ui/viewholder/SwitchSettingViewHolder.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 200fbc4738..25b689d668 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) } From d786d19880fbeddf4356d978ec858c4c4874935a Mon Sep 17 00:00:00 2001 From: Charles Lombardo Date: Tue, 22 Aug 2023 23:32:10 -0400 Subject: [PATCH 5/9] android: Implement paired settings Enables and disables editing on settings that rely on other boolean settings. --- .../settings/model/AbstractSetting.kt | 7 +- .../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 +- .../yuzu/yuzu_emu/model/SettingsViewModel.kt | 7 + .../org/yuzu/yuzu_emu/utils/NativeConfig.kt | 2 + .../app/src/main/jni/native_config.cpp | 15 +- 9 files changed, 335 insertions(+), 241 deletions(-) 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 724a2ecb84..73215c2473 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 3bdcc5bca2..b3b3fc2099 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 9883c2ec70..ff1e64e0ad 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 + return currentList[position].type } - fun setSettingsList(settings: ArrayList) { - this.settings = settings - notifyDataSetChanged() - } - - 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 de6aebd9df..5890b06425 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 ba45c317dd..22a529b1ba 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 25b689d668..0a37d36245 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 1763341e20..6f22762933 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 d4d981f9ef..9425f8b991 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 6123b3d08d..8a704960cd 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" From fd5c7b21ddc0e40df049773d9106e349072352f3 Mon Sep 17 00:00:00 2001 From: Charles Lombardo Date: Thu, 24 Aug 2023 16:11:08 -0400 Subject: [PATCH 6/9] android: Add search for settings --- .../features/settings/ui/SettingsAdapter.kt | 3 +- .../features/settings/ui/SettingsFragment.kt | 34 ++++ .../fragments/SettingsSearchFragment.kt | 189 ++++++++++++++++++ .../yuzu/yuzu_emu/model/SettingsViewModel.kt | 7 + .../res/layout/fragment_settings_search.xml | 120 +++++++++++ .../app/src/main/res/menu/menu_settings.xml | 11 + .../res/navigation/settings_navigation.xml | 8 + .../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 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 ff1e64e0ad..f5eba1222c 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 5890b06425..0ea587a880 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 0000000000..4f93db4adf --- /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 6f22762933..a0cb7225f1 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 0000000000..c779ed2fcd --- /dev/null +++ b/src/android/app/src/main/res/layout/fragment_settings_search.xml @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + +