summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt8
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt2
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt10
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractBooleanSetting.kt4
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractByteSetting.kt (renamed from src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/SettingsViewModel.kt)6
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractFloatSetting.kt4
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractIntSetting.kt4
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractLongSetting.kt10
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractSetting.kt20
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractShortSetting.kt10
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractStringSetting.kt4
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt60
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ByteSetting.kt25
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/FloatSetting.kt32
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt154
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/LongSetting.kt25
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/SettingSection.kt37
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt316
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ShortSetting.kt25
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/StringSetting.kt36
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/DateTimeSetting.kt27
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/HeaderSetting.kt2
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/RunnableSetting.kt2
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt247
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SingleChoiceSetting.kt37
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SliderSetting.kt55
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/StringSingleChoiceSetting.kt46
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt2
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SwitchSetting.kt53
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt200
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityPresenter.kt90
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityView.kt57
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt285
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt151
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt538
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentView.kt58
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt4
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt4
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SliderViewHolder.kt2
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt8
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.kt255
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt11
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt19
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsDialogFragment.kt235
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsSearchFragment.kt184
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt96
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt18
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/BiMap.kt25
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt10
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt33
-rw-r--r--src/android/app/src/main/jni/CMakeLists.txt2
-rw-r--r--src/android/app/src/main/jni/config.cpp31
-rw-r--r--src/android/app/src/main/jni/config.h24
-rw-r--r--src/android/app/src/main/jni/native.cpp28
-rw-r--r--src/android/app/src/main/jni/native_config.cpp237
-rw-r--r--src/android/app/src/main/jni/uisettings.cpp10
-rw-r--r--src/android/app/src/main/jni/uisettings.h29
-rw-r--r--src/android/app/src/main/res/anim-ldrtl/anim_pop_settings_fragment_out.xml16
-rw-r--r--src/android/app/src/main/res/anim-ldrtl/anim_settings_fragment_in.xml16
-rw-r--r--src/android/app/src/main/res/anim/anim_pop_settings_fragment_out.xml16
-rw-r--r--src/android/app/src/main/res/anim/anim_settings_fragment_in.xml16
-rw-r--r--src/android/app/src/main/res/anim/anim_settings_fragment_out.xml10
-rw-r--r--src/android/app/src/main/res/animator/menu_slide_in_from_start.xml20
-rw-r--r--src/android/app/src/main/res/animator/menu_slide_out_to_start.xml21
-rw-r--r--src/android/app/src/main/res/layout/activity_settings.xml52
-rw-r--r--src/android/app/src/main/res/layout/fragment_settings.xml39
-rw-r--r--src/android/app/src/main/res/layout/fragment_settings_search.xml120
-rw-r--r--src/android/app/src/main/res/menu/menu_settings.xml11
-rw-r--r--src/android/app/src/main/res/navigation/emulation_navigation.xml17
-rw-r--r--src/android/app/src/main/res/navigation/home_navigation.xml17
-rw-r--r--src/android/app/src/main/res/navigation/settings_navigation.xml32
-rw-r--r--src/android/app/src/main/res/values/arrays.xml10
-rw-r--r--src/android/app/src/main/res/values/strings.xml3
-rw-r--r--src/common/settings.cpp2
-rw-r--r--src/common/settings_common.cpp1
-rw-r--r--src/common/settings_common.h2
76 files changed, 2219 insertions, 2109 deletions
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt
index 9c32e044c..5a7cf4ed7 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt
@@ -219,10 +219,6 @@ object NativeLibrary {
219 219
220 external fun reloadSettings() 220 external fun reloadSettings()
221 221
222 external fun getUserSetting(gameID: String?, Section: String?, Key: String?): String?
223
224 external fun setUserSetting(gameID: String?, Section: String?, Key: String?, Value: String?)
225
226 external fun initGameIni(gameID: String?) 222 external fun initGameIni(gameID: String?)
227 223
228 /** 224 /**
@@ -413,14 +409,17 @@ object NativeLibrary {
413 details.ifEmpty { emulationActivity.getString(R.string.system_archive_general) } 409 details.ifEmpty { emulationActivity.getString(R.string.system_archive_general) }
414 ) 410 )
415 } 411 }
412
416 CoreError.ErrorSavestate -> { 413 CoreError.ErrorSavestate -> {
417 title = emulationActivity.getString(R.string.save_load_error) 414 title = emulationActivity.getString(R.string.save_load_error)
418 message = details 415 message = details
419 } 416 }
417
420 CoreError.ErrorUnknown -> { 418 CoreError.ErrorUnknown -> {
421 title = emulationActivity.getString(R.string.fatal_error) 419 title = emulationActivity.getString(R.string.fatal_error)
422 message = emulationActivity.getString(R.string.fatal_error_message) 420 message = emulationActivity.getString(R.string.fatal_error_message)
423 } 421 }
422
424 else -> { 423 else -> {
425 return true 424 return true
426 } 425 }
@@ -454,6 +453,7 @@ object NativeLibrary {
454 captionId = R.string.loader_error_video_core 453 captionId = R.string.loader_error_video_core
455 descriptionId = R.string.loader_error_video_core_description 454 descriptionId = R.string.loader_error_video_core_description
456 } 455 }
456
457 else -> { 457 else -> {
458 captionId = R.string.loader_error_encrypted 458 captionId = R.string.loader_error_encrypted
459 descriptionId = R.string.loader_error_encrypted_roms_description 459 descriptionId = R.string.loader_error_encrypted_roms_description
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt
index 04ab6a220..9561748cb 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt
@@ -46,7 +46,7 @@ class YuzuApplication : Application() {
46 super.onCreate() 46 super.onCreate()
47 application = this 47 application = this
48 documentsTree = DocumentsTree() 48 documentsTree = DocumentsTree()
49 DirectoryInitialization.start(applicationContext) 49 DirectoryInitialization.start()
50 GpuDriverHelper.initializeDriverParameters(applicationContext) 50 GpuDriverHelper.initializeDriverParameters(applicationContext)
51 NativeLibrary.logDeviceInfo() 51 NativeLibrary.logDeviceInfo()
52 52
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
index 7461fb093..dbd602a1d 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
@@ -28,7 +28,6 @@ import android.view.Surface
28import android.view.View 28import android.view.View
29import android.view.inputmethod.InputMethodManager 29import android.view.inputmethod.InputMethodManager
30import android.widget.Toast 30import android.widget.Toast
31import androidx.activity.viewModels
32import androidx.appcompat.app.AppCompatActivity 31import androidx.appcompat.app.AppCompatActivity
33import androidx.core.view.WindowCompat 32import androidx.core.view.WindowCompat
34import androidx.core.view.WindowInsetsCompat 33import androidx.core.view.WindowInsetsCompat
@@ -42,7 +41,6 @@ import org.yuzu.yuzu_emu.databinding.ActivityEmulationBinding
42import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting 41import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
43import org.yuzu.yuzu_emu.features.settings.model.IntSetting 42import org.yuzu.yuzu_emu.features.settings.model.IntSetting
44import org.yuzu.yuzu_emu.features.settings.model.Settings 43import org.yuzu.yuzu_emu.features.settings.model.Settings
45import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel
46import org.yuzu.yuzu_emu.model.Game 44import org.yuzu.yuzu_emu.model.Game
47import org.yuzu.yuzu_emu.utils.ControllerMappingHelper 45import org.yuzu.yuzu_emu.utils.ControllerMappingHelper
48import org.yuzu.yuzu_emu.utils.ForegroundService 46import org.yuzu.yuzu_emu.utils.ForegroundService
@@ -72,8 +70,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
72 private val actionMute = "ACTION_EMULATOR_MUTE" 70 private val actionMute = "ACTION_EMULATOR_MUTE"
73 private val actionUnmute = "ACTION_EMULATOR_UNMUTE" 71 private val actionUnmute = "ACTION_EMULATOR_UNMUTE"
74 72
75 private val settingsViewModel: SettingsViewModel by viewModels()
76
77 override fun onDestroy() { 73 override fun onDestroy() {
78 stopForegroundService(this) 74 stopForegroundService(this)
79 super.onDestroy() 75 super.onDestroy()
@@ -82,8 +78,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
82 override fun onCreate(savedInstanceState: Bundle?) { 78 override fun onCreate(savedInstanceState: Bundle?) {
83 ThemeHelper.setTheme(this) 79 ThemeHelper.setTheme(this)
84 80
85 settingsViewModel.settings.loadSettings()
86
87 super.onCreate(savedInstanceState) 81 super.onCreate(savedInstanceState)
88 82
89 binding = ActivityEmulationBinding.inflate(layoutInflater) 83 binding = ActivityEmulationBinding.inflate(layoutInflater)
@@ -91,9 +85,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
91 85
92 val navHostFragment = 86 val navHostFragment =
93 supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment 87 supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment
94 val navController = navHostFragment.navController 88 navHostFragment.navController.setGraph(R.navigation.emulation_navigation, intent.extras)
95 navController
96 .setGraph(R.navigation.emulation_navigation, intent.extras)
97 89
98 isActivityRecreated = savedInstanceState != null 90 isActivityRecreated = savedInstanceState != null
99 91
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractBooleanSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractBooleanSetting.kt
index a6e9833ee..aeda8d222 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractBooleanSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractBooleanSetting.kt
@@ -4,5 +4,7 @@
4package org.yuzu.yuzu_emu.features.settings.model 4package org.yuzu.yuzu_emu.features.settings.model
5 5
6interface AbstractBooleanSetting : AbstractSetting { 6interface AbstractBooleanSetting : AbstractSetting {
7 var boolean: Boolean 7 val boolean: Boolean
8
9 fun setBoolean(value: Boolean)
8} 10}
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
index bd9233d62..606519ad8 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 @@
3 3
4package org.yuzu.yuzu_emu.features.settings.model 4package org.yuzu.yuzu_emu.features.settings.model
5 5
6import androidx.lifecycle.ViewModel 6interface AbstractByteSetting : AbstractSetting {
7 val byte: Byte
7 8
8class SettingsViewModel : ViewModel() { 9 fun setByte(value: Byte)
9 val settings = Settings()
10} 10}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractFloatSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractFloatSetting.kt
index 6fe4bc263..974925eed 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractFloatSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractFloatSetting.kt
@@ -4,5 +4,7 @@
4package org.yuzu.yuzu_emu.features.settings.model 4package org.yuzu.yuzu_emu.features.settings.model
5 5
6interface AbstractFloatSetting : AbstractSetting { 6interface AbstractFloatSetting : AbstractSetting {
7 var float: Float 7 val float: Float
8
9 fun setFloat(value: Float)
8} 10}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractIntSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractIntSetting.kt
index 892b7dcfe..89b285b10 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractIntSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractIntSetting.kt
@@ -4,5 +4,7 @@
4package org.yuzu.yuzu_emu.features.settings.model 4package org.yuzu.yuzu_emu.features.settings.model
5 5
6interface AbstractIntSetting : AbstractSetting { 6interface AbstractIntSetting : AbstractSetting {
7 var int: Int 7 val int: Int
8
9 fun setInt(value: Int)
8} 10}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractLongSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractLongSetting.kt
new file mode 100644
index 000000000..4873942db
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractLongSetting.kt
@@ -0,0 +1,10 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.features.settings.model
5
6interface AbstractLongSetting : AbstractSetting {
7 val long: Long
8
9 fun setLong(value: Long)
10}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractSetting.kt
index 258580209..8b6d29fe5 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,22 @@
3 3
4package org.yuzu.yuzu_emu.features.settings.model 4package org.yuzu.yuzu_emu.features.settings.model
5 5
6import org.yuzu.yuzu_emu.utils.NativeConfig
7
6interface AbstractSetting { 8interface AbstractSetting {
7 val key: String? 9 val key: String
8 val section: String? 10 val category: Settings.Category
9 val isRuntimeEditable: Boolean
10 val valueAsString: String
11 val defaultValue: Any 11 val defaultValue: Any
12 val androidDefault: Any?
13 get() = null
14 val valueAsString: String
15 get() = ""
16
17 val isRuntimeModifiable: Boolean
18 get() = NativeConfig.getIsRuntimeModifiable(key)
19
20 val pairedSettingKey: String
21 get() = NativeConfig.getPairedSettingKey(key)
22
23 fun reset()
12} 24}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractShortSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractShortSetting.kt
new file mode 100644
index 000000000..91407ccbb
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractShortSetting.kt
@@ -0,0 +1,10 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.features.settings.model
5
6interface AbstractShortSetting : AbstractSetting {
7 val short: Short
8
9 fun setShort(value: Short)
10}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractStringSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractStringSetting.kt
index 0d02c5997..c8935cc48 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractStringSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractStringSetting.kt
@@ -4,5 +4,7 @@
4package org.yuzu.yuzu_emu.features.settings.model 4package org.yuzu.yuzu_emu.features.settings.model
5 5
6interface AbstractStringSetting : AbstractSetting { 6interface AbstractStringSetting : AbstractSetting {
7 var string: String 7 val string: String
8
9 fun setString(value: String)
8} 10}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt
index d41933766..e0c0538c7 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,37 @@
3 3
4package org.yuzu.yuzu_emu.features.settings.model 4package org.yuzu.yuzu_emu.features.settings.model
5 5
6import org.yuzu.yuzu_emu.utils.NativeConfig
7
6enum class BooleanSetting( 8enum class BooleanSetting(
7 override val key: String, 9 override val key: String,
8 override val section: String, 10 override val category: Settings.Category,
9 override val defaultValue: Boolean 11 override val androidDefault: Boolean? = null
10) : AbstractBooleanSetting { 12) : AbstractBooleanSetting {
11 CPU_DEBUG_MODE("cpu_debug_mode", Settings.SECTION_CPU, false), 13 CPU_DEBUG_MODE("cpu_debug_mode", Settings.Category.Cpu),
12 FASTMEM("cpuopt_fastmem", Settings.SECTION_CPU, true), 14 FASTMEM("cpuopt_fastmem", Settings.Category.Cpu),
13 FASTMEM_EXCLUSIVES("cpuopt_fastmem_exclusives", Settings.SECTION_CPU, true), 15 FASTMEM_EXCLUSIVES("cpuopt_fastmem_exclusives", Settings.Category.Cpu),
14 PICTURE_IN_PICTURE("picture_in_picture", Settings.SECTION_GENERAL, true), 16 RENDERER_USE_SPEED_LIMIT("use_speed_limit", Settings.Category.Core),
15 USE_CUSTOM_RTC("custom_rtc_enabled", Settings.SECTION_SYSTEM, false); 17 USE_DOCKED_MODE("use_docked_mode", Settings.Category.System, false),
16 18 RENDERER_USE_DISK_SHADER_CACHE("use_disk_shader_cache", Settings.Category.Renderer),
17 override var boolean: Boolean = defaultValue 19 RENDERER_FORCE_MAX_CLOCK("force_max_clock", Settings.Category.Renderer),
20 RENDERER_ASYNCHRONOUS_SHADERS("use_asynchronous_shaders", Settings.Category.Renderer),
21 RENDERER_REACTIVE_FLUSHING("use_reactive_flushing", Settings.Category.Renderer, false),
22 RENDERER_DEBUG("debug", Settings.Category.Renderer),
23 PICTURE_IN_PICTURE("picture_in_picture", Settings.Category.Android),
24 USE_CUSTOM_RTC("custom_rtc_enabled", Settings.Category.System);
25
26 override val boolean: Boolean
27 get() = NativeConfig.getBoolean(key, false)
28
29 override fun setBoolean(value: Boolean) = NativeConfig.setBoolean(key, value)
30
31 override val defaultValue: Boolean by lazy {
32 androidDefault ?: NativeConfig.getBoolean(key, true)
33 }
18 34
19 override val valueAsString: String 35 override val valueAsString: String
20 get() = boolean.toString() 36 get() = if (boolean) "1" else "0"
21 37
22 override val isRuntimeEditable: Boolean 38 override fun reset() = NativeConfig.setBoolean(key, defaultValue)
23 get() {
24 for (setting in NOT_RUNTIME_EDITABLE) {
25 if (setting == this) {
26 return false
27 }
28 }
29 return true
30 }
31
32 companion object {
33 private val NOT_RUNTIME_EDITABLE = listOf(
34 PICTURE_IN_PICTURE,
35 USE_CUSTOM_RTC
36 )
37
38 fun from(key: String): BooleanSetting? =
39 BooleanSetting.values().firstOrNull { it.key == key }
40
41 fun clear() = BooleanSetting.values().forEach { it.boolean = it.defaultValue }
42 }
43} 39}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ByteSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ByteSetting.kt
new file mode 100644
index 000000000..6ec0a765e
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ByteSetting.kt
@@ -0,0 +1,25 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.features.settings.model
5
6import org.yuzu.yuzu_emu.utils.NativeConfig
7
8enum class ByteSetting(
9 override val key: String,
10 override val category: Settings.Category
11) : AbstractByteSetting {
12 AUDIO_VOLUME("volume", Settings.Category.Audio);
13
14 override val byte: Byte
15 get() = NativeConfig.getByte(key, false)
16
17 override fun setByte(value: Byte) = NativeConfig.setByte(key, value)
18
19 override val defaultValue: Byte by lazy { NativeConfig.getByte(key, true) }
20
21 override val valueAsString: String
22 get() = byte.toString()
23
24 override fun reset() = NativeConfig.setByte(key, defaultValue)
25}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/FloatSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/FloatSetting.kt
index e5545a916..0181d06f2 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/FloatSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/FloatSetting.kt
@@ -3,34 +3,24 @@
3 3
4package org.yuzu.yuzu_emu.features.settings.model 4package org.yuzu.yuzu_emu.features.settings.model
5 5
6import org.yuzu.yuzu_emu.utils.NativeConfig
7
6enum class FloatSetting( 8enum class FloatSetting(
7 override val key: String, 9 override val key: String,
8 override val section: String, 10 override val category: Settings.Category
9 override val defaultValue: Float
10) : AbstractFloatSetting { 11) : AbstractFloatSetting {
11 // No float settings currently exist 12 // No float settings currently exist
12 EMPTY_SETTING("", "", 0f); 13 EMPTY_SETTING("", Settings.Category.UiGeneral);
13
14 override var float: Float = defaultValue
15 14
16 override val valueAsString: String 15 override val float: Float
17 get() = float.toString() 16 get() = NativeConfig.getFloat(key, false)
18 17
19 override val isRuntimeEditable: Boolean 18 override fun setFloat(value: Float) = NativeConfig.setFloat(key, value)
20 get() {
21 for (setting in NOT_RUNTIME_EDITABLE) {
22 if (setting == this) {
23 return false
24 }
25 }
26 return true
27 }
28 19
29 companion object { 20 override val defaultValue: Float by lazy { NativeConfig.getFloat(key, true) }
30 private val NOT_RUNTIME_EDITABLE = emptyList<FloatSetting>()
31 21
32 fun from(key: String): FloatSetting? = FloatSetting.values().firstOrNull { it.key == key } 22 override val valueAsString: String
23 get() = float.toString()
33 24
34 fun clear() = FloatSetting.values().forEach { it.float = it.defaultValue } 25 override fun reset() = NativeConfig.setFloat(key, defaultValue)
35 }
36} 26}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt
index 4427a7d9d..151362124 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,37 @@
3 3
4package org.yuzu.yuzu_emu.features.settings.model 4package org.yuzu.yuzu_emu.features.settings.model
5 5
6import org.yuzu.yuzu_emu.utils.NativeConfig
7
6enum class IntSetting( 8enum class IntSetting(
7 override val key: String, 9 override val key: String,
8 override val section: String, 10 override val category: Settings.Category,
9 override val defaultValue: Int 11 override val androidDefault: Int? = null
10) : AbstractIntSetting { 12) : AbstractIntSetting {
11 RENDERER_USE_SPEED_LIMIT( 13 CPU_ACCURACY("cpu_accuracy", Settings.Category.Cpu),
12 "use_speed_limit", 14 REGION_INDEX("region_index", Settings.Category.System),
13 Settings.SECTION_RENDERER, 15 LANGUAGE_INDEX("language_index", Settings.Category.System),
14 1 16 RENDERER_BACKEND("backend", Settings.Category.Renderer),
15 ), 17 RENDERER_ACCURACY("gpu_accuracy", Settings.Category.Renderer, 0),
16 USE_DOCKED_MODE( 18 RENDERER_RESOLUTION("resolution_setup", Settings.Category.Renderer),
17 "use_docked_mode", 19 RENDERER_VSYNC("use_vsync", Settings.Category.Renderer),
18 Settings.SECTION_SYSTEM, 20 RENDERER_SCALING_FILTER("scaling_filter", Settings.Category.Renderer),
19 0 21 RENDERER_ANTI_ALIASING("anti_aliasing", Settings.Category.Renderer),
20 ), 22 RENDERER_SCREEN_LAYOUT("screen_layout", Settings.Category.Android),
21 RENDERER_USE_DISK_SHADER_CACHE( 23 RENDERER_ASPECT_RATIO("aspect_ratio", Settings.Category.Renderer),
22 "use_disk_shader_cache", 24 AUDIO_OUTPUT_ENGINE("output_engine", Settings.Category.Audio);
23 Settings.SECTION_RENDERER, 25
24 1 26 override val int: Int
25 ), 27 get() = NativeConfig.getInt(key, false)
26 RENDERER_FORCE_MAX_CLOCK( 28
27 "force_max_clock", 29 override fun setInt(value: Int) = NativeConfig.setInt(key, value)
28 Settings.SECTION_RENDERER, 30
29 0 31 override val defaultValue: Int by lazy {
30 ), 32 androidDefault ?: NativeConfig.getInt(key, true)
31 RENDERER_ASYNCHRONOUS_SHADERS( 33 }
32 "use_asynchronous_shaders",
33 Settings.SECTION_RENDERER,
34 0
35 ),
36 RENDERER_REACTIVE_FLUSHING(
37 "use_reactive_flushing",
38 Settings.SECTION_RENDERER,
39 0
40 ),
41 RENDERER_DEBUG(
42 "debug",
43 Settings.SECTION_RENDERER,
44 0
45 ),
46 RENDERER_SPEED_LIMIT(
47 "speed_limit",
48 Settings.SECTION_RENDERER,
49 100
50 ),
51 CPU_ACCURACY(
52 "cpu_accuracy",
53 Settings.SECTION_CPU,
54 0
55 ),
56 REGION_INDEX(
57 "region_index",
58 Settings.SECTION_SYSTEM,
59 -1
60 ),
61 LANGUAGE_INDEX(
62 "language_index",
63 Settings.SECTION_SYSTEM,
64 1
65 ),
66 RENDERER_BACKEND(
67 "backend",
68 Settings.SECTION_RENDERER,
69 1
70 ),
71 RENDERER_ACCURACY(
72 "gpu_accuracy",
73 Settings.SECTION_RENDERER,
74 0
75 ),
76 RENDERER_RESOLUTION(
77 "resolution_setup",
78 Settings.SECTION_RENDERER,
79 2
80 ),
81 RENDERER_VSYNC(
82 "use_vsync",
83 Settings.SECTION_RENDERER,
84 0
85 ),
86 RENDERER_SCALING_FILTER(
87 "scaling_filter",
88 Settings.SECTION_RENDERER,
89 1
90 ),
91 RENDERER_ANTI_ALIASING(
92 "anti_aliasing",
93 Settings.SECTION_RENDERER,
94 0
95 ),
96 RENDERER_SCREEN_LAYOUT(
97 "screen_layout",
98 Settings.SECTION_RENDERER,
99 Settings.LayoutOption_MobileLandscape
100 ),
101 RENDERER_ASPECT_RATIO(
102 "aspect_ratio",
103 Settings.SECTION_RENDERER,
104 0
105 ),
106 AUDIO_VOLUME(
107 "volume",
108 Settings.SECTION_AUDIO,
109 100
110 );
111
112 override var int: Int = defaultValue
113 34
114 override val valueAsString: String 35 override val valueAsString: String
115 get() = int.toString() 36 get() = int.toString()
116 37
117 override val isRuntimeEditable: Boolean 38 override fun reset() = NativeConfig.setInt(key, defaultValue)
118 get() {
119 for (setting in NOT_RUNTIME_EDITABLE) {
120 if (setting == this) {
121 return false
122 }
123 }
124 return true
125 }
126
127 companion object {
128 private val NOT_RUNTIME_EDITABLE = listOf(
129 RENDERER_USE_DISK_SHADER_CACHE,
130 RENDERER_ASYNCHRONOUS_SHADERS,
131 RENDERER_DEBUG,
132 RENDERER_BACKEND,
133 RENDERER_RESOLUTION,
134 RENDERER_VSYNC
135 )
136
137 fun from(key: String): IntSetting? = IntSetting.values().firstOrNull { it.key == key }
138
139 fun clear() = IntSetting.values().forEach { it.int = it.defaultValue }
140 }
141} 39}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/LongSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/LongSetting.kt
new file mode 100644
index 000000000..c526fc4cf
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/LongSetting.kt
@@ -0,0 +1,25 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.features.settings.model
5
6import org.yuzu.yuzu_emu.utils.NativeConfig
7
8enum class LongSetting(
9 override val key: String,
10 override val category: Settings.Category
11) : AbstractLongSetting {
12 CUSTOM_RTC("custom_rtc", Settings.Category.System);
13
14 override val long: Long
15 get() = NativeConfig.getLong(key, false)
16
17 override fun setLong(value: Long) = NativeConfig.setLong(key, value)
18
19 override val defaultValue: Long by lazy { NativeConfig.getLong(key, true) }
20
21 override val valueAsString: String
22 get() = long.toString()
23
24 override fun reset() = NativeConfig.setLong(key, defaultValue)
25}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/SettingSection.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/SettingSection.kt
deleted file mode 100644
index 474f598a9..000000000
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/SettingSection.kt
+++ /dev/null
@@ -1,37 +0,0 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.features.settings.model
5
6/**
7 * A semantically-related group of Settings objects. These Settings are
8 * internally stored as a HashMap.
9 */
10class SettingSection(val name: String) {
11 val settings = HashMap<String, AbstractSetting>()
12
13 /**
14 * Convenience method; inserts a value directly into the backing HashMap.
15 *
16 * @param setting The Setting to be inserted.
17 */
18 fun putSetting(setting: AbstractSetting) {
19 settings[setting.key!!] = setting
20 }
21
22 /**
23 * Convenience method; gets a value directly from the backing HashMap.
24 *
25 * @param key Used to retrieve the Setting.
26 * @return A Setting object (you should probably cast this before using)
27 */
28 fun getSetting(key: String): AbstractSetting? {
29 return settings[key]
30 }
31
32 fun mergeSection(settingSection: SettingSection) {
33 for (setting in settingSection.settings.values) {
34 putSetting(setting)
35 }
36 }
37}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt
index a6251bafd..0702236e8 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt
@@ -4,195 +4,151 @@
4package org.yuzu.yuzu_emu.features.settings.model 4package org.yuzu.yuzu_emu.features.settings.model
5 5
6import android.text.TextUtils 6import android.text.TextUtils
7import java.util.* 7import android.widget.Toast
8import org.yuzu.yuzu_emu.R 8import org.yuzu.yuzu_emu.R
9import org.yuzu.yuzu_emu.YuzuApplication 9import org.yuzu.yuzu_emu.YuzuApplication
10import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivityView
11import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile 10import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
12 11
13class Settings { 12object Settings {
14 private var gameId: String? = null 13 private val context get() = YuzuApplication.appContext
15 14
16 var isLoaded = false 15 fun saveSettings(gameId: String = "") {
17
18 /**
19 * A HashMap<String></String>, SettingSection> that constructs a new SettingSection instead of returning null
20 * when getting a key not already in the map
21 */
22 class SettingsSectionMap : HashMap<String, SettingSection?>() {
23 override operator fun get(key: String): SettingSection? {
24 if (!super.containsKey(key)) {
25 val section = SettingSection(key)
26 super.put(key, section)
27 return section
28 }
29 return super.get(key)
30 }
31 }
32
33 var sections: HashMap<String, SettingSection?> = SettingsSectionMap()
34
35 fun getSection(sectionName: String): SettingSection? {
36 return sections[sectionName]
37 }
38
39 val isEmpty: Boolean
40 get() = sections.isEmpty()
41
42 fun loadSettings(view: SettingsActivityView? = null) {
43 sections = SettingsSectionMap()
44 loadYuzuSettings(view)
45 if (!TextUtils.isEmpty(gameId)) {
46 loadCustomGameSettings(gameId!!, view)
47 }
48 isLoaded = true
49 }
50
51 private fun loadYuzuSettings(view: SettingsActivityView?) {
52 for ((fileName) in configFileSectionsMap) {
53 sections.putAll(SettingsFile.readFile(fileName, view))
54 }
55 }
56
57 private fun loadCustomGameSettings(gameId: String, view: SettingsActivityView?) {
58 // Custom game settings
59 mergeSections(SettingsFile.readCustomGameSettings(gameId, view))
60 }
61
62 private fun mergeSections(updatedSections: HashMap<String, SettingSection?>) {
63 for ((key, updatedSection) in updatedSections) {
64 if (sections.containsKey(key)) {
65 val originalSection = sections[key]
66 originalSection!!.mergeSection(updatedSection!!)
67 } else {
68 sections[key] = updatedSection
69 }
70 }
71 }
72
73 fun loadSettings(gameId: String, view: SettingsActivityView) {
74 this.gameId = gameId
75 loadSettings(view)
76 }
77
78 fun saveSettings(view: SettingsActivityView) {
79 if (TextUtils.isEmpty(gameId)) { 16 if (TextUtils.isEmpty(gameId)) {
80 view.showToastMessage( 17 Toast.makeText(
81 YuzuApplication.appContext.getString(R.string.ini_saved), 18 context,
82 false 19 context.getString(R.string.ini_saved),
83 ) 20 Toast.LENGTH_SHORT
84 21 ).show()
85 for ((fileName, sectionNames) in configFileSectionsMap) { 22 SettingsFile.saveFile(SettingsFile.FILE_NAME_CONFIG)
86 val iniSections = TreeMap<String, SettingSection>()
87 for (section in sectionNames) {
88 iniSections[section] = sections[section]!!
89 }
90
91 SettingsFile.saveFile(fileName, iniSections, view)
92 }
93 } else { 23 } else {
94 // Custom game settings 24 // TODO: Save custom game settings
95 view.showToastMessage( 25 Toast.makeText(
96 YuzuApplication.appContext.getString(R.string.gameid_saved, gameId), 26 context,
97 false 27 context.getString(R.string.gameid_saved, gameId),
98 ) 28 Toast.LENGTH_SHORT
99 29 ).show()
100 SettingsFile.saveCustomGameSettings(gameId, sections)
101 } 30 }
102 } 31 }
103 32
104 companion object { 33 enum class Category {
105 const val SECTION_GENERAL = "General" 34 Android,
106 const val SECTION_SYSTEM = "System" 35 Audio,
107 const val SECTION_RENDERER = "Renderer" 36 Core,
108 const val SECTION_AUDIO = "Audio" 37 Cpu,
109 const val SECTION_CPU = "Cpu" 38 CpuDebug,
110 const val SECTION_THEME = "Theme" 39 CpuUnsafe,
111 const val SECTION_DEBUG = "Debug" 40 Renderer,
112 41 RendererAdvanced,
113 const val PREF_MEMORY_WARNING_SHOWN = "MemoryWarningShown" 42 RendererDebug,
114 43 System,
115 const val PREF_OVERLAY_VERSION = "OverlayVersion" 44 SystemAudio,
116 const val PREF_LANDSCAPE_OVERLAY_VERSION = "LandscapeOverlayVersion" 45 DataStorage,
117 const val PREF_PORTRAIT_OVERLAY_VERSION = "PortraitOverlayVersion" 46 Debugging,
118 const val PREF_FOLDABLE_OVERLAY_VERSION = "FoldableOverlayVersion" 47 DebuggingGraphics,
119 val overlayLayoutPrefs = listOf( 48 Miscellaneous,
120 PREF_LANDSCAPE_OVERLAY_VERSION, 49 Network,
121 PREF_PORTRAIT_OVERLAY_VERSION, 50 WebService,
122 PREF_FOLDABLE_OVERLAY_VERSION 51 AddOns,
123 ) 52 Controls,
124 53 Ui,
125 const val PREF_CONTROL_SCALE = "controlScale" 54 UiGeneral,
126 const val PREF_CONTROL_OPACITY = "controlOpacity" 55 UiLayout,
127 const val PREF_TOUCH_ENABLED = "isTouchEnabled" 56 UiGameList,
128 const val PREF_BUTTON_A = "buttonToggle0" 57 Screenshots,
129 const val PREF_BUTTON_B = "buttonToggle1" 58 Shortcuts,
130 const val PREF_BUTTON_X = "buttonToggle2" 59 Multiplayer,
131 const val PREF_BUTTON_Y = "buttonToggle3" 60 Services,
132 const val PREF_BUTTON_L = "buttonToggle4" 61 Paths,
133 const val PREF_BUTTON_R = "buttonToggle5" 62 MaxEnum
134 const val PREF_BUTTON_ZL = "buttonToggle6"
135 const val PREF_BUTTON_ZR = "buttonToggle7"
136 const val PREF_BUTTON_PLUS = "buttonToggle8"
137 const val PREF_BUTTON_MINUS = "buttonToggle9"
138 const val PREF_BUTTON_DPAD = "buttonToggle10"
139 const val PREF_STICK_L = "buttonToggle11"
140 const val PREF_STICK_R = "buttonToggle12"
141 const val PREF_BUTTON_STICK_L = "buttonToggle13"
142 const val PREF_BUTTON_STICK_R = "buttonToggle14"
143 const val PREF_BUTTON_HOME = "buttonToggle15"
144 const val PREF_BUTTON_SCREENSHOT = "buttonToggle16"
145
146 const val PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER = "EmulationMenuSettings_JoystickRelCenter"
147 const val PREF_MENU_SETTINGS_DPAD_SLIDE = "EmulationMenuSettings_DpadSlideEnable"
148 const val PREF_MENU_SETTINGS_HAPTICS = "EmulationMenuSettings_Haptics"
149 const val PREF_MENU_SETTINGS_SHOW_FPS = "EmulationMenuSettings_ShowFps"
150 const val PREF_MENU_SETTINGS_SHOW_OVERLAY = "EmulationMenuSettings_ShowOverlay"
151
152 const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch"
153 const val PREF_THEME = "Theme"
154 const val PREF_THEME_MODE = "ThemeMode"
155 const val PREF_BLACK_BACKGROUNDS = "BlackBackgrounds"
156
157 private val configFileSectionsMap: MutableMap<String, List<String>> = HashMap()
158
159 val overlayPreferences = listOf(
160 PREF_OVERLAY_VERSION,
161 PREF_CONTROL_SCALE,
162 PREF_CONTROL_OPACITY,
163 PREF_TOUCH_ENABLED,
164 PREF_BUTTON_A,
165 PREF_BUTTON_B,
166 PREF_BUTTON_X,
167 PREF_BUTTON_Y,
168 PREF_BUTTON_L,
169 PREF_BUTTON_R,
170 PREF_BUTTON_ZL,
171 PREF_BUTTON_ZR,
172 PREF_BUTTON_PLUS,
173 PREF_BUTTON_MINUS,
174 PREF_BUTTON_DPAD,
175 PREF_STICK_L,
176 PREF_STICK_R,
177 PREF_BUTTON_HOME,
178 PREF_BUTTON_SCREENSHOT,
179 PREF_BUTTON_STICK_L,
180 PREF_BUTTON_STICK_R
181 )
182
183 const val LayoutOption_Unspecified = 0
184 const val LayoutOption_MobilePortrait = 4
185 const val LayoutOption_MobileLandscape = 5
186
187 init {
188 configFileSectionsMap[SettingsFile.FILE_NAME_CONFIG] =
189 listOf(
190 SECTION_GENERAL,
191 SECTION_SYSTEM,
192 SECTION_RENDERER,
193 SECTION_AUDIO,
194 SECTION_CPU
195 )
196 }
197 } 63 }
64
65 val settingsList = listOf<AbstractSetting>(
66 *BooleanSetting.values(),
67 *ByteSetting.values(),
68 *ShortSetting.values(),
69 *IntSetting.values(),
70 *FloatSetting.values(),
71 *LongSetting.values(),
72 *StringSetting.values()
73 )
74
75 const val SECTION_GENERAL = "General"
76 const val SECTION_SYSTEM = "System"
77 const val SECTION_RENDERER = "Renderer"
78 const val SECTION_AUDIO = "Audio"
79 const val SECTION_CPU = "Cpu"
80 const val SECTION_THEME = "Theme"
81 const val SECTION_DEBUG = "Debug"
82
83 const val PREF_MEMORY_WARNING_SHOWN = "MemoryWarningShown"
84
85 const val PREF_OVERLAY_VERSION = "OverlayVersion"
86 const val PREF_LANDSCAPE_OVERLAY_VERSION = "LandscapeOverlayVersion"
87 const val PREF_PORTRAIT_OVERLAY_VERSION = "PortraitOverlayVersion"
88 const val PREF_FOLDABLE_OVERLAY_VERSION = "FoldableOverlayVersion"
89 val overlayLayoutPrefs = listOf(
90 PREF_LANDSCAPE_OVERLAY_VERSION,
91 PREF_PORTRAIT_OVERLAY_VERSION,
92 PREF_FOLDABLE_OVERLAY_VERSION
93 )
94
95 const val PREF_CONTROL_SCALE = "controlScale"
96 const val PREF_CONTROL_OPACITY = "controlOpacity"
97 const val PREF_TOUCH_ENABLED = "isTouchEnabled"
98 const val PREF_BUTTON_A = "buttonToggle0"
99 const val PREF_BUTTON_B = "buttonToggle1"
100 const val PREF_BUTTON_X = "buttonToggle2"
101 const val PREF_BUTTON_Y = "buttonToggle3"
102 const val PREF_BUTTON_L = "buttonToggle4"
103 const val PREF_BUTTON_R = "buttonToggle5"
104 const val PREF_BUTTON_ZL = "buttonToggle6"
105 const val PREF_BUTTON_ZR = "buttonToggle7"
106 const val PREF_BUTTON_PLUS = "buttonToggle8"
107 const val PREF_BUTTON_MINUS = "buttonToggle9"
108 const val PREF_BUTTON_DPAD = "buttonToggle10"
109 const val PREF_STICK_L = "buttonToggle11"
110 const val PREF_STICK_R = "buttonToggle12"
111 const val PREF_BUTTON_STICK_L = "buttonToggle13"
112 const val PREF_BUTTON_STICK_R = "buttonToggle14"
113 const val PREF_BUTTON_HOME = "buttonToggle15"
114 const val PREF_BUTTON_SCREENSHOT = "buttonToggle16"
115
116 const val PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER = "EmulationMenuSettings_JoystickRelCenter"
117 const val PREF_MENU_SETTINGS_DPAD_SLIDE = "EmulationMenuSettings_DpadSlideEnable"
118 const val PREF_MENU_SETTINGS_HAPTICS = "EmulationMenuSettings_Haptics"
119 const val PREF_MENU_SETTINGS_SHOW_FPS = "EmulationMenuSettings_ShowFps"
120 const val PREF_MENU_SETTINGS_SHOW_OVERLAY = "EmulationMenuSettings_ShowOverlay"
121
122 const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch"
123 const val PREF_THEME = "Theme"
124 const val PREF_THEME_MODE = "ThemeMode"
125 const val PREF_BLACK_BACKGROUNDS = "BlackBackgrounds"
126
127 val overlayPreferences = listOf(
128 PREF_OVERLAY_VERSION,
129 PREF_CONTROL_SCALE,
130 PREF_CONTROL_OPACITY,
131 PREF_TOUCH_ENABLED,
132 PREF_BUTTON_A,
133 PREF_BUTTON_B,
134 PREF_BUTTON_X,
135 PREF_BUTTON_Y,
136 PREF_BUTTON_L,
137 PREF_BUTTON_R,
138 PREF_BUTTON_ZL,
139 PREF_BUTTON_ZR,
140 PREF_BUTTON_PLUS,
141 PREF_BUTTON_MINUS,
142 PREF_BUTTON_DPAD,
143 PREF_STICK_L,
144 PREF_STICK_R,
145 PREF_BUTTON_HOME,
146 PREF_BUTTON_SCREENSHOT,
147 PREF_BUTTON_STICK_L,
148 PREF_BUTTON_STICK_R
149 )
150
151 const val LayoutOption_Unspecified = 0
152 const val LayoutOption_MobilePortrait = 4
153 const val LayoutOption_MobileLandscape = 5
198} 154}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ShortSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ShortSetting.kt
new file mode 100644
index 000000000..c9a0c664c
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ShortSetting.kt
@@ -0,0 +1,25 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.features.settings.model
5
6import org.yuzu.yuzu_emu.utils.NativeConfig
7
8enum class ShortSetting(
9 override val key: String,
10 override val category: Settings.Category
11) : AbstractShortSetting {
12 RENDERER_SPEED_LIMIT("speed_limit", Settings.Category.Core);
13
14 override val short: Short
15 get() = NativeConfig.getShort(key, false)
16
17 override fun setShort(value: Short) = NativeConfig.setShort(key, value)
18
19 override val defaultValue: Short by lazy { NativeConfig.getShort(key, true) }
20
21 override val valueAsString: String
22 get() = short.toString()
23
24 override fun reset() = NativeConfig.setShort(key, defaultValue)
25}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/StringSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/StringSetting.kt
index 6621289fd..9bb3e66d4 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/StringSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/StringSetting.kt
@@ -3,36 +3,24 @@
3 3
4package org.yuzu.yuzu_emu.features.settings.model 4package org.yuzu.yuzu_emu.features.settings.model
5 5
6import org.yuzu.yuzu_emu.utils.NativeConfig
7
6enum class StringSetting( 8enum class StringSetting(
7 override val key: String, 9 override val key: String,
8 override val section: String, 10 override val category: Settings.Category
9 override val defaultValue: String
10) : AbstractStringSetting { 11) : AbstractStringSetting {
11 AUDIO_OUTPUT_ENGINE("output_engine", Settings.SECTION_AUDIO, "auto"), 12 // No string settings currently exist
12 CUSTOM_RTC("custom_rtc", Settings.SECTION_SYSTEM, "0"); 13 EMPTY_SETTING("", Settings.Category.UiGeneral);
14
15 override val string: String
16 get() = NativeConfig.getString(key, false)
17
18 override fun setString(value: String) = NativeConfig.setString(key, value)
13 19
14 override var string: String = defaultValue 20 override val defaultValue: String by lazy { NativeConfig.getString(key, true) }
15 21
16 override val valueAsString: String 22 override val valueAsString: String
17 get() = string 23 get() = string
18 24
19 override val isRuntimeEditable: Boolean 25 override fun reset() = NativeConfig.setString(key, defaultValue)
20 get() {
21 for (setting in NOT_RUNTIME_EDITABLE) {
22 if (setting == this) {
23 return false
24 }
25 }
26 return true
27 }
28
29 companion object {
30 private val NOT_RUNTIME_EDITABLE = listOf(
31 CUSTOM_RTC
32 )
33
34 fun from(key: String): StringSetting? = StringSetting.values().firstOrNull { it.key == key }
35
36 fun clear() = StringSetting.values().forEach { it.string = it.defaultValue }
37 }
38} 26}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/DateTimeSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/DateTimeSetting.kt
index bc0bf7788..8bc164197 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/DateTimeSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/DateTimeSetting.kt
@@ -3,29 +3,16 @@
3 3
4package org.yuzu.yuzu_emu.features.settings.model.view 4package org.yuzu.yuzu_emu.features.settings.model.view
5 5
6import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting 6import org.yuzu.yuzu_emu.features.settings.model.AbstractLongSetting
7import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting
8 7
9class DateTimeSetting( 8class DateTimeSetting(
10 setting: AbstractSetting?, 9 private val longSetting: AbstractLongSetting,
11 titleId: Int, 10 titleId: Int,
12 descriptionId: Int, 11 descriptionId: Int
13 val key: String? = null, 12) : SettingsItem(longSetting, titleId, descriptionId) {
14 private val defaultValue: String? = null
15) : SettingsItem(setting, titleId, descriptionId) {
16 override val type = TYPE_DATETIME_SETTING 13 override val type = TYPE_DATETIME_SETTING
17 14
18 val value: String 15 var value: Long
19 get() = if (setting != null) { 16 get() = longSetting.long
20 val setting = setting as AbstractStringSetting 17 set(value) = (setting as AbstractLongSetting).setLong(value)
21 setting.string
22 } else {
23 defaultValue!!
24 }
25
26 fun setSelectedValue(datetime: String): AbstractStringSetting {
27 val stringSetting = setting as AbstractStringSetting
28 stringSetting.string = datetime
29 return stringSetting
30 }
31} 18}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/HeaderSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/HeaderSetting.kt
index a67001311..d31ce1c31 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/HeaderSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/HeaderSetting.kt
@@ -5,6 +5,6 @@ package org.yuzu.yuzu_emu.features.settings.model.view
5 5
6class HeaderSetting( 6class HeaderSetting(
7 titleId: Int 7 titleId: Int
8) : SettingsItem(null, titleId, 0) { 8) : SettingsItem(emptySetting, titleId, 0) {
9 override val type = TYPE_HEADER 9 override val type = TYPE_HEADER
10} 10}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/RunnableSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/RunnableSetting.kt
index caaab50d8..522cc49df 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/RunnableSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/RunnableSetting.kt
@@ -8,6 +8,6 @@ class RunnableSetting(
8 descriptionId: Int, 8 descriptionId: Int,
9 val isRuntimeRunnable: Boolean, 9 val isRuntimeRunnable: Boolean,
10 val runnable: () -> Unit 10 val runnable: () -> Unit
11) : SettingsItem(null, titleId, descriptionId) { 11) : SettingsItem(emptySetting, titleId, descriptionId) {
12 override val type = TYPE_RUNNABLE 12 override val type = TYPE_RUNNABLE
13} 13}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt
index 07520849e..b3b3fc209 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt
@@ -4,7 +4,15 @@
4package org.yuzu.yuzu_emu.features.settings.model.view 4package org.yuzu.yuzu_emu.features.settings.model.view
5 5
6import org.yuzu.yuzu_emu.NativeLibrary 6import org.yuzu.yuzu_emu.NativeLibrary
7import org.yuzu.yuzu_emu.R
8import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting
7import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting 9import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
10import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
11import org.yuzu.yuzu_emu.features.settings.model.ByteSetting
12import org.yuzu.yuzu_emu.features.settings.model.IntSetting
13import org.yuzu.yuzu_emu.features.settings.model.LongSetting
14import org.yuzu.yuzu_emu.features.settings.model.Settings
15import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
8 16
9/** 17/**
10 * ViewModel abstraction for an Item in the RecyclerView powering SettingsFragments. 18 * ViewModel abstraction for an Item in the RecyclerView powering SettingsFragments.
@@ -14,7 +22,7 @@ import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
14 * file.) 22 * file.)
15 */ 23 */
16abstract class SettingsItem( 24abstract class SettingsItem(
17 var setting: AbstractSetting?, 25 val setting: AbstractSetting,
18 val nameId: Int, 26 val nameId: Int,
19 val descriptionId: Int 27 val descriptionId: Int
20) { 28) {
@@ -23,7 +31,7 @@ abstract class SettingsItem(
23 val isEditable: Boolean 31 val isEditable: Boolean
24 get() { 32 get() {
25 if (!NativeLibrary.isRunning()) return true 33 if (!NativeLibrary.isRunning()) return true
26 return setting?.isRuntimeEditable ?: false 34 return setting.isRuntimeModifiable
27 } 35 }
28 36
29 companion object { 37 companion object {
@@ -35,5 +43,240 @@ abstract class SettingsItem(
35 const val TYPE_STRING_SINGLE_CHOICE = 5 43 const val TYPE_STRING_SINGLE_CHOICE = 5
36 const val TYPE_DATETIME_SETTING = 6 44 const val TYPE_DATETIME_SETTING = 6
37 const val TYPE_RUNNABLE = 7 45 const val TYPE_RUNNABLE = 7
46
47 const val FASTMEM_COMBINED = "fastmem_combined"
48
49 val emptySetting = object : AbstractSetting {
50 override val key: String = ""
51 override val category: Settings.Category = Settings.Category.Ui
52 override val defaultValue: Any = false
53 override fun reset() {}
54 }
55
56 // Extension for putting SettingsItems into a hashmap without repeating yourself
57 fun HashMap<String, SettingsItem>.put(item: SettingsItem) {
58 put(item.setting.key, item)
59 }
60
61 // List of all general
62 val settingsItems = HashMap<String, SettingsItem>().apply {
63 put(
64 SwitchSetting(
65 BooleanSetting.RENDERER_USE_SPEED_LIMIT,
66 R.string.frame_limit_enable,
67 R.string.frame_limit_enable_description
68 )
69 )
70 put(
71 SliderSetting(
72 ShortSetting.RENDERER_SPEED_LIMIT,
73 R.string.frame_limit_slider,
74 R.string.frame_limit_slider_description,
75 1,
76 200,
77 "%"
78 )
79 )
80 put(
81 SingleChoiceSetting(
82 IntSetting.CPU_ACCURACY,
83 R.string.cpu_accuracy,
84 0,
85 R.array.cpuAccuracyNames,
86 R.array.cpuAccuracyValues
87 )
88 )
89 put(
90 SwitchSetting(
91 BooleanSetting.PICTURE_IN_PICTURE,
92 R.string.picture_in_picture,
93 R.string.picture_in_picture_description
94 )
95 )
96 put(
97 SwitchSetting(
98 BooleanSetting.USE_DOCKED_MODE,
99 R.string.use_docked_mode,
100 R.string.use_docked_mode_description
101 )
102 )
103 put(
104 SingleChoiceSetting(
105 IntSetting.REGION_INDEX,
106 R.string.emulated_region,
107 0,
108 R.array.regionNames,
109 R.array.regionValues
110 )
111 )
112 put(
113 SingleChoiceSetting(
114 IntSetting.LANGUAGE_INDEX,
115 R.string.emulated_language,
116 0,
117 R.array.languageNames,
118 R.array.languageValues
119 )
120 )
121 put(
122 SwitchSetting(
123 BooleanSetting.USE_CUSTOM_RTC,
124 R.string.use_custom_rtc,
125 R.string.use_custom_rtc_description
126 )
127 )
128 put(DateTimeSetting(LongSetting.CUSTOM_RTC, R.string.set_custom_rtc, 0))
129 put(
130 SingleChoiceSetting(
131 IntSetting.RENDERER_ACCURACY,
132 R.string.renderer_accuracy,
133 0,
134 R.array.rendererAccuracyNames,
135 R.array.rendererAccuracyValues
136 )
137 )
138 put(
139 SingleChoiceSetting(
140 IntSetting.RENDERER_RESOLUTION,
141 R.string.renderer_resolution,
142 0,
143 R.array.rendererResolutionNames,
144 R.array.rendererResolutionValues
145 )
146 )
147 put(
148 SingleChoiceSetting(
149 IntSetting.RENDERER_VSYNC,
150 R.string.renderer_vsync,
151 0,
152 R.array.rendererVSyncNames,
153 R.array.rendererVSyncValues
154 )
155 )
156 put(
157 SingleChoiceSetting(
158 IntSetting.RENDERER_SCALING_FILTER,
159 R.string.renderer_scaling_filter,
160 0,
161 R.array.rendererScalingFilterNames,
162 R.array.rendererScalingFilterValues
163 )
164 )
165 put(
166 SingleChoiceSetting(
167 IntSetting.RENDERER_ANTI_ALIASING,
168 R.string.renderer_anti_aliasing,
169 0,
170 R.array.rendererAntiAliasingNames,
171 R.array.rendererAntiAliasingValues
172 )
173 )
174 put(
175 SingleChoiceSetting(
176 IntSetting.RENDERER_SCREEN_LAYOUT,
177 R.string.renderer_screen_layout,
178 0,
179 R.array.rendererScreenLayoutNames,
180 R.array.rendererScreenLayoutValues
181 )
182 )
183 put(
184 SingleChoiceSetting(
185 IntSetting.RENDERER_ASPECT_RATIO,
186 R.string.renderer_aspect_ratio,
187 0,
188 R.array.rendererAspectRatioNames,
189 R.array.rendererAspectRatioValues
190 )
191 )
192 put(
193 SwitchSetting(
194 BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE,
195 R.string.use_disk_shader_cache,
196 R.string.use_disk_shader_cache_description
197 )
198 )
199 put(
200 SwitchSetting(
201 BooleanSetting.RENDERER_FORCE_MAX_CLOCK,
202 R.string.renderer_force_max_clock,
203 R.string.renderer_force_max_clock_description
204 )
205 )
206 put(
207 SwitchSetting(
208 BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS,
209 R.string.renderer_asynchronous_shaders,
210 R.string.renderer_asynchronous_shaders_description
211 )
212 )
213 put(
214 SwitchSetting(
215 BooleanSetting.RENDERER_REACTIVE_FLUSHING,
216 R.string.renderer_reactive_flushing,
217 R.string.renderer_reactive_flushing_description
218 )
219 )
220 put(
221 SingleChoiceSetting(
222 IntSetting.AUDIO_OUTPUT_ENGINE,
223 R.string.audio_output_engine,
224 0,
225 R.array.outputEngineEntries,
226 R.array.outputEngineValues
227 )
228 )
229 put(
230 SliderSetting(
231 ByteSetting.AUDIO_VOLUME,
232 R.string.audio_volume,
233 R.string.audio_volume_description,
234 0,
235 100,
236 "%"
237 )
238 )
239 put(
240 SingleChoiceSetting(
241 IntSetting.RENDERER_BACKEND,
242 R.string.renderer_api,
243 0,
244 R.array.rendererApiNames,
245 R.array.rendererApiValues
246 )
247 )
248 put(
249 SwitchSetting(
250 BooleanSetting.RENDERER_DEBUG,
251 R.string.renderer_debug,
252 R.string.renderer_debug_description
253 )
254 )
255 put(
256 SwitchSetting(
257 BooleanSetting.CPU_DEBUG_MODE,
258 R.string.cpu_debug_mode,
259 R.string.cpu_debug_mode_description
260 )
261 )
262
263 val fastmem = object : AbstractBooleanSetting {
264 override val boolean: Boolean
265 get() =
266 BooleanSetting.FASTMEM.boolean && BooleanSetting.FASTMEM_EXCLUSIVES.boolean
267
268 override fun setBoolean(value: Boolean) {
269 BooleanSetting.FASTMEM.setBoolean(value)
270 BooleanSetting.FASTMEM_EXCLUSIVES.setBoolean(value)
271 }
272
273 override val key: String = FASTMEM_COMBINED
274 override val category = Settings.Category.Cpu
275 override val isRuntimeModifiable: Boolean = false
276 override val defaultValue: Boolean = true
277 override fun reset() = setBoolean(defaultValue)
278 }
279 put(SwitchSetting(fastmem, R.string.fastmem, 0))
280 }
38 } 281 }
39} 282}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SingleChoiceSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SingleChoiceSetting.kt
index 7306ec458..705527a73 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SingleChoiceSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SingleChoiceSetting.kt
@@ -4,36 +4,27 @@
4package org.yuzu.yuzu_emu.features.settings.model.view 4package org.yuzu.yuzu_emu.features.settings.model.view
5 5
6import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting 6import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
7import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
7 8
8class SingleChoiceSetting( 9class SingleChoiceSetting(
9 setting: AbstractIntSetting?, 10 setting: AbstractSetting,
10 titleId: Int, 11 titleId: Int,
11 descriptionId: Int, 12 descriptionId: Int,
12 val choicesId: Int, 13 val choicesId: Int,
13 val valuesId: Int, 14 val valuesId: Int
14 val key: String? = null,
15 val defaultValue: Int? = null
16) : SettingsItem(setting, titleId, descriptionId) { 15) : SettingsItem(setting, titleId, descriptionId) {
17 override val type = TYPE_SINGLE_CHOICE 16 override val type = TYPE_SINGLE_CHOICE
18 17
19 val selectedValue: Int 18 var selectedValue: Int
20 get() = if (setting != null) { 19 get() {
21 val setting = setting as AbstractIntSetting 20 return when (setting) {
22 setting.int 21 is AbstractIntSetting -> setting.int
23 } else { 22 else -> -1
24 defaultValue!! 23 }
24 }
25 set(value) {
26 when (setting) {
27 is AbstractIntSetting -> setting.setInt(value)
28 }
25 } 29 }
26
27 /**
28 * Write a value to the backing int. If that int was previously null,
29 * initializes a new one and returns it, so it can be added to the Hashmap.
30 *
31 * @param selection New value of the int.
32 * @return the existing setting with the new value applied.
33 */
34 fun setSelectedValue(selection: Int): AbstractIntSetting {
35 val intSetting = setting as AbstractIntSetting
36 intSetting.int = selection
37 return intSetting
38 }
39} 30}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SliderSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SliderSetting.kt
index 92d0167ae..c3b5df02c 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SliderSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SliderSetting.kt
@@ -3,60 +3,39 @@
3 3
4package org.yuzu.yuzu_emu.features.settings.model.view 4package org.yuzu.yuzu_emu.features.settings.model.view
5 5
6import kotlin.math.roundToInt 6import org.yuzu.yuzu_emu.features.settings.model.AbstractByteSetting
7import org.yuzu.yuzu_emu.features.settings.model.AbstractFloatSetting 7import org.yuzu.yuzu_emu.features.settings.model.AbstractFloatSetting
8import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting 8import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
9import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting 9import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
10import org.yuzu.yuzu_emu.utils.Log 10import org.yuzu.yuzu_emu.features.settings.model.AbstractShortSetting
11import kotlin.math.roundToInt
11 12
12class SliderSetting( 13class SliderSetting(
13 setting: AbstractSetting?, 14 setting: AbstractSetting,
14 titleId: Int, 15 titleId: Int,
15 descriptionId: Int, 16 descriptionId: Int,
16 val min: Int, 17 val min: Int,
17 val max: Int, 18 val max: Int,
18 val units: String, 19 val units: String
19 val key: String? = null,
20 val defaultValue: Int? = null
21) : SettingsItem(setting, titleId, descriptionId) { 20) : SettingsItem(setting, titleId, descriptionId) {
22 override val type = TYPE_SLIDER 21 override val type = TYPE_SLIDER
23 22
24 val selectedValue: Int 23 var selectedValue: Int
25 get() { 24 get() {
26 val setting = setting ?: return defaultValue!!
27 return when (setting) { 25 return when (setting) {
26 is AbstractByteSetting -> setting.byte.toInt()
27 is AbstractShortSetting -> setting.short.toInt()
28 is AbstractIntSetting -> setting.int 28 is AbstractIntSetting -> setting.int
29 is AbstractFloatSetting -> setting.float.roundToInt() 29 is AbstractFloatSetting -> setting.float.roundToInt()
30 else -> { 30 else -> -1
31 Log.error("[SliderSetting] Error casting setting type.") 31 }
32 -1 32 }
33 } 33 set(value) {
34 when (setting) {
35 is AbstractByteSetting -> setting.setByte(value.toByte())
36 is AbstractShortSetting -> setting.setShort(value.toShort())
37 is AbstractIntSetting -> setting.setInt(value)
38 is AbstractFloatSetting -> setting.setFloat(value.toFloat())
34 } 39 }
35 } 40 }
36
37 /**
38 * Write a value to the backing int. If that int was previously null,
39 * initializes a new one and returns it, so it can be added to the Hashmap.
40 *
41 * @param selection New value of the int.
42 * @return the existing setting with the new value applied.
43 */
44 fun setSelectedValue(selection: Int): AbstractIntSetting {
45 val intSetting = setting as AbstractIntSetting
46 intSetting.int = selection
47 return intSetting
48 }
49
50 /**
51 * Write a value to the backing float. If that float was previously null,
52 * initializes a new one and returns it, so it can be added to the Hashmap.
53 *
54 * @param selection New value of the float.
55 * @return the existing setting with the new value applied.
56 */
57 fun setSelectedValue(selection: Float): AbstractFloatSetting {
58 val floatSetting = setting as AbstractFloatSetting
59 floatSetting.float = selection
60 return floatSetting
61 }
62} 41}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/StringSingleChoiceSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/StringSingleChoiceSetting.kt
index 3b6731dcd..871dab4f3 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/StringSingleChoiceSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/StringSingleChoiceSetting.kt
@@ -3,57 +3,31 @@
3 3
4package org.yuzu.yuzu_emu.features.settings.model.view 4package org.yuzu.yuzu_emu.features.settings.model.view
5 5
6import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
7import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting 6import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting
8 7
9class StringSingleChoiceSetting( 8class StringSingleChoiceSetting(
10 setting: AbstractSetting?, 9 private val stringSetting: AbstractStringSetting,
11 titleId: Int, 10 titleId: Int,
12 descriptionId: Int, 11 descriptionId: Int,
13 val choices: Array<String>, 12 val choices: Array<String>,
14 val values: Array<String>?, 13 val values: Array<String>
15 val key: String? = null, 14) : SettingsItem(stringSetting, titleId, descriptionId) {
16 private val defaultValue: String? = null
17) : SettingsItem(setting, titleId, descriptionId) {
18 override val type = TYPE_STRING_SINGLE_CHOICE 15 override val type = TYPE_STRING_SINGLE_CHOICE
19 16
20 fun getValueAt(index: Int): String? { 17 fun getValueAt(index: Int): String =
21 if (values == null) return null 18 if (index >= 0 && index < values.size) values[index] else ""
22 return if (index >= 0 && index < values.size) { 19
23 values[index] 20 var selectedValue: String
24 } else { 21 get() = stringSetting.string
25 "" 22 set(value) = stringSetting.setString(value)
26 }
27 }
28 23
29 val selectedValue: String
30 get() = if (setting != null) {
31 val setting = setting as AbstractStringSetting
32 setting.string
33 } else {
34 defaultValue!!
35 }
36 val selectValueIndex: Int 24 val selectValueIndex: Int
37 get() { 25 get() {
38 val selectedValue = selectedValue 26 for (i in values.indices) {
39 for (i in values!!.indices) {
40 if (values[i] == selectedValue) { 27 if (values[i] == selectedValue) {
41 return i 28 return i
42 } 29 }
43 } 30 }
44 return -1 31 return -1
45 } 32 }
46
47 /**
48 * Write a value to the backing int. If that int was previously null,
49 * initializes a new one and returns it, so it can be added to the Hashmap.
50 *
51 * @param selection New value of the int.
52 * @return the existing setting with the new value applied.
53 */
54 fun setSelectedValue(selection: String): AbstractStringSetting {
55 val stringSetting = setting as AbstractStringSetting
56 stringSetting.string = selection
57 return stringSetting
58 }
59} 33}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt
index 8a9d13a92..91c273964 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt
@@ -7,6 +7,6 @@ class SubmenuSetting(
7 titleId: Int, 7 titleId: Int,
8 descriptionId: Int, 8 descriptionId: Int,
9 val menuKey: String 9 val menuKey: String
10) : SettingsItem(null, titleId, descriptionId) { 10) : SettingsItem(emptySetting, titleId, descriptionId) {
11 override val type = TYPE_SUBMENU 11 override val type = TYPE_SUBMENU
12} 12}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SwitchSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SwitchSetting.kt
index 90b198718..416967e64 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SwitchSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SwitchSetting.kt
@@ -10,53 +10,22 @@ import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
10class SwitchSetting( 10class SwitchSetting(
11 setting: AbstractSetting, 11 setting: AbstractSetting,
12 titleId: Int, 12 titleId: Int,
13 descriptionId: Int, 13 descriptionId: Int
14 val key: String? = null,
15 val defaultValue: Any? = null
16) : SettingsItem(setting, titleId, descriptionId) { 14) : SettingsItem(setting, titleId, descriptionId) {
17 override val type = TYPE_SWITCH 15 override val type = TYPE_SWITCH
18 16
19 val isChecked: Boolean 17 var checked: Boolean
20 get() { 18 get() {
21 if (setting == null) { 19 return when (setting) {
22 return defaultValue as Boolean 20 is AbstractIntSetting -> setting.int == 1
21 is AbstractBooleanSetting -> setting.boolean
22 else -> false
23 } 23 }
24
25 // Try integer setting
26 try {
27 val setting = setting as AbstractIntSetting
28 return setting.int == 1
29 } catch (_: ClassCastException) {
30 }
31
32 // Try boolean setting
33 try {
34 val setting = setting as AbstractBooleanSetting
35 return setting.boolean
36 } catch (_: ClassCastException) {
37 }
38 return defaultValue as Boolean
39 } 24 }
40 25 set(value) {
41 /** 26 when (setting) {
42 * Write a value to the backing boolean. If that boolean was previously null, 27 is AbstractIntSetting -> setting.setInt(if (value) 1 else 0)
43 * initializes a new one and returns it, so it can be added to the Hashmap. 28 is AbstractBooleanSetting -> setting.setBoolean(value)
44 * 29 }
45 * @param checked Pretty self explanatory.
46 * @return the existing setting with the new value applied.
47 */
48 fun setChecked(checked: Boolean): AbstractSetting {
49 // Try integer setting
50 try {
51 val setting = setting as AbstractIntSetting
52 setting.int = if (checked) 1 else 0
53 return setting
54 } catch (_: ClassCastException) {
55 } 30 }
56
57 // Try boolean setting
58 val setting = setting as AbstractBooleanSetting
59 setting.boolean = checked
60 return setting
61 }
62} 31}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt
index e6fffc832..908c01265 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 @@
3 3
4package org.yuzu.yuzu_emu.features.settings.ui 4package org.yuzu.yuzu_emu.features.settings.ui
5 5
6import android.content.Context
7import android.content.Intent
8import android.os.Bundle 6import android.os.Bundle
9import android.view.Menu
10import android.view.View 7import android.view.View
11import android.view.ViewGroup.MarginLayoutParams 8import android.view.ViewGroup.MarginLayoutParams
12import android.widget.Toast 9import android.widget.Toast
@@ -16,28 +13,24 @@ import androidx.appcompat.app.AppCompatActivity
16import androidx.core.view.ViewCompat 13import androidx.core.view.ViewCompat
17import androidx.core.view.WindowCompat 14import androidx.core.view.WindowCompat
18import androidx.core.view.WindowInsetsCompat 15import androidx.core.view.WindowInsetsCompat
19import androidx.core.view.updatePadding 16import androidx.navigation.fragment.NavHostFragment
17import androidx.navigation.navArgs
20import com.google.android.material.color.MaterialColors 18import com.google.android.material.color.MaterialColors
21import java.io.IOException 19import java.io.IOException
22import org.yuzu.yuzu_emu.R 20import org.yuzu.yuzu_emu.R
23import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding 21import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding
24import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
25import org.yuzu.yuzu_emu.features.settings.model.FloatSetting
26import org.yuzu.yuzu_emu.features.settings.model.IntSetting
27import org.yuzu.yuzu_emu.features.settings.model.Settings 22import org.yuzu.yuzu_emu.features.settings.model.Settings
28import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel
29import org.yuzu.yuzu_emu.features.settings.model.StringSetting
30import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile 23import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
24import org.yuzu.yuzu_emu.fragments.ResetSettingsDialogFragment
25import org.yuzu.yuzu_emu.model.SettingsViewModel
31import org.yuzu.yuzu_emu.utils.* 26import org.yuzu.yuzu_emu.utils.*
32 27
33class SettingsActivity : AppCompatActivity(), SettingsActivityView { 28class SettingsActivity : AppCompatActivity() {
34 private val presenter = SettingsActivityPresenter(this)
35
36 private lateinit var binding: ActivitySettingsBinding 29 private lateinit var binding: ActivitySettingsBinding
37 30
38 private val settingsViewModel: SettingsViewModel by viewModels() 31 private val args by navArgs<SettingsActivityArgs>()
39 32
40 override val settings: Settings get() = settingsViewModel.settings 33 private val settingsViewModel: SettingsViewModel by viewModels()
41 34
42 override fun onCreate(savedInstanceState: Bundle?) { 35 override fun onCreate(savedInstanceState: Bundle?) {
43 ThemeHelper.setTheme(this) 36 ThemeHelper.setTheme(this)
@@ -47,16 +40,17 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
47 binding = ActivitySettingsBinding.inflate(layoutInflater) 40 binding = ActivitySettingsBinding.inflate(layoutInflater)
48 setContentView(binding.root) 41 setContentView(binding.root)
49 42
50 WindowCompat.setDecorFitsSystemWindows(window, false) 43 settingsViewModel.game = args.game
51 44
52 val launcher = intent 45 val navHostFragment =
53 val gameID = launcher.getStringExtra(ARG_GAME_ID) 46 supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment
54 val menuTag = launcher.getStringExtra(ARG_MENU_TAG) 47 navHostFragment.navController.setGraph(R.navigation.settings_navigation, intent.extras)
55 presenter.onCreate(savedInstanceState, menuTag!!, gameID!!)
56 48
57 // Show "Back" button in the action bar for navigation 49 WindowCompat.setDecorFitsSystemWindows(window, false)
58 setSupportActionBar(binding.toolbarSettings) 50
59 supportActionBar!!.setDisplayHomeAsUpEnabled(true) 51 if (savedInstanceState != null) {
52 settingsViewModel.shouldSave = savedInstanceState.getBoolean(KEY_SHOULD_SAVE)
53 }
60 54
61 if (InsetsHelper.getSystemGestureType(applicationContext) != 55 if (InsetsHelper.getSystemGestureType(applicationContext) !=
62 InsetsHelper.GESTURE_NAVIGATION 56 InsetsHelper.GESTURE_NAVIGATION
@@ -72,6 +66,28 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
72 ) 66 )
73 } 67 }
74 68
69 settingsViewModel.shouldRecreate.observe(this) {
70 if (it) {
71 settingsViewModel.setShouldRecreate(false)
72 recreate()
73 }
74 }
75 settingsViewModel.shouldNavigateBack.observe(this) {
76 if (it) {
77 settingsViewModel.setShouldNavigateBack(false)
78 navigateBack()
79 }
80 }
81 settingsViewModel.shouldShowResetSettingsDialog.observe(this) {
82 if (it) {
83 settingsViewModel.setShouldShowResetSettingsDialog(false)
84 ResetSettingsDialogFragment().show(
85 supportFragmentManager,
86 ResetSettingsDialogFragment.TAG
87 )
88 }
89 }
90
75 onBackPressedDispatcher.addCallback( 91 onBackPressedDispatcher.addCallback(
76 this, 92 this,
77 object : OnBackPressedCallback(true) { 93 object : OnBackPressedCallback(true) {
@@ -82,34 +98,28 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
82 setInsets() 98 setInsets()
83 } 99 }
84 100
85 override fun onSupportNavigateUp(): Boolean { 101 fun navigateBack() {
86 navigateBack() 102 val navHostFragment =
87 return true 103 supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment
88 } 104 if (navHostFragment.childFragmentManager.backStackEntryCount > 0) {
89 105 navHostFragment.navController.popBackStack()
90 private fun navigateBack() {
91 if (supportFragmentManager.backStackEntryCount > 0) {
92 supportFragmentManager.popBackStack()
93 } else { 106 } else {
94 finish() 107 finish()
95 } 108 }
96 } 109 }
97 110
98 override fun onCreateOptionsMenu(menu: Menu): Boolean {
99 val inflater = menuInflater
100 inflater.inflate(R.menu.menu_settings, menu)
101 return true
102 }
103
104 override fun onSaveInstanceState(outState: Bundle) { 111 override fun onSaveInstanceState(outState: Bundle) {
105 // Critical: If super method is not called, rotations will be busted. 112 // Critical: If super method is not called, rotations will be busted.
106 super.onSaveInstanceState(outState) 113 super.onSaveInstanceState(outState)
107 presenter.saveState(outState) 114 outState.putBoolean(KEY_SHOULD_SAVE, settingsViewModel.shouldSave)
108 } 115 }
109 116
110 override fun onStart() { 117 override fun onStart() {
111 super.onStart() 118 super.onStart()
112 presenter.onStart() 119 // TODO: Load custom settings contextually
120 if (!DirectoryInitialization.areDirectoriesReady) {
121 DirectoryInitialization.start()
122 }
113 } 123 }
114 124
115 /** 125 /**
@@ -119,131 +129,51 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
119 */ 129 */
120 override fun onStop() { 130 override fun onStop() {
121 super.onStop() 131 super.onStop()
122 presenter.onStop(isFinishing) 132 if (isFinishing && settingsViewModel.shouldSave) {
123 } 133 Log.debug("[SettingsActivity] Settings activity stopping. Saving settings to INI...")
124 134 Settings.saveSettings()
125 override fun showSettingsFragment(menuTag: String, addToStack: Boolean, gameId: String) {
126 if (!addToStack && settingsFragment != null) {
127 return
128 } 135 }
129
130 val transaction = supportFragmentManager.beginTransaction()
131 if (addToStack) {
132 if (areSystemAnimationsEnabled()) {
133 transaction.setCustomAnimations(
134 R.anim.anim_settings_fragment_in,
135 R.anim.anim_settings_fragment_out,
136 0,
137 R.anim.anim_pop_settings_fragment_out
138 )
139 }
140 transaction.addToBackStack(null)
141 }
142 transaction.replace(
143 R.id.frame_content,
144 SettingsFragment.newInstance(menuTag, gameId),
145 FRAGMENT_TAG
146 )
147 transaction.commit()
148 }
149
150 private fun areSystemAnimationsEnabled(): Boolean {
151 val duration = android.provider.Settings.Global.getFloat(
152 contentResolver,
153 android.provider.Settings.Global.ANIMATOR_DURATION_SCALE,
154 1f
155 )
156 val transition = android.provider.Settings.Global.getFloat(
157 contentResolver,
158 android.provider.Settings.Global.TRANSITION_ANIMATION_SCALE,
159 1f
160 )
161 return duration != 0f && transition != 0f
162 }
163
164 override fun onSettingsFileLoaded() {
165 val fragment: SettingsFragmentView? = settingsFragment
166 fragment?.loadSettingsList()
167 }
168
169 override fun onSettingsFileNotFound() {
170 val fragment: SettingsFragmentView? = settingsFragment
171 fragment?.loadSettingsList()
172 }
173
174 override fun showToastMessage(message: String, is_long: Boolean) {
175 Toast.makeText(
176 this,
177 message,
178 if (is_long) Toast.LENGTH_LONG else Toast.LENGTH_SHORT
179 ).show()
180 } 136 }
181 137
182 override fun onSettingChanged() { 138 override fun onDestroy() {
183 presenter.onSettingChanged() 139 settingsViewModel.clear()
140 super.onDestroy()
184 } 141 }
185 142
186 fun onSettingsReset() { 143 fun onSettingsReset() {
187 // Prevents saving to a non-existent settings file 144 // Prevents saving to a non-existent settings file
188 presenter.onSettingsReset() 145 settingsViewModel.shouldSave = false
189
190 // Reset the static memory representation of each setting
191 BooleanSetting.clear()
192 FloatSetting.clear()
193 IntSetting.clear()
194 StringSetting.clear()
195 146
196 // Delete settings file because the user may have changed values that do not exist in the UI 147 // Delete settings file because the user may have changed values that do not exist in the UI
197 val settingsFile = SettingsFile.getSettingsFile(SettingsFile.FILE_NAME_CONFIG) 148 val settingsFile = SettingsFile.getSettingsFile(SettingsFile.FILE_NAME_CONFIG)
198 if (!settingsFile.delete()) { 149 if (!settingsFile.delete()) {
199 throw IOException("Failed to delete $settingsFile") 150 throw IOException("Failed to delete $settingsFile")
200 } 151 }
152 Settings.settingsList.forEach { it.reset() }
201 153
202 showToastMessage(getString(R.string.settings_reset), true) 154 Toast.makeText(
155 applicationContext,
156 getString(R.string.settings_reset),
157 Toast.LENGTH_LONG
158 ).show()
203 finish() 159 finish()
204 } 160 }
205 161
206 fun setToolbarTitle(title: String) {
207 binding.toolbarSettingsLayout.title = title
208 }
209
210 private val settingsFragment: SettingsFragment?
211 get() = supportFragmentManager.findFragmentByTag(FRAGMENT_TAG) as SettingsFragment?
212
213 private fun setInsets() { 162 private fun setInsets() {
214 ViewCompat.setOnApplyWindowInsetsListener( 163 ViewCompat.setOnApplyWindowInsetsListener(
215 binding.frameContent 164 binding.navigationBarShade
216 ) { view: View, windowInsets: WindowInsetsCompat -> 165 ) { view: View, windowInsets: WindowInsetsCompat ->
217 val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) 166 val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
218 val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
219 view.updatePadding(
220 left = barInsets.left + cutoutInsets.left,
221 right = barInsets.right + cutoutInsets.right
222 )
223
224 val mlpAppBar = binding.appbarSettings.layoutParams as MarginLayoutParams
225 mlpAppBar.leftMargin = barInsets.left + cutoutInsets.left
226 mlpAppBar.rightMargin = barInsets.right + cutoutInsets.right
227 binding.appbarSettings.layoutParams = mlpAppBar
228 167
229 val mlpShade = binding.navigationBarShade.layoutParams as MarginLayoutParams 168 val mlpShade = view.layoutParams as MarginLayoutParams
230 mlpShade.height = barInsets.bottom 169 mlpShade.height = barInsets.bottom
231 binding.navigationBarShade.layoutParams = mlpShade 170 view.layoutParams = mlpShade
232 171
233 windowInsets 172 windowInsets
234 } 173 }
235 } 174 }
236 175
237 companion object { 176 companion object {
238 private const val ARG_MENU_TAG = "menu_tag" 177 private const val KEY_SHOULD_SAVE = "should_save"
239 private const val ARG_GAME_ID = "game_id"
240 private const val FRAGMENT_TAG = "settings"
241
242 fun launch(context: Context, menuTag: String?, gameId: String?) {
243 val settings = Intent(context, SettingsActivity::class.java)
244 settings.putExtra(ARG_MENU_TAG, menuTag)
245 settings.putExtra(ARG_GAME_ID, gameId)
246 context.startActivity(settings)
247 }
248 } 178 }
249} 179}
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 93e677b21..000000000
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityPresenter.kt
+++ /dev/null
@@ -1,90 +0,0 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.features.settings.ui
5
6import android.content.Context
7import android.os.Bundle
8import android.text.TextUtils
9import java.io.File
10import org.yuzu.yuzu_emu.NativeLibrary
11import org.yuzu.yuzu_emu.features.settings.model.Settings
12import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
13import org.yuzu.yuzu_emu.utils.DirectoryInitialization
14import org.yuzu.yuzu_emu.utils.Log
15
16class SettingsActivityPresenter(private val activityView: SettingsActivityView) {
17 val settings: Settings get() = activityView.settings
18
19 private var shouldSave = false
20 private lateinit var menuTag: String
21 private lateinit var gameId: String
22
23 fun onCreate(savedInstanceState: Bundle?, menuTag: String, gameId: String) {
24 this.menuTag = menuTag
25 this.gameId = gameId
26 if (savedInstanceState != null) {
27 shouldSave = savedInstanceState.getBoolean(KEY_SHOULD_SAVE)
28 }
29 }
30
31 fun onStart() {
32 prepareDirectoriesIfNeeded()
33 }
34
35 private fun loadSettingsUI() {
36 if (!settings.isLoaded) {
37 if (!TextUtils.isEmpty(gameId)) {
38 settings.loadSettings(gameId, activityView)
39 } else {
40 settings.loadSettings(activityView)
41 }
42 }
43 activityView.showSettingsFragment(menuTag, false, gameId)
44 activityView.onSettingsFileLoaded()
45 }
46
47 private fun prepareDirectoriesIfNeeded() {
48 val configFile =
49 File(
50 "${DirectoryInitialization.userDirectory}/config/" +
51 "${SettingsFile.FILE_NAME_CONFIG}.ini"
52 )
53 if (!configFile.exists()) {
54 Log.error(
55 "${DirectoryInitialization.userDirectory}/config/" +
56 "${SettingsFile.FILE_NAME_CONFIG}.ini"
57 )
58 Log.error("yuzu config file could not be found!")
59 }
60
61 if (!DirectoryInitialization.areDirectoriesReady) {
62 DirectoryInitialization.start(activityView as Context)
63 }
64 loadSettingsUI()
65 }
66
67 fun onStop(finishing: Boolean) {
68 if (finishing && shouldSave) {
69 Log.debug("[SettingsActivity] Settings activity stopping. Saving settings to INI...")
70 settings.saveSettings(activityView)
71 }
72 NativeLibrary.reloadSettings()
73 }
74
75 fun onSettingChanged() {
76 shouldSave = true
77 }
78
79 fun onSettingsReset() {
80 shouldSave = false
81 }
82
83 fun saveState(outState: Bundle) {
84 outState.putBoolean(KEY_SHOULD_SAVE, shouldSave)
85 }
86
87 companion object {
88 private const val KEY_SHOULD_SAVE = "should_save"
89 }
90}
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 c186fc388..000000000
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityView.kt
+++ /dev/null
@@ -1,57 +0,0 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.features.settings.ui
5
6import org.yuzu.yuzu_emu.features.settings.model.Settings
7
8/**
9 * Abstraction for the Activity that manages SettingsFragments.
10 */
11interface SettingsActivityView {
12 /**
13 * Show a new SettingsFragment.
14 *
15 * @param menuTag Identifier for the settings group that should be displayed.
16 * @param addToStack Whether or not this fragment should replace a previous one.
17 */
18 fun showSettingsFragment(menuTag: String, addToStack: Boolean, gameId: String)
19
20 /**
21 * Called by a contained Fragment to get access to the Setting HashMap
22 * loaded from disk, so that each Fragment doesn't need to perform its own
23 * read operation.
24 *
25 * @return A HashMap of Settings.
26 */
27 val settings: Settings
28
29 /**
30 * Called when a load operation completes.
31 */
32 fun onSettingsFileLoaded()
33
34 /**
35 * Called when a load operation fails.
36 */
37 fun onSettingsFileNotFound()
38
39 /**
40 * Display a popup text message on screen.
41 *
42 * @param message The contents of the onscreen message.
43 * @param is_long Whether this should be a long Toast or short one.
44 */
45 fun showToastMessage(message: String, is_long: Boolean)
46
47 /**
48 * End the activity.
49 */
50 fun finish()
51
52 /**
53 * Called by a containing Fragment to tell the Activity that a setting was changed;
54 * unless this has been called, the Activity will not save to disk.
55 */
56 fun onSettingChanged()
57}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt
index 9711e2c51..a7a029fc1 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
@@ -4,51 +4,54 @@
4package org.yuzu.yuzu_emu.features.settings.ui 4package org.yuzu.yuzu_emu.features.settings.ui
5 5
6import android.content.Context 6import android.content.Context
7import android.content.DialogInterface
8import android.icu.util.Calendar 7import android.icu.util.Calendar
9import android.icu.util.TimeZone 8import android.icu.util.TimeZone
10import android.text.format.DateFormat 9import android.text.format.DateFormat
11import android.view.LayoutInflater 10import android.view.LayoutInflater
12import android.view.ViewGroup 11import android.view.ViewGroup
13import android.widget.TextView 12import androidx.fragment.app.Fragment
14import androidx.appcompat.app.AlertDialog 13import androidx.lifecycle.Lifecycle
15import androidx.appcompat.app.AppCompatActivity 14import androidx.lifecycle.ViewModelProvider
16import androidx.recyclerview.widget.RecyclerView 15import androidx.lifecycle.lifecycleScope
16import androidx.lifecycle.repeatOnLifecycle
17import androidx.navigation.findNavController
18import androidx.recyclerview.widget.AsyncDifferConfig
19import androidx.recyclerview.widget.DiffUtil
20import androidx.recyclerview.widget.ListAdapter
17import com.google.android.material.datepicker.MaterialDatePicker 21import com.google.android.material.datepicker.MaterialDatePicker
18import com.google.android.material.dialog.MaterialAlertDialogBuilder
19import com.google.android.material.slider.Slider
20import com.google.android.material.timepicker.MaterialTimePicker 22import com.google.android.material.timepicker.MaterialTimePicker
21import com.google.android.material.timepicker.TimeFormat 23import com.google.android.material.timepicker.TimeFormat
24import kotlinx.coroutines.launch
22import org.yuzu.yuzu_emu.R 25import org.yuzu.yuzu_emu.R
23import org.yuzu.yuzu_emu.databinding.DialogSliderBinding 26import org.yuzu.yuzu_emu.SettingsNavigationDirections
24import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding 27import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
25import org.yuzu.yuzu_emu.databinding.ListItemSettingSwitchBinding 28import org.yuzu.yuzu_emu.databinding.ListItemSettingSwitchBinding
26import org.yuzu.yuzu_emu.databinding.ListItemSettingsHeaderBinding 29import org.yuzu.yuzu_emu.databinding.ListItemSettingsHeaderBinding
27import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting
28import org.yuzu.yuzu_emu.features.settings.model.AbstractFloatSetting
29import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
30import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
31import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting
32import org.yuzu.yuzu_emu.features.settings.model.FloatSetting
33import org.yuzu.yuzu_emu.features.settings.model.view.* 30import org.yuzu.yuzu_emu.features.settings.model.view.*
34import org.yuzu.yuzu_emu.features.settings.ui.viewholder.* 31import org.yuzu.yuzu_emu.features.settings.ui.viewholder.*
32import org.yuzu.yuzu_emu.fragments.SettingsDialogFragment
33import org.yuzu.yuzu_emu.model.SettingsViewModel
35 34
36class SettingsAdapter( 35class SettingsAdapter(
37 private val fragmentView: SettingsFragmentView, 36 private val fragment: Fragment,
38 private val context: Context 37 private val context: Context
39) : RecyclerView.Adapter<SettingViewHolder?>(), DialogInterface.OnClickListener { 38) : ListAdapter<SettingsItem, SettingViewHolder>(
40 private var settings: ArrayList<SettingsItem>? = null 39 AsyncDifferConfig.Builder(DiffCallback()).build()
41 private var clickedItem: SettingsItem? = null 40) {
42 private var clickedPosition: Int 41 private val settingsViewModel: SettingsViewModel
43 private var dialog: AlertDialog? = null 42 get() = ViewModelProvider(fragment.requireActivity())[SettingsViewModel::class.java]
44 private var sliderProgress = 0
45 private var textSliderValue: TextView? = null
46
47 private var defaultCancelListener =
48 DialogInterface.OnClickListener { _: DialogInterface?, _: Int -> closeDialog() }
49 43
50 init { 44 init {
51 clickedPosition = -1 45 fragment.viewLifecycleOwner.lifecycleScope.launch {
46 fragment.repeatOnLifecycle(Lifecycle.State.STARTED) {
47 settingsViewModel.adapterItemChanged.collect {
48 if (it != -1) {
49 notifyItemChanged(it)
50 settingsViewModel.setAdapterItemChanged(-1)
51 }
52 }
53 }
54 }
52 } 55 }
53 56
54 override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SettingViewHolder { 57 override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SettingViewHolder {
@@ -90,67 +93,41 @@ class SettingsAdapter(
90 } 93 }
91 94
92 override fun onBindViewHolder(holder: SettingViewHolder, position: Int) { 95 override fun onBindViewHolder(holder: SettingViewHolder, position: Int) {
93 holder.bind(getItem(position)) 96 holder.bind(currentList[position])
94 } 97 }
95 98
96 private fun getItem(position: Int): SettingsItem { 99 override fun getItemCount(): Int = currentList.size
97 return settings!![position]
98 }
99
100 override fun getItemCount(): Int {
101 return if (settings != null) {
102 settings!!.size
103 } else {
104 0
105 }
106 }
107 100
108 override fun getItemViewType(position: Int): Int { 101 override fun getItemViewType(position: Int): Int {
109 return getItem(position).type 102 return currentList[position].type
110 }
111
112 fun setSettingsList(settings: ArrayList<SettingsItem>?) {
113 this.settings = settings
114 notifyDataSetChanged()
115 }
116
117 fun onBooleanClick(item: SwitchSetting, position: Int, checked: Boolean) {
118 val setting = item.setChecked(checked)
119 fragmentView.putSetting(setting)
120 fragmentView.onSettingChanged()
121 } 103 }
122 104
123 private fun onSingleChoiceClick(item: SingleChoiceSetting) { 105 fun onBooleanClick(item: SwitchSetting, checked: Boolean) {
124 clickedItem = item 106 item.checked = checked
125 val value = getSelectionForSingleChoiceValue(item) 107 settingsViewModel.setShouldReloadSettingsList(true)
126 dialog = MaterialAlertDialogBuilder(context) 108 settingsViewModel.shouldSave = true
127 .setTitle(item.nameId)
128 .setSingleChoiceItems(item.choicesId, value, this)
129 .show()
130 } 109 }
131 110
132 fun onSingleChoiceClick(item: SingleChoiceSetting, position: Int) { 111 fun onSingleChoiceClick(item: SingleChoiceSetting, position: Int) {
133 clickedPosition = position 112 SettingsDialogFragment.newInstance(
134 onSingleChoiceClick(item) 113 settingsViewModel,
135 } 114 item,
136 115 SettingsItem.TYPE_SINGLE_CHOICE,
137 private fun onStringSingleChoiceClick(item: StringSingleChoiceSetting) { 116 position
138 clickedItem = item 117 ).show(fragment.childFragmentManager, SettingsDialogFragment.TAG)
139 dialog = MaterialAlertDialogBuilder(context)
140 .setTitle(item.nameId)
141 .setSingleChoiceItems(item.choices, item.selectValueIndex, this)
142 .show()
143 } 118 }
144 119
145 fun onStringSingleChoiceClick(item: StringSingleChoiceSetting, position: Int) { 120 fun onStringSingleChoiceClick(item: StringSingleChoiceSetting, position: Int) {
146 clickedPosition = position 121 SettingsDialogFragment.newInstance(
147 onStringSingleChoiceClick(item) 122 settingsViewModel,
123 item,
124 SettingsItem.TYPE_STRING_SINGLE_CHOICE,
125 position
126 ).show(fragment.childFragmentManager, SettingsDialogFragment.TAG)
148 } 127 }
149 128
150 fun onDateTimeClick(item: DateTimeSetting, position: Int) { 129 fun onDateTimeClick(item: DateTimeSetting, position: Int) {
151 clickedItem = item 130 val storedTime = item.value * 1000
152 clickedPosition = position
153 val storedTime = java.lang.Long.decode(item.value) * 1000
154 131
155 // Helper to extract hour and minute from epoch time 132 // Helper to extract hour and minute from epoch time
156 val calendar: Calendar = Calendar.getInstance() 133 val calendar: Calendar = Calendar.getInstance()
@@ -158,7 +135,7 @@ class SettingsAdapter(
158 calendar.timeZone = TimeZone.getTimeZone("UTC") 135 calendar.timeZone = TimeZone.getTimeZone("UTC")
159 136
160 var timeFormat: Int = TimeFormat.CLOCK_12H 137 var timeFormat: Int = TimeFormat.CLOCK_12H
161 if (DateFormat.is24HourFormat(fragmentView.activityView as AppCompatActivity)) { 138 if (DateFormat.is24HourFormat(context)) {
162 timeFormat = TimeFormat.CLOCK_24H 139 timeFormat = TimeFormat.CLOCK_24H
163 } 140 }
164 141
@@ -175,7 +152,7 @@ class SettingsAdapter(
175 152
176 datePicker.addOnPositiveButtonClickListener { 153 datePicker.addOnPositiveButtonClickListener {
177 timePicker.show( 154 timePicker.show(
178 (fragmentView.activityView as AppCompatActivity).supportFragmentManager, 155 fragment.childFragmentManager,
179 "TimePicker" 156 "TimePicker"
180 ) 157 )
181 } 158 }
@@ -183,160 +160,50 @@ class SettingsAdapter(
183 var epochTime: Long = datePicker.selection!! / 1000 160 var epochTime: Long = datePicker.selection!! / 1000
184 epochTime += timePicker.hour.toLong() * 60 * 60 161 epochTime += timePicker.hour.toLong() * 60 * 60
185 epochTime += timePicker.minute.toLong() * 60 162 epochTime += timePicker.minute.toLong() * 60
186 val rtcString = epochTime.toString() 163 if (item.value != epochTime) {
187 if (item.value != rtcString) { 164 settingsViewModel.shouldSave = true
188 fragmentView.onSettingChanged() 165 notifyItemChanged(position)
166 item.value = epochTime
189 } 167 }
190 notifyItemChanged(clickedPosition)
191 val setting = item.setSelectedValue(rtcString)
192 fragmentView.putSetting(setting)
193 clickedItem = null
194 } 168 }
195 datePicker.show( 169 datePicker.show(
196 (fragmentView.activityView as AppCompatActivity).supportFragmentManager, 170 fragment.childFragmentManager,
197 "DatePicker" 171 "DatePicker"
198 ) 172 )
199 } 173 }
200 174
201 fun onSliderClick(item: SliderSetting, position: Int) { 175 fun onSliderClick(item: SliderSetting, position: Int) {
202 clickedItem = item 176 SettingsDialogFragment.newInstance(
203 clickedPosition = position 177 settingsViewModel,
204 sliderProgress = item.selectedValue 178 item,
205 179 SettingsItem.TYPE_SLIDER,
206 val inflater = LayoutInflater.from(context) 180 position
207 val sliderBinding = DialogSliderBinding.inflate(inflater) 181 ).show(fragment.childFragmentManager, SettingsDialogFragment.TAG)
208
209 textSliderValue = sliderBinding.textValue
210 textSliderValue!!.text = String.format(
211 context.getString(R.string.value_with_units),
212 sliderProgress.toString(),
213 item.units
214 )
215
216 sliderBinding.slider.apply {
217 valueFrom = item.min.toFloat()
218 valueTo = item.max.toFloat()
219 value = sliderProgress.toFloat()
220 addOnChangeListener { _: Slider, value: Float, _: Boolean ->
221 sliderProgress = value.toInt()
222 textSliderValue!!.text = String.format(
223 context.getString(R.string.value_with_units),
224 sliderProgress.toString(),
225 item.units
226 )
227 }
228 }
229
230 dialog = MaterialAlertDialogBuilder(context)
231 .setTitle(item.nameId)
232 .setView(sliderBinding.root)
233 .setPositiveButton(android.R.string.ok, this)
234 .setNegativeButton(android.R.string.cancel, defaultCancelListener)
235 .show()
236 } 182 }
237 183
238 fun onSubmenuClick(item: SubmenuSetting) { 184 fun onSubmenuClick(item: SubmenuSetting) {
239 fragmentView.loadSubMenu(item.menuKey) 185 val action = SettingsNavigationDirections.actionGlobalSettingsFragment(item.menuKey, null)
186 fragment.view?.findNavController()?.navigate(action)
240 } 187 }
241 188
242 override fun onClick(dialog: DialogInterface, which: Int) { 189 fun onLongClick(item: SettingsItem, position: Int): Boolean {
243 when (clickedItem) { 190 SettingsDialogFragment.newInstance(
244 is SingleChoiceSetting -> { 191 settingsViewModel,
245 val scSetting = clickedItem as SingleChoiceSetting 192 item,
246 val value = getValueForSingleChoiceSelection(scSetting, which) 193 SettingsDialogFragment.TYPE_RESET_SETTING,
247 if (scSetting.selectedValue != value) { 194 position
248 fragmentView.onSettingChanged() 195 ).show(fragment.childFragmentManager, SettingsDialogFragment.TAG)
249 }
250
251 // Get the backing Setting, which may be null (if for example it was missing from the file)
252 val setting = scSetting.setSelectedValue(value)
253 fragmentView.putSetting(setting)
254 closeDialog()
255 }
256
257 is StringSingleChoiceSetting -> {
258 val scSetting = clickedItem as StringSingleChoiceSetting
259 val value = scSetting.getValueAt(which)
260 if (scSetting.selectedValue != value) fragmentView.onSettingChanged()
261 val setting = scSetting.setSelectedValue(value!!)
262 fragmentView.putSetting(setting)
263 closeDialog()
264 }
265
266 is SliderSetting -> {
267 val sliderSetting = clickedItem as SliderSetting
268 if (sliderSetting.selectedValue != sliderProgress) {
269 fragmentView.onSettingChanged()
270 }
271 if (sliderSetting.setting is FloatSetting) {
272 val value = sliderProgress.toFloat()
273 val setting = sliderSetting.setSelectedValue(value)
274 fragmentView.putSetting(setting)
275 } else {
276 val setting = sliderSetting.setSelectedValue(sliderProgress)
277 fragmentView.putSetting(setting)
278 }
279 closeDialog()
280 }
281 }
282 clickedItem = null
283 sliderProgress = -1
284 }
285
286 fun onLongClick(setting: AbstractSetting, position: Int): Boolean {
287 MaterialAlertDialogBuilder(context)
288 .setMessage(R.string.reset_setting_confirmation)
289 .setPositiveButton(android.R.string.ok) { dialog: DialogInterface, which: Int ->
290 when (setting) {
291 is AbstractBooleanSetting -> setting.boolean = setting.defaultValue as Boolean
292 is AbstractFloatSetting -> setting.float = setting.defaultValue as Float
293 is AbstractIntSetting -> setting.int = setting.defaultValue as Int
294 is AbstractStringSetting -> setting.string = setting.defaultValue as String
295 }
296 notifyItemChanged(position)
297 fragmentView.onSettingChanged()
298 }
299 .setNegativeButton(android.R.string.cancel, null)
300 .show()
301 196
302 return true 197 return true
303 } 198 }
304 199
305 fun closeDialog() { 200 private class DiffCallback : DiffUtil.ItemCallback<SettingsItem>() {
306 if (dialog != null) { 201 override fun areItemsTheSame(oldItem: SettingsItem, newItem: SettingsItem): Boolean {
307 if (clickedPosition != -1) { 202 return oldItem.setting.key == newItem.setting.key
308 notifyItemChanged(clickedPosition)
309 clickedPosition = -1
310 }
311 dialog!!.dismiss()
312 dialog = null
313 } 203 }
314 }
315 204
316 private fun getValueForSingleChoiceSelection(item: SingleChoiceSetting, which: Int): Int { 205 override fun areContentsTheSame(oldItem: SettingsItem, newItem: SettingsItem): Boolean {
317 val valuesId = item.valuesId 206 return oldItem.setting.key == newItem.setting.key
318 return if (valuesId > 0) {
319 val valuesArray = context.resources.getIntArray(valuesId)
320 valuesArray[which]
321 } else {
322 which
323 }
324 }
325
326 private fun getSelectionForSingleChoiceValue(item: SingleChoiceSetting): Int {
327 val value = item.selectedValue
328 val valuesId = item.valuesId
329 if (valuesId > 0) {
330 val valuesArray = context.resources.getIntArray(valuesId)
331 for (index in valuesArray.indices) {
332 val current = valuesArray[index]
333 if (current == value) {
334 return index
335 }
336 }
337 } else {
338 return value
339 } 207 }
340 return -1
341 } 208 }
342} 209}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt
index 70a74c4dd..bc319714c 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,40 +3,43 @@
3 3
4package org.yuzu.yuzu_emu.features.settings.ui 4package org.yuzu.yuzu_emu.features.settings.ui
5 5
6import android.content.Context
7import android.os.Bundle 6import android.os.Bundle
8import android.view.LayoutInflater 7import android.view.LayoutInflater
9import android.view.View 8import android.view.View
10import android.view.ViewGroup 9import android.view.ViewGroup
10import android.view.ViewGroup.MarginLayoutParams
11import androidx.core.view.ViewCompat 11import androidx.core.view.ViewCompat
12import androidx.core.view.WindowInsetsCompat 12import androidx.core.view.WindowInsetsCompat
13import androidx.core.view.updatePadding 13import androidx.core.view.updatePadding
14import androidx.fragment.app.Fragment 14import androidx.fragment.app.Fragment
15import androidx.fragment.app.activityViewModels
16import androidx.navigation.findNavController
17import androidx.navigation.fragment.navArgs
15import androidx.recyclerview.widget.LinearLayoutManager 18import androidx.recyclerview.widget.LinearLayoutManager
16import com.google.android.material.divider.MaterialDividerItemDecoration 19import com.google.android.material.divider.MaterialDividerItemDecoration
20import com.google.android.material.transition.MaterialSharedAxis
21import org.yuzu.yuzu_emu.R
17import org.yuzu.yuzu_emu.databinding.FragmentSettingsBinding 22import org.yuzu.yuzu_emu.databinding.FragmentSettingsBinding
18import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting 23import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
19import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem 24import org.yuzu.yuzu_emu.model.SettingsViewModel
20 25
21class SettingsFragment : Fragment(), SettingsFragmentView { 26class SettingsFragment : Fragment() {
22 override var activityView: SettingsActivityView? = null 27 private lateinit var presenter: SettingsFragmentPresenter
23
24 private val fragmentPresenter = SettingsFragmentPresenter(this)
25 private var settingsAdapter: SettingsAdapter? = null 28 private var settingsAdapter: SettingsAdapter? = null
26 29
27 private var _binding: FragmentSettingsBinding? = null 30 private var _binding: FragmentSettingsBinding? = null
28 private val binding get() = _binding!! 31 private val binding get() = _binding!!
29 32
30 override fun onAttach(context: Context) { 33 private val args by navArgs<SettingsFragmentArgs>()
31 super.onAttach(context) 34
32 activityView = requireActivity() as SettingsActivityView 35 private val settingsViewModel: SettingsViewModel by activityViewModels()
33 }
34 36
35 override fun onCreate(savedInstanceState: Bundle?) { 37 override fun onCreate(savedInstanceState: Bundle?) {
36 super.onCreate(savedInstanceState) 38 super.onCreate(savedInstanceState)
37 val menuTag = requireArguments().getString(ARGUMENT_MENU_TAG) 39 enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
38 val gameId = requireArguments().getString(ARGUMENT_GAME_ID) 40 returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
39 fragmentPresenter.onCreate(menuTag!!, gameId!!) 41 reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
42 exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
40 } 43 }
41 44
42 override fun onCreateView( 45 override fun onCreateView(
@@ -49,7 +52,14 @@ class SettingsFragment : Fragment(), SettingsFragmentView {
49 } 52 }
50 53
51 override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 54 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
52 settingsAdapter = SettingsAdapter(this, requireActivity()) 55 settingsAdapter = SettingsAdapter(this, requireContext())
56 presenter = SettingsFragmentPresenter(
57 settingsViewModel,
58 settingsAdapter!!,
59 args.menuTag,
60 args.game?.gameId ?: ""
61 )
62
53 val dividerDecoration = MaterialDividerItemDecoration( 63 val dividerDecoration = MaterialDividerItemDecoration(
54 requireContext(), 64 requireContext(),
55 LinearLayoutManager.VERTICAL 65 LinearLayoutManager.VERTICAL
@@ -57,71 +67,86 @@ class SettingsFragment : Fragment(), SettingsFragmentView {
57 dividerDecoration.isLastItemDecorated = false 67 dividerDecoration.isLastItemDecorated = false
58 binding.listSettings.apply { 68 binding.listSettings.apply {
59 adapter = settingsAdapter 69 adapter = settingsAdapter
60 layoutManager = LinearLayoutManager(activity) 70 layoutManager = LinearLayoutManager(requireContext())
61 addItemDecoration(dividerDecoration) 71 addItemDecoration(dividerDecoration)
62 } 72 }
63 fragmentPresenter.onViewCreated()
64 73
65 setInsets() 74 binding.toolbarSettings.setNavigationOnClickListener {
66 } 75 settingsViewModel.setShouldNavigateBack(true)
76 }
67 77
68 override fun onDetach() { 78 settingsViewModel.toolbarTitle.observe(viewLifecycleOwner) {
69 super.onDetach() 79 if (it.isNotEmpty()) binding.toolbarSettingsLayout.title = it
70 activityView = null
71 if (settingsAdapter != null) {
72 settingsAdapter!!.closeDialog()
73 } 80 }
74 }
75 81
76 override fun showSettingsList(settingsList: ArrayList<SettingsItem>) { 82 settingsViewModel.shouldReloadSettingsList.observe(viewLifecycleOwner) {
77 settingsAdapter!!.setSettingsList(settingsList) 83 if (it) {
78 } 84 settingsViewModel.setShouldReloadSettingsList(false)
85 presenter.loadSettingsList()
86 }
87 }
79 88
80 override fun loadSettingsList() { 89 settingsViewModel.isUsingSearch.observe(viewLifecycleOwner) {
81 fragmentPresenter.loadSettingsList() 90 if (it) {
82 } 91 reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true)
92 exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false)
93 } else {
94 reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
95 exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
96 }
97 }
83 98
84 override fun loadSubMenu(menuKey: String) { 99 if (args.menuTag == SettingsFile.FILE_NAME_CONFIG) {
85 activityView!!.showSettingsFragment( 100 binding.toolbarSettings.inflateMenu(R.menu.menu_settings)
86 menuKey, 101 binding.toolbarSettings.setOnMenuItemClickListener {
87 true, 102 when (it.itemId) {
88 requireArguments().getString(ARGUMENT_GAME_ID)!! 103 R.id.action_search -> {
89 ) 104 reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true)
90 } 105 exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false)
106 view.findNavController()
107 .navigate(R.id.action_settingsFragment_to_settingsSearchFragment)
108 true
109 }
110
111 else -> false
112 }
113 }
114 }
91 115
92 override fun showToastMessage(message: String?, is_long: Boolean) { 116 presenter.onViewCreated()
93 activityView!!.showToastMessage(message!!, is_long)
94 }
95 117
96 override fun putSetting(setting: AbstractSetting) { 118 setInsets()
97 fragmentPresenter.putSetting(setting)
98 } 119 }
99 120
100 override fun onSettingChanged() { 121 override fun onResume() {
101 activityView!!.onSettingChanged() 122 super.onResume()
123 settingsViewModel.setIsUsingSearch(false)
102 } 124 }
103 125
104 private fun setInsets() { 126 private fun setInsets() {
105 ViewCompat.setOnApplyWindowInsetsListener( 127 ViewCompat.setOnApplyWindowInsetsListener(
106 binding.listSettings 128 binding.root
107 ) { view: View, windowInsets: WindowInsetsCompat -> 129 ) { _: View, windowInsets: WindowInsetsCompat ->
108 val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) 130 val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
109 view.updatePadding(bottom = insets.bottom) 131 val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
132
133 val leftInsets = barInsets.left + cutoutInsets.left
134 val rightInsets = barInsets.right + cutoutInsets.right
135
136 val sideMargin = resources.getDimensionPixelSize(R.dimen.spacing_medlarge)
137 val mlpSettingsList = binding.listSettings.layoutParams as MarginLayoutParams
138 mlpSettingsList.leftMargin = sideMargin + leftInsets
139 mlpSettingsList.rightMargin = sideMargin + rightInsets
140 binding.listSettings.layoutParams = mlpSettingsList
141 binding.listSettings.updatePadding(
142 bottom = barInsets.bottom
143 )
144
145 val mlpAppBar = binding.appbarSettings.layoutParams as MarginLayoutParams
146 mlpAppBar.leftMargin = leftInsets
147 mlpAppBar.rightMargin = rightInsets
148 binding.appbarSettings.layoutParams = mlpAppBar
110 windowInsets 149 windowInsets
111 } 150 }
112 } 151 }
113
114 companion object {
115 private const val ARGUMENT_MENU_TAG = "menu_tag"
116 private const val ARGUMENT_GAME_ID = "game_id"
117
118 fun newInstance(menuTag: String?, gameId: String?): Fragment {
119 val fragment = SettingsFragment()
120 val arguments = Bundle()
121 arguments.putString(ARGUMENT_MENU_TAG, menuTag)
122 arguments.putString(ARGUMENT_GAME_ID, gameId)
123 fragment.arguments = arguments
124 return fragment
125 }
126 }
127} 152}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt
index 59c1d9d54..22a529b1b 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt
@@ -3,63 +3,66 @@
3 3
4package org.yuzu.yuzu_emu.features.settings.ui 4package org.yuzu.yuzu_emu.features.settings.ui
5 5
6import android.content.Context
6import android.content.SharedPreferences 7import android.content.SharedPreferences
7import android.os.Build 8import android.os.Build
8import android.text.TextUtils 9import android.text.TextUtils
10import android.widget.Toast
9import androidx.preference.PreferenceManager 11import androidx.preference.PreferenceManager
10import org.yuzu.yuzu_emu.R 12import org.yuzu.yuzu_emu.R
11import org.yuzu.yuzu_emu.YuzuApplication 13import org.yuzu.yuzu_emu.YuzuApplication
12import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting 14import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting
13import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting 15import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
14import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
15import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting 16import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
17import org.yuzu.yuzu_emu.features.settings.model.ByteSetting
16import org.yuzu.yuzu_emu.features.settings.model.IntSetting 18import org.yuzu.yuzu_emu.features.settings.model.IntSetting
19import org.yuzu.yuzu_emu.features.settings.model.LongSetting
17import org.yuzu.yuzu_emu.features.settings.model.Settings 20import org.yuzu.yuzu_emu.features.settings.model.Settings
18import org.yuzu.yuzu_emu.features.settings.model.StringSetting 21import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
19import org.yuzu.yuzu_emu.features.settings.model.view.* 22import org.yuzu.yuzu_emu.features.settings.model.view.*
20import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile 23import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
21import org.yuzu.yuzu_emu.fragments.ResetSettingsDialogFragment 24import org.yuzu.yuzu_emu.model.SettingsViewModel
22import org.yuzu.yuzu_emu.utils.ThemeHelper 25import org.yuzu.yuzu_emu.utils.NativeConfig
23 26
24class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) { 27class SettingsFragmentPresenter(
25 private var menuTag: String? = null 28 private val settingsViewModel: SettingsViewModel,
26 private lateinit var gameId: String 29 private val adapter: SettingsAdapter,
27 private var settingsList: ArrayList<SettingsItem>? = null 30 private var menuTag: String,
31 private var gameId: String
32) {
33 private var settingsList = ArrayList<SettingsItem>()
28 34
29 private val settingsActivity get() = fragmentView.activityView as SettingsActivity 35 private val preferences: SharedPreferences
30 private val settings get() = fragmentView.activityView!!.settings 36 get() = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
31 37
32 private lateinit var preferences: SharedPreferences 38 private val context: Context get() = YuzuApplication.appContext
33 39
34 fun onCreate(menuTag: String, gameId: String) { 40 // Extension for populating settings list based on paired settings
35 this.gameId = gameId 41 fun ArrayList<SettingsItem>.add(key: String) {
36 this.menuTag = menuTag 42 val item = SettingsItem.settingsItems[key]!!
43 val pairedSettingKey = item.setting.pairedSettingKey
44 if (pairedSettingKey.isNotEmpty()) {
45 val pairedSettingValue = NativeConfig.getBoolean(pairedSettingKey, false)
46 if (!pairedSettingValue) return
47 }
48 add(item)
37 } 49 }
38 50
39 fun onViewCreated() { 51 fun onViewCreated() {
40 preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
41 loadSettingsList() 52 loadSettingsList()
42 } 53 }
43 54
44 fun putSetting(setting: AbstractSetting) {
45 if (setting.section == null || setting.key == null) {
46 return
47 }
48
49 val section = settings.getSection(setting.section!!)!!
50 if (section.getSetting(setting.key!!) == null) {
51 section.putSetting(setting)
52 }
53 }
54
55 fun loadSettingsList() { 55 fun loadSettingsList() {
56 if (!TextUtils.isEmpty(gameId)) { 56 if (!TextUtils.isEmpty(gameId)) {
57 settingsActivity.setToolbarTitle("Game Settings: $gameId") 57 settingsViewModel.setToolbarTitle(
58 context.getString(
59 R.string.advanced_settings_game,
60 gameId
61 )
62 )
58 } 63 }
64
59 val sl = ArrayList<SettingsItem>() 65 val sl = ArrayList<SettingsItem>()
60 if (menuTag == null) {
61 return
62 }
63 when (menuTag) { 66 when (menuTag) {
64 SettingsFile.FILE_NAME_CONFIG -> addConfigSettings(sl) 67 SettingsFile.FILE_NAME_CONFIG -> addConfigSettings(sl)
65 Settings.SECTION_GENERAL -> addGeneralSettings(sl) 68 Settings.SECTION_GENERAL -> addGeneralSettings(sl)
@@ -69,335 +72,104 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
69 Settings.SECTION_THEME -> addThemeSettings(sl) 72 Settings.SECTION_THEME -> addThemeSettings(sl)
70 Settings.SECTION_DEBUG -> addDebugSettings(sl) 73 Settings.SECTION_DEBUG -> addDebugSettings(sl)
71 else -> { 74 else -> {
72 fragmentView.showToastMessage("Unimplemented menu", false) 75 val context = YuzuApplication.appContext
76 Toast.makeText(
77 context,
78 context.getString(R.string.unimplemented_menu),
79 Toast.LENGTH_SHORT
80 ).show()
73 return 81 return
74 } 82 }
75 } 83 }
76 settingsList = sl 84 settingsList = sl
77 fragmentView.showSettingsList(settingsList!!) 85 adapter.submitList(settingsList)
78 } 86 }
79 87
80 private fun addConfigSettings(sl: ArrayList<SettingsItem>) { 88 private fun addConfigSettings(sl: ArrayList<SettingsItem>) {
81 settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.advanced_settings)) 89 settingsViewModel.setToolbarTitle(context.getString(R.string.advanced_settings))
82 sl.apply { 90 sl.apply {
83 add( 91 add(SubmenuSetting(R.string.preferences_general, 0, Settings.SECTION_GENERAL))
84 SubmenuSetting( 92 add(SubmenuSetting(R.string.preferences_system, 0, Settings.SECTION_SYSTEM))
85 R.string.preferences_general, 93 add(SubmenuSetting(R.string.preferences_graphics, 0, Settings.SECTION_RENDERER))
86 0, 94 add(SubmenuSetting(R.string.preferences_audio, 0, Settings.SECTION_AUDIO))
87 Settings.SECTION_GENERAL 95 add(SubmenuSetting(R.string.preferences_debug, 0, Settings.SECTION_DEBUG))
88 ) 96 add(
89 ) 97 RunnableSetting(R.string.reset_to_default, 0, false) {
90 add( 98 settingsViewModel.setShouldShowResetSettingsDialog(true)
91 SubmenuSetting(
92 R.string.preferences_system,
93 0,
94 Settings.SECTION_SYSTEM
95 )
96 )
97 add(
98 SubmenuSetting(
99 R.string.preferences_graphics,
100 0,
101 Settings.SECTION_RENDERER
102 )
103 )
104 add(
105 SubmenuSetting(
106 R.string.preferences_audio,
107 0,
108 Settings.SECTION_AUDIO
109 )
110 )
111 add(
112 SubmenuSetting(
113 R.string.preferences_debug,
114 0,
115 Settings.SECTION_DEBUG
116 )
117 )
118 add(
119 RunnableSetting(
120 R.string.reset_to_default,
121 0,
122 false
123 ) {
124 ResetSettingsDialogFragment().show(
125 settingsActivity.supportFragmentManager,
126 ResetSettingsDialogFragment.TAG
127 )
128 } 99 }
129 ) 100 )
130 } 101 }
131 } 102 }
132 103
133 private fun addGeneralSettings(sl: ArrayList<SettingsItem>) { 104 private fun addGeneralSettings(sl: ArrayList<SettingsItem>) {
134 settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_general)) 105 settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_general))
135 sl.apply { 106 sl.apply {
136 add( 107 add(BooleanSetting.RENDERER_USE_SPEED_LIMIT.key)
137 SwitchSetting( 108 add(ShortSetting.RENDERER_SPEED_LIMIT.key)
138 IntSetting.RENDERER_USE_SPEED_LIMIT, 109 add(IntSetting.CPU_ACCURACY.key)
139 R.string.frame_limit_enable, 110 add(BooleanSetting.PICTURE_IN_PICTURE.key)
140 R.string.frame_limit_enable_description,
141 IntSetting.RENDERER_USE_SPEED_LIMIT.key,
142 IntSetting.RENDERER_USE_SPEED_LIMIT.defaultValue
143 )
144 )
145 add(
146 SliderSetting(
147 IntSetting.RENDERER_SPEED_LIMIT,
148 R.string.frame_limit_slider,
149 R.string.frame_limit_slider_description,
150 1,
151 200,
152 "%",
153 IntSetting.RENDERER_SPEED_LIMIT.key,
154 IntSetting.RENDERER_SPEED_LIMIT.defaultValue
155 )
156 )
157 add(
158 SingleChoiceSetting(
159 IntSetting.CPU_ACCURACY,
160 R.string.cpu_accuracy,
161 0,
162 R.array.cpuAccuracyNames,
163 R.array.cpuAccuracyValues,
164 IntSetting.CPU_ACCURACY.key,
165 IntSetting.CPU_ACCURACY.defaultValue
166 )
167 )
168 add(
169 SwitchSetting(
170 BooleanSetting.PICTURE_IN_PICTURE,
171 R.string.picture_in_picture,
172 R.string.picture_in_picture_description,
173 BooleanSetting.PICTURE_IN_PICTURE.key,
174 BooleanSetting.PICTURE_IN_PICTURE.defaultValue
175 )
176 )
177 } 111 }
178 } 112 }
179 113
180 private fun addSystemSettings(sl: ArrayList<SettingsItem>) { 114 private fun addSystemSettings(sl: ArrayList<SettingsItem>) {
181 settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_system)) 115 settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_system))
182 sl.apply { 116 sl.apply {
183 add( 117 add(BooleanSetting.USE_DOCKED_MODE.key)
184 SwitchSetting( 118 add(IntSetting.REGION_INDEX.key)
185 IntSetting.USE_DOCKED_MODE, 119 add(IntSetting.LANGUAGE_INDEX.key)
186 R.string.use_docked_mode, 120 add(BooleanSetting.USE_CUSTOM_RTC.key)
187 R.string.use_docked_mode_description, 121 add(LongSetting.CUSTOM_RTC.key)
188 IntSetting.USE_DOCKED_MODE.key,
189 IntSetting.USE_DOCKED_MODE.defaultValue
190 )
191 )
192 add(
193 SingleChoiceSetting(
194 IntSetting.REGION_INDEX,
195 R.string.emulated_region,
196 0,
197 R.array.regionNames,
198 R.array.regionValues,
199 IntSetting.REGION_INDEX.key,
200 IntSetting.REGION_INDEX.defaultValue
201 )
202 )
203 add(
204 SingleChoiceSetting(
205 IntSetting.LANGUAGE_INDEX,
206 R.string.emulated_language,
207 0,
208 R.array.languageNames,
209 R.array.languageValues,
210 IntSetting.LANGUAGE_INDEX.key,
211 IntSetting.LANGUAGE_INDEX.defaultValue
212 )
213 )
214 add(
215 SwitchSetting(
216 BooleanSetting.USE_CUSTOM_RTC,
217 R.string.use_custom_rtc,
218 R.string.use_custom_rtc_description,
219 BooleanSetting.USE_CUSTOM_RTC.key,
220 BooleanSetting.USE_CUSTOM_RTC.defaultValue
221 )
222 )
223 add(
224 DateTimeSetting(
225 StringSetting.CUSTOM_RTC,
226 R.string.set_custom_rtc,
227 0,
228 StringSetting.CUSTOM_RTC.key,
229 StringSetting.CUSTOM_RTC.defaultValue
230 )
231 )
232 } 122 }
233 } 123 }
234 124
235 private fun addGraphicsSettings(sl: ArrayList<SettingsItem>) { 125 private fun addGraphicsSettings(sl: ArrayList<SettingsItem>) {
236 settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_graphics)) 126 settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_graphics))
237 sl.apply { 127 sl.apply {
238 add( 128 add(IntSetting.RENDERER_ACCURACY.key)
239 SingleChoiceSetting( 129 add(IntSetting.RENDERER_RESOLUTION.key)
240 IntSetting.RENDERER_ACCURACY, 130 add(IntSetting.RENDERER_VSYNC.key)
241 R.string.renderer_accuracy, 131 add(IntSetting.RENDERER_SCALING_FILTER.key)
242 0, 132 add(IntSetting.RENDERER_ANTI_ALIASING.key)
243 R.array.rendererAccuracyNames, 133 add(IntSetting.RENDERER_SCREEN_LAYOUT.key)
244 R.array.rendererAccuracyValues, 134 add(IntSetting.RENDERER_ASPECT_RATIO.key)
245 IntSetting.RENDERER_ACCURACY.key, 135 add(BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE.key)
246 IntSetting.RENDERER_ACCURACY.defaultValue 136 add(BooleanSetting.RENDERER_FORCE_MAX_CLOCK.key)
247 ) 137 add(BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS.key)
248 ) 138 add(BooleanSetting.RENDERER_REACTIVE_FLUSHING.key)
249 add(
250 SingleChoiceSetting(
251 IntSetting.RENDERER_RESOLUTION,
252 R.string.renderer_resolution,
253 0,
254 R.array.rendererResolutionNames,
255 R.array.rendererResolutionValues,
256 IntSetting.RENDERER_RESOLUTION.key,
257 IntSetting.RENDERER_RESOLUTION.defaultValue
258 )
259 )
260 add(
261 SingleChoiceSetting(
262 IntSetting.RENDERER_VSYNC,
263 R.string.renderer_vsync,
264 0,
265 R.array.rendererVSyncNames,
266 R.array.rendererVSyncValues,
267 IntSetting.RENDERER_VSYNC.key,
268 IntSetting.RENDERER_VSYNC.defaultValue
269 )
270 )
271 add(
272 SingleChoiceSetting(
273 IntSetting.RENDERER_SCALING_FILTER,
274 R.string.renderer_scaling_filter,
275 0,
276 R.array.rendererScalingFilterNames,
277 R.array.rendererScalingFilterValues,
278 IntSetting.RENDERER_SCALING_FILTER.key,
279 IntSetting.RENDERER_SCALING_FILTER.defaultValue
280 )
281 )
282 add(
283 SingleChoiceSetting(
284 IntSetting.RENDERER_ANTI_ALIASING,
285 R.string.renderer_anti_aliasing,
286 0,
287 R.array.rendererAntiAliasingNames,
288 R.array.rendererAntiAliasingValues,
289 IntSetting.RENDERER_ANTI_ALIASING.key,
290 IntSetting.RENDERER_ANTI_ALIASING.defaultValue
291 )
292 )
293 add(
294 SingleChoiceSetting(
295 IntSetting.RENDERER_SCREEN_LAYOUT,
296 R.string.renderer_screen_layout,
297 0,
298 R.array.rendererScreenLayoutNames,
299 R.array.rendererScreenLayoutValues,
300 IntSetting.RENDERER_SCREEN_LAYOUT.key,
301 IntSetting.RENDERER_SCREEN_LAYOUT.defaultValue
302 )
303 )
304 add(
305 SingleChoiceSetting(
306 IntSetting.RENDERER_ASPECT_RATIO,
307 R.string.renderer_aspect_ratio,
308 0,
309 R.array.rendererAspectRatioNames,
310 R.array.rendererAspectRatioValues,
311 IntSetting.RENDERER_ASPECT_RATIO.key,
312 IntSetting.RENDERER_ASPECT_RATIO.defaultValue
313 )
314 )
315 add(
316 SwitchSetting(
317 IntSetting.RENDERER_USE_DISK_SHADER_CACHE,
318 R.string.use_disk_shader_cache,
319 R.string.use_disk_shader_cache_description,
320 IntSetting.RENDERER_USE_DISK_SHADER_CACHE.key,
321 IntSetting.RENDERER_USE_DISK_SHADER_CACHE.defaultValue
322 )
323 )
324 add(
325 SwitchSetting(
326 IntSetting.RENDERER_FORCE_MAX_CLOCK,
327 R.string.renderer_force_max_clock,
328 R.string.renderer_force_max_clock_description,
329 IntSetting.RENDERER_FORCE_MAX_CLOCK.key,
330 IntSetting.RENDERER_FORCE_MAX_CLOCK.defaultValue
331 )
332 )
333 add(
334 SwitchSetting(
335 IntSetting.RENDERER_ASYNCHRONOUS_SHADERS,
336 R.string.renderer_asynchronous_shaders,
337 R.string.renderer_asynchronous_shaders_description,
338 IntSetting.RENDERER_ASYNCHRONOUS_SHADERS.key,
339 IntSetting.RENDERER_ASYNCHRONOUS_SHADERS.defaultValue
340 )
341 )
342 add(
343 SwitchSetting(
344 IntSetting.RENDERER_REACTIVE_FLUSHING,
345 R.string.renderer_reactive_flushing,
346 R.string.renderer_reactive_flushing_description,
347 IntSetting.RENDERER_REACTIVE_FLUSHING.key,
348 IntSetting.RENDERER_REACTIVE_FLUSHING.defaultValue
349 )
350 )
351 } 139 }
352 } 140 }
353 141
354 private fun addAudioSettings(sl: ArrayList<SettingsItem>) { 142 private fun addAudioSettings(sl: ArrayList<SettingsItem>) {
355 settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_audio)) 143 settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_audio))
356 sl.apply { 144 sl.apply {
357 add( 145 add(IntSetting.AUDIO_OUTPUT_ENGINE.key)
358 StringSingleChoiceSetting( 146 add(ByteSetting.AUDIO_VOLUME.key)
359 StringSetting.AUDIO_OUTPUT_ENGINE,
360 R.string.audio_output_engine,
361 0,
362 settingsActivity.resources.getStringArray(R.array.outputEngineEntries),
363 settingsActivity.resources.getStringArray(R.array.outputEngineValues),
364 StringSetting.AUDIO_OUTPUT_ENGINE.key,
365 StringSetting.AUDIO_OUTPUT_ENGINE.defaultValue
366 )
367 )
368 add(
369 SliderSetting(
370 IntSetting.AUDIO_VOLUME,
371 R.string.audio_volume,
372 R.string.audio_volume_description,
373 0,
374 100,
375 "%",
376 IntSetting.AUDIO_VOLUME.key,
377 IntSetting.AUDIO_VOLUME.defaultValue
378 )
379 )
380 } 147 }
381 } 148 }
382 149
383 private fun addThemeSettings(sl: ArrayList<SettingsItem>) { 150 private fun addThemeSettings(sl: ArrayList<SettingsItem>) {
384 settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_theme)) 151 settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_theme))
385 sl.apply { 152 sl.apply {
386 val theme: AbstractIntSetting = object : AbstractIntSetting { 153 val theme: AbstractIntSetting = object : AbstractIntSetting {
387 override var int: Int 154 override val int: Int
388 get() = preferences.getInt(Settings.PREF_THEME, 0) 155 get() = preferences.getInt(Settings.PREF_THEME, 0)
389 set(value) { 156
390 preferences.edit() 157 override fun setInt(value: Int) {
391 .putInt(Settings.PREF_THEME, value) 158 preferences.edit()
392 .apply() 159 .putInt(Settings.PREF_THEME, value)
393 settingsActivity.recreate() 160 .apply()
394 } 161 settingsViewModel.setShouldRecreate(true)
395 override val key: String? = null 162 }
396 override val section: String? = null 163
397 override val isRuntimeEditable: Boolean = false 164 override val key: String = Settings.PREF_THEME
398 override val valueAsString: String 165 override val category = Settings.Category.UiGeneral
399 get() = preferences.getInt(Settings.PREF_THEME, 0).toString() 166 override val isRuntimeModifiable: Boolean = false
400 override val defaultValue: Any = 0 167 override val defaultValue: Int = 0
168 override fun reset() {
169 preferences.edit()
170 .putInt(Settings.PREF_THEME, defaultValue)
171 .apply()
172 }
401 } 173 }
402 174
403 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { 175 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
@@ -423,20 +195,26 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
423 } 195 }
424 196
425 val themeMode: AbstractIntSetting = object : AbstractIntSetting { 197 val themeMode: AbstractIntSetting = object : AbstractIntSetting {
426 override var int: Int 198 override val int: Int
427 get() = preferences.getInt(Settings.PREF_THEME_MODE, -1) 199 get() = preferences.getInt(Settings.PREF_THEME_MODE, -1)
428 set(value) { 200
429 preferences.edit() 201 override fun setInt(value: Int) {
430 .putInt(Settings.PREF_THEME_MODE, value) 202 preferences.edit()
431 .apply() 203 .putInt(Settings.PREF_THEME_MODE, value)
432 ThemeHelper.setThemeMode(settingsActivity) 204 .apply()
433 } 205 settingsViewModel.setShouldRecreate(true)
434 override val key: String? = null 206 }
435 override val section: String? = null 207
436 override val isRuntimeEditable: Boolean = false 208 override val key: String = Settings.PREF_THEME_MODE
437 override val valueAsString: String 209 override val category = Settings.Category.UiGeneral
438 get() = preferences.getInt(Settings.PREF_THEME_MODE, -1).toString() 210 override val isRuntimeModifiable: Boolean = false
439 override val defaultValue: Any = -1 211 override val defaultValue: Int = -1
212 override fun reset() {
213 preferences.edit()
214 .putInt(Settings.PREF_BLACK_BACKGROUNDS, defaultValue)
215 .apply()
216 settingsViewModel.setShouldRecreate(true)
217 }
440 } 218 }
441 219
442 add( 220 add(
@@ -450,21 +228,26 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
450 ) 228 )
451 229
452 val blackBackgrounds: AbstractBooleanSetting = object : AbstractBooleanSetting { 230 val blackBackgrounds: AbstractBooleanSetting = object : AbstractBooleanSetting {
453 override var boolean: Boolean 231 override val boolean: Boolean
454 get() = preferences.getBoolean(Settings.PREF_BLACK_BACKGROUNDS, false)
455 set(value) {
456 preferences.edit()
457 .putBoolean(Settings.PREF_BLACK_BACKGROUNDS, value)
458 .apply()
459 settingsActivity.recreate()
460 }
461 override val key: String? = null
462 override val section: String? = null
463 override val isRuntimeEditable: Boolean = false
464 override val valueAsString: String
465 get() = preferences.getBoolean(Settings.PREF_BLACK_BACKGROUNDS, false) 232 get() = preferences.getBoolean(Settings.PREF_BLACK_BACKGROUNDS, false)
466 .toString() 233
467 override val defaultValue: Any = false 234 override fun setBoolean(value: Boolean) {
235 preferences.edit()
236 .putBoolean(Settings.PREF_BLACK_BACKGROUNDS, value)
237 .apply()
238 settingsViewModel.setShouldRecreate(true)
239 }
240
241 override val key: String = Settings.PREF_BLACK_BACKGROUNDS
242 override val category = Settings.Category.UiGeneral
243 override val isRuntimeModifiable: Boolean = false
244 override val defaultValue: Boolean = false
245 override fun reset() {
246 preferences.edit()
247 .putBoolean(Settings.PREF_BLACK_BACKGROUNDS, defaultValue)
248 .apply()
249 settingsViewModel.setShouldRecreate(true)
250 }
468 } 251 }
469 252
470 add( 253 add(
@@ -478,62 +261,15 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
478 } 261 }
479 262
480 private fun addDebugSettings(sl: ArrayList<SettingsItem>) { 263 private fun addDebugSettings(sl: ArrayList<SettingsItem>) {
481 settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_debug)) 264 settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_debug))
482 sl.apply { 265 sl.apply {
483 add(HeaderSetting(R.string.gpu)) 266 add(HeaderSetting(R.string.gpu))
484 add( 267 add(IntSetting.RENDERER_BACKEND.key)
485 SingleChoiceSetting( 268 add(BooleanSetting.RENDERER_DEBUG.key)
486 IntSetting.RENDERER_BACKEND,
487 R.string.renderer_api,
488 0,
489 R.array.rendererApiNames,
490 R.array.rendererApiValues,
491 IntSetting.RENDERER_BACKEND.key,
492 IntSetting.RENDERER_BACKEND.defaultValue
493 )
494 )
495 add(
496 SwitchSetting(
497 IntSetting.RENDERER_DEBUG,
498 R.string.renderer_debug,
499 R.string.renderer_debug_description,
500 IntSetting.RENDERER_DEBUG.key,
501 IntSetting.RENDERER_DEBUG.defaultValue
502 )
503 )
504 269
505 add(HeaderSetting(R.string.cpu)) 270 add(HeaderSetting(R.string.cpu))
506 add( 271 add(BooleanSetting.CPU_DEBUG_MODE.key)
507 SwitchSetting( 272 add(SettingsItem.FASTMEM_COMBINED)
508 BooleanSetting.CPU_DEBUG_MODE,
509 R.string.cpu_debug_mode,
510 R.string.cpu_debug_mode_description,
511 BooleanSetting.CPU_DEBUG_MODE.key,
512 BooleanSetting.CPU_DEBUG_MODE.defaultValue
513 )
514 )
515
516 val fastmem = object : AbstractBooleanSetting {
517 override var boolean: Boolean
518 get() =
519 BooleanSetting.FASTMEM.boolean && BooleanSetting.FASTMEM_EXCLUSIVES.boolean
520 set(value) {
521 BooleanSetting.FASTMEM.boolean = value
522 BooleanSetting.FASTMEM_EXCLUSIVES.boolean = value
523 }
524 override val key: String? = null
525 override val section: String = Settings.SECTION_CPU
526 override val isRuntimeEditable: Boolean = false
527 override val valueAsString: String = ""
528 override val defaultValue: Any = true
529 }
530 add(
531 SwitchSetting(
532 fastmem,
533 R.string.fastmem,
534 0
535 )
536 )
537 } 273 }
538 } 274 }
539} 275}
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 1ebe35eaa..000000000
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentView.kt
+++ /dev/null
@@ -1,58 +0,0 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.features.settings.ui
5
6import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
7import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
8
9/**
10 * Abstraction for a screen showing a list of settings. Instances of
11 * this type of view will each display a layer of the setting hierarchy.
12 */
13interface SettingsFragmentView {
14 /**
15 * Pass an ArrayList to the View so that it can be displayed on screen.
16 *
17 * @param settingsList The result of converting the HashMap to an ArrayList
18 */
19 fun showSettingsList(settingsList: ArrayList<SettingsItem>)
20
21 /**
22 * Instructs the Fragment to load the settings screen.
23 */
24 fun loadSettingsList()
25
26 /**
27 * @return The Fragment's containing activity.
28 */
29 val activityView: SettingsActivityView?
30
31 /**
32 * Tell the Fragment to tell the containing Activity to show a new
33 * Fragment containing a submenu of settings.
34 *
35 * @param menuKey Identifier for the settings group that should be shown.
36 */
37 fun loadSubMenu(menuKey: String)
38
39 /**
40 * Tell the Fragment to tell the containing activity to display a toast message.
41 *
42 * @param message Text to be shown in the Toast
43 * @param is_long Whether this should be a long Toast or short one.
44 */
45 fun showToastMessage(message: String?, is_long: Boolean)
46
47 /**
48 * Have the fragment add a setting to the HashMap.
49 *
50 * @param setting The (possibly previously missing) new setting.
51 */
52 fun putSetting(setting: AbstractSetting)
53
54 /**
55 * Have the fragment tell the containing Activity that a setting was modified.
56 */
57 fun onSettingChanged()
58}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt
index 79572fc06..525f013f8 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
29 } 29 }
30 30
31 binding.textSettingValue.visibility = View.VISIBLE 31 binding.textSettingValue.visibility = View.VISIBLE
32 val epochTime = setting.value.toLong() 32 val epochTime = setting.value
33 val instant = Instant.ofEpochMilli(epochTime * 1000) 33 val instant = Instant.ofEpochMilli(epochTime * 1000)
34 val zonedTime = ZonedDateTime.ofInstant(instant, ZoneId.of("UTC")) 34 val zonedTime = ZonedDateTime.ofInstant(instant, ZoneId.of("UTC"))
35 val dateFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM) 35 val dateFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)
@@ -46,7 +46,7 @@ class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA
46 46
47 override fun onLongClick(clicked: View): Boolean { 47 override fun onLongClick(clicked: View): Boolean {
48 if (setting.isEditable) { 48 if (setting.isEditable) {
49 return adapter.onLongClick(setting.setting!!, bindingAdapterPosition) 49 return adapter.onLongClick(setting, bindingAdapterPosition)
50 } 50 }
51 return false 51 return false
52 } 52 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt
index b42d955aa..80d1b22c1 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
35 } 35 }
36 } 36 }
37 } else if (item is StringSingleChoiceSetting) { 37 } else if (item is StringSingleChoiceSetting) {
38 for (i in item.values!!.indices) { 38 for (i in item.values.indices) {
39 if (item.values[i] == item.selectedValue) { 39 if (item.values[i] == item.selectedValue) {
40 binding.textSettingValue.text = item.choices[i] 40 binding.textSettingValue.text = item.choices[i]
41 break 41 break
@@ -66,7 +66,7 @@ class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: Setti
66 66
67 override fun onLongClick(clicked: View): Boolean { 67 override fun onLongClick(clicked: View): Boolean {
68 if (setting.isEditable) { 68 if (setting.isEditable) {
69 return adapter.onLongClick(setting.setting!!, bindingAdapterPosition) 69 return adapter.onLongClick(setting, bindingAdapterPosition)
70 } 70 }
71 return false 71 return false
72 } 72 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SliderViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SliderViewHolder.kt
index a23b5d109..b83c90100 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
41 41
42 override fun onLongClick(clicked: View): Boolean { 42 override fun onLongClick(clicked: View): Boolean {
43 if (setting.isEditable) { 43 if (setting.isEditable) {
44 return adapter.onLongClick(setting.setting!!, bindingAdapterPosition) 44 return adapter.onLongClick(setting, bindingAdapterPosition)
45 } 45 }
46 return false 46 return false
47 } 47 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt
index ef34bf5f4..57fdeaa20 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
25 binding.textSettingDescription.text = "" 25 binding.textSettingDescription.text = ""
26 binding.textSettingDescription.visibility = View.GONE 26 binding.textSettingDescription.visibility = View.GONE
27 } 27 }
28
29 binding.switchWidget.setOnCheckedChangeListener(null)
30 binding.switchWidget.isChecked = setting.checked
28 binding.switchWidget.setOnCheckedChangeListener { _: CompoundButton, _: Boolean -> 31 binding.switchWidget.setOnCheckedChangeListener { _: CompoundButton, _: Boolean ->
29 adapter.onBooleanClick(item, bindingAdapterPosition, binding.switchWidget.isChecked) 32 adapter.onBooleanClick(item, binding.switchWidget.isChecked)
30 } 33 }
31 binding.switchWidget.isChecked = setting.isChecked
32 34
33 setStyle(setting.isEditable, binding) 35 setStyle(setting.isEditable, binding)
34 } 36 }
@@ -41,7 +43,7 @@ class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter
41 43
42 override fun onLongClick(clicked: View): Boolean { 44 override fun onLongClick(clicked: View): Boolean {
43 if (setting.isEditable) { 45 if (setting.isEditable) {
44 return adapter.onLongClick(setting.setting!!, bindingAdapterPosition) 46 return adapter.onLongClick(setting, bindingAdapterPosition)
45 } 47 }
46 return false 48 return false
47 } 49 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.kt
index 70a52df5d..2b04d666a 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.kt
@@ -3,18 +3,15 @@
3 3
4package org.yuzu.yuzu_emu.features.settings.utils 4package org.yuzu.yuzu_emu.features.settings.utils
5 5
6import android.widget.Toast
6import java.io.* 7import java.io.*
7import java.util.*
8import org.ini4j.Wini 8import org.ini4j.Wini
9import org.yuzu.yuzu_emu.NativeLibrary
10import org.yuzu.yuzu_emu.R 9import org.yuzu.yuzu_emu.R
11import org.yuzu.yuzu_emu.YuzuApplication 10import org.yuzu.yuzu_emu.YuzuApplication
12import org.yuzu.yuzu_emu.features.settings.model.* 11import org.yuzu.yuzu_emu.features.settings.model.*
13import org.yuzu.yuzu_emu.features.settings.model.Settings.SettingsSectionMap
14import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivityView
15import org.yuzu.yuzu_emu.utils.BiMap
16import org.yuzu.yuzu_emu.utils.DirectoryInitialization 12import org.yuzu.yuzu_emu.utils.DirectoryInitialization
17import org.yuzu.yuzu_emu.utils.Log 13import org.yuzu.yuzu_emu.utils.Log
14import org.yuzu.yuzu_emu.utils.NativeConfig
18 15
19/** 16/**
20 * Contains static methods for interacting with .ini files in which settings are stored. 17 * Contains static methods for interacting with .ini files in which settings are stored.
@@ -22,243 +19,41 @@ import org.yuzu.yuzu_emu.utils.Log
22object SettingsFile { 19object SettingsFile {
23 const val FILE_NAME_CONFIG = "config" 20 const val FILE_NAME_CONFIG = "config"
24 21
25 private var sectionsMap = BiMap<String?, String?>()
26
27 /**
28 * Reads a given .ini file from disk and returns it as a HashMap of Settings, themselves
29 * effectively a HashMap of key/value settings. If unsuccessful, outputs an error telling why it
30 * failed.
31 *
32 * @param ini The ini file to load the settings from
33 * @param isCustomGame
34 * @param view The current view.
35 * @return An Observable that emits a HashMap of the file's contents, then completes.
36 */
37 private fun readFile(
38 ini: File?,
39 isCustomGame: Boolean,
40 view: SettingsActivityView? = null
41 ): HashMap<String, SettingSection?> {
42 val sections: HashMap<String, SettingSection?> = SettingsSectionMap()
43 var reader: BufferedReader? = null
44 try {
45 reader = BufferedReader(FileReader(ini))
46 var current: SettingSection? = null
47 var line: String?
48 while (reader.readLine().also { line = it } != null) {
49 if (line!!.startsWith("[") && line!!.endsWith("]")) {
50 current = sectionFromLine(line!!, isCustomGame)
51 sections[current.name] = current
52 } else if (current != null) {
53 val setting = settingFromLine(line!!)
54 if (setting != null) {
55 current.putSetting(setting)
56 }
57 }
58 }
59 } catch (e: FileNotFoundException) {
60 Log.error("[SettingsFile] File not found: " + e.message)
61 view?.onSettingsFileNotFound()
62 } catch (e: IOException) {
63 Log.error("[SettingsFile] Error reading from: " + e.message)
64 view?.onSettingsFileNotFound()
65 } finally {
66 if (reader != null) {
67 try {
68 reader.close()
69 } catch (e: IOException) {
70 Log.error("[SettingsFile] Error closing: " + e.message)
71 }
72 }
73 }
74 return sections
75 }
76
77 fun readFile(fileName: String, view: SettingsActivityView?): HashMap<String, SettingSection?> {
78 return readFile(getSettingsFile(fileName), false, view)
79 }
80
81 fun readFile(fileName: String): HashMap<String, SettingSection?> =
82 readFile(getSettingsFile(fileName), false)
83
84 /**
85 * Reads a given .ini file from disk and returns it as a HashMap of SettingSections, themselves
86 * effectively a HashMap of key/value settings. If unsuccessful, outputs an error telling why it
87 * failed.
88 *
89 * @param gameId the id of the game to load it's settings.
90 * @param view The current view.
91 */
92 fun readCustomGameSettings(
93 gameId: String,
94 view: SettingsActivityView?
95 ): HashMap<String, SettingSection?> {
96 return readFile(getCustomGameSettingsFile(gameId), true, view)
97 }
98
99 /** 22 /**
100 * Saves a Settings HashMap to a given .ini file on disk. If unsuccessful, outputs an error 23 * Saves a Settings HashMap to a given .ini file on disk. If unsuccessful, outputs an error
101 * telling why it failed. 24 * telling why it failed.
102 * 25 *
103 * @param fileName The target filename without a path or extension. 26 * @param fileName The target filename without a path or extension.
104 * @param sections The HashMap containing the Settings we want to serialize.
105 * @param view The current view.
106 */ 27 */
107 fun saveFile( 28 fun saveFile(fileName: String) {
108 fileName: String,
109 sections: TreeMap<String, SettingSection>,
110 view: SettingsActivityView
111 ) {
112 val ini = getSettingsFile(fileName) 29 val ini = getSettingsFile(fileName)
113 try { 30 try {
114 val writer = Wini(ini) 31 val wini = Wini(ini)
115 val keySet: Set<String> = sections.keys 32 for (specificCategory in Settings.Category.values()) {
116 for (key in keySet) { 33 val categoryHeader = NativeConfig.getConfigHeader(specificCategory.ordinal)
117 val section = sections[key] 34 for (setting in Settings.settingsList) {
118 writeSection(writer, section!!) 35 if (setting.key!!.isEmpty()) continue
36
37 val settingCategoryHeader =
38 NativeConfig.getConfigHeader(setting.category.ordinal)
39 val iniSetting: String? = wini.get(categoryHeader, setting.key)
40 if (iniSetting != null || settingCategoryHeader == categoryHeader) {
41 wini.put(settingCategoryHeader, setting.key, setting.valueAsString)
42 }
43 }
119 } 44 }
120 writer.store() 45 wini.store()
121 } catch (e: IOException) { 46 } catch (e: IOException) {
122 Log.error("[SettingsFile] File not found: " + fileName + ".ini: " + e.message) 47 Log.error("[SettingsFile] File not found: " + fileName + ".ini: " + e.message)
123 view.showToastMessage( 48 val context = YuzuApplication.appContext
124 YuzuApplication.appContext 49 Toast.makeText(
125 .getString(R.string.error_saving, fileName, e.message), 50 context,
126 false 51 context.getString(R.string.error_saving, fileName, e.message),
127 ) 52 Toast.LENGTH_SHORT
128 } 53 ).show()
129 }
130
131 fun saveCustomGameSettings(gameId: String?, sections: HashMap<String, SettingSection?>) {
132 val sortedSections: Set<String> = TreeSet(sections.keys)
133 for (sectionKey in sortedSections) {
134 val section = sections[sectionKey]
135 val settings = section!!.settings
136 val sortedKeySet: Set<String> = TreeSet(settings.keys)
137 for (settingKey in sortedKeySet) {
138 val setting = settings[settingKey]
139 NativeLibrary.setUserSetting(
140 gameId,
141 mapSectionNameFromIni(
142 section.name
143 ),
144 setting!!.key,
145 setting.valueAsString
146 )
147 }
148 }
149 }
150
151 private fun mapSectionNameFromIni(generalSectionName: String): String? {
152 return if (sectionsMap.getForward(generalSectionName) != null) {
153 sectionsMap.getForward(generalSectionName)
154 } else {
155 generalSectionName
156 }
157 }
158
159 private fun mapSectionNameToIni(generalSectionName: String): String {
160 return if (sectionsMap.getBackward(generalSectionName) != null) {
161 sectionsMap.getBackward(generalSectionName).toString()
162 } else {
163 generalSectionName
164 }
165 }
166
167 fun getSettingsFile(fileName: String): File {
168 return File(
169 DirectoryInitialization.userDirectory + "/config/" + fileName + ".ini"
170 )
171 }
172
173 private fun getCustomGameSettingsFile(gameId: String): File {
174 return File(DirectoryInitialization.userDirectory + "/GameSettings/" + gameId + ".ini")
175 }
176
177 private fun sectionFromLine(line: String, isCustomGame: Boolean): SettingSection {
178 var sectionName: String = line.substring(1, line.length - 1)
179 if (isCustomGame) {
180 sectionName = mapSectionNameToIni(sectionName)
181 } 54 }
182 return SettingSection(sectionName)
183 } 55 }
184 56
185 /** 57 fun getSettingsFile(fileName: String): File =
186 * For a line of text, determines what type of data is being represented, and returns 58 File(DirectoryInitialization.userDirectory + "/config/" + fileName + ".ini")
187 * a Setting object containing this data.
188 *
189 * @param line The line of text being parsed.
190 * @return A typed Setting containing the key/value contained in the line.
191 */
192 private fun settingFromLine(line: String): AbstractSetting? {
193 val splitLine = line.split("=".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
194 if (splitLine.size != 2) {
195 return null
196 }
197 val key = splitLine[0].trim { it <= ' ' }
198 val value = splitLine[1].trim { it <= ' ' }
199 if (value.isEmpty()) {
200 return null
201 }
202
203 val booleanSetting = BooleanSetting.from(key)
204 if (booleanSetting != null) {
205 booleanSetting.boolean = value.toBoolean()
206 return booleanSetting
207 }
208
209 val intSetting = IntSetting.from(key)
210 if (intSetting != null) {
211 intSetting.int = value.toInt()
212 return intSetting
213 }
214
215 val floatSetting = FloatSetting.from(key)
216 if (floatSetting != null) {
217 floatSetting.float = value.toFloat()
218 return floatSetting
219 }
220
221 val stringSetting = StringSetting.from(key)
222 if (stringSetting != null) {
223 stringSetting.string = value
224 return stringSetting
225 }
226
227 return null
228 }
229
230 /**
231 * Writes the contents of a Section HashMap to disk.
232 *
233 * @param parser A Wini pointed at a file on disk.
234 * @param section A section containing settings to be written to the file.
235 */
236 private fun writeSection(parser: Wini, section: SettingSection) {
237 // Write the section header.
238 val header = section.name
239
240 // Write this section's values.
241 val settings = section.settings
242 val keySet: Set<String> = settings.keys
243 for (key in keySet) {
244 val setting = settings[key]
245 parser.put(header, setting!!.key, setting.valueAsString)
246 }
247
248 BooleanSetting.values().forEach {
249 if (!keySet.contains(it.key)) {
250 parser.put(header, it.key, it.valueAsString)
251 }
252 }
253 IntSetting.values().forEach {
254 if (!keySet.contains(it.key)) {
255 parser.put(header, it.key, it.valueAsString)
256 }
257 }
258 StringSetting.values().forEach {
259 if (!keySet.contains(it.key)) {
260 parser.put(header, it.key, it.valueAsString)
261 }
262 }
263 }
264} 59}
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 956c35c0a..53f19c4f8 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
@@ -29,6 +29,7 @@ import androidx.fragment.app.Fragment
29import androidx.lifecycle.Lifecycle 29import androidx.lifecycle.Lifecycle
30import androidx.lifecycle.lifecycleScope 30import androidx.lifecycle.lifecycleScope
31import androidx.lifecycle.repeatOnLifecycle 31import androidx.lifecycle.repeatOnLifecycle
32import androidx.navigation.findNavController
32import androidx.navigation.fragment.navArgs 33import androidx.navigation.fragment.navArgs
33import androidx.preference.PreferenceManager 34import androidx.preference.PreferenceManager
34import androidx.window.layout.FoldingFeature 35import androidx.window.layout.FoldingFeature
@@ -38,6 +39,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
38import com.google.android.material.slider.Slider 39import com.google.android.material.slider.Slider
39import kotlinx.coroutines.Dispatchers 40import kotlinx.coroutines.Dispatchers
40import kotlinx.coroutines.launch 41import kotlinx.coroutines.launch
42import org.yuzu.yuzu_emu.HomeNavigationDirections
41import org.yuzu.yuzu_emu.NativeLibrary 43import org.yuzu.yuzu_emu.NativeLibrary
42import org.yuzu.yuzu_emu.R 44import org.yuzu.yuzu_emu.R
43import org.yuzu.yuzu_emu.YuzuApplication 45import org.yuzu.yuzu_emu.YuzuApplication
@@ -46,7 +48,6 @@ import org.yuzu.yuzu_emu.databinding.DialogOverlayAdjustBinding
46import org.yuzu.yuzu_emu.databinding.FragmentEmulationBinding 48import org.yuzu.yuzu_emu.databinding.FragmentEmulationBinding
47import org.yuzu.yuzu_emu.features.settings.model.IntSetting 49import org.yuzu.yuzu_emu.features.settings.model.IntSetting
48import org.yuzu.yuzu_emu.features.settings.model.Settings 50import org.yuzu.yuzu_emu.features.settings.model.Settings
49import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity
50import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile 51import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
51import org.yuzu.yuzu_emu.model.Game 52import org.yuzu.yuzu_emu.model.Game
52import org.yuzu.yuzu_emu.overlay.InputOverlay 53import org.yuzu.yuzu_emu.overlay.InputOverlay
@@ -158,7 +159,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
158 } 159 }
159 160
160 R.id.menu_settings -> { 161 R.id.menu_settings -> {
161 SettingsActivity.launch(requireContext(), SettingsFile.FILE_NAME_CONFIG, "") 162 val action = HomeNavigationDirections.actionGlobalSettingsActivity(
163 null,
164 SettingsFile.FILE_NAME_CONFIG
165 )
166 binding.root.findNavController().navigate(action)
162 true 167 true
163 } 168 }
164 169
@@ -230,7 +235,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
230 override fun onResume() { 235 override fun onResume() {
231 super.onResume() 236 super.onResume()
232 if (!DirectoryInitialization.areDirectoriesReady) { 237 if (!DirectoryInitialization.areDirectoriesReady) {
233 DirectoryInitialization.start(requireContext()) 238 DirectoryInitialization.start()
234 } 239 }
235 240
236 updateScreenLayout() 241 updateScreenLayout()
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt
index d5e793491..cbbe14d22 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt
@@ -25,17 +25,18 @@ import androidx.core.view.updatePadding
25import androidx.documentfile.provider.DocumentFile 25import androidx.documentfile.provider.DocumentFile
26import androidx.fragment.app.Fragment 26import androidx.fragment.app.Fragment
27import androidx.fragment.app.activityViewModels 27import androidx.fragment.app.activityViewModels
28import androidx.navigation.findNavController
28import androidx.navigation.fragment.findNavController 29import androidx.navigation.fragment.findNavController
29import androidx.recyclerview.widget.LinearLayoutManager 30import androidx.recyclerview.widget.LinearLayoutManager
30import com.google.android.material.dialog.MaterialAlertDialogBuilder 31import com.google.android.material.dialog.MaterialAlertDialogBuilder
31import com.google.android.material.transition.MaterialSharedAxis 32import com.google.android.material.transition.MaterialSharedAxis
32import org.yuzu.yuzu_emu.BuildConfig 33import org.yuzu.yuzu_emu.BuildConfig
34import org.yuzu.yuzu_emu.HomeNavigationDirections
33import org.yuzu.yuzu_emu.R 35import org.yuzu.yuzu_emu.R
34import org.yuzu.yuzu_emu.adapters.HomeSettingAdapter 36import org.yuzu.yuzu_emu.adapters.HomeSettingAdapter
35import org.yuzu.yuzu_emu.databinding.FragmentHomeSettingsBinding 37import org.yuzu.yuzu_emu.databinding.FragmentHomeSettingsBinding
36import org.yuzu.yuzu_emu.features.DocumentProvider 38import org.yuzu.yuzu_emu.features.DocumentProvider
37import org.yuzu.yuzu_emu.features.settings.model.Settings 39import org.yuzu.yuzu_emu.features.settings.model.Settings
38import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity
39import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile 40import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
40import org.yuzu.yuzu_emu.model.HomeSetting 41import org.yuzu.yuzu_emu.model.HomeSetting
41import org.yuzu.yuzu_emu.model.HomeViewModel 42import org.yuzu.yuzu_emu.model.HomeViewModel
@@ -74,7 +75,13 @@ class HomeSettingsFragment : Fragment() {
74 R.string.advanced_settings, 75 R.string.advanced_settings,
75 R.string.settings_description, 76 R.string.settings_description,
76 R.drawable.ic_settings, 77 R.drawable.ic_settings,
77 { SettingsActivity.launch(requireContext(), SettingsFile.FILE_NAME_CONFIG, "") } 78 {
79 val action = HomeNavigationDirections.actionGlobalSettingsActivity(
80 null,
81 SettingsFile.FILE_NAME_CONFIG
82 )
83 binding.root.findNavController().navigate(action)
84 }
78 ) 85 )
79 ) 86 )
80 add( 87 add(
@@ -90,7 +97,13 @@ class HomeSettingsFragment : Fragment() {
90 R.string.preferences_theme, 97 R.string.preferences_theme,
91 R.string.theme_and_color_description, 98 R.string.theme_and_color_description,
92 R.drawable.ic_palette, 99 R.drawable.ic_palette,
93 { SettingsActivity.launch(requireContext(), Settings.SECTION_THEME, "") } 100 {
101 val action = HomeNavigationDirections.actionGlobalSettingsActivity(
102 null,
103 Settings.SECTION_THEME
104 )
105 binding.root.findNavController().navigate(action)
106 }
94 ) 107 )
95 ) 108 )
96 add( 109 add(
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsDialogFragment.kt
new file mode 100644
index 000000000..d18ec6974
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsDialogFragment.kt
@@ -0,0 +1,235 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.fragments
5
6import android.app.Dialog
7import android.content.DialogInterface
8import android.os.Bundle
9import android.view.LayoutInflater
10import android.view.View
11import android.view.ViewGroup
12import androidx.fragment.app.DialogFragment
13import androidx.fragment.app.activityViewModels
14import androidx.lifecycle.Lifecycle
15import androidx.lifecycle.lifecycleScope
16import androidx.lifecycle.repeatOnLifecycle
17import com.google.android.material.dialog.MaterialAlertDialogBuilder
18import com.google.android.material.slider.Slider
19import kotlinx.coroutines.launch
20import org.yuzu.yuzu_emu.R
21import org.yuzu.yuzu_emu.databinding.DialogSliderBinding
22import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
23import org.yuzu.yuzu_emu.features.settings.model.view.SingleChoiceSetting
24import org.yuzu.yuzu_emu.features.settings.model.view.SliderSetting
25import org.yuzu.yuzu_emu.features.settings.model.view.StringSingleChoiceSetting
26import org.yuzu.yuzu_emu.model.SettingsViewModel
27
28class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener {
29 private var type = 0
30 private var position = 0
31
32 private var defaultCancelListener =
33 DialogInterface.OnClickListener { _: DialogInterface?, _: Int -> closeDialog() }
34
35 private val settingsViewModel: SettingsViewModel by activityViewModels()
36
37 private lateinit var sliderBinding: DialogSliderBinding
38
39 override fun onCreate(savedInstanceState: Bundle?) {
40 super.onCreate(savedInstanceState)
41 type = requireArguments().getInt(TYPE)
42 position = requireArguments().getInt(POSITION)
43
44 if (settingsViewModel.clickedItem == null) dismiss()
45 }
46
47 override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
48 return when (type) {
49 TYPE_RESET_SETTING -> {
50 MaterialAlertDialogBuilder(requireContext())
51 .setMessage(R.string.reset_setting_confirmation)
52 .setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
53 settingsViewModel.clickedItem!!.setting.reset()
54 settingsViewModel.setAdapterItemChanged(position)
55 settingsViewModel.shouldSave = true
56 }
57 .setNegativeButton(android.R.string.cancel, null)
58 .create()
59 }
60
61 SettingsItem.TYPE_SINGLE_CHOICE -> {
62 val item = settingsViewModel.clickedItem as SingleChoiceSetting
63 val value = getSelectionForSingleChoiceValue(item)
64 MaterialAlertDialogBuilder(requireContext())
65 .setTitle(item.nameId)
66 .setSingleChoiceItems(item.choicesId, value, this)
67 .create()
68 }
69
70 SettingsItem.TYPE_SLIDER -> {
71 sliderBinding = DialogSliderBinding.inflate(layoutInflater)
72 val item = settingsViewModel.clickedItem as SliderSetting
73
74 settingsViewModel.setSliderTextValue(item.selectedValue.toFloat(), item.units)
75 sliderBinding.slider.apply {
76 valueFrom = item.min.toFloat()
77 valueTo = item.max.toFloat()
78 value = settingsViewModel.sliderProgress.value.toFloat()
79 addOnChangeListener { _: Slider, value: Float, _: Boolean ->
80 settingsViewModel.setSliderTextValue(value, item.units)
81 }
82 }
83
84 MaterialAlertDialogBuilder(requireContext())
85 .setTitle(item.nameId)
86 .setView(sliderBinding.root)
87 .setPositiveButton(android.R.string.ok, this)
88 .setNegativeButton(android.R.string.cancel, defaultCancelListener)
89 .create()
90 }
91
92 SettingsItem.TYPE_STRING_SINGLE_CHOICE -> {
93 val item = settingsViewModel.clickedItem as StringSingleChoiceSetting
94 MaterialAlertDialogBuilder(requireContext())
95 .setTitle(item.nameId)
96 .setSingleChoiceItems(item.choices, item.selectValueIndex, this)
97 .create()
98 }
99
100 else -> super.onCreateDialog(savedInstanceState)
101 }
102 }
103
104 override fun onCreateView(
105 inflater: LayoutInflater,
106 container: ViewGroup?,
107 savedInstanceState: Bundle?
108 ): View? {
109 return when (type) {
110 SettingsItem.TYPE_SLIDER -> sliderBinding.root
111 else -> super.onCreateView(inflater, container, savedInstanceState)
112 }
113 }
114
115 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
116 super.onViewCreated(view, savedInstanceState)
117 when (type) {
118 SettingsItem.TYPE_SLIDER -> {
119 viewLifecycleOwner.lifecycleScope.launch {
120 repeatOnLifecycle(Lifecycle.State.CREATED) {
121 settingsViewModel.sliderTextValue.collect {
122 sliderBinding.textValue.text = it
123 }
124 }
125 repeatOnLifecycle(Lifecycle.State.CREATED) {
126 settingsViewModel.sliderProgress.collect {
127 sliderBinding.slider.value = it.toFloat()
128 }
129 }
130 }
131 }
132 }
133 }
134
135 override fun onClick(dialog: DialogInterface, which: Int) {
136 when (settingsViewModel.clickedItem) {
137 is SingleChoiceSetting -> {
138 val scSetting = settingsViewModel.clickedItem as SingleChoiceSetting
139 val value = getValueForSingleChoiceSelection(scSetting, which)
140 if (scSetting.selectedValue != value) {
141 settingsViewModel.shouldSave = true
142 }
143 scSetting.selectedValue = value
144 }
145
146 is StringSingleChoiceSetting -> {
147 val scSetting = settingsViewModel.clickedItem as StringSingleChoiceSetting
148 val value = scSetting.getValueAt(which)
149 if (scSetting.selectedValue != value) settingsViewModel.shouldSave = true
150 scSetting.selectedValue = value
151 }
152
153 is SliderSetting -> {
154 val sliderSetting = settingsViewModel.clickedItem as SliderSetting
155 if (sliderSetting.selectedValue != settingsViewModel.sliderProgress.value) {
156 settingsViewModel.shouldSave = true
157 }
158 sliderSetting.selectedValue = settingsViewModel.sliderProgress.value
159 }
160 }
161 closeDialog()
162 }
163
164 private fun closeDialog() {
165 settingsViewModel.setAdapterItemChanged(position)
166 settingsViewModel.clickedItem = null
167 settingsViewModel.setSliderProgress(-1f)
168 dismiss()
169 }
170
171 private fun getValueForSingleChoiceSelection(item: SingleChoiceSetting, which: Int): Int {
172 val valuesId = item.valuesId
173 return if (valuesId > 0) {
174 val valuesArray = requireContext().resources.getIntArray(valuesId)
175 valuesArray[which]
176 } else {
177 which
178 }
179 }
180
181 private fun getSelectionForSingleChoiceValue(item: SingleChoiceSetting): Int {
182 val value = item.selectedValue
183 val valuesId = item.valuesId
184 if (valuesId > 0) {
185 val valuesArray = requireContext().resources.getIntArray(valuesId)
186 for (index in valuesArray.indices) {
187 val current = valuesArray[index]
188 if (current == value) {
189 return index
190 }
191 }
192 } else {
193 return value
194 }
195 return -1
196 }
197
198 companion object {
199 const val TAG = "SettingsDialogFragment"
200
201 const val TYPE_RESET_SETTING = -1
202
203 const val TITLE = "Title"
204 const val TYPE = "Type"
205 const val POSITION = "Position"
206
207 fun newInstance(
208 settingsViewModel: SettingsViewModel,
209 clickedItem: SettingsItem,
210 type: Int,
211 position: Int
212 ): SettingsDialogFragment {
213 when (type) {
214 SettingsItem.TYPE_HEADER,
215 SettingsItem.TYPE_SWITCH,
216 SettingsItem.TYPE_SUBMENU,
217 SettingsItem.TYPE_DATETIME_SETTING,
218 SettingsItem.TYPE_RUNNABLE ->
219 throw IllegalArgumentException("[SettingsDialogFragment] Incompatible type!")
220
221 SettingsItem.TYPE_SLIDER -> settingsViewModel.setSliderProgress(
222 (clickedItem as SliderSetting).selectedValue.toFloat()
223 )
224 }
225 settingsViewModel.clickedItem = clickedItem
226
227 val args = Bundle()
228 args.putInt(TYPE, type)
229 args.putInt(POSITION, position)
230 val fragment = SettingsDialogFragment()
231 fragment.arguments = args
232 return fragment
233 }
234 }
235}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsSearchFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsSearchFragment.kt
new file mode 100644
index 000000000..55b6a0367
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsSearchFragment.kt
@@ -0,0 +1,184 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.fragments
5
6import android.content.Context
7import android.os.Bundle
8import android.view.LayoutInflater
9import android.view.View
10import android.view.ViewGroup
11import android.view.inputmethod.InputMethodManager
12import androidx.core.view.ViewCompat
13import androidx.core.view.WindowInsetsCompat
14import androidx.core.view.updatePadding
15import androidx.core.widget.doOnTextChanged
16import androidx.fragment.app.Fragment
17import androidx.fragment.app.activityViewModels
18import androidx.recyclerview.widget.LinearLayoutManager
19import com.google.android.material.divider.MaterialDividerItemDecoration
20import com.google.android.material.transition.MaterialSharedAxis
21import info.debatty.java.stringsimilarity.Cosine
22import org.yuzu.yuzu_emu.R
23import org.yuzu.yuzu_emu.databinding.FragmentSettingsSearchBinding
24import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
25import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
26import org.yuzu.yuzu_emu.model.SettingsViewModel
27import org.yuzu.yuzu_emu.utils.NativeConfig
28
29class SettingsSearchFragment : Fragment() {
30 private var _binding: FragmentSettingsSearchBinding? = null
31 private val binding get() = _binding!!
32
33 private var settingsAdapter: SettingsAdapter? = null
34
35 private val settingsViewModel: SettingsViewModel by activityViewModels()
36
37 override fun onCreate(savedInstanceState: Bundle?) {
38 super.onCreate(savedInstanceState)
39 enterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false)
40 returnTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true)
41 }
42
43 override fun onCreateView(
44 inflater: LayoutInflater,
45 container: ViewGroup?,
46 savedInstanceState: Bundle?
47 ): View {
48 _binding = FragmentSettingsSearchBinding.inflate(layoutInflater)
49 return binding.root
50 }
51
52 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
53 super.onViewCreated(view, savedInstanceState)
54 settingsViewModel.setIsUsingSearch(true)
55
56 if (savedInstanceState != null) {
57 binding.searchText.setText(savedInstanceState.getString(SEARCH_TEXT))
58 }
59
60 settingsAdapter = SettingsAdapter(this, requireContext())
61
62 val dividerDecoration = MaterialDividerItemDecoration(
63 requireContext(),
64 LinearLayoutManager.VERTICAL
65 )
66 dividerDecoration.isLastItemDecorated = false
67 binding.settingsList.apply {
68 adapter = settingsAdapter
69 layoutManager = LinearLayoutManager(requireContext())
70 addItemDecoration(dividerDecoration)
71 }
72
73 focusSearch()
74
75 binding.backButton.setOnClickListener { settingsViewModel.setShouldNavigateBack(true) }
76 binding.searchBackground.setOnClickListener { focusSearch() }
77 binding.clearButton.setOnClickListener { binding.searchText.setText("") }
78 binding.searchText.doOnTextChanged { _, _, _, _ ->
79 search()
80 binding.settingsList.smoothScrollToPosition(0)
81 }
82 settingsViewModel.shouldReloadSettingsList.observe(viewLifecycleOwner) {
83 if (it) {
84 settingsViewModel.setShouldReloadSettingsList(false)
85 search()
86 }
87 }
88
89 search()
90
91 setInsets()
92 }
93
94 override fun onSaveInstanceState(outState: Bundle) {
95 super.onSaveInstanceState(outState)
96 outState.putString(SEARCH_TEXT, binding.searchText.text.toString())
97 }
98
99 private fun search() {
100 val searchTerm = binding.searchText.text.toString().lowercase()
101 binding.clearButton.visibility =
102 if (searchTerm.isEmpty()) View.INVISIBLE else View.VISIBLE
103 if (searchTerm.isEmpty()) {
104 binding.noResultsView.visibility = View.VISIBLE
105 settingsAdapter?.submitList(emptyList())
106 return
107 }
108
109 val baseList = SettingsItem.settingsItems
110 val similarityAlgorithm = if (searchTerm.length > 2) Cosine() else Cosine(1)
111 val sortedList: List<SettingsItem> = baseList.mapNotNull { item ->
112 val title = getString(item.value.nameId).lowercase()
113 val similarity = similarityAlgorithm.similarity(searchTerm, title)
114 if (similarity > 0.08) {
115 Pair(similarity, item)
116 } else {
117 null
118 }
119 }.sortedByDescending { it.first }.mapNotNull {
120 val item = it.second.value
121 val pairedSettingKey = item.setting.pairedSettingKey
122 val optionalSetting: SettingsItem? = if (pairedSettingKey.isNotEmpty()) {
123 val pairedSettingValue = NativeConfig.getBoolean(pairedSettingKey, false)
124 if (pairedSettingValue) it.second.value else null
125 } else {
126 it.second.value
127 }
128 optionalSetting
129 }
130 settingsAdapter?.submitList(sortedList)
131 binding.noResultsView.visibility =
132 if (sortedList.isEmpty()) View.VISIBLE else View.INVISIBLE
133 }
134
135 private fun focusSearch() {
136 binding.searchText.requestFocus()
137 val imm = requireActivity()
138 .getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager?
139 imm?.showSoftInput(binding.searchText, InputMethodManager.SHOW_IMPLICIT)
140 }
141
142 private fun setInsets() =
143 ViewCompat.setOnApplyWindowInsetsListener(
144 binding.root
145 ) { _: View, windowInsets: WindowInsetsCompat ->
146 val extraListSpacing = resources.getDimensionPixelSize(R.dimen.spacing_med)
147 val sideMargin = resources.getDimensionPixelSize(R.dimen.spacing_medlarge)
148 val topMargin = resources.getDimensionPixelSize(R.dimen.spacing_chip)
149
150 val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
151 val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
152
153 val leftInsets = barInsets.left + cutoutInsets.left
154 val rightInsets = barInsets.right + cutoutInsets.right
155
156 binding.settingsList.updatePadding(bottom = barInsets.bottom + extraListSpacing)
157 binding.frameSearch.updatePadding(
158 left = leftInsets + sideMargin,
159 top = barInsets.top + topMargin,
160 right = rightInsets + sideMargin
161 )
162 binding.noResultsView.updatePadding(
163 left = leftInsets,
164 right = rightInsets,
165 bottom = barInsets.bottom
166 )
167
168 val mlpSettingsList = binding.settingsList.layoutParams as ViewGroup.MarginLayoutParams
169 mlpSettingsList.leftMargin = leftInsets + sideMargin
170 mlpSettingsList.rightMargin = rightInsets + sideMargin
171 binding.settingsList.layoutParams = mlpSettingsList
172
173 val mlpDivider = binding.divider.layoutParams as ViewGroup.MarginLayoutParams
174 mlpDivider.leftMargin = leftInsets + sideMargin
175 mlpDivider.rightMargin = rightInsets + sideMargin
176 binding.divider.layoutParams = mlpDivider
177
178 windowInsets
179 }
180
181 companion object {
182 const val SEARCH_TEXT = "SearchText"
183 }
184}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt
new file mode 100644
index 000000000..d16d15fa6
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt
@@ -0,0 +1,96 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.model
5
6import androidx.lifecycle.LiveData
7import androidx.lifecycle.MutableLiveData
8import androidx.lifecycle.SavedStateHandle
9import androidx.lifecycle.ViewModel
10import org.yuzu.yuzu_emu.R
11import org.yuzu.yuzu_emu.YuzuApplication
12import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
13
14class SettingsViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() {
15 var game: Game? = null
16
17 var shouldSave = false
18
19 var clickedItem: SettingsItem? = null
20
21 private val _toolbarTitle = MutableLiveData("")
22 val toolbarTitle: LiveData<String> get() = _toolbarTitle
23
24 private val _shouldRecreate = MutableLiveData(false)
25 val shouldRecreate: LiveData<Boolean> get() = _shouldRecreate
26
27 private val _shouldNavigateBack = MutableLiveData(false)
28 val shouldNavigateBack: LiveData<Boolean> get() = _shouldNavigateBack
29
30 private val _shouldShowResetSettingsDialog = MutableLiveData(false)
31 val shouldShowResetSettingsDialog: LiveData<Boolean> get() = _shouldShowResetSettingsDialog
32
33 private val _shouldReloadSettingsList = MutableLiveData(false)
34 val shouldReloadSettingsList: LiveData<Boolean> get() = _shouldReloadSettingsList
35
36 private val _isUsingSearch = MutableLiveData(false)
37 val isUsingSearch: LiveData<Boolean> get() = _isUsingSearch
38
39 val sliderProgress = savedStateHandle.getStateFlow(KEY_SLIDER_PROGRESS, -1)
40
41 val sliderTextValue = savedStateHandle.getStateFlow(KEY_SLIDER_TEXT_VALUE, "")
42
43 val adapterItemChanged = savedStateHandle.getStateFlow(KEY_ADAPTER_ITEM_CHANGED, -1)
44
45 fun setToolbarTitle(value: String) {
46 _toolbarTitle.value = value
47 }
48
49 fun setShouldRecreate(value: Boolean) {
50 _shouldRecreate.value = value
51 }
52
53 fun setShouldNavigateBack(value: Boolean) {
54 _shouldNavigateBack.value = value
55 }
56
57 fun setShouldShowResetSettingsDialog(value: Boolean) {
58 _shouldShowResetSettingsDialog.value = value
59 }
60
61 fun setShouldReloadSettingsList(value: Boolean) {
62 _shouldReloadSettingsList.value = value
63 }
64
65 fun setIsUsingSearch(value: Boolean) {
66 _isUsingSearch.value = value
67 }
68
69 fun setSliderTextValue(value: Float, units: String) {
70 savedStateHandle[KEY_SLIDER_PROGRESS] = value
71 savedStateHandle[KEY_SLIDER_TEXT_VALUE] = String.format(
72 YuzuApplication.appContext.getString(R.string.value_with_units),
73 value.toInt().toString(),
74 units
75 )
76 }
77
78 fun setSliderProgress(value: Float) {
79 savedStateHandle[KEY_SLIDER_PROGRESS] = value
80 }
81
82 fun setAdapterItemChanged(value: Int) {
83 savedStateHandle[KEY_ADAPTER_ITEM_CHANGED] = value
84 }
85
86 fun clear() {
87 game = null
88 shouldSave = false
89 }
90
91 companion object {
92 const val KEY_SLIDER_TEXT_VALUE = "SliderTextValue"
93 const val KEY_SLIDER_PROGRESS = "SliderProgress"
94 const val KEY_ADAPTER_ITEM_CHANGED = "AdapterItemChanged"
95 }
96}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
index aaf3a0ec1..7735452e5 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
@@ -33,14 +33,13 @@ import java.io.IOException
33import kotlinx.coroutines.Dispatchers 33import kotlinx.coroutines.Dispatchers
34import kotlinx.coroutines.launch 34import kotlinx.coroutines.launch
35import kotlinx.coroutines.withContext 35import kotlinx.coroutines.withContext
36import org.yuzu.yuzu_emu.HomeNavigationDirections
36import org.yuzu.yuzu_emu.NativeLibrary 37import org.yuzu.yuzu_emu.NativeLibrary
37import org.yuzu.yuzu_emu.R 38import org.yuzu.yuzu_emu.R
38import org.yuzu.yuzu_emu.activities.EmulationActivity 39import org.yuzu.yuzu_emu.activities.EmulationActivity
39import org.yuzu.yuzu_emu.databinding.ActivityMainBinding 40import org.yuzu.yuzu_emu.databinding.ActivityMainBinding
40import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding 41import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
41import org.yuzu.yuzu_emu.features.settings.model.Settings 42import org.yuzu.yuzu_emu.features.settings.model.Settings
42import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel
43import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity
44import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile 43import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
45import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment 44import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment
46import org.yuzu.yuzu_emu.fragments.LongMessageDialogFragment 45import org.yuzu.yuzu_emu.fragments.LongMessageDialogFragment
@@ -54,7 +53,6 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
54 53
55 private val homeViewModel: HomeViewModel by viewModels() 54 private val homeViewModel: HomeViewModel by viewModels()
56 private val gamesViewModel: GamesViewModel by viewModels() 55 private val gamesViewModel: GamesViewModel by viewModels()
57 private val settingsViewModel: SettingsViewModel by viewModels()
58 56
59 override var themeId: Int = 0 57 override var themeId: Int = 0
60 58
@@ -62,8 +60,6 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
62 val splashScreen = installSplashScreen() 60 val splashScreen = installSplashScreen()
63 splashScreen.setKeepOnScreenCondition { !DirectoryInitialization.areDirectoriesReady } 61 splashScreen.setKeepOnScreenCondition { !DirectoryInitialization.areDirectoriesReady }
64 62
65 settingsViewModel.settings.loadSettings()
66
67 ThemeHelper.setTheme(this) 63 ThemeHelper.setTheme(this)
68 64
69 super.onCreate(savedInstanceState) 65 super.onCreate(savedInstanceState)
@@ -109,11 +105,13 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
109 when (it.itemId) { 105 when (it.itemId) {
110 R.id.gamesFragment -> gamesViewModel.setShouldScrollToTop(true) 106 R.id.gamesFragment -> gamesViewModel.setShouldScrollToTop(true)
111 R.id.searchFragment -> gamesViewModel.setSearchFocused(true) 107 R.id.searchFragment -> gamesViewModel.setSearchFocused(true)
112 R.id.homeSettingsFragment -> SettingsActivity.launch( 108 R.id.homeSettingsFragment -> {
113 this, 109 val action = HomeNavigationDirections.actionGlobalSettingsActivity(
114 SettingsFile.FILE_NAME_CONFIG, 110 null,
115 "" 111 SettingsFile.FILE_NAME_CONFIG
116 ) 112 )
113 navHostFragment.navController.navigate(action)
114 }
117 } 115 }
118 } 116 }
119 117
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/BiMap.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/BiMap.kt
deleted file mode 100644
index 9cfda74ee..000000000
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/BiMap.kt
+++ /dev/null
@@ -1,25 +0,0 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.utils
5
6class BiMap<K, V> {
7 private val forward: MutableMap<K, V> = HashMap()
8 private val backward: MutableMap<V, K> = HashMap()
9
10 @Synchronized
11 fun add(key: K, value: V) {
12 forward[key] = value
13 backward[value] = key
14 }
15
16 @Synchronized
17 fun getForward(key: K): V? {
18 return forward[key]
19 }
20
21 @Synchronized
22 fun getBackward(key: V): K? {
23 return backward[key]
24 }
25}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt
index 2ee63697e..3c9f6bad0 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt
@@ -3,18 +3,18 @@
3 3
4package org.yuzu.yuzu_emu.utils 4package org.yuzu.yuzu_emu.utils
5 5
6import android.content.Context
7import java.io.IOException 6import java.io.IOException
8import org.yuzu.yuzu_emu.NativeLibrary 7import org.yuzu.yuzu_emu.NativeLibrary
8import org.yuzu.yuzu_emu.YuzuApplication
9 9
10object DirectoryInitialization { 10object DirectoryInitialization {
11 private var userPath: String? = null 11 private var userPath: String? = null
12 12
13 var areDirectoriesReady: Boolean = false 13 var areDirectoriesReady: Boolean = false
14 14
15 fun start(context: Context) { 15 fun start() {
16 if (!areDirectoriesReady) { 16 if (!areDirectoriesReady) {
17 initializeInternalStorage(context) 17 initializeInternalStorage()
18 NativeLibrary.initializeEmulation() 18 NativeLibrary.initializeEmulation()
19 areDirectoriesReady = true 19 areDirectoriesReady = true
20 } 20 }
@@ -26,9 +26,9 @@ object DirectoryInitialization {
26 return userPath 26 return userPath
27 } 27 }
28 28
29 private fun initializeInternalStorage(context: Context) { 29 private fun initializeInternalStorage() {
30 try { 30 try {
31 userPath = context.getExternalFilesDir(null)!!.canonicalPath 31 userPath = YuzuApplication.appContext.getExternalFilesDir(null)!!.canonicalPath
32 NativeLibrary.setAppDirectory(userPath!!) 32 NativeLibrary.setAppDirectory(userPath!!)
33 } catch (e: IOException) { 33 } catch (e: IOException) {
34 e.printStackTrace() 34 e.printStackTrace()
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt
new file mode 100644
index 000000000..9425f8b99
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt
@@ -0,0 +1,33 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.utils
5
6object NativeConfig {
7 external fun getBoolean(key: String, getDefault: Boolean): Boolean
8 external fun setBoolean(key: String, value: Boolean)
9
10 external fun getByte(key: String, getDefault: Boolean): Byte
11 external fun setByte(key: String, value: Byte)
12
13 external fun getShort(key: String, getDefault: Boolean): Short
14 external fun setShort(key: String, value: Short)
15
16 external fun getInt(key: String, getDefault: Boolean): Int
17 external fun setInt(key: String, value: Int)
18
19 external fun getFloat(key: String, getDefault: Boolean): Float
20 external fun setFloat(key: String, value: Float)
21
22 external fun getLong(key: String, getDefault: Boolean): Long
23 external fun setLong(key: String, value: Long)
24
25 external fun getString(key: String, getDefault: Boolean): String
26 external fun setString(key: String, value: String)
27
28 external fun getIsRuntimeModifiable(key: String): Boolean
29
30 external fun getConfigHeader(category: Int): String
31
32 external fun getPairedSettingKey(key: String): String
33}
diff --git a/src/android/app/src/main/jni/CMakeLists.txt b/src/android/app/src/main/jni/CMakeLists.txt
index e2ed08e9f..e15d1480b 100644
--- a/src/android/app/src/main/jni/CMakeLists.txt
+++ b/src/android/app/src/main/jni/CMakeLists.txt
@@ -14,6 +14,8 @@ add_library(yuzu-android SHARED
14 id_cache.cpp 14 id_cache.cpp
15 id_cache.h 15 id_cache.h
16 native.cpp 16 native.cpp
17 native_config.cpp
18 uisettings.cpp
17) 19)
18 20
19set_property(TARGET yuzu-android PROPERTY IMPORTED_LOCATION ${FFmpeg_LIBRARY_DIR}) 21set_property(TARGET yuzu-android PROPERTY IMPORTED_LOCATION ${FFmpeg_LIBRARY_DIR})
diff --git a/src/android/app/src/main/jni/config.cpp b/src/android/app/src/main/jni/config.cpp
index 9de9bd93e..34b425cb4 100644
--- a/src/android/app/src/main/jni/config.cpp
+++ b/src/android/app/src/main/jni/config.cpp
@@ -16,18 +16,20 @@
16#include "input_common/main.h" 16#include "input_common/main.h"
17#include "jni/config.h" 17#include "jni/config.h"
18#include "jni/default_ini.h" 18#include "jni/default_ini.h"
19#include "uisettings.h"
19 20
20namespace FS = Common::FS; 21namespace FS = Common::FS;
21 22
22Config::Config(std::optional<std::filesystem::path> config_path) 23Config::Config(const std::string& config_name, ConfigType config_type)
23 : config_loc{config_path.value_or(FS::GetYuzuPath(FS::YuzuPath::ConfigDir) / "config.ini")}, 24 : type(config_type), global{config_type == ConfigType::GlobalConfig} {
24 config{std::make_unique<INIReader>(FS::PathToUTF8String(config_loc))} { 25 Initialize(config_name);
25 Reload();
26} 26}
27 27
28Config::~Config() = default; 28Config::~Config() = default;
29 29
30bool Config::LoadINI(const std::string& default_contents, bool retry) { 30bool Config::LoadINI(const std::string& default_contents, bool retry) {
31 void(FS::CreateParentDir(config_loc));
32 config = std::make_unique<INIReader>(FS::PathToUTF8String(config_loc));
31 const auto config_loc_str = FS::PathToUTF8String(config_loc); 33 const auto config_loc_str = FS::PathToUTF8String(config_loc);
32 if (config->ParseError() < 0) { 34 if (config->ParseError() < 0) {
33 if (retry) { 35 if (retry) {
@@ -301,9 +303,28 @@ void Config::ReadValues() {
301 303
302 // Network 304 // Network
303 ReadSetting("Network", Settings::values.network_interface); 305 ReadSetting("Network", Settings::values.network_interface);
306
307 // Android
308 ReadSetting("Android", AndroidSettings::values.picture_in_picture);
309 ReadSetting("Android", AndroidSettings::values.screen_layout);
304} 310}
305 311
306void Config::Reload() { 312void Config::Initialize(const std::string& config_name) {
313 const auto fs_config_loc = FS::GetYuzuPath(FS::YuzuPath::ConfigDir);
314 const auto config_file = fmt::format("{}.ini", config_name);
315
316 switch (type) {
317 case ConfigType::GlobalConfig:
318 config_loc = FS::PathToUTF8String(fs_config_loc / config_file);
319 break;
320 case ConfigType::PerGameConfig:
321 config_loc = FS::PathToUTF8String(fs_config_loc / "custom" / FS::ToU8String(config_file));
322 break;
323 case ConfigType::InputProfile:
324 config_loc = FS::PathToUTF8String(fs_config_loc / "input" / config_file);
325 LoadINI(DefaultINI::android_config_file);
326 return;
327 }
307 LoadINI(DefaultINI::android_config_file); 328 LoadINI(DefaultINI::android_config_file);
308 ReadValues(); 329 ReadValues();
309} 330}
diff --git a/src/android/app/src/main/jni/config.h b/src/android/app/src/main/jni/config.h
index 0d7d6e94d..e1e8f47ed 100644
--- a/src/android/app/src/main/jni/config.h
+++ b/src/android/app/src/main/jni/config.h
@@ -13,25 +13,35 @@
13class INIReader; 13class INIReader;
14 14
15class Config { 15class Config {
16 std::filesystem::path config_loc;
17 std::unique_ptr<INIReader> config;
18
19 bool LoadINI(const std::string& default_contents = "", bool retry = true); 16 bool LoadINI(const std::string& default_contents = "", bool retry = true);
20 void ReadValues();
21 17
22public: 18public:
23 explicit Config(std::optional<std::filesystem::path> config_path = std::nullopt); 19 enum class ConfigType {
20 GlobalConfig,
21 PerGameConfig,
22 InputProfile,
23 };
24
25 explicit Config(const std::string& config_name = "config",
26 ConfigType config_type = ConfigType::GlobalConfig);
24 ~Config(); 27 ~Config();
25 28
26 void Reload(); 29 void Initialize(const std::string& config_name);
27 30
28private: 31private:
29 /** 32 /**
30 * Applies a value read from the sdl2_config to a Setting. 33 * Applies a value read from the config to a Setting.
31 * 34 *
32 * @param group The name of the INI group 35 * @param group The name of the INI group
33 * @param setting The yuzu setting to modify 36 * @param setting The yuzu setting to modify
34 */ 37 */
35 template <typename Type, bool ranged> 38 template <typename Type, bool ranged>
36 void ReadSetting(const std::string& group, Settings::Setting<Type, ranged>& setting); 39 void ReadSetting(const std::string& group, Settings::Setting<Type, ranged>& setting);
40
41 void ReadValues();
42
43 const ConfigType type;
44 std::unique_ptr<INIReader> config;
45 std::string config_loc;
46 const bool global;
37}; 47};
diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp
index 7e17833a0..b2adfdeda 100644
--- a/src/android/app/src/main/jni/native.cpp
+++ b/src/android/app/src/main/jni/native.cpp
@@ -824,34 +824,6 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_reloadSettings(JNIEnv* env, jclass cl
824 Config{}; 824 Config{};
825} 825}
826 826
827jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getUserSetting(JNIEnv* env, jclass clazz,
828 jstring j_game_id, jstring j_section,
829 jstring j_key) {
830 std::string_view game_id = env->GetStringUTFChars(j_game_id, 0);
831 std::string_view section = env->GetStringUTFChars(j_section, 0);
832 std::string_view key = env->GetStringUTFChars(j_key, 0);
833
834 env->ReleaseStringUTFChars(j_game_id, game_id.data());
835 env->ReleaseStringUTFChars(j_section, section.data());
836 env->ReleaseStringUTFChars(j_key, key.data());
837
838 return env->NewStringUTF("");
839}
840
841void Java_org_yuzu_yuzu_1emu_NativeLibrary_setUserSetting(JNIEnv* env, jclass clazz,
842 jstring j_game_id, jstring j_section,
843 jstring j_key, jstring j_value) {
844 std::string_view game_id = env->GetStringUTFChars(j_game_id, 0);
845 std::string_view section = env->GetStringUTFChars(j_section, 0);
846 std::string_view key = env->GetStringUTFChars(j_key, 0);
847 std::string_view value = env->GetStringUTFChars(j_value, 0);
848
849 env->ReleaseStringUTFChars(j_game_id, game_id.data());
850 env->ReleaseStringUTFChars(j_section, section.data());
851 env->ReleaseStringUTFChars(j_key, key.data());
852 env->ReleaseStringUTFChars(j_value, value.data());
853}
854
855void Java_org_yuzu_yuzu_1emu_NativeLibrary_initGameIni(JNIEnv* env, jclass clazz, 827void Java_org_yuzu_yuzu_1emu_NativeLibrary_initGameIni(JNIEnv* env, jclass clazz,
856 jstring j_game_id) { 828 jstring j_game_id) {
857 std::string_view game_id = env->GetStringUTFChars(j_game_id, 0); 829 std::string_view game_id = env->GetStringUTFChars(j_game_id, 0);
diff --git a/src/android/app/src/main/jni/native_config.cpp b/src/android/app/src/main/jni/native_config.cpp
new file mode 100644
index 000000000..8a704960c
--- /dev/null
+++ b/src/android/app/src/main/jni/native_config.cpp
@@ -0,0 +1,237 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <string>
5
6#include <jni.h>
7
8#include "common/logging/log.h"
9#include "common/settings.h"
10#include "jni/android_common/android_common.h"
11#include "jni/config.h"
12#include "uisettings.h"
13
14template <typename T>
15Settings::Setting<T>* getSetting(JNIEnv* env, jstring jkey) {
16 auto key = GetJString(env, jkey);
17 auto basicSetting = Settings::values.linkage.by_key[key];
18 auto basicAndroidSetting = AndroidSettings::values.linkage.by_key[key];
19 if (basicSetting != 0) {
20 return static_cast<Settings::Setting<T>*>(basicSetting);
21 }
22 if (basicAndroidSetting != 0) {
23 return static_cast<Settings::Setting<T>*>(basicAndroidSetting);
24 }
25 LOG_ERROR(Frontend, "[Android Native] Could not find setting - {}", key);
26 return nullptr;
27}
28
29extern "C" {
30
31jboolean Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getBoolean(JNIEnv* env, jobject obj,
32 jstring jkey, jboolean getDefault) {
33 auto setting = getSetting<bool>(env, jkey);
34 if (setting == nullptr) {
35 return false;
36 }
37 setting->SetGlobal(true);
38
39 if (static_cast<bool>(getDefault)) {
40 return setting->GetDefault();
41 }
42
43 return setting->GetValue();
44}
45
46void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setBoolean(JNIEnv* env, jobject obj, jstring jkey,
47 jboolean value) {
48 auto setting = getSetting<bool>(env, jkey);
49 if (setting == nullptr) {
50 return;
51 }
52 setting->SetGlobal(true);
53 setting->SetValue(static_cast<bool>(value));
54}
55
56jbyte Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getByte(JNIEnv* env, jobject obj, jstring jkey,
57 jboolean getDefault) {
58 auto setting = getSetting<u8>(env, jkey);
59 if (setting == nullptr) {
60 return -1;
61 }
62 setting->SetGlobal(true);
63
64 if (static_cast<bool>(getDefault)) {
65 return setting->GetDefault();
66 }
67
68 return setting->GetValue();
69}
70
71void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setByte(JNIEnv* env, jobject obj, jstring jkey,
72 jbyte value) {
73 auto setting = getSetting<u8>(env, jkey);
74 if (setting == nullptr) {
75 return;
76 }
77 setting->SetGlobal(true);
78 setting->SetValue(value);
79}
80
81jshort Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getShort(JNIEnv* env, jobject obj, jstring jkey,
82 jboolean getDefault) {
83 auto setting = getSetting<u16>(env, jkey);
84 if (setting == nullptr) {
85 return -1;
86 }
87 setting->SetGlobal(true);
88
89 if (static_cast<bool>(getDefault)) {
90 return setting->GetDefault();
91 }
92
93 return setting->GetValue();
94}
95
96void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setShort(JNIEnv* env, jobject obj, jstring jkey,
97 jshort value) {
98 auto setting = getSetting<u16>(env, jkey);
99 if (setting == nullptr) {
100 return;
101 }
102 setting->SetGlobal(true);
103 setting->SetValue(value);
104}
105
106jint Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getInt(JNIEnv* env, jobject obj, jstring jkey,
107 jboolean getDefault) {
108 auto setting = getSetting<int>(env, jkey);
109 if (setting == nullptr) {
110 return -1;
111 }
112 setting->SetGlobal(true);
113
114 if (static_cast<bool>(getDefault)) {
115 return setting->GetDefault();
116 }
117
118 return setting->GetValue();
119}
120
121void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setInt(JNIEnv* env, jobject obj, jstring jkey,
122 jint value) {
123 auto setting = getSetting<int>(env, jkey);
124 if (setting == nullptr) {
125 return;
126 }
127 setting->SetGlobal(true);
128 setting->SetValue(value);
129}
130
131jfloat Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getFloat(JNIEnv* env, jobject obj, jstring jkey,
132 jboolean getDefault) {
133 auto setting = getSetting<float>(env, jkey);
134 if (setting == nullptr) {
135 return -1;
136 }
137 setting->SetGlobal(true);
138
139 if (static_cast<bool>(getDefault)) {
140 return setting->GetDefault();
141 }
142
143 return setting->GetValue();
144}
145
146void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setFloat(JNIEnv* env, jobject obj, jstring jkey,
147 jfloat value) {
148 auto setting = getSetting<float>(env, jkey);
149 if (setting == nullptr) {
150 return;
151 }
152 setting->SetGlobal(true);
153 setting->SetValue(value);
154}
155
156jlong Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getLong(JNIEnv* env, jobject obj, jstring jkey,
157 jboolean getDefault) {
158 auto setting = getSetting<long>(env, jkey);
159 if (setting == nullptr) {
160 return -1;
161 }
162 setting->SetGlobal(true);
163
164 if (static_cast<bool>(getDefault)) {
165 return setting->GetDefault();
166 }
167
168 return setting->GetValue();
169}
170
171void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setLong(JNIEnv* env, jobject obj, jstring jkey,
172 jlong value) {
173 auto setting = getSetting<long>(env, jkey);
174 if (setting == nullptr) {
175 return;
176 }
177 setting->SetGlobal(true);
178 setting->SetValue(value);
179}
180
181jstring Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getString(JNIEnv* env, jobject obj, jstring jkey,
182 jboolean getDefault) {
183 auto setting = getSetting<std::string>(env, jkey);
184 if (setting == nullptr) {
185 return ToJString(env, "");
186 }
187 setting->SetGlobal(true);
188
189 if (static_cast<bool>(getDefault)) {
190 return ToJString(env, setting->GetDefault());
191 }
192
193 return ToJString(env, setting->GetValue());
194}
195
196void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setString(JNIEnv* env, jobject obj, jstring jkey,
197 jstring value) {
198 auto setting = getSetting<std::string>(env, jkey);
199 if (setting == nullptr) {
200 return;
201 }
202
203 setting->SetGlobal(true);
204 setting->SetValue(GetJString(env, value));
205}
206
207jboolean Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getIsRuntimeModifiable(JNIEnv* env, jobject obj,
208 jstring jkey) {
209 auto key = GetJString(env, jkey);
210 auto setting = Settings::values.linkage.by_key[key];
211 if (setting != 0) {
212 return setting->RuntimeModfiable();
213 }
214 LOG_ERROR(Frontend, "[Android Native] Could not find setting - {}", key);
215 return true;
216}
217
218jstring Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getConfigHeader(JNIEnv* env, jobject obj,
219 jint jcategory) {
220 auto category = static_cast<Settings::Category>(jcategory);
221 return ToJString(env, Settings::TranslateCategory(category));
222}
223
224jstring Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getPairedSettingKey(JNIEnv* env, jobject obj,
225 jstring jkey) {
226 auto setting = getSetting<std::string>(env, jkey);
227 if (setting == nullptr) {
228 return ToJString(env, "");
229 }
230 if (setting->PairedSetting() == nullptr) {
231 return ToJString(env, "");
232 }
233
234 return ToJString(env, setting->PairedSetting()->GetLabel());
235}
236
237} // extern "C"
diff --git a/src/android/app/src/main/jni/uisettings.cpp b/src/android/app/src/main/jni/uisettings.cpp
new file mode 100644
index 000000000..f2f0bad50
--- /dev/null
+++ b/src/android/app/src/main/jni/uisettings.cpp
@@ -0,0 +1,10 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "uisettings.h"
5
6namespace AndroidSettings {
7
8Values values;
9
10} // namespace AndroidSettings
diff --git a/src/android/app/src/main/jni/uisettings.h b/src/android/app/src/main/jni/uisettings.h
new file mode 100644
index 000000000..494654af7
--- /dev/null
+++ b/src/android/app/src/main/jni/uisettings.h
@@ -0,0 +1,29 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <common/settings_common.h>
7#include "common/common_types.h"
8#include "common/settings_setting.h"
9
10namespace AndroidSettings {
11
12struct Values {
13 Settings::Linkage linkage;
14
15 // Android
16 Settings::Setting<bool> picture_in_picture{linkage, true, "picture_in_picture",
17 Settings::Category::Android};
18 Settings::Setting<s32> screen_layout{linkage,
19 5,
20 "screen_layout",
21 Settings::Category::Android,
22 Settings::Specialization::Default,
23 true,
24 true};
25};
26
27extern Values values;
28
29} // namespace AndroidSettings
diff --git a/src/android/app/src/main/res/anim-ldrtl/anim_pop_settings_fragment_out.xml b/src/android/app/src/main/res/anim-ldrtl/anim_pop_settings_fragment_out.xml
deleted file mode 100644
index 9f49c133a..000000000
--- a/src/android/app/src/main/res/anim-ldrtl/anim_pop_settings_fragment_out.xml
+++ /dev/null
@@ -1,16 +0,0 @@
1<?xml version="1.0" encoding="utf-8"?>
2<set xmlns:android="http://schemas.android.com/apk/res/android">
3
4 <alpha
5 android:duration="125"
6 android:interpolator="@android:anim/decelerate_interpolator"
7 android:fromAlpha="1"
8 android:toAlpha="0" />
9
10 <translate
11 android:duration="125"
12 android:interpolator="@android:anim/decelerate_interpolator"
13 android:fromXDelta="0"
14 android:toXDelta="-75" />
15
16</set>
diff --git a/src/android/app/src/main/res/anim-ldrtl/anim_settings_fragment_in.xml b/src/android/app/src/main/res/anim-ldrtl/anim_settings_fragment_in.xml
deleted file mode 100644
index 82fd719db..000000000
--- a/src/android/app/src/main/res/anim-ldrtl/anim_settings_fragment_in.xml
+++ /dev/null
@@ -1,16 +0,0 @@
1<?xml version="1.0" encoding="utf-8"?>
2<set xmlns:android="http://schemas.android.com/apk/res/android">
3
4 <alpha
5 android:duration="@android:integer/config_shortAnimTime"
6 android:interpolator="@android:anim/decelerate_interpolator"
7 android:fromAlpha="0"
8 android:toAlpha="1" />
9
10 <translate
11 android:duration="@android:integer/config_shortAnimTime"
12 android:interpolator="@android:anim/decelerate_interpolator"
13 android:fromXDelta="-200"
14 android:toXDelta="0" />
15
16</set>
diff --git a/src/android/app/src/main/res/anim/anim_pop_settings_fragment_out.xml b/src/android/app/src/main/res/anim/anim_pop_settings_fragment_out.xml
deleted file mode 100644
index 5892128f1..000000000
--- a/src/android/app/src/main/res/anim/anim_pop_settings_fragment_out.xml
+++ /dev/null
@@ -1,16 +0,0 @@
1<?xml version="1.0" encoding="utf-8"?>
2<set xmlns:android="http://schemas.android.com/apk/res/android">
3
4 <alpha
5 android:duration="125"
6 android:interpolator="@android:anim/decelerate_interpolator"
7 android:fromAlpha="1"
8 android:toAlpha="0" />
9
10 <translate
11 android:duration="125"
12 android:interpolator="@android:anim/decelerate_interpolator"
13 android:fromXDelta="0"
14 android:toXDelta="75" />
15
16</set>
diff --git a/src/android/app/src/main/res/anim/anim_settings_fragment_in.xml b/src/android/app/src/main/res/anim/anim_settings_fragment_in.xml
deleted file mode 100644
index 98e0cf8bd..000000000
--- a/src/android/app/src/main/res/anim/anim_settings_fragment_in.xml
+++ /dev/null
@@ -1,16 +0,0 @@
1<?xml version="1.0" encoding="utf-8"?>
2<set xmlns:android="http://schemas.android.com/apk/res/android">
3
4 <alpha
5 android:duration="@android:integer/config_shortAnimTime"
6 android:interpolator="@android:anim/decelerate_interpolator"
7 android:fromAlpha="0"
8 android:toAlpha="1" />
9
10 <translate
11 android:duration="@android:integer/config_shortAnimTime"
12 android:interpolator="@android:anim/decelerate_interpolator"
13 android:fromXDelta="200"
14 android:toXDelta="0" />
15
16</set>
diff --git a/src/android/app/src/main/res/anim/anim_settings_fragment_out.xml b/src/android/app/src/main/res/anim/anim_settings_fragment_out.xml
deleted file mode 100644
index 77a40a4d1..000000000
--- a/src/android/app/src/main/res/anim/anim_settings_fragment_out.xml
+++ /dev/null
@@ -1,10 +0,0 @@
1<?xml version="1.0" encoding="utf-8"?>
2<set xmlns:android="http://schemas.android.com/apk/res/android">
3
4 <alpha
5 android:duration="@android:integer/config_shortAnimTime"
6 android:interpolator="@android:anim/decelerate_interpolator"
7 android:fromAlpha="1"
8 android:toAlpha="0" />
9
10</set>
diff --git a/src/android/app/src/main/res/animator/menu_slide_in_from_start.xml b/src/android/app/src/main/res/animator/menu_slide_in_from_start.xml
deleted file mode 100644
index 4612aee13..000000000
--- a/src/android/app/src/main/res/animator/menu_slide_in_from_start.xml
+++ /dev/null
@@ -1,20 +0,0 @@
1<?xml version="1.0" encoding="utf-8"?>
2<set xmlns:android="http://schemas.android.com/apk/res/android">
3
4 <objectAnimator
5 android:propertyName="translationX"
6 android:valueType="floatType"
7 android:valueFrom="-1280dp"
8 android:valueTo="0"
9 android:interpolator="@android:interpolator/decelerate_quad"
10 android:duration="300"/>
11
12 <objectAnimator
13 android:propertyName="alpha"
14 android:valueType="floatType"
15 android:valueFrom="0"
16 android:valueTo="1"
17 android:interpolator="@android:interpolator/accelerate_quad"
18 android:duration="300"/>
19
20</set> \ No newline at end of file
diff --git a/src/android/app/src/main/res/animator/menu_slide_out_to_start.xml b/src/android/app/src/main/res/animator/menu_slide_out_to_start.xml
deleted file mode 100644
index c00478946..000000000
--- a/src/android/app/src/main/res/animator/menu_slide_out_to_start.xml
+++ /dev/null
@@ -1,21 +0,0 @@
1<?xml version="1.0" encoding="utf-8"?>
2<set xmlns:android="http://schemas.android.com/apk/res/android">
3
4 <!-- This animation is used ONLY when a submenu is replaced. -->
5 <objectAnimator
6 android:propertyName="translationX"
7 android:valueType="floatType"
8 android:valueFrom="0"
9 android:valueTo="-1280dp"
10 android:interpolator="@android:interpolator/decelerate_quad"
11 android:duration="200"/>
12
13 <objectAnimator
14 android:propertyName="alpha"
15 android:valueType="floatType"
16 android:valueFrom="1"
17 android:valueTo="0"
18 android:interpolator="@android:interpolator/decelerate_quad"
19 android:duration="200"/>
20
21</set> \ No newline at end of file
diff --git a/src/android/app/src/main/res/layout/activity_settings.xml b/src/android/app/src/main/res/layout/activity_settings.xml
index 14ae83b04..8a026a30a 100644
--- a/src/android/app/src/main/res/layout/activity_settings.xml
+++ b/src/android/app/src/main/res/layout/activity_settings.xml
@@ -1,42 +1,24 @@
1<?xml version="1.0" encoding="utf-8"?> 1<?xml version="1.0" encoding="utf-8"?>
2<androidx.coordinatorlayout.widget.CoordinatorLayout 2<androidx.constraintlayout.widget.ConstraintLayout
3 android:id="@+id/coordinator_main"
4 xmlns:android="http://schemas.android.com/apk/res/android" 3 xmlns:android="http://schemas.android.com/apk/res/android"
5 xmlns:app="http://schemas.android.com/apk/res-auto" 4 xmlns:app="http://schemas.android.com/apk/res-auto"
5 xmlns:tools="http://schemas.android.com/tools"
6 android:id="@+id/constraint_settings"
6 android:layout_width="match_parent" 7 android:layout_width="match_parent"
7 android:layout_height="match_parent" 8 android:layout_height="match_parent"
8 android:background="?attr/colorSurface"> 9 android:background="?attr/colorSurface">
9 10
10 <com.google.android.material.appbar.AppBarLayout 11 <androidx.fragment.app.FragmentContainerView
11 android:id="@+id/appbar_settings" 12 android:id="@+id/fragment_container"
12 android:layout_width="match_parent" 13 android:name="androidx.navigation.fragment.NavHostFragment"
13 android:layout_height="wrap_content" 14 android:layout_width="0dp"
14 android:fitsSystemWindows="true" 15 android:layout_height="0dp"
15 app:elevation="0dp"> 16 app:defaultNavHost="true"
16 17 app:layout_constraintBottom_toBottomOf="parent"
17 <com.google.android.material.appbar.CollapsingToolbarLayout 18 app:layout_constraintLeft_toLeftOf="parent"
18 style="?attr/collapsingToolbarLayoutMediumStyle" 19 app:layout_constraintRight_toRightOf="parent"
19 android:id="@+id/toolbar_settings_layout" 20 app:layout_constraintTop_toTopOf="parent"
20 android:layout_width="match_parent" 21 tools:layout="@layout/fragment_settings" />
21 android:layout_height="?attr/collapsingToolbarLayoutMediumSize"
22 app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">
23
24 <com.google.android.material.appbar.MaterialToolbar
25 android:id="@+id/toolbar_settings"
26 android:layout_width="match_parent"
27 android:layout_height="?attr/actionBarSize"
28 app:layout_collapseMode="pin" />
29
30 </com.google.android.material.appbar.CollapsingToolbarLayout>
31
32 </com.google.android.material.appbar.AppBarLayout>
33
34 <FrameLayout
35 android:id="@+id/frame_content"
36 android:layout_width="match_parent"
37 android:layout_height="match_parent"
38 android:layout_marginHorizontal="12dp"
39 app:layout_behavior="@string/appbar_scrolling_view_behavior" />
40 22
41 <View 23 <View
42 android:id="@+id/navigation_bar_shade" 24 android:id="@+id/navigation_bar_shade"
@@ -45,6 +27,8 @@
45 android:background="@android:color/transparent" 27 android:background="@android:color/transparent"
46 android:clickable="false" 28 android:clickable="false"
47 android:focusable="false" 29 android:focusable="false"
48 android:layout_gravity="bottom|center_horizontal" /> 30 app:layout_constraintBottom_toBottomOf="parent"
31 app:layout_constraintEnd_toEndOf="parent"
32 app:layout_constraintStart_toStartOf="parent" />
49 33
50</androidx.coordinatorlayout.widget.CoordinatorLayout> 34</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/src/android/app/src/main/res/layout/fragment_settings.xml b/src/android/app/src/main/res/layout/fragment_settings.xml
index 167720347..ebedbf1ec 100644
--- a/src/android/app/src/main/res/layout/fragment_settings.xml
+++ b/src/android/app/src/main/res/layout/fragment_settings.xml
@@ -1,14 +1,41 @@
1<?xml version="1.0" encoding="utf-8"?> 1<?xml version="1.0" encoding="utf-8"?>
2<FrameLayout 2<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
3 xmlns:android="http://schemas.android.com/apk/res/android" 3 xmlns:app="http://schemas.android.com/apk/res-auto"
4 android:id="@+id/coordinator_main"
4 android:layout_width="match_parent" 5 android:layout_width="match_parent"
5 android:layout_height="match_parent"> 6 android:layout_height="match_parent"
7 android:background="?attr/colorSurface">
8
9 <com.google.android.material.appbar.AppBarLayout
10 android:id="@+id/appbar_settings"
11 android:layout_width="match_parent"
12 android:layout_height="wrap_content"
13 android:fitsSystemWindows="true"
14 app:elevation="0dp">
15
16 <com.google.android.material.appbar.CollapsingToolbarLayout
17 android:id="@+id/toolbar_settings_layout"
18 style="?attr/collapsingToolbarLayoutMediumStyle"
19 android:layout_width="match_parent"
20 android:layout_height="?attr/collapsingToolbarLayoutMediumSize"
21 app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">
22
23 <com.google.android.material.appbar.MaterialToolbar
24 android:id="@+id/toolbar_settings"
25 android:layout_width="match_parent"
26 android:layout_height="?attr/actionBarSize"
27 app:layout_collapseMode="pin"
28 app:navigationIcon="@drawable/ic_back" />
29
30 </com.google.android.material.appbar.CollapsingToolbarLayout>
31
32 </com.google.android.material.appbar.AppBarLayout>
6 33
7 <androidx.recyclerview.widget.RecyclerView 34 <androidx.recyclerview.widget.RecyclerView
8 android:id="@+id/list_settings" 35 android:id="@+id/list_settings"
9 android:layout_width="match_parent" 36 android:layout_width="match_parent"
10 android:layout_height="match_parent" 37 android:layout_height="match_parent"
11 android:background="?attr/colorSurface" 38 android:clipToPadding="false"
12 android:clipToPadding="false" /> 39 app:layout_behavior="@string/appbar_scrolling_view_behavior" />
13 40
14</FrameLayout> 41</androidx.coordinatorlayout.widget.CoordinatorLayout>
diff --git a/src/android/app/src/main/res/layout/fragment_settings_search.xml b/src/android/app/src/main/res/layout/fragment_settings_search.xml
new file mode 100644
index 000000000..c779ed2fc
--- /dev/null
+++ b/src/android/app/src/main/res/layout/fragment_settings_search.xml
@@ -0,0 +1,120 @@
1<?xml version="1.0" encoding="utf-8"?>
2<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
3 xmlns:app="http://schemas.android.com/apk/res-auto"
4 xmlns:tools="http://schemas.android.com/tools"
5 android:layout_width="match_parent"
6 android:layout_height="match_parent">
7
8 <RelativeLayout
9 android:id="@+id/relativeLayout"
10 android:layout_width="0dp"
11 android:layout_height="0dp"
12 app:layout_constraintBottom_toBottomOf="parent"
13 app:layout_constraintEnd_toEndOf="parent"
14 app:layout_constraintStart_toStartOf="parent"
15 app:layout_constraintTop_toBottomOf="@+id/divider">
16
17 <LinearLayout
18 android:id="@+id/no_results_view"
19 android:layout_width="match_parent"
20 android:layout_height="match_parent"
21 android:gravity="center"
22 android:orientation="vertical">
23
24 <ImageView
25 android:id="@+id/icon_no_results"
26 android:layout_width="match_parent"
27 android:layout_height="80dp"
28 android:src="@drawable/ic_search" />
29
30 <com.google.android.material.textview.MaterialTextView
31 android:id="@+id/notice_text"
32 style="@style/TextAppearance.Material3.TitleLarge"
33 android:layout_width="match_parent"
34 android:layout_height="wrap_content"
35 android:gravity="center"
36 android:paddingTop="8dp"
37 android:text="@string/search_settings"
38 tools:visibility="visible" />
39
40 </LinearLayout>
41
42 <androidx.recyclerview.widget.RecyclerView
43 android:id="@+id/settings_list"
44 android:layout_width="match_parent"
45 android:layout_height="match_parent"
46 android:clipToPadding="false" />
47
48 </RelativeLayout>
49
50 <FrameLayout
51 android:id="@+id/frame_search"
52 android:layout_width="match_parent"
53 android:layout_height="wrap_content"
54 android:clipToPadding="false"
55 app:layout_constraintEnd_toEndOf="parent"
56 app:layout_constraintStart_toStartOf="parent"
57 app:layout_constraintTop_toTopOf="parent">
58
59 <com.google.android.material.card.MaterialCardView
60 android:id="@+id/search_background"
61 style="?attr/materialCardViewFilledStyle"
62 android:layout_width="match_parent"
63 android:layout_height="56dp"
64 app:cardCornerRadius="28dp">
65
66 <LinearLayout
67 android:id="@+id/search_container"
68 android:layout_width="match_parent"
69 android:layout_height="match_parent"
70 android:layout_marginEnd="56dp"
71 android:orientation="horizontal">
72
73 <Button
74 android:id="@+id/back_button"
75 style="?attr/materialIconButtonFilledTonalStyle"
76 android:layout_width="wrap_content"
77 android:layout_height="wrap_content"
78 android:layout_gravity="center_vertical"
79 android:layout_marginStart="8dp"
80 app:backgroundTint="@android:color/transparent"
81 app:icon="@drawable/ic_back" />
82
83 <EditText
84 android:id="@+id/search_text"
85 android:layout_width="match_parent"
86 android:layout_height="match_parent"
87 android:background="@android:color/transparent"
88 android:hint="@string/search_settings"
89 android:imeOptions="flagNoFullscreen"
90 android:inputType="text"
91 android:maxLines="1" />
92
93 </LinearLayout>
94
95 <Button
96 android:id="@+id/clear_button"
97 style="?attr/materialIconButtonFilledTonalStyle"
98 android:layout_width="wrap_content"
99 android:layout_height="wrap_content"
100 android:layout_gravity="center_vertical|end"
101 android:layout_marginEnd="8dp"
102 android:visibility="invisible"
103 app:backgroundTint="@android:color/transparent"
104 app:icon="@drawable/ic_clear"
105 tools:visibility="visible" />
106
107 </com.google.android.material.card.MaterialCardView>
108
109 </FrameLayout>
110
111 <com.google.android.material.divider.MaterialDivider
112 android:id="@+id/divider"
113 android:layout_width="match_parent"
114 android:layout_height="wrap_content"
115 android:layout_marginTop="20dp"
116 app:layout_constraintEnd_toEndOf="parent"
117 app:layout_constraintStart_toStartOf="parent"
118 app:layout_constraintTop_toBottomOf="@+id/frame_search" />
119
120</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/src/android/app/src/main/res/menu/menu_settings.xml b/src/android/app/src/main/res/menu/menu_settings.xml
index 1fe7aa6d4..21501a471 100644
--- a/src/android/app/src/main/res/menu/menu_settings.xml
+++ b/src/android/app/src/main/res/menu/menu_settings.xml
@@ -1,2 +1,11 @@
1<?xml version="1.0" encoding="utf-8"?> 1<?xml version="1.0" encoding="utf-8"?>
2<menu /> \ No newline at end of file 2<menu xmlns:android="http://schemas.android.com/apk/res/android"
3 xmlns:app="http://schemas.android.com/apk/res-auto">
4
5 <item
6 android:id="@+id/action_search"
7 android:icon="@drawable/ic_search"
8 android:title="@string/home_search"
9 app:showAsAction="always" />
10
11</menu>
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 cd1d36a12..c7be37f9b 100644
--- a/src/android/app/src/main/res/navigation/emulation_navigation.xml
+++ b/src/android/app/src/main/res/navigation/emulation_navigation.xml
@@ -17,4 +17,21 @@
17 android:defaultValue="@null" /> 17 android:defaultValue="@null" />
18 </fragment> 18 </fragment>
19 19
20 <activity
21 android:id="@+id/settingsActivity"
22 android:name="org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity"
23 android:label="SettingsActivity">
24 <argument
25 android:name="game"
26 app:argType="org.yuzu.yuzu_emu.model.Game"
27 app:nullable="true" />
28 <argument
29 android:name="menuTag"
30 app:argType="string" />
31 </activity>
32
33 <action
34 android:id="@+id/action_global_settingsActivity"
35 app:destination="@id/settingsActivity" />
36
20</navigation> 37</navigation>
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 42f987fed..2085430bf 100644
--- a/src/android/app/src/main/res/navigation/home_navigation.xml
+++ b/src/android/app/src/main/res/navigation/home_navigation.xml
@@ -72,4 +72,21 @@
72 app:destination="@id/emulationActivity" 72 app:destination="@id/emulationActivity"
73 app:launchSingleTop="true" /> 73 app:launchSingleTop="true" />
74 74
75 <activity
76 android:id="@+id/settingsActivity"
77 android:name="org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity"
78 android:label="SettingsActivity">
79 <argument
80 android:name="game"
81 app:argType="org.yuzu.yuzu_emu.model.Game"
82 app:nullable="true" />
83 <argument
84 android:name="menuTag"
85 app:argType="string" />
86 </activity>
87
88 <action
89 android:id="@+id/action_global_settingsActivity"
90 app:destination="@id/settingsActivity" />
91
75</navigation> 92</navigation>
diff --git a/src/android/app/src/main/res/navigation/settings_navigation.xml b/src/android/app/src/main/res/navigation/settings_navigation.xml
new file mode 100644
index 000000000..88e1b4587
--- /dev/null
+++ b/src/android/app/src/main/res/navigation/settings_navigation.xml
@@ -0,0 +1,32 @@
1<?xml version="1.0" encoding="utf-8"?>
2<navigation xmlns:android="http://schemas.android.com/apk/res/android"
3 xmlns:app="http://schemas.android.com/apk/res-auto"
4 android:id="@+id/settings_navigation"
5 app:startDestination="@id/settingsFragment">
6
7 <fragment
8 android:id="@+id/settingsFragment"
9 android:name="org.yuzu.yuzu_emu.features.settings.ui.SettingsFragment"
10 android:label="SettingsFragment">
11 <argument
12 android:name="menuTag"
13 app:argType="string" />
14 <argument
15 android:name="game"
16 app:argType="org.yuzu.yuzu_emu.model.Game"
17 app:nullable="true" />
18 <action
19 android:id="@+id/action_settingsFragment_to_settingsSearchFragment"
20 app:destination="@id/settingsSearchFragment" />
21 </fragment>
22
23 <action
24 android:id="@+id/action_global_settingsFragment"
25 app:destination="@id/settingsFragment" />
26
27 <fragment
28 android:id="@+id/settingsSearchFragment"
29 android:name="org.yuzu.yuzu_emu.fragments.SettingsSearchFragment"
30 android:label="SettingsSearchFragment" />
31
32</navigation>
diff --git a/src/android/app/src/main/res/values/arrays.xml b/src/android/app/src/main/res/values/arrays.xml
index 200b99185..dc10159c9 100644
--- a/src/android/app/src/main/res/values/arrays.xml
+++ b/src/android/app/src/main/res/values/arrays.xml
@@ -243,10 +243,10 @@
243 <item>@string/cubeb</item> 243 <item>@string/cubeb</item>
244 <item>@string/string_null</item> 244 <item>@string/string_null</item>
245 </string-array> 245 </string-array>
246 <string-array name="outputEngineValues"> 246 <integer-array name="outputEngineValues">
247 <item>auto</item> 247 <item>0</item>
248 <item>cubeb</item> 248 <item>1</item>
249 <item>null</item> 249 <item>3</item>
250 </string-array> 250 </integer-array>
251 251
252</resources> 252</resources>
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml
index de1b2909b..d43891cec 100644
--- a/src/android/app/src/main/res/values/strings.xml
+++ b/src/android/app/src/main/res/values/strings.xml
@@ -43,6 +43,7 @@
43 <string name="add_games_warning_description">Games won\'t be displayed in the Games list if a folder isn\'t selected.</string> 43 <string name="add_games_warning_description">Games won\'t be displayed in the Games list if a folder isn\'t selected.</string>
44 <string name="add_games_warning_help">https://yuzu-emu.org/help/quickstart/#dumping-games</string> 44 <string name="add_games_warning_help">https://yuzu-emu.org/help/quickstart/#dumping-games</string>
45 <string name="home_search_games">Search games</string> 45 <string name="home_search_games">Search games</string>
46 <string name="search_settings">Search settings</string>
46 <string name="games_dir_selected">Games directory selected</string> 47 <string name="games_dir_selected">Games directory selected</string>
47 <string name="install_prod_keys">Install prod.keys</string> 48 <string name="install_prod_keys">Install prod.keys</string>
48 <string name="install_prod_keys_description">Required to decrypt retail games</string> 49 <string name="install_prod_keys_description">Required to decrypt retail games</string>
@@ -74,6 +75,7 @@
74 <string name="install_gpu_driver">Install GPU driver</string> 75 <string name="install_gpu_driver">Install GPU driver</string>
75 <string name="install_gpu_driver_description">Install alternative drivers for potentially better performance or accuracy</string> 76 <string name="install_gpu_driver_description">Install alternative drivers for potentially better performance or accuracy</string>
76 <string name="advanced_settings">Advanced settings</string> 77 <string name="advanced_settings">Advanced settings</string>
78 <string name="advanced_settings_game">Advanced settings: %1$s</string>
77 <string name="settings_description">Configure emulator settings</string> 79 <string name="settings_description">Configure emulator settings</string>
78 <string name="search_recently_played">Recently played</string> 80 <string name="search_recently_played">Recently played</string>
79 <string name="search_recently_added">Recently added</string> 81 <string name="search_recently_added">Recently added</string>
@@ -200,6 +202,7 @@
200 <string name="ini_saved">Saved settings</string> 202 <string name="ini_saved">Saved settings</string>
201 <string name="gameid_saved">Saved settings for %1$s</string> 203 <string name="gameid_saved">Saved settings for %1$s</string>
202 <string name="error_saving">Error saving %1$s.ini: %2$s</string> 204 <string name="error_saving">Error saving %1$s.ini: %2$s</string>
205 <string name="unimplemented_menu">Unimplemented Menu</string>
203 <string name="loading">Loading…</string> 206 <string name="loading">Loading…</string>
204 <string name="reset_setting_confirmation">Do you want to reset this setting back to its default value?</string> 207 <string name="reset_setting_confirmation">Do you want to reset this setting back to its default value?</string>
205 <string name="reset_to_default">Reset to default</string> 208 <string name="reset_to_default">Reset to default</string>
diff --git a/src/common/settings.cpp b/src/common/settings.cpp
index 524056841..4ecaf550b 100644
--- a/src/common/settings.cpp
+++ b/src/common/settings.cpp
@@ -159,6 +159,8 @@ float Volume() {
159 159
160const char* TranslateCategory(Category category) { 160const char* TranslateCategory(Category category) {
161 switch (category) { 161 switch (category) {
162 case Category::Android:
163 return "Android";
162 case Category::Audio: 164 case Category::Audio:
163 return "Audio"; 165 return "Audio";
164 case Category::Core: 166 case Category::Core:
diff --git a/src/common/settings_common.cpp b/src/common/settings_common.cpp
index 137b65d5f..5960b78aa 100644
--- a/src/common/settings_common.cpp
+++ b/src/common/settings_common.cpp
@@ -14,6 +14,7 @@ BasicSetting::BasicSetting(Linkage& linkage, const std::string& name, enum Categ
14 : label{name}, category{category_}, id{linkage.count}, save{save_}, 14 : label{name}, category{category_}, id{linkage.count}, save{save_},
15 runtime_modifiable{runtime_modifiable_}, specialization{specialization_}, 15 runtime_modifiable{runtime_modifiable_}, specialization{specialization_},
16 other_setting{other_setting_} { 16 other_setting{other_setting_} {
17 linkage.by_key.insert({name, this});
17 linkage.by_category[category].push_back(this); 18 linkage.by_category[category].push_back(this);
18 linkage.count++; 19 linkage.count++;
19} 20}
diff --git a/src/common/settings_common.h b/src/common/settings_common.h
index 3082e0ce1..5b170dfd5 100644
--- a/src/common/settings_common.h
+++ b/src/common/settings_common.h
@@ -12,6 +12,7 @@
12namespace Settings { 12namespace Settings {
13 13
14enum class Category : u32 { 14enum class Category : u32 {
15 Android,
15 Audio, 16 Audio,
16 Core, 17 Core,
17 Cpu, 18 Cpu,
@@ -68,6 +69,7 @@ public:
68 explicit Linkage(u32 initial_count = 0); 69 explicit Linkage(u32 initial_count = 0);
69 ~Linkage(); 70 ~Linkage();
70 std::map<Category, std::vector<BasicSetting*>> by_category{}; 71 std::map<Category, std::vector<BasicSetting*>> by_category{};
72 std::map<std::string, Settings::BasicSetting*> by_key{};
71 std::vector<std::function<void()>> restore_functions{}; 73 std::vector<std::function<void()>> restore_functions{};
72 u32 count; 74 u32 count;
73}; 75};