summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt15
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt8
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt23
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt10
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt1
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt11
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt4
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt53
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt53
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt45
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt113
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt5
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt32
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt46
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsSearchFragment.kt16
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt16
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/EmulationViewModel.kt44
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt44
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeSetting.kt6
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt28
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt57
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt27
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt64
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt23
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconUtils.kt11
-rw-r--r--src/android/app/src/main/jni/config.cpp2
-rw-r--r--src/android/app/src/main/jni/native.cpp20
-rw-r--r--src/android/app/src/main/res/drawable/shortcut.xml11
-rw-r--r--src/android/app/src/main/res/navigation/emulation_navigation.xml2
-rw-r--r--src/android/app/src/main/res/navigation/home_navigation.xml2
-rw-r--r--src/android/app/src/main/res/navigation/settings_navigation.xml2
-rw-r--r--src/android/app/src/main/res/values/dimens.xml1
-rw-r--r--src/common/settings.h2
-rw-r--r--src/common/settings_common.h10
-rw-r--r--src/common/settings_setting.h33
-rw-r--r--src/core/CMakeLists.txt5
-rw-r--r--src/core/core.cpp11
-rw-r--r--src/core/core.h6
-rw-r--r--src/core/crypto/key_manager.cpp8
-rw-r--r--src/core/file_sys/ips_layer.cpp4
-rw-r--r--src/core/hle/service/am/am.cpp14
-rw-r--r--src/core/hle/service/am/am.h1
-rw-r--r--src/core/hle/service/sockets/bsd.cpp2
-rw-r--r--src/core/memory/cheat_engine.cpp2
-rw-r--r--src/core/tools/renderdoc.cpp55
-rw-r--r--src/core/tools/renderdoc.h22
-rw-r--r--src/yuzu/configuration/configure_debug.cpp3
-rw-r--r--src/yuzu/configuration/configure_debug.ui99
-rw-r--r--src/yuzu/configuration/configure_ui.cpp7
-rw-r--r--src/yuzu/configuration/shared_widget.cpp171
-rw-r--r--src/yuzu/configuration/shared_widget.h21
-rw-r--r--src/yuzu/hotkeys.h4
-rw-r--r--src/yuzu/main.cpp6
-rw-r--r--src/yuzu_cmd/config.cpp2
-rw-r--r--src/yuzu_cmd/yuzu.cpp5
55 files changed, 838 insertions, 450 deletions
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt
index c8706d7a6..21f67f32a 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
@@ -308,21 +308,6 @@ object NativeLibrary {
308 external fun isPaused(): Boolean 308 external fun isPaused(): Boolean
309 309
310 /** 310 /**
311 * Mutes emulation sound
312 */
313 external fun muteAudio(): Boolean
314
315 /**
316 * Unmutes emulation sound
317 */
318 external fun unmuteAudio(): Boolean
319
320 /**
321 * Returns true if emulation audio is muted.
322 */
323 external fun isMuted(): Boolean
324
325 /**
326 * Returns the performance stats for the current game 311 * Returns the performance stats for the current game
327 */ 312 */
328 external fun getPerfStats(): DoubleArray 313 external fun getPerfStats(): DoubleArray
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 bbd328c71..d4ae39661 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
@@ -332,7 +332,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
332 pictureInPictureActions.add(pauseRemoteAction) 332 pictureInPictureActions.add(pauseRemoteAction)
333 } 333 }
334 334
335 if (NativeLibrary.isMuted()) { 335 if (BooleanSetting.AUDIO_MUTED.boolean) {
336 val unmuteIcon = Icon.createWithResource( 336 val unmuteIcon = Icon.createWithResource(
337 this@EmulationActivity, 337 this@EmulationActivity,
338 R.drawable.ic_pip_unmute 338 R.drawable.ic_pip_unmute
@@ -389,9 +389,9 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
389 if (!NativeLibrary.isPaused()) NativeLibrary.pauseEmulation() 389 if (!NativeLibrary.isPaused()) NativeLibrary.pauseEmulation()
390 } 390 }
391 if (intent.action == actionUnmute) { 391 if (intent.action == actionUnmute) {
392 if (NativeLibrary.isMuted()) NativeLibrary.unmuteAudio() 392 if (BooleanSetting.AUDIO_MUTED.boolean) BooleanSetting.AUDIO_MUTED.setBoolean(false)
393 } else if (intent.action == actionMute) { 393 } else if (intent.action == actionMute) {
394 if (!NativeLibrary.isMuted()) NativeLibrary.muteAudio() 394 if (!BooleanSetting.AUDIO_MUTED.boolean) BooleanSetting.AUDIO_MUTED.setBoolean(true)
395 } 395 }
396 buildPictureInPictureParams() 396 buildPictureInPictureParams()
397 } 397 }
@@ -417,7 +417,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
417 } catch (ignored: Exception) { 417 } catch (ignored: Exception) {
418 } 418 }
419 // Always resume audio, since there is no UI button 419 // Always resume audio, since there is no UI button
420 if (NativeLibrary.isMuted()) NativeLibrary.unmuteAudio() 420 if (BooleanSetting.AUDIO_MUTED.boolean) BooleanSetting.AUDIO_MUTED.setBoolean(false)
421 } 421 }
422 } 422 }
423 423
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 0013e8512..f9f88a1d2 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
@@ -4,7 +4,8 @@
4package org.yuzu.yuzu_emu.adapters 4package org.yuzu.yuzu_emu.adapters
5 5
6import android.content.Intent 6import android.content.Intent
7import android.graphics.drawable.BitmapDrawable 7import android.graphics.Bitmap
8import android.graphics.drawable.LayerDrawable
8import android.net.Uri 9import android.net.Uri
9import android.text.TextUtils 10import android.text.TextUtils
10import android.view.LayoutInflater 11import android.view.LayoutInflater
@@ -15,7 +16,10 @@ import android.widget.Toast
15import androidx.appcompat.app.AppCompatActivity 16import androidx.appcompat.app.AppCompatActivity
16import androidx.core.content.pm.ShortcutInfoCompat 17import androidx.core.content.pm.ShortcutInfoCompat
17import androidx.core.content.pm.ShortcutManagerCompat 18import androidx.core.content.pm.ShortcutManagerCompat
19import androidx.core.content.res.ResourcesCompat
18import androidx.core.graphics.drawable.IconCompat 20import androidx.core.graphics.drawable.IconCompat
21import androidx.core.graphics.drawable.toBitmap
22import androidx.core.graphics.drawable.toDrawable
19import androidx.documentfile.provider.DocumentFile 23import androidx.documentfile.provider.DocumentFile
20import androidx.lifecycle.ViewModelProvider 24import androidx.lifecycle.ViewModelProvider
21import androidx.navigation.findNavController 25import androidx.navigation.findNavController
@@ -87,11 +91,24 @@ class GameAdapter(private val activity: AppCompatActivity) :
87 action = Intent.ACTION_VIEW 91 action = Intent.ACTION_VIEW
88 data = Uri.parse(holder.game.path) 92 data = Uri.parse(holder.game.path)
89 } 93 }
94
95 val layerDrawable = ResourcesCompat.getDrawable(
96 YuzuApplication.appContext.resources,
97 R.drawable.shortcut,
98 null
99 ) as LayerDrawable
100 layerDrawable.setDrawableByLayerId(
101 R.id.shortcut_foreground,
102 GameIconUtils.getGameIcon(holder.game).toDrawable(YuzuApplication.appContext.resources)
103 )
104 val inset = YuzuApplication.appContext.resources
105 .getDimensionPixelSize(R.dimen.icon_inset)
106 layerDrawable.setLayerInset(1, inset, inset, inset, inset)
90 val shortcut = ShortcutInfoCompat.Builder(YuzuApplication.appContext, holder.game.path) 107 val shortcut = ShortcutInfoCompat.Builder(YuzuApplication.appContext, holder.game.path)
91 .setShortLabel(holder.game.title) 108 .setShortLabel(holder.game.title)
92 .setIcon( 109 .setIcon(
93 IconCompat.createWithBitmap( 110 IconCompat.createWithAdaptiveBitmap(
94 (holder.binding.imageGameScreen.drawable as BitmapDrawable).bitmap 111 layerDrawable.toBitmap(config = Bitmap.Config.ARGB_8888)
95 ) 112 )
96 ) 113 )
97 .setIntent(openIntent) 114 .setIntent(openIntent)
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 8d87d3bd7..1675627a1 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
@@ -10,8 +10,12 @@ import android.view.ViewGroup
10import androidx.appcompat.app.AppCompatActivity 10import androidx.appcompat.app.AppCompatActivity
11import androidx.core.content.ContextCompat 11import androidx.core.content.ContextCompat
12import androidx.core.content.res.ResourcesCompat 12import androidx.core.content.res.ResourcesCompat
13import androidx.lifecycle.Lifecycle
13import androidx.lifecycle.LifecycleOwner 14import androidx.lifecycle.LifecycleOwner
15import androidx.lifecycle.lifecycleScope
16import androidx.lifecycle.repeatOnLifecycle
14import androidx.recyclerview.widget.RecyclerView 17import androidx.recyclerview.widget.RecyclerView
18import kotlinx.coroutines.launch
15import org.yuzu.yuzu_emu.R 19import org.yuzu.yuzu_emu.R
16import org.yuzu.yuzu_emu.databinding.CardHomeOptionBinding 20import org.yuzu.yuzu_emu.databinding.CardHomeOptionBinding
17import org.yuzu.yuzu_emu.fragments.MessageDialogFragment 21import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
@@ -86,7 +90,11 @@ class HomeSettingAdapter(
86 binding.optionIcon.alpha = 0.5f 90 binding.optionIcon.alpha = 0.5f
87 } 91 }
88 92
89 option.details.observe(viewLifecycle) { updateOptionDetails(it) } 93 viewLifecycle.lifecycleScope.launch {
94 viewLifecycle.repeatOnLifecycle(Lifecycle.State.CREATED) {
95 option.details.collect { updateOptionDetails(it) }
96 }
97 }
90 binding.optionDetail.postDelayed( 98 binding.optionDetail.postDelayed(
91 { 99 {
92 binding.optionDetail.ellipsize = TextUtils.TruncateAt.MARQUEE 100 binding.optionDetail.ellipsize = TextUtils.TruncateAt.MARQUEE
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 e0c0538c7..8476ce867 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
@@ -10,6 +10,7 @@ enum class BooleanSetting(
10 override val category: Settings.Category, 10 override val category: Settings.Category,
11 override val androidDefault: Boolean? = null 11 override val androidDefault: Boolean? = null
12) : AbstractBooleanSetting { 12) : AbstractBooleanSetting {
13 AUDIO_MUTED("audio_muted", Settings.Category.Audio),
13 CPU_DEBUG_MODE("cpu_debug_mode", Settings.Category.Cpu), 14 CPU_DEBUG_MODE("cpu_debug_mode", Settings.Category.Cpu),
14 FASTMEM("cpuopt_fastmem", Settings.Category.Cpu), 15 FASTMEM("cpuopt_fastmem", Settings.Category.Cpu),
15 FASTMEM_EXCLUSIVES("cpuopt_fastmem_exclusives", Settings.Category.Cpu), 16 FASTMEM_EXCLUSIVES("cpuopt_fastmem_exclusives", Settings.Category.Cpu),
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 0702236e8..08e2a973d 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
@@ -80,6 +80,17 @@ object Settings {
80 const val SECTION_THEME = "Theme" 80 const val SECTION_THEME = "Theme"
81 const val SECTION_DEBUG = "Debug" 81 const val SECTION_DEBUG = "Debug"
82 82
83 enum class MenuTag(val titleId: Int) {
84 SECTION_ROOT(R.string.advanced_settings),
85 SECTION_GENERAL(R.string.preferences_general),
86 SECTION_SYSTEM(R.string.preferences_system),
87 SECTION_RENDERER(R.string.preferences_graphics),
88 SECTION_AUDIO(R.string.preferences_audio),
89 SECTION_CPU(R.string.cpu),
90 SECTION_THEME(R.string.preferences_theme),
91 SECTION_DEBUG(R.string.preferences_debug);
92 }
93
83 const val PREF_MEMORY_WARNING_SHOWN = "MemoryWarningShown" 94 const val PREF_MEMORY_WARNING_SHOWN = "MemoryWarningShown"
84 95
85 const val PREF_OVERLAY_VERSION = "OverlayVersion" 96 const val PREF_OVERLAY_VERSION = "OverlayVersion"
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 91c273964..b343e527e 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
@@ -3,10 +3,12 @@
3 3
4package org.yuzu.yuzu_emu.features.settings.model.view 4package org.yuzu.yuzu_emu.features.settings.model.view
5 5
6import org.yuzu.yuzu_emu.features.settings.model.Settings
7
6class SubmenuSetting( 8class SubmenuSetting(
7 titleId: Int, 9 titleId: Int,
8 descriptionId: Int, 10 descriptionId: Int,
9 val menuKey: String 11 val menuKey: Settings.MenuTag
10) : SettingsItem(emptySetting, titleId, descriptionId) { 12) : SettingsItem(emptySetting, titleId, descriptionId) {
11 override val type = TYPE_SUBMENU 13 override val type = TYPE_SUBMENU
12} 14}
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 908c01265..4d2f2f604 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
@@ -13,9 +13,14 @@ import androidx.appcompat.app.AppCompatActivity
13import androidx.core.view.ViewCompat 13import androidx.core.view.ViewCompat
14import androidx.core.view.WindowCompat 14import androidx.core.view.WindowCompat
15import androidx.core.view.WindowInsetsCompat 15import androidx.core.view.WindowInsetsCompat
16import androidx.lifecycle.Lifecycle
17import androidx.lifecycle.lifecycleScope
18import androidx.lifecycle.repeatOnLifecycle
16import androidx.navigation.fragment.NavHostFragment 19import androidx.navigation.fragment.NavHostFragment
17import androidx.navigation.navArgs 20import androidx.navigation.navArgs
18import com.google.android.material.color.MaterialColors 21import com.google.android.material.color.MaterialColors
22import kotlinx.coroutines.flow.collectLatest
23import kotlinx.coroutines.launch
19import java.io.IOException 24import java.io.IOException
20import org.yuzu.yuzu_emu.R 25import org.yuzu.yuzu_emu.R
21import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding 26import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding
@@ -66,25 +71,39 @@ class SettingsActivity : AppCompatActivity() {
66 ) 71 )
67 } 72 }
68 73
69 settingsViewModel.shouldRecreate.observe(this) { 74 lifecycleScope.apply {
70 if (it) { 75 launch {
71 settingsViewModel.setShouldRecreate(false) 76 repeatOnLifecycle(Lifecycle.State.CREATED) {
72 recreate() 77 settingsViewModel.shouldRecreate.collectLatest {
78 if (it) {
79 settingsViewModel.setShouldRecreate(false)
80 recreate()
81 }
82 }
83 }
73 } 84 }
74 } 85 launch {
75 settingsViewModel.shouldNavigateBack.observe(this) { 86 repeatOnLifecycle(Lifecycle.State.CREATED) {
76 if (it) { 87 settingsViewModel.shouldNavigateBack.collectLatest {
77 settingsViewModel.setShouldNavigateBack(false) 88 if (it) {
78 navigateBack() 89 settingsViewModel.setShouldNavigateBack(false)
90 navigateBack()
91 }
92 }
93 }
79 } 94 }
80 } 95 launch {
81 settingsViewModel.shouldShowResetSettingsDialog.observe(this) { 96 repeatOnLifecycle(Lifecycle.State.CREATED) {
82 if (it) { 97 settingsViewModel.shouldShowResetSettingsDialog.collectLatest {
83 settingsViewModel.setShouldShowResetSettingsDialog(false) 98 if (it) {
84 ResetSettingsDialogFragment().show( 99 settingsViewModel.setShouldShowResetSettingsDialog(false)
85 supportFragmentManager, 100 ResetSettingsDialogFragment().show(
86 ResetSettingsDialogFragment.TAG 101 supportFragmentManager,
87 ) 102 ResetSettingsDialogFragment.TAG
103 )
104 }
105 }
106 }
88 } 107 }
89 } 108 }
90 109
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 bc319714c..70d8ec14b 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,6 +3,7 @@
3 3
4package org.yuzu.yuzu_emu.features.settings.ui 4package org.yuzu.yuzu_emu.features.settings.ui
5 5
6import android.annotation.SuppressLint
6import android.os.Bundle 7import android.os.Bundle
7import android.view.LayoutInflater 8import android.view.LayoutInflater
8import android.view.View 9import android.view.View
@@ -13,14 +14,19 @@ import androidx.core.view.WindowInsetsCompat
13import androidx.core.view.updatePadding 14import androidx.core.view.updatePadding
14import androidx.fragment.app.Fragment 15import androidx.fragment.app.Fragment
15import androidx.fragment.app.activityViewModels 16import androidx.fragment.app.activityViewModels
17import androidx.lifecycle.Lifecycle
18import androidx.lifecycle.lifecycleScope
19import androidx.lifecycle.repeatOnLifecycle
16import androidx.navigation.findNavController 20import androidx.navigation.findNavController
17import androidx.navigation.fragment.navArgs 21import androidx.navigation.fragment.navArgs
18import androidx.recyclerview.widget.LinearLayoutManager 22import androidx.recyclerview.widget.LinearLayoutManager
19import com.google.android.material.divider.MaterialDividerItemDecoration 23import com.google.android.material.divider.MaterialDividerItemDecoration
20import com.google.android.material.transition.MaterialSharedAxis 24import com.google.android.material.transition.MaterialSharedAxis
25import kotlinx.coroutines.flow.collectLatest
26import kotlinx.coroutines.launch
21import org.yuzu.yuzu_emu.R 27import org.yuzu.yuzu_emu.R
22import org.yuzu.yuzu_emu.databinding.FragmentSettingsBinding 28import org.yuzu.yuzu_emu.databinding.FragmentSettingsBinding
23import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile 29import org.yuzu.yuzu_emu.features.settings.model.Settings
24import org.yuzu.yuzu_emu.model.SettingsViewModel 30import org.yuzu.yuzu_emu.model.SettingsViewModel
25 31
26class SettingsFragment : Fragment() { 32class SettingsFragment : Fragment() {
@@ -51,15 +57,17 @@ class SettingsFragment : Fragment() {
51 return binding.root 57 return binding.root
52 } 58 }
53 59
60 // This is using the correct scope, lint is just acting up
61 @SuppressLint("UnsafeRepeatOnLifecycleDetector")
54 override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 62 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
55 settingsAdapter = SettingsAdapter(this, requireContext()) 63 settingsAdapter = SettingsAdapter(this, requireContext())
56 presenter = SettingsFragmentPresenter( 64 presenter = SettingsFragmentPresenter(
57 settingsViewModel, 65 settingsViewModel,
58 settingsAdapter!!, 66 settingsAdapter!!,
59 args.menuTag, 67 args.menuTag
60 args.game?.gameId ?: ""
61 ) 68 )
62 69
70 binding.toolbarSettingsLayout.title = getString(args.menuTag.titleId)
63 val dividerDecoration = MaterialDividerItemDecoration( 71 val dividerDecoration = MaterialDividerItemDecoration(
64 requireContext(), 72 requireContext(),
65 LinearLayoutManager.VERTICAL 73 LinearLayoutManager.VERTICAL
@@ -75,28 +83,31 @@ class SettingsFragment : Fragment() {
75 settingsViewModel.setShouldNavigateBack(true) 83 settingsViewModel.setShouldNavigateBack(true)
76 } 84 }
77 85
78 settingsViewModel.toolbarTitle.observe(viewLifecycleOwner) { 86 viewLifecycleOwner.lifecycleScope.apply {
79 if (it.isNotEmpty()) binding.toolbarSettingsLayout.title = it 87 launch {
80 } 88 repeatOnLifecycle(Lifecycle.State.CREATED) {
81 89 settingsViewModel.shouldReloadSettingsList.collectLatest {
82 settingsViewModel.shouldReloadSettingsList.observe(viewLifecycleOwner) { 90 if (it) {
83 if (it) { 91 settingsViewModel.setShouldReloadSettingsList(false)
84 settingsViewModel.setShouldReloadSettingsList(false) 92 presenter.loadSettingsList()
85 presenter.loadSettingsList() 93 }
94 }
95 }
86 } 96 }
87 } 97 launch {
88 98 settingsViewModel.isUsingSearch.collectLatest {
89 settingsViewModel.isUsingSearch.observe(viewLifecycleOwner) { 99 if (it) {
90 if (it) { 100 reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true)
91 reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true) 101 exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false)
92 exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false) 102 } else {
93 } else { 103 reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
94 reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false) 104 exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
95 exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) 105 }
106 }
96 } 107 }
97 } 108 }
98 109
99 if (args.menuTag == SettingsFile.FILE_NAME_CONFIG) { 110 if (args.menuTag == Settings.MenuTag.SECTION_ROOT) {
100 binding.toolbarSettings.inflateMenu(R.menu.menu_settings) 111 binding.toolbarSettings.inflateMenu(R.menu.menu_settings)
101 binding.toolbarSettings.setOnMenuItemClickListener { 112 binding.toolbarSettings.setOnMenuItemClickListener {
102 when (it.itemId) { 113 when (it.itemId) {
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 22a529b1b..766414a6c 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt
@@ -6,7 +6,6 @@ package org.yuzu.yuzu_emu.features.settings.ui
6import android.content.Context 6import android.content.Context
7import android.content.SharedPreferences 7import android.content.SharedPreferences
8import android.os.Build 8import android.os.Build
9import android.text.TextUtils
10import android.widget.Toast 9import android.widget.Toast
11import androidx.preference.PreferenceManager 10import androidx.preference.PreferenceManager
12import org.yuzu.yuzu_emu.R 11import org.yuzu.yuzu_emu.R
@@ -20,15 +19,13 @@ import org.yuzu.yuzu_emu.features.settings.model.LongSetting
20import org.yuzu.yuzu_emu.features.settings.model.Settings 19import org.yuzu.yuzu_emu.features.settings.model.Settings
21import org.yuzu.yuzu_emu.features.settings.model.ShortSetting 20import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
22import org.yuzu.yuzu_emu.features.settings.model.view.* 21import org.yuzu.yuzu_emu.features.settings.model.view.*
23import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
24import org.yuzu.yuzu_emu.model.SettingsViewModel 22import org.yuzu.yuzu_emu.model.SettingsViewModel
25import org.yuzu.yuzu_emu.utils.NativeConfig 23import org.yuzu.yuzu_emu.utils.NativeConfig
26 24
27class SettingsFragmentPresenter( 25class SettingsFragmentPresenter(
28 private val settingsViewModel: SettingsViewModel, 26 private val settingsViewModel: SettingsViewModel,
29 private val adapter: SettingsAdapter, 27 private val adapter: SettingsAdapter,
30 private var menuTag: String, 28 private var menuTag: Settings.MenuTag
31 private var gameId: String
32) { 29) {
33 private var settingsList = ArrayList<SettingsItem>() 30 private var settingsList = ArrayList<SettingsItem>()
34 31
@@ -53,24 +50,15 @@ class SettingsFragmentPresenter(
53 } 50 }
54 51
55 fun loadSettingsList() { 52 fun loadSettingsList() {
56 if (!TextUtils.isEmpty(gameId)) {
57 settingsViewModel.setToolbarTitle(
58 context.getString(
59 R.string.advanced_settings_game,
60 gameId
61 )
62 )
63 }
64
65 val sl = ArrayList<SettingsItem>() 53 val sl = ArrayList<SettingsItem>()
66 when (menuTag) { 54 when (menuTag) {
67 SettingsFile.FILE_NAME_CONFIG -> addConfigSettings(sl) 55 Settings.MenuTag.SECTION_ROOT -> addConfigSettings(sl)
68 Settings.SECTION_GENERAL -> addGeneralSettings(sl) 56 Settings.MenuTag.SECTION_GENERAL -> addGeneralSettings(sl)
69 Settings.SECTION_SYSTEM -> addSystemSettings(sl) 57 Settings.MenuTag.SECTION_SYSTEM -> addSystemSettings(sl)
70 Settings.SECTION_RENDERER -> addGraphicsSettings(sl) 58 Settings.MenuTag.SECTION_RENDERER -> addGraphicsSettings(sl)
71 Settings.SECTION_AUDIO -> addAudioSettings(sl) 59 Settings.MenuTag.SECTION_AUDIO -> addAudioSettings(sl)
72 Settings.SECTION_THEME -> addThemeSettings(sl) 60 Settings.MenuTag.SECTION_THEME -> addThemeSettings(sl)
73 Settings.SECTION_DEBUG -> addDebugSettings(sl) 61 Settings.MenuTag.SECTION_DEBUG -> addDebugSettings(sl)
74 else -> { 62 else -> {
75 val context = YuzuApplication.appContext 63 val context = YuzuApplication.appContext
76 Toast.makeText( 64 Toast.makeText(
@@ -86,13 +74,12 @@ class SettingsFragmentPresenter(
86 } 74 }
87 75
88 private fun addConfigSettings(sl: ArrayList<SettingsItem>) { 76 private fun addConfigSettings(sl: ArrayList<SettingsItem>) {
89 settingsViewModel.setToolbarTitle(context.getString(R.string.advanced_settings))
90 sl.apply { 77 sl.apply {
91 add(SubmenuSetting(R.string.preferences_general, 0, Settings.SECTION_GENERAL)) 78 add(SubmenuSetting(R.string.preferences_general, 0, Settings.MenuTag.SECTION_GENERAL))
92 add(SubmenuSetting(R.string.preferences_system, 0, Settings.SECTION_SYSTEM)) 79 add(SubmenuSetting(R.string.preferences_system, 0, Settings.MenuTag.SECTION_SYSTEM))
93 add(SubmenuSetting(R.string.preferences_graphics, 0, Settings.SECTION_RENDERER)) 80 add(SubmenuSetting(R.string.preferences_graphics, 0, Settings.MenuTag.SECTION_RENDERER))
94 add(SubmenuSetting(R.string.preferences_audio, 0, Settings.SECTION_AUDIO)) 81 add(SubmenuSetting(R.string.preferences_audio, 0, Settings.MenuTag.SECTION_AUDIO))
95 add(SubmenuSetting(R.string.preferences_debug, 0, Settings.SECTION_DEBUG)) 82 add(SubmenuSetting(R.string.preferences_debug, 0, Settings.MenuTag.SECTION_DEBUG))
96 add( 83 add(
97 RunnableSetting(R.string.reset_to_default, 0, false) { 84 RunnableSetting(R.string.reset_to_default, 0, false) {
98 settingsViewModel.setShouldShowResetSettingsDialog(true) 85 settingsViewModel.setShouldShowResetSettingsDialog(true)
@@ -102,7 +89,6 @@ class SettingsFragmentPresenter(
102 } 89 }
103 90
104 private fun addGeneralSettings(sl: ArrayList<SettingsItem>) { 91 private fun addGeneralSettings(sl: ArrayList<SettingsItem>) {
105 settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_general))
106 sl.apply { 92 sl.apply {
107 add(BooleanSetting.RENDERER_USE_SPEED_LIMIT.key) 93 add(BooleanSetting.RENDERER_USE_SPEED_LIMIT.key)
108 add(ShortSetting.RENDERER_SPEED_LIMIT.key) 94 add(ShortSetting.RENDERER_SPEED_LIMIT.key)
@@ -112,7 +98,6 @@ class SettingsFragmentPresenter(
112 } 98 }
113 99
114 private fun addSystemSettings(sl: ArrayList<SettingsItem>) { 100 private fun addSystemSettings(sl: ArrayList<SettingsItem>) {
115 settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_system))
116 sl.apply { 101 sl.apply {
117 add(BooleanSetting.USE_DOCKED_MODE.key) 102 add(BooleanSetting.USE_DOCKED_MODE.key)
118 add(IntSetting.REGION_INDEX.key) 103 add(IntSetting.REGION_INDEX.key)
@@ -123,7 +108,6 @@ class SettingsFragmentPresenter(
123 } 108 }
124 109
125 private fun addGraphicsSettings(sl: ArrayList<SettingsItem>) { 110 private fun addGraphicsSettings(sl: ArrayList<SettingsItem>) {
126 settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_graphics))
127 sl.apply { 111 sl.apply {
128 add(IntSetting.RENDERER_ACCURACY.key) 112 add(IntSetting.RENDERER_ACCURACY.key)
129 add(IntSetting.RENDERER_RESOLUTION.key) 113 add(IntSetting.RENDERER_RESOLUTION.key)
@@ -140,7 +124,6 @@ class SettingsFragmentPresenter(
140 } 124 }
141 125
142 private fun addAudioSettings(sl: ArrayList<SettingsItem>) { 126 private fun addAudioSettings(sl: ArrayList<SettingsItem>) {
143 settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_audio))
144 sl.apply { 127 sl.apply {
145 add(IntSetting.AUDIO_OUTPUT_ENGINE.key) 128 add(IntSetting.AUDIO_OUTPUT_ENGINE.key)
146 add(ByteSetting.AUDIO_VOLUME.key) 129 add(ByteSetting.AUDIO_VOLUME.key)
@@ -148,7 +131,6 @@ class SettingsFragmentPresenter(
148 } 131 }
149 132
150 private fun addThemeSettings(sl: ArrayList<SettingsItem>) { 133 private fun addThemeSettings(sl: ArrayList<SettingsItem>) {
151 settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_theme))
152 sl.apply { 134 sl.apply {
153 val theme: AbstractIntSetting = object : AbstractIntSetting { 135 val theme: AbstractIntSetting = object : AbstractIntSetting {
154 override val int: Int 136 override val int: Int
@@ -261,7 +243,6 @@ class SettingsFragmentPresenter(
261 } 243 }
262 244
263 private fun addDebugSettings(sl: ArrayList<SettingsItem>) { 245 private fun addDebugSettings(sl: ArrayList<SettingsItem>) {
264 settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_debug))
265 sl.apply { 246 sl.apply {
266 add(HeaderSetting(R.string.gpu)) 247 add(HeaderSetting(R.string.gpu))
267 add(IntSetting.RENDERER_BACKEND.key) 248 add(IntSetting.RENDERER_BACKEND.key)
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 944ae652e..3e6c157c7 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
@@ -39,6 +39,7 @@ import androidx.window.layout.WindowLayoutInfo
39import com.google.android.material.dialog.MaterialAlertDialogBuilder 39import com.google.android.material.dialog.MaterialAlertDialogBuilder
40import com.google.android.material.slider.Slider 40import com.google.android.material.slider.Slider
41import kotlinx.coroutines.Dispatchers 41import kotlinx.coroutines.Dispatchers
42import kotlinx.coroutines.flow.collectLatest
42import kotlinx.coroutines.launch 43import kotlinx.coroutines.launch
43import org.yuzu.yuzu_emu.HomeNavigationDirections 44import org.yuzu.yuzu_emu.HomeNavigationDirections
44import org.yuzu.yuzu_emu.NativeLibrary 45import org.yuzu.yuzu_emu.NativeLibrary
@@ -49,7 +50,6 @@ import org.yuzu.yuzu_emu.databinding.DialogOverlayAdjustBinding
49import org.yuzu.yuzu_emu.databinding.FragmentEmulationBinding 50import org.yuzu.yuzu_emu.databinding.FragmentEmulationBinding
50import org.yuzu.yuzu_emu.features.settings.model.IntSetting 51import org.yuzu.yuzu_emu.features.settings.model.IntSetting
51import org.yuzu.yuzu_emu.features.settings.model.Settings 52import org.yuzu.yuzu_emu.features.settings.model.Settings
52import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
53import org.yuzu.yuzu_emu.model.Game 53import org.yuzu.yuzu_emu.model.Game
54import org.yuzu.yuzu_emu.model.EmulationViewModel 54import org.yuzu.yuzu_emu.model.EmulationViewModel
55import org.yuzu.yuzu_emu.overlay.InputOverlay 55import org.yuzu.yuzu_emu.overlay.InputOverlay
@@ -129,6 +129,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
129 return binding.root 129 return binding.root
130 } 130 }
131 131
132 // This is using the correct scope, lint is just acting up
133 @SuppressLint("UnsafeRepeatOnLifecycleDetector")
132 override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 134 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
133 binding.surfaceEmulation.holder.addCallback(this) 135 binding.surfaceEmulation.holder.addCallback(this)
134 binding.showFpsText.setTextColor(Color.YELLOW) 136 binding.showFpsText.setTextColor(Color.YELLOW)
@@ -163,7 +165,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
163 R.id.menu_settings -> { 165 R.id.menu_settings -> {
164 val action = HomeNavigationDirections.actionGlobalSettingsActivity( 166 val action = HomeNavigationDirections.actionGlobalSettingsActivity(
165 null, 167 null,
166 SettingsFile.FILE_NAME_CONFIG 168 Settings.MenuTag.SECTION_ROOT
167 ) 169 )
168 binding.root.findNavController().navigate(action) 170 binding.root.findNavController().navigate(action)
169 true 171 true
@@ -205,59 +207,80 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
205 } 207 }
206 ) 208 )
207 209
208 viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Main) {
209 lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
210 WindowInfoTracker.getOrCreate(requireContext())
211 .windowLayoutInfo(requireActivity())
212 .collect { updateFoldableLayout(requireActivity() as EmulationActivity, it) }
213 }
214 }
215
216 GameIconUtils.loadGameIcon(game, binding.loadingImage) 210 GameIconUtils.loadGameIcon(game, binding.loadingImage)
217 binding.loadingTitle.text = game.title 211 binding.loadingTitle.text = game.title
218 binding.loadingTitle.isSelected = true 212 binding.loadingTitle.isSelected = true
219 binding.loadingText.isSelected = true 213 binding.loadingText.isSelected = true
220 214
221 emulationViewModel.shaderProgress.observe(viewLifecycleOwner) { 215 viewLifecycleOwner.lifecycleScope.apply {
222 if (it > 0 && it != emulationViewModel.totalShaders.value!!) { 216 launch {
223 binding.loadingProgressIndicator.isIndeterminate = false 217 repeatOnLifecycle(Lifecycle.State.STARTED) {
224 218 WindowInfoTracker.getOrCreate(requireContext())
225 if (it < binding.loadingProgressIndicator.max) { 219 .windowLayoutInfo(requireActivity())
226 binding.loadingProgressIndicator.progress = it 220 .collect {
221 updateFoldableLayout(requireActivity() as EmulationActivity, it)
222 }
227 } 223 }
228 } 224 }
225 launch {
226 repeatOnLifecycle(Lifecycle.State.CREATED) {
227 emulationViewModel.shaderProgress.collectLatest {
228 if (it > 0 && it != emulationViewModel.totalShaders.value) {
229 binding.loadingProgressIndicator.isIndeterminate = false
230
231 if (it < binding.loadingProgressIndicator.max) {
232 binding.loadingProgressIndicator.progress = it
233 }
234 }
229 235
230 if (it == emulationViewModel.totalShaders.value!!) { 236 if (it == emulationViewModel.totalShaders.value) {
231 binding.loadingText.setText(R.string.loading) 237 binding.loadingText.setText(R.string.loading)
232 binding.loadingProgressIndicator.isIndeterminate = true 238 binding.loadingProgressIndicator.isIndeterminate = true
239 }
240 }
241 }
233 } 242 }
234 } 243 launch {
235 emulationViewModel.totalShaders.observe(viewLifecycleOwner) { 244 repeatOnLifecycle(Lifecycle.State.CREATED) {
236 binding.loadingProgressIndicator.max = it 245 emulationViewModel.totalShaders.collectLatest {
237 } 246 binding.loadingProgressIndicator.max = it
238 emulationViewModel.shaderMessage.observe(viewLifecycleOwner) { 247 }
239 if (it.isNotEmpty()) { 248 }
240 binding.loadingText.text = it
241 } 249 }
242 } 250 launch {
243 251 repeatOnLifecycle(Lifecycle.State.CREATED) {
244 emulationViewModel.emulationStarted.observe(viewLifecycleOwner) { started -> 252 emulationViewModel.shaderMessage.collectLatest {
245 if (started) { 253 if (it.isNotEmpty()) {
246 binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED) 254 binding.loadingText.text = it
247 ViewUtils.showView(binding.surfaceInputOverlay) 255 }
248 ViewUtils.hideView(binding.loadingIndicator) 256 }
249 257 }
250 // Setup overlay
251 updateShowFpsOverlay()
252 } 258 }
253 } 259 launch {
260 repeatOnLifecycle(Lifecycle.State.CREATED) {
261 emulationViewModel.emulationStarted.collectLatest {
262 if (it) {
263 binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)
264 ViewUtils.showView(binding.surfaceInputOverlay)
265 ViewUtils.hideView(binding.loadingIndicator)
254 266
255 emulationViewModel.isEmulationStopping.observe(viewLifecycleOwner) { 267 // Setup overlay
256 if (it) { 268 updateShowFpsOverlay()
257 binding.loadingText.setText(R.string.shutting_down) 269 }
258 ViewUtils.showView(binding.loadingIndicator) 270 }
259 ViewUtils.hideView(binding.inputContainer) 271 }
260 ViewUtils.hideView(binding.showFpsText) 272 }
273 launch {
274 repeatOnLifecycle(Lifecycle.State.CREATED) {
275 emulationViewModel.isEmulationStopping.collectLatest {
276 if (it) {
277 binding.loadingText.setText(R.string.shutting_down)
278 ViewUtils.showView(binding.loadingIndicator)
279 ViewUtils.hideView(binding.inputContainer)
280 ViewUtils.hideView(binding.showFpsText)
281 }
282 }
283 }
261 } 284 }
262 } 285 }
263 } 286 }
@@ -274,9 +297,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
274 } 297 }
275 } 298 }
276 } else { 299 } else {
277 if (EmulationMenuSettings.showOverlay && 300 if (EmulationMenuSettings.showOverlay && emulationViewModel.emulationStarted.value) {
278 emulationViewModel.emulationStarted.value == true
279 ) {
280 binding.surfaceInputOverlay.post { 301 binding.surfaceInputOverlay.post {
281 binding.surfaceInputOverlay.visibility = View.VISIBLE 302 binding.surfaceInputOverlay.visibility = View.VISIBLE
282 } 303 }
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 cbbe14d22..c119e69c9 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
@@ -37,7 +37,6 @@ import org.yuzu.yuzu_emu.adapters.HomeSettingAdapter
37import org.yuzu.yuzu_emu.databinding.FragmentHomeSettingsBinding 37import org.yuzu.yuzu_emu.databinding.FragmentHomeSettingsBinding
38import org.yuzu.yuzu_emu.features.DocumentProvider 38import org.yuzu.yuzu_emu.features.DocumentProvider
39import org.yuzu.yuzu_emu.features.settings.model.Settings 39import org.yuzu.yuzu_emu.features.settings.model.Settings
40import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
41import org.yuzu.yuzu_emu.model.HomeSetting 40import org.yuzu.yuzu_emu.model.HomeSetting
42import org.yuzu.yuzu_emu.model.HomeViewModel 41import org.yuzu.yuzu_emu.model.HomeViewModel
43import org.yuzu.yuzu_emu.ui.main.MainActivity 42import org.yuzu.yuzu_emu.ui.main.MainActivity
@@ -78,7 +77,7 @@ class HomeSettingsFragment : Fragment() {
78 { 77 {
79 val action = HomeNavigationDirections.actionGlobalSettingsActivity( 78 val action = HomeNavigationDirections.actionGlobalSettingsActivity(
80 null, 79 null,
81 SettingsFile.FILE_NAME_CONFIG 80 Settings.MenuTag.SECTION_ROOT
82 ) 81 )
83 binding.root.findNavController().navigate(action) 82 binding.root.findNavController().navigate(action)
84 } 83 }
@@ -100,7 +99,7 @@ class HomeSettingsFragment : Fragment() {
100 { 99 {
101 val action = HomeNavigationDirections.actionGlobalSettingsActivity( 100 val action = HomeNavigationDirections.actionGlobalSettingsActivity(
102 null, 101 null,
103 Settings.SECTION_THEME 102 Settings.MenuTag.SECTION_THEME
104 ) 103 )
105 binding.root.findNavController().navigate(action) 104 binding.root.findNavController().navigate(action)
106 } 105 }
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 181bd983a..ea8eb073a 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
@@ -9,8 +9,12 @@ import android.widget.Toast
9import androidx.appcompat.app.AppCompatActivity 9import androidx.appcompat.app.AppCompatActivity
10import androidx.fragment.app.DialogFragment 10import androidx.fragment.app.DialogFragment
11import androidx.fragment.app.activityViewModels 11import androidx.fragment.app.activityViewModels
12import androidx.lifecycle.Lifecycle
12import androidx.lifecycle.ViewModelProvider 13import androidx.lifecycle.ViewModelProvider
14import androidx.lifecycle.lifecycleScope
15import androidx.lifecycle.repeatOnLifecycle
13import com.google.android.material.dialog.MaterialAlertDialogBuilder 16import com.google.android.material.dialog.MaterialAlertDialogBuilder
17import kotlinx.coroutines.launch
14import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding 18import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
15import org.yuzu.yuzu_emu.model.TaskViewModel 19import org.yuzu.yuzu_emu.model.TaskViewModel
16 20
@@ -28,21 +32,27 @@ class IndeterminateProgressDialogFragment : DialogFragment() {
28 .create() 32 .create()
29 dialog.setCanceledOnTouchOutside(false) 33 dialog.setCanceledOnTouchOutside(false)
30 34
31 taskViewModel.isComplete.observe(this) { complete -> 35 viewLifecycleOwner.lifecycleScope.launch {
32 if (complete) { 36 repeatOnLifecycle(Lifecycle.State.CREATED) {
33 dialog.dismiss() 37 taskViewModel.isComplete.collect {
34 when (val result = taskViewModel.result.value) { 38 if (it) {
35 is String -> Toast.makeText(requireContext(), result, Toast.LENGTH_LONG).show() 39 dialog.dismiss()
36 is MessageDialogFragment -> result.show( 40 when (val result = taskViewModel.result.value) {
37 requireActivity().supportFragmentManager, 41 is String -> Toast.makeText(requireContext(), result, Toast.LENGTH_LONG)
38 MessageDialogFragment.TAG 42 .show()
39 ) 43
44 is MessageDialogFragment -> result.show(
45 requireActivity().supportFragmentManager,
46 MessageDialogFragment.TAG
47 )
48 }
49 taskViewModel.clear()
50 }
40 } 51 }
41 taskViewModel.clear()
42 } 52 }
43 } 53 }
44 54
45 if (taskViewModel.isRunning.value == false) { 55 if (!taskViewModel.isRunning.value) {
46 taskViewModel.runTask() 56 taskViewModel.runTask()
47 } 57 }
48 return dialog 58 return dialog
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt
index f54dccc69..2dbca76a5 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt
@@ -3,6 +3,7 @@
3 3
4package org.yuzu.yuzu_emu.fragments 4package org.yuzu.yuzu_emu.fragments
5 5
6import android.annotation.SuppressLint
6import android.content.Context 7import android.content.Context
7import android.content.SharedPreferences 8import android.content.SharedPreferences
8import android.os.Bundle 9import android.os.Bundle
@@ -17,9 +18,13 @@ import androidx.core.view.updatePadding
17import androidx.core.widget.doOnTextChanged 18import androidx.core.widget.doOnTextChanged
18import androidx.fragment.app.Fragment 19import androidx.fragment.app.Fragment
19import androidx.fragment.app.activityViewModels 20import androidx.fragment.app.activityViewModels
21import androidx.lifecycle.Lifecycle
22import androidx.lifecycle.lifecycleScope
23import androidx.lifecycle.repeatOnLifecycle
20import androidx.preference.PreferenceManager 24import androidx.preference.PreferenceManager
21import info.debatty.java.stringsimilarity.Jaccard 25import info.debatty.java.stringsimilarity.Jaccard
22import info.debatty.java.stringsimilarity.JaroWinkler 26import info.debatty.java.stringsimilarity.JaroWinkler
27import kotlinx.coroutines.launch
23import java.util.Locale 28import java.util.Locale
24import org.yuzu.yuzu_emu.R 29import org.yuzu.yuzu_emu.R
25import org.yuzu.yuzu_emu.YuzuApplication 30import org.yuzu.yuzu_emu.YuzuApplication
@@ -52,6 +57,8 @@ class SearchFragment : Fragment() {
52 return binding.root 57 return binding.root
53 } 58 }
54 59
60 // This is using the correct scope, lint is just acting up
61 @SuppressLint("UnsafeRepeatOnLifecycleDetector")
55 override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 62 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
56 homeViewModel.setNavigationVisibility(visible = true, animated = false) 63 homeViewModel.setNavigationVisibility(visible = true, animated = false)
57 preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) 64 preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
@@ -79,21 +86,32 @@ class SearchFragment : Fragment() {
79 filterAndSearch() 86 filterAndSearch()
80 } 87 }
81 88
82 gamesViewModel.apply { 89 viewLifecycleOwner.lifecycleScope.apply {
83 searchFocused.observe(viewLifecycleOwner) { searchFocused -> 90 launch {
84 if (searchFocused) { 91 repeatOnLifecycle(Lifecycle.State.CREATED) {
85 focusSearch() 92 gamesViewModel.searchFocused.collect {
86 gamesViewModel.setSearchFocused(false) 93 if (it) {
94 focusSearch()
95 gamesViewModel.setSearchFocused(false)
96 }
97 }
87 } 98 }
88 } 99 }
89 100 launch {
90 games.observe(viewLifecycleOwner) { filterAndSearch() } 101 repeatOnLifecycle(Lifecycle.State.CREATED) {
91 searchedGames.observe(viewLifecycleOwner) { 102 gamesViewModel.games.collect { filterAndSearch() }
92 (binding.gridGamesSearch.adapter as GameAdapter).submitList(it) 103 }
93 if (it.isEmpty()) { 104 }
94 binding.noResultsView.visibility = View.VISIBLE 105 launch {
95 } else { 106 repeatOnLifecycle(Lifecycle.State.CREATED) {
96 binding.noResultsView.visibility = View.GONE 107 gamesViewModel.searchedGames.collect {
108 (binding.gridGamesSearch.adapter as GameAdapter).submitList(it)
109 if (it.isEmpty()) {
110 binding.noResultsView.visibility = View.VISIBLE
111 } else {
112 binding.noResultsView.visibility = View.GONE
113 }
114 }
97 } 115 }
98 } 116 }
99 } 117 }
@@ -109,7 +127,7 @@ class SearchFragment : Fragment() {
109 private inner class ScoredGame(val score: Double, val item: Game) 127 private inner class ScoredGame(val score: Double, val item: Game)
110 128
111 private fun filterAndSearch() { 129 private fun filterAndSearch() {
112 val baseList = gamesViewModel.games.value!! 130 val baseList = gamesViewModel.games.value
113 val filteredList: List<Game> = when (binding.chipGroup.checkedChipId) { 131 val filteredList: List<Game> = when (binding.chipGroup.checkedChipId) {
114 R.id.chip_recently_played -> { 132 R.id.chip_recently_played -> {
115 baseList.filter { 133 baseList.filter {
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
index 55b6a0367..9d0594c6e 100644
--- 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
@@ -15,10 +15,14 @@ import androidx.core.view.updatePadding
15import androidx.core.widget.doOnTextChanged 15import androidx.core.widget.doOnTextChanged
16import androidx.fragment.app.Fragment 16import androidx.fragment.app.Fragment
17import androidx.fragment.app.activityViewModels 17import androidx.fragment.app.activityViewModels
18import androidx.lifecycle.Lifecycle
19import androidx.lifecycle.lifecycleScope
20import androidx.lifecycle.repeatOnLifecycle
18import androidx.recyclerview.widget.LinearLayoutManager 21import androidx.recyclerview.widget.LinearLayoutManager
19import com.google.android.material.divider.MaterialDividerItemDecoration 22import com.google.android.material.divider.MaterialDividerItemDecoration
20import com.google.android.material.transition.MaterialSharedAxis 23import com.google.android.material.transition.MaterialSharedAxis
21import info.debatty.java.stringsimilarity.Cosine 24import info.debatty.java.stringsimilarity.Cosine
25import kotlinx.coroutines.launch
22import org.yuzu.yuzu_emu.R 26import org.yuzu.yuzu_emu.R
23import org.yuzu.yuzu_emu.databinding.FragmentSettingsSearchBinding 27import org.yuzu.yuzu_emu.databinding.FragmentSettingsSearchBinding
24import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem 28import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
@@ -79,10 +83,14 @@ class SettingsSearchFragment : Fragment() {
79 search() 83 search()
80 binding.settingsList.smoothScrollToPosition(0) 84 binding.settingsList.smoothScrollToPosition(0)
81 } 85 }
82 settingsViewModel.shouldReloadSettingsList.observe(viewLifecycleOwner) { 86 viewLifecycleOwner.lifecycleScope.launch {
83 if (it) { 87 repeatOnLifecycle(Lifecycle.State.CREATED) {
84 settingsViewModel.setShouldReloadSettingsList(false) 88 settingsViewModel.shouldReloadSettingsList.collect {
85 search() 89 if (it) {
90 settingsViewModel.setShouldReloadSettingsList(false)
91 search()
92 }
93 }
86 } 94 }
87 } 95 }
88 96
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt
index d50c421a0..fbb2f6e18 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt
@@ -22,10 +22,14 @@ import androidx.core.view.isVisible
22import androidx.core.view.updatePadding 22import androidx.core.view.updatePadding
23import androidx.fragment.app.Fragment 23import androidx.fragment.app.Fragment
24import androidx.fragment.app.activityViewModels 24import androidx.fragment.app.activityViewModels
25import androidx.lifecycle.Lifecycle
26import androidx.lifecycle.lifecycleScope
27import androidx.lifecycle.repeatOnLifecycle
25import androidx.navigation.findNavController 28import androidx.navigation.findNavController
26import androidx.preference.PreferenceManager 29import androidx.preference.PreferenceManager
27import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback 30import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback
28import com.google.android.material.transition.MaterialFadeThrough 31import com.google.android.material.transition.MaterialFadeThrough
32import kotlinx.coroutines.launch
29import java.io.File 33import java.io.File
30import org.yuzu.yuzu_emu.R 34import org.yuzu.yuzu_emu.R
31import org.yuzu.yuzu_emu.YuzuApplication 35import org.yuzu.yuzu_emu.YuzuApplication
@@ -206,10 +210,14 @@ class SetupFragment : Fragment() {
206 ) 210 )
207 } 211 }
208 212
209 homeViewModel.shouldPageForward.observe(viewLifecycleOwner) { 213 viewLifecycleOwner.lifecycleScope.launch {
210 if (it) { 214 repeatOnLifecycle(Lifecycle.State.CREATED) {
211 pageForward() 215 homeViewModel.shouldPageForward.collect {
212 homeViewModel.setShouldPageForward(false) 216 if (it) {
217 pageForward()
218 homeViewModel.setShouldPageForward(false)
219 }
220 }
213 } 221 }
214 } 222 }
215 223
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
index e35f51bc3..f34870c2d 100644
--- 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
@@ -3,28 +3,28 @@
3 3
4package org.yuzu.yuzu_emu.model 4package org.yuzu.yuzu_emu.model
5 5
6import androidx.lifecycle.LiveData
7import androidx.lifecycle.MutableLiveData
8import androidx.lifecycle.ViewModel 6import androidx.lifecycle.ViewModel
7import kotlinx.coroutines.flow.MutableStateFlow
8import kotlinx.coroutines.flow.StateFlow
9 9
10class EmulationViewModel : ViewModel() { 10class EmulationViewModel : ViewModel() {
11 private val _emulationStarted = MutableLiveData(false) 11 val emulationStarted: StateFlow<Boolean> get() = _emulationStarted
12 val emulationStarted: LiveData<Boolean> get() = _emulationStarted 12 private val _emulationStarted = MutableStateFlow(false)
13 13
14 private val _isEmulationStopping = MutableLiveData(false) 14 val isEmulationStopping: StateFlow<Boolean> get() = _isEmulationStopping
15 val isEmulationStopping: LiveData<Boolean> get() = _isEmulationStopping 15 private val _isEmulationStopping = MutableStateFlow(false)
16 16
17 private val _shaderProgress = MutableLiveData(0) 17 val shaderProgress: StateFlow<Int> get() = _shaderProgress
18 val shaderProgress: LiveData<Int> get() = _shaderProgress 18 private val _shaderProgress = MutableStateFlow(0)
19 19
20 private val _totalShaders = MutableLiveData(0) 20 val totalShaders: StateFlow<Int> get() = _totalShaders
21 val totalShaders: LiveData<Int> get() = _totalShaders 21 private val _totalShaders = MutableStateFlow(0)
22 22
23 private val _shaderMessage = MutableLiveData("") 23 val shaderMessage: StateFlow<String> get() = _shaderMessage
24 val shaderMessage: LiveData<String> get() = _shaderMessage 24 private val _shaderMessage = MutableStateFlow("")
25 25
26 fun setEmulationStarted(started: Boolean) { 26 fun setEmulationStarted(started: Boolean) {
27 _emulationStarted.postValue(started) 27 _emulationStarted.value = started
28 } 28 }
29 29
30 fun setIsEmulationStopping(value: Boolean) { 30 fun setIsEmulationStopping(value: Boolean) {
@@ -50,10 +50,18 @@ class EmulationViewModel : ViewModel() {
50 } 50 }
51 51
52 fun clear() { 52 fun clear() {
53 _emulationStarted.value = false 53 setEmulationStarted(false)
54 _isEmulationStopping.value = false 54 setIsEmulationStopping(false)
55 _shaderProgress.value = 0 55 setShaderProgress(0)
56 _totalShaders.value = 0 56 setTotalShaders(0)
57 _shaderMessage.value = "" 57 setShaderMessage("")
58 }
59
60 companion object {
61 const val KEY_EMULATION_STARTED = "EmulationStarted"
62 const val KEY_IS_EMULATION_STOPPING = "IsEmulationStarting"
63 const val KEY_SHADER_PROGRESS = "ShaderProgress"
64 const val KEY_TOTAL_SHADERS = "TotalShaders"
65 const val KEY_SHADER_MESSAGE = "ShaderMessage"
58 } 66 }
59} 67}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt
index 1fe42f922..6e09fa81d 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt
@@ -5,13 +5,13 @@ package org.yuzu.yuzu_emu.model
5 5
6import android.net.Uri 6import android.net.Uri
7import androidx.documentfile.provider.DocumentFile 7import androidx.documentfile.provider.DocumentFile
8import androidx.lifecycle.LiveData
9import androidx.lifecycle.MutableLiveData
10import androidx.lifecycle.ViewModel 8import androidx.lifecycle.ViewModel
11import androidx.lifecycle.viewModelScope 9import androidx.lifecycle.viewModelScope
12import androidx.preference.PreferenceManager 10import androidx.preference.PreferenceManager
13import java.util.Locale 11import java.util.Locale
14import kotlinx.coroutines.Dispatchers 12import kotlinx.coroutines.Dispatchers
13import kotlinx.coroutines.flow.MutableStateFlow
14import kotlinx.coroutines.flow.StateFlow
15import kotlinx.coroutines.launch 15import kotlinx.coroutines.launch
16import kotlinx.coroutines.withContext 16import kotlinx.coroutines.withContext
17import kotlinx.serialization.ExperimentalSerializationApi 17import kotlinx.serialization.ExperimentalSerializationApi
@@ -24,23 +24,23 @@ import org.yuzu.yuzu_emu.utils.GameHelper
24 24
25@OptIn(ExperimentalSerializationApi::class) 25@OptIn(ExperimentalSerializationApi::class)
26class GamesViewModel : ViewModel() { 26class GamesViewModel : ViewModel() {
27 private val _games = MutableLiveData<List<Game>>(emptyList()) 27 val games: StateFlow<List<Game>> get() = _games
28 val games: LiveData<List<Game>> get() = _games 28 private val _games = MutableStateFlow(emptyList<Game>())
29 29
30 private val _searchedGames = MutableLiveData<List<Game>>(emptyList()) 30 val searchedGames: StateFlow<List<Game>> get() = _searchedGames
31 val searchedGames: LiveData<List<Game>> get() = _searchedGames 31 private val _searchedGames = MutableStateFlow(emptyList<Game>())
32 32
33 private val _isReloading = MutableLiveData(false) 33 val isReloading: StateFlow<Boolean> get() = _isReloading
34 val isReloading: LiveData<Boolean> get() = _isReloading 34 private val _isReloading = MutableStateFlow(false)
35 35
36 private val _shouldSwapData = MutableLiveData(false) 36 val shouldSwapData: StateFlow<Boolean> get() = _shouldSwapData
37 val shouldSwapData: LiveData<Boolean> get() = _shouldSwapData 37 private val _shouldSwapData = MutableStateFlow(false)
38 38
39 private val _shouldScrollToTop = MutableLiveData(false) 39 val shouldScrollToTop: StateFlow<Boolean> get() = _shouldScrollToTop
40 val shouldScrollToTop: LiveData<Boolean> get() = _shouldScrollToTop 40 private val _shouldScrollToTop = MutableStateFlow(false)
41 41
42 private val _searchFocused = MutableLiveData(false) 42 val searchFocused: StateFlow<Boolean> get() = _searchFocused
43 val searchFocused: LiveData<Boolean> get() = _searchFocused 43 private val _searchFocused = MutableStateFlow(false)
44 44
45 init { 45 init {
46 // Ensure keys are loaded so that ROM metadata can be decrypted. 46 // Ensure keys are loaded so that ROM metadata can be decrypted.
@@ -79,36 +79,36 @@ class GamesViewModel : ViewModel() {
79 ) 79 )
80 ) 80 )
81 81
82 _games.postValue(sortedList) 82 _games.value = sortedList
83 } 83 }
84 84
85 fun setSearchedGames(games: List<Game>) { 85 fun setSearchedGames(games: List<Game>) {
86 _searchedGames.postValue(games) 86 _searchedGames.value = games
87 } 87 }
88 88
89 fun setShouldSwapData(shouldSwap: Boolean) { 89 fun setShouldSwapData(shouldSwap: Boolean) {
90 _shouldSwapData.postValue(shouldSwap) 90 _shouldSwapData.value = shouldSwap
91 } 91 }
92 92
93 fun setShouldScrollToTop(shouldScroll: Boolean) { 93 fun setShouldScrollToTop(shouldScroll: Boolean) {
94 _shouldScrollToTop.postValue(shouldScroll) 94 _shouldScrollToTop.value = shouldScroll
95 } 95 }
96 96
97 fun setSearchFocused(searchFocused: Boolean) { 97 fun setSearchFocused(searchFocused: Boolean) {
98 _searchFocused.postValue(searchFocused) 98 _searchFocused.value = searchFocused
99 } 99 }
100 100
101 fun reloadGames(directoryChanged: Boolean) { 101 fun reloadGames(directoryChanged: Boolean) {
102 if (isReloading.value == true) { 102 if (isReloading.value) {
103 return 103 return
104 } 104 }
105 _isReloading.postValue(true) 105 _isReloading.value = true
106 106
107 viewModelScope.launch { 107 viewModelScope.launch {
108 withContext(Dispatchers.IO) { 108 withContext(Dispatchers.IO) {
109 NativeLibrary.resetRomMetadata() 109 NativeLibrary.resetRomMetadata()
110 setGames(GameHelper.getGames()) 110 setGames(GameHelper.getGames())
111 _isReloading.postValue(false) 111 _isReloading.value = false
112 112
113 if (directoryChanged) { 113 if (directoryChanged) {
114 setShouldSwapData(true) 114 setShouldSwapData(true)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeSetting.kt
index 498c222fa..b32e19373 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeSetting.kt
@@ -3,8 +3,8 @@
3 3
4package org.yuzu.yuzu_emu.model 4package org.yuzu.yuzu_emu.model
5 5
6import androidx.lifecycle.LiveData 6import kotlinx.coroutines.flow.MutableStateFlow
7import androidx.lifecycle.MutableLiveData 7import kotlinx.coroutines.flow.StateFlow
8 8
9data class HomeSetting( 9data class HomeSetting(
10 val titleId: Int, 10 val titleId: Int,
@@ -14,5 +14,5 @@ data class HomeSetting(
14 val isEnabled: () -> Boolean = { true }, 14 val isEnabled: () -> Boolean = { true },
15 val disabledTitleId: Int = 0, 15 val disabledTitleId: Int = 0,
16 val disabledMessageId: Int = 0, 16 val disabledMessageId: Int = 0,
17 val details: LiveData<String> = MutableLiveData("") 17 val details: StateFlow<String> = MutableStateFlow("")
18) 18)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt
index a48ef7a88..756f76721 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt
@@ -5,47 +5,43 @@ package org.yuzu.yuzu_emu.model
5 5
6import android.net.Uri 6import android.net.Uri
7import androidx.fragment.app.FragmentActivity 7import androidx.fragment.app.FragmentActivity
8import androidx.lifecycle.LiveData
9import androidx.lifecycle.MutableLiveData
10import androidx.lifecycle.ViewModel 8import androidx.lifecycle.ViewModel
11import androidx.lifecycle.ViewModelProvider 9import androidx.lifecycle.ViewModelProvider
12import androidx.preference.PreferenceManager 10import androidx.preference.PreferenceManager
11import kotlinx.coroutines.flow.MutableStateFlow
12import kotlinx.coroutines.flow.StateFlow
13import org.yuzu.yuzu_emu.YuzuApplication 13import org.yuzu.yuzu_emu.YuzuApplication
14import org.yuzu.yuzu_emu.utils.GameHelper 14import org.yuzu.yuzu_emu.utils.GameHelper
15 15
16class HomeViewModel : ViewModel() { 16class HomeViewModel : ViewModel() {
17 private val _navigationVisible = MutableLiveData<Pair<Boolean, Boolean>>() 17 val navigationVisible: StateFlow<Pair<Boolean, Boolean>> get() = _navigationVisible
18 val navigationVisible: LiveData<Pair<Boolean, Boolean>> get() = _navigationVisible 18 private val _navigationVisible = MutableStateFlow(Pair(false, false))
19 19
20 private val _statusBarShadeVisible = MutableLiveData(true) 20 val statusBarShadeVisible: StateFlow<Boolean> get() = _statusBarShadeVisible
21 val statusBarShadeVisible: LiveData<Boolean> get() = _statusBarShadeVisible 21 private val _statusBarShadeVisible = MutableStateFlow(true)
22 22
23 private val _shouldPageForward = MutableLiveData(false) 23 val shouldPageForward: StateFlow<Boolean> get() = _shouldPageForward
24 val shouldPageForward: LiveData<Boolean> get() = _shouldPageForward 24 private val _shouldPageForward = MutableStateFlow(false)
25 25
26 private val _gamesDir = MutableLiveData( 26 val gamesDir: StateFlow<String> get() = _gamesDir
27 private val _gamesDir = MutableStateFlow(
27 Uri.parse( 28 Uri.parse(
28 PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) 29 PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
29 .getString(GameHelper.KEY_GAME_PATH, "") 30 .getString(GameHelper.KEY_GAME_PATH, "")
30 ).path ?: "" 31 ).path ?: ""
31 ) 32 )
32 val gamesDir: LiveData<String> get() = _gamesDir
33 33
34 var navigatedToSetup = false 34 var navigatedToSetup = false
35 35
36 init {
37 _navigationVisible.value = Pair(false, false)
38 }
39
40 fun setNavigationVisibility(visible: Boolean, animated: Boolean) { 36 fun setNavigationVisibility(visible: Boolean, animated: Boolean) {
41 if (_navigationVisible.value?.first == visible) { 37 if (navigationVisible.value.first == visible) {
42 return 38 return
43 } 39 }
44 _navigationVisible.value = Pair(visible, animated) 40 _navigationVisible.value = Pair(visible, animated)
45 } 41 }
46 42
47 fun setStatusBarShadeVisibility(visible: Boolean) { 43 fun setStatusBarShadeVisibility(visible: Boolean) {
48 if (_statusBarShadeVisible.value == visible) { 44 if (statusBarShadeVisible.value == visible) {
49 return 45 return
50 } 46 }
51 _statusBarShadeVisible.value = visible 47 _statusBarShadeVisible.value = visible
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt
index d16d15fa6..53fa7a8de 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt
@@ -3,48 +3,43 @@
3 3
4package org.yuzu.yuzu_emu.model 4package org.yuzu.yuzu_emu.model
5 5
6import androidx.lifecycle.LiveData
7import androidx.lifecycle.MutableLiveData
8import androidx.lifecycle.SavedStateHandle
9import androidx.lifecycle.ViewModel 6import androidx.lifecycle.ViewModel
7import kotlinx.coroutines.flow.MutableStateFlow
8import kotlinx.coroutines.flow.StateFlow
10import org.yuzu.yuzu_emu.R 9import org.yuzu.yuzu_emu.R
11import org.yuzu.yuzu_emu.YuzuApplication 10import org.yuzu.yuzu_emu.YuzuApplication
12import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem 11import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
13 12
14class SettingsViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() { 13class SettingsViewModel : ViewModel() {
15 var game: Game? = null 14 var game: Game? = null
16 15
17 var shouldSave = false 16 var shouldSave = false
18 17
19 var clickedItem: SettingsItem? = null 18 var clickedItem: SettingsItem? = null
20 19
21 private val _toolbarTitle = MutableLiveData("") 20 val shouldRecreate: StateFlow<Boolean> get() = _shouldRecreate
22 val toolbarTitle: LiveData<String> get() = _toolbarTitle 21 private val _shouldRecreate = MutableStateFlow(false)
23 22
24 private val _shouldRecreate = MutableLiveData(false) 23 val shouldNavigateBack: StateFlow<Boolean> get() = _shouldNavigateBack
25 val shouldRecreate: LiveData<Boolean> get() = _shouldRecreate 24 private val _shouldNavigateBack = MutableStateFlow(false)
26 25
27 private val _shouldNavigateBack = MutableLiveData(false) 26 val shouldShowResetSettingsDialog: StateFlow<Boolean> get() = _shouldShowResetSettingsDialog
28 val shouldNavigateBack: LiveData<Boolean> get() = _shouldNavigateBack 27 private val _shouldShowResetSettingsDialog = MutableStateFlow(false)
29 28
30 private val _shouldShowResetSettingsDialog = MutableLiveData(false) 29 val shouldReloadSettingsList: StateFlow<Boolean> get() = _shouldReloadSettingsList
31 val shouldShowResetSettingsDialog: LiveData<Boolean> get() = _shouldShowResetSettingsDialog 30 private val _shouldReloadSettingsList = MutableStateFlow(false)
32 31
33 private val _shouldReloadSettingsList = MutableLiveData(false) 32 val isUsingSearch: StateFlow<Boolean> get() = _isUsingSearch
34 val shouldReloadSettingsList: LiveData<Boolean> get() = _shouldReloadSettingsList 33 private val _isUsingSearch = MutableStateFlow(false)
35 34
36 private val _isUsingSearch = MutableLiveData(false) 35 val sliderProgress: StateFlow<Int> get() = _sliderProgress
37 val isUsingSearch: LiveData<Boolean> get() = _isUsingSearch 36 private val _sliderProgress = MutableStateFlow(-1)
38 37
39 val sliderProgress = savedStateHandle.getStateFlow(KEY_SLIDER_PROGRESS, -1) 38 val sliderTextValue: StateFlow<String> get() = _sliderTextValue
39 private val _sliderTextValue = MutableStateFlow("")
40 40
41 val sliderTextValue = savedStateHandle.getStateFlow(KEY_SLIDER_TEXT_VALUE, "") 41 val adapterItemChanged: StateFlow<Int> get() = _adapterItemChanged
42 42 private val _adapterItemChanged = MutableStateFlow(-1)
43 val adapterItemChanged = savedStateHandle.getStateFlow(KEY_ADAPTER_ITEM_CHANGED, -1)
44
45 fun setToolbarTitle(value: String) {
46 _toolbarTitle.value = value
47 }
48 43
49 fun setShouldRecreate(value: Boolean) { 44 fun setShouldRecreate(value: Boolean) {
50 _shouldRecreate.value = value 45 _shouldRecreate.value = value
@@ -67,8 +62,8 @@ class SettingsViewModel(private val savedStateHandle: SavedStateHandle) : ViewMo
67 } 62 }
68 63
69 fun setSliderTextValue(value: Float, units: String) { 64 fun setSliderTextValue(value: Float, units: String) {
70 savedStateHandle[KEY_SLIDER_PROGRESS] = value 65 _sliderProgress.value = value.toInt()
71 savedStateHandle[KEY_SLIDER_TEXT_VALUE] = String.format( 66 _sliderTextValue.value = String.format(
72 YuzuApplication.appContext.getString(R.string.value_with_units), 67 YuzuApplication.appContext.getString(R.string.value_with_units),
73 value.toInt().toString(), 68 value.toInt().toString(),
74 units 69 units
@@ -76,21 +71,15 @@ class SettingsViewModel(private val savedStateHandle: SavedStateHandle) : ViewMo
76 } 71 }
77 72
78 fun setSliderProgress(value: Float) { 73 fun setSliderProgress(value: Float) {
79 savedStateHandle[KEY_SLIDER_PROGRESS] = value 74 _sliderProgress.value = value.toInt()
80 } 75 }
81 76
82 fun setAdapterItemChanged(value: Int) { 77 fun setAdapterItemChanged(value: Int) {
83 savedStateHandle[KEY_ADAPTER_ITEM_CHANGED] = value 78 _adapterItemChanged.value = value
84 } 79 }
85 80
86 fun clear() { 81 fun clear() {
87 game = null 82 game = null
88 shouldSave = false 83 shouldSave = false
89 } 84 }
90
91 companion object {
92 const val KEY_SLIDER_TEXT_VALUE = "SliderTextValue"
93 const val KEY_SLIDER_PROGRESS = "SliderProgress"
94 const val KEY_ADAPTER_ITEM_CHANGED = "AdapterItemChanged"
95 }
96} 85}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt
index 27ea725a5..531c2aaf0 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt
@@ -3,29 +3,25 @@
3 3
4package org.yuzu.yuzu_emu.model 4package org.yuzu.yuzu_emu.model
5 5
6import androidx.lifecycle.LiveData
7import androidx.lifecycle.MutableLiveData
8import androidx.lifecycle.ViewModel 6import androidx.lifecycle.ViewModel
9import androidx.lifecycle.viewModelScope 7import androidx.lifecycle.viewModelScope
10import kotlinx.coroutines.Dispatchers 8import kotlinx.coroutines.Dispatchers
9import kotlinx.coroutines.flow.MutableStateFlow
10import kotlinx.coroutines.flow.StateFlow
11import kotlinx.coroutines.launch 11import kotlinx.coroutines.launch
12 12
13class TaskViewModel : ViewModel() { 13class TaskViewModel : ViewModel() {
14 private val _result = MutableLiveData<Any>() 14 val result: StateFlow<Any> get() = _result
15 val result: LiveData<Any> = _result 15 private val _result = MutableStateFlow(Any())
16 16
17 private val _isComplete = MutableLiveData<Boolean>() 17 val isComplete: StateFlow<Boolean> get() = _isComplete
18 val isComplete: LiveData<Boolean> = _isComplete 18 private val _isComplete = MutableStateFlow(false)
19 19
20 private val _isRunning = MutableLiveData<Boolean>() 20 val isRunning: StateFlow<Boolean> get() = _isRunning
21 val isRunning: LiveData<Boolean> = _isRunning 21 private val _isRunning = MutableStateFlow(false)
22 22
23 lateinit var task: () -> Any 23 lateinit var task: () -> Any
24 24
25 init {
26 clear()
27 }
28
29 fun clear() { 25 fun clear() {
30 _result.value = Any() 26 _result.value = Any()
31 _isComplete.value = false 27 _isComplete.value = false
@@ -33,15 +29,16 @@ class TaskViewModel : ViewModel() {
33 } 29 }
34 30
35 fun runTask() { 31 fun runTask() {
36 if (_isRunning.value == true) { 32 if (isRunning.value) {
37 return 33 return
38 } 34 }
39 _isRunning.value = true 35 _isRunning.value = true
40 36
41 viewModelScope.launch(Dispatchers.IO) { 37 viewModelScope.launch(Dispatchers.IO) {
42 val res = task() 38 val res = task()
43 _result.postValue(res) 39 _result.value = res
44 _isComplete.postValue(true) 40 _isComplete.value = true
41 _isRunning.value = false
45 } 42 }
46 } 43 }
47} 44}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt
index b0156dca5..805b89b31 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt
@@ -3,6 +3,7 @@
3 3
4package org.yuzu.yuzu_emu.ui 4package org.yuzu.yuzu_emu.ui
5 5
6import android.annotation.SuppressLint
6import android.os.Bundle 7import android.os.Bundle
7import android.view.LayoutInflater 8import android.view.LayoutInflater
8import android.view.View 9import android.view.View
@@ -14,8 +15,12 @@ import androidx.core.view.WindowInsetsCompat
14import androidx.core.view.updatePadding 15import androidx.core.view.updatePadding
15import androidx.fragment.app.Fragment 16import androidx.fragment.app.Fragment
16import androidx.fragment.app.activityViewModels 17import androidx.fragment.app.activityViewModels
18import androidx.lifecycle.Lifecycle
19import androidx.lifecycle.lifecycleScope
20import androidx.lifecycle.repeatOnLifecycle
17import com.google.android.material.color.MaterialColors 21import com.google.android.material.color.MaterialColors
18import com.google.android.material.transition.MaterialFadeThrough 22import com.google.android.material.transition.MaterialFadeThrough
23import kotlinx.coroutines.launch
19import org.yuzu.yuzu_emu.R 24import org.yuzu.yuzu_emu.R
20import org.yuzu.yuzu_emu.adapters.GameAdapter 25import org.yuzu.yuzu_emu.adapters.GameAdapter
21import org.yuzu.yuzu_emu.databinding.FragmentGamesBinding 26import org.yuzu.yuzu_emu.databinding.FragmentGamesBinding
@@ -44,6 +49,8 @@ class GamesFragment : Fragment() {
44 return binding.root 49 return binding.root
45 } 50 }
46 51
52 // This is using the correct scope, lint is just acting up
53 @SuppressLint("UnsafeRepeatOnLifecycleDetector")
47 override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 54 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
48 homeViewModel.setNavigationVisibility(visible = true, animated = false) 55 homeViewModel.setNavigationVisibility(visible = true, animated = false)
49 56
@@ -80,37 +87,48 @@ class GamesFragment : Fragment() {
80 if (_binding == null) { 87 if (_binding == null) {
81 return@post 88 return@post
82 } 89 }
83 binding.swipeRefresh.isRefreshing = gamesViewModel.isReloading.value!! 90 binding.swipeRefresh.isRefreshing = gamesViewModel.isReloading.value
84 } 91 }
85 } 92 }
86 93
87 gamesViewModel.apply { 94 viewLifecycleOwner.lifecycleScope.apply {
88 // Watch for when we get updates to any of our games lists 95 launch {
89 isReloading.observe(viewLifecycleOwner) { isReloading -> 96 repeatOnLifecycle(Lifecycle.State.RESUMED) {
90 binding.swipeRefresh.isRefreshing = isReloading 97 gamesViewModel.isReloading.collect { binding.swipeRefresh.isRefreshing = it }
98 }
91 } 99 }
92 games.observe(viewLifecycleOwner) { 100 launch {
93 (binding.gridGames.adapter as GameAdapter).submitList(it) 101 repeatOnLifecycle(Lifecycle.State.RESUMED) {
94 if (it.isEmpty()) { 102 gamesViewModel.games.collect {
95 binding.noticeText.visibility = View.VISIBLE 103 (binding.gridGames.adapter as GameAdapter).submitList(it)
96 } else { 104 if (it.isEmpty()) {
97 binding.noticeText.visibility = View.GONE 105 binding.noticeText.visibility = View.VISIBLE
106 } else {
107 binding.noticeText.visibility = View.GONE
108 }
109 }
98 } 110 }
99 } 111 }
100 shouldSwapData.observe(viewLifecycleOwner) { shouldSwapData -> 112 launch {
101 if (shouldSwapData) { 113 repeatOnLifecycle(Lifecycle.State.RESUMED) {
102 (binding.gridGames.adapter as GameAdapter).submitList( 114 gamesViewModel.shouldSwapData.collect {
103 gamesViewModel.games.value!! 115 if (it) {
104 ) 116 (binding.gridGames.adapter as GameAdapter).submitList(
105 gamesViewModel.setShouldSwapData(false) 117 gamesViewModel.games.value
118 )
119 gamesViewModel.setShouldSwapData(false)
120 }
121 }
106 } 122 }
107 } 123 }
108 124 launch {
109 // Check if the user reselected the games menu item and then scroll to top of the list 125 repeatOnLifecycle(Lifecycle.State.RESUMED) {
110 shouldScrollToTop.observe(viewLifecycleOwner) { shouldScroll -> 126 gamesViewModel.shouldScrollToTop.collect {
111 if (shouldScroll) { 127 if (it) {
112 scrollToTop() 128 scrollToTop()
113 gamesViewModel.setShouldScrollToTop(false) 129 gamesViewModel.setShouldScrollToTop(false)
130 }
131 }
114 } 132 }
115 } 133 }
116 } 134 }
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 7d8e06ad8..b6b6c6c17 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
@@ -19,7 +19,9 @@ import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
19import androidx.core.view.ViewCompat 19import androidx.core.view.ViewCompat
20import androidx.core.view.WindowCompat 20import androidx.core.view.WindowCompat
21import androidx.core.view.WindowInsetsCompat 21import androidx.core.view.WindowInsetsCompat
22import androidx.lifecycle.Lifecycle
22import androidx.lifecycle.lifecycleScope 23import androidx.lifecycle.lifecycleScope
24import androidx.lifecycle.repeatOnLifecycle
23import androidx.navigation.NavController 25import androidx.navigation.NavController
24import androidx.navigation.fragment.NavHostFragment 26import androidx.navigation.fragment.NavHostFragment
25import androidx.navigation.ui.setupWithNavController 27import androidx.navigation.ui.setupWithNavController
@@ -40,7 +42,6 @@ import org.yuzu.yuzu_emu.activities.EmulationActivity
40import org.yuzu.yuzu_emu.databinding.ActivityMainBinding 42import org.yuzu.yuzu_emu.databinding.ActivityMainBinding
41import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding 43import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
42import org.yuzu.yuzu_emu.features.settings.model.Settings 44import org.yuzu.yuzu_emu.features.settings.model.Settings
43import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
44import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment 45import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment
45import org.yuzu.yuzu_emu.fragments.MessageDialogFragment 46import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
46import org.yuzu.yuzu_emu.model.GamesViewModel 47import org.yuzu.yuzu_emu.model.GamesViewModel
@@ -107,7 +108,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
107 R.id.homeSettingsFragment -> { 108 R.id.homeSettingsFragment -> {
108 val action = HomeNavigationDirections.actionGlobalSettingsActivity( 109 val action = HomeNavigationDirections.actionGlobalSettingsActivity(
109 null, 110 null,
110 SettingsFile.FILE_NAME_CONFIG 111 Settings.MenuTag.SECTION_ROOT
111 ) 112 )
112 navHostFragment.navController.navigate(action) 113 navHostFragment.navController.navigate(action)
113 } 114 }
@@ -115,16 +116,22 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
115 } 116 }
116 117
117 // Prevents navigation from being drawn for a short time on recreation if set to hidden 118 // Prevents navigation from being drawn for a short time on recreation if set to hidden
118 if (!homeViewModel.navigationVisible.value?.first!!) { 119 if (!homeViewModel.navigationVisible.value.first) {
119 binding.navigationView.visibility = View.INVISIBLE 120 binding.navigationView.visibility = View.INVISIBLE
120 binding.statusBarShade.visibility = View.INVISIBLE 121 binding.statusBarShade.visibility = View.INVISIBLE
121 } 122 }
122 123
123 homeViewModel.navigationVisible.observe(this) { 124 lifecycleScope.apply {
124 showNavigation(it.first, it.second) 125 launch {
125 } 126 repeatOnLifecycle(Lifecycle.State.CREATED) {
126 homeViewModel.statusBarShadeVisible.observe(this) { visible -> 127 homeViewModel.navigationVisible.collect { showNavigation(it.first, it.second) }
127 showStatusBarShade(visible) 128 }
129 }
130 launch {
131 repeatOnLifecycle(Lifecycle.State.CREATED) {
132 homeViewModel.statusBarShadeVisible.collect { showStatusBarShade(it) }
133 }
134 }
128 } 135 }
129 136
130 // Dismiss previous notifications (should not happen unless a crash occurred) 137 // Dismiss previous notifications (should not happen unless a crash occurred)
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
index c0fe596d7..9fe99fab1 100644
--- 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
@@ -6,9 +6,11 @@ package org.yuzu.yuzu_emu.utils
6import android.graphics.Bitmap 6import android.graphics.Bitmap
7import android.graphics.BitmapFactory 7import android.graphics.BitmapFactory
8import android.widget.ImageView 8import android.widget.ImageView
9import androidx.core.graphics.drawable.toBitmap
9import androidx.core.graphics.drawable.toDrawable 10import androidx.core.graphics.drawable.toDrawable
10import coil.ImageLoader 11import coil.ImageLoader
11import coil.decode.DataSource 12import coil.decode.DataSource
13import coil.executeBlocking
12import coil.fetch.DrawableResult 14import coil.fetch.DrawableResult
13import coil.fetch.FetchResult 15import coil.fetch.FetchResult
14import coil.fetch.Fetcher 16import coil.fetch.Fetcher
@@ -74,4 +76,13 @@ object GameIconUtils {
74 .build() 76 .build()
75 imageLoader.enqueue(request) 77 imageLoader.enqueue(request)
76 } 78 }
79
80 fun getGameIcon(game: Game): Bitmap {
81 val request = ImageRequest.Builder(YuzuApplication.appContext)
82 .data(game)
83 .error(R.drawable.default_icon)
84 .build()
85 return imageLoader.executeBlocking(request)
86 .drawable!!.toBitmap(config = Bitmap.Config.ARGB_8888)
87 }
77} 88}
diff --git a/src/android/app/src/main/jni/config.cpp b/src/android/app/src/main/jni/config.cpp
index 34b425cb4..81120ab0f 100644
--- a/src/android/app/src/main/jni/config.cpp
+++ b/src/android/app/src/main/jni/config.cpp
@@ -282,7 +282,7 @@ void Config::ReadValues() {
282 std::stringstream ss(title_list); 282 std::stringstream ss(title_list);
283 std::string line; 283 std::string line;
284 while (std::getline(ss, line, '|')) { 284 while (std::getline(ss, line, '|')) {
285 const auto title_id = std::stoul(line, nullptr, 16); 285 const auto title_id = std::strtoul(line.c_str(), nullptr, 16);
286 const auto disabled_list = config->Get("AddOns", "disabled_" + line, ""); 286 const auto disabled_list = config->Get("AddOns", "disabled_" + line, "");
287 287
288 std::stringstream inner_ss(disabled_list); 288 std::stringstream inner_ss(disabled_list);
diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp
index b9ecefa74..f31fe054b 100644
--- a/src/android/app/src/main/jni/native.cpp
+++ b/src/android/app/src/main/jni/native.cpp
@@ -262,9 +262,6 @@ public:
262 Core::SystemResultStatus InitializeEmulation(const std::string& filepath) { 262 Core::SystemResultStatus InitializeEmulation(const std::string& filepath) {
263 std::scoped_lock lock(m_mutex); 263 std::scoped_lock lock(m_mutex);
264 264
265 // Loads the configuration.
266 Config{};
267
268 // Create the render window. 265 // Create the render window.
269 m_window = std::make_unique<EmuWindow_Android>(&m_input_subsystem, m_native_window, 266 m_window = std::make_unique<EmuWindow_Android>(&m_input_subsystem, m_native_window,
270 m_vulkan_library); 267 m_vulkan_library);
@@ -330,12 +327,13 @@ public:
330 m_system.ShutdownMainProcess(); 327 m_system.ShutdownMainProcess();
331 m_detached_tasks.WaitForAllTasks(); 328 m_detached_tasks.WaitForAllTasks();
332 m_load_result = Core::SystemResultStatus::ErrorNotInitialized; 329 m_load_result = Core::SystemResultStatus::ErrorNotInitialized;
330 m_window.reset();
331 OnEmulationStopped(Core::SystemResultStatus::Success);
332 return;
333 } 333 }
334 334
335 // Tear down the render window. 335 // Tear down the render window.
336 m_window.reset(); 336 m_window.reset();
337
338 OnEmulationStopped(m_load_result);
339 } 337 }
340 338
341 void PauseEmulation() { 339 void PauseEmulation() {
@@ -672,18 +670,6 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isPaused(JNIEnv* env, jclass claz
672 return static_cast<jboolean>(EmulationSession::GetInstance().IsPaused()); 670 return static_cast<jboolean>(EmulationSession::GetInstance().IsPaused());
673} 671}
674 672
675void Java_org_yuzu_yuzu_1emu_NativeLibrary_muteAduio(JNIEnv* env, jclass clazz) {
676 Settings::values.audio_muted = true;
677}
678
679void Java_org_yuzu_yuzu_1emu_NativeLibrary_unmuteAudio(JNIEnv* env, jclass clazz) {
680 Settings::values.audio_muted = false;
681}
682
683jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isMuted(JNIEnv* env, jclass clazz) {
684 return static_cast<jboolean>(Settings::values.audio_muted.GetValue());
685}
686
687jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isHandheldOnly(JNIEnv* env, jclass clazz) { 673jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isHandheldOnly(JNIEnv* env, jclass clazz) {
688 return EmulationSession::GetInstance().IsHandheldOnly(); 674 return EmulationSession::GetInstance().IsHandheldOnly();
689} 675}
diff --git a/src/android/app/src/main/res/drawable/shortcut.xml b/src/android/app/src/main/res/drawable/shortcut.xml
new file mode 100644
index 000000000..c749e5d72
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/shortcut.xml
@@ -0,0 +1,11 @@
1<?xml version="1.0" encoding="utf-8"?>
2<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
3
4 <item>
5 <color android:color="@android:color/white" />
6 </item>
7 <item android:id="@+id/shortcut_foreground">
8 <bitmap android:src="@drawable/default_icon" />
9 </item>
10
11</layer-list>
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 c7be37f9b..cfc494b3f 100644
--- a/src/android/app/src/main/res/navigation/emulation_navigation.xml
+++ b/src/android/app/src/main/res/navigation/emulation_navigation.xml
@@ -27,7 +27,7 @@
27 app:nullable="true" /> 27 app:nullable="true" />
28 <argument 28 <argument
29 android:name="menuTag" 29 android:name="menuTag"
30 app:argType="string" /> 30 app:argType="org.yuzu.yuzu_emu.features.settings.model.Settings$MenuTag" />
31 </activity> 31 </activity>
32 32
33 <action 33 <action
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 2085430bf..2e0ce7a3d 100644
--- a/src/android/app/src/main/res/navigation/home_navigation.xml
+++ b/src/android/app/src/main/res/navigation/home_navigation.xml
@@ -82,7 +82,7 @@
82 app:nullable="true" /> 82 app:nullable="true" />
83 <argument 83 <argument
84 android:name="menuTag" 84 android:name="menuTag"
85 app:argType="string" /> 85 app:argType="org.yuzu.yuzu_emu.features.settings.model.Settings$MenuTag" />
86 </activity> 86 </activity>
87 87
88 <action 88 <action
diff --git a/src/android/app/src/main/res/navigation/settings_navigation.xml b/src/android/app/src/main/res/navigation/settings_navigation.xml
index 88e1b4587..1d87d36b3 100644
--- a/src/android/app/src/main/res/navigation/settings_navigation.xml
+++ b/src/android/app/src/main/res/navigation/settings_navigation.xml
@@ -10,7 +10,7 @@
10 android:label="SettingsFragment"> 10 android:label="SettingsFragment">
11 <argument 11 <argument
12 android:name="menuTag" 12 android:name="menuTag"
13 app:argType="string" /> 13 app:argType="org.yuzu.yuzu_emu.features.settings.model.Settings$MenuTag" />
14 <argument 14 <argument
15 android:name="game" 15 android:name="game"
16 app:argType="org.yuzu.yuzu_emu.model.Game" 16 app:argType="org.yuzu.yuzu_emu.model.Game"
diff --git a/src/android/app/src/main/res/values/dimens.xml b/src/android/app/src/main/res/values/dimens.xml
index 00757e5e8..7b2296d95 100644
--- a/src/android/app/src/main/res/values/dimens.xml
+++ b/src/android/app/src/main/res/values/dimens.xml
@@ -12,6 +12,7 @@
12 <dimen name="spacing_refresh_end">72dp</dimen> 12 <dimen name="spacing_refresh_end">72dp</dimen>
13 <dimen name="menu_width">256dp</dimen> 13 <dimen name="menu_width">256dp</dimen>
14 <dimen name="card_width">165dp</dimen> 14 <dimen name="card_width">165dp</dimen>
15 <dimen name="icon_inset">24dp</dimen>
15 16
16 <dimen name="dialog_margin">20dp</dimen> 17 <dimen name="dialog_margin">20dp</dimen>
17 <dimen name="elevated_app_bar">3dp</dimen> 18 <dimen name="elevated_app_bar">3dp</dimen>
diff --git a/src/common/settings.h b/src/common/settings.h
index b15213bd7..82ec9077e 100644
--- a/src/common/settings.h
+++ b/src/common/settings.h
@@ -348,6 +348,8 @@ struct Values {
348 Category::RendererDebug}; 348 Category::RendererDebug};
349 Setting<bool> disable_shader_loop_safety_checks{ 349 Setting<bool> disable_shader_loop_safety_checks{
350 linkage, false, "disable_shader_loop_safety_checks", Category::RendererDebug}; 350 linkage, false, "disable_shader_loop_safety_checks", Category::RendererDebug};
351 Setting<bool> enable_renderdoc_hotkey{linkage, false, "renderdoc_hotkey",
352 Category::RendererDebug};
351 353
352 // System 354 // System
353 SwitchableSetting<Language, true> language_index{linkage, 355 SwitchableSetting<Language, true> language_index{linkage,
diff --git a/src/common/settings_common.h b/src/common/settings_common.h
index 5b170dfd5..1800ab10d 100644
--- a/src/common/settings_common.h
+++ b/src/common/settings_common.h
@@ -225,6 +225,16 @@ public:
225 */ 225 */
226 [[nodiscard]] virtual constexpr u32 EnumIndex() const = 0; 226 [[nodiscard]] virtual constexpr u32 EnumIndex() const = 0;
227 227
228 /**
229 * @returns True if the underlying type is a floating point storage
230 */
231 [[nodiscard]] virtual constexpr bool IsFloatingPoint() const = 0;
232
233 /**
234 * @returns True if the underlying type is an integer storage
235 */
236 [[nodiscard]] virtual constexpr bool IsIntegral() const = 0;
237
228 /* 238 /*
229 * Switchable settings 239 * Switchable settings
230 */ 240 */
diff --git a/src/common/settings_setting.h b/src/common/settings_setting.h
index e10843c73..7be6f26f7 100644
--- a/src/common/settings_setting.h
+++ b/src/common/settings_setting.h
@@ -10,6 +10,7 @@
10#include <string> 10#include <string>
11#include <typeindex> 11#include <typeindex>
12#include <typeinfo> 12#include <typeinfo>
13#include <fmt/core.h>
13#include "common/common_types.h" 14#include "common/common_types.h"
14#include "common/settings_common.h" 15#include "common/settings_common.h"
15#include "common/settings_enums.h" 16#include "common/settings_enums.h"
@@ -115,8 +116,12 @@ protected:
115 } else if constexpr (std::is_same_v<Type, AudioEngine>) { 116 } else if constexpr (std::is_same_v<Type, AudioEngine>) {
116 // Compatibility with old AudioEngine setting being a string 117 // Compatibility with old AudioEngine setting being a string
117 return CanonicalizeEnum(value_); 118 return CanonicalizeEnum(value_);
119 } else if constexpr (std::is_floating_point_v<Type>) {
120 return fmt::format("{:f}", value_);
121 } else if constexpr (std::is_enum_v<Type>) {
122 return std::to_string(static_cast<u32>(value_));
118 } else { 123 } else {
119 return std::to_string(static_cast<u64>(value_)); 124 return std::to_string(value_);
120 } 125 }
121 } 126 }
122 127
@@ -180,13 +185,15 @@ public:
180 this->SetValue(static_cast<u32>(std::stoul(input))); 185 this->SetValue(static_cast<u32>(std::stoul(input)));
181 } else if constexpr (std::is_same_v<Type, bool>) { 186 } else if constexpr (std::is_same_v<Type, bool>) {
182 this->SetValue(input == "true"); 187 this->SetValue(input == "true");
183 } else if constexpr (std::is_same_v<Type, AudioEngine>) { 188 } else if constexpr (std::is_same_v<Type, float>) {
184 this->SetValue(ToEnum<Type>(input)); 189 this->SetValue(std::stof(input));
185 } else { 190 } else {
186 this->SetValue(static_cast<Type>(std::stoll(input))); 191 this->SetValue(static_cast<Type>(std::stoll(input)));
187 } 192 }
188 } catch (std::invalid_argument&) { 193 } catch (std::invalid_argument&) {
189 this->SetValue(this->GetDefault()); 194 this->SetValue(this->GetDefault());
195 } catch (std::out_of_range&) {
196 this->SetValue(this->GetDefault());
190 } 197 }
191 } 198 }
192 199
@@ -215,11 +222,27 @@ public:
215 } 222 }
216 } 223 }
217 224
225 [[nodiscard]] constexpr bool IsFloatingPoint() const final {
226 return std::is_floating_point_v<Type>;
227 }
228
229 [[nodiscard]] constexpr bool IsIntegral() const final {
230 return std::is_integral_v<Type>;
231 }
232
218 [[nodiscard]] std::string MinVal() const override final { 233 [[nodiscard]] std::string MinVal() const override final {
219 return this->ToString(minimum); 234 if constexpr (std::is_arithmetic_v<Type> && !ranged) {
235 return this->ToString(std::numeric_limits<Type>::min());
236 } else {
237 return this->ToString(minimum);
238 }
220 } 239 }
221 [[nodiscard]] std::string MaxVal() const override final { 240 [[nodiscard]] std::string MaxVal() const override final {
222 return this->ToString(maximum); 241 if constexpr (std::is_arithmetic_v<Type> && !ranged) {
242 return this->ToString(std::numeric_limits<Type>::max());
243 } else {
244 return this->ToString(maximum);
245 }
223 } 246 }
224 247
225 [[nodiscard]] constexpr bool Ranged() const override { 248 [[nodiscard]] constexpr bool Ranged() const override {
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 6cd1a28f2..30d2f7df6 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -864,6 +864,8 @@ add_library(core STATIC
864 telemetry_session.h 864 telemetry_session.h
865 tools/freezer.cpp 865 tools/freezer.cpp
866 tools/freezer.h 866 tools/freezer.h
867 tools/renderdoc.cpp
868 tools/renderdoc.h
867) 869)
868 870
869if (MSVC) 871if (MSVC)
@@ -879,6 +881,7 @@ else()
879 -Werror=conversion 881 -Werror=conversion
880 882
881 -Wno-sign-conversion 883 -Wno-sign-conversion
884 -Wno-cast-function-type
882 885
883 $<$<CXX_COMPILER_ID:Clang>:-fsized-deallocation> 886 $<$<CXX_COMPILER_ID:Clang>:-fsized-deallocation>
884 ) 887 )
@@ -887,7 +890,7 @@ endif()
887create_target_directory_groups(core) 890create_target_directory_groups(core)
888 891
889target_link_libraries(core PUBLIC common PRIVATE audio_core network video_core nx_tzdb) 892target_link_libraries(core PUBLIC common PRIVATE audio_core network video_core nx_tzdb)
890target_link_libraries(core PUBLIC Boost::headers PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls Opus::opus) 893target_link_libraries(core PUBLIC Boost::headers PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls Opus::opus renderdoc)
891if (MINGW) 894if (MINGW)
892 target_link_libraries(core PRIVATE ${MSWSOCK_LIBRARY}) 895 target_link_libraries(core PRIVATE ${MSWSOCK_LIBRARY})
893endif() 896endif()
diff --git a/src/core/core.cpp b/src/core/core.cpp
index 2d6e61398..e8300cd05 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -51,6 +51,7 @@
51#include "core/reporter.h" 51#include "core/reporter.h"
52#include "core/telemetry_session.h" 52#include "core/telemetry_session.h"
53#include "core/tools/freezer.h" 53#include "core/tools/freezer.h"
54#include "core/tools/renderdoc.h"
54#include "network/network.h" 55#include "network/network.h"
55#include "video_core/host1x/host1x.h" 56#include "video_core/host1x/host1x.h"
56#include "video_core/renderer_base.h" 57#include "video_core/renderer_base.h"
@@ -281,6 +282,10 @@ struct System::Impl {
281 microprofile_cpu[2] = MICROPROFILE_TOKEN(ARM_CPU2); 282 microprofile_cpu[2] = MICROPROFILE_TOKEN(ARM_CPU2);
282 microprofile_cpu[3] = MICROPROFILE_TOKEN(ARM_CPU3); 283 microprofile_cpu[3] = MICROPROFILE_TOKEN(ARM_CPU3);
283 284
285 if (Settings::values.enable_renderdoc_hotkey) {
286 renderdoc_api = std::make_unique<Tools::RenderdocAPI>();
287 }
288
284 LOG_DEBUG(Core, "Initialized OK"); 289 LOG_DEBUG(Core, "Initialized OK");
285 290
286 return SystemResultStatus::Success; 291 return SystemResultStatus::Success;
@@ -521,6 +526,8 @@ struct System::Impl {
521 std::unique_ptr<Tools::Freezer> memory_freezer; 526 std::unique_ptr<Tools::Freezer> memory_freezer;
522 std::array<u8, 0x20> build_id{}; 527 std::array<u8, 0x20> build_id{};
523 528
529 std::unique_ptr<Tools::RenderdocAPI> renderdoc_api;
530
524 /// Frontend applets 531 /// Frontend applets
525 Service::AM::Applets::AppletManager applet_manager; 532 Service::AM::Applets::AppletManager applet_manager;
526 533
@@ -1024,6 +1031,10 @@ const Network::RoomNetwork& System::GetRoomNetwork() const {
1024 return impl->room_network; 1031 return impl->room_network;
1025} 1032}
1026 1033
1034Tools::RenderdocAPI& System::GetRenderdocAPI() {
1035 return *impl->renderdoc_api;
1036}
1037
1027void System::RunServer(std::unique_ptr<Service::ServerManager>&& server_manager) { 1038void System::RunServer(std::unique_ptr<Service::ServerManager>&& server_manager) {
1028 return impl->kernel.RunServer(std::move(server_manager)); 1039 return impl->kernel.RunServer(std::move(server_manager));
1029} 1040}
diff --git a/src/core/core.h b/src/core/core.h
index fba312125..df20f26f3 100644
--- a/src/core/core.h
+++ b/src/core/core.h
@@ -102,6 +102,10 @@ namespace Network {
102class RoomNetwork; 102class RoomNetwork;
103} 103}
104 104
105namespace Tools {
106class RenderdocAPI;
107}
108
105namespace Core { 109namespace Core {
106 110
107class ARM_Interface; 111class ARM_Interface;
@@ -413,6 +417,8 @@ public:
413 /// Gets an immutable reference to the Room Network. 417 /// Gets an immutable reference to the Room Network.
414 [[nodiscard]] const Network::RoomNetwork& GetRoomNetwork() const; 418 [[nodiscard]] const Network::RoomNetwork& GetRoomNetwork() const;
415 419
420 [[nodiscard]] Tools::RenderdocAPI& GetRenderdocAPI();
421
416 void SetExitLocked(bool locked); 422 void SetExitLocked(bool locked);
417 bool GetExitLocked() const; 423 bool GetExitLocked() const;
418 424
diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp
index e13c5cdc7..43a3c5ffd 100644
--- a/src/core/crypto/key_manager.cpp
+++ b/src/core/crypto/key_manager.cpp
@@ -724,14 +724,14 @@ void KeyManager::LoadFromFile(const std::filesystem::path& file_path, bool is_ti
724 continue; 724 continue;
725 } 725 }
726 726
727 const auto index = std::stoul(out[0].substr(8, 2), nullptr, 16); 727 const auto index = std::strtoul(out[0].substr(8, 2).c_str(), nullptr, 16);
728 keyblobs[index] = Common::HexStringToArray<0x90>(out[1]); 728 keyblobs[index] = Common::HexStringToArray<0x90>(out[1]);
729 } else if (out[0].compare(0, 18, "encrypted_keyblob_") == 0) { 729 } else if (out[0].compare(0, 18, "encrypted_keyblob_") == 0) {
730 if (!ValidCryptoRevisionString(out[0], 18, 2)) { 730 if (!ValidCryptoRevisionString(out[0], 18, 2)) {
731 continue; 731 continue;
732 } 732 }
733 733
734 const auto index = std::stoul(out[0].substr(18, 2), nullptr, 16); 734 const auto index = std::strtoul(out[0].substr(18, 2).c_str(), nullptr, 16);
735 encrypted_keyblobs[index] = Common::HexStringToArray<0xB0>(out[1]); 735 encrypted_keyblobs[index] = Common::HexStringToArray<0xB0>(out[1]);
736 } else if (out[0].compare(0, 20, "eticket_extended_kek") == 0) { 736 } else if (out[0].compare(0, 20, "eticket_extended_kek") == 0) {
737 eticket_extended_kek = Common::HexStringToArray<576>(out[1]); 737 eticket_extended_kek = Common::HexStringToArray<576>(out[1]);
@@ -750,7 +750,7 @@ void KeyManager::LoadFromFile(const std::filesystem::path& file_path, bool is_ti
750 } 750 }
751 if (out[0].compare(0, kv.second.size(), kv.second) == 0) { 751 if (out[0].compare(0, kv.second.size(), kv.second) == 0) {
752 const auto index = 752 const auto index =
753 std::stoul(out[0].substr(kv.second.size(), 2), nullptr, 16); 753 std::strtoul(out[0].substr(kv.second.size(), 2).c_str(), nullptr, 16);
754 const auto sub = kv.first.second; 754 const auto sub = kv.first.second;
755 if (sub == 0) { 755 if (sub == 0) {
756 s128_keys[{kv.first.first, index, 0}] = 756 s128_keys[{kv.first.first, index, 0}] =
@@ -770,7 +770,7 @@ void KeyManager::LoadFromFile(const std::filesystem::path& file_path, bool is_ti
770 const auto& match = kak_names[j]; 770 const auto& match = kak_names[j];
771 if (out[0].compare(0, std::strlen(match), match) == 0) { 771 if (out[0].compare(0, std::strlen(match), match) == 0) {
772 const auto index = 772 const auto index =
773 std::stoul(out[0].substr(std::strlen(match), 2), nullptr, 16); 773 std::strtoul(out[0].substr(std::strlen(match), 2).c_str(), nullptr, 16);
774 s128_keys[{S128KeyType::KeyArea, index, j}] = 774 s128_keys[{S128KeyType::KeyArea, index, j}] =
775 Common::HexStringToArray<16>(out[1]); 775 Common::HexStringToArray<16>(out[1]);
776 } 776 }
diff --git a/src/core/file_sys/ips_layer.cpp b/src/core/file_sys/ips_layer.cpp
index efdf18cee..7be1322cc 100644
--- a/src/core/file_sys/ips_layer.cpp
+++ b/src/core/file_sys/ips_layer.cpp
@@ -165,7 +165,7 @@ static std::string EscapeStringSequences(std::string in) {
165void IPSwitchCompiler::ParseFlag(const std::string& line) { 165void IPSwitchCompiler::ParseFlag(const std::string& line) {
166 if (StartsWith(line, "@flag offset_shift ")) { 166 if (StartsWith(line, "@flag offset_shift ")) {
167 // Offset Shift Flag 167 // Offset Shift Flag
168 offset_shift = std::stoll(line.substr(19), nullptr, 0); 168 offset_shift = std::strtoll(line.substr(19).c_str(), nullptr, 0);
169 } else if (StartsWith(line, "@little-endian")) { 169 } else if (StartsWith(line, "@little-endian")) {
170 // Set values to read as little endian 170 // Set values to read as little endian
171 is_little_endian = true; 171 is_little_endian = true;
@@ -263,7 +263,7 @@ void IPSwitchCompiler::Parse() {
263 // 11 - 8 hex digit offset + space + minimum two digit overwrite val 263 // 11 - 8 hex digit offset + space + minimum two digit overwrite val
264 if (patch_line.length() < 11) 264 if (patch_line.length() < 11)
265 break; 265 break;
266 auto offset = std::stoul(patch_line.substr(0, 8), nullptr, 16); 266 auto offset = std::strtoul(patch_line.substr(0, 8).c_str(), nullptr, 16);
267 offset += static_cast<unsigned long>(offset_shift); 267 offset += static_cast<unsigned long>(offset_shift);
268 268
269 std::vector<u8> replace; 269 std::vector<u8> replace;
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index f9c4f9678..8ffdd19e7 100644
--- a/src/core/hle/service/am/am.cpp
+++ b/src/core/hle/service/am/am.cpp
@@ -1386,7 +1386,7 @@ IApplicationFunctions::IApplicationFunctions(Core::System& system_)
1386 {25, &IApplicationFunctions::ExtendSaveData, "ExtendSaveData"}, 1386 {25, &IApplicationFunctions::ExtendSaveData, "ExtendSaveData"},
1387 {26, &IApplicationFunctions::GetSaveDataSize, "GetSaveDataSize"}, 1387 {26, &IApplicationFunctions::GetSaveDataSize, "GetSaveDataSize"},
1388 {27, &IApplicationFunctions::CreateCacheStorage, "CreateCacheStorage"}, 1388 {27, &IApplicationFunctions::CreateCacheStorage, "CreateCacheStorage"},
1389 {28, nullptr, "GetSaveDataSizeMax"}, 1389 {28, &IApplicationFunctions::GetSaveDataSizeMax, "GetSaveDataSizeMax"},
1390 {29, nullptr, "GetCacheStorageMax"}, 1390 {29, nullptr, "GetCacheStorageMax"},
1391 {30, &IApplicationFunctions::BeginBlockingHomeButtonShortAndLongPressed, "BeginBlockingHomeButtonShortAndLongPressed"}, 1391 {30, &IApplicationFunctions::BeginBlockingHomeButtonShortAndLongPressed, "BeginBlockingHomeButtonShortAndLongPressed"},
1392 {31, &IApplicationFunctions::EndBlockingHomeButtonShortAndLongPressed, "EndBlockingHomeButtonShortAndLongPressed"}, 1392 {31, &IApplicationFunctions::EndBlockingHomeButtonShortAndLongPressed, "EndBlockingHomeButtonShortAndLongPressed"},
@@ -1821,6 +1821,18 @@ void IApplicationFunctions::CreateCacheStorage(HLERequestContext& ctx) {
1821 rb.PushRaw(resp); 1821 rb.PushRaw(resp);
1822} 1822}
1823 1823
1824void IApplicationFunctions::GetSaveDataSizeMax(HLERequestContext& ctx) {
1825 LOG_WARNING(Service_AM, "(STUBBED) called");
1826
1827 constexpr u64 size_max_normal = 0xFFFFFFF;
1828 constexpr u64 size_max_journal = 0xFFFFFFF;
1829
1830 IPC::ResponseBuilder rb{ctx, 6};
1831 rb.Push(ResultSuccess);
1832 rb.Push(size_max_normal);
1833 rb.Push(size_max_journal);
1834}
1835
1824void IApplicationFunctions::QueryApplicationPlayStatistics(HLERequestContext& ctx) { 1836void IApplicationFunctions::QueryApplicationPlayStatistics(HLERequestContext& ctx) {
1825 LOG_WARNING(Service_AM, "(STUBBED) called"); 1837 LOG_WARNING(Service_AM, "(STUBBED) called");
1826 1838
diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h
index f75a665b2..f86841c60 100644
--- a/src/core/hle/service/am/am.h
+++ b/src/core/hle/service/am/am.h
@@ -316,6 +316,7 @@ private:
316 void ExtendSaveData(HLERequestContext& ctx); 316 void ExtendSaveData(HLERequestContext& ctx);
317 void GetSaveDataSize(HLERequestContext& ctx); 317 void GetSaveDataSize(HLERequestContext& ctx);
318 void CreateCacheStorage(HLERequestContext& ctx); 318 void CreateCacheStorage(HLERequestContext& ctx);
319 void GetSaveDataSizeMax(HLERequestContext& ctx);
319 void BeginBlockingHomeButtonShortAndLongPressed(HLERequestContext& ctx); 320 void BeginBlockingHomeButtonShortAndLongPressed(HLERequestContext& ctx);
320 void EndBlockingHomeButtonShortAndLongPressed(HLERequestContext& ctx); 321 void EndBlockingHomeButtonShortAndLongPressed(HLERequestContext& ctx);
321 void BeginBlockingHomeButton(HLERequestContext& ctx); 322 void BeginBlockingHomeButton(HLERequestContext& ctx);
diff --git a/src/core/hle/service/sockets/bsd.cpp b/src/core/hle/service/sockets/bsd.cpp
index d8509c1dd..85849d5f3 100644
--- a/src/core/hle/service/sockets/bsd.cpp
+++ b/src/core/hle/service/sockets/bsd.cpp
@@ -170,7 +170,7 @@ void BSD::Socket(HLERequestContext& ctx) {
170} 170}
171 171
172void BSD::Select(HLERequestContext& ctx) { 172void BSD::Select(HLERequestContext& ctx) {
173 LOG_WARNING(Service, "(STUBBED) called"); 173 LOG_DEBUG(Service, "(STUBBED) called");
174 174
175 IPC::ResponseBuilder rb{ctx, 4}; 175 IPC::ResponseBuilder rb{ctx, 4};
176 176
diff --git a/src/core/memory/cheat_engine.cpp b/src/core/memory/cheat_engine.cpp
index 7b52f61a7..a06e99166 100644
--- a/src/core/memory/cheat_engine.cpp
+++ b/src/core/memory/cheat_engine.cpp
@@ -154,7 +154,7 @@ std::vector<CheatEntry> TextCheatParser::Parse(std::string_view data) const {
154 return {}; 154 return {};
155 } 155 }
156 156
157 const auto value = static_cast<u32>(std::stoul(hex, nullptr, 0x10)); 157 const auto value = static_cast<u32>(std::strtoul(hex.c_str(), nullptr, 0x10));
158 out[*current_entry].definition.opcodes[out[*current_entry].definition.num_opcodes++] = 158 out[*current_entry].definition.opcodes[out[*current_entry].definition.num_opcodes++] =
159 value; 159 value;
160 160
diff --git a/src/core/tools/renderdoc.cpp b/src/core/tools/renderdoc.cpp
new file mode 100644
index 000000000..44d24822a
--- /dev/null
+++ b/src/core/tools/renderdoc.cpp
@@ -0,0 +1,55 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <renderdoc_app.h>
5
6#include "common/assert.h"
7#include "common/dynamic_library.h"
8#include "core/tools/renderdoc.h"
9
10#ifdef WIN32
11#include <windows.h>
12#else
13#include <dlfcn.h>
14#endif
15
16namespace Tools {
17
18RenderdocAPI::RenderdocAPI() {
19#ifdef WIN32
20 if (HMODULE mod = GetModuleHandleA("renderdoc.dll")) {
21 const auto RENDERDOC_GetAPI =
22 reinterpret_cast<pRENDERDOC_GetAPI>(GetProcAddress(mod, "RENDERDOC_GetAPI"));
23 const s32 ret = RENDERDOC_GetAPI(eRENDERDOC_API_Version_1_6_0, (void**)&rdoc_api);
24 ASSERT(ret == 1);
25 }
26#else
27#ifdef ANDROID
28 static constexpr const char RENDERDOC_LIB[] = "libVkLayer_GLES_RenderDoc.so";
29#else
30 static constexpr const char RENDERDOC_LIB[] = "librenderdoc.so";
31#endif
32 if (void* mod = dlopen(RENDERDOC_LIB, RTLD_NOW | RTLD_NOLOAD)) {
33 const auto RENDERDOC_GetAPI =
34 reinterpret_cast<pRENDERDOC_GetAPI>(dlsym(mod, "RENDERDOC_GetAPI"));
35 const s32 ret = RENDERDOC_GetAPI(eRENDERDOC_API_Version_1_6_0, (void**)&rdoc_api);
36 ASSERT(ret == 1);
37 }
38#endif
39}
40
41RenderdocAPI::~RenderdocAPI() = default;
42
43void RenderdocAPI::ToggleCapture() {
44 if (!rdoc_api) [[unlikely]] {
45 return;
46 }
47 if (!is_capturing) {
48 rdoc_api->StartFrameCapture(NULL, NULL);
49 } else {
50 rdoc_api->EndFrameCapture(NULL, NULL);
51 }
52 is_capturing = !is_capturing;
53}
54
55} // namespace Tools
diff --git a/src/core/tools/renderdoc.h b/src/core/tools/renderdoc.h
new file mode 100644
index 000000000..0e5e43da5
--- /dev/null
+++ b/src/core/tools/renderdoc.h
@@ -0,0 +1,22 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6struct RENDERDOC_API_1_6_0;
7
8namespace Tools {
9
10class RenderdocAPI {
11public:
12 explicit RenderdocAPI();
13 ~RenderdocAPI();
14
15 void ToggleCapture();
16
17private:
18 RENDERDOC_API_1_6_0* rdoc_api{};
19 bool is_capturing{false};
20};
21
22} // namespace Tools
diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp
index cbeb8f168..b22fda746 100644
--- a/src/yuzu/configuration/configure_debug.cpp
+++ b/src/yuzu/configuration/configure_debug.cpp
@@ -59,6 +59,8 @@ void ConfigureDebug::SetConfiguration() {
59 ui->use_debug_asserts->setChecked(Settings::values.use_debug_asserts.GetValue()); 59 ui->use_debug_asserts->setChecked(Settings::values.use_debug_asserts.GetValue());
60 ui->use_auto_stub->setChecked(Settings::values.use_auto_stub.GetValue()); 60 ui->use_auto_stub->setChecked(Settings::values.use_auto_stub.GetValue());
61 ui->enable_all_controllers->setChecked(Settings::values.enable_all_controllers.GetValue()); 61 ui->enable_all_controllers->setChecked(Settings::values.enable_all_controllers.GetValue());
62 ui->enable_renderdoc_hotkey->setEnabled(runtime_lock);
63 ui->enable_renderdoc_hotkey->setChecked(Settings::values.enable_renderdoc_hotkey.GetValue());
62 ui->enable_graphics_debugging->setEnabled(runtime_lock); 64 ui->enable_graphics_debugging->setEnabled(runtime_lock);
63 ui->enable_graphics_debugging->setChecked(Settings::values.renderer_debug.GetValue()); 65 ui->enable_graphics_debugging->setChecked(Settings::values.renderer_debug.GetValue());
64 ui->enable_shader_feedback->setEnabled(runtime_lock); 66 ui->enable_shader_feedback->setEnabled(runtime_lock);
@@ -111,6 +113,7 @@ void ConfigureDebug::ApplyConfiguration() {
111 Settings::values.use_auto_stub = ui->use_auto_stub->isChecked(); 113 Settings::values.use_auto_stub = ui->use_auto_stub->isChecked();
112 Settings::values.enable_all_controllers = ui->enable_all_controllers->isChecked(); 114 Settings::values.enable_all_controllers = ui->enable_all_controllers->isChecked();
113 Settings::values.renderer_debug = ui->enable_graphics_debugging->isChecked(); 115 Settings::values.renderer_debug = ui->enable_graphics_debugging->isChecked();
116 Settings::values.enable_renderdoc_hotkey = ui->enable_renderdoc_hotkey->isChecked();
114 Settings::values.renderer_shader_feedback = ui->enable_shader_feedback->isChecked(); 117 Settings::values.renderer_shader_feedback = ui->enable_shader_feedback->isChecked();
115 Settings::values.cpu_debug_mode = ui->enable_cpu_debugging->isChecked(); 118 Settings::values.cpu_debug_mode = ui->enable_cpu_debugging->isChecked();
116 Settings::values.enable_nsight_aftermath = ui->enable_nsight_aftermath->isChecked(); 119 Settings::values.enable_nsight_aftermath = ui->enable_nsight_aftermath->isChecked();
diff --git a/src/yuzu/configuration/configure_debug.ui b/src/yuzu/configuration/configure_debug.ui
index 97c7d9022..66b8b7459 100644
--- a/src/yuzu/configuration/configure_debug.ui
+++ b/src/yuzu/configuration/configure_debug.ui
@@ -18,8 +18,8 @@
18 <rect> 18 <rect>
19 <x>0</x> 19 <x>0</x>
20 <y>0</y> 20 <y>0</y>
21 <width>829</width> 21 <width>842</width>
22 <height>758</height> 22 <height>741</height>
23 </rect> 23 </rect>
24 </property> 24 </property>
25 <layout class="QVBoxLayout" name="verticalLayout_1"> 25 <layout class="QVBoxLayout" name="verticalLayout_1">
@@ -260,7 +260,7 @@
260 <string>Graphics</string> 260 <string>Graphics</string>
261 </property> 261 </property>
262 <layout class="QGridLayout" name="gridLayout_2"> 262 <layout class="QGridLayout" name="gridLayout_2">
263 <item row="3" column="0"> 263 <item row="4" column="0">
264 <widget class="QCheckBox" name="disable_loop_safety_checks"> 264 <widget class="QCheckBox" name="disable_loop_safety_checks">
265 <property name="toolTip"> 265 <property name="toolTip">
266 <string>When checked, it executes shaders without loop logic changes</string> 266 <string>When checked, it executes shaders without loop logic changes</string>
@@ -270,33 +270,53 @@
270 </property> 270 </property>
271 </widget> 271 </widget>
272 </item> 272 </item>
273 <item row="4" column="0"> 273 <item row="8" column="0">
274 <widget class="QCheckBox" name="dump_shaders"> 274 <widget class="QCheckBox" name="disable_macro_hle">
275 <property name="enabled"> 275 <property name="enabled">
276 <bool>true</bool> 276 <bool>true</bool>
277 </property> 277 </property>
278 <property name="toolTip"> 278 <property name="toolTip">
279 <string>When checked, it will dump all the original assembler shaders from the disk shader cache or game as found</string> 279 <string>When checked, it disables the macro HLE functions. Enabling this makes games run slower</string>
280 </property> 280 </property>
281 <property name="text"> 281 <property name="text">
282 <string>Dump Game Shaders</string> 282 <string>Disable Macro HLE</string>
283 </property> 283 </property>
284 </widget> 284 </widget>
285 </item> 285 </item>
286 <item row="7" column="0"> 286 <item row="7" column="0">
287 <widget class="QCheckBox" name="disable_macro_hle"> 287 <widget class="QCheckBox" name="dump_macros">
288 <property name="enabled"> 288 <property name="enabled">
289 <bool>true</bool> 289 <bool>true</bool>
290 </property> 290 </property>
291 <property name="toolTip"> 291 <property name="toolTip">
292 <string>When checked, it disables the macro HLE functions. Enabling this makes games run slower</string> 292 <string>When checked, it will dump all the macro programs of the GPU</string>
293 </property> 293 </property>
294 <property name="text"> 294 <property name="text">
295 <string>Disable Macro HLE</string> 295 <string>Dump Maxwell Macros</string>
296 </property> 296 </property>
297 </widget> 297 </widget>
298 </item> 298 </item>
299 <item row="5" column="0"> 299 <item row="3" column="0">
300 <widget class="QCheckBox" name="enable_nsight_aftermath">
301 <property name="toolTip">
302 <string>When checked, it enables Nsight Aftermath crash dumps</string>
303 </property>
304 <property name="text">
305 <string>Enable Nsight Aftermath</string>
306 </property>
307 </widget>
308 </item>
309 <item row="2" column="0">
310 <widget class="QCheckBox" name="enable_shader_feedback">
311 <property name="toolTip">
312 <string>When checked, yuzu will log statistics about the compiled pipeline cache</string>
313 </property>
314 <property name="text">
315 <string>Enable Shader Feedback</string>
316 </property>
317 </widget>
318 </item>
319 <item row="6" column="0">
300 <widget class="QCheckBox" name="disable_macro_jit"> 320 <widget class="QCheckBox" name="disable_macro_jit">
301 <property name="enabled"> 321 <property name="enabled">
302 <bool>true</bool> 322 <bool>true</bool>
@@ -309,6 +329,22 @@
309 </property> 329 </property>
310 </widget> 330 </widget>
311 </item> 331 </item>
332 <item row="9" column="0">
333 <spacer name="verticalSpacer_5">
334 <property name="orientation">
335 <enum>Qt::Vertical</enum>
336 </property>
337 <property name="sizeType">
338 <enum>QSizePolicy::Preferred</enum>
339 </property>
340 <property name="sizeHint" stdset="0">
341 <size>
342 <width>20</width>
343 <height>0</height>
344 </size>
345 </property>
346 </spacer>
347 </item>
312 <item row="0" column="0"> 348 <item row="0" column="0">
313 <widget class="QCheckBox" name="enable_graphics_debugging"> 349 <widget class="QCheckBox" name="enable_graphics_debugging">
314 <property name="enabled"> 350 <property name="enabled">
@@ -322,55 +358,26 @@
322 </property> 358 </property>
323 </widget> 359 </widget>
324 </item> 360 </item>
325 <item row="6" column="0"> 361 <item row="5" column="0">
326 <widget class="QCheckBox" name="dump_macros"> 362 <widget class="QCheckBox" name="dump_shaders">
327 <property name="enabled"> 363 <property name="enabled">
328 <bool>true</bool> 364 <bool>true</bool>
329 </property> 365 </property>
330 <property name="toolTip"> 366 <property name="toolTip">
331 <string>When checked, it will dump all the macro programs of the GPU</string> 367 <string>When checked, it will dump all the original assembler shaders from the disk shader cache or game as found</string>
332 </property> 368 </property>
333 <property name="text"> 369 <property name="text">
334 <string>Dump Maxwell Macros</string> 370 <string>Dump Game Shaders</string>
335 </property> 371 </property>
336 </widget> 372 </widget>
337 </item> 373 </item>
338 <item row="1" column="0"> 374 <item row="1" column="0">
339 <widget class="QCheckBox" name="enable_shader_feedback"> 375 <widget class="QCheckBox" name="enable_renderdoc_hotkey">
340 <property name="toolTip">
341 <string>When checked, yuzu will log statistics about the compiled pipeline cache</string>
342 </property>
343 <property name="text">
344 <string>Enable Shader Feedback</string>
345 </property>
346 </widget>
347 </item>
348 <item row="2" column="0">
349 <widget class="QCheckBox" name="enable_nsight_aftermath">
350 <property name="toolTip">
351 <string>When checked, it enables Nsight Aftermath crash dumps</string>
352 </property>
353 <property name="text"> 376 <property name="text">
354 <string>Enable Nsight Aftermath</string> 377 <string>Enable Renderdoc Hotkey</string>
355 </property> 378 </property>
356 </widget> 379 </widget>
357 </item> 380 </item>
358 <item row="8" column="0">
359 <spacer name="verticalSpacer_5">
360 <property name="orientation">
361 <enum>Qt::Vertical</enum>
362 </property>
363 <property name="sizeType">
364 <enum>QSizePolicy::Preferred</enum>
365 </property>
366 <property name="sizeHint" stdset="0">
367 <size>
368 <width>20</width>
369 <height>0</height>
370 </size>
371 </property>
372 </spacer>
373 </item>
374 </layout> 381 </layout>
375 </widget> 382 </widget>
376 </item> 383 </item>
diff --git a/src/yuzu/configuration/configure_ui.cpp b/src/yuzu/configuration/configure_ui.cpp
index 34ab01617..a9fde9f4f 100644
--- a/src/yuzu/configuration/configure_ui.cpp
+++ b/src/yuzu/configuration/configure_ui.cpp
@@ -4,6 +4,7 @@
4#include "yuzu/configuration/configure_ui.h" 4#include "yuzu/configuration/configure_ui.h"
5 5
6#include <array> 6#include <array>
7#include <cstdlib>
7#include <set> 8#include <set>
8#include <stdexcept> 9#include <stdexcept>
9#include <string> 10#include <string>
@@ -94,11 +95,7 @@ static void PopulateResolutionComboBox(QComboBox* screenshot_height, QWidget* pa
94} 95}
95 96
96static u32 ScreenshotDimensionToInt(const QString& height) { 97static u32 ScreenshotDimensionToInt(const QString& height) {
97 try { 98 return std::strtoul(height.toUtf8(), nullptr, 0);
98 return std::stoi(height.toStdString());
99 } catch (std::invalid_argument&) {
100 return 0;
101 }
102} 99}
103 100
104ConfigureUi::ConfigureUi(Core::System& system_, QWidget* parent) 101ConfigureUi::ConfigureUi(Core::System& system_, QWidget* parent)
diff --git a/src/yuzu/configuration/shared_widget.cpp b/src/yuzu/configuration/shared_widget.cpp
index d63093985..ea8d7add4 100644
--- a/src/yuzu/configuration/shared_widget.cpp
+++ b/src/yuzu/configuration/shared_widget.cpp
@@ -63,7 +63,7 @@ static QString DefaultSuffix(QWidget* parent, Settings::BasicSetting& setting) {
63 return tr("%", context.c_str()); 63 return tr("%", context.c_str());
64 } 64 }
65 65
66 return QStringLiteral(""); 66 return default_suffix;
67} 67}
68 68
69QPushButton* Widget::CreateRestoreGlobalButton(bool using_global, QWidget* parent) { 69QPushButton* Widget::CreateRestoreGlobalButton(bool using_global, QWidget* parent) {
@@ -71,7 +71,7 @@ QPushButton* Widget::CreateRestoreGlobalButton(bool using_global, QWidget* paren
71 71
72 QStyle* style = parent->style(); 72 QStyle* style = parent->style();
73 QIcon* icon = new QIcon(style->standardIcon(QStyle::SP_LineEditClearButton)); 73 QIcon* icon = new QIcon(style->standardIcon(QStyle::SP_LineEditClearButton));
74 QPushButton* restore_button = new QPushButton(*icon, QStringLiteral(""), parent); 74 QPushButton* restore_button = new QPushButton(*icon, QStringLiteral(), parent);
75 restore_button->setObjectName(QStringLiteral("RestoreButton%1").arg(restore_button_count)); 75 restore_button->setObjectName(QStringLiteral("RestoreButton%1").arg(restore_button_count));
76 restore_button->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); 76 restore_button->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
77 77
@@ -151,7 +151,7 @@ QWidget* Widget::CreateCombobox(std::function<std::string()>& serializer,
151 return -1; 151 return -1;
152 }; 152 };
153 153
154 const u32 setting_value = std::stoi(setting.ToString()); 154 const u32 setting_value = std::strtoul(setting.ToString().c_str(), nullptr, 0);
155 combobox->setCurrentIndex(find_index(setting_value)); 155 combobox->setCurrentIndex(find_index(setting_value));
156 156
157 serializer = [this, enumeration]() { 157 serializer = [this, enumeration]() {
@@ -160,7 +160,7 @@ QWidget* Widget::CreateCombobox(std::function<std::string()>& serializer,
160 }; 160 };
161 161
162 restore_func = [this, find_index]() { 162 restore_func = [this, find_index]() {
163 const u32 global_value = std::stoi(RelevantDefault(setting)); 163 const u32 global_value = std::strtoul(RelevantDefault(setting).c_str(), nullptr, 0);
164 combobox->setCurrentIndex(find_index(global_value)); 164 combobox->setCurrentIndex(find_index(global_value));
165 }; 165 };
166 166
@@ -209,7 +209,7 @@ QWidget* Widget::CreateRadioGroup(std::function<std::string()>& serializer,
209 } 209 }
210 }; 210 };
211 211
212 const u32 setting_value = std::stoi(setting.ToString()); 212 const u32 setting_value = std::strtoul(setting.ToString().c_str(), nullptr, 0);
213 set_index(setting_value); 213 set_index(setting_value);
214 214
215 serializer = [get_selected]() { 215 serializer = [get_selected]() {
@@ -218,7 +218,7 @@ QWidget* Widget::CreateRadioGroup(std::function<std::string()>& serializer,
218 }; 218 };
219 219
220 restore_func = [this, set_index]() { 220 restore_func = [this, set_index]() {
221 const u32 global_value = std::stoi(RelevantDefault(setting)); 221 const u32 global_value = std::strtoul(RelevantDefault(setting).c_str(), nullptr, 0);
222 set_index(global_value); 222 set_index(global_value);
223 }; 223 };
224 224
@@ -255,6 +255,59 @@ QWidget* Widget::CreateLineEdit(std::function<std::string()>& serializer,
255 return line_edit; 255 return line_edit;
256} 256}
257 257
258static void CreateIntSlider(Settings::BasicSetting& setting, bool reversed, float multiplier,
259 QLabel* feedback, const QString& use_format, QSlider* slider,
260 std::function<std::string()>& serializer,
261 std::function<void()>& restore_func) {
262 const int max_val = std::strtol(setting.MaxVal().c_str(), nullptr, 0);
263
264 const auto update_feedback = [=](int value) {
265 int present = (reversed ? max_val - value : value) * multiplier + 0.5f;
266 feedback->setText(use_format.arg(QVariant::fromValue(present).value<QString>()));
267 };
268
269 QObject::connect(slider, &QAbstractSlider::valueChanged, update_feedback);
270 update_feedback(std::strtol(setting.ToString().c_str(), nullptr, 0));
271
272 slider->setMinimum(std::strtol(setting.MinVal().c_str(), nullptr, 0));
273 slider->setMaximum(max_val);
274 slider->setValue(std::strtol(setting.ToString().c_str(), nullptr, 0));
275
276 serializer = [slider]() { return std::to_string(slider->value()); };
277 restore_func = [slider, &setting]() {
278 slider->setValue(std::strtol(RelevantDefault(setting).c_str(), nullptr, 0));
279 };
280}
281
282static void CreateFloatSlider(Settings::BasicSetting& setting, bool reversed, float multiplier,
283 QLabel* feedback, const QString& use_format, QSlider* slider,
284 std::function<std::string()>& serializer,
285 std::function<void()>& restore_func) {
286 const float max_val = std::strtof(setting.MaxVal().c_str(), nullptr);
287 const float min_val = std::strtof(setting.MinVal().c_str(), nullptr);
288 const float use_multiplier =
289 multiplier == default_multiplier ? default_float_multiplier : multiplier;
290
291 const auto update_feedback = [=](float value) {
292 int present = (reversed ? max_val - value : value) + 0.5f;
293 feedback->setText(use_format.arg(QVariant::fromValue(present).value<QString>()));
294 };
295
296 QObject::connect(slider, &QAbstractSlider::valueChanged, update_feedback);
297 update_feedback(std::strtof(setting.ToString().c_str(), nullptr));
298
299 slider->setMinimum(min_val * use_multiplier);
300 slider->setMaximum(max_val * use_multiplier);
301 slider->setValue(std::strtof(setting.ToString().c_str(), nullptr) * use_multiplier);
302
303 serializer = [slider, use_multiplier]() {
304 return std::to_string(slider->value() / use_multiplier);
305 };
306 restore_func = [slider, &setting, use_multiplier]() {
307 slider->setValue(std::strtof(RelevantDefault(setting).c_str(), nullptr) * use_multiplier);
308 };
309}
310
258QWidget* Widget::CreateSlider(bool reversed, float multiplier, const QString& given_suffix, 311QWidget* Widget::CreateSlider(bool reversed, float multiplier, const QString& given_suffix,
259 std::function<std::string()>& serializer, 312 std::function<std::string()>& serializer,
260 std::function<void()>& restore_func, 313 std::function<void()>& restore_func,
@@ -278,27 +331,19 @@ QWidget* Widget::CreateSlider(bool reversed, float multiplier, const QString& gi
278 331
279 layout->setContentsMargins(0, 0, 0, 0); 332 layout->setContentsMargins(0, 0, 0, 0);
280 333
281 int max_val = std::stoi(setting.MaxVal()); 334 QString suffix = given_suffix == default_suffix ? DefaultSuffix(this, setting) : given_suffix;
282
283 QString suffix =
284 given_suffix == QStringLiteral("") ? DefaultSuffix(this, setting) : given_suffix;
285 335
286 const QString use_format = QStringLiteral("%1").append(suffix); 336 const QString use_format = QStringLiteral("%1").append(suffix);
287 337
288 QObject::connect(slider, &QAbstractSlider::valueChanged, [=](int value) { 338 if (setting.IsIntegral()) {
289 int present = (reversed ? max_val - value : value) * multiplier + 0.5f; 339 CreateIntSlider(setting, reversed, multiplier, feedback, use_format, slider, serializer,
290 feedback->setText(use_format.arg(QVariant::fromValue(present).value<QString>())); 340 restore_func);
291 }); 341 } else {
292 342 CreateFloatSlider(setting, reversed, multiplier, feedback, use_format, slider, serializer,
293 slider->setMinimum(std::stoi(setting.MinVal())); 343 restore_func);
294 slider->setMaximum(max_val); 344 }
295 slider->setValue(std::stoi(setting.ToString()));
296 345
297 slider->setInvertedAppearance(reversed); 346 slider->setInvertedAppearance(reversed);
298 slider->setInvertedControls(reversed);
299
300 serializer = [this]() { return std::to_string(slider->value()); };
301 restore_func = [this]() { slider->setValue(std::stoi(RelevantDefault(setting))); };
302 347
303 if (!Settings::IsConfiguringGlobal()) { 348 if (!Settings::IsConfiguringGlobal()) {
304 QObject::connect(slider, &QAbstractSlider::actionTriggered, [touch]() { touch(); }); 349 QObject::connect(slider, &QAbstractSlider::actionTriggered, [touch]() { touch(); });
@@ -311,14 +356,11 @@ QWidget* Widget::CreateSpinBox(const QString& given_suffix,
311 std::function<std::string()>& serializer, 356 std::function<std::string()>& serializer,
312 std::function<void()>& restore_func, 357 std::function<void()>& restore_func,
313 const std::function<void()>& touch) { 358 const std::function<void()>& touch) {
314 const int min_val = 359 const auto min_val = std::strtol(setting.MinVal().c_str(), nullptr, 0);
315 setting.Ranged() ? std::stoi(setting.MinVal()) : std::numeric_limits<int>::min(); 360 const auto max_val = std::strtol(setting.MaxVal().c_str(), nullptr, 0);
316 const int max_val = 361 const auto default_val = std::strtol(setting.ToString().c_str(), nullptr, 0);
317 setting.Ranged() ? std::stoi(setting.MaxVal()) : std::numeric_limits<int>::max();
318 const int default_val = std::stoi(setting.ToString());
319 362
320 QString suffix = 363 QString suffix = given_suffix == default_suffix ? DefaultSuffix(this, setting) : given_suffix;
321 given_suffix == QStringLiteral("") ? DefaultSuffix(this, setting) : given_suffix;
322 364
323 spinbox = new QSpinBox(this); 365 spinbox = new QSpinBox(this);
324 spinbox->setRange(min_val, max_val); 366 spinbox->setRange(min_val, max_val);
@@ -329,13 +371,13 @@ QWidget* Widget::CreateSpinBox(const QString& given_suffix,
329 serializer = [this]() { return std::to_string(spinbox->value()); }; 371 serializer = [this]() { return std::to_string(spinbox->value()); };
330 372
331 restore_func = [this]() { 373 restore_func = [this]() {
332 auto value{std::stol(RelevantDefault(setting))}; 374 auto value{std::strtol(RelevantDefault(setting).c_str(), nullptr, 0)};
333 spinbox->setValue(value); 375 spinbox->setValue(value);
334 }; 376 };
335 377
336 if (!Settings::IsConfiguringGlobal()) { 378 if (!Settings::IsConfiguringGlobal()) {
337 QObject::connect(spinbox, QOverload<int>::of(&QSpinBox::valueChanged), [this, touch]() { 379 QObject::connect(spinbox, QOverload<int>::of(&QSpinBox::valueChanged), [this, touch]() {
338 if (spinbox->value() != std::stoi(setting.ToStringGlobal())) { 380 if (spinbox->value() != std::strtol(setting.ToStringGlobal().c_str(), nullptr, 0)) {
339 touch(); 381 touch();
340 } 382 }
341 }); 383 });
@@ -344,6 +386,42 @@ QWidget* Widget::CreateSpinBox(const QString& given_suffix,
344 return spinbox; 386 return spinbox;
345} 387}
346 388
389QWidget* Widget::CreateDoubleSpinBox(const QString& given_suffix,
390 std::function<std::string()>& serializer,
391 std::function<void()>& restore_func,
392 const std::function<void()>& touch) {
393 const auto min_val = std::strtod(setting.MinVal().c_str(), nullptr);
394 const auto max_val = std::strtod(setting.MaxVal().c_str(), nullptr);
395 const auto default_val = std::strtod(setting.ToString().c_str(), nullptr);
396
397 QString suffix = given_suffix == default_suffix ? DefaultSuffix(this, setting) : given_suffix;
398
399 double_spinbox = new QDoubleSpinBox(this);
400 double_spinbox->setRange(min_val, max_val);
401 double_spinbox->setValue(default_val);
402 double_spinbox->setSuffix(suffix);
403 double_spinbox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
404
405 serializer = [this]() { return fmt::format("{:f}", double_spinbox->value()); };
406
407 restore_func = [this]() {
408 auto value{std::strtod(RelevantDefault(setting).c_str(), nullptr)};
409 double_spinbox->setValue(value);
410 };
411
412 if (!Settings::IsConfiguringGlobal()) {
413 QObject::connect(double_spinbox, QOverload<double>::of(&QDoubleSpinBox::valueChanged),
414 [this, touch]() {
415 if (double_spinbox->value() !=
416 std::strtod(setting.ToStringGlobal().c_str(), nullptr)) {
417 touch();
418 }
419 });
420 }
421
422 return double_spinbox;
423}
424
347QWidget* Widget::CreateHexEdit(std::function<std::string()>& serializer, 425QWidget* Widget::CreateHexEdit(std::function<std::string()>& serializer,
348 std::function<void()>& restore_func, 426 std::function<void()>& restore_func,
349 const std::function<void()>& touch) { 427 const std::function<void()>& touch) {
@@ -353,7 +431,8 @@ QWidget* Widget::CreateHexEdit(std::function<std::string()>& serializer,
353 } 431 }
354 432
355 auto to_hex = [=](const std::string& input) { 433 auto to_hex = [=](const std::string& input) {
356 return QString::fromStdString(fmt::format("{:08x}", std::stoul(input))); 434 return QString::fromStdString(
435 fmt::format("{:08x}", std::strtoul(input.c_str(), nullptr, 0)));
357 }; 436 };
358 437
359 QRegularExpressionValidator* regex = new QRegularExpressionValidator( 438 QRegularExpressionValidator* regex = new QRegularExpressionValidator(
@@ -366,7 +445,7 @@ QWidget* Widget::CreateHexEdit(std::function<std::string()>& serializer,
366 line_edit->setValidator(regex); 445 line_edit->setValidator(regex);
367 446
368 auto hex_to_dec = [this]() -> std::string { 447 auto hex_to_dec = [this]() -> std::string {
369 return std::to_string(std::stoul(line_edit->text().toStdString(), nullptr, 16)); 448 return std::to_string(std::strtoul(line_edit->text().toStdString().c_str(), nullptr, 16));
370 }; 449 };
371 450
372 serializer = [hex_to_dec]() { return hex_to_dec(); }; 451 serializer = [hex_to_dec]() { return hex_to_dec(); };
@@ -386,7 +465,8 @@ QWidget* Widget::CreateDateTimeEdit(bool disabled, bool restrict,
386 std::function<void()>& restore_func, 465 std::function<void()>& restore_func,
387 const std::function<void()>& touch) { 466 const std::function<void()>& touch) {
388 const long long current_time = QDateTime::currentSecsSinceEpoch(); 467 const long long current_time = QDateTime::currentSecsSinceEpoch();
389 const s64 the_time = disabled ? current_time : std::stoll(setting.ToString()); 468 const s64 the_time =
469 disabled ? current_time : std::strtoll(setting.ToString().c_str(), nullptr, 0);
390 const auto default_val = QDateTime::fromSecsSinceEpoch(the_time); 470 const auto default_val = QDateTime::fromSecsSinceEpoch(the_time);
391 471
392 date_time_edit = new QDateTimeEdit(this); 472 date_time_edit = new QDateTimeEdit(this);
@@ -399,7 +479,7 @@ QWidget* Widget::CreateDateTimeEdit(bool disabled, bool restrict,
399 auto get_clear_val = [this, restrict, current_time]() { 479 auto get_clear_val = [this, restrict, current_time]() {
400 return QDateTime::fromSecsSinceEpoch([this, restrict, current_time]() { 480 return QDateTime::fromSecsSinceEpoch([this, restrict, current_time]() {
401 if (restrict && checkbox->checkState() == Qt::Checked) { 481 if (restrict && checkbox->checkState() == Qt::Checked) {
402 return std::stoll(RelevantDefault(setting)); 482 return std::strtoll(RelevantDefault(setting).c_str(), nullptr, 0);
403 } 483 }
404 return current_time; 484 return current_time;
405 }()); 485 }());
@@ -506,8 +586,7 @@ void Widget::SetupComponent(const QString& label, std::function<void()>& load_fu
506 } else { 586 } else {
507 data_component = CreateCombobox(serializer, restore_func, touch); 587 data_component = CreateCombobox(serializer, restore_func, touch);
508 } 588 }
509 } else if (type == typeid(u32) || type == typeid(int) || type == typeid(u16) || 589 } else if (setting.IsIntegral()) {
510 type == typeid(s64) || type == typeid(u8)) {
511 switch (request) { 590 switch (request) {
512 case RequestType::Slider: 591 case RequestType::Slider:
513 case RequestType::ReverseSlider: 592 case RequestType::ReverseSlider:
@@ -534,6 +613,20 @@ void Widget::SetupComponent(const QString& label, std::function<void()>& load_fu
534 default: 613 default:
535 UNIMPLEMENTED(); 614 UNIMPLEMENTED();
536 } 615 }
616 } else if (setting.IsFloatingPoint()) {
617 switch (request) {
618 case RequestType::Default:
619 case RequestType::SpinBox:
620 data_component = CreateDoubleSpinBox(suffix, serializer, restore_func, touch);
621 break;
622 case RequestType::Slider:
623 case RequestType::ReverseSlider:
624 data_component = CreateSlider(request == RequestType::ReverseSlider, multiplier, suffix,
625 serializer, restore_func, touch);
626 break;
627 default:
628 UNIMPLEMENTED();
629 }
537 } else if (type == typeid(std::string)) { 630 } else if (type == typeid(std::string)) {
538 switch (request) { 631 switch (request) {
539 case RequestType::Default: 632 case RequestType::Default:
@@ -638,10 +731,10 @@ Widget::Widget(Settings::BasicSetting* setting_, const TranslationMap& translati
638 return std::pair{translations.at(id).first, translations.at(id).second}; 731 return std::pair{translations.at(id).first, translations.at(id).second};
639 } 732 }
640 LOG_WARNING(Frontend, "Translation table lacks entry for \"{}\"", setting_label); 733 LOG_WARNING(Frontend, "Translation table lacks entry for \"{}\"", setting_label);
641 return std::pair{QString::fromStdString(setting_label), QStringLiteral("")}; 734 return std::pair{QString::fromStdString(setting_label), QStringLiteral()};
642 }(); 735 }();
643 736
644 if (label == QStringLiteral("")) { 737 if (label == QStringLiteral()) {
645 LOG_DEBUG(Frontend, "Translation table has empty entry for \"{}\", skipping...", 738 LOG_DEBUG(Frontend, "Translation table has empty entry for \"{}\", skipping...",
646 setting.GetLabel()); 739 setting.GetLabel());
647 return; 740 return;
diff --git a/src/yuzu/configuration/shared_widget.h b/src/yuzu/configuration/shared_widget.h
index 5303dd898..226284cf3 100644
--- a/src/yuzu/configuration/shared_widget.h
+++ b/src/yuzu/configuration/shared_widget.h
@@ -22,6 +22,7 @@ class QObject;
22class QPushButton; 22class QPushButton;
23class QSlider; 23class QSlider;
24class QSpinBox; 24class QSpinBox;
25class QDoubleSpinBox;
25class QRadioButton; 26class QRadioButton;
26 27
27namespace Settings { 28namespace Settings {
@@ -43,6 +44,10 @@ enum class RequestType {
43 MaxEnum, 44 MaxEnum,
44}; 45};
45 46
47constexpr float default_multiplier{1.f};
48constexpr float default_float_multiplier{100.f};
49static const QString default_suffix = QStringLiteral();
50
46class Widget : public QWidget { 51class Widget : public QWidget {
47 Q_OBJECT 52 Q_OBJECT
48 53
@@ -66,8 +71,9 @@ public:
66 const ComboboxTranslationMap& combobox_translations, QWidget* parent, 71 const ComboboxTranslationMap& combobox_translations, QWidget* parent,
67 bool runtime_lock, std::vector<std::function<void(bool)>>& apply_funcs_, 72 bool runtime_lock, std::vector<std::function<void(bool)>>& apply_funcs_,
68 RequestType request = RequestType::Default, bool managed = true, 73 RequestType request = RequestType::Default, bool managed = true,
69 float multiplier = 1.0f, Settings::BasicSetting* other_setting = nullptr, 74 float multiplier = default_multiplier,
70 const QString& suffix = QStringLiteral("")); 75 Settings::BasicSetting* other_setting = nullptr,
76 const QString& suffix = default_suffix);
71 virtual ~Widget(); 77 virtual ~Widget();
72 78
73 /** 79 /**
@@ -89,6 +95,7 @@ public:
89 QPushButton* restore_button{}; ///< Restore button for custom configurations 95 QPushButton* restore_button{}; ///< Restore button for custom configurations
90 QLineEdit* line_edit{}; ///< QLineEdit, used for LineEdit and HexEdit 96 QLineEdit* line_edit{}; ///< QLineEdit, used for LineEdit and HexEdit
91 QSpinBox* spinbox{}; 97 QSpinBox* spinbox{};
98 QDoubleSpinBox* double_spinbox{};
92 QCheckBox* checkbox{}; 99 QCheckBox* checkbox{};
93 QSlider* slider{}; 100 QSlider* slider{};
94 QComboBox* combobox{}; 101 QComboBox* combobox{};
@@ -126,6 +133,9 @@ private:
126 const std::function<void()>& touch); 133 const std::function<void()>& touch);
127 QWidget* CreateSpinBox(const QString& suffix, std::function<std::string()>& serializer, 134 QWidget* CreateSpinBox(const QString& suffix, std::function<std::string()>& serializer,
128 std::function<void()>& restore_func, const std::function<void()>& touch); 135 std::function<void()>& restore_func, const std::function<void()>& touch);
136 QWidget* CreateDoubleSpinBox(const QString& suffix, std::function<std::string()>& serializer,
137 std::function<void()>& restore_func,
138 const std::function<void()>& touch);
129 139
130 QWidget* parent; 140 QWidget* parent;
131 const TranslationMap& translations; 141 const TranslationMap& translations;
@@ -145,14 +155,15 @@ public:
145 Widget* BuildWidget(Settings::BasicSetting* setting, 155 Widget* BuildWidget(Settings::BasicSetting* setting,
146 std::vector<std::function<void(bool)>>& apply_funcs, 156 std::vector<std::function<void(bool)>>& apply_funcs,
147 RequestType request = RequestType::Default, bool managed = true, 157 RequestType request = RequestType::Default, bool managed = true,
148 float multiplier = 1.0f, Settings::BasicSetting* other_setting = nullptr, 158 float multiplier = default_multiplier,
149 const QString& suffix = QStringLiteral("")) const; 159 Settings::BasicSetting* other_setting = nullptr,
160 const QString& suffix = default_suffix) const;
150 161
151 Widget* BuildWidget(Settings::BasicSetting* setting, 162 Widget* BuildWidget(Settings::BasicSetting* setting,
152 std::vector<std::function<void(bool)>>& apply_funcs, 163 std::vector<std::function<void(bool)>>& apply_funcs,
153 Settings::BasicSetting* other_setting, 164 Settings::BasicSetting* other_setting,
154 RequestType request = RequestType::Default, 165 RequestType request = RequestType::Default,
155 const QString& suffix = QStringLiteral("")) const; 166 const QString& suffix = default_suffix) const;
156 167
157 const ComboboxTranslationMap& ComboboxTranslations() const; 168 const ComboboxTranslationMap& ComboboxTranslations() const;
158 169
diff --git a/src/yuzu/hotkeys.h b/src/yuzu/hotkeys.h
index 848239c35..56eee8d82 100644
--- a/src/yuzu/hotkeys.h
+++ b/src/yuzu/hotkeys.h
@@ -4,10 +4,12 @@
4#pragma once 4#pragma once
5 5
6#include <map> 6#include <map>
7#include <QKeySequence>
8#include <QString>
9#include <QWidget>
7#include "core/hid/hid_types.h" 10#include "core/hid/hid_types.h"
8 11
9class QDialog; 12class QDialog;
10class QKeySequence;
11class QSettings; 13class QSettings;
12class QShortcut; 14class QShortcut;
13class ControllerShortcut; 15class ControllerShortcut;
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 97d216638..d32aa9615 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -9,6 +9,7 @@
9#include <memory> 9#include <memory>
10#include <thread> 10#include <thread>
11#include "core/loader/nca.h" 11#include "core/loader/nca.h"
12#include "core/tools/renderdoc.h"
12#ifdef __APPLE__ 13#ifdef __APPLE__
13#include <unistd.h> // for chdir 14#include <unistd.h> // for chdir
14#endif 15#endif
@@ -1348,6 +1349,11 @@ void GMainWindow::InitializeHotkeys() {
1348 connect_shortcut(QStringLiteral("Toggle Framerate Limit"), [] { 1349 connect_shortcut(QStringLiteral("Toggle Framerate Limit"), [] {
1349 Settings::values.use_speed_limit.SetValue(!Settings::values.use_speed_limit.GetValue()); 1350 Settings::values.use_speed_limit.SetValue(!Settings::values.use_speed_limit.GetValue());
1350 }); 1351 });
1352 connect_shortcut(QStringLiteral("Toggle Renderdoc Capture"), [this] {
1353 if (Settings::values.enable_renderdoc_hotkey) {
1354 system->GetRenderdocAPI().ToggleCapture();
1355 }
1356 });
1351 connect_shortcut(QStringLiteral("Toggle Mouse Panning"), [&] { 1357 connect_shortcut(QStringLiteral("Toggle Mouse Panning"), [&] {
1352 if (Settings::values.mouse_enabled) { 1358 if (Settings::values.mouse_enabled) {
1353 Settings::values.mouse_panning = false; 1359 Settings::values.mouse_panning = false;
diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp
index c42d98709..0d25ff400 100644
--- a/src/yuzu_cmd/config.cpp
+++ b/src/yuzu_cmd/config.cpp
@@ -259,7 +259,7 @@ void Config::ReadValues() {
259 std::stringstream ss(title_list); 259 std::stringstream ss(title_list);
260 std::string line; 260 std::string line;
261 while (std::getline(ss, line, '|')) { 261 while (std::getline(ss, line, '|')) {
262 const auto title_id = std::stoul(line, nullptr, 16); 262 const auto title_id = std::strtoul(line.c_str(), nullptr, 16);
263 const auto disabled_list = sdl2_config->Get("AddOns", "disabled_" + line, ""); 263 const auto disabled_list = sdl2_config->Get("AddOns", "disabled_" + line, "");
264 264
265 std::stringstream inner_ss(disabled_list); 265 std::stringstream inner_ss(disabled_list);
diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp
index 55d0938f7..087cfaa26 100644
--- a/src/yuzu_cmd/yuzu.cpp
+++ b/src/yuzu_cmd/yuzu.cpp
@@ -264,8 +264,9 @@ int main(int argc, char** argv) {
264 nickname = match[1]; 264 nickname = match[1];
265 password = match[2]; 265 password = match[2];
266 address = match[3]; 266 address = match[3];
267 if (!match[4].str().empty()) 267 if (!match[4].str().empty()) {
268 port = static_cast<u16>(std::stoi(match[4])); 268 port = static_cast<u16>(std::strtoul(match[4].str().c_str(), nullptr, 0));
269 }
269 std::regex nickname_re("^[a-zA-Z0-9._\\- ]+$"); 270 std::regex nickname_re("^[a-zA-Z0-9._\\- ]+$");
270 if (!std::regex_match(nickname, nickname_re)) { 271 if (!std::regex_match(nickname, nickname_re)) {
271 std::cout 272 std::cout