summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/CMakeLists.txt22
-rw-r--r--src/android/app/src/main/AndroidManifest.xml9
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt30
-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.kt21
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt45
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt4
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/DiskShaderCacheProgress.kt52
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/ShaderProgressViewModel.kt31
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/ui/ShaderProgressDialogFragment.kt103
-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.kt213
-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.kt159
-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/ImportExportSavesFragment.kt4
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt2
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LongMessageDialogFragment.kt62
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/MessageDialogFragment.kt32
-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/EmulationViewModel.kt59
-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.kt206
-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/GameHelper.kt16
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconUtils.kt77
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt33
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/views/FixedRatioSurfaceView.kt48
-rw-r--r--src/android/app/src/main/jni/CMakeLists.txt2
-rw-r--r--src/android/app/src/main/jni/config.cpp36
-rw-r--r--src/android/app/src/main/jni/config.h24
-rw-r--r--src/android/app/src/main/jni/id_cache.cpp14
-rw-r--r--src/android/app/src/main/jni/id_cache.h2
-rw-r--r--src/android/app/src/main/jni/native.cpp52
-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_emulation.xml83
-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.xml21
-rw-r--r--src/android/app/src/main/res/navigation/home_navigation.xml21
-rw-r--r--src/android/app/src/main/res/navigation/settings_navigation.xml32
-rw-r--r--src/android/app/src/main/res/values-de/strings.xml1
-rw-r--r--src/android/app/src/main/res/values-es/strings.xml1
-rw-r--r--src/android/app/src/main/res/values-fr/strings.xml1
-rw-r--r--src/android/app/src/main/res/values-it/strings.xml1
-rw-r--r--src/android/app/src/main/res/values-ja/strings.xml1
-rw-r--r--src/android/app/src/main/res/values-ko/strings.xml1
-rw-r--r--src/android/app/src/main/res/values-nb/strings.xml1
-rw-r--r--src/android/app/src/main/res/values-pl/strings.xml1
-rw-r--r--src/android/app/src/main/res/values-pt-rBR/strings.xml1
-rw-r--r--src/android/app/src/main/res/values-pt-rPT/strings.xml1
-rw-r--r--src/android/app/src/main/res/values-ru/strings.xml1
-rw-r--r--src/android/app/src/main/res/values-uk/strings.xml1
-rw-r--r--src/android/app/src/main/res/values-zh-rCN/strings.xml1
-rw-r--r--src/android/app/src/main/res/values-zh-rTW/strings.xml1
-rw-r--r--src/android/app/src/main/res/values/arrays.xml10
-rw-r--r--src/android/app/src/main/res/values/strings.xml5
-rw-r--r--src/audio_core/renderer/system.cpp2
-rw-r--r--src/audio_core/sink/cubeb_sink.cpp47
-rw-r--r--src/audio_core/sink/cubeb_sink.h7
-rw-r--r--src/audio_core/sink/sdl2_sink.cpp40
-rw-r--r--src/audio_core/sink/sdl2_sink.h7
-rw-r--r--src/audio_core/sink/sink_details.cpp45
-rw-r--r--src/common/settings.cpp7
-rw-r--r--src/common/settings.h10
-rw-r--r--src/common/settings_common.cpp1
-rw-r--r--src/common/settings_common.h3
-rw-r--r--src/common/settings_enums.h2
-rw-r--r--src/common/swap.h5
-rw-r--r--src/core/core.cpp25
-rw-r--r--src/core/core.h7
-rw-r--r--src/core/file_sys/content_archive.cpp17
-rw-r--r--src/core/frontend/applets/controller.cpp4
-rw-r--r--src/core/frontend/framebuffer_layout.cpp3
-rw-r--r--src/core/hle/service/am/am.cpp13
-rw-r--r--src/core/hle/service/apm/apm_controller.cpp4
-rw-r--r--src/core/hle/service/audio/hwopus.cpp55
-rw-r--r--src/core/hle/service/audio/hwopus.h6
-rw-r--r--src/core/hle/service/hid/controllers/gesture.cpp2
-rw-r--r--src/core/hle/service/hid/controllers/npad.cpp2
-rw-r--r--src/core/hle/service/sockets/nsd.cpp11
-rw-r--r--src/core/hle/service/sockets/sfdnsres.cpp12
-rw-r--r--src/core/hle/service/vi/vi.cpp2
-rw-r--r--src/core/telemetry_session.cpp3
-rw-r--r--src/input_common/input_poller.cpp10
-rw-r--r--src/shader_recompiler/backend/glsl/emit_glsl_image.cpp2
-rw-r--r--src/shader_recompiler/backend/spirv/emit_spirv_image.cpp35
-rw-r--r--src/video_core/buffer_cache/buffer_cache.h10
-rw-r--r--src/video_core/engines/maxwell_3d.cpp6
-rw-r--r--src/video_core/host1x/codecs/codec.cpp3
-rw-r--r--src/video_core/macro/macro.cpp24
-rw-r--r--src/video_core/renderer_vulkan/vk_pipeline_cache.cpp7
-rw-r--r--src/web_service/verify_user_jwt.cpp1
-rw-r--r--src/yuzu/applets/qt_controller.cpp14
-rw-r--r--src/yuzu/bootmanager.cpp4
-rw-r--r--src/yuzu/configuration/config.cpp9
-rw-r--r--src/yuzu/configuration/config.h3
-rw-r--r--src/yuzu/configuration/configure_input.cpp14
-rw-r--r--src/yuzu/configuration/configure_per_game.cpp7
-rw-r--r--src/yuzu/configuration/configure_system.cpp5
-rw-r--r--src/yuzu/configuration/shared_translation.cpp5
-rw-r--r--src/yuzu/configuration/shared_widget.cpp68
-rw-r--r--src/yuzu/configuration/shared_widget.h6
-rw-r--r--src/yuzu/main.cpp31
152 files changed, 3319 insertions, 2710 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 7bb88c8ea..a9f68a8f2 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -105,6 +105,8 @@ if (MSVC)
set(CMAKE_EXE_LINKER_FLAGS_RELEASE "/DEBUG /MANIFEST:NO /INCREMENTAL:NO /OPT:REF,ICF" CACHE STRING "" FORCE)
else()
add_compile_options(
+ -fwrapv
+
-Werror=all
-Werror=extra
-Werror=missing-declarations
@@ -114,19 +116,21 @@ else()
-Wno-attributes
-Wno-invalid-offsetof
-Wno-unused-parameter
-
- $<$<CXX_COMPILER_ID:Clang>:-Wno-braced-scalar-init>
- $<$<CXX_COMPILER_ID:Clang>:-Wno-unused-private-field>
- $<$<CXX_COMPILER_ID:Clang>:-Werror=shadow-uncaptured-local>
- $<$<CXX_COMPILER_ID:Clang>:-Werror=implicit-fallthrough>
- $<$<CXX_COMPILER_ID:Clang>:-Werror=type-limits>
- $<$<CXX_COMPILER_ID:AppleClang>:-Wno-braced-scalar-init>
- $<$<CXX_COMPILER_ID:AppleClang>:-Wno-unused-private-field>
)
+ if (CMAKE_CXX_COMPILER_ID MATCHES Clang) # Clang or AppleClang
+ add_compile_options(
+ -Wno-braced-scalar-init
+ -Wno-unused-private-field
+ -Wno-nullability-completeness
+ -Werror=shadow-uncaptured-local
+ -Werror=implicit-fallthrough
+ -Werror=type-limits
+ )
+ endif()
+
if (ARCHITECTURE_x86_64)
add_compile_options("-mcx16")
- add_compile_options("-fwrapv")
endif()
if (APPLE AND CMAKE_CXX_COMPILER_ID STREQUAL Clang)
diff --git a/src/android/app/src/main/AndroidManifest.xml b/src/android/app/src/main/AndroidManifest.xml
index 36e2dac98..832c08e15 100644
--- a/src/android/app/src/main/AndroidManifest.xml
+++ b/src/android/app/src/main/AndroidManifest.xml
@@ -56,7 +56,6 @@ SPDX-License-Identifier: GPL-3.0-or-later
android:name="org.yuzu.yuzu_emu.activities.EmulationActivity"
android:theme="@style/Theme.Yuzu.Main"
android:launchMode="singleTop"
- android:screenOrientation="userLandscape"
android:supportsPictureInPicture="true"
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|uiMode"
android:exported="true">
@@ -67,6 +66,14 @@ SPDX-License-Identifier: GPL-3.0-or-later
<data android:mimeType="application/octet-stream" />
</intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <data
+ android:mimeType="application/octet-stream"
+ android:scheme="content"/>
+ </intent-filter>
+
<meta-data
android:name="android.nfc.action.TECH_DISCOVERED"
android:resource="@xml/nfc_tech_filter" />
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..c8706d7a6 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
@@ -22,9 +22,7 @@ import org.yuzu.yuzu_emu.utils.FileUtil.exists
import org.yuzu.yuzu_emu.utils.FileUtil.getFileSize
import org.yuzu.yuzu_emu.utils.FileUtil.isDirectory
import org.yuzu.yuzu_emu.utils.FileUtil.openContentUri
-import org.yuzu.yuzu_emu.utils.Log.error
-import org.yuzu.yuzu_emu.utils.Log.verbose
-import org.yuzu.yuzu_emu.utils.Log.warning
+import org.yuzu.yuzu_emu.utils.Log
import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable
/**
@@ -219,10 +217,6 @@ object NativeLibrary {
external fun reloadSettings()
- external fun getUserSetting(gameID: String?, Section: String?, Key: String?): String?
-
- external fun setUserSetting(gameID: String?, Section: String?, Key: String?, Value: String?)
-
external fun initGameIni(gameID: String?)
/**
@@ -413,14 +407,17 @@ object NativeLibrary {
details.ifEmpty { emulationActivity.getString(R.string.system_archive_general) }
)
}
+
CoreError.ErrorSavestate -> {
title = emulationActivity.getString(R.string.save_load_error)
message = details
}
+
CoreError.ErrorUnknown -> {
title = emulationActivity.getString(R.string.fatal_error)
message = emulationActivity.getString(R.string.fatal_error_message)
}
+
else -> {
return true
}
@@ -454,6 +451,7 @@ object NativeLibrary {
captionId = R.string.loader_error_video_core
descriptionId = R.string.loader_error_video_core_description
}
+
else -> {
captionId = R.string.loader_error_encrypted
descriptionId = R.string.loader_error_encrypted_roms_description
@@ -465,7 +463,7 @@ object NativeLibrary {
val emulationActivity = sEmulationActivity.get()
if (emulationActivity == null) {
- warning("[NativeLibrary] EmulationActivity is null, can't exit.")
+ Log.warning("[NativeLibrary] EmulationActivity is null, can't exit.")
return
}
@@ -490,15 +488,27 @@ object NativeLibrary {
}
fun setEmulationActivity(emulationActivity: EmulationActivity?) {
- verbose("[NativeLibrary] Registering EmulationActivity.")
+ Log.verbose("[NativeLibrary] Registering EmulationActivity.")
sEmulationActivity = WeakReference(emulationActivity)
}
fun clearEmulationActivity() {
- verbose("[NativeLibrary] Unregistering EmulationActivity.")
+ Log.verbose("[NativeLibrary] Unregistering EmulationActivity.")
sEmulationActivity.clear()
}
+ @Keep
+ @JvmStatic
+ fun onEmulationStarted() {
+ sEmulationActivity.get()!!.onEmulationStarted()
+ }
+
+ @Keep
+ @JvmStatic
+ fun onEmulationStopped(status: Int) {
+ sEmulationActivity.get()!!.onEmulationStopped(status)
+ }
+
/**
* Logs the Yuzu version, Android version and, CPU.
*/
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt
index 04ab6a220..9561748cb 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt
@@ -46,7 +46,7 @@ class YuzuApplication : Application() {
super.onCreate()
application = this
documentsTree = DocumentsTree()
- DirectoryInitialization.start(applicationContext)
+ DirectoryInitialization.start()
GpuDriverHelper.initializeDriverParameters(applicationContext)
NativeLibrary.logDeviceInfo()
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
index 7461fb093..bbd328c71 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
@@ -42,7 +42,7 @@ import org.yuzu.yuzu_emu.databinding.ActivityEmulationBinding
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
import org.yuzu.yuzu_emu.features.settings.model.Settings
-import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel
+import org.yuzu.yuzu_emu.model.EmulationViewModel
import org.yuzu.yuzu_emu.model.Game
import org.yuzu.yuzu_emu.utils.ControllerMappingHelper
import org.yuzu.yuzu_emu.utils.ForegroundService
@@ -72,18 +72,17 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
private val actionMute = "ACTION_EMULATOR_MUTE"
private val actionUnmute = "ACTION_EMULATOR_UNMUTE"
- private val settingsViewModel: SettingsViewModel by viewModels()
+ private val emulationViewModel: EmulationViewModel by viewModels()
override fun onDestroy() {
stopForegroundService(this)
+ emulationViewModel.clear()
super.onDestroy()
}
override fun onCreate(savedInstanceState: Bundle?) {
ThemeHelper.setTheme(this)
- settingsViewModel.settings.loadSettings()
-
super.onCreate(savedInstanceState)
binding = ActivityEmulationBinding.inflate(layoutInflater)
@@ -91,9 +90,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
val navHostFragment =
supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment
- val navController = navHostFragment.navController
- navController
- .setGraph(R.navigation.emulation_navigation, intent.extras)
+ navHostFragment.navController.setGraph(R.navigation.emulation_navigation, intent.extras)
isActivityRecreated = savedInstanceState != null
@@ -424,6 +421,16 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
}
}
+ fun onEmulationStarted() {
+ emulationViewModel.setEmulationStarted(true)
+ }
+
+ fun onEmulationStopped(status: Int) {
+ if (status == 0) {
+ finish()
+ }
+ }
+
private fun startMotionSensorListener() {
val sensorManager = this.getSystemService(Context.SENSOR_SERVICE) as SensorManager
val gyroSensor = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt
index e91277d35..0013e8512 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt
@@ -3,8 +3,8 @@
package org.yuzu.yuzu_emu.adapters
-import android.graphics.Bitmap
-import android.graphics.BitmapFactory
+import android.content.Intent
+import android.graphics.drawable.BitmapDrawable
import android.net.Uri
import android.text.TextUtils
import android.view.LayoutInflater
@@ -13,25 +13,26 @@ import android.view.ViewGroup
import android.widget.ImageView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
+import androidx.core.content.pm.ShortcutInfoCompat
+import androidx.core.content.pm.ShortcutManagerCompat
+import androidx.core.graphics.drawable.IconCompat
import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.ViewModelProvider
-import androidx.lifecycle.lifecycleScope
import androidx.navigation.findNavController
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.AsyncDifferConfig
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
-import coil.load
-import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.HomeNavigationDirections
-import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication
+import org.yuzu.yuzu_emu.activities.EmulationActivity
import org.yuzu.yuzu_emu.adapters.GameAdapter.GameViewHolder
import org.yuzu.yuzu_emu.databinding.CardGameBinding
import org.yuzu.yuzu_emu.model.Game
import org.yuzu.yuzu_emu.model.GamesViewModel
+import org.yuzu.yuzu_emu.utils.GameIconUtils
class GameAdapter(private val activity: AppCompatActivity) :
ListAdapter<Game, GameViewHolder>(AsyncDifferConfig.Builder(DiffCallback()).build()),
@@ -82,6 +83,21 @@ class GameAdapter(private val activity: AppCompatActivity) :
)
.apply()
+ val openIntent = Intent(YuzuApplication.appContext, EmulationActivity::class.java).apply {
+ action = Intent.ACTION_VIEW
+ data = Uri.parse(holder.game.path)
+ }
+ val shortcut = ShortcutInfoCompat.Builder(YuzuApplication.appContext, holder.game.path)
+ .setShortLabel(holder.game.title)
+ .setIcon(
+ IconCompat.createWithBitmap(
+ (holder.binding.imageGameScreen.drawable as BitmapDrawable).bitmap
+ )
+ )
+ .setIntent(openIntent)
+ .build()
+ ShortcutManagerCompat.pushDynamicShortcut(YuzuApplication.appContext, shortcut)
+
val action = HomeNavigationDirections.actionGlobalEmulationActivity(holder.game)
view.findNavController().navigate(action)
}
@@ -98,12 +114,7 @@ class GameAdapter(private val activity: AppCompatActivity) :
this.game = game
binding.imageGameScreen.scaleType = ImageView.ScaleType.CENTER_CROP
- activity.lifecycleScope.launch {
- val bitmap = decodeGameIcon(game.path)
- binding.imageGameScreen.load(bitmap) {
- error(R.drawable.default_icon)
- }
- }
+ GameIconUtils.loadGameIcon(game, binding.imageGameScreen)
binding.textGameTitle.text = game.title.replace("[\\t\\n\\r]+".toRegex(), " ")
@@ -126,14 +137,4 @@ class GameAdapter(private val activity: AppCompatActivity) :
return oldItem == newItem
}
}
-
- private fun decodeGameIcon(uri: String): Bitmap? {
- val data = NativeLibrary.getIcon(uri)
- return BitmapFactory.decodeByteArray(
- data,
- 0,
- data.size,
- BitmapFactory.Options()
- )
- }
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt
index 9f859b442..8d87d3bd7 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt
@@ -45,8 +45,8 @@ class HomeSettingAdapter(
holder.option.onClick.invoke()
} else {
MessageDialogFragment.newInstance(
- holder.option.disabledTitleId,
- holder.option.disabledMessageId
+ titleId = holder.option.disabledTitleId,
+ descriptionId = holder.option.disabledMessageId
).show(activity.supportFragmentManager, MessageDialogFragment.TAG)
}
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/DiskShaderCacheProgress.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/DiskShaderCacheProgress.kt
index a18efef19..6f4b5b13f 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/DiskShaderCacheProgress.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/DiskShaderCacheProgress.kt
@@ -4,43 +4,43 @@
package org.yuzu.yuzu_emu.disk_shader_cache
import androidx.annotation.Keep
+import androidx.lifecycle.ViewModelProvider
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R
-import org.yuzu.yuzu_emu.disk_shader_cache.ui.ShaderProgressDialogFragment
+import org.yuzu.yuzu_emu.activities.EmulationActivity
+import org.yuzu.yuzu_emu.model.EmulationViewModel
+import org.yuzu.yuzu_emu.utils.Log
@Keep
object DiskShaderCacheProgress {
- val finishLock = Object()
- private lateinit var fragment: ShaderProgressDialogFragment
+ private lateinit var emulationViewModel: EmulationViewModel
- private fun prepareDialog() {
- val emulationActivity = NativeLibrary.sEmulationActivity.get()!!
- emulationActivity.runOnUiThread {
- fragment = ShaderProgressDialogFragment.newInstance(
- emulationActivity.getString(R.string.loading),
- emulationActivity.getString(R.string.preparing_shaders)
- )
- fragment.show(
- emulationActivity.supportFragmentManager,
- ShaderProgressDialogFragment.TAG
- )
- }
- synchronized(finishLock) { finishLock.wait() }
+ private fun prepareViewModel() {
+ emulationViewModel =
+ ViewModelProvider(
+ NativeLibrary.sEmulationActivity.get() as EmulationActivity
+ )[EmulationViewModel::class.java]
}
@JvmStatic
fun loadProgress(stage: Int, progress: Int, max: Int) {
val emulationActivity = NativeLibrary.sEmulationActivity.get()
- ?: error("[DiskShaderCacheProgress] EmulationActivity not present")
-
- when (LoadCallbackStage.values()[stage]) {
- LoadCallbackStage.Prepare -> prepareDialog()
- LoadCallbackStage.Build -> fragment.onUpdateProgress(
- emulationActivity.getString(R.string.building_shaders),
- progress,
- max
- )
- LoadCallbackStage.Complete -> fragment.dismiss()
+ if (emulationActivity == null) {
+ Log.error("[DiskShaderCacheProgress] EmulationActivity not present")
+ return
+ }
+
+ emulationActivity.runOnUiThread {
+ when (LoadCallbackStage.values()[stage]) {
+ LoadCallbackStage.Prepare -> prepareViewModel()
+ LoadCallbackStage.Build -> emulationViewModel.updateProgress(
+ emulationActivity.getString(R.string.building_shaders),
+ progress,
+ max
+ )
+
+ LoadCallbackStage.Complete -> {}
+ }
}
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/ShaderProgressViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/ShaderProgressViewModel.kt
deleted file mode 100644
index bf6f0366d..000000000
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/ShaderProgressViewModel.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-package org.yuzu.yuzu_emu.disk_shader_cache
-
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.ViewModel
-
-class ShaderProgressViewModel : ViewModel() {
- private val _progress = MutableLiveData(0)
- val progress: LiveData<Int> get() = _progress
-
- private val _max = MutableLiveData(0)
- val max: LiveData<Int> get() = _max
-
- private val _message = MutableLiveData("")
- val message: LiveData<String> get() = _message
-
- fun setProgress(progress: Int) {
- _progress.postValue(progress)
- }
-
- fun setMax(max: Int) {
- _max.postValue(max)
- }
-
- fun setMessage(msg: String) {
- _message.postValue(msg)
- }
-}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/ui/ShaderProgressDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/ui/ShaderProgressDialogFragment.kt
deleted file mode 100644
index 8a8e0a6e8..000000000
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/ui/ShaderProgressDialogFragment.kt
+++ /dev/null
@@ -1,103 +0,0 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-package org.yuzu.yuzu_emu.disk_shader_cache.ui
-
-import android.app.Dialog
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import androidx.appcompat.app.AlertDialog
-import androidx.fragment.app.DialogFragment
-import androidx.lifecycle.ViewModelProvider
-import com.google.android.material.dialog.MaterialAlertDialogBuilder
-import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
-import org.yuzu.yuzu_emu.disk_shader_cache.DiskShaderCacheProgress
-import org.yuzu.yuzu_emu.disk_shader_cache.ShaderProgressViewModel
-
-class ShaderProgressDialogFragment : DialogFragment() {
- private var _binding: DialogProgressBarBinding? = null
- private val binding get() = _binding!!
-
- private lateinit var alertDialog: AlertDialog
-
- private lateinit var shaderProgressViewModel: ShaderProgressViewModel
-
- override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
- _binding = DialogProgressBarBinding.inflate(layoutInflater)
- shaderProgressViewModel =
- ViewModelProvider(requireActivity())[ShaderProgressViewModel::class.java]
-
- val title = requireArguments().getString(TITLE)
- val message = requireArguments().getString(MESSAGE)
-
- isCancelable = false
- alertDialog = MaterialAlertDialogBuilder(requireActivity())
- .setView(binding.root)
- .setTitle(title)
- .setMessage(message)
- .create()
- return alertDialog
- }
-
- override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View {
- return binding.root
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
- shaderProgressViewModel.progress.observe(viewLifecycleOwner) { progress ->
- binding.progressBar.progress = progress
- setUpdateText()
- }
- shaderProgressViewModel.max.observe(viewLifecycleOwner) { max ->
- binding.progressBar.max = max
- setUpdateText()
- }
- shaderProgressViewModel.message.observe(viewLifecycleOwner) { msg ->
- alertDialog.setMessage(msg)
- }
- synchronized(DiskShaderCacheProgress.finishLock) {
- DiskShaderCacheProgress.finishLock.notifyAll()
- }
- }
-
- override fun onDestroyView() {
- super.onDestroyView()
- _binding = null
- }
-
- fun onUpdateProgress(msg: String, progress: Int, max: Int) {
- shaderProgressViewModel.setProgress(progress)
- shaderProgressViewModel.setMax(max)
- shaderProgressViewModel.setMessage(msg)
- }
-
- private fun setUpdateText() {
- binding.progressText.text = String.format(
- "%d/%d",
- shaderProgressViewModel.progress.value,
- shaderProgressViewModel.max.value
- )
- }
-
- companion object {
- const val TAG = "ProgressDialogFragment"
- const val TITLE = "title"
- const val MESSAGE = "message"
-
- fun newInstance(title: String, message: String): ShaderProgressDialogFragment {
- val frag = ShaderProgressDialogFragment()
- val args = Bundle()
- args.putString(TITLE, title)
- args.putString(MESSAGE, message)
- frag.arguments = args
- return frag
- }
- }
-}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractBooleanSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractBooleanSetting.kt
index a6e9833ee..aeda8d222 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractBooleanSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractBooleanSetting.kt
@@ -4,5 +4,7 @@
package org.yuzu.yuzu_emu.features.settings.model
interface AbstractBooleanSetting : AbstractSetting {
- var boolean: Boolean
+ val boolean: Boolean
+
+ fun setBoolean(value: Boolean)
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/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 @@
package org.yuzu.yuzu_emu.features.settings.model
-import androidx.lifecycle.ViewModel
+interface AbstractByteSetting : AbstractSetting {
+ val byte: Byte
-class SettingsViewModel : ViewModel() {
- val settings = Settings()
+ fun setByte(value: Byte)
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractFloatSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractFloatSetting.kt
index 6fe4bc263..974925eed 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractFloatSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractFloatSetting.kt
@@ -4,5 +4,7 @@
package org.yuzu.yuzu_emu.features.settings.model
interface AbstractFloatSetting : AbstractSetting {
- var float: Float
+ val float: Float
+
+ fun setFloat(value: Float)
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractIntSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractIntSetting.kt
index 892b7dcfe..89b285b10 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractIntSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractIntSetting.kt
@@ -4,5 +4,7 @@
package org.yuzu.yuzu_emu.features.settings.model
interface AbstractIntSetting : AbstractSetting {
- var int: Int
+ val int: Int
+
+ fun setInt(value: Int)
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractLongSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractLongSetting.kt
new file mode 100644
index 000000000..4873942db
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractLongSetting.kt
@@ -0,0 +1,10 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.features.settings.model
+
+interface AbstractLongSetting : AbstractSetting {
+ val long: Long
+
+ fun setLong(value: Long)
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractSetting.kt
index 258580209..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 @@
package org.yuzu.yuzu_emu.features.settings.model
+import org.yuzu.yuzu_emu.utils.NativeConfig
+
interface AbstractSetting {
- val key: String?
- val section: String?
- val isRuntimeEditable: Boolean
- val valueAsString: String
+ val key: String
+ val category: Settings.Category
val defaultValue: Any
+ val androidDefault: Any?
+ get() = null
+ val valueAsString: String
+ get() = ""
+
+ val isRuntimeModifiable: Boolean
+ get() = NativeConfig.getIsRuntimeModifiable(key)
+
+ val pairedSettingKey: String
+ get() = NativeConfig.getPairedSettingKey(key)
+
+ fun reset()
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractShortSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractShortSetting.kt
new file mode 100644
index 000000000..91407ccbb
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractShortSetting.kt
@@ -0,0 +1,10 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.features.settings.model
+
+interface AbstractShortSetting : AbstractSetting {
+ val short: Short
+
+ fun setShort(value: Short)
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractStringSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractStringSetting.kt
index 0d02c5997..c8935cc48 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractStringSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractStringSetting.kt
@@ -4,5 +4,7 @@
package org.yuzu.yuzu_emu.features.settings.model
interface AbstractStringSetting : AbstractSetting {
- var string: String
+ val string: String
+
+ fun setString(value: String)
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt
index d41933766..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 @@
package org.yuzu.yuzu_emu.features.settings.model
+import org.yuzu.yuzu_emu.utils.NativeConfig
+
enum class BooleanSetting(
override val key: String,
- override val section: String,
- override val defaultValue: Boolean
+ override val category: Settings.Category,
+ override val androidDefault: Boolean? = null
) : AbstractBooleanSetting {
- CPU_DEBUG_MODE("cpu_debug_mode", Settings.SECTION_CPU, false),
- FASTMEM("cpuopt_fastmem", Settings.SECTION_CPU, true),
- FASTMEM_EXCLUSIVES("cpuopt_fastmem_exclusives", Settings.SECTION_CPU, true),
- PICTURE_IN_PICTURE("picture_in_picture", Settings.SECTION_GENERAL, true),
- USE_CUSTOM_RTC("custom_rtc_enabled", Settings.SECTION_SYSTEM, false);
-
- override var boolean: Boolean = defaultValue
+ CPU_DEBUG_MODE("cpu_debug_mode", Settings.Category.Cpu),
+ FASTMEM("cpuopt_fastmem", Settings.Category.Cpu),
+ FASTMEM_EXCLUSIVES("cpuopt_fastmem_exclusives", Settings.Category.Cpu),
+ RENDERER_USE_SPEED_LIMIT("use_speed_limit", Settings.Category.Core),
+ USE_DOCKED_MODE("use_docked_mode", Settings.Category.System, false),
+ RENDERER_USE_DISK_SHADER_CACHE("use_disk_shader_cache", Settings.Category.Renderer),
+ RENDERER_FORCE_MAX_CLOCK("force_max_clock", Settings.Category.Renderer),
+ RENDERER_ASYNCHRONOUS_SHADERS("use_asynchronous_shaders", Settings.Category.Renderer),
+ RENDERER_REACTIVE_FLUSHING("use_reactive_flushing", Settings.Category.Renderer, false),
+ RENDERER_DEBUG("debug", Settings.Category.Renderer),
+ PICTURE_IN_PICTURE("picture_in_picture", Settings.Category.Android),
+ USE_CUSTOM_RTC("custom_rtc_enabled", Settings.Category.System);
+
+ override val boolean: Boolean
+ get() = NativeConfig.getBoolean(key, false)
+
+ override fun setBoolean(value: Boolean) = NativeConfig.setBoolean(key, value)
+
+ override val defaultValue: Boolean by lazy {
+ androidDefault ?: NativeConfig.getBoolean(key, true)
+ }
override val valueAsString: String
- get() = boolean.toString()
-
- override val isRuntimeEditable: Boolean
- get() {
- for (setting in NOT_RUNTIME_EDITABLE) {
- if (setting == this) {
- return false
- }
- }
- return true
- }
-
- companion object {
- private val NOT_RUNTIME_EDITABLE = listOf(
- PICTURE_IN_PICTURE,
- USE_CUSTOM_RTC
- )
-
- fun from(key: String): BooleanSetting? =
- BooleanSetting.values().firstOrNull { it.key == key }
-
- fun clear() = BooleanSetting.values().forEach { it.boolean = it.defaultValue }
- }
+ get() = if (boolean) "1" else "0"
+
+ override fun reset() = NativeConfig.setBoolean(key, defaultValue)
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ByteSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ByteSetting.kt
new file mode 100644
index 000000000..6ec0a765e
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ByteSetting.kt
@@ -0,0 +1,25 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.features.settings.model
+
+import org.yuzu.yuzu_emu.utils.NativeConfig
+
+enum class ByteSetting(
+ override val key: String,
+ override val category: Settings.Category
+) : AbstractByteSetting {
+ AUDIO_VOLUME("volume", Settings.Category.Audio);
+
+ override val byte: Byte
+ get() = NativeConfig.getByte(key, false)
+
+ override fun setByte(value: Byte) = NativeConfig.setByte(key, value)
+
+ override val defaultValue: Byte by lazy { NativeConfig.getByte(key, true) }
+
+ override val valueAsString: String
+ get() = byte.toString()
+
+ override fun reset() = NativeConfig.setByte(key, defaultValue)
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/FloatSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/FloatSetting.kt
index e5545a916..0181d06f2 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/FloatSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/FloatSetting.kt
@@ -3,34 +3,24 @@
package org.yuzu.yuzu_emu.features.settings.model
+import org.yuzu.yuzu_emu.utils.NativeConfig
+
enum class FloatSetting(
override val key: String,
- override val section: String,
- override val defaultValue: Float
+ override val category: Settings.Category
) : AbstractFloatSetting {
// No float settings currently exist
- EMPTY_SETTING("", "", 0f);
-
- override var float: Float = defaultValue
+ EMPTY_SETTING("", Settings.Category.UiGeneral);
- override val valueAsString: String
- get() = float.toString()
+ override val float: Float
+ get() = NativeConfig.getFloat(key, false)
- override val isRuntimeEditable: Boolean
- get() {
- for (setting in NOT_RUNTIME_EDITABLE) {
- if (setting == this) {
- return false
- }
- }
- return true
- }
+ override fun setFloat(value: Float) = NativeConfig.setFloat(key, value)
- companion object {
- private val NOT_RUNTIME_EDITABLE = emptyList<FloatSetting>()
+ override val defaultValue: Float by lazy { NativeConfig.getFloat(key, true) }
- fun from(key: String): FloatSetting? = FloatSetting.values().firstOrNull { it.key == key }
+ override val valueAsString: String
+ get() = float.toString()
- fun clear() = FloatSetting.values().forEach { it.float = it.defaultValue }
- }
+ override fun reset() = NativeConfig.setFloat(key, defaultValue)
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt
index 4427a7d9d..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 @@
package org.yuzu.yuzu_emu.features.settings.model
+import org.yuzu.yuzu_emu.utils.NativeConfig
+
enum class IntSetting(
override val key: String,
- override val section: String,
- override val defaultValue: Int
+ override val category: Settings.Category,
+ override val androidDefault: Int? = null
) : AbstractIntSetting {
- RENDERER_USE_SPEED_LIMIT(
- "use_speed_limit",
- Settings.SECTION_RENDERER,
- 1
- ),
- USE_DOCKED_MODE(
- "use_docked_mode",
- Settings.SECTION_SYSTEM,
- 0
- ),
- RENDERER_USE_DISK_SHADER_CACHE(
- "use_disk_shader_cache",
- Settings.SECTION_RENDERER,
- 1
- ),
- RENDERER_FORCE_MAX_CLOCK(
- "force_max_clock",
- Settings.SECTION_RENDERER,
- 0
- ),
- RENDERER_ASYNCHRONOUS_SHADERS(
- "use_asynchronous_shaders",
- Settings.SECTION_RENDERER,
- 0
- ),
- RENDERER_REACTIVE_FLUSHING(
- "use_reactive_flushing",
- Settings.SECTION_RENDERER,
- 0
- ),
- RENDERER_DEBUG(
- "debug",
- Settings.SECTION_RENDERER,
- 0
- ),
- RENDERER_SPEED_LIMIT(
- "speed_limit",
- Settings.SECTION_RENDERER,
- 100
- ),
- CPU_ACCURACY(
- "cpu_accuracy",
- Settings.SECTION_CPU,
- 0
- ),
- REGION_INDEX(
- "region_index",
- Settings.SECTION_SYSTEM,
- -1
- ),
- LANGUAGE_INDEX(
- "language_index",
- Settings.SECTION_SYSTEM,
- 1
- ),
- RENDERER_BACKEND(
- "backend",
- Settings.SECTION_RENDERER,
- 1
- ),
- RENDERER_ACCURACY(
- "gpu_accuracy",
- Settings.SECTION_RENDERER,
- 0
- ),
- RENDERER_RESOLUTION(
- "resolution_setup",
- Settings.SECTION_RENDERER,
- 2
- ),
- RENDERER_VSYNC(
- "use_vsync",
- Settings.SECTION_RENDERER,
- 0
- ),
- RENDERER_SCALING_FILTER(
- "scaling_filter",
- Settings.SECTION_RENDERER,
- 1
- ),
- RENDERER_ANTI_ALIASING(
- "anti_aliasing",
- Settings.SECTION_RENDERER,
- 0
- ),
- RENDERER_SCREEN_LAYOUT(
- "screen_layout",
- Settings.SECTION_RENDERER,
- Settings.LayoutOption_MobileLandscape
- ),
- RENDERER_ASPECT_RATIO(
- "aspect_ratio",
- Settings.SECTION_RENDERER,
- 0
- ),
- AUDIO_VOLUME(
- "volume",
- Settings.SECTION_AUDIO,
- 100
- );
-
- override var int: Int = defaultValue
+ CPU_ACCURACY("cpu_accuracy", Settings.Category.Cpu),
+ REGION_INDEX("region_index", Settings.Category.System),
+ LANGUAGE_INDEX("language_index", Settings.Category.System),
+ RENDERER_BACKEND("backend", Settings.Category.Renderer),
+ RENDERER_ACCURACY("gpu_accuracy", Settings.Category.Renderer, 0),
+ RENDERER_RESOLUTION("resolution_setup", Settings.Category.Renderer),
+ RENDERER_VSYNC("use_vsync", Settings.Category.Renderer),
+ RENDERER_SCALING_FILTER("scaling_filter", Settings.Category.Renderer),
+ RENDERER_ANTI_ALIASING("anti_aliasing", Settings.Category.Renderer),
+ RENDERER_SCREEN_LAYOUT("screen_layout", Settings.Category.Android),
+ RENDERER_ASPECT_RATIO("aspect_ratio", Settings.Category.Renderer),
+ AUDIO_OUTPUT_ENGINE("output_engine", Settings.Category.Audio);
+
+ override val int: Int
+ get() = NativeConfig.getInt(key, false)
+
+ override fun setInt(value: Int) = NativeConfig.setInt(key, value)
+
+ override val defaultValue: Int by lazy {
+ androidDefault ?: NativeConfig.getInt(key, true)
+ }
override val valueAsString: String
get() = int.toString()
- override val isRuntimeEditable: Boolean
- get() {
- for (setting in NOT_RUNTIME_EDITABLE) {
- if (setting == this) {
- return false
- }
- }
- return true
- }
-
- companion object {
- private val NOT_RUNTIME_EDITABLE = listOf(
- RENDERER_USE_DISK_SHADER_CACHE,
- RENDERER_ASYNCHRONOUS_SHADERS,
- RENDERER_DEBUG,
- RENDERER_BACKEND,
- RENDERER_RESOLUTION,
- RENDERER_VSYNC
- )
-
- fun from(key: String): IntSetting? = IntSetting.values().firstOrNull { it.key == key }
-
- fun clear() = IntSetting.values().forEach { it.int = it.defaultValue }
- }
+ override fun reset() = NativeConfig.setInt(key, defaultValue)
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/LongSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/LongSetting.kt
new file mode 100644
index 000000000..c526fc4cf
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/LongSetting.kt
@@ -0,0 +1,25 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.features.settings.model
+
+import org.yuzu.yuzu_emu.utils.NativeConfig
+
+enum class LongSetting(
+ override val key: String,
+ override val category: Settings.Category
+) : AbstractLongSetting {
+ CUSTOM_RTC("custom_rtc", Settings.Category.System);
+
+ override val long: Long
+ get() = NativeConfig.getLong(key, false)
+
+ override fun setLong(value: Long) = NativeConfig.setLong(key, value)
+
+ override val defaultValue: Long by lazy { NativeConfig.getLong(key, true) }
+
+ override val valueAsString: String
+ get() = long.toString()
+
+ override fun reset() = NativeConfig.setLong(key, defaultValue)
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/SettingSection.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/SettingSection.kt
deleted file mode 100644
index 474f598a9..000000000
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/SettingSection.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-package org.yuzu.yuzu_emu.features.settings.model
-
-/**
- * A semantically-related group of Settings objects. These Settings are
- * internally stored as a HashMap.
- */
-class SettingSection(val name: String) {
- val settings = HashMap<String, AbstractSetting>()
-
- /**
- * Convenience method; inserts a value directly into the backing HashMap.
- *
- * @param setting The Setting to be inserted.
- */
- fun putSetting(setting: AbstractSetting) {
- settings[setting.key!!] = setting
- }
-
- /**
- * Convenience method; gets a value directly from the backing HashMap.
- *
- * @param key Used to retrieve the Setting.
- * @return A Setting object (you should probably cast this before using)
- */
- fun getSetting(key: String): AbstractSetting? {
- return settings[key]
- }
-
- fun mergeSection(settingSection: SettingSection) {
- for (setting in settingSection.settings.values) {
- putSetting(setting)
- }
- }
-}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt
index a6251bafd..0702236e8 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt
@@ -4,195 +4,151 @@
package org.yuzu.yuzu_emu.features.settings.model
import android.text.TextUtils
-import java.util.*
+import android.widget.Toast
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication
-import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivityView
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
-class Settings {
- private var gameId: String? = null
+object Settings {
+ private val context get() = YuzuApplication.appContext
- var isLoaded = false
-
- /**
- * A HashMap<String></String>, SettingSection> that constructs a new SettingSection instead of returning null
- * when getting a key not already in the map
- */
- class SettingsSectionMap : HashMap<String, SettingSection?>() {
- override operator fun get(key: String): SettingSection? {
- if (!super.containsKey(key)) {
- val section = SettingSection(key)
- super.put(key, section)
- return section
- }
- return super.get(key)
- }
- }
-
- var sections: HashMap<String, SettingSection?> = SettingsSectionMap()
-
- fun getSection(sectionName: String): SettingSection? {
- return sections[sectionName]
- }
-
- val isEmpty: Boolean
- get() = sections.isEmpty()
-
- fun loadSettings(view: SettingsActivityView? = null) {
- sections = SettingsSectionMap()
- loadYuzuSettings(view)
- if (!TextUtils.isEmpty(gameId)) {
- loadCustomGameSettings(gameId!!, view)
- }
- isLoaded = true
- }
-
- private fun loadYuzuSettings(view: SettingsActivityView?) {
- for ((fileName) in configFileSectionsMap) {
- sections.putAll(SettingsFile.readFile(fileName, view))
- }
- }
-
- private fun loadCustomGameSettings(gameId: String, view: SettingsActivityView?) {
- // Custom game settings
- mergeSections(SettingsFile.readCustomGameSettings(gameId, view))
- }
-
- private fun mergeSections(updatedSections: HashMap<String, SettingSection?>) {
- for ((key, updatedSection) in updatedSections) {
- if (sections.containsKey(key)) {
- val originalSection = sections[key]
- originalSection!!.mergeSection(updatedSection!!)
- } else {
- sections[key] = updatedSection
- }
- }
- }
-
- fun loadSettings(gameId: String, view: SettingsActivityView) {
- this.gameId = gameId
- loadSettings(view)
- }
-
- fun saveSettings(view: SettingsActivityView) {
+ fun saveSettings(gameId: String = "") {
if (TextUtils.isEmpty(gameId)) {
- view.showToastMessage(
- YuzuApplication.appContext.getString(R.string.ini_saved),
- false
- )
-
- for ((fileName, sectionNames) in configFileSectionsMap) {
- val iniSections = TreeMap<String, SettingSection>()
- for (section in sectionNames) {
- iniSections[section] = sections[section]!!
- }
-
- SettingsFile.saveFile(fileName, iniSections, view)
- }
+ Toast.makeText(
+ context,
+ context.getString(R.string.ini_saved),
+ Toast.LENGTH_SHORT
+ ).show()
+ SettingsFile.saveFile(SettingsFile.FILE_NAME_CONFIG)
} else {
- // Custom game settings
- view.showToastMessage(
- YuzuApplication.appContext.getString(R.string.gameid_saved, gameId),
- false
- )
-
- SettingsFile.saveCustomGameSettings(gameId, sections)
+ // TODO: Save custom game settings
+ Toast.makeText(
+ context,
+ context.getString(R.string.gameid_saved, gameId),
+ Toast.LENGTH_SHORT
+ ).show()
}
}
- companion object {
- const val SECTION_GENERAL = "General"
- const val SECTION_SYSTEM = "System"
- const val SECTION_RENDERER = "Renderer"
- const val SECTION_AUDIO = "Audio"
- const val SECTION_CPU = "Cpu"
- const val SECTION_THEME = "Theme"
- const val SECTION_DEBUG = "Debug"
-
- const val PREF_MEMORY_WARNING_SHOWN = "MemoryWarningShown"
-
- const val PREF_OVERLAY_VERSION = "OverlayVersion"
- const val PREF_LANDSCAPE_OVERLAY_VERSION = "LandscapeOverlayVersion"
- const val PREF_PORTRAIT_OVERLAY_VERSION = "PortraitOverlayVersion"
- const val PREF_FOLDABLE_OVERLAY_VERSION = "FoldableOverlayVersion"
- val overlayLayoutPrefs = listOf(
- PREF_LANDSCAPE_OVERLAY_VERSION,
- PREF_PORTRAIT_OVERLAY_VERSION,
- PREF_FOLDABLE_OVERLAY_VERSION
- )
-
- const val PREF_CONTROL_SCALE = "controlScale"
- const val PREF_CONTROL_OPACITY = "controlOpacity"
- const val PREF_TOUCH_ENABLED = "isTouchEnabled"
- const val PREF_BUTTON_A = "buttonToggle0"
- const val PREF_BUTTON_B = "buttonToggle1"
- const val PREF_BUTTON_X = "buttonToggle2"
- const val PREF_BUTTON_Y = "buttonToggle3"
- const val PREF_BUTTON_L = "buttonToggle4"
- const val PREF_BUTTON_R = "buttonToggle5"
- const val PREF_BUTTON_ZL = "buttonToggle6"
- const val PREF_BUTTON_ZR = "buttonToggle7"
- const val PREF_BUTTON_PLUS = "buttonToggle8"
- const val PREF_BUTTON_MINUS = "buttonToggle9"
- const val PREF_BUTTON_DPAD = "buttonToggle10"
- const val PREF_STICK_L = "buttonToggle11"
- const val PREF_STICK_R = "buttonToggle12"
- const val PREF_BUTTON_STICK_L = "buttonToggle13"
- const val PREF_BUTTON_STICK_R = "buttonToggle14"
- const val PREF_BUTTON_HOME = "buttonToggle15"
- const val PREF_BUTTON_SCREENSHOT = "buttonToggle16"
-
- const val PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER = "EmulationMenuSettings_JoystickRelCenter"
- const val PREF_MENU_SETTINGS_DPAD_SLIDE = "EmulationMenuSettings_DpadSlideEnable"
- const val PREF_MENU_SETTINGS_HAPTICS = "EmulationMenuSettings_Haptics"
- const val PREF_MENU_SETTINGS_SHOW_FPS = "EmulationMenuSettings_ShowFps"
- const val PREF_MENU_SETTINGS_SHOW_OVERLAY = "EmulationMenuSettings_ShowOverlay"
-
- const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch"
- const val PREF_THEME = "Theme"
- const val PREF_THEME_MODE = "ThemeMode"
- const val PREF_BLACK_BACKGROUNDS = "BlackBackgrounds"
-
- private val configFileSectionsMap: MutableMap<String, List<String>> = HashMap()
-
- val overlayPreferences = listOf(
- PREF_OVERLAY_VERSION,
- PREF_CONTROL_SCALE,
- PREF_CONTROL_OPACITY,
- PREF_TOUCH_ENABLED,
- PREF_BUTTON_A,
- PREF_BUTTON_B,
- PREF_BUTTON_X,
- PREF_BUTTON_Y,
- PREF_BUTTON_L,
- PREF_BUTTON_R,
- PREF_BUTTON_ZL,
- PREF_BUTTON_ZR,
- PREF_BUTTON_PLUS,
- PREF_BUTTON_MINUS,
- PREF_BUTTON_DPAD,
- PREF_STICK_L,
- PREF_STICK_R,
- PREF_BUTTON_HOME,
- PREF_BUTTON_SCREENSHOT,
- PREF_BUTTON_STICK_L,
- PREF_BUTTON_STICK_R
- )
-
- const val LayoutOption_Unspecified = 0
- const val LayoutOption_MobilePortrait = 4
- const val LayoutOption_MobileLandscape = 5
-
- init {
- configFileSectionsMap[SettingsFile.FILE_NAME_CONFIG] =
- listOf(
- SECTION_GENERAL,
- SECTION_SYSTEM,
- SECTION_RENDERER,
- SECTION_AUDIO,
- SECTION_CPU
- )
- }
+ enum class Category {
+ Android,
+ Audio,
+ Core,
+ Cpu,
+ CpuDebug,
+ CpuUnsafe,
+ Renderer,
+ RendererAdvanced,
+ RendererDebug,
+ System,
+ SystemAudio,
+ DataStorage,
+ Debugging,
+ DebuggingGraphics,
+ Miscellaneous,
+ Network,
+ WebService,
+ AddOns,
+ Controls,
+ Ui,
+ UiGeneral,
+ UiLayout,
+ UiGameList,
+ Screenshots,
+ Shortcuts,
+ Multiplayer,
+ Services,
+ Paths,
+ MaxEnum
}
+
+ val settingsList = listOf<AbstractSetting>(
+ *BooleanSetting.values(),
+ *ByteSetting.values(),
+ *ShortSetting.values(),
+ *IntSetting.values(),
+ *FloatSetting.values(),
+ *LongSetting.values(),
+ *StringSetting.values()
+ )
+
+ const val SECTION_GENERAL = "General"
+ const val SECTION_SYSTEM = "System"
+ const val SECTION_RENDERER = "Renderer"
+ const val SECTION_AUDIO = "Audio"
+ const val SECTION_CPU = "Cpu"
+ const val SECTION_THEME = "Theme"
+ const val SECTION_DEBUG = "Debug"
+
+ const val PREF_MEMORY_WARNING_SHOWN = "MemoryWarningShown"
+
+ const val PREF_OVERLAY_VERSION = "OverlayVersion"
+ const val PREF_LANDSCAPE_OVERLAY_VERSION = "LandscapeOverlayVersion"
+ const val PREF_PORTRAIT_OVERLAY_VERSION = "PortraitOverlayVersion"
+ const val PREF_FOLDABLE_OVERLAY_VERSION = "FoldableOverlayVersion"
+ val overlayLayoutPrefs = listOf(
+ PREF_LANDSCAPE_OVERLAY_VERSION,
+ PREF_PORTRAIT_OVERLAY_VERSION,
+ PREF_FOLDABLE_OVERLAY_VERSION
+ )
+
+ const val PREF_CONTROL_SCALE = "controlScale"
+ const val PREF_CONTROL_OPACITY = "controlOpacity"
+ const val PREF_TOUCH_ENABLED = "isTouchEnabled"
+ const val PREF_BUTTON_A = "buttonToggle0"
+ const val PREF_BUTTON_B = "buttonToggle1"
+ const val PREF_BUTTON_X = "buttonToggle2"
+ const val PREF_BUTTON_Y = "buttonToggle3"
+ const val PREF_BUTTON_L = "buttonToggle4"
+ const val PREF_BUTTON_R = "buttonToggle5"
+ const val PREF_BUTTON_ZL = "buttonToggle6"
+ const val PREF_BUTTON_ZR = "buttonToggle7"
+ const val PREF_BUTTON_PLUS = "buttonToggle8"
+ const val PREF_BUTTON_MINUS = "buttonToggle9"
+ const val PREF_BUTTON_DPAD = "buttonToggle10"
+ const val PREF_STICK_L = "buttonToggle11"
+ const val PREF_STICK_R = "buttonToggle12"
+ const val PREF_BUTTON_STICK_L = "buttonToggle13"
+ const val PREF_BUTTON_STICK_R = "buttonToggle14"
+ const val PREF_BUTTON_HOME = "buttonToggle15"
+ const val PREF_BUTTON_SCREENSHOT = "buttonToggle16"
+
+ const val PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER = "EmulationMenuSettings_JoystickRelCenter"
+ const val PREF_MENU_SETTINGS_DPAD_SLIDE = "EmulationMenuSettings_DpadSlideEnable"
+ const val PREF_MENU_SETTINGS_HAPTICS = "EmulationMenuSettings_Haptics"
+ const val PREF_MENU_SETTINGS_SHOW_FPS = "EmulationMenuSettings_ShowFps"
+ const val PREF_MENU_SETTINGS_SHOW_OVERLAY = "EmulationMenuSettings_ShowOverlay"
+
+ const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch"
+ const val PREF_THEME = "Theme"
+ const val PREF_THEME_MODE = "ThemeMode"
+ const val PREF_BLACK_BACKGROUNDS = "BlackBackgrounds"
+
+ val overlayPreferences = listOf(
+ PREF_OVERLAY_VERSION,
+ PREF_CONTROL_SCALE,
+ PREF_CONTROL_OPACITY,
+ PREF_TOUCH_ENABLED,
+ PREF_BUTTON_A,
+ PREF_BUTTON_B,
+ PREF_BUTTON_X,
+ PREF_BUTTON_Y,
+ PREF_BUTTON_L,
+ PREF_BUTTON_R,
+ PREF_BUTTON_ZL,
+ PREF_BUTTON_ZR,
+ PREF_BUTTON_PLUS,
+ PREF_BUTTON_MINUS,
+ PREF_BUTTON_DPAD,
+ PREF_STICK_L,
+ PREF_STICK_R,
+ PREF_BUTTON_HOME,
+ PREF_BUTTON_SCREENSHOT,
+ PREF_BUTTON_STICK_L,
+ PREF_BUTTON_STICK_R
+ )
+
+ const val LayoutOption_Unspecified = 0
+ const val LayoutOption_MobilePortrait = 4
+ const val LayoutOption_MobileLandscape = 5
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ShortSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ShortSetting.kt
new file mode 100644
index 000000000..c9a0c664c
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ShortSetting.kt
@@ -0,0 +1,25 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.features.settings.model
+
+import org.yuzu.yuzu_emu.utils.NativeConfig
+
+enum class ShortSetting(
+ override val key: String,
+ override val category: Settings.Category
+) : AbstractShortSetting {
+ RENDERER_SPEED_LIMIT("speed_limit", Settings.Category.Core);
+
+ override val short: Short
+ get() = NativeConfig.getShort(key, false)
+
+ override fun setShort(value: Short) = NativeConfig.setShort(key, value)
+
+ override val defaultValue: Short by lazy { NativeConfig.getShort(key, true) }
+
+ override val valueAsString: String
+ get() = short.toString()
+
+ override fun reset() = NativeConfig.setShort(key, defaultValue)
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/StringSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/StringSetting.kt
index 6621289fd..9bb3e66d4 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/StringSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/StringSetting.kt
@@ -3,36 +3,24 @@
package org.yuzu.yuzu_emu.features.settings.model
+import org.yuzu.yuzu_emu.utils.NativeConfig
+
enum class StringSetting(
override val key: String,
- override val section: String,
- override val defaultValue: String
+ override val category: Settings.Category
) : AbstractStringSetting {
- AUDIO_OUTPUT_ENGINE("output_engine", Settings.SECTION_AUDIO, "auto"),
- CUSTOM_RTC("custom_rtc", Settings.SECTION_SYSTEM, "0");
+ // No string settings currently exist
+ EMPTY_SETTING("", Settings.Category.UiGeneral);
+
+ override val string: String
+ get() = NativeConfig.getString(key, false)
+
+ override fun setString(value: String) = NativeConfig.setString(key, value)
- override var string: String = defaultValue
+ override val defaultValue: String by lazy { NativeConfig.getString(key, true) }
override val valueAsString: String
get() = string
- override val isRuntimeEditable: Boolean
- get() {
- for (setting in NOT_RUNTIME_EDITABLE) {
- if (setting == this) {
- return false
- }
- }
- return true
- }
-
- companion object {
- private val NOT_RUNTIME_EDITABLE = listOf(
- CUSTOM_RTC
- )
-
- fun from(key: String): StringSetting? = StringSetting.values().firstOrNull { it.key == key }
-
- fun clear() = StringSetting.values().forEach { it.string = it.defaultValue }
- }
+ override fun reset() = NativeConfig.setString(key, defaultValue)
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/DateTimeSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/DateTimeSetting.kt
index bc0bf7788..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 @@
package org.yuzu.yuzu_emu.features.settings.model.view
-import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
-import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting
+import org.yuzu.yuzu_emu.features.settings.model.AbstractLongSetting
class DateTimeSetting(
- setting: AbstractSetting?,
+ private val longSetting: AbstractLongSetting,
titleId: Int,
- descriptionId: Int,
- val key: String? = null,
- private val defaultValue: String? = null
-) : SettingsItem(setting, titleId, descriptionId) {
+ descriptionId: Int
+) : SettingsItem(longSetting, titleId, descriptionId) {
override val type = TYPE_DATETIME_SETTING
- val value: String
- get() = if (setting != null) {
- val setting = setting as AbstractStringSetting
- setting.string
- } else {
- defaultValue!!
- }
-
- fun setSelectedValue(datetime: String): AbstractStringSetting {
- val stringSetting = setting as AbstractStringSetting
- stringSetting.string = datetime
- return stringSetting
- }
+ var value: Long
+ get() = longSetting.long
+ set(value) = (setting as AbstractLongSetting).setLong(value)
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/HeaderSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/HeaderSetting.kt
index a67001311..d31ce1c31 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/HeaderSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/HeaderSetting.kt
@@ -5,6 +5,6 @@ package org.yuzu.yuzu_emu.features.settings.model.view
class HeaderSetting(
titleId: Int
-) : SettingsItem(null, titleId, 0) {
+) : SettingsItem(emptySetting, titleId, 0) {
override val type = TYPE_HEADER
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/RunnableSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/RunnableSetting.kt
index caaab50d8..522cc49df 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/RunnableSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/RunnableSetting.kt
@@ -8,6 +8,6 @@ class RunnableSetting(
descriptionId: Int,
val isRuntimeRunnable: Boolean,
val runnable: () -> Unit
-) : SettingsItem(null, titleId, descriptionId) {
+) : SettingsItem(emptySetting, titleId, descriptionId) {
override val type = TYPE_RUNNABLE
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt
index 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 @@
package org.yuzu.yuzu_emu.features.settings.model.view
import org.yuzu.yuzu_emu.NativeLibrary
+import org.yuzu.yuzu_emu.R
+import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting
import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
+import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
+import org.yuzu.yuzu_emu.features.settings.model.ByteSetting
+import org.yuzu.yuzu_emu.features.settings.model.IntSetting
+import org.yuzu.yuzu_emu.features.settings.model.LongSetting
+import org.yuzu.yuzu_emu.features.settings.model.Settings
+import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
/**
* ViewModel abstraction for an Item in the RecyclerView powering SettingsFragments.
@@ -14,7 +22,7 @@ import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
* file.)
*/
abstract class SettingsItem(
- var setting: AbstractSetting?,
+ val setting: AbstractSetting,
val nameId: Int,
val descriptionId: Int
) {
@@ -23,7 +31,7 @@ abstract class SettingsItem(
val isEditable: Boolean
get() {
if (!NativeLibrary.isRunning()) return true
- return setting?.isRuntimeEditable ?: false
+ return setting.isRuntimeModifiable
}
companion object {
@@ -35,5 +43,240 @@ abstract class SettingsItem(
const val TYPE_STRING_SINGLE_CHOICE = 5
const val TYPE_DATETIME_SETTING = 6
const val TYPE_RUNNABLE = 7
+
+ const val FASTMEM_COMBINED = "fastmem_combined"
+
+ val emptySetting = object : AbstractSetting {
+ override val key: String = ""
+ override val category: Settings.Category = Settings.Category.Ui
+ override val defaultValue: Any = false
+ override fun reset() {}
+ }
+
+ // Extension for putting SettingsItems into a hashmap without repeating yourself
+ fun HashMap<String, SettingsItem>.put(item: SettingsItem) {
+ put(item.setting.key, item)
+ }
+
+ // List of all general
+ val settingsItems = HashMap<String, SettingsItem>().apply {
+ put(
+ SwitchSetting(
+ BooleanSetting.RENDERER_USE_SPEED_LIMIT,
+ R.string.frame_limit_enable,
+ R.string.frame_limit_enable_description
+ )
+ )
+ put(
+ SliderSetting(
+ ShortSetting.RENDERER_SPEED_LIMIT,
+ R.string.frame_limit_slider,
+ R.string.frame_limit_slider_description,
+ 1,
+ 200,
+ "%"
+ )
+ )
+ put(
+ SingleChoiceSetting(
+ IntSetting.CPU_ACCURACY,
+ R.string.cpu_accuracy,
+ 0,
+ R.array.cpuAccuracyNames,
+ R.array.cpuAccuracyValues
+ )
+ )
+ put(
+ SwitchSetting(
+ BooleanSetting.PICTURE_IN_PICTURE,
+ R.string.picture_in_picture,
+ R.string.picture_in_picture_description
+ )
+ )
+ put(
+ SwitchSetting(
+ BooleanSetting.USE_DOCKED_MODE,
+ R.string.use_docked_mode,
+ R.string.use_docked_mode_description
+ )
+ )
+ put(
+ SingleChoiceSetting(
+ IntSetting.REGION_INDEX,
+ R.string.emulated_region,
+ 0,
+ R.array.regionNames,
+ R.array.regionValues
+ )
+ )
+ put(
+ SingleChoiceSetting(
+ IntSetting.LANGUAGE_INDEX,
+ R.string.emulated_language,
+ 0,
+ R.array.languageNames,
+ R.array.languageValues
+ )
+ )
+ put(
+ SwitchSetting(
+ BooleanSetting.USE_CUSTOM_RTC,
+ R.string.use_custom_rtc,
+ R.string.use_custom_rtc_description
+ )
+ )
+ put(DateTimeSetting(LongSetting.CUSTOM_RTC, R.string.set_custom_rtc, 0))
+ put(
+ SingleChoiceSetting(
+ IntSetting.RENDERER_ACCURACY,
+ R.string.renderer_accuracy,
+ 0,
+ R.array.rendererAccuracyNames,
+ R.array.rendererAccuracyValues
+ )
+ )
+ put(
+ SingleChoiceSetting(
+ IntSetting.RENDERER_RESOLUTION,
+ R.string.renderer_resolution,
+ 0,
+ R.array.rendererResolutionNames,
+ R.array.rendererResolutionValues
+ )
+ )
+ put(
+ SingleChoiceSetting(
+ IntSetting.RENDERER_VSYNC,
+ R.string.renderer_vsync,
+ 0,
+ R.array.rendererVSyncNames,
+ R.array.rendererVSyncValues
+ )
+ )
+ put(
+ SingleChoiceSetting(
+ IntSetting.RENDERER_SCALING_FILTER,
+ R.string.renderer_scaling_filter,
+ 0,
+ R.array.rendererScalingFilterNames,
+ R.array.rendererScalingFilterValues
+ )
+ )
+ put(
+ SingleChoiceSetting(
+ IntSetting.RENDERER_ANTI_ALIASING,
+ R.string.renderer_anti_aliasing,
+ 0,
+ R.array.rendererAntiAliasingNames,
+ R.array.rendererAntiAliasingValues
+ )
+ )
+ put(
+ SingleChoiceSetting(
+ IntSetting.RENDERER_SCREEN_LAYOUT,
+ R.string.renderer_screen_layout,
+ 0,
+ R.array.rendererScreenLayoutNames,
+ R.array.rendererScreenLayoutValues
+ )
+ )
+ put(
+ SingleChoiceSetting(
+ IntSetting.RENDERER_ASPECT_RATIO,
+ R.string.renderer_aspect_ratio,
+ 0,
+ R.array.rendererAspectRatioNames,
+ R.array.rendererAspectRatioValues
+ )
+ )
+ put(
+ SwitchSetting(
+ BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE,
+ R.string.use_disk_shader_cache,
+ R.string.use_disk_shader_cache_description
+ )
+ )
+ put(
+ SwitchSetting(
+ BooleanSetting.RENDERER_FORCE_MAX_CLOCK,
+ R.string.renderer_force_max_clock,
+ R.string.renderer_force_max_clock_description
+ )
+ )
+ put(
+ SwitchSetting(
+ BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS,
+ R.string.renderer_asynchronous_shaders,
+ R.string.renderer_asynchronous_shaders_description
+ )
+ )
+ put(
+ SwitchSetting(
+ BooleanSetting.RENDERER_REACTIVE_FLUSHING,
+ R.string.renderer_reactive_flushing,
+ R.string.renderer_reactive_flushing_description
+ )
+ )
+ put(
+ SingleChoiceSetting(
+ IntSetting.AUDIO_OUTPUT_ENGINE,
+ R.string.audio_output_engine,
+ 0,
+ R.array.outputEngineEntries,
+ R.array.outputEngineValues
+ )
+ )
+ put(
+ SliderSetting(
+ ByteSetting.AUDIO_VOLUME,
+ R.string.audio_volume,
+ R.string.audio_volume_description,
+ 0,
+ 100,
+ "%"
+ )
+ )
+ put(
+ SingleChoiceSetting(
+ IntSetting.RENDERER_BACKEND,
+ R.string.renderer_api,
+ 0,
+ R.array.rendererApiNames,
+ R.array.rendererApiValues
+ )
+ )
+ put(
+ SwitchSetting(
+ BooleanSetting.RENDERER_DEBUG,
+ R.string.renderer_debug,
+ R.string.renderer_debug_description
+ )
+ )
+ put(
+ SwitchSetting(
+ BooleanSetting.CPU_DEBUG_MODE,
+ R.string.cpu_debug_mode,
+ R.string.cpu_debug_mode_description
+ )
+ )
+
+ val fastmem = object : AbstractBooleanSetting {
+ override val boolean: Boolean
+ get() =
+ BooleanSetting.FASTMEM.boolean && BooleanSetting.FASTMEM_EXCLUSIVES.boolean
+
+ override fun setBoolean(value: Boolean) {
+ BooleanSetting.FASTMEM.setBoolean(value)
+ BooleanSetting.FASTMEM_EXCLUSIVES.setBoolean(value)
+ }
+
+ override val key: String = FASTMEM_COMBINED
+ override val category = Settings.Category.Cpu
+ override val isRuntimeModifiable: Boolean = false
+ override val defaultValue: Boolean = true
+ override fun reset() = setBoolean(defaultValue)
+ }
+ put(SwitchSetting(fastmem, R.string.fastmem, 0))
+ }
}
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/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 @@
package org.yuzu.yuzu_emu.features.settings.model.view
import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
+import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
class SingleChoiceSetting(
- setting: AbstractIntSetting?,
+ setting: AbstractSetting,
titleId: Int,
descriptionId: Int,
val choicesId: Int,
- val valuesId: Int,
- val key: String? = null,
- val defaultValue: Int? = null
+ val valuesId: Int
) : SettingsItem(setting, titleId, descriptionId) {
override val type = TYPE_SINGLE_CHOICE
- val selectedValue: Int
- get() = if (setting != null) {
- val setting = setting as AbstractIntSetting
- setting.int
- } else {
- defaultValue!!
+ var selectedValue: Int
+ get() {
+ return when (setting) {
+ is AbstractIntSetting -> setting.int
+ else -> -1
+ }
+ }
+ set(value) {
+ when (setting) {
+ is AbstractIntSetting -> setting.setInt(value)
+ }
}
-
- /**
- * Write a value to the backing int. If that int was previously null,
- * initializes a new one and returns it, so it can be added to the Hashmap.
- *
- * @param selection New value of the int.
- * @return the existing setting with the new value applied.
- */
- fun setSelectedValue(selection: Int): AbstractIntSetting {
- val intSetting = setting as AbstractIntSetting
- intSetting.int = selection
- return intSetting
- }
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SliderSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SliderSetting.kt
index 92d0167ae..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 @@
package org.yuzu.yuzu_emu.features.settings.model.view
-import kotlin.math.roundToInt
+import org.yuzu.yuzu_emu.features.settings.model.AbstractByteSetting
import org.yuzu.yuzu_emu.features.settings.model.AbstractFloatSetting
import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
-import org.yuzu.yuzu_emu.utils.Log
+import org.yuzu.yuzu_emu.features.settings.model.AbstractShortSetting
+import kotlin.math.roundToInt
class SliderSetting(
- setting: AbstractSetting?,
+ setting: AbstractSetting,
titleId: Int,
descriptionId: Int,
val min: Int,
val max: Int,
- val units: String,
- val key: String? = null,
- val defaultValue: Int? = null
+ val units: String
) : SettingsItem(setting, titleId, descriptionId) {
override val type = TYPE_SLIDER
- val selectedValue: Int
+ var selectedValue: Int
get() {
- val setting = setting ?: return defaultValue!!
return when (setting) {
+ is AbstractByteSetting -> setting.byte.toInt()
+ is AbstractShortSetting -> setting.short.toInt()
is AbstractIntSetting -> setting.int
is AbstractFloatSetting -> setting.float.roundToInt()
- else -> {
- Log.error("[SliderSetting] Error casting setting type.")
- -1
- }
+ else -> -1
+ }
+ }
+ set(value) {
+ when (setting) {
+ is AbstractByteSetting -> setting.setByte(value.toByte())
+ is AbstractShortSetting -> setting.setShort(value.toShort())
+ is AbstractIntSetting -> setting.setInt(value)
+ is AbstractFloatSetting -> setting.setFloat(value.toFloat())
}
}
-
- /**
- * Write a value to the backing int. If that int was previously null,
- * initializes a new one and returns it, so it can be added to the Hashmap.
- *
- * @param selection New value of the int.
- * @return the existing setting with the new value applied.
- */
- fun setSelectedValue(selection: Int): AbstractIntSetting {
- val intSetting = setting as AbstractIntSetting
- intSetting.int = selection
- return intSetting
- }
-
- /**
- * Write a value to the backing float. If that float was previously null,
- * initializes a new one and returns it, so it can be added to the Hashmap.
- *
- * @param selection New value of the float.
- * @return the existing setting with the new value applied.
- */
- fun setSelectedValue(selection: Float): AbstractFloatSetting {
- val floatSetting = setting as AbstractFloatSetting
- floatSetting.float = selection
- return floatSetting
- }
}
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 @@
package org.yuzu.yuzu_emu.features.settings.model.view
-import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting
class StringSingleChoiceSetting(
- setting: AbstractSetting?,
+ private val stringSetting: AbstractStringSetting,
titleId: Int,
descriptionId: Int,
val choices: Array<String>,
- val values: Array<String>?,
- val key: String? = null,
- private val defaultValue: String? = null
-) : SettingsItem(setting, titleId, descriptionId) {
+ val values: Array<String>
+) : SettingsItem(stringSetting, titleId, descriptionId) {
override val type = TYPE_STRING_SINGLE_CHOICE
- fun getValueAt(index: Int): String? {
- if (values == null) return null
- return if (index >= 0 && index < values.size) {
- values[index]
- } else {
- ""
- }
- }
+ fun getValueAt(index: Int): String =
+ if (index >= 0 && index < values.size) values[index] else ""
+
+ var selectedValue: String
+ get() = stringSetting.string
+ set(value) = stringSetting.setString(value)
- val selectedValue: String
- get() = if (setting != null) {
- val setting = setting as AbstractStringSetting
- setting.string
- } else {
- defaultValue!!
- }
val selectValueIndex: Int
get() {
- val selectedValue = selectedValue
- for (i in values!!.indices) {
+ for (i in values.indices) {
if (values[i] == selectedValue) {
return i
}
}
return -1
}
-
- /**
- * Write a value to the backing int. If that int was previously null,
- * initializes a new one and returns it, so it can be added to the Hashmap.
- *
- * @param selection New value of the int.
- * @return the existing setting with the new value applied.
- */
- fun setSelectedValue(selection: String): AbstractStringSetting {
- val stringSetting = setting as AbstractStringSetting
- stringSetting.string = selection
- return stringSetting
- }
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt
index 8a9d13a92..91c273964 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt
@@ -7,6 +7,6 @@ class SubmenuSetting(
titleId: Int,
descriptionId: Int,
val menuKey: String
-) : SettingsItem(null, titleId, descriptionId) {
+) : SettingsItem(emptySetting, titleId, descriptionId) {
override val type = TYPE_SUBMENU
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SwitchSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SwitchSetting.kt
index 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
class SwitchSetting(
setting: AbstractSetting,
titleId: Int,
- descriptionId: Int,
- val key: String? = null,
- val defaultValue: Any? = null
+ descriptionId: Int
) : SettingsItem(setting, titleId, descriptionId) {
override val type = TYPE_SWITCH
- val isChecked: Boolean
+ var checked: Boolean
get() {
- if (setting == null) {
- return defaultValue as Boolean
+ return when (setting) {
+ is AbstractIntSetting -> setting.int == 1
+ is AbstractBooleanSetting -> setting.boolean
+ else -> false
}
-
- // Try integer setting
- try {
- val setting = setting as AbstractIntSetting
- return setting.int == 1
- } catch (_: ClassCastException) {
- }
-
- // Try boolean setting
- try {
- val setting = setting as AbstractBooleanSetting
- return setting.boolean
- } catch (_: ClassCastException) {
- }
- return defaultValue as Boolean
}
-
- /**
- * Write a value to the backing boolean. If that boolean was previously null,
- * initializes a new one and returns it, so it can be added to the Hashmap.
- *
- * @param checked Pretty self explanatory.
- * @return the existing setting with the new value applied.
- */
- fun setChecked(checked: Boolean): AbstractSetting {
- // Try integer setting
- try {
- val setting = setting as AbstractIntSetting
- setting.int = if (checked) 1 else 0
- return setting
- } catch (_: ClassCastException) {
+ set(value) {
+ when (setting) {
+ is AbstractIntSetting -> setting.setInt(if (value) 1 else 0)
+ is AbstractBooleanSetting -> setting.setBoolean(value)
+ }
}
-
- // Try boolean setting
- val setting = setting as AbstractBooleanSetting
- setting.boolean = checked
- return setting
- }
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt
index a5af5a7ae..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,42 +3,34 @@
package org.yuzu.yuzu_emu.features.settings.ui
-import android.content.Context
-import android.content.Intent
import android.os.Bundle
-import android.view.Menu
import android.view.View
import android.view.ViewGroup.MarginLayoutParams
import android.widget.Toast
import androidx.activity.OnBackPressedCallback
-import androidx.activity.result.ActivityResultLauncher
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
-import androidx.core.view.updatePadding
+import androidx.navigation.fragment.NavHostFragment
+import androidx.navigation.navArgs
import com.google.android.material.color.MaterialColors
import java.io.IOException
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding
-import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
-import org.yuzu.yuzu_emu.features.settings.model.FloatSetting
-import org.yuzu.yuzu_emu.features.settings.model.IntSetting
import org.yuzu.yuzu_emu.features.settings.model.Settings
-import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel
-import org.yuzu.yuzu_emu.features.settings.model.StringSetting
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
+import org.yuzu.yuzu_emu.fragments.ResetSettingsDialogFragment
+import org.yuzu.yuzu_emu.model.SettingsViewModel
import org.yuzu.yuzu_emu.utils.*
-class SettingsActivity : AppCompatActivity(), SettingsActivityView {
- private val presenter = SettingsActivityPresenter(this)
-
+class SettingsActivity : AppCompatActivity() {
private lateinit var binding: ActivitySettingsBinding
- private val settingsViewModel: SettingsViewModel by viewModels()
+ private val args by navArgs<SettingsActivityArgs>()
- override val settings: Settings get() = settingsViewModel.settings
+ private val settingsViewModel: SettingsViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
ThemeHelper.setTheme(this)
@@ -48,16 +40,17 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
binding = ActivitySettingsBinding.inflate(layoutInflater)
setContentView(binding.root)
- WindowCompat.setDecorFitsSystemWindows(window, false)
+ settingsViewModel.game = args.game
- val launcher = intent
- val gameID = launcher.getStringExtra(ARG_GAME_ID)
- val menuTag = launcher.getStringExtra(ARG_MENU_TAG)
- presenter.onCreate(savedInstanceState, menuTag!!, gameID!!)
+ val navHostFragment =
+ supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment
+ navHostFragment.navController.setGraph(R.navigation.settings_navigation, intent.extras)
- // Show "Back" button in the action bar for navigation
- setSupportActionBar(binding.toolbarSettings)
- supportActionBar!!.setDisplayHomeAsUpEnabled(true)
+ WindowCompat.setDecorFitsSystemWindows(window, false)
+
+ if (savedInstanceState != null) {
+ settingsViewModel.shouldSave = savedInstanceState.getBoolean(KEY_SHOULD_SAVE)
+ }
if (InsetsHelper.getSystemGestureType(applicationContext) !=
InsetsHelper.GESTURE_NAVIGATION
@@ -73,6 +66,28 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
)
}
+ settingsViewModel.shouldRecreate.observe(this) {
+ if (it) {
+ settingsViewModel.setShouldRecreate(false)
+ recreate()
+ }
+ }
+ settingsViewModel.shouldNavigateBack.observe(this) {
+ if (it) {
+ settingsViewModel.setShouldNavigateBack(false)
+ navigateBack()
+ }
+ }
+ settingsViewModel.shouldShowResetSettingsDialog.observe(this) {
+ if (it) {
+ settingsViewModel.setShouldShowResetSettingsDialog(false)
+ ResetSettingsDialogFragment().show(
+ supportFragmentManager,
+ ResetSettingsDialogFragment.TAG
+ )
+ }
+ }
+
onBackPressedDispatcher.addCallback(
this,
object : OnBackPressedCallback(true) {
@@ -83,34 +98,28 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
setInsets()
}
- override fun onSupportNavigateUp(): Boolean {
- navigateBack()
- return true
- }
-
- private fun navigateBack() {
- if (supportFragmentManager.backStackEntryCount > 0) {
- supportFragmentManager.popBackStack()
+ fun navigateBack() {
+ val navHostFragment =
+ supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment
+ if (navHostFragment.childFragmentManager.backStackEntryCount > 0) {
+ navHostFragment.navController.popBackStack()
} else {
finish()
}
}
- override fun onCreateOptionsMenu(menu: Menu): Boolean {
- val inflater = menuInflater
- inflater.inflate(R.menu.menu_settings, menu)
- return true
- }
-
override fun onSaveInstanceState(outState: Bundle) {
// Critical: If super method is not called, rotations will be busted.
super.onSaveInstanceState(outState)
- presenter.saveState(outState)
+ outState.putBoolean(KEY_SHOULD_SAVE, settingsViewModel.shouldSave)
}
override fun onStart() {
super.onStart()
- presenter.onStart()
+ // TODO: Load custom settings contextually
+ if (!DirectoryInitialization.areDirectoriesReady) {
+ DirectoryInitialization.start()
+ }
}
/**
@@ -120,143 +129,51 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
*/
override fun onStop() {
super.onStop()
- presenter.onStop(isFinishing)
- }
-
- override fun showSettingsFragment(menuTag: String, addToStack: Boolean, gameId: String) {
- if (!addToStack && settingsFragment != null) {
- return
- }
-
- val transaction = supportFragmentManager.beginTransaction()
- if (addToStack) {
- if (areSystemAnimationsEnabled()) {
- transaction.setCustomAnimations(
- R.anim.anim_settings_fragment_in,
- R.anim.anim_settings_fragment_out,
- 0,
- R.anim.anim_pop_settings_fragment_out
- )
- }
- transaction.addToBackStack(null)
+ if (isFinishing && settingsViewModel.shouldSave) {
+ Log.debug("[SettingsActivity] Settings activity stopping. Saving settings to INI...")
+ Settings.saveSettings()
}
- transaction.replace(
- R.id.frame_content,
- SettingsFragment.newInstance(menuTag, gameId),
- FRAGMENT_TAG
- )
- transaction.commit()
}
- private fun areSystemAnimationsEnabled(): Boolean {
- val duration = android.provider.Settings.Global.getFloat(
- contentResolver,
- android.provider.Settings.Global.ANIMATOR_DURATION_SCALE,
- 1f
- )
- val transition = android.provider.Settings.Global.getFloat(
- contentResolver,
- android.provider.Settings.Global.TRANSITION_ANIMATION_SCALE,
- 1f
- )
- return duration != 0f && transition != 0f
- }
-
- override fun onSettingsFileLoaded() {
- val fragment: SettingsFragmentView? = settingsFragment
- fragment?.loadSettingsList()
- }
-
- override fun onSettingsFileNotFound() {
- val fragment: SettingsFragmentView? = settingsFragment
- fragment?.loadSettingsList()
- }
-
- override fun showToastMessage(message: String, is_long: Boolean) {
- Toast.makeText(
- this,
- message,
- if (is_long) Toast.LENGTH_LONG else Toast.LENGTH_SHORT
- ).show()
- }
-
- override fun onSettingChanged() {
- presenter.onSettingChanged()
+ override fun onDestroy() {
+ settingsViewModel.clear()
+ super.onDestroy()
}
fun onSettingsReset() {
// Prevents saving to a non-existent settings file
- presenter.onSettingsReset()
-
- // Reset the static memory representation of each setting
- BooleanSetting.clear()
- FloatSetting.clear()
- IntSetting.clear()
- StringSetting.clear()
+ settingsViewModel.shouldSave = false
// Delete settings file because the user may have changed values that do not exist in the UI
val settingsFile = SettingsFile.getSettingsFile(SettingsFile.FILE_NAME_CONFIG)
if (!settingsFile.delete()) {
throw IOException("Failed to delete $settingsFile")
}
+ Settings.settingsList.forEach { it.reset() }
- showToastMessage(getString(R.string.settings_reset), true)
+ Toast.makeText(
+ applicationContext,
+ getString(R.string.settings_reset),
+ Toast.LENGTH_LONG
+ ).show()
finish()
}
- fun setToolbarTitle(title: String) {
- binding.toolbarSettingsLayout.title = title
- }
-
- private val settingsFragment: SettingsFragment?
- get() = supportFragmentManager.findFragmentByTag(FRAGMENT_TAG) as SettingsFragment?
-
private fun setInsets() {
ViewCompat.setOnApplyWindowInsetsListener(
- binding.frameContent
+ binding.navigationBarShade
) { view: View, windowInsets: WindowInsetsCompat ->
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
- val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
- view.updatePadding(
- left = barInsets.left + cutoutInsets.left,
- right = barInsets.right + cutoutInsets.right
- )
- val mlpAppBar = binding.appbarSettings.layoutParams as MarginLayoutParams
- mlpAppBar.leftMargin = barInsets.left + cutoutInsets.left
- mlpAppBar.rightMargin = barInsets.right + cutoutInsets.right
- binding.appbarSettings.layoutParams = mlpAppBar
-
- val mlpShade = binding.navigationBarShade.layoutParams as MarginLayoutParams
+ val mlpShade = view.layoutParams as MarginLayoutParams
mlpShade.height = barInsets.bottom
- binding.navigationBarShade.layoutParams = mlpShade
+ view.layoutParams = mlpShade
windowInsets
}
}
companion object {
- private const val ARG_MENU_TAG = "menu_tag"
- private const val ARG_GAME_ID = "game_id"
- private const val FRAGMENT_TAG = "settings"
-
- fun launch(context: Context, menuTag: String?, gameId: String?) {
- val settings = Intent(context, SettingsActivity::class.java)
- settings.putExtra(ARG_MENU_TAG, menuTag)
- settings.putExtra(ARG_GAME_ID, gameId)
- context.startActivity(settings)
- }
-
- fun launch(
- context: Context,
- launcher: ActivityResultLauncher<Intent>,
- menuTag: String?,
- gameId: String?
- ) {
- val settings = Intent(context, SettingsActivity::class.java)
- settings.putExtra(ARG_MENU_TAG, menuTag)
- settings.putExtra(ARG_GAME_ID, gameId)
- launcher.launch(settings)
- }
+ private const val KEY_SHOULD_SAVE = "should_save"
}
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityPresenter.kt
deleted file mode 100644
index 93e677b21..000000000
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityPresenter.kt
+++ /dev/null
@@ -1,90 +0,0 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-package org.yuzu.yuzu_emu.features.settings.ui
-
-import android.content.Context
-import android.os.Bundle
-import android.text.TextUtils
-import java.io.File
-import org.yuzu.yuzu_emu.NativeLibrary
-import org.yuzu.yuzu_emu.features.settings.model.Settings
-import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
-import org.yuzu.yuzu_emu.utils.DirectoryInitialization
-import org.yuzu.yuzu_emu.utils.Log
-
-class SettingsActivityPresenter(private val activityView: SettingsActivityView) {
- val settings: Settings get() = activityView.settings
-
- private var shouldSave = false
- private lateinit var menuTag: String
- private lateinit var gameId: String
-
- fun onCreate(savedInstanceState: Bundle?, menuTag: String, gameId: String) {
- this.menuTag = menuTag
- this.gameId = gameId
- if (savedInstanceState != null) {
- shouldSave = savedInstanceState.getBoolean(KEY_SHOULD_SAVE)
- }
- }
-
- fun onStart() {
- prepareDirectoriesIfNeeded()
- }
-
- private fun loadSettingsUI() {
- if (!settings.isLoaded) {
- if (!TextUtils.isEmpty(gameId)) {
- settings.loadSettings(gameId, activityView)
- } else {
- settings.loadSettings(activityView)
- }
- }
- activityView.showSettingsFragment(menuTag, false, gameId)
- activityView.onSettingsFileLoaded()
- }
-
- private fun prepareDirectoriesIfNeeded() {
- val configFile =
- File(
- "${DirectoryInitialization.userDirectory}/config/" +
- "${SettingsFile.FILE_NAME_CONFIG}.ini"
- )
- if (!configFile.exists()) {
- Log.error(
- "${DirectoryInitialization.userDirectory}/config/" +
- "${SettingsFile.FILE_NAME_CONFIG}.ini"
- )
- Log.error("yuzu config file could not be found!")
- }
-
- if (!DirectoryInitialization.areDirectoriesReady) {
- DirectoryInitialization.start(activityView as Context)
- }
- loadSettingsUI()
- }
-
- fun onStop(finishing: Boolean) {
- if (finishing && shouldSave) {
- Log.debug("[SettingsActivity] Settings activity stopping. Saving settings to INI...")
- settings.saveSettings(activityView)
- }
- NativeLibrary.reloadSettings()
- }
-
- fun onSettingChanged() {
- shouldSave = true
- }
-
- fun onSettingsReset() {
- shouldSave = false
- }
-
- fun saveState(outState: Bundle) {
- outState.putBoolean(KEY_SHOULD_SAVE, shouldSave)
- }
-
- companion object {
- private const val KEY_SHOULD_SAVE = "should_save"
- }
-}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityView.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityView.kt
deleted file mode 100644
index c186fc388..000000000
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityView.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-package org.yuzu.yuzu_emu.features.settings.ui
-
-import org.yuzu.yuzu_emu.features.settings.model.Settings
-
-/**
- * Abstraction for the Activity that manages SettingsFragments.
- */
-interface SettingsActivityView {
- /**
- * Show a new SettingsFragment.
- *
- * @param menuTag Identifier for the settings group that should be displayed.
- * @param addToStack Whether or not this fragment should replace a previous one.
- */
- fun showSettingsFragment(menuTag: String, addToStack: Boolean, gameId: String)
-
- /**
- * Called by a contained Fragment to get access to the Setting HashMap
- * loaded from disk, so that each Fragment doesn't need to perform its own
- * read operation.
- *
- * @return A HashMap of Settings.
- */
- val settings: Settings
-
- /**
- * Called when a load operation completes.
- */
- fun onSettingsFileLoaded()
-
- /**
- * Called when a load operation fails.
- */
- fun onSettingsFileNotFound()
-
- /**
- * Display a popup text message on screen.
- *
- * @param message The contents of the onscreen message.
- * @param is_long Whether this should be a long Toast or short one.
- */
- fun showToastMessage(message: String, is_long: Boolean)
-
- /**
- * End the activity.
- */
- fun finish()
-
- /**
- * Called by a containing Fragment to tell the Activity that a setting was changed;
- * unless this has been called, the Activity will not save to disk.
- */
- fun onSettingChanged()
-}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt
index 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 @@
package org.yuzu.yuzu_emu.features.settings.ui
import android.content.Context
-import android.content.DialogInterface
import android.icu.util.Calendar
import android.icu.util.TimeZone
import android.text.format.DateFormat
import android.view.LayoutInflater
import android.view.ViewGroup
-import android.widget.TextView
-import androidx.appcompat.app.AlertDialog
-import androidx.appcompat.app.AppCompatActivity
-import androidx.recyclerview.widget.RecyclerView
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import androidx.navigation.findNavController
+import androidx.recyclerview.widget.AsyncDifferConfig
+import androidx.recyclerview.widget.DiffUtil
+import androidx.recyclerview.widget.ListAdapter
import com.google.android.material.datepicker.MaterialDatePicker
-import com.google.android.material.dialog.MaterialAlertDialogBuilder
-import com.google.android.material.slider.Slider
import com.google.android.material.timepicker.MaterialTimePicker
import com.google.android.material.timepicker.TimeFormat
+import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.R
-import org.yuzu.yuzu_emu.databinding.DialogSliderBinding
+import org.yuzu.yuzu_emu.SettingsNavigationDirections
import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
import org.yuzu.yuzu_emu.databinding.ListItemSettingSwitchBinding
import org.yuzu.yuzu_emu.databinding.ListItemSettingsHeaderBinding
-import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting
-import org.yuzu.yuzu_emu.features.settings.model.AbstractFloatSetting
-import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
-import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
-import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting
-import org.yuzu.yuzu_emu.features.settings.model.FloatSetting
import org.yuzu.yuzu_emu.features.settings.model.view.*
import org.yuzu.yuzu_emu.features.settings.ui.viewholder.*
+import org.yuzu.yuzu_emu.fragments.SettingsDialogFragment
+import org.yuzu.yuzu_emu.model.SettingsViewModel
class SettingsAdapter(
- private val fragmentView: SettingsFragmentView,
+ private val fragment: Fragment,
private val context: Context
-) : RecyclerView.Adapter<SettingViewHolder?>(), DialogInterface.OnClickListener {
- private var settings: ArrayList<SettingsItem>? = null
- private var clickedItem: SettingsItem? = null
- private var clickedPosition: Int
- private var dialog: AlertDialog? = null
- private var sliderProgress = 0
- private var textSliderValue: TextView? = null
-
- private var defaultCancelListener =
- DialogInterface.OnClickListener { _: DialogInterface?, _: Int -> closeDialog() }
+) : ListAdapter<SettingsItem, SettingViewHolder>(
+ AsyncDifferConfig.Builder(DiffCallback()).build()
+) {
+ private val settingsViewModel: SettingsViewModel
+ get() = ViewModelProvider(fragment.requireActivity())[SettingsViewModel::class.java]
init {
- clickedPosition = -1
+ fragment.viewLifecycleOwner.lifecycleScope.launch {
+ fragment.repeatOnLifecycle(Lifecycle.State.STARTED) {
+ settingsViewModel.adapterItemChanged.collect {
+ if (it != -1) {
+ notifyItemChanged(it)
+ settingsViewModel.setAdapterItemChanged(-1)
+ }
+ }
+ }
+ }
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SettingViewHolder {
@@ -90,67 +93,41 @@ class SettingsAdapter(
}
override fun onBindViewHolder(holder: SettingViewHolder, position: Int) {
- holder.bind(getItem(position))
+ holder.bind(currentList[position])
}
- private fun getItem(position: Int): SettingsItem {
- return settings!![position]
- }
-
- override fun getItemCount(): Int {
- return if (settings != null) {
- settings!!.size
- } else {
- 0
- }
- }
+ override fun getItemCount(): Int = currentList.size
override fun getItemViewType(position: Int): Int {
- return getItem(position).type
- }
-
- fun setSettingsList(settings: ArrayList<SettingsItem>?) {
- this.settings = settings
- notifyDataSetChanged()
- }
-
- fun onBooleanClick(item: SwitchSetting, position: Int, checked: Boolean) {
- val setting = item.setChecked(checked)
- fragmentView.putSetting(setting)
- fragmentView.onSettingChanged()
+ return currentList[position].type
}
- private fun onSingleChoiceClick(item: SingleChoiceSetting) {
- clickedItem = item
- val value = getSelectionForSingleChoiceValue(item)
- dialog = MaterialAlertDialogBuilder(context)
- .setTitle(item.nameId)
- .setSingleChoiceItems(item.choicesId, value, this)
- .show()
+ fun onBooleanClick(item: SwitchSetting, checked: Boolean) {
+ item.checked = checked
+ settingsViewModel.setShouldReloadSettingsList(true)
+ settingsViewModel.shouldSave = true
}
fun onSingleChoiceClick(item: SingleChoiceSetting, position: Int) {
- clickedPosition = position
- onSingleChoiceClick(item)
- }
-
- private fun onStringSingleChoiceClick(item: StringSingleChoiceSetting) {
- clickedItem = item
- dialog = MaterialAlertDialogBuilder(context)
- .setTitle(item.nameId)
- .setSingleChoiceItems(item.choices, item.selectValueIndex, this)
- .show()
+ SettingsDialogFragment.newInstance(
+ settingsViewModel,
+ item,
+ SettingsItem.TYPE_SINGLE_CHOICE,
+ position
+ ).show(fragment.childFragmentManager, SettingsDialogFragment.TAG)
}
fun onStringSingleChoiceClick(item: StringSingleChoiceSetting, position: Int) {
- clickedPosition = position
- onStringSingleChoiceClick(item)
+ SettingsDialogFragment.newInstance(
+ settingsViewModel,
+ item,
+ SettingsItem.TYPE_STRING_SINGLE_CHOICE,
+ position
+ ).show(fragment.childFragmentManager, SettingsDialogFragment.TAG)
}
fun onDateTimeClick(item: DateTimeSetting, position: Int) {
- clickedItem = item
- clickedPosition = position
- val storedTime = java.lang.Long.decode(item.value) * 1000
+ val storedTime = item.value * 1000
// Helper to extract hour and minute from epoch time
val calendar: Calendar = Calendar.getInstance()
@@ -158,7 +135,7 @@ class SettingsAdapter(
calendar.timeZone = TimeZone.getTimeZone("UTC")
var timeFormat: Int = TimeFormat.CLOCK_12H
- if (DateFormat.is24HourFormat(fragmentView.activityView as AppCompatActivity)) {
+ if (DateFormat.is24HourFormat(context)) {
timeFormat = TimeFormat.CLOCK_24H
}
@@ -175,7 +152,7 @@ class SettingsAdapter(
datePicker.addOnPositiveButtonClickListener {
timePicker.show(
- (fragmentView.activityView as AppCompatActivity).supportFragmentManager,
+ fragment.childFragmentManager,
"TimePicker"
)
}
@@ -183,160 +160,50 @@ class SettingsAdapter(
var epochTime: Long = datePicker.selection!! / 1000
epochTime += timePicker.hour.toLong() * 60 * 60
epochTime += timePicker.minute.toLong() * 60
- val rtcString = epochTime.toString()
- if (item.value != rtcString) {
- fragmentView.onSettingChanged()
+ if (item.value != epochTime) {
+ settingsViewModel.shouldSave = true
+ notifyItemChanged(position)
+ item.value = epochTime
}
- notifyItemChanged(clickedPosition)
- val setting = item.setSelectedValue(rtcString)
- fragmentView.putSetting(setting)
- clickedItem = null
}
datePicker.show(
- (fragmentView.activityView as AppCompatActivity).supportFragmentManager,
+ fragment.childFragmentManager,
"DatePicker"
)
}
fun onSliderClick(item: SliderSetting, position: Int) {
- clickedItem = item
- clickedPosition = position
- sliderProgress = item.selectedValue
-
- val inflater = LayoutInflater.from(context)
- val sliderBinding = DialogSliderBinding.inflate(inflater)
-
- textSliderValue = sliderBinding.textValue
- textSliderValue!!.text = String.format(
- context.getString(R.string.value_with_units),
- sliderProgress.toString(),
- item.units
- )
-
- sliderBinding.slider.apply {
- valueFrom = item.min.toFloat()
- valueTo = item.max.toFloat()
- value = sliderProgress.toFloat()
- addOnChangeListener { _: Slider, value: Float, _: Boolean ->
- sliderProgress = value.toInt()
- textSliderValue!!.text = String.format(
- context.getString(R.string.value_with_units),
- sliderProgress.toString(),
- item.units
- )
- }
- }
-
- dialog = MaterialAlertDialogBuilder(context)
- .setTitle(item.nameId)
- .setView(sliderBinding.root)
- .setPositiveButton(android.R.string.ok, this)
- .setNegativeButton(android.R.string.cancel, defaultCancelListener)
- .show()
+ SettingsDialogFragment.newInstance(
+ settingsViewModel,
+ item,
+ SettingsItem.TYPE_SLIDER,
+ position
+ ).show(fragment.childFragmentManager, SettingsDialogFragment.TAG)
}
fun onSubmenuClick(item: SubmenuSetting) {
- fragmentView.loadSubMenu(item.menuKey)
+ val action = SettingsNavigationDirections.actionGlobalSettingsFragment(item.menuKey, null)
+ fragment.view?.findNavController()?.navigate(action)
}
- override fun onClick(dialog: DialogInterface, which: Int) {
- when (clickedItem) {
- is SingleChoiceSetting -> {
- val scSetting = clickedItem as SingleChoiceSetting
- val value = getValueForSingleChoiceSelection(scSetting, which)
- if (scSetting.selectedValue != value) {
- fragmentView.onSettingChanged()
- }
-
- // Get the backing Setting, which may be null (if for example it was missing from the file)
- val setting = scSetting.setSelectedValue(value)
- fragmentView.putSetting(setting)
- closeDialog()
- }
-
- is StringSingleChoiceSetting -> {
- val scSetting = clickedItem as StringSingleChoiceSetting
- val value = scSetting.getValueAt(which)
- if (scSetting.selectedValue != value) fragmentView.onSettingChanged()
- val setting = scSetting.setSelectedValue(value!!)
- fragmentView.putSetting(setting)
- closeDialog()
- }
-
- is SliderSetting -> {
- val sliderSetting = clickedItem as SliderSetting
- if (sliderSetting.selectedValue != sliderProgress) {
- fragmentView.onSettingChanged()
- }
- if (sliderSetting.setting is FloatSetting) {
- val value = sliderProgress.toFloat()
- val setting = sliderSetting.setSelectedValue(value)
- fragmentView.putSetting(setting)
- } else {
- val setting = sliderSetting.setSelectedValue(sliderProgress)
- fragmentView.putSetting(setting)
- }
- closeDialog()
- }
- }
- clickedItem = null
- sliderProgress = -1
- }
-
- fun onLongClick(setting: AbstractSetting, position: Int): Boolean {
- MaterialAlertDialogBuilder(context)
- .setMessage(R.string.reset_setting_confirmation)
- .setPositiveButton(android.R.string.ok) { dialog: DialogInterface, which: Int ->
- when (setting) {
- is AbstractBooleanSetting -> setting.boolean = setting.defaultValue as Boolean
- is AbstractFloatSetting -> setting.float = setting.defaultValue as Float
- is AbstractIntSetting -> setting.int = setting.defaultValue as Int
- is AbstractStringSetting -> setting.string = setting.defaultValue as String
- }
- notifyItemChanged(position)
- fragmentView.onSettingChanged()
- }
- .setNegativeButton(android.R.string.cancel, null)
- .show()
+ fun onLongClick(item: SettingsItem, position: Int): Boolean {
+ SettingsDialogFragment.newInstance(
+ settingsViewModel,
+ item,
+ SettingsDialogFragment.TYPE_RESET_SETTING,
+ position
+ ).show(fragment.childFragmentManager, SettingsDialogFragment.TAG)
return true
}
- fun closeDialog() {
- if (dialog != null) {
- if (clickedPosition != -1) {
- notifyItemChanged(clickedPosition)
- clickedPosition = -1
- }
- dialog!!.dismiss()
- dialog = null
+ private class DiffCallback : DiffUtil.ItemCallback<SettingsItem>() {
+ override fun areItemsTheSame(oldItem: SettingsItem, newItem: SettingsItem): Boolean {
+ return oldItem.setting.key == newItem.setting.key
}
- }
- private fun getValueForSingleChoiceSelection(item: SingleChoiceSetting, which: Int): Int {
- val valuesId = item.valuesId
- return if (valuesId > 0) {
- val valuesArray = context.resources.getIntArray(valuesId)
- valuesArray[which]
- } else {
- which
- }
- }
-
- private fun getSelectionForSingleChoiceValue(item: SingleChoiceSetting): Int {
- val value = item.selectedValue
- val valuesId = item.valuesId
- if (valuesId > 0) {
- val valuesArray = context.resources.getIntArray(valuesId)
- for (index in valuesArray.indices) {
- val current = valuesArray[index]
- if (current == value) {
- return index
- }
- }
- } else {
- return value
+ override fun areContentsTheSame(oldItem: SettingsItem, newItem: SettingsItem): Boolean {
+ return oldItem.setting.key == newItem.setting.key
}
- return -1
}
}
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 @@
package org.yuzu.yuzu_emu.features.settings.ui
-import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import android.view.ViewGroup.MarginLayoutParams
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
+import androidx.navigation.findNavController
+import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.divider.MaterialDividerItemDecoration
+import com.google.android.material.transition.MaterialSharedAxis
+import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.FragmentSettingsBinding
-import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
-import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
+import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
+import org.yuzu.yuzu_emu.model.SettingsViewModel
-class SettingsFragment : Fragment(), SettingsFragmentView {
- override var activityView: SettingsActivityView? = null
-
- private val fragmentPresenter = SettingsFragmentPresenter(this)
+class SettingsFragment : Fragment() {
+ private lateinit var presenter: SettingsFragmentPresenter
private var settingsAdapter: SettingsAdapter? = null
private var _binding: FragmentSettingsBinding? = null
private val binding get() = _binding!!
- override fun onAttach(context: Context) {
- super.onAttach(context)
- activityView = requireActivity() as SettingsActivityView
- }
+ private val args by navArgs<SettingsFragmentArgs>()
+
+ private val settingsViewModel: SettingsViewModel by activityViewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- val menuTag = requireArguments().getString(ARGUMENT_MENU_TAG)
- val gameId = requireArguments().getString(ARGUMENT_GAME_ID)
- fragmentPresenter.onCreate(menuTag!!, gameId!!)
+ enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
+ returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
+ reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
+ exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
}
override fun onCreateView(
@@ -49,7 +52,14 @@ class SettingsFragment : Fragment(), SettingsFragmentView {
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- settingsAdapter = SettingsAdapter(this, requireActivity())
+ settingsAdapter = SettingsAdapter(this, requireContext())
+ presenter = SettingsFragmentPresenter(
+ settingsViewModel,
+ settingsAdapter!!,
+ args.menuTag,
+ args.game?.gameId ?: ""
+ )
+
val dividerDecoration = MaterialDividerItemDecoration(
requireContext(),
LinearLayoutManager.VERTICAL
@@ -57,71 +67,86 @@ class SettingsFragment : Fragment(), SettingsFragmentView {
dividerDecoration.isLastItemDecorated = false
binding.listSettings.apply {
adapter = settingsAdapter
- layoutManager = LinearLayoutManager(activity)
+ layoutManager = LinearLayoutManager(requireContext())
addItemDecoration(dividerDecoration)
}
- fragmentPresenter.onViewCreated()
- setInsets()
- }
+ binding.toolbarSettings.setNavigationOnClickListener {
+ settingsViewModel.setShouldNavigateBack(true)
+ }
- override fun onDetach() {
- super.onDetach()
- activityView = null
- if (settingsAdapter != null) {
- settingsAdapter!!.closeDialog()
+ settingsViewModel.toolbarTitle.observe(viewLifecycleOwner) {
+ if (it.isNotEmpty()) binding.toolbarSettingsLayout.title = it
}
- }
- override fun showSettingsList(settingsList: ArrayList<SettingsItem>) {
- settingsAdapter!!.setSettingsList(settingsList)
- }
+ settingsViewModel.shouldReloadSettingsList.observe(viewLifecycleOwner) {
+ if (it) {
+ settingsViewModel.setShouldReloadSettingsList(false)
+ presenter.loadSettingsList()
+ }
+ }
- override fun loadSettingsList() {
- fragmentPresenter.loadSettingsList()
- }
+ settingsViewModel.isUsingSearch.observe(viewLifecycleOwner) {
+ if (it) {
+ reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true)
+ exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false)
+ } else {
+ reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
+ exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
+ }
+ }
- override fun loadSubMenu(menuKey: String) {
- activityView!!.showSettingsFragment(
- menuKey,
- true,
- requireArguments().getString(ARGUMENT_GAME_ID)!!
- )
- }
+ if (args.menuTag == SettingsFile.FILE_NAME_CONFIG) {
+ binding.toolbarSettings.inflateMenu(R.menu.menu_settings)
+ binding.toolbarSettings.setOnMenuItemClickListener {
+ when (it.itemId) {
+ R.id.action_search -> {
+ reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true)
+ exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false)
+ view.findNavController()
+ .navigate(R.id.action_settingsFragment_to_settingsSearchFragment)
+ true
+ }
+
+ else -> false
+ }
+ }
+ }
- override fun showToastMessage(message: String?, is_long: Boolean) {
- activityView!!.showToastMessage(message!!, is_long)
- }
+ presenter.onViewCreated()
- override fun putSetting(setting: AbstractSetting) {
- fragmentPresenter.putSetting(setting)
+ setInsets()
}
- override fun onSettingChanged() {
- activityView!!.onSettingChanged()
+ override fun onResume() {
+ super.onResume()
+ settingsViewModel.setIsUsingSearch(false)
}
private fun setInsets() {
ViewCompat.setOnApplyWindowInsetsListener(
- binding.listSettings
- ) { view: View, windowInsets: WindowInsetsCompat ->
- val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
- view.updatePadding(bottom = insets.bottom)
+ binding.root
+ ) { _: View, windowInsets: WindowInsetsCompat ->
+ val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
+ val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
+
+ val leftInsets = barInsets.left + cutoutInsets.left
+ val rightInsets = barInsets.right + cutoutInsets.right
+
+ val sideMargin = resources.getDimensionPixelSize(R.dimen.spacing_medlarge)
+ val mlpSettingsList = binding.listSettings.layoutParams as MarginLayoutParams
+ mlpSettingsList.leftMargin = sideMargin + leftInsets
+ mlpSettingsList.rightMargin = sideMargin + rightInsets
+ binding.listSettings.layoutParams = mlpSettingsList
+ binding.listSettings.updatePadding(
+ bottom = barInsets.bottom
+ )
+
+ val mlpAppBar = binding.appbarSettings.layoutParams as MarginLayoutParams
+ mlpAppBar.leftMargin = leftInsets
+ mlpAppBar.rightMargin = rightInsets
+ binding.appbarSettings.layoutParams = mlpAppBar
windowInsets
}
}
-
- companion object {
- private const val ARGUMENT_MENU_TAG = "menu_tag"
- private const val ARGUMENT_GAME_ID = "game_id"
-
- fun newInstance(menuTag: String?, gameId: String?): Fragment {
- val fragment = SettingsFragment()
- val arguments = Bundle()
- arguments.putString(ARGUMENT_MENU_TAG, menuTag)
- arguments.putString(ARGUMENT_GAME_ID, gameId)
- fragment.arguments = arguments
- return fragment
- }
- }
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt
index 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 @@
package org.yuzu.yuzu_emu.features.settings.ui
+import android.content.Context
import android.content.SharedPreferences
import android.os.Build
import android.text.TextUtils
+import android.widget.Toast
import androidx.preference.PreferenceManager
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting
import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
-import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
+import org.yuzu.yuzu_emu.features.settings.model.ByteSetting
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
+import org.yuzu.yuzu_emu.features.settings.model.LongSetting
import org.yuzu.yuzu_emu.features.settings.model.Settings
-import org.yuzu.yuzu_emu.features.settings.model.StringSetting
+import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
import org.yuzu.yuzu_emu.features.settings.model.view.*
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
-import org.yuzu.yuzu_emu.fragments.ResetSettingsDialogFragment
-import org.yuzu.yuzu_emu.utils.ThemeHelper
+import org.yuzu.yuzu_emu.model.SettingsViewModel
+import org.yuzu.yuzu_emu.utils.NativeConfig
-class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) {
- private var menuTag: String? = null
- private lateinit var gameId: String
- private var settingsList: ArrayList<SettingsItem>? = null
+class SettingsFragmentPresenter(
+ private val settingsViewModel: SettingsViewModel,
+ private val adapter: SettingsAdapter,
+ private var menuTag: String,
+ private var gameId: String
+) {
+ private var settingsList = ArrayList<SettingsItem>()
- private val settingsActivity get() = fragmentView.activityView as SettingsActivity
- private val settings get() = fragmentView.activityView!!.settings
+ private val preferences: SharedPreferences
+ get() = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
- private lateinit var preferences: SharedPreferences
+ private val context: Context get() = YuzuApplication.appContext
- fun onCreate(menuTag: String, gameId: String) {
- this.gameId = gameId
- this.menuTag = menuTag
+ // Extension for populating settings list based on paired settings
+ fun ArrayList<SettingsItem>.add(key: String) {
+ val item = SettingsItem.settingsItems[key]!!
+ val pairedSettingKey = item.setting.pairedSettingKey
+ if (pairedSettingKey.isNotEmpty()) {
+ val pairedSettingValue = NativeConfig.getBoolean(pairedSettingKey, false)
+ if (!pairedSettingValue) return
+ }
+ add(item)
}
fun onViewCreated() {
- preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
loadSettingsList()
}
- fun putSetting(setting: AbstractSetting) {
- if (setting.section == null || setting.key == null) {
- return
- }
-
- val section = settings.getSection(setting.section!!)!!
- if (section.getSetting(setting.key!!) == null) {
- section.putSetting(setting)
- }
- }
-
fun loadSettingsList() {
if (!TextUtils.isEmpty(gameId)) {
- settingsActivity.setToolbarTitle("Game Settings: $gameId")
+ settingsViewModel.setToolbarTitle(
+ context.getString(
+ R.string.advanced_settings_game,
+ gameId
+ )
+ )
}
+
val sl = ArrayList<SettingsItem>()
- if (menuTag == null) {
- return
- }
when (menuTag) {
SettingsFile.FILE_NAME_CONFIG -> addConfigSettings(sl)
Settings.SECTION_GENERAL -> addGeneralSettings(sl)
@@ -69,335 +72,104 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
Settings.SECTION_THEME -> addThemeSettings(sl)
Settings.SECTION_DEBUG -> addDebugSettings(sl)
else -> {
- fragmentView.showToastMessage("Unimplemented menu", false)
+ val context = YuzuApplication.appContext
+ Toast.makeText(
+ context,
+ context.getString(R.string.unimplemented_menu),
+ Toast.LENGTH_SHORT
+ ).show()
return
}
}
settingsList = sl
- fragmentView.showSettingsList(settingsList!!)
+ adapter.submitList(settingsList)
}
private fun addConfigSettings(sl: ArrayList<SettingsItem>) {
- settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.advanced_settings))
+ settingsViewModel.setToolbarTitle(context.getString(R.string.advanced_settings))
sl.apply {
- add(
- SubmenuSetting(
- R.string.preferences_general,
- 0,
- Settings.SECTION_GENERAL
- )
- )
- add(
- SubmenuSetting(
- R.string.preferences_system,
- 0,
- Settings.SECTION_SYSTEM
- )
- )
- add(
- SubmenuSetting(
- R.string.preferences_graphics,
- 0,
- Settings.SECTION_RENDERER
- )
- )
- add(
- SubmenuSetting(
- R.string.preferences_audio,
- 0,
- Settings.SECTION_AUDIO
- )
- )
- add(
- SubmenuSetting(
- R.string.preferences_debug,
- 0,
- Settings.SECTION_DEBUG
- )
- )
- add(
- RunnableSetting(
- R.string.reset_to_default,
- 0,
- false
- ) {
- ResetSettingsDialogFragment().show(
- settingsActivity.supportFragmentManager,
- ResetSettingsDialogFragment.TAG
- )
+ add(SubmenuSetting(R.string.preferences_general, 0, Settings.SECTION_GENERAL))
+ add(SubmenuSetting(R.string.preferences_system, 0, Settings.SECTION_SYSTEM))
+ add(SubmenuSetting(R.string.preferences_graphics, 0, Settings.SECTION_RENDERER))
+ add(SubmenuSetting(R.string.preferences_audio, 0, Settings.SECTION_AUDIO))
+ add(SubmenuSetting(R.string.preferences_debug, 0, Settings.SECTION_DEBUG))
+ add(
+ RunnableSetting(R.string.reset_to_default, 0, false) {
+ settingsViewModel.setShouldShowResetSettingsDialog(true)
}
)
}
}
private fun addGeneralSettings(sl: ArrayList<SettingsItem>) {
- settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_general))
+ settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_general))
sl.apply {
- add(
- SwitchSetting(
- IntSetting.RENDERER_USE_SPEED_LIMIT,
- R.string.frame_limit_enable,
- R.string.frame_limit_enable_description,
- IntSetting.RENDERER_USE_SPEED_LIMIT.key,
- IntSetting.RENDERER_USE_SPEED_LIMIT.defaultValue
- )
- )
- add(
- SliderSetting(
- IntSetting.RENDERER_SPEED_LIMIT,
- R.string.frame_limit_slider,
- R.string.frame_limit_slider_description,
- 1,
- 200,
- "%",
- IntSetting.RENDERER_SPEED_LIMIT.key,
- IntSetting.RENDERER_SPEED_LIMIT.defaultValue
- )
- )
- add(
- SingleChoiceSetting(
- IntSetting.CPU_ACCURACY,
- R.string.cpu_accuracy,
- 0,
- R.array.cpuAccuracyNames,
- R.array.cpuAccuracyValues,
- IntSetting.CPU_ACCURACY.key,
- IntSetting.CPU_ACCURACY.defaultValue
- )
- )
- add(
- SwitchSetting(
- BooleanSetting.PICTURE_IN_PICTURE,
- R.string.picture_in_picture,
- R.string.picture_in_picture_description,
- BooleanSetting.PICTURE_IN_PICTURE.key,
- BooleanSetting.PICTURE_IN_PICTURE.defaultValue
- )
- )
+ add(BooleanSetting.RENDERER_USE_SPEED_LIMIT.key)
+ add(ShortSetting.RENDERER_SPEED_LIMIT.key)
+ add(IntSetting.CPU_ACCURACY.key)
+ add(BooleanSetting.PICTURE_IN_PICTURE.key)
}
}
private fun addSystemSettings(sl: ArrayList<SettingsItem>) {
- settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_system))
+ settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_system))
sl.apply {
- add(
- SwitchSetting(
- IntSetting.USE_DOCKED_MODE,
- R.string.use_docked_mode,
- R.string.use_docked_mode_description,
- IntSetting.USE_DOCKED_MODE.key,
- IntSetting.USE_DOCKED_MODE.defaultValue
- )
- )
- add(
- SingleChoiceSetting(
- IntSetting.REGION_INDEX,
- R.string.emulated_region,
- 0,
- R.array.regionNames,
- R.array.regionValues,
- IntSetting.REGION_INDEX.key,
- IntSetting.REGION_INDEX.defaultValue
- )
- )
- add(
- SingleChoiceSetting(
- IntSetting.LANGUAGE_INDEX,
- R.string.emulated_language,
- 0,
- R.array.languageNames,
- R.array.languageValues,
- IntSetting.LANGUAGE_INDEX.key,
- IntSetting.LANGUAGE_INDEX.defaultValue
- )
- )
- add(
- SwitchSetting(
- BooleanSetting.USE_CUSTOM_RTC,
- R.string.use_custom_rtc,
- R.string.use_custom_rtc_description,
- BooleanSetting.USE_CUSTOM_RTC.key,
- BooleanSetting.USE_CUSTOM_RTC.defaultValue
- )
- )
- add(
- DateTimeSetting(
- StringSetting.CUSTOM_RTC,
- R.string.set_custom_rtc,
- 0,
- StringSetting.CUSTOM_RTC.key,
- StringSetting.CUSTOM_RTC.defaultValue
- )
- )
+ add(BooleanSetting.USE_DOCKED_MODE.key)
+ add(IntSetting.REGION_INDEX.key)
+ add(IntSetting.LANGUAGE_INDEX.key)
+ add(BooleanSetting.USE_CUSTOM_RTC.key)
+ add(LongSetting.CUSTOM_RTC.key)
}
}
private fun addGraphicsSettings(sl: ArrayList<SettingsItem>) {
- settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_graphics))
+ settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_graphics))
sl.apply {
- add(
- SingleChoiceSetting(
- IntSetting.RENDERER_ACCURACY,
- R.string.renderer_accuracy,
- 0,
- R.array.rendererAccuracyNames,
- R.array.rendererAccuracyValues,
- IntSetting.RENDERER_ACCURACY.key,
- IntSetting.RENDERER_ACCURACY.defaultValue
- )
- )
- add(
- SingleChoiceSetting(
- IntSetting.RENDERER_RESOLUTION,
- R.string.renderer_resolution,
- 0,
- R.array.rendererResolutionNames,
- R.array.rendererResolutionValues,
- IntSetting.RENDERER_RESOLUTION.key,
- IntSetting.RENDERER_RESOLUTION.defaultValue
- )
- )
- add(
- SingleChoiceSetting(
- IntSetting.RENDERER_VSYNC,
- R.string.renderer_vsync,
- 0,
- R.array.rendererVSyncNames,
- R.array.rendererVSyncValues,
- IntSetting.RENDERER_VSYNC.key,
- IntSetting.RENDERER_VSYNC.defaultValue
- )
- )
- add(
- SingleChoiceSetting(
- IntSetting.RENDERER_SCALING_FILTER,
- R.string.renderer_scaling_filter,
- 0,
- R.array.rendererScalingFilterNames,
- R.array.rendererScalingFilterValues,
- IntSetting.RENDERER_SCALING_FILTER.key,
- IntSetting.RENDERER_SCALING_FILTER.defaultValue
- )
- )
- add(
- SingleChoiceSetting(
- IntSetting.RENDERER_ANTI_ALIASING,
- R.string.renderer_anti_aliasing,
- 0,
- R.array.rendererAntiAliasingNames,
- R.array.rendererAntiAliasingValues,
- IntSetting.RENDERER_ANTI_ALIASING.key,
- IntSetting.RENDERER_ANTI_ALIASING.defaultValue
- )
- )
- add(
- SingleChoiceSetting(
- IntSetting.RENDERER_SCREEN_LAYOUT,
- R.string.renderer_screen_layout,
- 0,
- R.array.rendererScreenLayoutNames,
- R.array.rendererScreenLayoutValues,
- IntSetting.RENDERER_SCREEN_LAYOUT.key,
- IntSetting.RENDERER_SCREEN_LAYOUT.defaultValue
- )
- )
- add(
- SingleChoiceSetting(
- IntSetting.RENDERER_ASPECT_RATIO,
- R.string.renderer_aspect_ratio,
- 0,
- R.array.rendererAspectRatioNames,
- R.array.rendererAspectRatioValues,
- IntSetting.RENDERER_ASPECT_RATIO.key,
- IntSetting.RENDERER_ASPECT_RATIO.defaultValue
- )
- )
- add(
- SwitchSetting(
- IntSetting.RENDERER_USE_DISK_SHADER_CACHE,
- R.string.use_disk_shader_cache,
- R.string.use_disk_shader_cache_description,
- IntSetting.RENDERER_USE_DISK_SHADER_CACHE.key,
- IntSetting.RENDERER_USE_DISK_SHADER_CACHE.defaultValue
- )
- )
- add(
- SwitchSetting(
- IntSetting.RENDERER_FORCE_MAX_CLOCK,
- R.string.renderer_force_max_clock,
- R.string.renderer_force_max_clock_description,
- IntSetting.RENDERER_FORCE_MAX_CLOCK.key,
- IntSetting.RENDERER_FORCE_MAX_CLOCK.defaultValue
- )
- )
- add(
- SwitchSetting(
- IntSetting.RENDERER_ASYNCHRONOUS_SHADERS,
- R.string.renderer_asynchronous_shaders,
- R.string.renderer_asynchronous_shaders_description,
- IntSetting.RENDERER_ASYNCHRONOUS_SHADERS.key,
- IntSetting.RENDERER_ASYNCHRONOUS_SHADERS.defaultValue
- )
- )
- add(
- SwitchSetting(
- IntSetting.RENDERER_REACTIVE_FLUSHING,
- R.string.renderer_reactive_flushing,
- R.string.renderer_reactive_flushing_description,
- IntSetting.RENDERER_REACTIVE_FLUSHING.key,
- IntSetting.RENDERER_REACTIVE_FLUSHING.defaultValue
- )
- )
+ add(IntSetting.RENDERER_ACCURACY.key)
+ add(IntSetting.RENDERER_RESOLUTION.key)
+ add(IntSetting.RENDERER_VSYNC.key)
+ add(IntSetting.RENDERER_SCALING_FILTER.key)
+ add(IntSetting.RENDERER_ANTI_ALIASING.key)
+ add(IntSetting.RENDERER_SCREEN_LAYOUT.key)
+ add(IntSetting.RENDERER_ASPECT_RATIO.key)
+ add(BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE.key)
+ add(BooleanSetting.RENDERER_FORCE_MAX_CLOCK.key)
+ add(BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS.key)
+ add(BooleanSetting.RENDERER_REACTIVE_FLUSHING.key)
}
}
private fun addAudioSettings(sl: ArrayList<SettingsItem>) {
- settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_audio))
+ settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_audio))
sl.apply {
- add(
- StringSingleChoiceSetting(
- StringSetting.AUDIO_OUTPUT_ENGINE,
- R.string.audio_output_engine,
- 0,
- settingsActivity.resources.getStringArray(R.array.outputEngineEntries),
- settingsActivity.resources.getStringArray(R.array.outputEngineValues),
- StringSetting.AUDIO_OUTPUT_ENGINE.key,
- StringSetting.AUDIO_OUTPUT_ENGINE.defaultValue
- )
- )
- add(
- SliderSetting(
- IntSetting.AUDIO_VOLUME,
- R.string.audio_volume,
- R.string.audio_volume_description,
- 0,
- 100,
- "%",
- IntSetting.AUDIO_VOLUME.key,
- IntSetting.AUDIO_VOLUME.defaultValue
- )
- )
+ add(IntSetting.AUDIO_OUTPUT_ENGINE.key)
+ add(ByteSetting.AUDIO_VOLUME.key)
}
}
private fun addThemeSettings(sl: ArrayList<SettingsItem>) {
- settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_theme))
+ settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_theme))
sl.apply {
val theme: AbstractIntSetting = object : AbstractIntSetting {
- override var int: Int
+ override val int: Int
get() = preferences.getInt(Settings.PREF_THEME, 0)
- set(value) {
- preferences.edit()
- .putInt(Settings.PREF_THEME, value)
- .apply()
- settingsActivity.recreate()
- }
- override val key: String? = null
- override val section: String? = null
- override val isRuntimeEditable: Boolean = false
- override val valueAsString: String
- get() = preferences.getInt(Settings.PREF_THEME, 0).toString()
- override val defaultValue: Any = 0
+
+ override fun setInt(value: Int) {
+ preferences.edit()
+ .putInt(Settings.PREF_THEME, value)
+ .apply()
+ settingsViewModel.setShouldRecreate(true)
+ }
+
+ override val key: String = Settings.PREF_THEME
+ override val category = Settings.Category.UiGeneral
+ override val isRuntimeModifiable: Boolean = false
+ override val defaultValue: Int = 0
+ override fun reset() {
+ preferences.edit()
+ .putInt(Settings.PREF_THEME, defaultValue)
+ .apply()
+ }
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
@@ -423,20 +195,26 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
}
val themeMode: AbstractIntSetting = object : AbstractIntSetting {
- override var int: Int
+ override val int: Int
get() = preferences.getInt(Settings.PREF_THEME_MODE, -1)
- set(value) {
- preferences.edit()
- .putInt(Settings.PREF_THEME_MODE, value)
- .apply()
- ThemeHelper.setThemeMode(settingsActivity)
- }
- override val key: String? = null
- override val section: String? = null
- override val isRuntimeEditable: Boolean = false
- override val valueAsString: String
- get() = preferences.getInt(Settings.PREF_THEME_MODE, -1).toString()
- override val defaultValue: Any = -1
+
+ override fun setInt(value: Int) {
+ preferences.edit()
+ .putInt(Settings.PREF_THEME_MODE, value)
+ .apply()
+ settingsViewModel.setShouldRecreate(true)
+ }
+
+ override val key: String = Settings.PREF_THEME_MODE
+ override val category = Settings.Category.UiGeneral
+ override val isRuntimeModifiable: Boolean = false
+ override val defaultValue: Int = -1
+ override fun reset() {
+ preferences.edit()
+ .putInt(Settings.PREF_BLACK_BACKGROUNDS, defaultValue)
+ .apply()
+ settingsViewModel.setShouldRecreate(true)
+ }
}
add(
@@ -450,21 +228,26 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
)
val blackBackgrounds: AbstractBooleanSetting = object : AbstractBooleanSetting {
- override var boolean: Boolean
- get() = preferences.getBoolean(Settings.PREF_BLACK_BACKGROUNDS, false)
- set(value) {
- preferences.edit()
- .putBoolean(Settings.PREF_BLACK_BACKGROUNDS, value)
- .apply()
- settingsActivity.recreate()
- }
- override val key: String? = null
- override val section: String? = null
- override val isRuntimeEditable: Boolean = false
- override val valueAsString: String
+ override val boolean: Boolean
get() = preferences.getBoolean(Settings.PREF_BLACK_BACKGROUNDS, false)
- .toString()
- override val defaultValue: Any = false
+
+ override fun setBoolean(value: Boolean) {
+ preferences.edit()
+ .putBoolean(Settings.PREF_BLACK_BACKGROUNDS, value)
+ .apply()
+ settingsViewModel.setShouldRecreate(true)
+ }
+
+ override val key: String = Settings.PREF_BLACK_BACKGROUNDS
+ override val category = Settings.Category.UiGeneral
+ override val isRuntimeModifiable: Boolean = false
+ override val defaultValue: Boolean = false
+ override fun reset() {
+ preferences.edit()
+ .putBoolean(Settings.PREF_BLACK_BACKGROUNDS, defaultValue)
+ .apply()
+ settingsViewModel.setShouldRecreate(true)
+ }
}
add(
@@ -478,62 +261,15 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
}
private fun addDebugSettings(sl: ArrayList<SettingsItem>) {
- settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_debug))
+ settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_debug))
sl.apply {
add(HeaderSetting(R.string.gpu))
- add(
- SingleChoiceSetting(
- IntSetting.RENDERER_BACKEND,
- R.string.renderer_api,
- 0,
- R.array.rendererApiNames,
- R.array.rendererApiValues,
- IntSetting.RENDERER_BACKEND.key,
- IntSetting.RENDERER_BACKEND.defaultValue
- )
- )
- add(
- SwitchSetting(
- IntSetting.RENDERER_DEBUG,
- R.string.renderer_debug,
- R.string.renderer_debug_description,
- IntSetting.RENDERER_DEBUG.key,
- IntSetting.RENDERER_DEBUG.defaultValue
- )
- )
+ add(IntSetting.RENDERER_BACKEND.key)
+ add(BooleanSetting.RENDERER_DEBUG.key)
add(HeaderSetting(R.string.cpu))
- add(
- SwitchSetting(
- BooleanSetting.CPU_DEBUG_MODE,
- R.string.cpu_debug_mode,
- R.string.cpu_debug_mode_description,
- BooleanSetting.CPU_DEBUG_MODE.key,
- BooleanSetting.CPU_DEBUG_MODE.defaultValue
- )
- )
-
- val fastmem = object : AbstractBooleanSetting {
- override var boolean: Boolean
- get() =
- BooleanSetting.FASTMEM.boolean && BooleanSetting.FASTMEM_EXCLUSIVES.boolean
- set(value) {
- BooleanSetting.FASTMEM.boolean = value
- BooleanSetting.FASTMEM_EXCLUSIVES.boolean = value
- }
- override val key: String? = null
- override val section: String = Settings.SECTION_CPU
- override val isRuntimeEditable: Boolean = false
- override val valueAsString: String = ""
- override val defaultValue: Any = true
- }
- add(
- SwitchSetting(
- fastmem,
- R.string.fastmem,
- 0
- )
- )
+ add(BooleanSetting.CPU_DEBUG_MODE.key)
+ add(SettingsItem.FASTMEM_COMBINED)
}
}
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/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 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-package org.yuzu.yuzu_emu.features.settings.ui
-
-import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
-import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
-
-/**
- * Abstraction for a screen showing a list of settings. Instances of
- * this type of view will each display a layer of the setting hierarchy.
- */
-interface SettingsFragmentView {
- /**
- * Pass an ArrayList to the View so that it can be displayed on screen.
- *
- * @param settingsList The result of converting the HashMap to an ArrayList
- */
- fun showSettingsList(settingsList: ArrayList<SettingsItem>)
-
- /**
- * Instructs the Fragment to load the settings screen.
- */
- fun loadSettingsList()
-
- /**
- * @return The Fragment's containing activity.
- */
- val activityView: SettingsActivityView?
-
- /**
- * Tell the Fragment to tell the containing Activity to show a new
- * Fragment containing a submenu of settings.
- *
- * @param menuKey Identifier for the settings group that should be shown.
- */
- fun loadSubMenu(menuKey: String)
-
- /**
- * Tell the Fragment to tell the containing activity to display a toast message.
- *
- * @param message Text to be shown in the Toast
- * @param is_long Whether this should be a long Toast or short one.
- */
- fun showToastMessage(message: String?, is_long: Boolean)
-
- /**
- * Have the fragment add a setting to the HashMap.
- *
- * @param setting The (possibly previously missing) new setting.
- */
- fun putSetting(setting: AbstractSetting)
-
- /**
- * Have the fragment tell the containing Activity that a setting was modified.
- */
- fun onSettingChanged()
-}
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
}
binding.textSettingValue.visibility = View.VISIBLE
- val epochTime = setting.value.toLong()
+ val epochTime = setting.value
val instant = Instant.ofEpochMilli(epochTime * 1000)
val zonedTime = ZonedDateTime.ofInstant(instant, ZoneId.of("UTC"))
val dateFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)
@@ -46,7 +46,7 @@ class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA
override fun onLongClick(clicked: View): Boolean {
if (setting.isEditable) {
- return adapter.onLongClick(setting.setting!!, bindingAdapterPosition)
+ return adapter.onLongClick(setting, bindingAdapterPosition)
}
return false
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt
index b42d955aa..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
}
}
} else if (item is StringSingleChoiceSetting) {
- for (i in item.values!!.indices) {
+ for (i in item.values.indices) {
if (item.values[i] == item.selectedValue) {
binding.textSettingValue.text = item.choices[i]
break
@@ -66,7 +66,7 @@ class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: Setti
override fun onLongClick(clicked: View): Boolean {
if (setting.isEditable) {
- return adapter.onLongClick(setting.setting!!, bindingAdapterPosition)
+ return adapter.onLongClick(setting, bindingAdapterPosition)
}
return false
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SliderViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SliderViewHolder.kt
index a23b5d109..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
override fun onLongClick(clicked: View): Boolean {
if (setting.isEditable) {
- return adapter.onLongClick(setting.setting!!, bindingAdapterPosition)
+ return adapter.onLongClick(setting, bindingAdapterPosition)
}
return false
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt
index ef34bf5f4..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
binding.textSettingDescription.text = ""
binding.textSettingDescription.visibility = View.GONE
}
+
+ binding.switchWidget.setOnCheckedChangeListener(null)
+ binding.switchWidget.isChecked = setting.checked
binding.switchWidget.setOnCheckedChangeListener { _: CompoundButton, _: Boolean ->
- adapter.onBooleanClick(item, bindingAdapterPosition, binding.switchWidget.isChecked)
+ adapter.onBooleanClick(item, binding.switchWidget.isChecked)
}
- binding.switchWidget.isChecked = setting.isChecked
setStyle(setting.isEditable, binding)
}
@@ -41,7 +43,7 @@ class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter
override fun onLongClick(clicked: View): Boolean {
if (setting.isEditable) {
- return adapter.onLongClick(setting.setting!!, bindingAdapterPosition)
+ return adapter.onLongClick(setting, bindingAdapterPosition)
}
return false
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.kt
index 70a52df5d..2b04d666a 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.kt
@@ -3,18 +3,15 @@
package org.yuzu.yuzu_emu.features.settings.utils
+import android.widget.Toast
import java.io.*
-import java.util.*
import org.ini4j.Wini
-import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.features.settings.model.*
-import org.yuzu.yuzu_emu.features.settings.model.Settings.SettingsSectionMap
-import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivityView
-import org.yuzu.yuzu_emu.utils.BiMap
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
import org.yuzu.yuzu_emu.utils.Log
+import org.yuzu.yuzu_emu.utils.NativeConfig
/**
* Contains static methods for interacting with .ini files in which settings are stored.
@@ -22,243 +19,41 @@ import org.yuzu.yuzu_emu.utils.Log
object SettingsFile {
const val FILE_NAME_CONFIG = "config"
- private var sectionsMap = BiMap<String?, String?>()
-
- /**
- * Reads a given .ini file from disk and returns it as a HashMap of Settings, themselves
- * effectively a HashMap of key/value settings. If unsuccessful, outputs an error telling why it
- * failed.
- *
- * @param ini The ini file to load the settings from
- * @param isCustomGame
- * @param view The current view.
- * @return An Observable that emits a HashMap of the file's contents, then completes.
- */
- private fun readFile(
- ini: File?,
- isCustomGame: Boolean,
- view: SettingsActivityView? = null
- ): HashMap<String, SettingSection?> {
- val sections: HashMap<String, SettingSection?> = SettingsSectionMap()
- var reader: BufferedReader? = null
- try {
- reader = BufferedReader(FileReader(ini))
- var current: SettingSection? = null
- var line: String?
- while (reader.readLine().also { line = it } != null) {
- if (line!!.startsWith("[") && line!!.endsWith("]")) {
- current = sectionFromLine(line!!, isCustomGame)
- sections[current.name] = current
- } else if (current != null) {
- val setting = settingFromLine(line!!)
- if (setting != null) {
- current.putSetting(setting)
- }
- }
- }
- } catch (e: FileNotFoundException) {
- Log.error("[SettingsFile] File not found: " + e.message)
- view?.onSettingsFileNotFound()
- } catch (e: IOException) {
- Log.error("[SettingsFile] Error reading from: " + e.message)
- view?.onSettingsFileNotFound()
- } finally {
- if (reader != null) {
- try {
- reader.close()
- } catch (e: IOException) {
- Log.error("[SettingsFile] Error closing: " + e.message)
- }
- }
- }
- return sections
- }
-
- fun readFile(fileName: String, view: SettingsActivityView?): HashMap<String, SettingSection?> {
- return readFile(getSettingsFile(fileName), false, view)
- }
-
- fun readFile(fileName: String): HashMap<String, SettingSection?> =
- readFile(getSettingsFile(fileName), false)
-
- /**
- * Reads a given .ini file from disk and returns it as a HashMap of SettingSections, themselves
- * effectively a HashMap of key/value settings. If unsuccessful, outputs an error telling why it
- * failed.
- *
- * @param gameId the id of the game to load it's settings.
- * @param view The current view.
- */
- fun readCustomGameSettings(
- gameId: String,
- view: SettingsActivityView?
- ): HashMap<String, SettingSection?> {
- return readFile(getCustomGameSettingsFile(gameId), true, view)
- }
-
/**
* Saves a Settings HashMap to a given .ini file on disk. If unsuccessful, outputs an error
* telling why it failed.
*
* @param fileName The target filename without a path or extension.
- * @param sections The HashMap containing the Settings we want to serialize.
- * @param view The current view.
*/
- fun saveFile(
- fileName: String,
- sections: TreeMap<String, SettingSection>,
- view: SettingsActivityView
- ) {
+ fun saveFile(fileName: String) {
val ini = getSettingsFile(fileName)
try {
- val writer = Wini(ini)
- val keySet: Set<String> = sections.keys
- for (key in keySet) {
- val section = sections[key]
- writeSection(writer, section!!)
+ val wini = Wini(ini)
+ for (specificCategory in Settings.Category.values()) {
+ val categoryHeader = NativeConfig.getConfigHeader(specificCategory.ordinal)
+ for (setting in Settings.settingsList) {
+ if (setting.key!!.isEmpty()) continue
+
+ val settingCategoryHeader =
+ NativeConfig.getConfigHeader(setting.category.ordinal)
+ val iniSetting: String? = wini.get(categoryHeader, setting.key)
+ if (iniSetting != null || settingCategoryHeader == categoryHeader) {
+ wini.put(settingCategoryHeader, setting.key, setting.valueAsString)
+ }
+ }
}
- writer.store()
+ wini.store()
} catch (e: IOException) {
Log.error("[SettingsFile] File not found: " + fileName + ".ini: " + e.message)
- view.showToastMessage(
- YuzuApplication.appContext
- .getString(R.string.error_saving, fileName, e.message),
- false
- )
- }
- }
-
- fun saveCustomGameSettings(gameId: String?, sections: HashMap<String, SettingSection?>) {
- val sortedSections: Set<String> = TreeSet(sections.keys)
- for (sectionKey in sortedSections) {
- val section = sections[sectionKey]
- val settings = section!!.settings
- val sortedKeySet: Set<String> = TreeSet(settings.keys)
- for (settingKey in sortedKeySet) {
- val setting = settings[settingKey]
- NativeLibrary.setUserSetting(
- gameId,
- mapSectionNameFromIni(
- section.name
- ),
- setting!!.key,
- setting.valueAsString
- )
- }
- }
- }
-
- private fun mapSectionNameFromIni(generalSectionName: String): String? {
- return if (sectionsMap.getForward(generalSectionName) != null) {
- sectionsMap.getForward(generalSectionName)
- } else {
- generalSectionName
- }
- }
-
- private fun mapSectionNameToIni(generalSectionName: String): String {
- return if (sectionsMap.getBackward(generalSectionName) != null) {
- sectionsMap.getBackward(generalSectionName).toString()
- } else {
- generalSectionName
- }
- }
-
- fun getSettingsFile(fileName: String): File {
- return File(
- DirectoryInitialization.userDirectory + "/config/" + fileName + ".ini"
- )
- }
-
- private fun getCustomGameSettingsFile(gameId: String): File {
- return File(DirectoryInitialization.userDirectory + "/GameSettings/" + gameId + ".ini")
- }
-
- private fun sectionFromLine(line: String, isCustomGame: Boolean): SettingSection {
- var sectionName: String = line.substring(1, line.length - 1)
- if (isCustomGame) {
- sectionName = mapSectionNameToIni(sectionName)
+ val context = YuzuApplication.appContext
+ Toast.makeText(
+ context,
+ context.getString(R.string.error_saving, fileName, e.message),
+ Toast.LENGTH_SHORT
+ ).show()
}
- return SettingSection(sectionName)
}
- /**
- * For a line of text, determines what type of data is being represented, and returns
- * a Setting object containing this data.
- *
- * @param line The line of text being parsed.
- * @return A typed Setting containing the key/value contained in the line.
- */
- private fun settingFromLine(line: String): AbstractSetting? {
- val splitLine = line.split("=".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
- if (splitLine.size != 2) {
- return null
- }
- val key = splitLine[0].trim { it <= ' ' }
- val value = splitLine[1].trim { it <= ' ' }
- if (value.isEmpty()) {
- return null
- }
-
- val booleanSetting = BooleanSetting.from(key)
- if (booleanSetting != null) {
- booleanSetting.boolean = value.toBoolean()
- return booleanSetting
- }
-
- val intSetting = IntSetting.from(key)
- if (intSetting != null) {
- intSetting.int = value.toInt()
- return intSetting
- }
-
- val floatSetting = FloatSetting.from(key)
- if (floatSetting != null) {
- floatSetting.float = value.toFloat()
- return floatSetting
- }
-
- val stringSetting = StringSetting.from(key)
- if (stringSetting != null) {
- stringSetting.string = value
- return stringSetting
- }
-
- return null
- }
-
- /**
- * Writes the contents of a Section HashMap to disk.
- *
- * @param parser A Wini pointed at a file on disk.
- * @param section A section containing settings to be written to the file.
- */
- private fun writeSection(parser: Wini, section: SettingSection) {
- // Write the section header.
- val header = section.name
-
- // Write this section's values.
- val settings = section.settings
- val keySet: Set<String> = settings.keys
- for (key in keySet) {
- val setting = settings[key]
- parser.put(header, setting!!.key, setting.valueAsString)
- }
-
- BooleanSetting.values().forEach {
- if (!keySet.contains(it.key)) {
- parser.put(header, it.key, it.valueAsString)
- }
- }
- IntSetting.values().forEach {
- if (!keySet.contains(it.key)) {
- parser.put(header, it.key, it.valueAsString)
- }
- }
- StringSetting.values().forEach {
- if (!keySet.contains(it.key)) {
- parser.put(header, it.key, it.valueAsString)
- }
- }
- }
+ fun getSettingsFile(fileName: String): File =
+ File(DirectoryInitialization.userDirectory + "/config/" + fileName + ".ini")
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
index 25b9d4018..944ae652e 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
@@ -7,11 +7,11 @@ import android.annotation.SuppressLint
import android.app.AlertDialog
import android.content.Context
import android.content.DialogInterface
-import android.content.Intent
import android.content.SharedPreferences
import android.content.pm.ActivityInfo
import android.content.res.Configuration
import android.graphics.Color
+import android.net.Uri
import android.os.Bundle
import android.os.Handler
import android.os.Looper
@@ -19,18 +19,18 @@ import android.util.Rational
import android.view.*
import android.widget.TextView
import androidx.activity.OnBackPressedCallback
-import androidx.activity.result.ActivityResultLauncher
-import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.widget.PopupMenu
import androidx.core.content.res.ResourcesCompat
import androidx.core.graphics.Insets
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
-import androidx.core.view.isVisible
+import androidx.drawerlayout.widget.DrawerLayout
import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
+import androidx.navigation.findNavController
import androidx.navigation.fragment.navArgs
import androidx.preference.PreferenceManager
import androidx.window.layout.FoldingFeature
@@ -40,6 +40,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.slider.Slider
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
+import org.yuzu.yuzu_emu.HomeNavigationDirections
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication
@@ -48,8 +49,9 @@ import org.yuzu.yuzu_emu.databinding.DialogOverlayAdjustBinding
import org.yuzu.yuzu_emu.databinding.FragmentEmulationBinding
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
import org.yuzu.yuzu_emu.features.settings.model.Settings
-import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
+import org.yuzu.yuzu_emu.model.Game
+import org.yuzu.yuzu_emu.model.EmulationViewModel
import org.yuzu.yuzu_emu.overlay.InputOverlay
import org.yuzu.yuzu_emu.utils.*
@@ -62,11 +64,13 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
private var _binding: FragmentEmulationBinding? = null
private val binding get() = _binding!!
- val args by navArgs<EmulationFragmentArgs>()
+ private val args by navArgs<EmulationFragmentArgs>()
- private var isInFoldableLayout = false
+ private lateinit var game: Game
+
+ private val emulationViewModel: EmulationViewModel by activityViewModels()
- private lateinit var onReturnFromSettings: ActivityResultLauncher<Intent>
+ private var isInFoldableLayout = false
override fun onAttach(context: Context) {
super.onAttach(context)
@@ -81,11 +85,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
.collect { updateFoldableLayout(context, it) }
}
}
-
- onReturnFromSettings = context.activityResultRegistry.register(
- "SettingsResult",
- ActivityResultContracts.StartActivityForResult()
- ) { updateScreenLayout() }
} else {
throw IllegalStateException("EmulationFragment must have EmulationActivity parent")
}
@@ -97,10 +96,25 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+ val intentUri: Uri? = requireActivity().intent.data
+ var intentGame: Game? = null
+ if (intentUri != null) {
+ intentGame = if (Game.extensions.contains(FileUtil.getExtension(intentUri))) {
+ GameHelper.getGame(requireActivity().intent.data!!, false)
+ } else {
+ null
+ }
+ }
+ game = if (args.game != null) {
+ args.game!!
+ } else {
+ intentGame ?: error("[EmulationFragment] No bootable game present!")
+ }
+
// So this fragment doesn't restart on configuration changes; i.e. rotation.
retainInstance = true
preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
- emulationState = EmulationState(args.game.path)
+ emulationState = EmulationState(game.path)
}
/**
@@ -120,11 +134,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
binding.showFpsText.setTextColor(Color.YELLOW)
binding.doneControlConfig.setOnClickListener { stopConfiguringControls() }
- // Setup overlay.
- updateShowFpsOverlay()
-
+ binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
binding.inGameMenu.getHeaderView(0).findViewById<TextView>(R.id.text_game_title).text =
- args.game.title
+ game.title
binding.inGameMenu.setNavigationItemSelectedListener {
when (it.itemId) {
R.id.menu_pause_emulation -> {
@@ -149,12 +161,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
}
R.id.menu_settings -> {
- SettingsActivity.launch(
- requireContext(),
- onReturnFromSettings,
- SettingsFile.FILE_NAME_CONFIG,
- ""
+ val action = HomeNavigationDirections.actionGlobalSettingsActivity(
+ null,
+ SettingsFile.FILE_NAME_CONFIG
)
+ binding.root.findNavController().navigate(action)
true
}
@@ -165,7 +176,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
R.id.menu_exit -> {
emulationState.stop()
- requireActivity().finish()
+ emulationViewModel.setIsEmulationStopping(true)
+ binding.drawerLayout.close()
+ binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
true
}
@@ -179,6 +192,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
requireActivity(),
object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
+ if (!NativeLibrary.isRunning()) {
+ return
+ }
+
if (binding.drawerLayout.isOpen) {
binding.drawerLayout.close()
} else {
@@ -195,6 +212,54 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
.collect { updateFoldableLayout(requireActivity() as EmulationActivity, it) }
}
}
+
+ GameIconUtils.loadGameIcon(game, binding.loadingImage)
+ binding.loadingTitle.text = game.title
+ binding.loadingTitle.isSelected = true
+ binding.loadingText.isSelected = true
+
+ emulationViewModel.shaderProgress.observe(viewLifecycleOwner) {
+ if (it > 0 && it != emulationViewModel.totalShaders.value!!) {
+ binding.loadingProgressIndicator.isIndeterminate = false
+
+ if (it < binding.loadingProgressIndicator.max) {
+ binding.loadingProgressIndicator.progress = it
+ }
+ }
+
+ if (it == emulationViewModel.totalShaders.value!!) {
+ binding.loadingText.setText(R.string.loading)
+ binding.loadingProgressIndicator.isIndeterminate = true
+ }
+ }
+ emulationViewModel.totalShaders.observe(viewLifecycleOwner) {
+ binding.loadingProgressIndicator.max = it
+ }
+ emulationViewModel.shaderMessage.observe(viewLifecycleOwner) {
+ if (it.isNotEmpty()) {
+ binding.loadingText.text = it
+ }
+ }
+
+ emulationViewModel.emulationStarted.observe(viewLifecycleOwner) { started ->
+ if (started) {
+ binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)
+ ViewUtils.showView(binding.surfaceInputOverlay)
+ ViewUtils.hideView(binding.loadingIndicator)
+
+ // Setup overlay
+ updateShowFpsOverlay()
+ }
+ }
+
+ emulationViewModel.isEmulationStopping.observe(viewLifecycleOwner) {
+ if (it) {
+ binding.loadingText.setText(R.string.shutting_down)
+ ViewUtils.showView(binding.loadingIndicator)
+ ViewUtils.hideView(binding.inputContainer)
+ ViewUtils.hideView(binding.showFpsText)
+ }
+ }
}
override fun onConfigurationChanged(newConfig: Configuration) {
@@ -204,11 +269,21 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
binding.drawerLayout.close()
}
if (EmulationMenuSettings.showOverlay) {
- binding.surfaceInputOverlay.post { binding.surfaceInputOverlay.isVisible = false }
+ binding.surfaceInputOverlay.post {
+ binding.surfaceInputOverlay.visibility = View.VISIBLE
+ }
}
} else {
- if (EmulationMenuSettings.showOverlay) {
- binding.surfaceInputOverlay.post { binding.surfaceInputOverlay.isVisible = true }
+ if (EmulationMenuSettings.showOverlay &&
+ emulationViewModel.emulationStarted.value == true
+ ) {
+ binding.surfaceInputOverlay.post {
+ binding.surfaceInputOverlay.visibility = View.VISIBLE
+ }
+ } else {
+ binding.surfaceInputOverlay.post {
+ binding.surfaceInputOverlay.visibility = View.INVISIBLE
+ }
}
if (!isInFoldableLayout) {
if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
@@ -217,16 +292,13 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
binding.surfaceInputOverlay.layout = InputOverlay.LANDSCAPE
}
}
- if (!binding.surfaceInputOverlay.isInEditMode) {
- refreshInputOverlay()
- }
}
}
override fun onResume() {
super.onResume()
if (!DirectoryInitialization.areDirectoriesReady) {
- DirectoryInitialization.start(requireContext())
+ DirectoryInitialization.start()
}
updateScreenLayout()
@@ -251,10 +323,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
super.onDetach()
}
- private fun refreshInputOverlay() {
- binding.surfaceInputOverlay.refreshControls()
- }
-
private fun resetInputOverlay() {
preferences.edit()
.remove(Settings.PREF_CONTROL_SCALE)
@@ -272,17 +340,15 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
val FRAMETIME = 2
val SPEED = 3
perfStatsUpdater = {
- val perfStats = NativeLibrary.getPerfStats()
- if (perfStats[FPS] > 0 && _binding != null) {
- binding.showFpsText.text = String.format("FPS: %.1f", perfStats[FPS])
- }
-
- if (!emulationState.isStopped) {
+ if (emulationViewModel.emulationStarted.value == true) {
+ val perfStats = NativeLibrary.getPerfStats()
+ if (perfStats[FPS] > 0 && _binding != null) {
+ binding.showFpsText.text = String.format("FPS: %.1f", perfStats[FPS])
+ }
perfStatsUpdateHandler.postDelayed(perfStatsUpdater!!, 100)
}
}
perfStatsUpdateHandler.post(perfStatsUpdater!!)
- binding.showFpsText.text = resources.getString(R.string.emulation_game_loading)
binding.showFpsText.visibility = View.VISIBLE
} else {
if (perfStatsUpdater != null) {
@@ -340,7 +406,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
isInFoldableLayout = true
binding.surfaceInputOverlay.layout = InputOverlay.FOLDABLE
- refreshInputOverlay()
}
}
it.isSeparating
@@ -428,7 +493,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
.apply()
}
.setPositiveButton(android.R.string.ok) { _, _ ->
- refreshInputOverlay()
+ binding.surfaceInputOverlay.refreshControls()
}
.setNegativeButton(android.R.string.cancel, null)
.setNeutralButton(R.string.emulation_toggle_all) { _, _ -> }
@@ -452,7 +517,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
R.id.menu_show_overlay -> {
it.isChecked = !it.isChecked
EmulationMenuSettings.showOverlay = it.isChecked
- refreshInputOverlay()
+ binding.surfaceInputOverlay.refreshControls()
true
}
@@ -558,14 +623,14 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
preferences.edit()
.putInt(Settings.PREF_CONTROL_SCALE, scale)
.apply()
- refreshInputOverlay()
+ binding.surfaceInputOverlay.refreshControls()
}
private fun setControlOpacity(opacity: Int) {
preferences.edit()
.putInt(Settings.PREF_CONTROL_OPACITY, opacity)
.apply()
- refreshInputOverlay()
+ binding.surfaceInputOverlay.refreshControls()
}
private fun setInsets() {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt
index d5e793491..cbbe14d22 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt
@@ -25,17 +25,18 @@ import androidx.core.view.updatePadding
import androidx.documentfile.provider.DocumentFile
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
+import androidx.navigation.findNavController
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.transition.MaterialSharedAxis
import org.yuzu.yuzu_emu.BuildConfig
+import org.yuzu.yuzu_emu.HomeNavigationDirections
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.adapters.HomeSettingAdapter
import org.yuzu.yuzu_emu.databinding.FragmentHomeSettingsBinding
import org.yuzu.yuzu_emu.features.DocumentProvider
import org.yuzu.yuzu_emu.features.settings.model.Settings
-import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
import org.yuzu.yuzu_emu.model.HomeSetting
import org.yuzu.yuzu_emu.model.HomeViewModel
@@ -74,7 +75,13 @@ class HomeSettingsFragment : Fragment() {
R.string.advanced_settings,
R.string.settings_description,
R.drawable.ic_settings,
- { SettingsActivity.launch(requireContext(), SettingsFile.FILE_NAME_CONFIG, "") }
+ {
+ val action = HomeNavigationDirections.actionGlobalSettingsActivity(
+ null,
+ SettingsFile.FILE_NAME_CONFIG
+ )
+ binding.root.findNavController().navigate(action)
+ }
)
)
add(
@@ -90,7 +97,13 @@ class HomeSettingsFragment : Fragment() {
R.string.preferences_theme,
R.string.theme_and_color_description,
R.drawable.ic_palette,
- { SettingsActivity.launch(requireContext(), Settings.SECTION_THEME, "") }
+ {
+ val action = HomeNavigationDirections.actionGlobalSettingsActivity(
+ null,
+ Settings.SECTION_THEME
+ )
+ binding.root.findNavController().navigate(action)
+ }
)
)
add(
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ImportExportSavesFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ImportExportSavesFragment.kt
index e1495ee8c..f38aeea53 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ImportExportSavesFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ImportExportSavesFragment.kt
@@ -187,8 +187,8 @@ class ImportExportSavesFragment : DialogFragment() {
withContext(Dispatchers.Main) {
if (!validZip) {
MessageDialogFragment.newInstance(
- R.string.save_file_invalid_zip_structure,
- R.string.save_file_invalid_zip_structure_description
+ titleId = R.string.save_file_invalid_zip_structure,
+ descriptionId = R.string.save_file_invalid_zip_structure_description
).show(activity.supportFragmentManager, MessageDialogFragment.TAG)
return@withContext
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt
index 739b26f99..181bd983a 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt
@@ -34,7 +34,7 @@ class IndeterminateProgressDialogFragment : DialogFragment() {
when (val result = taskViewModel.result.value) {
is String -> Toast.makeText(requireContext(), result, Toast.LENGTH_LONG).show()
is MessageDialogFragment -> result.show(
- parentFragmentManager,
+ requireActivity().supportFragmentManager,
MessageDialogFragment.TAG
)
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LongMessageDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LongMessageDialogFragment.kt
deleted file mode 100644
index b29b627e9..000000000
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LongMessageDialogFragment.kt
+++ /dev/null
@@ -1,62 +0,0 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-package org.yuzu.yuzu_emu.fragments
-
-import android.app.Dialog
-import android.content.Intent
-import android.net.Uri
-import android.os.Bundle
-import androidx.fragment.app.DialogFragment
-import com.google.android.material.dialog.MaterialAlertDialogBuilder
-import org.yuzu.yuzu_emu.R
-
-class LongMessageDialogFragment : DialogFragment() {
- override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
- val titleId = requireArguments().getInt(TITLE)
- val description = requireArguments().getString(DESCRIPTION)
- val helpLinkId = requireArguments().getInt(HELP_LINK)
-
- val dialog = MaterialAlertDialogBuilder(requireContext())
- .setPositiveButton(R.string.close, null)
- .setTitle(titleId)
- .setMessage(description)
-
- if (helpLinkId != 0) {
- dialog.setNeutralButton(R.string.learn_more) { _, _ ->
- openLink(getString(helpLinkId))
- }
- }
-
- return dialog.show()
- }
-
- private fun openLink(link: String) {
- val intent = Intent(Intent.ACTION_VIEW, Uri.parse(link))
- startActivity(intent)
- }
-
- companion object {
- const val TAG = "LongMessageDialogFragment"
-
- private const val TITLE = "Title"
- private const val DESCRIPTION = "Description"
- private const val HELP_LINK = "Link"
-
- fun newInstance(
- titleId: Int,
- description: String,
- helpLinkId: Int = 0
- ): LongMessageDialogFragment {
- val dialog = LongMessageDialogFragment()
- val bundle = Bundle()
- bundle.apply {
- putInt(TITLE, titleId)
- putString(DESCRIPTION, description)
- putInt(HELP_LINK, helpLinkId)
- }
- dialog.arguments = bundle
- return dialog
- }
- }
-}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/MessageDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/MessageDialogFragment.kt
index 2db38fdc2..7d1c2c8dd 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/MessageDialogFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/MessageDialogFragment.kt
@@ -13,14 +13,20 @@ import org.yuzu.yuzu_emu.R
class MessageDialogFragment : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
- val titleId = requireArguments().getInt(TITLE)
- val descriptionId = requireArguments().getInt(DESCRIPTION)
+ val titleId = requireArguments().getInt(TITLE_ID)
+ val titleString = requireArguments().getString(TITLE_STRING)!!
+ val descriptionId = requireArguments().getInt(DESCRIPTION_ID)
+ val descriptionString = requireArguments().getString(DESCRIPTION_STRING)!!
val helpLinkId = requireArguments().getInt(HELP_LINK)
val dialog = MaterialAlertDialogBuilder(requireContext())
.setPositiveButton(R.string.close, null)
- .setTitle(titleId)
- .setMessage(descriptionId)
+
+ if (titleId != 0) dialog.setTitle(titleId)
+ if (titleString.isNotEmpty()) dialog.setTitle(titleString)
+
+ if (descriptionId != 0) dialog.setMessage(descriptionId)
+ if (descriptionString.isNotEmpty()) dialog.setMessage(descriptionString)
if (helpLinkId != 0) {
dialog.setNeutralButton(R.string.learn_more) { _, _ ->
@@ -39,20 +45,26 @@ class MessageDialogFragment : DialogFragment() {
companion object {
const val TAG = "MessageDialogFragment"
- private const val TITLE = "Title"
- private const val DESCRIPTION = "Description"
+ private const val TITLE_ID = "Title"
+ private const val TITLE_STRING = "TitleString"
+ private const val DESCRIPTION_ID = "DescriptionId"
+ private const val DESCRIPTION_STRING = "DescriptionString"
private const val HELP_LINK = "Link"
fun newInstance(
- titleId: Int,
- descriptionId: Int,
+ titleId: Int = 0,
+ titleString: String = "",
+ descriptionId: Int = 0,
+ descriptionString: String = "",
helpLinkId: Int = 0
): MessageDialogFragment {
val dialog = MessageDialogFragment()
val bundle = Bundle()
bundle.apply {
- putInt(TITLE, titleId)
- putInt(DESCRIPTION, descriptionId)
+ putInt(TITLE_ID, titleId)
+ putString(TITLE_STRING, titleString)
+ putInt(DESCRIPTION_ID, descriptionId)
+ putString(DESCRIPTION_STRING, descriptionString)
putInt(HELP_LINK, helpLinkId)
}
dialog.arguments = bundle
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 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.fragments
+
+import android.app.Dialog
+import android.content.DialogInterface
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.DialogFragment
+import androidx.fragment.app.activityViewModels
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import com.google.android.material.slider.Slider
+import kotlinx.coroutines.launch
+import org.yuzu.yuzu_emu.R
+import org.yuzu.yuzu_emu.databinding.DialogSliderBinding
+import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
+import org.yuzu.yuzu_emu.features.settings.model.view.SingleChoiceSetting
+import org.yuzu.yuzu_emu.features.settings.model.view.SliderSetting
+import org.yuzu.yuzu_emu.features.settings.model.view.StringSingleChoiceSetting
+import org.yuzu.yuzu_emu.model.SettingsViewModel
+
+class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener {
+ private var type = 0
+ private var position = 0
+
+ private var defaultCancelListener =
+ DialogInterface.OnClickListener { _: DialogInterface?, _: Int -> closeDialog() }
+
+ private val settingsViewModel: SettingsViewModel by activityViewModels()
+
+ private lateinit var sliderBinding: DialogSliderBinding
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ type = requireArguments().getInt(TYPE)
+ position = requireArguments().getInt(POSITION)
+
+ if (settingsViewModel.clickedItem == null) dismiss()
+ }
+
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ return when (type) {
+ TYPE_RESET_SETTING -> {
+ MaterialAlertDialogBuilder(requireContext())
+ .setMessage(R.string.reset_setting_confirmation)
+ .setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
+ settingsViewModel.clickedItem!!.setting.reset()
+ settingsViewModel.setAdapterItemChanged(position)
+ settingsViewModel.shouldSave = true
+ }
+ .setNegativeButton(android.R.string.cancel, null)
+ .create()
+ }
+
+ SettingsItem.TYPE_SINGLE_CHOICE -> {
+ val item = settingsViewModel.clickedItem as SingleChoiceSetting
+ val value = getSelectionForSingleChoiceValue(item)
+ MaterialAlertDialogBuilder(requireContext())
+ .setTitle(item.nameId)
+ .setSingleChoiceItems(item.choicesId, value, this)
+ .create()
+ }
+
+ SettingsItem.TYPE_SLIDER -> {
+ sliderBinding = DialogSliderBinding.inflate(layoutInflater)
+ val item = settingsViewModel.clickedItem as SliderSetting
+
+ settingsViewModel.setSliderTextValue(item.selectedValue.toFloat(), item.units)
+ sliderBinding.slider.apply {
+ valueFrom = item.min.toFloat()
+ valueTo = item.max.toFloat()
+ value = settingsViewModel.sliderProgress.value.toFloat()
+ addOnChangeListener { _: Slider, value: Float, _: Boolean ->
+ settingsViewModel.setSliderTextValue(value, item.units)
+ }
+ }
+
+ MaterialAlertDialogBuilder(requireContext())
+ .setTitle(item.nameId)
+ .setView(sliderBinding.root)
+ .setPositiveButton(android.R.string.ok, this)
+ .setNegativeButton(android.R.string.cancel, defaultCancelListener)
+ .create()
+ }
+
+ SettingsItem.TYPE_STRING_SINGLE_CHOICE -> {
+ val item = settingsViewModel.clickedItem as StringSingleChoiceSetting
+ MaterialAlertDialogBuilder(requireContext())
+ .setTitle(item.nameId)
+ .setSingleChoiceItems(item.choices, item.selectValueIndex, this)
+ .create()
+ }
+
+ else -> super.onCreateDialog(savedInstanceState)
+ }
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ return when (type) {
+ SettingsItem.TYPE_SLIDER -> sliderBinding.root
+ else -> super.onCreateView(inflater, container, savedInstanceState)
+ }
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ when (type) {
+ SettingsItem.TYPE_SLIDER -> {
+ viewLifecycleOwner.lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ settingsViewModel.sliderTextValue.collect {
+ sliderBinding.textValue.text = it
+ }
+ }
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ settingsViewModel.sliderProgress.collect {
+ sliderBinding.slider.value = it.toFloat()
+ }
+ }
+ }
+ }
+ }
+ }
+
+ override fun onClick(dialog: DialogInterface, which: Int) {
+ when (settingsViewModel.clickedItem) {
+ is SingleChoiceSetting -> {
+ val scSetting = settingsViewModel.clickedItem as SingleChoiceSetting
+ val value = getValueForSingleChoiceSelection(scSetting, which)
+ if (scSetting.selectedValue != value) {
+ settingsViewModel.shouldSave = true
+ }
+ scSetting.selectedValue = value
+ }
+
+ is StringSingleChoiceSetting -> {
+ val scSetting = settingsViewModel.clickedItem as StringSingleChoiceSetting
+ val value = scSetting.getValueAt(which)
+ if (scSetting.selectedValue != value) settingsViewModel.shouldSave = true
+ scSetting.selectedValue = value
+ }
+
+ is SliderSetting -> {
+ val sliderSetting = settingsViewModel.clickedItem as SliderSetting
+ if (sliderSetting.selectedValue != settingsViewModel.sliderProgress.value) {
+ settingsViewModel.shouldSave = true
+ }
+ sliderSetting.selectedValue = settingsViewModel.sliderProgress.value
+ }
+ }
+ closeDialog()
+ }
+
+ private fun closeDialog() {
+ settingsViewModel.setAdapterItemChanged(position)
+ settingsViewModel.clickedItem = null
+ settingsViewModel.setSliderProgress(-1f)
+ dismiss()
+ }
+
+ private fun getValueForSingleChoiceSelection(item: SingleChoiceSetting, which: Int): Int {
+ val valuesId = item.valuesId
+ return if (valuesId > 0) {
+ val valuesArray = requireContext().resources.getIntArray(valuesId)
+ valuesArray[which]
+ } else {
+ which
+ }
+ }
+
+ private fun getSelectionForSingleChoiceValue(item: SingleChoiceSetting): Int {
+ val value = item.selectedValue
+ val valuesId = item.valuesId
+ if (valuesId > 0) {
+ val valuesArray = requireContext().resources.getIntArray(valuesId)
+ for (index in valuesArray.indices) {
+ val current = valuesArray[index]
+ if (current == value) {
+ return index
+ }
+ }
+ } else {
+ return value
+ }
+ return -1
+ }
+
+ companion object {
+ const val TAG = "SettingsDialogFragment"
+
+ const val TYPE_RESET_SETTING = -1
+
+ const val TITLE = "Title"
+ const val TYPE = "Type"
+ const val POSITION = "Position"
+
+ fun newInstance(
+ settingsViewModel: SettingsViewModel,
+ clickedItem: SettingsItem,
+ type: Int,
+ position: Int
+ ): SettingsDialogFragment {
+ when (type) {
+ SettingsItem.TYPE_HEADER,
+ SettingsItem.TYPE_SWITCH,
+ SettingsItem.TYPE_SUBMENU,
+ SettingsItem.TYPE_DATETIME_SETTING,
+ SettingsItem.TYPE_RUNNABLE ->
+ throw IllegalArgumentException("[SettingsDialogFragment] Incompatible type!")
+
+ SettingsItem.TYPE_SLIDER -> settingsViewModel.setSliderProgress(
+ (clickedItem as SliderSetting).selectedValue.toFloat()
+ )
+ }
+ settingsViewModel.clickedItem = clickedItem
+
+ val args = Bundle()
+ args.putInt(TYPE, type)
+ args.putInt(POSITION, position)
+ val fragment = SettingsDialogFragment()
+ fragment.arguments = args
+ return fragment
+ }
+ }
+}
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 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.fragments
+
+import android.content.Context
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.view.inputmethod.InputMethodManager
+import androidx.core.view.ViewCompat
+import androidx.core.view.WindowInsetsCompat
+import androidx.core.view.updatePadding
+import androidx.core.widget.doOnTextChanged
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.google.android.material.divider.MaterialDividerItemDecoration
+import com.google.android.material.transition.MaterialSharedAxis
+import info.debatty.java.stringsimilarity.Cosine
+import org.yuzu.yuzu_emu.R
+import org.yuzu.yuzu_emu.databinding.FragmentSettingsSearchBinding
+import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
+import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
+import org.yuzu.yuzu_emu.model.SettingsViewModel
+import org.yuzu.yuzu_emu.utils.NativeConfig
+
+class SettingsSearchFragment : Fragment() {
+ private var _binding: FragmentSettingsSearchBinding? = null
+ private val binding get() = _binding!!
+
+ private var settingsAdapter: SettingsAdapter? = null
+
+ private val settingsViewModel: SettingsViewModel by activityViewModels()
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ enterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false)
+ returnTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true)
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ _binding = FragmentSettingsSearchBinding.inflate(layoutInflater)
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ settingsViewModel.setIsUsingSearch(true)
+
+ if (savedInstanceState != null) {
+ binding.searchText.setText(savedInstanceState.getString(SEARCH_TEXT))
+ }
+
+ settingsAdapter = SettingsAdapter(this, requireContext())
+
+ val dividerDecoration = MaterialDividerItemDecoration(
+ requireContext(),
+ LinearLayoutManager.VERTICAL
+ )
+ dividerDecoration.isLastItemDecorated = false
+ binding.settingsList.apply {
+ adapter = settingsAdapter
+ layoutManager = LinearLayoutManager(requireContext())
+ addItemDecoration(dividerDecoration)
+ }
+
+ focusSearch()
+
+ binding.backButton.setOnClickListener { settingsViewModel.setShouldNavigateBack(true) }
+ binding.searchBackground.setOnClickListener { focusSearch() }
+ binding.clearButton.setOnClickListener { binding.searchText.setText("") }
+ binding.searchText.doOnTextChanged { _, _, _, _ ->
+ search()
+ binding.settingsList.smoothScrollToPosition(0)
+ }
+ settingsViewModel.shouldReloadSettingsList.observe(viewLifecycleOwner) {
+ if (it) {
+ settingsViewModel.setShouldReloadSettingsList(false)
+ search()
+ }
+ }
+
+ search()
+
+ setInsets()
+ }
+
+ override fun onSaveInstanceState(outState: Bundle) {
+ super.onSaveInstanceState(outState)
+ outState.putString(SEARCH_TEXT, binding.searchText.text.toString())
+ }
+
+ private fun search() {
+ val searchTerm = binding.searchText.text.toString().lowercase()
+ binding.clearButton.visibility =
+ if (searchTerm.isEmpty()) View.INVISIBLE else View.VISIBLE
+ if (searchTerm.isEmpty()) {
+ binding.noResultsView.visibility = View.VISIBLE
+ settingsAdapter?.submitList(emptyList())
+ return
+ }
+
+ val baseList = SettingsItem.settingsItems
+ val similarityAlgorithm = if (searchTerm.length > 2) Cosine() else Cosine(1)
+ val sortedList: List<SettingsItem> = baseList.mapNotNull { item ->
+ val title = getString(item.value.nameId).lowercase()
+ val similarity = similarityAlgorithm.similarity(searchTerm, title)
+ if (similarity > 0.08) {
+ Pair(similarity, item)
+ } else {
+ null
+ }
+ }.sortedByDescending { it.first }.mapNotNull {
+ val item = it.second.value
+ val pairedSettingKey = item.setting.pairedSettingKey
+ val optionalSetting: SettingsItem? = if (pairedSettingKey.isNotEmpty()) {
+ val pairedSettingValue = NativeConfig.getBoolean(pairedSettingKey, false)
+ if (pairedSettingValue) it.second.value else null
+ } else {
+ it.second.value
+ }
+ optionalSetting
+ }
+ settingsAdapter?.submitList(sortedList)
+ binding.noResultsView.visibility =
+ if (sortedList.isEmpty()) View.VISIBLE else View.INVISIBLE
+ }
+
+ private fun focusSearch() {
+ binding.searchText.requestFocus()
+ val imm = requireActivity()
+ .getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager?
+ imm?.showSoftInput(binding.searchText, InputMethodManager.SHOW_IMPLICIT)
+ }
+
+ private fun setInsets() =
+ ViewCompat.setOnApplyWindowInsetsListener(
+ binding.root
+ ) { _: View, windowInsets: WindowInsetsCompat ->
+ val extraListSpacing = resources.getDimensionPixelSize(R.dimen.spacing_med)
+ val sideMargin = resources.getDimensionPixelSize(R.dimen.spacing_medlarge)
+ val topMargin = resources.getDimensionPixelSize(R.dimen.spacing_chip)
+
+ val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
+ val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
+
+ val leftInsets = barInsets.left + cutoutInsets.left
+ val rightInsets = barInsets.right + cutoutInsets.right
+
+ binding.settingsList.updatePadding(bottom = barInsets.bottom + extraListSpacing)
+ binding.frameSearch.updatePadding(
+ left = leftInsets + sideMargin,
+ top = barInsets.top + topMargin,
+ right = rightInsets + sideMargin
+ )
+ binding.noResultsView.updatePadding(
+ left = leftInsets,
+ right = rightInsets,
+ bottom = barInsets.bottom
+ )
+
+ val mlpSettingsList = binding.settingsList.layoutParams as ViewGroup.MarginLayoutParams
+ mlpSettingsList.leftMargin = leftInsets + sideMargin
+ mlpSettingsList.rightMargin = rightInsets + sideMargin
+ binding.settingsList.layoutParams = mlpSettingsList
+
+ val mlpDivider = binding.divider.layoutParams as ViewGroup.MarginLayoutParams
+ mlpDivider.leftMargin = leftInsets + sideMargin
+ mlpDivider.rightMargin = rightInsets + sideMargin
+ binding.divider.layoutParams = mlpDivider
+
+ windowInsets
+ }
+
+ companion object {
+ const val SEARCH_TEXT = "SearchText"
+ }
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/EmulationViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/EmulationViewModel.kt
new file mode 100644
index 000000000..e35f51bc3
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/EmulationViewModel.kt
@@ -0,0 +1,59 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package org.yuzu.yuzu_emu.model
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+
+class EmulationViewModel : ViewModel() {
+ private val _emulationStarted = MutableLiveData(false)
+ val emulationStarted: LiveData<Boolean> get() = _emulationStarted
+
+ private val _isEmulationStopping = MutableLiveData(false)
+ val isEmulationStopping: LiveData<Boolean> get() = _isEmulationStopping
+
+ private val _shaderProgress = MutableLiveData(0)
+ val shaderProgress: LiveData<Int> get() = _shaderProgress
+
+ private val _totalShaders = MutableLiveData(0)
+ val totalShaders: LiveData<Int> get() = _totalShaders
+
+ private val _shaderMessage = MutableLiveData("")
+ val shaderMessage: LiveData<String> get() = _shaderMessage
+
+ fun setEmulationStarted(started: Boolean) {
+ _emulationStarted.postValue(started)
+ }
+
+ fun setIsEmulationStopping(value: Boolean) {
+ _isEmulationStopping.value = value
+ }
+
+ fun setShaderProgress(progress: Int) {
+ _shaderProgress.value = progress
+ }
+
+ fun setTotalShaders(max: Int) {
+ _totalShaders.value = max
+ }
+
+ fun setShaderMessage(msg: String) {
+ _shaderMessage.value = msg
+ }
+
+ fun updateProgress(msg: String, progress: Int, max: Int) {
+ setShaderMessage(msg)
+ setShaderProgress(progress)
+ setTotalShaders(max)
+ }
+
+ fun clear() {
+ _emulationStarted.value = false
+ _isEmulationStopping.value = false
+ _shaderProgress.value = 0
+ _totalShaders.value = 0
+ _shaderMessage.value = ""
+ }
+}
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 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.model
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.SavedStateHandle
+import androidx.lifecycle.ViewModel
+import org.yuzu.yuzu_emu.R
+import org.yuzu.yuzu_emu.YuzuApplication
+import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
+
+class SettingsViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() {
+ var game: Game? = null
+
+ var shouldSave = false
+
+ var clickedItem: SettingsItem? = null
+
+ private val _toolbarTitle = MutableLiveData("")
+ val toolbarTitle: LiveData<String> get() = _toolbarTitle
+
+ private val _shouldRecreate = MutableLiveData(false)
+ val shouldRecreate: LiveData<Boolean> get() = _shouldRecreate
+
+ private val _shouldNavigateBack = MutableLiveData(false)
+ val shouldNavigateBack: LiveData<Boolean> get() = _shouldNavigateBack
+
+ private val _shouldShowResetSettingsDialog = MutableLiveData(false)
+ val shouldShowResetSettingsDialog: LiveData<Boolean> get() = _shouldShowResetSettingsDialog
+
+ private val _shouldReloadSettingsList = MutableLiveData(false)
+ val shouldReloadSettingsList: LiveData<Boolean> get() = _shouldReloadSettingsList
+
+ private val _isUsingSearch = MutableLiveData(false)
+ val isUsingSearch: LiveData<Boolean> get() = _isUsingSearch
+
+ val sliderProgress = savedStateHandle.getStateFlow(KEY_SLIDER_PROGRESS, -1)
+
+ val sliderTextValue = savedStateHandle.getStateFlow(KEY_SLIDER_TEXT_VALUE, "")
+
+ val adapterItemChanged = savedStateHandle.getStateFlow(KEY_ADAPTER_ITEM_CHANGED, -1)
+
+ fun setToolbarTitle(value: String) {
+ _toolbarTitle.value = value
+ }
+
+ fun setShouldRecreate(value: Boolean) {
+ _shouldRecreate.value = value
+ }
+
+ fun setShouldNavigateBack(value: Boolean) {
+ _shouldNavigateBack.value = value
+ }
+
+ fun setShouldShowResetSettingsDialog(value: Boolean) {
+ _shouldShowResetSettingsDialog.value = value
+ }
+
+ fun setShouldReloadSettingsList(value: Boolean) {
+ _shouldReloadSettingsList.value = value
+ }
+
+ fun setIsUsingSearch(value: Boolean) {
+ _isUsingSearch.value = value
+ }
+
+ fun setSliderTextValue(value: Float, units: String) {
+ savedStateHandle[KEY_SLIDER_PROGRESS] = value
+ savedStateHandle[KEY_SLIDER_TEXT_VALUE] = String.format(
+ YuzuApplication.appContext.getString(R.string.value_with_units),
+ value.toInt().toString(),
+ units
+ )
+ }
+
+ fun setSliderProgress(value: Float) {
+ savedStateHandle[KEY_SLIDER_PROGRESS] = value
+ }
+
+ fun setAdapterItemChanged(value: Int) {
+ savedStateHandle[KEY_ADAPTER_ITEM_CHANGED] = value
+ }
+
+ fun clear() {
+ game = null
+ shouldSave = false
+ }
+
+ companion object {
+ const val KEY_SLIDER_TEXT_VALUE = "SliderTextValue"
+ const val KEY_SLIDER_PROGRESS = "SliderProgress"
+ const val KEY_ADAPTER_ITEM_CHANGED = "AdapterItemChanged"
+ }
+}
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..7d8e06ad8 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,17 +33,15 @@ import java.io.IOException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
+import org.yuzu.yuzu_emu.HomeNavigationDirections
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.activities.EmulationActivity
import org.yuzu.yuzu_emu.databinding.ActivityMainBinding
import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
import org.yuzu.yuzu_emu.features.settings.model.Settings
-import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel
-import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment
-import org.yuzu.yuzu_emu.fragments.LongMessageDialogFragment
import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
import org.yuzu.yuzu_emu.model.GamesViewModel
import org.yuzu.yuzu_emu.model.HomeViewModel
@@ -54,7 +52,6 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
private val homeViewModel: HomeViewModel by viewModels()
private val gamesViewModel: GamesViewModel by viewModels()
- private val settingsViewModel: SettingsViewModel by viewModels()
override var themeId: Int = 0
@@ -62,8 +59,6 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
val splashScreen = installSplashScreen()
splashScreen.setKeepOnScreenCondition { !DirectoryInitialization.areDirectoriesReady }
- settingsViewModel.settings.loadSettings()
-
ThemeHelper.setTheme(this)
super.onCreate(savedInstanceState)
@@ -109,11 +104,13 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
when (it.itemId) {
R.id.gamesFragment -> gamesViewModel.setShouldScrollToTop(true)
R.id.searchFragment -> gamesViewModel.setSearchFocused(true)
- R.id.homeSettingsFragment -> SettingsActivity.launch(
- this,
- SettingsFile.FILE_NAME_CONFIG,
- ""
- )
+ R.id.homeSettingsFragment -> {
+ val action = HomeNavigationDirections.actionGlobalSettingsActivity(
+ null,
+ SettingsFile.FILE_NAME_CONFIG
+ )
+ navHostFragment.navController.navigate(action)
+ }
}
}
@@ -303,8 +300,8 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
fun processKey(result: Uri): Boolean {
if (FileUtil.getExtension(result) != "keys") {
MessageDialogFragment.newInstance(
- R.string.reading_keys_failure,
- R.string.install_prod_keys_failure_extension_description
+ titleId = R.string.reading_keys_failure,
+ descriptionId = R.string.install_prod_keys_failure_extension_description
).show(supportFragmentManager, MessageDialogFragment.TAG)
return false
}
@@ -332,9 +329,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
return true
} else {
MessageDialogFragment.newInstance(
- R.string.invalid_keys_error,
- R.string.install_keys_failure_description,
- R.string.dumping_keys_quickstart_link
+ titleId = R.string.invalid_keys_error,
+ descriptionId = R.string.install_keys_failure_description,
+ helpLinkId = R.string.dumping_keys_quickstart_link
).show(supportFragmentManager, MessageDialogFragment.TAG)
return false
}
@@ -372,8 +369,8 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
val filteredNumOfFiles = cacheFirmwareDir.list(filterNCA)?.size ?: -2
messageToShow = if (unfilteredNumOfFiles != filteredNumOfFiles) {
MessageDialogFragment.newInstance(
- R.string.firmware_installed_failure,
- R.string.firmware_installed_failure_description
+ titleId = R.string.firmware_installed_failure,
+ descriptionId = R.string.firmware_installed_failure_description
)
} else {
firmwarePath.deleteRecursively()
@@ -403,8 +400,8 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
if (FileUtil.getExtension(result) != "bin") {
MessageDialogFragment.newInstance(
- R.string.reading_keys_failure,
- R.string.install_amiibo_keys_failure_extension_description
+ titleId = R.string.reading_keys_failure,
+ descriptionId = R.string.install_amiibo_keys_failure_extension_description
).show(supportFragmentManager, MessageDialogFragment.TAG)
return@registerForActivityResult
}
@@ -430,9 +427,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
).show()
} else {
MessageDialogFragment.newInstance(
- R.string.invalid_keys_error,
- R.string.install_keys_failure_description,
- R.string.dumping_keys_quickstart_link
+ titleId = R.string.invalid_keys_error,
+ descriptionId = R.string.install_keys_failure_description,
+ helpLinkId = R.string.dumping_keys_quickstart_link
).show(supportFragmentManager, MessageDialogFragment.TAG)
}
}
@@ -504,96 +501,91 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
var errorBaseGame = 0
var errorExtension = 0
var errorOther = 0
- var errorTotal = 0
- lifecycleScope.launch {
- documents.forEach {
- when (NativeLibrary.installFileToNand(it.toString())) {
- NativeLibrary.InstallFileToNandResult.Success -> {
- installSuccess += 1
- }
-
- NativeLibrary.InstallFileToNandResult.SuccessFileOverwritten -> {
- installOverwrite += 1
- }
-
- NativeLibrary.InstallFileToNandResult.ErrorBaseGame -> {
- errorBaseGame += 1
- }
-
- NativeLibrary.InstallFileToNandResult.ErrorFilenameExtension -> {
- errorExtension += 1
- }
-
- else -> {
- errorOther += 1
- }
+ documents.forEach {
+ when (NativeLibrary.installFileToNand(it.toString())) {
+ NativeLibrary.InstallFileToNandResult.Success -> {
+ installSuccess += 1
}
- }
- withContext(Dispatchers.Main) {
- val separator = System.getProperty("line.separator") ?: "\n"
- val installResult = StringBuilder()
- if (installSuccess > 0) {
- installResult.append(
- getString(
- R.string.install_game_content_success_install,
- installSuccess
- )
- )
- installResult.append(separator)
+
+ NativeLibrary.InstallFileToNandResult.SuccessFileOverwritten -> {
+ installOverwrite += 1
}
- if (installOverwrite > 0) {
- installResult.append(
- getString(
- R.string.install_game_content_success_overwrite,
- installOverwrite
- )
- )
- installResult.append(separator)
+
+ NativeLibrary.InstallFileToNandResult.ErrorBaseGame -> {
+ errorBaseGame += 1
}
- errorTotal = errorBaseGame + errorExtension + errorOther
- if (errorTotal > 0) {
- installResult.append(separator)
- installResult.append(
- getString(
- R.string.install_game_content_failed_count,
- errorTotal
- )
- )
- installResult.append(separator)
- if (errorBaseGame > 0) {
- installResult.append(separator)
- installResult.append(
- getString(R.string.install_game_content_failure_base)
- )
- installResult.append(separator)
- }
- if (errorExtension > 0) {
- installResult.append(separator)
- installResult.append(
- getString(R.string.install_game_content_failure_file_extension)
- )
- installResult.append(separator)
- }
- if (errorOther > 0) {
- installResult.append(
- getString(R.string.install_game_content_failure_description)
- )
- installResult.append(separator)
- }
- LongMessageDialogFragment.newInstance(
- R.string.install_game_content_failure,
- installResult.toString().trim(),
- R.string.install_game_content_help_link
- ).show(supportFragmentManager, LongMessageDialogFragment.TAG)
- } else {
- LongMessageDialogFragment.newInstance(
- R.string.install_game_content_success,
- installResult.toString().trim()
- ).show(supportFragmentManager, LongMessageDialogFragment.TAG)
+
+ NativeLibrary.InstallFileToNandResult.ErrorFilenameExtension -> {
+ errorExtension += 1
+ }
+
+ else -> {
+ errorOther += 1
}
}
}
- return@newInstance installSuccess + installOverwrite + errorTotal
+
+ val separator = System.getProperty("line.separator") ?: "\n"
+ val installResult = StringBuilder()
+ if (installSuccess > 0) {
+ installResult.append(
+ getString(
+ R.string.install_game_content_success_install,
+ installSuccess
+ )
+ )
+ installResult.append(separator)
+ }
+ if (installOverwrite > 0) {
+ installResult.append(
+ getString(
+ R.string.install_game_content_success_overwrite,
+ installOverwrite
+ )
+ )
+ installResult.append(separator)
+ }
+ val errorTotal: Int = errorBaseGame + errorExtension + errorOther
+ if (errorTotal > 0) {
+ installResult.append(separator)
+ installResult.append(
+ getString(
+ R.string.install_game_content_failed_count,
+ errorTotal
+ )
+ )
+ installResult.append(separator)
+ if (errorBaseGame > 0) {
+ installResult.append(separator)
+ installResult.append(
+ getString(R.string.install_game_content_failure_base)
+ )
+ installResult.append(separator)
+ }
+ if (errorExtension > 0) {
+ installResult.append(separator)
+ installResult.append(
+ getString(R.string.install_game_content_failure_file_extension)
+ )
+ installResult.append(separator)
+ }
+ if (errorOther > 0) {
+ installResult.append(
+ getString(R.string.install_game_content_failure_description)
+ )
+ installResult.append(separator)
+ }
+ return@newInstance MessageDialogFragment.newInstance(
+ titleId = R.string.install_game_content_failure,
+ descriptionString = installResult.toString().trim(),
+ helpLinkId = R.string.install_game_content_help_link
+ )
+ } else {
+ return@newInstance MessageDialogFragment.newInstance(
+ titleId = R.string.install_game_content_success,
+ descriptionString = installResult.toString().trim()
+ )
+ }
}.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
}
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/BiMap.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/BiMap.kt
deleted file mode 100644
index 9cfda74ee..000000000
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/BiMap.kt
+++ /dev/null
@@ -1,25 +0,0 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-package org.yuzu.yuzu_emu.utils
-
-class BiMap<K, V> {
- private val forward: MutableMap<K, V> = HashMap()
- private val backward: MutableMap<V, K> = HashMap()
-
- @Synchronized
- fun add(key: K, value: V) {
- forward[key] = value
- backward[value] = key
- }
-
- @Synchronized
- fun getForward(key: K): V? {
- return forward[key]
- }
-
- @Synchronized
- fun getBackward(key: V): K? {
- return backward[key]
- }
-}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt
index 2ee63697e..3c9f6bad0 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt
@@ -3,18 +3,18 @@
package org.yuzu.yuzu_emu.utils
-import android.content.Context
import java.io.IOException
import org.yuzu.yuzu_emu.NativeLibrary
+import org.yuzu.yuzu_emu.YuzuApplication
object DirectoryInitialization {
private var userPath: String? = null
var areDirectoriesReady: Boolean = false
- fun start(context: Context) {
+ fun start() {
if (!areDirectoriesReady) {
- initializeInternalStorage(context)
+ initializeInternalStorage()
NativeLibrary.initializeEmulation()
areDirectoriesReady = true
}
@@ -26,9 +26,9 @@ object DirectoryInitialization {
return userPath
}
- private fun initializeInternalStorage(context: Context) {
+ private fun initializeInternalStorage() {
try {
- userPath = context.getExternalFilesDir(null)!!.canonicalPath
+ userPath = YuzuApplication.appContext.getExternalFilesDir(null)!!.canonicalPath
NativeLibrary.setAppDirectory(userPath!!)
} catch (e: IOException) {
e.printStackTrace()
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt
index f71d0a098..e0ee29c9b 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt
@@ -63,13 +63,13 @@ object GameHelper {
)
} else {
if (Game.extensions.contains(FileUtil.getExtension(it.uri))) {
- games.add(getGame(it.uri))
+ games.add(getGame(it.uri, true))
}
}
}
}
- private fun getGame(uri: Uri): Game {
+ fun getGame(uri: Uri, addedToLibrary: Boolean): Game {
val filePath = uri.toString()
var name = NativeLibrary.getTitle(filePath)
@@ -94,11 +94,13 @@ object GameHelper {
NativeLibrary.isHomebrew(filePath)
)
- val addedTime = preferences.getLong(newGame.keyAddedToLibraryTime, 0L)
- if (addedTime == 0L) {
- preferences.edit()
- .putLong(newGame.keyAddedToLibraryTime, System.currentTimeMillis())
- .apply()
+ if (addedToLibrary) {
+ val addedTime = preferences.getLong(newGame.keyAddedToLibraryTime, 0L)
+ if (addedTime == 0L) {
+ preferences.edit()
+ .putLong(newGame.keyAddedToLibraryTime, System.currentTimeMillis())
+ .apply()
+ }
}
return newGame
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconUtils.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconUtils.kt
new file mode 100644
index 000000000..c0fe596d7
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconUtils.kt
@@ -0,0 +1,77 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.utils
+
+import android.graphics.Bitmap
+import android.graphics.BitmapFactory
+import android.widget.ImageView
+import androidx.core.graphics.drawable.toDrawable
+import coil.ImageLoader
+import coil.decode.DataSource
+import coil.fetch.DrawableResult
+import coil.fetch.FetchResult
+import coil.fetch.Fetcher
+import coil.key.Keyer
+import coil.memory.MemoryCache
+import coil.request.ImageRequest
+import coil.request.Options
+import org.yuzu.yuzu_emu.NativeLibrary
+import org.yuzu.yuzu_emu.R
+import org.yuzu.yuzu_emu.YuzuApplication
+import org.yuzu.yuzu_emu.model.Game
+
+class GameIconFetcher(
+ private val game: Game,
+ private val options: Options
+) : Fetcher {
+ override suspend fun fetch(): FetchResult {
+ return DrawableResult(
+ drawable = decodeGameIcon(game.path)!!.toDrawable(options.context.resources),
+ isSampled = false,
+ dataSource = DataSource.DISK
+ )
+ }
+
+ private fun decodeGameIcon(uri: String): Bitmap? {
+ val data = NativeLibrary.getIcon(uri)
+ return BitmapFactory.decodeByteArray(
+ data,
+ 0,
+ data.size,
+ BitmapFactory.Options()
+ )
+ }
+
+ class Factory : Fetcher.Factory<Game> {
+ override fun create(data: Game, options: Options, imageLoader: ImageLoader): Fetcher =
+ GameIconFetcher(data, options)
+ }
+}
+
+class GameIconKeyer : Keyer<Game> {
+ override fun key(data: Game, options: Options): String = data.path
+}
+
+object GameIconUtils {
+ private val imageLoader = ImageLoader.Builder(YuzuApplication.appContext)
+ .components {
+ add(GameIconKeyer())
+ add(GameIconFetcher.Factory())
+ }
+ .memoryCache {
+ MemoryCache.Builder(YuzuApplication.appContext)
+ .maxSizePercent(0.25)
+ .build()
+ }
+ .build()
+
+ fun loadGameIcon(game: Game, imageView: ImageView) {
+ val request = ImageRequest.Builder(YuzuApplication.appContext)
+ .data(game)
+ .target(imageView)
+ .error(R.drawable.default_icon)
+ .build()
+ imageLoader.enqueue(request)
+ }
+}
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 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.utils
+
+object NativeConfig {
+ external fun getBoolean(key: String, getDefault: Boolean): Boolean
+ external fun setBoolean(key: String, value: Boolean)
+
+ external fun getByte(key: String, getDefault: Boolean): Byte
+ external fun setByte(key: String, value: Byte)
+
+ external fun getShort(key: String, getDefault: Boolean): Short
+ external fun setShort(key: String, value: Short)
+
+ external fun getInt(key: String, getDefault: Boolean): Int
+ external fun setInt(key: String, value: Int)
+
+ external fun getFloat(key: String, getDefault: Boolean): Float
+ external fun setFloat(key: String, value: Float)
+
+ external fun getLong(key: String, getDefault: Boolean): Long
+ external fun setLong(key: String, value: Long)
+
+ external fun getString(key: String, getDefault: Boolean): String
+ external fun setString(key: String, value: String)
+
+ external fun getIsRuntimeModifiable(key: String): Boolean
+
+ external fun getConfigHeader(category: Int): String
+
+ external fun getPairedSettingKey(key: String): String
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/views/FixedRatioSurfaceView.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/views/FixedRatioSurfaceView.kt
index 685ccaa76..2f0868c63 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/views/FixedRatioSurfaceView.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/views/FixedRatioSurfaceView.kt
@@ -7,7 +7,6 @@ import android.content.Context
import android.util.AttributeSet
import android.util.Rational
import android.view.SurfaceView
-import kotlin.math.roundToInt
class FixedRatioSurfaceView @JvmOverloads constructor(
context: Context,
@@ -22,27 +21,44 @@ class FixedRatioSurfaceView @JvmOverloads constructor(
*/
fun setAspectRatio(ratio: Rational?) {
aspectRatio = ratio?.toFloat() ?: 0f
+ requestLayout()
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec)
- val width = MeasureSpec.getSize(widthMeasureSpec)
- val height = MeasureSpec.getSize(heightMeasureSpec)
+ val displayWidth: Float = MeasureSpec.getSize(widthMeasureSpec).toFloat()
+ val displayHeight: Float = MeasureSpec.getSize(heightMeasureSpec).toFloat()
if (aspectRatio != 0f) {
- val newWidth: Int
- val newHeight: Int
- if (height * aspectRatio < width) {
- newWidth = (height * aspectRatio).roundToInt()
- newHeight = height
+ val displayAspect = displayWidth / displayHeight
+ if (displayAspect < aspectRatio) {
+ // Max out width
+ val halfHeight = displayHeight / 2
+ val surfaceHeight = displayWidth / aspectRatio
+ val newTop: Float = halfHeight - (surfaceHeight / 2)
+ val newBottom: Float = halfHeight + (surfaceHeight / 2)
+ super.onMeasure(
+ widthMeasureSpec,
+ MeasureSpec.makeMeasureSpec(
+ newBottom.toInt() - newTop.toInt(),
+ MeasureSpec.EXACTLY
+ )
+ )
+ return
} else {
- newWidth = width
- newHeight = (width / aspectRatio).roundToInt()
+ // Max out height
+ val halfWidth = displayWidth / 2
+ val surfaceWidth = displayHeight * aspectRatio
+ val newLeft: Float = halfWidth - (surfaceWidth / 2)
+ val newRight: Float = halfWidth + (surfaceWidth / 2)
+ super.onMeasure(
+ MeasureSpec.makeMeasureSpec(
+ newRight.toInt() - newLeft.toInt(),
+ MeasureSpec.EXACTLY
+ ),
+ heightMeasureSpec
+ )
+ return
}
- val left = (width - newWidth) / 2
- val top = (height - newHeight) / 2
- setLeftTopRightBottom(left, top, left + newWidth, top + newHeight)
- } else {
- setLeftTopRightBottom(0, 0, width, height)
}
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec)
}
}
diff --git a/src/android/app/src/main/jni/CMakeLists.txt b/src/android/app/src/main/jni/CMakeLists.txt
index e2ed08e9f..e15d1480b 100644
--- a/src/android/app/src/main/jni/CMakeLists.txt
+++ b/src/android/app/src/main/jni/CMakeLists.txt
@@ -14,6 +14,8 @@ add_library(yuzu-android SHARED
id_cache.cpp
id_cache.h
native.cpp
+ native_config.cpp
+ uisettings.cpp
)
set_property(TARGET yuzu-android PROPERTY IMPORTED_LOCATION ${FFmpeg_LIBRARY_DIR})
diff --git a/src/android/app/src/main/jni/config.cpp b/src/android/app/src/main/jni/config.cpp
index 5e1f10f99..34b425cb4 100644
--- a/src/android/app/src/main/jni/config.cpp
+++ b/src/android/app/src/main/jni/config.cpp
@@ -11,22 +11,25 @@
#include "common/fs/path_util.h"
#include "common/logging/log.h"
#include "common/settings.h"
+#include "common/settings_enums.h"
#include "core/hle/service/acc/profile_manager.h"
#include "input_common/main.h"
#include "jni/config.h"
#include "jni/default_ini.h"
+#include "uisettings.h"
namespace FS = Common::FS;
-Config::Config(std::optional<std::filesystem::path> config_path)
- : config_loc{config_path.value_or(FS::GetYuzuPath(FS::YuzuPath::ConfigDir) / "config.ini")},
- config{std::make_unique<INIReader>(FS::PathToUTF8String(config_loc))} {
- Reload();
+Config::Config(const std::string& config_name, ConfigType config_type)
+ : type(config_type), global{config_type == ConfigType::GlobalConfig} {
+ Initialize(config_name);
}
Config::~Config() = default;
bool Config::LoadINI(const std::string& default_contents, bool retry) {
+ void(FS::CreateParentDir(config_loc));
+ config = std::make_unique<INIReader>(FS::PathToUTF8String(config_loc));
const auto config_loc_str = FS::PathToUTF8String(config_loc);
if (config->ParseError() < 0) {
if (retry) {
@@ -144,7 +147,9 @@ void Config::ReadValues() {
Service::Account::MAX_USERS - 1);
// Disable docked mode by default on Android
- Settings::values.use_docked_mode = config->GetBoolean("System", "use_docked_mode", false);
+ Settings::values.use_docked_mode.SetValue(config->GetBoolean("System", "use_docked_mode", false)
+ ? Settings::ConsoleMode::Docked
+ : Settings::ConsoleMode::Handheld);
const auto rng_seed_enabled = config->GetBoolean("System", "rng_seed_enabled", false);
if (rng_seed_enabled) {
@@ -298,9 +303,28 @@ void Config::ReadValues() {
// Network
ReadSetting("Network", Settings::values.network_interface);
+
+ // Android
+ ReadSetting("Android", AndroidSettings::values.picture_in_picture);
+ ReadSetting("Android", AndroidSettings::values.screen_layout);
}
-void Config::Reload() {
+void Config::Initialize(const std::string& config_name) {
+ const auto fs_config_loc = FS::GetYuzuPath(FS::YuzuPath::ConfigDir);
+ const auto config_file = fmt::format("{}.ini", config_name);
+
+ switch (type) {
+ case ConfigType::GlobalConfig:
+ config_loc = FS::PathToUTF8String(fs_config_loc / config_file);
+ break;
+ case ConfigType::PerGameConfig:
+ config_loc = FS::PathToUTF8String(fs_config_loc / "custom" / FS::ToU8String(config_file));
+ break;
+ case ConfigType::InputProfile:
+ config_loc = FS::PathToUTF8String(fs_config_loc / "input" / config_file);
+ LoadINI(DefaultINI::android_config_file);
+ return;
+ }
LoadINI(DefaultINI::android_config_file);
ReadValues();
}
diff --git a/src/android/app/src/main/jni/config.h b/src/android/app/src/main/jni/config.h
index 0d7d6e94d..e1e8f47ed 100644
--- a/src/android/app/src/main/jni/config.h
+++ b/src/android/app/src/main/jni/config.h
@@ -13,25 +13,35 @@
class INIReader;
class Config {
- std::filesystem::path config_loc;
- std::unique_ptr<INIReader> config;
-
bool LoadINI(const std::string& default_contents = "", bool retry = true);
- void ReadValues();
public:
- explicit Config(std::optional<std::filesystem::path> config_path = std::nullopt);
+ enum class ConfigType {
+ GlobalConfig,
+ PerGameConfig,
+ InputProfile,
+ };
+
+ explicit Config(const std::string& config_name = "config",
+ ConfigType config_type = ConfigType::GlobalConfig);
~Config();
- void Reload();
+ void Initialize(const std::string& config_name);
private:
/**
- * Applies a value read from the sdl2_config to a Setting.
+ * Applies a value read from the config to a Setting.
*
* @param group The name of the INI group
* @param setting The yuzu setting to modify
*/
template <typename Type, bool ranged>
void ReadSetting(const std::string& group, Settings::Setting<Type, ranged>& setting);
+
+ void ReadValues();
+
+ const ConfigType type;
+ std::unique_ptr<INIReader> config;
+ std::string config_loc;
+ const bool global;
};
diff --git a/src/android/app/src/main/jni/id_cache.cpp b/src/android/app/src/main/jni/id_cache.cpp
index 9cbbf23a3..960abf95a 100644
--- a/src/android/app/src/main/jni/id_cache.cpp
+++ b/src/android/app/src/main/jni/id_cache.cpp
@@ -15,6 +15,8 @@ static jclass s_disk_cache_progress_class;
static jclass s_load_callback_stage_class;
static jmethodID s_exit_emulation_activity;
static jmethodID s_disk_cache_load_progress;
+static jmethodID s_on_emulation_started;
+static jmethodID s_on_emulation_stopped;
static constexpr jint JNI_VERSION = JNI_VERSION_1_6;
@@ -59,6 +61,14 @@ jmethodID GetDiskCacheLoadProgress() {
return s_disk_cache_load_progress;
}
+jmethodID GetOnEmulationStarted() {
+ return s_on_emulation_started;
+}
+
+jmethodID GetOnEmulationStopped() {
+ return s_on_emulation_stopped;
+}
+
} // namespace IDCache
#ifdef __cplusplus
@@ -85,6 +95,10 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) {
env->GetStaticMethodID(s_native_library_class, "exitEmulationActivity", "(I)V");
s_disk_cache_load_progress =
env->GetStaticMethodID(s_disk_cache_progress_class, "loadProgress", "(III)V");
+ s_on_emulation_started =
+ env->GetStaticMethodID(s_native_library_class, "onEmulationStarted", "()V");
+ s_on_emulation_stopped =
+ env->GetStaticMethodID(s_native_library_class, "onEmulationStopped", "(I)V");
// Initialize Android Storage
Common::FS::Android::RegisterCallbacks(env, s_native_library_class);
diff --git a/src/android/app/src/main/jni/id_cache.h b/src/android/app/src/main/jni/id_cache.h
index be535fe1e..b76158928 100644
--- a/src/android/app/src/main/jni/id_cache.h
+++ b/src/android/app/src/main/jni/id_cache.h
@@ -15,5 +15,7 @@ jclass GetDiskCacheProgressClass();
jclass GetDiskCacheLoadCallbackStageClass();
jmethodID GetExitEmulationActivity();
jmethodID GetDiskCacheLoadProgress();
+jmethodID GetOnEmulationStarted();
+jmethodID GetOnEmulationStopped();
} // namespace IDCache
diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp
index 8b99d1d6e..0f2a6d9e4 100644
--- a/src/android/app/src/main/jni/native.cpp
+++ b/src/android/app/src/main/jni/native.cpp
@@ -203,12 +203,10 @@ public:
}
bool IsRunning() const {
- std::scoped_lock lock(m_mutex);
return m_is_running;
}
bool IsPaused() const {
- std::scoped_lock lock(m_mutex);
return m_is_running && m_is_paused;
}
@@ -335,6 +333,8 @@ public:
// Tear down the render window.
m_window.reset();
+
+ OnEmulationStopped(m_load_result);
}
void PauseEmulation() {
@@ -376,6 +376,8 @@ public:
m_system.InitializeDebugger();
}
+ OnEmulationStarted();
+
while (true) {
{
[[maybe_unused]] std::unique_lock lock(m_mutex);
@@ -420,7 +422,7 @@ public:
return false;
}
- return !Settings::values.use_docked_mode.GetValue();
+ return !Settings::IsDockedMode();
}
void SetDeviceType([[maybe_unused]] int index, int type) {
@@ -511,6 +513,18 @@ private:
static_cast<jint>(progress), static_cast<jint>(max));
}
+ static void OnEmulationStarted() {
+ JNIEnv* env = IDCache::GetEnvForThread();
+ env->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(),
+ IDCache::GetOnEmulationStarted());
+ }
+
+ static void OnEmulationStopped(Core::SystemResultStatus result) {
+ JNIEnv* env = IDCache::GetEnvForThread();
+ env->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(),
+ IDCache::GetOnEmulationStopped(), static_cast<jint>(result));
+ }
+
private:
static EmulationSession s_instance;
@@ -528,8 +542,8 @@ private:
Core::PerfStatsResults m_perf_stats{};
std::shared_ptr<FileSys::VfsFilesystem> m_vfs;
Core::SystemResultStatus m_load_result{Core::SystemResultStatus::ErrorNotInitialized};
- bool m_is_running{};
- bool m_is_paused{};
+ std::atomic<bool> m_is_running = false;
+ std::atomic<bool> m_is_paused = false;
SoftwareKeyboard::AndroidKeyboard* m_software_keyboard{};
std::unique_ptr<Service::Account::ProfileManager> m_profile_manager;
std::unique_ptr<FileSys::ManualContentProvider> m_manual_provider;
@@ -824,34 +838,6 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_reloadSettings(JNIEnv* env, jclass cl
Config{};
}
-jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getUserSetting(JNIEnv* env, jclass clazz,
- jstring j_game_id, jstring j_section,
- jstring j_key) {
- std::string_view game_id = env->GetStringUTFChars(j_game_id, 0);
- std::string_view section = env->GetStringUTFChars(j_section, 0);
- std::string_view key = env->GetStringUTFChars(j_key, 0);
-
- env->ReleaseStringUTFChars(j_game_id, game_id.data());
- env->ReleaseStringUTFChars(j_section, section.data());
- env->ReleaseStringUTFChars(j_key, key.data());
-
- return env->NewStringUTF("");
-}
-
-void Java_org_yuzu_yuzu_1emu_NativeLibrary_setUserSetting(JNIEnv* env, jclass clazz,
- jstring j_game_id, jstring j_section,
- jstring j_key, jstring j_value) {
- std::string_view game_id = env->GetStringUTFChars(j_game_id, 0);
- std::string_view section = env->GetStringUTFChars(j_section, 0);
- std::string_view key = env->GetStringUTFChars(j_key, 0);
- std::string_view value = env->GetStringUTFChars(j_value, 0);
-
- env->ReleaseStringUTFChars(j_game_id, game_id.data());
- env->ReleaseStringUTFChars(j_section, section.data());
- env->ReleaseStringUTFChars(j_key, key.data());
- env->ReleaseStringUTFChars(j_value, value.data());
-}
-
void Java_org_yuzu_yuzu_1emu_NativeLibrary_initGameIni(JNIEnv* env, jclass clazz,
jstring j_game_id) {
std::string_view game_id = env->GetStringUTFChars(j_game_id, 0);
diff --git a/src/android/app/src/main/jni/native_config.cpp b/src/android/app/src/main/jni/native_config.cpp
new file mode 100644
index 000000000..8a704960c
--- /dev/null
+++ b/src/android/app/src/main/jni/native_config.cpp
@@ -0,0 +1,237 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <string>
+
+#include <jni.h>
+
+#include "common/logging/log.h"
+#include "common/settings.h"
+#include "jni/android_common/android_common.h"
+#include "jni/config.h"
+#include "uisettings.h"
+
+template <typename T>
+Settings::Setting<T>* getSetting(JNIEnv* env, jstring jkey) {
+ auto key = GetJString(env, jkey);
+ auto basicSetting = Settings::values.linkage.by_key[key];
+ auto basicAndroidSetting = AndroidSettings::values.linkage.by_key[key];
+ if (basicSetting != 0) {
+ return static_cast<Settings::Setting<T>*>(basicSetting);
+ }
+ if (basicAndroidSetting != 0) {
+ return static_cast<Settings::Setting<T>*>(basicAndroidSetting);
+ }
+ LOG_ERROR(Frontend, "[Android Native] Could not find setting - {}", key);
+ return nullptr;
+}
+
+extern "C" {
+
+jboolean Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getBoolean(JNIEnv* env, jobject obj,
+ jstring jkey, jboolean getDefault) {
+ auto setting = getSetting<bool>(env, jkey);
+ if (setting == nullptr) {
+ return false;
+ }
+ setting->SetGlobal(true);
+
+ if (static_cast<bool>(getDefault)) {
+ return setting->GetDefault();
+ }
+
+ return setting->GetValue();
+}
+
+void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setBoolean(JNIEnv* env, jobject obj, jstring jkey,
+ jboolean value) {
+ auto setting = getSetting<bool>(env, jkey);
+ if (setting == nullptr) {
+ return;
+ }
+ setting->SetGlobal(true);
+ setting->SetValue(static_cast<bool>(value));
+}
+
+jbyte Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getByte(JNIEnv* env, jobject obj, jstring jkey,
+ jboolean getDefault) {
+ auto setting = getSetting<u8>(env, jkey);
+ if (setting == nullptr) {
+ return -1;
+ }
+ setting->SetGlobal(true);
+
+ if (static_cast<bool>(getDefault)) {
+ return setting->GetDefault();
+ }
+
+ return setting->GetValue();
+}
+
+void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setByte(JNIEnv* env, jobject obj, jstring jkey,
+ jbyte value) {
+ auto setting = getSetting<u8>(env, jkey);
+ if (setting == nullptr) {
+ return;
+ }
+ setting->SetGlobal(true);
+ setting->SetValue(value);
+}
+
+jshort Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getShort(JNIEnv* env, jobject obj, jstring jkey,
+ jboolean getDefault) {
+ auto setting = getSetting<u16>(env, jkey);
+ if (setting == nullptr) {
+ return -1;
+ }
+ setting->SetGlobal(true);
+
+ if (static_cast<bool>(getDefault)) {
+ return setting->GetDefault();
+ }
+
+ return setting->GetValue();
+}
+
+void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setShort(JNIEnv* env, jobject obj, jstring jkey,
+ jshort value) {
+ auto setting = getSetting<u16>(env, jkey);
+ if (setting == nullptr) {
+ return;
+ }
+ setting->SetGlobal(true);
+ setting->SetValue(value);
+}
+
+jint Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getInt(JNIEnv* env, jobject obj, jstring jkey,
+ jboolean getDefault) {
+ auto setting = getSetting<int>(env, jkey);
+ if (setting == nullptr) {
+ return -1;
+ }
+ setting->SetGlobal(true);
+
+ if (static_cast<bool>(getDefault)) {
+ return setting->GetDefault();
+ }
+
+ return setting->GetValue();
+}
+
+void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setInt(JNIEnv* env, jobject obj, jstring jkey,
+ jint value) {
+ auto setting = getSetting<int>(env, jkey);
+ if (setting == nullptr) {
+ return;
+ }
+ setting->SetGlobal(true);
+ setting->SetValue(value);
+}
+
+jfloat Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getFloat(JNIEnv* env, jobject obj, jstring jkey,
+ jboolean getDefault) {
+ auto setting = getSetting<float>(env, jkey);
+ if (setting == nullptr) {
+ return -1;
+ }
+ setting->SetGlobal(true);
+
+ if (static_cast<bool>(getDefault)) {
+ return setting->GetDefault();
+ }
+
+ return setting->GetValue();
+}
+
+void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setFloat(JNIEnv* env, jobject obj, jstring jkey,
+ jfloat value) {
+ auto setting = getSetting<float>(env, jkey);
+ if (setting == nullptr) {
+ return;
+ }
+ setting->SetGlobal(true);
+ setting->SetValue(value);
+}
+
+jlong Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getLong(JNIEnv* env, jobject obj, jstring jkey,
+ jboolean getDefault) {
+ auto setting = getSetting<long>(env, jkey);
+ if (setting == nullptr) {
+ return -1;
+ }
+ setting->SetGlobal(true);
+
+ if (static_cast<bool>(getDefault)) {
+ return setting->GetDefault();
+ }
+
+ return setting->GetValue();
+}
+
+void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setLong(JNIEnv* env, jobject obj, jstring jkey,
+ jlong value) {
+ auto setting = getSetting<long>(env, jkey);
+ if (setting == nullptr) {
+ return;
+ }
+ setting->SetGlobal(true);
+ setting->SetValue(value);
+}
+
+jstring Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getString(JNIEnv* env, jobject obj, jstring jkey,
+ jboolean getDefault) {
+ auto setting = getSetting<std::string>(env, jkey);
+ if (setting == nullptr) {
+ return ToJString(env, "");
+ }
+ setting->SetGlobal(true);
+
+ if (static_cast<bool>(getDefault)) {
+ return ToJString(env, setting->GetDefault());
+ }
+
+ return ToJString(env, setting->GetValue());
+}
+
+void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setString(JNIEnv* env, jobject obj, jstring jkey,
+ jstring value) {
+ auto setting = getSetting<std::string>(env, jkey);
+ if (setting == nullptr) {
+ return;
+ }
+
+ setting->SetGlobal(true);
+ setting->SetValue(GetJString(env, value));
+}
+
+jboolean Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getIsRuntimeModifiable(JNIEnv* env, jobject obj,
+ jstring jkey) {
+ auto key = GetJString(env, jkey);
+ auto setting = Settings::values.linkage.by_key[key];
+ if (setting != 0) {
+ return setting->RuntimeModfiable();
+ }
+ LOG_ERROR(Frontend, "[Android Native] Could not find setting - {}", key);
+ return true;
+}
+
+jstring Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getConfigHeader(JNIEnv* env, jobject obj,
+ jint jcategory) {
+ auto category = static_cast<Settings::Category>(jcategory);
+ return ToJString(env, Settings::TranslateCategory(category));
+}
+
+jstring Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getPairedSettingKey(JNIEnv* env, jobject obj,
+ jstring jkey) {
+ auto setting = getSetting<std::string>(env, jkey);
+ if (setting == nullptr) {
+ return ToJString(env, "");
+ }
+ if (setting->PairedSetting() == nullptr) {
+ return ToJString(env, "");
+ }
+
+ return ToJString(env, setting->PairedSetting()->GetLabel());
+}
+
+} // extern "C"
diff --git a/src/android/app/src/main/jni/uisettings.cpp b/src/android/app/src/main/jni/uisettings.cpp
new file mode 100644
index 000000000..f2f0bad50
--- /dev/null
+++ b/src/android/app/src/main/jni/uisettings.cpp
@@ -0,0 +1,10 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "uisettings.h"
+
+namespace AndroidSettings {
+
+Values values;
+
+} // namespace AndroidSettings
diff --git a/src/android/app/src/main/jni/uisettings.h b/src/android/app/src/main/jni/uisettings.h
new file mode 100644
index 000000000..494654af7
--- /dev/null
+++ b/src/android/app/src/main/jni/uisettings.h
@@ -0,0 +1,29 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <common/settings_common.h>
+#include "common/common_types.h"
+#include "common/settings_setting.h"
+
+namespace AndroidSettings {
+
+struct Values {
+ Settings::Linkage linkage;
+
+ // Android
+ Settings::Setting<bool> picture_in_picture{linkage, true, "picture_in_picture",
+ Settings::Category::Android};
+ Settings::Setting<s32> screen_layout{linkage,
+ 5,
+ "screen_layout",
+ Settings::Category::Android,
+ Settings::Specialization::Default,
+ true,
+ true};
+};
+
+extern Values values;
+
+} // namespace AndroidSettings
diff --git a/src/android/app/src/main/res/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 @@
-<?xml version="1.0" encoding="utf-8"?>
-<set xmlns:android="http://schemas.android.com/apk/res/android">
-
- <alpha
- android:duration="125"
- android:interpolator="@android:anim/decelerate_interpolator"
- android:fromAlpha="1"
- android:toAlpha="0" />
-
- <translate
- android:duration="125"
- android:interpolator="@android:anim/decelerate_interpolator"
- android:fromXDelta="0"
- android:toXDelta="-75" />
-
-</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 @@
-<?xml version="1.0" encoding="utf-8"?>
-<set xmlns:android="http://schemas.android.com/apk/res/android">
-
- <alpha
- android:duration="@android:integer/config_shortAnimTime"
- android:interpolator="@android:anim/decelerate_interpolator"
- android:fromAlpha="0"
- android:toAlpha="1" />
-
- <translate
- android:duration="@android:integer/config_shortAnimTime"
- android:interpolator="@android:anim/decelerate_interpolator"
- android:fromXDelta="-200"
- android:toXDelta="0" />
-
-</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 @@
-<?xml version="1.0" encoding="utf-8"?>
-<set xmlns:android="http://schemas.android.com/apk/res/android">
-
- <alpha
- android:duration="125"
- android:interpolator="@android:anim/decelerate_interpolator"
- android:fromAlpha="1"
- android:toAlpha="0" />
-
- <translate
- android:duration="125"
- android:interpolator="@android:anim/decelerate_interpolator"
- android:fromXDelta="0"
- android:toXDelta="75" />
-
-</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 @@
-<?xml version="1.0" encoding="utf-8"?>
-<set xmlns:android="http://schemas.android.com/apk/res/android">
-
- <alpha
- android:duration="@android:integer/config_shortAnimTime"
- android:interpolator="@android:anim/decelerate_interpolator"
- android:fromAlpha="0"
- android:toAlpha="1" />
-
- <translate
- android:duration="@android:integer/config_shortAnimTime"
- android:interpolator="@android:anim/decelerate_interpolator"
- android:fromXDelta="200"
- android:toXDelta="0" />
-
-</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 @@
-<?xml version="1.0" encoding="utf-8"?>
-<set xmlns:android="http://schemas.android.com/apk/res/android">
-
- <alpha
- android:duration="@android:integer/config_shortAnimTime"
- android:interpolator="@android:anim/decelerate_interpolator"
- android:fromAlpha="1"
- android:toAlpha="0" />
-
-</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 @@
-<?xml version="1.0" encoding="utf-8"?>
-<set xmlns:android="http://schemas.android.com/apk/res/android">
-
- <objectAnimator
- android:propertyName="translationX"
- android:valueType="floatType"
- android:valueFrom="-1280dp"
- android:valueTo="0"
- android:interpolator="@android:interpolator/decelerate_quad"
- android:duration="300"/>
-
- <objectAnimator
- android:propertyName="alpha"
- android:valueType="floatType"
- android:valueFrom="0"
- android:valueTo="1"
- android:interpolator="@android:interpolator/accelerate_quad"
- android:duration="300"/>
-
-</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 @@
-<?xml version="1.0" encoding="utf-8"?>
-<set xmlns:android="http://schemas.android.com/apk/res/android">
-
- <!-- This animation is used ONLY when a submenu is replaced. -->
- <objectAnimator
- android:propertyName="translationX"
- android:valueType="floatType"
- android:valueFrom="0"
- android:valueTo="-1280dp"
- android:interpolator="@android:interpolator/decelerate_quad"
- android:duration="200"/>
-
- <objectAnimator
- android:propertyName="alpha"
- android:valueType="floatType"
- android:valueFrom="1"
- android:valueTo="0"
- android:interpolator="@android:interpolator/decelerate_quad"
- android:duration="200"/>
-
-</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 @@
<?xml version="1.0" encoding="utf-8"?>
-<androidx.coordinatorlayout.widget.CoordinatorLayout
- android:id="@+id/coordinator_main"
+<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/constraint_settings"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/colorSurface">
- <com.google.android.material.appbar.AppBarLayout
- android:id="@+id/appbar_settings"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:fitsSystemWindows="true"
- app:elevation="0dp">
-
- <com.google.android.material.appbar.CollapsingToolbarLayout
- style="?attr/collapsingToolbarLayoutMediumStyle"
- android:id="@+id/toolbar_settings_layout"
- android:layout_width="match_parent"
- android:layout_height="?attr/collapsingToolbarLayoutMediumSize"
- app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">
-
- <com.google.android.material.appbar.MaterialToolbar
- android:id="@+id/toolbar_settings"
- android:layout_width="match_parent"
- android:layout_height="?attr/actionBarSize"
- app:layout_collapseMode="pin" />
-
- </com.google.android.material.appbar.CollapsingToolbarLayout>
-
- </com.google.android.material.appbar.AppBarLayout>
-
- <FrameLayout
- android:id="@+id/frame_content"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_marginHorizontal="12dp"
- app:layout_behavior="@string/appbar_scrolling_view_behavior" />
+ <androidx.fragment.app.FragmentContainerView
+ android:id="@+id/fragment_container"
+ android:name="androidx.navigation.fragment.NavHostFragment"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ app:defaultNavHost="true"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintLeft_toLeftOf="parent"
+ app:layout_constraintRight_toRightOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ tools:layout="@layout/fragment_settings" />
<View
android:id="@+id/navigation_bar_shade"
@@ -45,6 +27,8 @@
android:background="@android:color/transparent"
android:clickable="false"
android:focusable="false"
- android:layout_gravity="bottom|center_horizontal" />
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent" />
-</androidx.coordinatorlayout.widget.CoordinatorLayout>
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/src/android/app/src/main/res/layout/fragment_emulation.xml b/src/android/app/src/main/res/layout/fragment_emulation.xml
index e54a10e8f..da97d85c1 100644
--- a/src/android/app/src/main/res/layout/fragment_emulation.xml
+++ b/src/android/app/src/main/res/layout/fragment_emulation.xml
@@ -26,6 +26,81 @@
android:focusable="false"
android:focusableInTouchMode="false" />
+ <com.google.android.material.card.MaterialCardView
+ android:id="@+id/loading_indicator"
+ style="?attr/materialCardViewOutlinedStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:focusable="false">
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:id="@+id/loading_layout"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal">
+
+ <ImageView
+ android:id="@+id/loading_image"
+ android:layout_width="wrap_content"
+ android:layout_height="0dp"
+ android:adjustViewBounds="true"
+ app:layout_constraintBottom_toBottomOf="@+id/linearLayout"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="@+id/linearLayout"
+ tools:src="@drawable/default_icon" />
+
+ <LinearLayout
+ android:id="@+id/linearLayout"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:paddingHorizontal="24dp"
+ android:paddingVertical="36dp"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toEndOf="@id/loading_image"
+ app:layout_constraintTop_toTopOf="parent">
+
+ <com.google.android.material.textview.MaterialTextView
+ android:id="@+id/loading_title"
+ style="@style/TextAppearance.Material3.TitleMedium"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:ellipsize="marquee"
+ android:marqueeRepeatLimit="marquee_forever"
+ android:requiresFadingEdge="horizontal"
+ android:singleLine="true"
+ android:textAlignment="viewStart"
+ tools:text="@string/games" />
+
+ <com.google.android.material.textview.MaterialTextView
+ android:id="@+id/loading_text"
+ style="@style/TextAppearance.Material3.TitleSmall"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="4dp"
+ android:ellipsize="marquee"
+ android:marqueeRepeatLimit="marquee_forever"
+ android:requiresFadingEdge="horizontal"
+ android:singleLine="true"
+ android:text="@string/loading"
+ android:textAlignment="viewStart" />
+
+ <com.google.android.material.progressindicator.LinearProgressIndicator
+ android:id="@+id/loading_progress_indicator"
+ android:layout_width="192dp"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="12dp"
+ android:indeterminate="true"
+ app:trackCornerRadius="8dp" />
+
+ </LinearLayout>
+
+ </androidx.constraintlayout.widget.ConstraintLayout>
+
+ </com.google.android.material.card.MaterialCardView>
+
</FrameLayout>
<FrameLayout
@@ -41,11 +116,12 @@
android:layout_height="match_parent"
android:layout_gravity="center"
android:focusable="true"
- android:focusableInTouchMode="true" />
+ android:focusableInTouchMode="true"
+ android:visibility="invisible" />
<Button
- style="@style/Widget.Material3.Button.ElevatedButton"
android:id="@+id/done_control_config"
+ style="@style/Widget.Material3.Button.ElevatedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
@@ -81,6 +157,7 @@
android:layout_height="match_parent"
android:layout_gravity="start|bottom"
app:headerLayout="@layout/header_in_game"
- app:menu="@menu/menu_in_game" />
+ app:menu="@menu/menu_in_game"
+ tools:visibility="gone" />
</androidx.drawerlayout.widget.DrawerLayout>
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 @@
<?xml version="1.0" encoding="utf-8"?>
-<FrameLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
+<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/coordinator_main"
android:layout_width="match_parent"
- android:layout_height="match_parent">
+ android:layout_height="match_parent"
+ android:background="?attr/colorSurface">
+
+ <com.google.android.material.appbar.AppBarLayout
+ android:id="@+id/appbar_settings"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:fitsSystemWindows="true"
+ app:elevation="0dp">
+
+ <com.google.android.material.appbar.CollapsingToolbarLayout
+ android:id="@+id/toolbar_settings_layout"
+ style="?attr/collapsingToolbarLayoutMediumStyle"
+ android:layout_width="match_parent"
+ android:layout_height="?attr/collapsingToolbarLayoutMediumSize"
+ app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">
+
+ <com.google.android.material.appbar.MaterialToolbar
+ android:id="@+id/toolbar_settings"
+ android:layout_width="match_parent"
+ android:layout_height="?attr/actionBarSize"
+ app:layout_collapseMode="pin"
+ app:navigationIcon="@drawable/ic_back" />
+
+ </com.google.android.material.appbar.CollapsingToolbarLayout>
+
+ </com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/list_settings"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:background="?attr/colorSurface"
- android:clipToPadding="false" />
+ android:clipToPadding="false"
+ app:layout_behavior="@string/appbar_scrolling_view_behavior" />
-</FrameLayout>
+</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 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <RelativeLayout
+ android:id="@+id/relativeLayout"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/divider">
+
+ <LinearLayout
+ android:id="@+id/no_results_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:orientation="vertical">
+
+ <ImageView
+ android:id="@+id/icon_no_results"
+ android:layout_width="match_parent"
+ android:layout_height="80dp"
+ android:src="@drawable/ic_search" />
+
+ <com.google.android.material.textview.MaterialTextView
+ android:id="@+id/notice_text"
+ style="@style/TextAppearance.Material3.TitleLarge"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:paddingTop="8dp"
+ android:text="@string/search_settings"
+ tools:visibility="visible" />
+
+ </LinearLayout>
+
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/settings_list"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:clipToPadding="false" />
+
+ </RelativeLayout>
+
+ <FrameLayout
+ android:id="@+id/frame_search"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:clipToPadding="false"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent">
+
+ <com.google.android.material.card.MaterialCardView
+ android:id="@+id/search_background"
+ style="?attr/materialCardViewFilledStyle"
+ android:layout_width="match_parent"
+ android:layout_height="56dp"
+ app:cardCornerRadius="28dp">
+
+ <LinearLayout
+ android:id="@+id/search_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginEnd="56dp"
+ android:orientation="horizontal">
+
+ <Button
+ android:id="@+id/back_button"
+ style="?attr/materialIconButtonFilledTonalStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_marginStart="8dp"
+ app:backgroundTint="@android:color/transparent"
+ app:icon="@drawable/ic_back" />
+
+ <EditText
+ android:id="@+id/search_text"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@android:color/transparent"
+ android:hint="@string/search_settings"
+ android:imeOptions="flagNoFullscreen"
+ android:inputType="text"
+ android:maxLines="1" />
+
+ </LinearLayout>
+
+ <Button
+ android:id="@+id/clear_button"
+ style="?attr/materialIconButtonFilledTonalStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical|end"
+ android:layout_marginEnd="8dp"
+ android:visibility="invisible"
+ app:backgroundTint="@android:color/transparent"
+ app:icon="@drawable/ic_clear"
+ tools:visibility="visible" />
+
+ </com.google.android.material.card.MaterialCardView>
+
+ </FrameLayout>
+
+ <com.google.android.material.divider.MaterialDivider
+ android:id="@+id/divider"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="20dp"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/frame_search" />
+
+</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 @@
<?xml version="1.0" encoding="utf-8"?>
-<menu /> \ No newline at end of file
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+
+ <item
+ android:id="@+id/action_search"
+ android:icon="@drawable/ic_search"
+ android:title="@string/home_search"
+ app:showAsAction="always" />
+
+</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 8208f4c2c..c7be37f9b 100644
--- a/src/android/app/src/main/res/navigation/emulation_navigation.xml
+++ b/src/android/app/src/main/res/navigation/emulation_navigation.xml
@@ -12,7 +12,26 @@
tools:layout="@layout/fragment_emulation" >
<argument
android:name="game"
- app:argType="org.yuzu.yuzu_emu.model.Game" />
+ app:argType="org.yuzu.yuzu_emu.model.Game"
+ app:nullable="true"
+ android:defaultValue="@null" />
</fragment>
+ <activity
+ android:id="@+id/settingsActivity"
+ android:name="org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity"
+ android:label="SettingsActivity">
+ <argument
+ android:name="game"
+ app:argType="org.yuzu.yuzu_emu.model.Game"
+ app:nullable="true" />
+ <argument
+ android:name="menuTag"
+ app:argType="string" />
+ </activity>
+
+ <action
+ android:id="@+id/action_global_settingsActivity"
+ app:destination="@id/settingsActivity" />
+
</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 fcebba726..2085430bf 100644
--- a/src/android/app/src/main/res/navigation/home_navigation.xml
+++ b/src/android/app/src/main/res/navigation/home_navigation.xml
@@ -62,7 +62,9 @@
android:label="EmulationActivity">
<argument
android:name="game"
- app:argType="org.yuzu.yuzu_emu.model.Game" />
+ app:argType="org.yuzu.yuzu_emu.model.Game"
+ app:nullable="true"
+ android:defaultValue="@null" />
</activity>
<action
@@ -70,4 +72,21 @@
app:destination="@id/emulationActivity"
app:launchSingleTop="true" />
+ <activity
+ android:id="@+id/settingsActivity"
+ android:name="org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity"
+ android:label="SettingsActivity">
+ <argument
+ android:name="game"
+ app:argType="org.yuzu.yuzu_emu.model.Game"
+ app:nullable="true" />
+ <argument
+ android:name="menuTag"
+ app:argType="string" />
+ </activity>
+
+ <action
+ android:id="@+id/action_global_settingsActivity"
+ app:destination="@id/settingsActivity" />
+
</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 @@
+<?xml version="1.0" encoding="utf-8"?>
+<navigation xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/settings_navigation"
+ app:startDestination="@id/settingsFragment">
+
+ <fragment
+ android:id="@+id/settingsFragment"
+ android:name="org.yuzu.yuzu_emu.features.settings.ui.SettingsFragment"
+ android:label="SettingsFragment">
+ <argument
+ android:name="menuTag"
+ app:argType="string" />
+ <argument
+ android:name="game"
+ app:argType="org.yuzu.yuzu_emu.model.Game"
+ app:nullable="true" />
+ <action
+ android:id="@+id/action_settingsFragment_to_settingsSearchFragment"
+ app:destination="@id/settingsSearchFragment" />
+ </fragment>
+
+ <action
+ android:id="@+id/action_global_settingsFragment"
+ app:destination="@id/settingsFragment" />
+
+ <fragment
+ android:id="@+id/settingsSearchFragment"
+ android:name="org.yuzu.yuzu_emu.fragments.SettingsSearchFragment"
+ android:label="SettingsSearchFragment" />
+
+</navigation>
diff --git a/src/android/app/src/main/res/values-de/strings.xml b/src/android/app/src/main/res/values-de/strings.xml
index 0c1d91264..daaa7ffde 100644
--- a/src/android/app/src/main/res/values-de/strings.xml
+++ b/src/android/app/src/main/res/values-de/strings.xml
@@ -209,7 +209,6 @@
<string name="emulation_pause">Emulation pausieren</string>
<string name="emulation_unpause">Emulation fortsetzen</string>
<string name="emulation_input_overlay">Overlay-Optionen</string>
- <string name="emulation_game_loading">Spiel lädt…</string>
<string name="load_settings">Lädt Einstellungen...</string>
diff --git a/src/android/app/src/main/res/values-es/strings.xml b/src/android/app/src/main/res/values-es/strings.xml
index 357f956d1..e9129cb00 100644
--- a/src/android/app/src/main/res/values-es/strings.xml
+++ b/src/android/app/src/main/res/values-es/strings.xml
@@ -213,7 +213,6 @@
<string name="emulation_pause">Pausar Emulación</string>
<string name="emulation_unpause">Reanudar Emulación</string>
<string name="emulation_input_overlay">Opciones de pantalla </string>
- <string name="emulation_game_loading">Cargando juego...</string>
<string name="load_settings">Cargando configuración...</string>
diff --git a/src/android/app/src/main/res/values-fr/strings.xml b/src/android/app/src/main/res/values-fr/strings.xml
index dfca1c830..2d99d618e 100644
--- a/src/android/app/src/main/res/values-fr/strings.xml
+++ b/src/android/app/src/main/res/values-fr/strings.xml
@@ -213,7 +213,6 @@
<string name="emulation_pause">Mettre en pause l\'émulation</string>
<string name="emulation_unpause">Reprendre l\'émulation</string>
<string name="emulation_input_overlay">Options de l\'overlay</string>
- <string name="emulation_game_loading">Chargement du jeu...</string>
<string name="load_settings">Chargement des paramètres…</string>
diff --git a/src/android/app/src/main/res/values-it/strings.xml b/src/android/app/src/main/res/values-it/strings.xml
index 089d93ed6..d9c3de385 100644
--- a/src/android/app/src/main/res/values-it/strings.xml
+++ b/src/android/app/src/main/res/values-it/strings.xml
@@ -213,7 +213,6 @@
<string name="emulation_pause">Metti in pausa l\'emulazione</string>
<string name="emulation_unpause">Riprendi Emulazione</string>
<string name="emulation_input_overlay">Impostazioni Overlay</string>
- <string name="emulation_game_loading">Caricamento del gioco...</string>
<string name="load_settings">Caricamento delle impostazioni...</string>
diff --git a/src/android/app/src/main/res/values-ja/strings.xml b/src/android/app/src/main/res/values-ja/strings.xml
index 39b590bee..7a226cd5c 100644
--- a/src/android/app/src/main/res/values-ja/strings.xml
+++ b/src/android/app/src/main/res/values-ja/strings.xml
@@ -211,7 +211,6 @@
<string name="emulation_pause">エミュレーションを一時停止</string>
<string name="emulation_unpause">エミュレーションを再開</string>
<string name="emulation_input_overlay">オーバーレイオプション</string>
- <string name="emulation_game_loading">ロード中…</string>
<string name="load_settings">設定をロード中…</string>
diff --git a/src/android/app/src/main/res/values-ko/strings.xml b/src/android/app/src/main/res/values-ko/strings.xml
index cbcb2873f..427b6e5a0 100644
--- a/src/android/app/src/main/res/values-ko/strings.xml
+++ b/src/android/app/src/main/res/values-ko/strings.xml
@@ -213,7 +213,6 @@
<string name="emulation_pause">에뮬레이션 일시 중지</string>
<string name="emulation_unpause">에뮬레이션 일시 중지 해제</string>
<string name="emulation_input_overlay">오버레이 옵션</string>
- <string name="emulation_game_loading">게임 불러오기 중...</string>
<string name="load_settings">설정 불러오기 중...</string>
diff --git a/src/android/app/src/main/res/values-nb/strings.xml b/src/android/app/src/main/res/values-nb/strings.xml
index e48a4be38..ce8d7a9e4 100644
--- a/src/android/app/src/main/res/values-nb/strings.xml
+++ b/src/android/app/src/main/res/values-nb/strings.xml
@@ -213,7 +213,6 @@
<string name="emulation_pause">Pause Emulering</string>
<string name="emulation_unpause">Opphev pausing av emulering</string>
<string name="emulation_input_overlay">Alternativer for overlegg</string>
- <string name="emulation_game_loading">Spillet lastes inn...</string>
<string name="load_settings">Laster inn innstillinger...</string>
diff --git a/src/android/app/src/main/res/values-pl/strings.xml b/src/android/app/src/main/res/values-pl/strings.xml
index bc9c0f7f4..c2c24b48f 100644
--- a/src/android/app/src/main/res/values-pl/strings.xml
+++ b/src/android/app/src/main/res/values-pl/strings.xml
@@ -213,7 +213,6 @@
<string name="emulation_pause">Wstrzymaj emulację</string>
<string name="emulation_unpause">Wznów emulację</string>
<string name="emulation_input_overlay">Opcje nakładki</string>
- <string name="emulation_game_loading">Wczytywanie gry...</string>
<string name="load_settings">Wczytywanie ustawień...</string>
diff --git a/src/android/app/src/main/res/values-pt-rBR/strings.xml b/src/android/app/src/main/res/values-pt-rBR/strings.xml
index 75fe0edbf..04f276108 100644
--- a/src/android/app/src/main/res/values-pt-rBR/strings.xml
+++ b/src/android/app/src/main/res/values-pt-rBR/strings.xml
@@ -213,7 +213,6 @@
<string name="emulation_pause">Pausa emulação</string>
<string name="emulation_unpause">Retomar emulação</string>
<string name="emulation_input_overlay">Opções de sobreposição </string>
- <string name="emulation_game_loading">Jogo a carregar...</string>
<string name="load_settings">Configurações a carregar...</string>
diff --git a/src/android/app/src/main/res/values-pt-rPT/strings.xml b/src/android/app/src/main/res/values-pt-rPT/strings.xml
index 96b040c66..66a3a1a2e 100644
--- a/src/android/app/src/main/res/values-pt-rPT/strings.xml
+++ b/src/android/app/src/main/res/values-pt-rPT/strings.xml
@@ -213,7 +213,6 @@
<string name="emulation_pause">Pausa emulação</string>
<string name="emulation_unpause">Retomar emulação</string>
<string name="emulation_input_overlay">Opções de sobreposição </string>
- <string name="emulation_game_loading">Jogo a carregar...</string>
<string name="load_settings">Configurações a carregar...</string>
diff --git a/src/android/app/src/main/res/values-ru/strings.xml b/src/android/app/src/main/res/values-ru/strings.xml
index 8d954f59e..f770e954f 100644
--- a/src/android/app/src/main/res/values-ru/strings.xml
+++ b/src/android/app/src/main/res/values-ru/strings.xml
@@ -213,7 +213,6 @@
<string name="emulation_pause">Пауза эмуляции</string>
<string name="emulation_unpause">Возобновление эмуляции</string>
<string name="emulation_input_overlay">Настройки оверлея</string>
- <string name="emulation_game_loading">Загрузка игры...</string>
<string name="load_settings">Загрузка настроек...</string>
diff --git a/src/android/app/src/main/res/values-uk/strings.xml b/src/android/app/src/main/res/values-uk/strings.xml
index 6c028535b..ea3ab1b15 100644
--- a/src/android/app/src/main/res/values-uk/strings.xml
+++ b/src/android/app/src/main/res/values-uk/strings.xml
@@ -213,7 +213,6 @@
<string name="emulation_pause">Пауза емуляції</string>
<string name="emulation_unpause">Відновлення емуляції</string>
<string name="emulation_input_overlay">Налаштування оверлея</string>
- <string name="emulation_game_loading">Завантаження гри...</string>
<string name="load_settings">Завантаження налаштувань...</string>
diff --git a/src/android/app/src/main/res/values-zh-rCN/strings.xml b/src/android/app/src/main/res/values-zh-rCN/strings.xml
index e4ad2ed07..b45a5a528 100644
--- a/src/android/app/src/main/res/values-zh-rCN/strings.xml
+++ b/src/android/app/src/main/res/values-zh-rCN/strings.xml
@@ -213,7 +213,6 @@
<string name="emulation_pause">暂停模拟</string>
<string name="emulation_unpause">继续模拟</string>
<string name="emulation_input_overlay">虚拟按键选项</string>
- <string name="emulation_game_loading">载入游戏中…</string>
<string name="load_settings">正在载入设定…</string>
diff --git a/src/android/app/src/main/res/values-zh-rTW/strings.xml b/src/android/app/src/main/res/values-zh-rTW/strings.xml
index 0d32f23df..3aab889e4 100644
--- a/src/android/app/src/main/res/values-zh-rTW/strings.xml
+++ b/src/android/app/src/main/res/values-zh-rTW/strings.xml
@@ -213,7 +213,6 @@
<string name="emulation_pause">暫停模擬</string>
<string name="emulation_unpause">取消暫停模擬</string>
<string name="emulation_input_overlay">覆疊選項</string>
- <string name="emulation_game_loading">遊戲正在載入…</string>
<string name="load_settings">正在載入設定…</string>
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 @@
<item>@string/cubeb</item>
<item>@string/string_null</item>
</string-array>
- <string-array name="outputEngineValues">
- <item>auto</item>
- <item>cubeb</item>
- <item>null</item>
- </string-array>
+ <integer-array name="outputEngineValues">
+ <item>0</item>
+ <item>1</item>
+ <item>3</item>
+ </integer-array>
</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..b163e6fc1 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 @@
<string name="add_games_warning_description">Games won\'t be displayed in the Games list if a folder isn\'t selected.</string>
<string name="add_games_warning_help">https://yuzu-emu.org/help/quickstart/#dumping-games</string>
<string name="home_search_games">Search games</string>
+ <string name="search_settings">Search settings</string>
<string name="games_dir_selected">Games directory selected</string>
<string name="install_prod_keys">Install prod.keys</string>
<string name="install_prod_keys_description">Required to decrypt retail games</string>
@@ -74,6 +75,7 @@
<string name="install_gpu_driver">Install GPU driver</string>
<string name="install_gpu_driver_description">Install alternative drivers for potentially better performance or accuracy</string>
<string name="advanced_settings">Advanced settings</string>
+ <string name="advanced_settings_game">Advanced settings: %1$s</string>
<string name="settings_description">Configure emulator settings</string>
<string name="search_recently_played">Recently played</string>
<string name="search_recently_added">Recently added</string>
@@ -200,7 +202,9 @@
<string name="ini_saved">Saved settings</string>
<string name="gameid_saved">Saved settings for %1$s</string>
<string name="error_saving">Error saving %1$s.ini: %2$s</string>
+ <string name="unimplemented_menu">Unimplemented Menu</string>
<string name="loading">Loading…</string>
+ <string name="shutting_down">Shutting down…</string>
<string name="reset_setting_confirmation">Do you want to reset this setting back to its default value?</string>
<string name="reset_to_default">Reset to default</string>
<string name="reset_all_settings">Reset all settings?</string>
@@ -259,7 +263,6 @@
<string name="emulation_pause">Pause emulation</string>
<string name="emulation_unpause">Unpause emulation</string>
<string name="emulation_input_overlay">Overlay options</string>
- <string name="emulation_game_loading">Game loading…</string>
<string name="load_settings">Loading settings…</string>
diff --git a/src/audio_core/renderer/system.cpp b/src/audio_core/renderer/system.cpp
index a23627472..6e07baa54 100644
--- a/src/audio_core/renderer/system.cpp
+++ b/src/audio_core/renderer/system.cpp
@@ -778,7 +778,7 @@ u32 System::DropVoices(CommandBuffer& command_buffer, u32 estimated_process_time
while (i < command_buffer.count) {
const auto node_id{cmd->node_id};
const auto node_id_type{cmd->node_id >> 28};
- const auto node_id_base{cmd->node_id & 0xFFF};
+ const auto node_id_base{(cmd->node_id >> 16) & 0xFFF};
// If the new estimated process time falls below the limit, we're done dropping.
if (estimated_process_time <= time_limit) {
diff --git a/src/audio_core/sink/cubeb_sink.cpp b/src/audio_core/sink/cubeb_sink.cpp
index 9a0801888..bbb598bc5 100644
--- a/src/audio_core/sink/cubeb_sink.cpp
+++ b/src/audio_core/sink/cubeb_sink.cpp
@@ -8,6 +8,7 @@
#include "audio_core/sink/cubeb_sink.h"
#include "audio_core/sink/sink_stream.h"
#include "common/logging/log.h"
+#include "common/scope_exit.h"
#include "core/core.h"
#ifdef _WIN32
@@ -332,25 +333,38 @@ std::vector<std::string> ListCubebSinkDevices(bool capture) {
return device_list;
}
-u32 GetCubebLatency() {
- cubeb* ctx;
+namespace {
+static long TmpDataCallback(cubeb_stream*, void*, const void*, void*, long) {
+ return TargetSampleCount;
+}
+static void TmpStateCallback(cubeb_stream*, void*, cubeb_state) {}
+} // namespace
+
+bool IsCubebSuitable() {
+#if !defined(HAVE_CUBEB)
+ return false;
+#else
+ cubeb* ctx{nullptr};
#ifdef _WIN32
auto com_init_result = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
#endif
+ // Init cubeb
if (cubeb_init(&ctx, "yuzu Latency Getter", nullptr) != CUBEB_OK) {
- LOG_CRITICAL(Audio_Sink, "cubeb_init failed");
- // Return a large latency so we choose SDL instead.
- return 10000u;
+ LOG_ERROR(Audio_Sink, "Cubeb failed to init, it is not suitable.");
+ return false;
}
+ SCOPE_EXIT({ cubeb_destroy(ctx); });
+
#ifdef _WIN32
if (SUCCEEDED(com_init_result)) {
CoUninitialize();
}
#endif
+ // Get min latency
cubeb_stream_params params{};
params.rate = TargetSampleRate;
params.channels = 2;
@@ -361,12 +375,27 @@ u32 GetCubebLatency() {
u32 latency{0};
const auto latency_error = cubeb_get_min_latency(ctx, &params, &latency);
if (latency_error != CUBEB_OK) {
- LOG_CRITICAL(Audio_Sink, "Error getting minimum latency, error: {}", latency_error);
- latency = TargetSampleCount * 2;
+ LOG_ERROR(Audio_Sink, "Cubeb could not get min latency, it is not suitable.");
+ return false;
}
latency = std::max(latency, TargetSampleCount * 2);
- cubeb_destroy(ctx);
- return latency;
+
+ // Test opening a device with standard parameters
+ cubeb_devid output_device{0};
+ cubeb_devid input_device{0};
+ std::string name{"Yuzu test"};
+ cubeb_stream* stream{nullptr};
+
+ if (cubeb_stream_init(ctx, &stream, name.c_str(), input_device, nullptr, output_device, &params,
+ latency, &TmpDataCallback, &TmpStateCallback, nullptr) != CUBEB_OK) {
+ LOG_CRITICAL(Audio_Sink, "Cubeb could not open a device, it is not suitable.");
+ return false;
+ }
+
+ cubeb_stream_stop(stream);
+ cubeb_stream_destroy(stream);
+ return true;
+#endif
}
} // namespace AudioCore::Sink
diff --git a/src/audio_core/sink/cubeb_sink.h b/src/audio_core/sink/cubeb_sink.h
index 3302cb98d..f49a6fdaa 100644
--- a/src/audio_core/sink/cubeb_sink.h
+++ b/src/audio_core/sink/cubeb_sink.h
@@ -97,10 +97,11 @@ private:
std::vector<std::string> ListCubebSinkDevices(bool capture);
/**
- * Get the reported latency for this sink.
+ * Check if this backend is suitable for use.
+ * Checks if enabled, its latency, whether it opens successfully, etc.
*
- * @return Minimum latency for this sink.
+ * @return True is this backend is suitable, false otherwise.
*/
-u32 GetCubebLatency();
+bool IsCubebSuitable();
} // namespace AudioCore::Sink
diff --git a/src/audio_core/sink/sdl2_sink.cpp b/src/audio_core/sink/sdl2_sink.cpp
index c1529d1f9..7b89151de 100644
--- a/src/audio_core/sink/sdl2_sink.cpp
+++ b/src/audio_core/sink/sdl2_sink.cpp
@@ -9,6 +9,7 @@
#include "audio_core/sink/sdl2_sink.h"
#include "audio_core/sink/sink_stream.h"
#include "common/logging/log.h"
+#include "common/scope_exit.h"
#include "core/core.h"
namespace AudioCore::Sink {
@@ -84,6 +85,7 @@ public:
}
Stop();
+ SDL_ClearQueuedAudio(device);
SDL_CloseAudioDevice(device);
}
@@ -227,8 +229,42 @@ std::vector<std::string> ListSDLSinkDevices(bool capture) {
return device_list;
}
-u32 GetSDLLatency() {
- return TargetSampleCount * 2;
+bool IsSDLSuitable() {
+#if !defined(HAVE_SDL2)
+ return false;
+#else
+ // Check SDL can init
+ if (!SDL_WasInit(SDL_INIT_AUDIO)) {
+ if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) {
+ LOG_ERROR(Audio_Sink, "SDL failed to init, it is not suitable. Error: {}",
+ SDL_GetError());
+ return false;
+ }
+ }
+
+ // We can set any latency frequency we want with SDL, so no need to check that.
+
+ // Check we can open a device with standard parameters
+ SDL_AudioSpec spec;
+ spec.freq = TargetSampleRate;
+ spec.channels = 2u;
+ spec.format = AUDIO_S16SYS;
+ spec.samples = TargetSampleCount * 2;
+ spec.callback = nullptr;
+ spec.userdata = nullptr;
+
+ SDL_AudioSpec obtained;
+ auto device = SDL_OpenAudioDevice(nullptr, false, &spec, &obtained, false);
+
+ if (device == 0) {
+ LOG_ERROR(Audio_Sink, "SDL failed to open a device, it is not suitable. Error: {}",
+ SDL_GetError());
+ return false;
+ }
+
+ SDL_CloseAudioDevice(device);
+ return true;
+#endif
}
} // namespace AudioCore::Sink
diff --git a/src/audio_core/sink/sdl2_sink.h b/src/audio_core/sink/sdl2_sink.h
index 27ed1ab94..9211d2e97 100644
--- a/src/audio_core/sink/sdl2_sink.h
+++ b/src/audio_core/sink/sdl2_sink.h
@@ -88,10 +88,11 @@ private:
std::vector<std::string> ListSDLSinkDevices(bool capture);
/**
- * Get the reported latency for this sink.
+ * Check if this backend is suitable for use.
+ * Checks if enabled, its latency, whether it opens successfully, etc.
*
- * @return Minimum latency for this sink.
+ * @return True is this backend is suitable, false otherwise.
*/
-u32 GetSDLLatency();
+bool IsSDLSuitable();
} // namespace AudioCore::Sink
diff --git a/src/audio_core/sink/sink_details.cpp b/src/audio_core/sink/sink_details.cpp
index 027bfa517..7c9a4e3ac 100644
--- a/src/audio_core/sink/sink_details.cpp
+++ b/src/audio_core/sink/sink_details.cpp
@@ -22,7 +22,7 @@ namespace {
struct SinkDetails {
using FactoryFn = std::unique_ptr<Sink> (*)(std::string_view);
using ListDevicesFn = std::vector<std::string> (*)(bool);
- using LatencyFn = u32 (*)();
+ using SuitableFn = bool (*)();
/// Name for this sink.
Settings::AudioEngine id;
@@ -30,8 +30,8 @@ struct SinkDetails {
FactoryFn factory;
/// A method to call to list available devices.
ListDevicesFn list_devices;
- /// Method to get the latency of this backend.
- LatencyFn latency;
+ /// Check whether this backend is suitable to be used.
+ SuitableFn is_suitable;
};
// sink_details is ordered in terms of desirability, with the best choice at the top.
@@ -43,7 +43,7 @@ constexpr SinkDetails sink_details[] = {
return std::make_unique<CubebSink>(device_id);
},
&ListCubebSinkDevices,
- &GetCubebLatency,
+ &IsCubebSuitable,
},
#endif
#ifdef HAVE_SDL2
@@ -53,14 +53,17 @@ constexpr SinkDetails sink_details[] = {
return std::make_unique<SDLSink>(device_id);
},
&ListSDLSinkDevices,
- &GetSDLLatency,
+ &IsSDLSuitable,
},
#endif
- SinkDetails{Settings::AudioEngine::Null,
- [](std::string_view device_id) -> std::unique_ptr<Sink> {
- return std::make_unique<NullSink>(device_id);
- },
- [](bool capture) { return std::vector<std::string>{"null"}; }, []() { return 0u; }},
+ SinkDetails{
+ Settings::AudioEngine::Null,
+ [](std::string_view device_id) -> std::unique_ptr<Sink> {
+ return std::make_unique<NullSink>(device_id);
+ },
+ [](bool capture) { return std::vector<std::string>{"null"}; },
+ []() { return true; },
+ },
};
const SinkDetails& GetOutputSinkDetails(Settings::AudioEngine sink_id) {
@@ -72,18 +75,22 @@ const SinkDetails& GetOutputSinkDetails(Settings::AudioEngine sink_id) {
auto iter = find_backend(sink_id);
if (sink_id == Settings::AudioEngine::Auto) {
- // Auto-select a backend. Prefer CubeB, but it may report a large minimum latency which
- // causes audio issues, in that case go with SDL.
-#if defined(HAVE_CUBEB) && defined(HAVE_SDL2)
- iter = find_backend(Settings::AudioEngine::Cubeb);
- if (iter->latency() > TargetSampleCount * 3) {
- iter = find_backend(Settings::AudioEngine::Sdl2);
+ // Auto-select a backend. Use the sink details ordering, preferring cubeb first, checking
+ // that the backend is available and suitable to use.
+ for (auto& details : sink_details) {
+ if (details.is_suitable()) {
+ iter = &details;
+ break;
+ }
}
-#else
- iter = std::begin(sink_details);
-#endif
LOG_INFO(Service_Audio, "Auto-selecting the {} backend",
Settings::CanonicalizeEnum(iter->id));
+ } else {
+ if (iter != std::end(sink_details) && !iter->is_suitable()) {
+ LOG_ERROR(Service_Audio, "Selected backend {} is not suitable, falling back to null",
+ Settings::CanonicalizeEnum(iter->id));
+ iter = find_backend(Settings::AudioEngine::Null);
+ }
}
if (iter == std::end(sink_details)) {
diff --git a/src/common/settings.cpp b/src/common/settings.cpp
index 16a58a750..4ecaf550b 100644
--- a/src/common/settings.cpp
+++ b/src/common/settings.cpp
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <version>
+#include "common/settings_enums.h"
#if __cpp_lib_chrono >= 201907L
#include <chrono>
#include <exception>
@@ -145,6 +146,10 @@ bool IsFastmemEnabled() {
return true;
}
+bool IsDockedMode() {
+ return values.use_docked_mode.GetValue() == Settings::ConsoleMode::Docked;
+}
+
float Volume() {
if (values.audio_muted) {
return 0.0f;
@@ -154,6 +159,8 @@ float Volume() {
const char* TranslateCategory(Category category) {
switch (category) {
+ case Category::Android:
+ return "Android";
case Category::Audio:
return "Audio";
case Category::Core:
diff --git a/src/common/settings.h b/src/common/settings.h
index 4407c1e6d..b15213bd7 100644
--- a/src/common/settings.h
+++ b/src/common/settings.h
@@ -379,7 +379,13 @@ struct Values {
Setting<s32> current_user{linkage, 0, "current_user", Category::System};
- SwitchableSetting<bool> use_docked_mode{linkage, true, "use_docked_mode", Category::System};
+ SwitchableSetting<ConsoleMode> use_docked_mode{linkage,
+ ConsoleMode::Docked,
+ "use_docked_mode",
+ Category::System,
+ Specialization::Radio,
+ true,
+ true};
// Controls
InputSetting<std::array<PlayerInput, 10>> players;
@@ -519,6 +525,8 @@ bool IsGPULevelHigh();
bool IsFastmemEnabled();
+bool IsDockedMode();
+
float Volume();
std::string GetTimeZoneString(TimeZone time_zone);
diff --git a/src/common/settings_common.cpp b/src/common/settings_common.cpp
index 137b65d5f..5960b78aa 100644
--- a/src/common/settings_common.cpp
+++ b/src/common/settings_common.cpp
@@ -14,6 +14,7 @@ BasicSetting::BasicSetting(Linkage& linkage, const std::string& name, enum Categ
: label{name}, category{category_}, id{linkage.count}, save{save_},
runtime_modifiable{runtime_modifiable_}, specialization{specialization_},
other_setting{other_setting_} {
+ linkage.by_key.insert({name, this});
linkage.by_category[category].push_back(this);
linkage.count++;
}
diff --git a/src/common/settings_common.h b/src/common/settings_common.h
index 2efb329b0..5b170dfd5 100644
--- a/src/common/settings_common.h
+++ b/src/common/settings_common.h
@@ -12,6 +12,7 @@
namespace Settings {
enum class Category : u32 {
+ Android,
Audio,
Core,
Cpu,
@@ -56,6 +57,7 @@ enum Specialization : u8 {
Scalar = 5, // Values are continuous
Countable = 6, // Can be stepped through
Paired = 7, // Another setting is associated with this setting
+ Radio = 8, // Setting should be presented in a radio group
Percentage = (1 << SpecializationAttributeOffset), // Should be represented as a percentage
};
@@ -67,6 +69,7 @@ public:
explicit Linkage(u32 initial_count = 0);
~Linkage();
std::map<Category, std::vector<BasicSetting*>> by_category{};
+ std::map<std::string, Settings::BasicSetting*> by_key{};
std::vector<std::function<void()>> restore_functions{};
u32 count;
};
diff --git a/src/common/settings_enums.h b/src/common/settings_enums.h
index e7cb59ea5..815cafe15 100644
--- a/src/common/settings_enums.h
+++ b/src/common/settings_enums.h
@@ -146,6 +146,8 @@ ENUM(AntiAliasing, None, Fxaa, Smaa, MaxEnum);
ENUM(AspectRatio, R16_9, R4_3, R21_9, R16_10, Stretch);
+ENUM(ConsoleMode, Handheld, Docked);
+
template <typename Type>
inline std::string CanonicalizeEnum(Type id) {
const auto group = EnumMetadata<Type>::Canonicalizations();
diff --git a/src/common/swap.h b/src/common/swap.h
index 085baaf9a..fde343e45 100644
--- a/src/common/swap.h
+++ b/src/common/swap.h
@@ -460,11 +460,6 @@ S operator&(const S& i, const swap_struct_t<T, F> v) {
return i & v.swap();
}
-template <typename S, typename T, typename F>
-S operator&(const swap_struct_t<T, F> v, const S& i) {
- return static_cast<S>(v.swap() & i);
-}
-
// Comparison
template <typename S, typename T, typename F>
bool operator<(const S& p, const swap_struct_t<T, F> v) {
diff --git a/src/core/core.cpp b/src/core/core.cpp
index 2f67e60a9..e95ae80da 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -273,7 +273,8 @@ struct System::Impl {
time_manager.Initialize();
is_powered_on = true;
- exit_lock = false;
+ exit_locked = false;
+ exit_requested = false;
microprofile_cpu[0] = MICROPROFILE_TOKEN(ARM_CPU0);
microprofile_cpu[1] = MICROPROFILE_TOKEN(ARM_CPU1);
@@ -398,7 +399,8 @@ struct System::Impl {
}
is_powered_on = false;
- exit_lock = false;
+ exit_locked = false;
+ exit_requested = false;
if (gpu_core != nullptr) {
gpu_core->NotifyShutdown();
@@ -507,7 +509,8 @@ struct System::Impl {
CpuManager cpu_manager;
std::atomic_bool is_powered_on{};
- bool exit_lock = false;
+ bool exit_locked = false;
+ bool exit_requested = false;
bool nvdec_active{};
@@ -943,12 +946,20 @@ const Service::Time::TimeManager& System::GetTimeManager() const {
return impl->time_manager;
}
-void System::SetExitLock(bool locked) {
- impl->exit_lock = locked;
+void System::SetExitLocked(bool locked) {
+ impl->exit_locked = locked;
}
-bool System::GetExitLock() const {
- return impl->exit_lock;
+bool System::GetExitLocked() const {
+ return impl->exit_locked;
+}
+
+void System::SetExitRequested(bool requested) {
+ impl->exit_requested = requested;
+}
+
+bool System::GetExitRequested() const {
+ return impl->exit_requested;
}
void System::SetApplicationProcessBuildID(const CurrentBuildProcessID& id) {
diff --git a/src/core/core.h b/src/core/core.h
index c70ea1965..a9ff9315e 100644
--- a/src/core/core.h
+++ b/src/core/core.h
@@ -412,8 +412,11 @@ public:
/// Gets an immutable reference to the Room Network.
[[nodiscard]] const Network::RoomNetwork& GetRoomNetwork() const;
- void SetExitLock(bool locked);
- [[nodiscard]] bool GetExitLock() const;
+ void SetExitLocked(bool locked);
+ bool GetExitLocked() const;
+
+ void SetExitRequested(bool requested);
+ bool GetExitRequested() const;
void SetApplicationProcessBuildID(const CurrentBuildProcessID& id);
[[nodiscard]] const CurrentBuildProcessID& GetApplicationProcessBuildID() const;
diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp
index 44e6852fe..7d2f0abb8 100644
--- a/src/core/file_sys/content_archive.cpp
+++ b/src/core/file_sys/content_archive.cpp
@@ -22,6 +22,10 @@
namespace FileSys {
+static u8 MasterKeyIdForKeyGeneration(u8 key_generation) {
+ return std::max<u8>(key_generation, 1) - 1;
+}
+
NCA::NCA(VirtualFile file_, const NCA* base_nca)
: file(std::move(file_)), keys{Core::Crypto::KeyManager::Instance()} {
if (file == nullptr) {
@@ -41,12 +45,17 @@ NCA::NCA(VirtualFile file_, const NCA* base_nca)
return;
}
+ // Ensure we have the proper key area keys to continue.
+ const u8 master_key_id = MasterKeyIdForKeyGeneration(reader->GetKeyGeneration());
+ if (!keys.HasKey(Core::Crypto::S128KeyType::KeyArea, master_key_id, reader->GetKeyIndex())) {
+ status = Loader::ResultStatus::ErrorMissingKeyAreaKey;
+ return;
+ }
+
RightsId rights_id{};
reader->GetRightsId(rights_id.data(), rights_id.size());
if (rights_id != RightsId{}) {
// External decryption key required; provide it here.
- const auto key_generation = std::max<s32>(reader->GetKeyGeneration(), 1) - 1;
-
u128 rights_id_u128;
std::memcpy(rights_id_u128.data(), rights_id.data(), sizeof(rights_id));
@@ -57,12 +66,12 @@ NCA::NCA(VirtualFile file_, const NCA* base_nca)
return;
}
- if (!keys.HasKey(Core::Crypto::S128KeyType::Titlekek, key_generation)) {
+ if (!keys.HasKey(Core::Crypto::S128KeyType::Titlekek, master_key_id)) {
status = Loader::ResultStatus::ErrorMissingTitlekek;
return;
}
- auto titlekek = keys.GetKey(Core::Crypto::S128KeyType::Titlekek, key_generation);
+ auto titlekek = keys.GetKey(Core::Crypto::S128KeyType::Titlekek, master_key_id);
Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(titlekek, Core::Crypto::Mode::ECB);
cipher.Transcode(titlekey.data(), titlekey.size(), titlekey.data(),
Core::Crypto::Op::Decrypt);
diff --git a/src/core/frontend/applets/controller.cpp b/src/core/frontend/applets/controller.cpp
index 3300d4f79..27755cb58 100644
--- a/src/core/frontend/applets/controller.cpp
+++ b/src/core/frontend/applets/controller.cpp
@@ -3,6 +3,8 @@
#include "common/assert.h"
#include "common/logging/log.h"
+#include "common/settings.h"
+#include "common/settings_enums.h"
#include "core/frontend/applets/controller.h"
#include "core/hid/emulated_controller.h"
#include "core/hid/hid_core.h"
@@ -62,7 +64,7 @@ void DefaultControllerApplet::ReconfigureControllers(ReconfigureCallback callbac
controller->Connect(true);
}
} else if (index == 0 && parameters.enable_single_mode && parameters.allow_handheld &&
- !Settings::values.use_docked_mode.GetValue()) {
+ !Settings::IsDockedMode()) {
// We should *never* reach here under any normal circumstances.
controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld);
controller->Connect(true);
diff --git a/src/core/frontend/framebuffer_layout.cpp b/src/core/frontend/framebuffer_layout.cpp
index b4081fc39..2590b20da 100644
--- a/src/core/frontend/framebuffer_layout.cpp
+++ b/src/core/frontend/framebuffer_layout.cpp
@@ -5,6 +5,7 @@
#include "common/assert.h"
#include "common/settings.h"
+#include "common/settings_enums.h"
#include "core/frontend/framebuffer_layout.h"
namespace Layout {
@@ -49,7 +50,7 @@ FramebufferLayout DefaultFrameLayout(u32 width, u32 height) {
}
FramebufferLayout FrameLayoutFromResolutionScale(f32 res_scale) {
- const bool is_docked = Settings::values.use_docked_mode.GetValue();
+ const bool is_docked = Settings::IsDockedMode();
const u32 screen_width = is_docked ? ScreenDocked::Width : ScreenUndocked::Width;
const u32 screen_height = is_docked ? ScreenDocked::Height : ScreenUndocked::Height;
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index 8d057b3a8..e92f400de 100644
--- a/src/core/hle/service/am/am.cpp
+++ b/src/core/hle/service/am/am.cpp
@@ -6,6 +6,7 @@
#include <cinttypes>
#include <cstring>
#include "common/settings.h"
+#include "common/settings_enums.h"
#include "core/core.h"
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/patch_manager.h"
@@ -340,7 +341,7 @@ void ISelfController::Exit(HLERequestContext& ctx) {
void ISelfController::LockExit(HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called");
- system.SetExitLock(true);
+ system.SetExitLocked(true);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
@@ -349,10 +350,14 @@ void ISelfController::LockExit(HLERequestContext& ctx) {
void ISelfController::UnlockExit(HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called");
- system.SetExitLock(false);
+ system.SetExitLocked(false);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
+
+ if (system.GetExitRequested()) {
+ system.Exit();
+ }
}
void ISelfController::EnterFatalSection(HLERequestContext& ctx) {
@@ -833,7 +838,7 @@ void ICommonStateGetter::GetDefaultDisplayResolution(HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(ResultSuccess);
- if (Settings::values.use_docked_mode.GetValue()) {
+ if (Settings::IsDockedMode()) {
rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedWidth));
rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedHeight));
} else {
@@ -921,7 +926,7 @@ void IStorage::Open(HLERequestContext& ctx) {
}
void ICommonStateGetter::GetOperationMode(HLERequestContext& ctx) {
- const bool use_docked_mode{Settings::values.use_docked_mode.GetValue()};
+ const bool use_docked_mode{Settings::IsDockedMode()};
LOG_DEBUG(Service_AM, "called, use_docked_mode={}", use_docked_mode);
IPC::ResponseBuilder rb{ctx, 3};
diff --git a/src/core/hle/service/apm/apm_controller.cpp b/src/core/hle/service/apm/apm_controller.cpp
index 227fdd0cf..4f1aa5cc2 100644
--- a/src/core/hle/service/apm/apm_controller.cpp
+++ b/src/core/hle/service/apm/apm_controller.cpp
@@ -7,6 +7,7 @@
#include "common/logging/log.h"
#include "common/settings.h"
+#include "common/settings_enums.h"
#include "core/core_timing.h"
#include "core/hle/service/apm/apm_controller.h"
@@ -67,8 +68,7 @@ void Controller::SetFromCpuBoostMode(CpuBoostMode mode) {
}
PerformanceMode Controller::GetCurrentPerformanceMode() const {
- return Settings::values.use_docked_mode.GetValue() ? PerformanceMode::Boost
- : PerformanceMode::Normal;
+ return Settings::IsDockedMode() ? PerformanceMode::Boost : PerformanceMode::Normal;
}
PerformanceConfiguration Controller::GetCurrentPerformanceConfiguration(PerformanceMode mode) {
diff --git a/src/core/hle/service/audio/hwopus.cpp b/src/core/hle/service/audio/hwopus.cpp
index fa77007f3..1557e6088 100644
--- a/src/core/hle/service/audio/hwopus.cpp
+++ b/src/core/hle/service/audio/hwopus.cpp
@@ -174,7 +174,7 @@ public:
{6, &IHardwareOpusDecoderManager::DecodeInterleaved, "DecodeInterleavedWithPerfAndResetOld"},
{7, nullptr, "DecodeInterleavedForMultiStreamWithPerfAndResetOld"},
{8, &IHardwareOpusDecoderManager::DecodeInterleaved, "DecodeInterleaved"},
- {9, nullptr, "DecodeInterleavedForMultiStream"},
+ {9, &IHardwareOpusDecoderManager::DecodeInterleavedForMultiStream, "DecodeInterleavedForMultiStream"},
};
// clang-format on
@@ -206,6 +206,16 @@ private:
decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Enabled, extra_behavior);
}
+ void DecodeInterleavedForMultiStream(HLERequestContext& ctx) {
+ LOG_DEBUG(Audio, "called");
+
+ IPC::RequestParser rp{ctx};
+ const auto extra_behavior = rp.Pop<bool>() ? OpusDecoderState::ExtraBehavior::ResetContext
+ : OpusDecoderState::ExtraBehavior::None;
+
+ decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Enabled, extra_behavior);
+ }
+
OpusDecoderState decoder_state;
};
@@ -257,6 +267,10 @@ void HwOpus::GetWorkBufferSizeEx(HLERequestContext& ctx) {
GetWorkBufferSize(ctx);
}
+void HwOpus::GetWorkBufferSizeExEx(HLERequestContext& ctx) {
+ GetWorkBufferSizeEx(ctx);
+}
+
void HwOpus::GetWorkBufferSizeForMultiStreamEx(HLERequestContext& ctx) {
OpusMultiStreamParametersEx param;
std::memcpy(&param, ctx.ReadBuffer().data(), ctx.GetReadBufferSize());
@@ -354,6 +368,40 @@ void HwOpus::OpenHardwareOpusDecoderEx(HLERequestContext& ctx) {
system, OpusDecoderState{std::move(decoder), sample_rate, channel_count});
}
+void HwOpus::OpenHardwareOpusDecoderForMultiStreamEx(HLERequestContext& ctx) {
+ OpusMultiStreamParametersEx params;
+ std::memcpy(&params, ctx.ReadBuffer().data(), ctx.GetReadBufferSize());
+
+ const auto& sample_rate = params.sample_rate;
+ const auto& channel_count = params.channel_count;
+
+ LOG_INFO(
+ Audio,
+ "called with sample_rate={}, channel_count={}, number_streams={}, number_stereo_streams={}",
+ sample_rate, channel_count, params.number_streams, params.number_stereo_streams);
+
+ ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 ||
+ sample_rate == 12000 || sample_rate == 8000,
+ "Invalid sample rate");
+
+ int error = 0;
+ OpusDecoderPtr decoder{opus_multistream_decoder_create(
+ sample_rate, static_cast<int>(channel_count), params.number_streams,
+ params.number_stereo_streams, params.channel_mappings.data(), &error)};
+ if (error != OPUS_OK || decoder == nullptr) {
+ LOG_ERROR(Audio, "Failed to create Opus decoder (error={}).", error);
+ IPC::ResponseBuilder rb{ctx, 2};
+ // TODO(ogniK): Use correct error code
+ rb.Push(ResultUnknown);
+ return;
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(ResultSuccess);
+ rb.PushIpcInterface<IHardwareOpusDecoderManager>(
+ system, OpusDecoderState{std::move(decoder), sample_rate, channel_count});
+}
+
HwOpus::HwOpus(Core::System& system_) : ServiceFramework{system_, "hwopus"} {
static const FunctionInfo functions[] = {
{0, &HwOpus::OpenHardwareOpusDecoder, "OpenHardwareOpusDecoder"},
@@ -362,9 +410,10 @@ HwOpus::HwOpus(Core::System& system_) : ServiceFramework{system_, "hwopus"} {
{3, nullptr, "GetWorkBufferSizeForMultiStream"},
{4, &HwOpus::OpenHardwareOpusDecoderEx, "OpenHardwareOpusDecoderEx"},
{5, &HwOpus::GetWorkBufferSizeEx, "GetWorkBufferSizeEx"},
- {6, nullptr, "OpenHardwareOpusDecoderForMultiStreamEx"},
+ {6, &HwOpus::OpenHardwareOpusDecoderForMultiStreamEx,
+ "OpenHardwareOpusDecoderForMultiStreamEx"},
{7, &HwOpus::GetWorkBufferSizeForMultiStreamEx, "GetWorkBufferSizeForMultiStreamEx"},
- {8, nullptr, "GetWorkBufferSizeExEx"},
+ {8, &HwOpus::GetWorkBufferSizeExEx, "GetWorkBufferSizeExEx"},
{9, nullptr, "GetWorkBufferSizeForMultiStreamExEx"},
};
RegisterHandlers(functions);
diff --git a/src/core/hle/service/audio/hwopus.h b/src/core/hle/service/audio/hwopus.h
index ece65c02c..90867bf74 100644
--- a/src/core/hle/service/audio/hwopus.h
+++ b/src/core/hle/service/audio/hwopus.h
@@ -18,8 +18,10 @@ struct OpusMultiStreamParametersEx {
u32 number_stereo_streams;
u32 use_large_frame_size;
u32 padding;
- std::array<u32, 64> channel_mappings;
+ std::array<u8, 0x100> channel_mappings;
};
+static_assert(sizeof(OpusMultiStreamParametersEx) == 0x118,
+ "OpusMultiStreamParametersEx has incorrect size");
class HwOpus final : public ServiceFramework<HwOpus> {
public:
@@ -29,8 +31,10 @@ public:
private:
void OpenHardwareOpusDecoder(HLERequestContext& ctx);
void OpenHardwareOpusDecoderEx(HLERequestContext& ctx);
+ void OpenHardwareOpusDecoderForMultiStreamEx(HLERequestContext& ctx);
void GetWorkBufferSize(HLERequestContext& ctx);
void GetWorkBufferSizeEx(HLERequestContext& ctx);
+ void GetWorkBufferSizeExEx(HLERequestContext& ctx);
void GetWorkBufferSizeForMultiStreamEx(HLERequestContext& ctx);
};
diff --git a/src/core/hle/service/hid/controllers/gesture.cpp b/src/core/hle/service/hid/controllers/gesture.cpp
index 03432f7cb..63eecd42b 100644
--- a/src/core/hle/service/hid/controllers/gesture.cpp
+++ b/src/core/hle/service/hid/controllers/gesture.cpp
@@ -331,7 +331,7 @@ Controller_Gesture::GestureProperties Controller_Gesture::GetGestureProperties()
};
// Hack: There is no touch in docked but games still allow it
- if (Settings::values.use_docked_mode.GetValue()) {
+ if (Settings::IsDockedMode()) {
gesture.points[id] = {
.x = static_cast<s32>(active_x * Layout::ScreenDocked::Width),
.y = static_cast<s32>(active_y * Layout::ScreenDocked::Height),
diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp
index 28818c813..3b349b4c4 100644
--- a/src/core/hle/service/hid/controllers/npad.cpp
+++ b/src/core/hle/service/hid/controllers/npad.cpp
@@ -1518,7 +1518,7 @@ bool Controller_NPad::IsControllerSupported(Core::HID::NpadStyleIndex controller
return false;
}
// Handheld shouldn't be supported in docked mode
- if (Settings::values.use_docked_mode.GetValue()) {
+ if (Settings::IsDockedMode()) {
return false;
}
diff --git a/src/core/hle/service/sockets/nsd.cpp b/src/core/hle/service/sockets/nsd.cpp
index bac21752a..491b76d48 100644
--- a/src/core/hle/service/sockets/nsd.cpp
+++ b/src/core/hle/service/sockets/nsd.cpp
@@ -19,6 +19,12 @@ enum class ServerEnvironmentType : u8 {
Dp,
};
+// This is nn::nsd::EnvironmentIdentifier
+struct EnvironmentIdentifier {
+ std::array<u8, 8> identifier;
+};
+static_assert(sizeof(EnvironmentIdentifier) == 0x8);
+
NSD::NSD(Core::System& system_, const char* name) : ServiceFramework{system_, name} {
// clang-format off
static const FunctionInfo functions[] = {
@@ -101,8 +107,9 @@ void NSD::ResolveEx(HLERequestContext& ctx) {
}
void NSD::GetEnvironmentIdentifier(HLERequestContext& ctx) {
- const std::string environment_identifier = "lp1";
- ctx.WriteBuffer(environment_identifier);
+ constexpr EnvironmentIdentifier lp1 = {
+ .identifier = {'l', 'p', '1', '\0', '\0', '\0', '\0', '\0'}};
+ ctx.WriteBuffer(lp1);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
diff --git a/src/core/hle/service/sockets/sfdnsres.cpp b/src/core/hle/service/sockets/sfdnsres.cpp
index 22e4a6f49..c657c4efd 100644
--- a/src/core/hle/service/sockets/sfdnsres.cpp
+++ b/src/core/hle/service/sockets/sfdnsres.cpp
@@ -150,6 +150,12 @@ static std::pair<u32, GetAddrInfoError> GetHostByNameRequestImpl(HLERequestConte
const std::string host = Common::StringFromBuffer(host_buffer);
// For now, ignore options, which are in input buffer 1 for GetHostByNameRequestWithOptions.
+ // Prevent resolution of Nintendo servers
+ if (host.find("srv.nintendo.net") != std::string::npos) {
+ LOG_WARNING(Network, "Resolution of hostname {} requested, returning EAI_AGAIN", host);
+ return {0, GetAddrInfoError::AGAIN};
+ }
+
auto res = Network::GetAddressInfo(host, /*service*/ std::nullopt);
if (!res.has_value()) {
return {0, Translate(res.error())};
@@ -261,6 +267,12 @@ static std::pair<u32, GetAddrInfoError> GetAddrInfoRequestImpl(HLERequestContext
const auto host_buffer = ctx.ReadBuffer(0);
const std::string host = Common::StringFromBuffer(host_buffer);
+ // Prevent resolution of Nintendo servers
+ if (host.find("srv.nintendo.net") != std::string::npos) {
+ LOG_WARNING(Network, "Resolution of hostname {} requested, returning EAI_AGAIN", host);
+ return {0, GetAddrInfoError::AGAIN};
+ }
+
std::optional<std::string> service = std::nullopt;
if (ctx.CanReadBuffer(1)) {
const std::span<const u8> service_buffer = ctx.ReadBuffer(1);
diff --git a/src/core/hle/service/vi/vi.cpp b/src/core/hle/service/vi/vi.cpp
index 6bb02393c..2eb978379 100644
--- a/src/core/hle/service/vi/vi.cpp
+++ b/src/core/hle/service/vi/vi.cpp
@@ -217,7 +217,7 @@ private:
IPC::ResponseBuilder rb{ctx, 6};
rb.Push(ResultSuccess);
- if (Settings::values.use_docked_mode.GetValue()) {
+ if (Settings::IsDockedMode()) {
rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedWidth));
rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedHeight));
} else {
diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp
index 62b3f6636..c26179e03 100644
--- a/src/core/telemetry_session.cpp
+++ b/src/core/telemetry_session.cpp
@@ -14,6 +14,7 @@
#include "common/logging/log.h"
#include "common/settings.h"
+#include "common/settings_enums.h"
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/patch_manager.h"
#include "core/loader/loader.h"
@@ -275,7 +276,7 @@ void TelemetrySession::AddInitialInfo(Loader::AppLoader& app_loader,
static_cast<u32>(Settings::values.shader_backend.GetValue()));
AddField(field_type, "Renderer_UseAsynchronousShaders",
Settings::values.use_asynchronous_shaders.GetValue());
- AddField(field_type, "System_UseDockedMode", Settings::values.use_docked_mode.GetValue());
+ AddField(field_type, "System_UseDockedMode", Settings::IsDockedMode());
}
bool TelemetrySession::SubmitTestcase() {
diff --git a/src/input_common/input_poller.cpp b/src/input_common/input_poller.cpp
index 870e76ab0..188e862d7 100644
--- a/src/input_common/input_poller.cpp
+++ b/src/input_common/input_poller.cpp
@@ -835,15 +835,15 @@ public:
return input_engine->SupportsNfc(identifier);
}
- Common::Input::NfcState StartNfcPolling() {
+ Common::Input::NfcState StartNfcPolling() override {
return input_engine->StartNfcPolling(identifier);
}
- Common::Input::NfcState StopNfcPolling() {
+ Common::Input::NfcState StopNfcPolling() override {
return input_engine->StopNfcPolling(identifier);
}
- Common::Input::NfcState ReadAmiiboData(std::vector<u8>& out_data) {
+ Common::Input::NfcState ReadAmiiboData(std::vector<u8>& out_data) override {
return input_engine->ReadAmiiboData(identifier, out_data);
}
@@ -852,11 +852,11 @@ public:
}
Common::Input::NfcState ReadMifareData(const Common::Input::MifareRequest& request,
- Common::Input::MifareRequest& out_data) {
+ Common::Input::MifareRequest& out_data) override {
return input_engine->ReadMifareData(identifier, request, out_data);
}
- Common::Input::NfcState WriteMifareData(const Common::Input::MifareRequest& request) {
+ Common::Input::NfcState WriteMifareData(const Common::Input::MifareRequest& request) override {
return input_engine->WriteMifareData(identifier, request);
}
diff --git a/src/shader_recompiler/backend/glsl/emit_glsl_image.cpp b/src/shader_recompiler/backend/glsl/emit_glsl_image.cpp
index 3ad668a47..d9872ecc2 100644
--- a/src/shader_recompiler/backend/glsl/emit_glsl_image.cpp
+++ b/src/shader_recompiler/backend/glsl/emit_glsl_image.cpp
@@ -558,7 +558,7 @@ void EmitImageGradient(EmitContext& ctx, IR::Inst& inst, const IR::Value& index,
if (multi_component) {
if (info.num_derivates >= 3) {
const auto offset_vec{ctx.var_alloc.Consume(offset)};
- ctx.Add("{}=textureGrad({},{},vec3({}.xz, {}.x),vec3({}.yz, {}.y));", texel, texture,
+ ctx.Add("{}=textureGrad({},{},vec3({}.xz, {}.x),vec3({}.yw, {}.y));", texel, texture,
coords, derivatives_vec, offset_vec, derivatives_vec, offset_vec);
return;
}
diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp
index 7d901c04b..34240b36f 100644
--- a/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp
+++ b/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp
@@ -91,6 +91,34 @@ public:
}
}
+ explicit ImageOperands(EmitContext& ctx, bool has_lod_clamp, Id derivates_1, Id derivates_2,
+ Id offset, Id lod_clamp) {
+ if (!Sirit::ValidId(derivates_1) || !Sirit::ValidId(derivates_2)) {
+ throw LogicError("Derivates must be present");
+ }
+ boost::container::static_vector<Id, 3> deriv_1_accum{
+ ctx.OpCompositeExtract(ctx.F32[1], derivates_1, 0),
+ ctx.OpCompositeExtract(ctx.F32[1], derivates_1, 2),
+ ctx.OpCompositeExtract(ctx.F32[1], derivates_2, 0),
+ };
+ boost::container::static_vector<Id, 3> deriv_2_accum{
+ ctx.OpCompositeExtract(ctx.F32[1], derivates_1, 1),
+ ctx.OpCompositeExtract(ctx.F32[1], derivates_1, 3),
+ ctx.OpCompositeExtract(ctx.F32[1], derivates_2, 1),
+ };
+ const Id derivates_id1{ctx.OpCompositeConstruct(
+ ctx.F32[3], std::span{deriv_1_accum.data(), deriv_1_accum.size()})};
+ const Id derivates_id2{ctx.OpCompositeConstruct(
+ ctx.F32[3], std::span{deriv_2_accum.data(), deriv_2_accum.size()})};
+ Add(spv::ImageOperandsMask::Grad, derivates_id1, derivates_id2);
+ if (Sirit::ValidId(offset)) {
+ Add(spv::ImageOperandsMask::Offset, offset);
+ }
+ if (has_lod_clamp) {
+ Add(spv::ImageOperandsMask::MinLod, lod_clamp);
+ }
+ }
+
std::span<const Id> Span() const noexcept {
return std::span{operands.data(), operands.size()};
}
@@ -524,8 +552,11 @@ Id EmitImageQueryLod(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, I
Id EmitImageGradient(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords,
Id derivates, Id offset, Id lod_clamp) {
const auto info{inst->Flags<IR::TextureInstInfo>()};
- const ImageOperands operands(ctx, info.has_lod_clamp != 0, derivates, info.num_derivates,
- offset, lod_clamp);
+ const auto operands =
+ info.num_derivates == 3
+ ? ImageOperands(ctx, info.has_lod_clamp != 0, derivates, offset, {}, lod_clamp)
+ : ImageOperands(ctx, info.has_lod_clamp != 0, derivates, info.num_derivates, offset,
+ lod_clamp);
return Emit(&EmitContext::OpImageSparseSampleExplicitLod,
&EmitContext::OpImageSampleExplicitLod, ctx, inst, ctx.F32[4],
Texture(ctx, info, index), coords, operands.Mask(), operands.Span());
diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h
index f0f450edb..8be7bd594 100644
--- a/src/video_core/buffer_cache/buffer_cache.h
+++ b/src/video_core/buffer_cache/buffer_cache.h
@@ -289,8 +289,11 @@ std::pair<typename P::Buffer*, u32> BufferCache<P>::ObtainBuffer(GPUVAddr gpu_ad
MarkWrittenBuffer(buffer_id, *cpu_addr, size);
break;
case ObtainBufferOperation::DiscardWrite: {
- IntervalType interval{*cpu_addr, size};
+ VAddr cpu_addr_start = Common::AlignDown(*cpu_addr, 64);
+ VAddr cpu_addr_end = Common::AlignUp(*cpu_addr + size, 64);
+ IntervalType interval{cpu_addr_start, cpu_addr_end};
ClearDownload(interval);
+ common_ranges.subtract(interval);
break;
}
default:
@@ -1159,6 +1162,11 @@ void BufferCache<P>::UpdateDrawIndirect() {
.size = static_cast<u32>(size),
.buffer_id = FindBuffer(*cpu_addr, static_cast<u32>(size)),
};
+ VAddr cpu_addr_start = Common::AlignDown(*cpu_addr, 64);
+ VAddr cpu_addr_end = Common::AlignUp(*cpu_addr + size, 64);
+ IntervalType interval{cpu_addr_start, cpu_addr_end};
+ ClearDownload(interval);
+ common_ranges.subtract(interval);
};
if (current_draw_indirect->include_count) {
update(current_draw_indirect->count_start_address, sizeof(u32),
diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp
index c3696096d..06e349e43 100644
--- a/src/video_core/engines/maxwell_3d.cpp
+++ b/src/video_core/engines/maxwell_3d.cpp
@@ -257,6 +257,7 @@ u32 Maxwell3D::GetMaxCurrentVertices() {
const u32 address_size = static_cast<u32>(gpu_addr_end - gpu_addr_begin);
num_vertices = std::max(
num_vertices, address_size / std::max(attribute.SizeInBytes(), array.stride.Value()));
+ break;
}
return num_vertices;
}
@@ -269,10 +270,13 @@ size_t Maxwell3D::EstimateIndexBufferSize() {
std::numeric_limits<u32>::max()};
const size_t byte_size = regs.index_buffer.FormatSizeInBytes();
const size_t log2_byte_size = Common::Log2Ceil64(byte_size);
+ const size_t cap{GetMaxCurrentVertices() * 3 * byte_size};
+ const size_t lower_cap =
+ std::min<size_t>(static_cast<size_t>(end_address - start_address), cap);
return std::min<size_t>(
memory_manager.GetMemoryLayoutSize(start_address, byte_size * max_sizes[log2_byte_size]) /
byte_size,
- static_cast<size_t>(end_address - start_address));
+ lower_cap);
}
u32 Maxwell3D::ProcessShadowRam(u32 method, u32 argument) {
diff --git a/src/video_core/host1x/codecs/codec.cpp b/src/video_core/host1x/codecs/codec.cpp
index 220cce28a..8d7da50fc 100644
--- a/src/video_core/host1x/codecs/codec.cpp
+++ b/src/video_core/host1x/codecs/codec.cpp
@@ -319,6 +319,7 @@ void Codec::Decode() {
LOG_WARNING(Service_NVDRV, "Zero width or height in frame");
return;
}
+ bool is_interlaced = initial_frame->interlaced_frame != 0;
if (av_codec_ctx->hw_device_ctx) {
final_frame = AVFramePtr{av_frame_alloc(), AVFrameDeleter};
ASSERT_MSG(final_frame, "av_frame_alloc final_frame failed");
@@ -334,7 +335,7 @@ void Codec::Decode() {
UNIMPLEMENTED_MSG("Unexpected video format: {}", final_frame->format);
return;
}
- if (!final_frame->interlaced_frame) {
+ if (!is_interlaced) {
av_frames.push(std::move(final_frame));
} else {
if (!filters_initialized) {
diff --git a/src/video_core/macro/macro.cpp b/src/video_core/macro/macro.cpp
index 905505ca1..5d0bb9cc4 100644
--- a/src/video_core/macro/macro.cpp
+++ b/src/video_core/macro/macro.cpp
@@ -27,14 +27,24 @@ MICROPROFILE_DEFINE(MacroHLE, "GPU", "Execute macro HLE", MP_RGB(128, 192, 192))
namespace Tegra {
-static void Dump(u64 hash, std::span<const u32> code) {
+static void Dump(u64 hash, std::span<const u32> code, bool decompiled = false) {
const auto base_dir{Common::FS::GetYuzuPath(Common::FS::YuzuPath::DumpDir)};
const auto macro_dir{base_dir / "macros"};
if (!Common::FS::CreateDir(base_dir) || !Common::FS::CreateDir(macro_dir)) {
LOG_ERROR(Common_Filesystem, "Failed to create macro dump directories");
return;
}
- const auto name{macro_dir / fmt::format("{:016x}.macro", hash)};
+ auto name{macro_dir / fmt::format("{:016x}.macro", hash)};
+
+ if (decompiled) {
+ auto new_name{macro_dir / fmt::format("decompiled_{:016x}.macro", hash)};
+ if (Common::FS::Exists(name)) {
+ (void)Common::FS::RenameFile(name, new_name);
+ return;
+ }
+ name = new_name;
+ }
+
std::fstream macro_file(name, std::ios::out | std::ios::binary);
if (!macro_file) {
LOG_ERROR(Common_Filesystem, "Unable to open or create file at {}",
@@ -90,9 +100,6 @@ void MacroEngine::Execute(u32 method, const std::vector<u32>& parameters) {
if (!mid_method.has_value()) {
cache_info.lle_program = Compile(macro_code->second);
cache_info.hash = Common::HashValue(macro_code->second);
- if (Settings::values.dump_macros) {
- Dump(cache_info.hash, macro_code->second);
- }
} else {
const auto& macro_cached = uploaded_macro_code[mid_method.value()];
const auto rebased_method = method - mid_method.value();
@@ -102,9 +109,6 @@ void MacroEngine::Execute(u32 method, const std::vector<u32>& parameters) {
code.size() * sizeof(u32));
cache_info.hash = Common::HashValue(code);
cache_info.lle_program = Compile(code);
- if (Settings::values.dump_macros) {
- Dump(cache_info.hash, code);
- }
}
auto hle_program = hle_macros->GetHLEProgram(cache_info.hash);
@@ -117,6 +121,10 @@ void MacroEngine::Execute(u32 method, const std::vector<u32>& parameters) {
MICROPROFILE_SCOPE(MacroHLE);
cache_info.hle_program->Execute(parameters, method);
}
+
+ if (Settings::values.dump_macros) {
+ Dump(cache_info.hash, macro_code->second, cache_info.has_hle_program);
+ }
}
}
diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
index b1730a170..4f83a88e1 100644
--- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
@@ -611,9 +611,6 @@ std::unique_ptr<GraphicsPipeline> PipelineCache::CreateGraphicsPipeline(
const u32 cfg_offset{static_cast<u32>(env.StartAddress() + sizeof(Shader::ProgramHeader))};
Shader::Maxwell::Flow::CFG cfg(env, pools.flow_block, cfg_offset, index == 0);
- if (Settings::values.dump_shaders) {
- env.Dump(hash, key.unique_hashes[index]);
- }
if (!uses_vertex_a || index != 1) {
// Normal path
programs[index] = TranslateProgram(pools.inst, pools.block, env, cfg, host_info);
@@ -624,6 +621,10 @@ std::unique_ptr<GraphicsPipeline> PipelineCache::CreateGraphicsPipeline(
programs[index] = MergeDualVertexPrograms(program_va, program_vb, env);
}
+ if (Settings::values.dump_shaders) {
+ env.Dump(hash, key.unique_hashes[index]);
+ }
+
if (programs[index].info.requires_layer_emulation) {
layer_source_program = &programs[index];
}
diff --git a/src/web_service/verify_user_jwt.cpp b/src/web_service/verify_user_jwt.cpp
index 129eb1968..f88f67620 100644
--- a/src/web_service/verify_user_jwt.cpp
+++ b/src/web_service/verify_user_jwt.cpp
@@ -4,6 +4,7 @@
#if defined(__GNUC__) || defined(__clang__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wimplicit-fallthrough"
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations" // for deprecated OpenSSL functions
#endif
#include <jwt/jwt.hpp>
#if defined(__GNUC__) || defined(__clang__)
diff --git a/src/yuzu/applets/qt_controller.cpp b/src/yuzu/applets/qt_controller.cpp
index 00aafb8f8..d15559518 100644
--- a/src/yuzu/applets/qt_controller.cpp
+++ b/src/yuzu/applets/qt_controller.cpp
@@ -5,6 +5,8 @@
#include <thread>
#include "common/assert.h"
+#include "common/settings.h"
+#include "common/settings_enums.h"
#include "common/string_util.h"
#include "core/core.h"
#include "core/hid/emulated_controller.h"
@@ -226,9 +228,11 @@ int QtControllerSelectorDialog::exec() {
}
void QtControllerSelectorDialog::ApplyConfiguration() {
- const bool pre_docked_mode = Settings::values.use_docked_mode.GetValue();
- Settings::values.use_docked_mode.SetValue(ui->radioDocked->isChecked());
- OnDockedModeChanged(pre_docked_mode, Settings::values.use_docked_mode.GetValue(), system);
+ const bool pre_docked_mode = Settings::IsDockedMode();
+ const bool docked_mode_selected = ui->radioDocked->isChecked();
+ Settings::values.use_docked_mode.SetValue(
+ docked_mode_selected ? Settings::ConsoleMode::Docked : Settings::ConsoleMode::Handheld);
+ OnDockedModeChanged(pre_docked_mode, docked_mode_selected, system);
Settings::values.vibration_enabled.SetValue(ui->vibrationGroup->isChecked());
Settings::values.motion_enabled.SetValue(ui->motionGroup->isChecked());
@@ -616,8 +620,8 @@ void QtControllerSelectorDialog::UpdateDockedState(bool is_handheld) {
ui->radioDocked->setEnabled(!is_handheld);
ui->radioUndocked->setEnabled(!is_handheld);
- ui->radioDocked->setChecked(Settings::values.use_docked_mode.GetValue());
- ui->radioUndocked->setChecked(!Settings::values.use_docked_mode.GetValue());
+ ui->radioDocked->setChecked(Settings::IsDockedMode());
+ ui->radioUndocked->setChecked(!Settings::IsDockedMode());
// Also force into undocked mode if the controller type is handheld.
if (is_handheld) {
diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp
index 407988b8f..2afa72140 100644
--- a/src/yuzu/bootmanager.cpp
+++ b/src/yuzu/bootmanager.cpp
@@ -928,8 +928,8 @@ void GRenderWindow::CaptureScreenshot(const QString& screenshot_path) {
const Layout::FramebufferLayout layout{[]() {
u32 height = UISettings::values.screenshot_height.GetValue();
if (height == 0) {
- height = Settings::values.use_docked_mode.GetValue() ? Layout::ScreenDocked::Height
- : Layout::ScreenUndocked::Height;
+ height = Settings::IsDockedMode() ? Layout::ScreenDocked::Height
+ : Layout::ScreenUndocked::Height;
height *= Settings::values.resolution_info.up_factor;
}
const u32 width =
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index b22c83303..1de093447 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -9,6 +9,7 @@
#include "common/fs/path_util.h"
#include "common/settings.h"
#include "common/settings_common.h"
+#include "common/settings_enums.h"
#include "core/core.h"
#include "core/hle/service/acc/profile_manager.h"
#include "core/hle/service/hid/controllers/npad.h"
@@ -85,9 +86,9 @@ const std::map<Settings::ScalingFilter, QString> Config::scaling_filter_texts_ma
{Settings::ScalingFilter::Fsr, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "FSR"))},
};
-const std::map<bool, QString> Config::use_docked_mode_texts_map = {
- {true, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Docked"))},
- {false, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Handheld"))},
+const std::map<Settings::ConsoleMode, QString> Config::use_docked_mode_texts_map = {
+ {Settings::ConsoleMode::Docked, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Docked"))},
+ {Settings::ConsoleMode::Handheld, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Handheld"))},
};
const std::map<Settings::GpuAccuracy, QString> Config::gpu_accuracy_texts_map = {
@@ -376,7 +377,7 @@ void Config::ReadControlValues() {
const auto controller_type = Settings::values.players.GetValue()[0].controller_type;
if (controller_type == Settings::ControllerType::Handheld) {
Settings::values.use_docked_mode.SetGlobal(!IsCustomConfig());
- Settings::values.use_docked_mode.SetValue(false);
+ Settings::values.use_docked_mode.SetValue(Settings::ConsoleMode::Handheld);
}
if (IsCustomConfig()) {
diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h
index 0ac74c8e7..727feebfb 100644
--- a/src/yuzu/configuration/config.h
+++ b/src/yuzu/configuration/config.h
@@ -9,6 +9,7 @@
#include <QMetaType>
#include <QVariant>
#include "common/settings.h"
+#include "common/settings_enums.h"
#include "yuzu/uisettings.h"
class QSettings;
@@ -51,7 +52,7 @@ public:
static const std::map<Settings::AntiAliasing, QString> anti_aliasing_texts_map;
static const std::map<Settings::ScalingFilter, QString> scaling_filter_texts_map;
- static const std::map<bool, QString> use_docked_mode_texts_map;
+ static const std::map<Settings::ConsoleMode, QString> use_docked_mode_texts_map;
static const std::map<Settings::GpuAccuracy, QString> gpu_accuracy_texts_map;
static const std::map<Settings::RendererBackend, QString> renderer_backend_texts_map;
static const std::map<Settings::ShaderBackend, QString> shader_backend_texts_map;
diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp
index 7fce85bca..e8f9ebfd8 100644
--- a/src/yuzu/configuration/configure_input.cpp
+++ b/src/yuzu/configuration/configure_input.cpp
@@ -4,6 +4,8 @@
#include <memory>
#include <thread>
+#include "common/settings.h"
+#include "common/settings_enums.h"
#include "core/core.h"
#include "core/hid/emulated_controller.h"
#include "core/hid/hid_core.h"
@@ -197,9 +199,11 @@ void ConfigureInput::ApplyConfiguration() {
advanced->ApplyConfiguration();
- const bool pre_docked_mode = Settings::values.use_docked_mode.GetValue();
- Settings::values.use_docked_mode.SetValue(ui->radioDocked->isChecked());
- OnDockedModeChanged(pre_docked_mode, Settings::values.use_docked_mode.GetValue(), system);
+ const bool pre_docked_mode = Settings::IsDockedMode();
+ const bool docked_mode_selected = ui->radioDocked->isChecked();
+ Settings::values.use_docked_mode.SetValue(
+ docked_mode_selected ? Settings::ConsoleMode::Docked : Settings::ConsoleMode::Handheld);
+ OnDockedModeChanged(pre_docked_mode, docked_mode_selected, system);
Settings::values.vibration_enabled.SetValue(ui->vibrationGroup->isChecked());
Settings::values.motion_enabled.SetValue(ui->motionGroup->isChecked());
@@ -267,8 +271,8 @@ void ConfigureInput::UpdateDockedState(bool is_handheld) {
ui->radioDocked->setEnabled(!is_handheld);
ui->radioUndocked->setEnabled(!is_handheld);
- ui->radioDocked->setChecked(Settings::values.use_docked_mode.GetValue());
- ui->radioUndocked->setChecked(!Settings::values.use_docked_mode.GetValue());
+ ui->radioDocked->setChecked(Settings::IsDockedMode());
+ ui->radioUndocked->setChecked(!Settings::IsDockedMode());
// Also force into undocked mode if the controller type is handheld.
if (is_handheld) {
diff --git a/src/yuzu/configuration/configure_per_game.cpp b/src/yuzu/configuration/configure_per_game.cpp
index 4f9e8db08..b91d6ad4a 100644
--- a/src/yuzu/configuration/configure_per_game.cpp
+++ b/src/yuzu/configuration/configure_per_game.cpp
@@ -18,6 +18,7 @@
#include "common/fs/fs_util.h"
#include "common/settings_enums.h"
+#include "common/settings_input.h"
#include "configuration/shared_widget.h"
#include "core/core.h"
#include "core/file_sys/control_metadata.h"
@@ -98,6 +99,12 @@ void ConfigurePerGame::ApplyConfiguration() {
addons_tab->ApplyConfiguration();
input_tab->ApplyConfiguration();
+ if (Settings::IsDockedMode() && Settings::values.players.GetValue()[0].controller_type ==
+ Settings::ControllerType::Handheld) {
+ Settings::values.use_docked_mode.SetValue(Settings::ConsoleMode::Handheld);
+ Settings::values.use_docked_mode.SetGlobal(true);
+ }
+
system.ApplySettings();
Settings::LogSettings();
diff --git a/src/yuzu/configuration/configure_system.cpp b/src/yuzu/configuration/configure_system.cpp
index c4833f4e7..0c8e5c8b4 100644
--- a/src/yuzu/configuration/configure_system.cpp
+++ b/src/yuzu/configuration/configure_system.cpp
@@ -106,6 +106,11 @@ void ConfigureSystem::Setup(const ConfigurationShared::Builder& builder) {
push(Settings::values.linkage.by_category[Settings::Category::System]);
for (auto setting : settings) {
+ if (setting->Id() == Settings::values.use_docked_mode.Id() &&
+ Settings::IsConfiguringGlobal()) {
+ continue;
+ }
+
ConfigurationShared::Widget* widget = builder.BuildWidget(setting, apply_funcs);
if (widget == nullptr) {
diff --git a/src/yuzu/configuration/shared_translation.cpp b/src/yuzu/configuration/shared_translation.cpp
index 335810788..276bdbaba 100644
--- a/src/yuzu/configuration/shared_translation.cpp
+++ b/src/yuzu/configuration/shared_translation.cpp
@@ -135,7 +135,7 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QWidget* parent) {
INSERT(Settings, region_index, "Region:", "");
INSERT(Settings, time_zone_index, "Time Zone:", "");
INSERT(Settings, sound_index, "Sound Output Mode:", "");
- INSERT(Settings, use_docked_mode, "", "");
+ INSERT(Settings, use_docked_mode, "Console Mode:", "");
INSERT(Settings, current_user, "", "");
// Controls
@@ -379,6 +379,9 @@ std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QWidget* parent) {
PAIR(MemoryLayout, Memory_6Gb, "6GB DRAM (Unsafe)"),
PAIR(MemoryLayout, Memory_8Gb, "8GB DRAM (Unsafe)"),
}});
+ translations->insert(
+ {Settings::EnumMetadata<Settings::ConsoleMode>::Index(),
+ {PAIR(ConsoleMode, Docked, "Docked"), PAIR(ConsoleMode, Handheld, "Handheld")}});
#undef PAIR
#undef CTX_PAIR
diff --git a/src/yuzu/configuration/shared_widget.cpp b/src/yuzu/configuration/shared_widget.cpp
index 4e45bc844..7721e58f9 100644
--- a/src/yuzu/configuration/shared_widget.cpp
+++ b/src/yuzu/configuration/shared_widget.cpp
@@ -23,6 +23,7 @@
#include <QLineEdit>
#include <QObject>
#include <QPushButton>
+#include <QRadioButton>
#include <QRegularExpression>
#include <QSizePolicy>
#include <QSlider>
@@ -171,6 +172,65 @@ QWidget* Widget::CreateCombobox(std::function<std::string()>& serializer,
return combobox;
}
+QWidget* Widget::CreateRadioGroup(std::function<std::string()>& serializer,
+ std::function<void()>& restore_func,
+ const std::function<void()>& touch) {
+ const auto type = setting.EnumIndex();
+
+ QWidget* group = new QWidget(this);
+ QHBoxLayout* layout = new QHBoxLayout(group);
+ layout->setContentsMargins(0, 0, 0, 0);
+ group->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
+
+ const ComboboxTranslations* enumeration{nullptr};
+ if (combobox_enumerations.contains(type)) {
+ enumeration = &combobox_enumerations.at(type);
+ for (const auto& [id, name] : *enumeration) {
+ QRadioButton* radio_button = new QRadioButton(name, group);
+ layout->addWidget(radio_button);
+ radio_buttons.push_back({id, radio_button});
+ }
+ } else {
+ return group;
+ }
+
+ const auto get_selected = [=]() -> u32 {
+ for (const auto& [id, button] : radio_buttons) {
+ if (button->isChecked()) {
+ return id;
+ }
+ }
+ return -1;
+ };
+
+ const auto set_index = [=](u32 value) {
+ for (const auto& [id, button] : radio_buttons) {
+ button->setChecked(id == value);
+ }
+ };
+
+ const u32 setting_value = std::stoi(setting.ToString());
+ set_index(setting_value);
+
+ serializer = [get_selected]() {
+ int current = get_selected();
+ return std::to_string(current);
+ };
+
+ restore_func = [this, set_index]() {
+ const u32 global_value = std::stoi(RelevantDefault(setting));
+ set_index(global_value);
+ };
+
+ if (!Settings::IsConfiguringGlobal()) {
+ for (const auto& [id, button] : radio_buttons) {
+ QObject::connect(button, &QAbstractButton::clicked, [touch]() { touch(); });
+ }
+ }
+
+ return group;
+}
+
QWidget* Widget::CreateLineEdit(std::function<std::string()>& serializer,
std::function<void()>& restore_func,
const std::function<void()>& touch, bool managed) {
@@ -411,6 +471,8 @@ void Widget::SetupComponent(const QString& label, std::function<void()>& load_fu
return RequestType::Slider;
case Settings::Specialization::Countable:
return RequestType::SpinBox;
+ case Settings::Specialization::Radio:
+ return RequestType::RadioGroup;
default:
break;
}
@@ -439,7 +501,11 @@ void Widget::SetupComponent(const QString& label, std::function<void()>& load_fu
if (setting.TypeId() == typeid(bool)) {
data_component = CreateCheckBox(&setting, label, serializer, restore_func, touch);
} else if (setting.IsEnum()) {
- data_component = CreateCombobox(serializer, restore_func, touch);
+ if (request == RequestType::RadioGroup) {
+ data_component = CreateRadioGroup(serializer, restore_func, touch);
+ } else {
+ data_component = CreateCombobox(serializer, restore_func, touch);
+ }
} else if (type == typeid(u32) || type == typeid(int) || type == typeid(u16) ||
type == typeid(s64) || type == typeid(u8)) {
switch (request) {
diff --git a/src/yuzu/configuration/shared_widget.h b/src/yuzu/configuration/shared_widget.h
index e64693bab..5303dd898 100644
--- a/src/yuzu/configuration/shared_widget.h
+++ b/src/yuzu/configuration/shared_widget.h
@@ -22,6 +22,7 @@ class QObject;
class QPushButton;
class QSlider;
class QSpinBox;
+class QRadioButton;
namespace Settings {
class BasicSetting;
@@ -38,6 +39,7 @@ enum class RequestType {
LineEdit,
HexEdit,
DateTimeEdit,
+ RadioGroup,
MaxEnum,
};
@@ -91,6 +93,7 @@ public:
QSlider* slider{};
QComboBox* combobox{};
QDateTimeEdit* date_time_edit{};
+ std::vector<std::pair<u32, QRadioButton*>> radio_buttons{};
private:
void SetupComponent(const QString& label, std::function<void()>& load_func, bool managed,
@@ -106,6 +109,9 @@ private:
QWidget* CreateCombobox(std::function<std::string()>& serializer,
std::function<void()>& restore_func,
const std::function<void()>& touch);
+ QWidget* CreateRadioGroup(std::function<std::string()>& serializer,
+ std::function<void()>& restore_func,
+ const std::function<void()>& touch);
QWidget* CreateLineEdit(std::function<std::string()>& serializer,
std::function<void()>& restore_func, const std::function<void()>& touch,
bool managed = true);
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 8e933af64..9ee7a5ee4 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -1158,9 +1158,9 @@ void GMainWindow::InitializeWidgets() {
[this](const QPoint& menu_location) {
QMenu context_menu;
- for (auto const& docked_mode_pair : Config::use_docked_mode_texts_map) {
- context_menu.addAction(docked_mode_pair.second, [this, docked_mode_pair] {
- if (docked_mode_pair.first != Settings::values.use_docked_mode.GetValue()) {
+ for (auto const& pair : Config::use_docked_mode_texts_map) {
+ context_menu.addAction(pair.second, [this, &pair] {
+ if (pair.first != Settings::values.use_docked_mode.GetValue()) {
OnToggleDockedMode();
}
});
@@ -2010,8 +2010,16 @@ bool GMainWindow::OnShutdownBegin() {
emit EmulationStopping();
+ int shutdown_time = 1000;
+
+ if (system->DebuggerEnabled()) {
+ shutdown_time = 0;
+ } else if (system->GetExitLocked()) {
+ shutdown_time = 5000;
+ }
+
shutdown_timer.setSingleShot(true);
- shutdown_timer.start(system->DebuggerEnabled() ? 0 : 5000);
+ shutdown_timer.start(shutdown_time);
connect(&shutdown_timer, &QTimer::timeout, this, &GMainWindow::OnEmulationStopTimeExpired);
connect(emu_thread.get(), &QThread::finished, this, &GMainWindow::OnEmulationStopped);
@@ -3261,7 +3269,7 @@ void GMainWindow::OnPauseContinueGame() {
}
void GMainWindow::OnStopGame() {
- if (system->GetExitLock() && !ConfirmForceLockedExit()) {
+ if (system->GetExitLocked() && !ConfirmForceLockedExit()) {
return;
}
@@ -3674,7 +3682,7 @@ void GMainWindow::OnTasReset() {
}
void GMainWindow::OnToggleDockedMode() {
- const bool is_docked = Settings::values.use_docked_mode.GetValue();
+ const bool is_docked = Settings::IsDockedMode();
auto* player_1 = system->HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1);
auto* handheld = system->HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld);
@@ -3688,7 +3696,8 @@ void GMainWindow::OnToggleDockedMode() {
controller_dialog->refreshConfiguration();
}
- Settings::values.use_docked_mode.SetValue(!is_docked);
+ Settings::values.use_docked_mode.SetValue(is_docked ? Settings::ConsoleMode::Handheld
+ : Settings::ConsoleMode::Docked);
UpdateDockedButton();
OnDockedModeChanged(is_docked, !is_docked, *system);
}
@@ -4118,10 +4127,10 @@ void GMainWindow::UpdateGPUAccuracyButton() {
}
void GMainWindow::UpdateDockedButton() {
- const bool is_docked = Settings::values.use_docked_mode.GetValue();
- dock_status_button->setChecked(is_docked);
+ const auto console_mode = Settings::values.use_docked_mode.GetValue();
+ dock_status_button->setChecked(Settings::IsDockedMode());
dock_status_button->setText(
- Config::use_docked_mode_texts_map.find(is_docked)->second.toUpper());
+ Config::use_docked_mode_texts_map.find(console_mode)->second.toUpper());
}
void GMainWindow::UpdateAPIText() {
@@ -4514,6 +4523,8 @@ void GMainWindow::RequestGameExit() {
auto applet_ae = sm.GetService<Service::AM::AppletAE>("appletAE");
bool has_signalled = false;
+ system->SetExitRequested(true);
+
if (applet_oe != nullptr) {
applet_oe->GetMessageQueue()->RequestExit();
has_signalled = true;