diff options
Diffstat (limited to 'src')
135 files changed, 3091 insertions, 2614 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6068c7a1f..a9f68a8f2 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt | |||
| @@ -105,6 +105,8 @@ if (MSVC) | |||
| 105 | set(CMAKE_EXE_LINKER_FLAGS_RELEASE "/DEBUG /MANIFEST:NO /INCREMENTAL:NO /OPT:REF,ICF" CACHE STRING "" FORCE) | 105 | set(CMAKE_EXE_LINKER_FLAGS_RELEASE "/DEBUG /MANIFEST:NO /INCREMENTAL:NO /OPT:REF,ICF" CACHE STRING "" FORCE) |
| 106 | else() | 106 | else() |
| 107 | add_compile_options( | 107 | add_compile_options( |
| 108 | -fwrapv | ||
| 109 | |||
| 108 | -Werror=all | 110 | -Werror=all |
| 109 | -Werror=extra | 111 | -Werror=extra |
| 110 | -Werror=missing-declarations | 112 | -Werror=missing-declarations |
| @@ -129,7 +131,6 @@ else() | |||
| 129 | 131 | ||
| 130 | if (ARCHITECTURE_x86_64) | 132 | if (ARCHITECTURE_x86_64) |
| 131 | add_compile_options("-mcx16") | 133 | add_compile_options("-mcx16") |
| 132 | add_compile_options("-fwrapv") | ||
| 133 | endif() | 134 | endif() |
| 134 | 135 | ||
| 135 | if (APPLE AND CMAKE_CXX_COMPILER_ID STREQUAL Clang) | 136 | if (APPLE AND CMAKE_CXX_COMPILER_ID STREQUAL Clang) |
diff --git a/src/android/app/src/main/AndroidManifest.xml b/src/android/app/src/main/AndroidManifest.xml index 36e2dac98..832c08e15 100644 --- a/src/android/app/src/main/AndroidManifest.xml +++ b/src/android/app/src/main/AndroidManifest.xml | |||
| @@ -56,7 +56,6 @@ SPDX-License-Identifier: GPL-3.0-or-later | |||
| 56 | android:name="org.yuzu.yuzu_emu.activities.EmulationActivity" | 56 | android:name="org.yuzu.yuzu_emu.activities.EmulationActivity" |
| 57 | android:theme="@style/Theme.Yuzu.Main" | 57 | android:theme="@style/Theme.Yuzu.Main" |
| 58 | android:launchMode="singleTop" | 58 | android:launchMode="singleTop" |
| 59 | android:screenOrientation="userLandscape" | ||
| 60 | android:supportsPictureInPicture="true" | 59 | android:supportsPictureInPicture="true" |
| 61 | android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|uiMode" | 60 | android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|uiMode" |
| 62 | android:exported="true"> | 61 | android:exported="true"> |
| @@ -67,6 +66,14 @@ SPDX-License-Identifier: GPL-3.0-or-later | |||
| 67 | <data android:mimeType="application/octet-stream" /> | 66 | <data android:mimeType="application/octet-stream" /> |
| 68 | </intent-filter> | 67 | </intent-filter> |
| 69 | 68 | ||
| 69 | <intent-filter> | ||
| 70 | <action android:name="android.intent.action.VIEW" /> | ||
| 71 | <category android:name="android.intent.category.DEFAULT" /> | ||
| 72 | <data | ||
| 73 | android:mimeType="application/octet-stream" | ||
| 74 | android:scheme="content"/> | ||
| 75 | </intent-filter> | ||
| 76 | |||
| 70 | <meta-data | 77 | <meta-data |
| 71 | android:name="android.nfc.action.TECH_DISCOVERED" | 78 | android:name="android.nfc.action.TECH_DISCOVERED" |
| 72 | android:resource="@xml/nfc_tech_filter" /> | 79 | android:resource="@xml/nfc_tech_filter" /> |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt index 9c32e044c..c8706d7a6 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt | |||
| @@ -22,9 +22,7 @@ import org.yuzu.yuzu_emu.utils.FileUtil.exists | |||
| 22 | import org.yuzu.yuzu_emu.utils.FileUtil.getFileSize | 22 | import org.yuzu.yuzu_emu.utils.FileUtil.getFileSize |
| 23 | import org.yuzu.yuzu_emu.utils.FileUtil.isDirectory | 23 | import org.yuzu.yuzu_emu.utils.FileUtil.isDirectory |
| 24 | import org.yuzu.yuzu_emu.utils.FileUtil.openContentUri | 24 | import org.yuzu.yuzu_emu.utils.FileUtil.openContentUri |
| 25 | import org.yuzu.yuzu_emu.utils.Log.error | 25 | import org.yuzu.yuzu_emu.utils.Log |
| 26 | import org.yuzu.yuzu_emu.utils.Log.verbose | ||
| 27 | import org.yuzu.yuzu_emu.utils.Log.warning | ||
| 28 | import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable | 26 | import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable |
| 29 | 27 | ||
| 30 | /** | 28 | /** |
| @@ -219,10 +217,6 @@ object NativeLibrary { | |||
| 219 | 217 | ||
| 220 | external fun reloadSettings() | 218 | external fun reloadSettings() |
| 221 | 219 | ||
| 222 | external fun getUserSetting(gameID: String?, Section: String?, Key: String?): String? | ||
| 223 | |||
| 224 | external fun setUserSetting(gameID: String?, Section: String?, Key: String?, Value: String?) | ||
| 225 | |||
| 226 | external fun initGameIni(gameID: String?) | 220 | external fun initGameIni(gameID: String?) |
| 227 | 221 | ||
| 228 | /** | 222 | /** |
| @@ -413,14 +407,17 @@ object NativeLibrary { | |||
| 413 | details.ifEmpty { emulationActivity.getString(R.string.system_archive_general) } | 407 | details.ifEmpty { emulationActivity.getString(R.string.system_archive_general) } |
| 414 | ) | 408 | ) |
| 415 | } | 409 | } |
| 410 | |||
| 416 | CoreError.ErrorSavestate -> { | 411 | CoreError.ErrorSavestate -> { |
| 417 | title = emulationActivity.getString(R.string.save_load_error) | 412 | title = emulationActivity.getString(R.string.save_load_error) |
| 418 | message = details | 413 | message = details |
| 419 | } | 414 | } |
| 415 | |||
| 420 | CoreError.ErrorUnknown -> { | 416 | CoreError.ErrorUnknown -> { |
| 421 | title = emulationActivity.getString(R.string.fatal_error) | 417 | title = emulationActivity.getString(R.string.fatal_error) |
| 422 | message = emulationActivity.getString(R.string.fatal_error_message) | 418 | message = emulationActivity.getString(R.string.fatal_error_message) |
| 423 | } | 419 | } |
| 420 | |||
| 424 | else -> { | 421 | else -> { |
| 425 | return true | 422 | return true |
| 426 | } | 423 | } |
| @@ -454,6 +451,7 @@ object NativeLibrary { | |||
| 454 | captionId = R.string.loader_error_video_core | 451 | captionId = R.string.loader_error_video_core |
| 455 | descriptionId = R.string.loader_error_video_core_description | 452 | descriptionId = R.string.loader_error_video_core_description |
| 456 | } | 453 | } |
| 454 | |||
| 457 | else -> { | 455 | else -> { |
| 458 | captionId = R.string.loader_error_encrypted | 456 | captionId = R.string.loader_error_encrypted |
| 459 | descriptionId = R.string.loader_error_encrypted_roms_description | 457 | descriptionId = R.string.loader_error_encrypted_roms_description |
| @@ -465,7 +463,7 @@ object NativeLibrary { | |||
| 465 | 463 | ||
| 466 | val emulationActivity = sEmulationActivity.get() | 464 | val emulationActivity = sEmulationActivity.get() |
| 467 | if (emulationActivity == null) { | 465 | if (emulationActivity == null) { |
| 468 | warning("[NativeLibrary] EmulationActivity is null, can't exit.") | 466 | Log.warning("[NativeLibrary] EmulationActivity is null, can't exit.") |
| 469 | return | 467 | return |
| 470 | } | 468 | } |
| 471 | 469 | ||
| @@ -490,15 +488,27 @@ object NativeLibrary { | |||
| 490 | } | 488 | } |
| 491 | 489 | ||
| 492 | fun setEmulationActivity(emulationActivity: EmulationActivity?) { | 490 | fun setEmulationActivity(emulationActivity: EmulationActivity?) { |
| 493 | verbose("[NativeLibrary] Registering EmulationActivity.") | 491 | Log.verbose("[NativeLibrary] Registering EmulationActivity.") |
| 494 | sEmulationActivity = WeakReference(emulationActivity) | 492 | sEmulationActivity = WeakReference(emulationActivity) |
| 495 | } | 493 | } |
| 496 | 494 | ||
| 497 | fun clearEmulationActivity() { | 495 | fun clearEmulationActivity() { |
| 498 | verbose("[NativeLibrary] Unregistering EmulationActivity.") | 496 | Log.verbose("[NativeLibrary] Unregistering EmulationActivity.") |
| 499 | sEmulationActivity.clear() | 497 | sEmulationActivity.clear() |
| 500 | } | 498 | } |
| 501 | 499 | ||
| 500 | @Keep | ||
| 501 | @JvmStatic | ||
| 502 | fun onEmulationStarted() { | ||
| 503 | sEmulationActivity.get()!!.onEmulationStarted() | ||
| 504 | } | ||
| 505 | |||
| 506 | @Keep | ||
| 507 | @JvmStatic | ||
| 508 | fun onEmulationStopped(status: Int) { | ||
| 509 | sEmulationActivity.get()!!.onEmulationStopped(status) | ||
| 510 | } | ||
| 511 | |||
| 502 | /** | 512 | /** |
| 503 | * Logs the Yuzu version, Android version and, CPU. | 513 | * Logs the Yuzu version, Android version and, CPU. |
| 504 | */ | 514 | */ |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt index 04ab6a220..9561748cb 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt | |||
| @@ -46,7 +46,7 @@ class YuzuApplication : Application() { | |||
| 46 | super.onCreate() | 46 | super.onCreate() |
| 47 | application = this | 47 | application = this |
| 48 | documentsTree = DocumentsTree() | 48 | documentsTree = DocumentsTree() |
| 49 | DirectoryInitialization.start(applicationContext) | 49 | DirectoryInitialization.start() |
| 50 | GpuDriverHelper.initializeDriverParameters(applicationContext) | 50 | GpuDriverHelper.initializeDriverParameters(applicationContext) |
| 51 | NativeLibrary.logDeviceInfo() | 51 | NativeLibrary.logDeviceInfo() |
| 52 | 52 | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt index 7461fb093..bbd328c71 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt | |||
| @@ -42,7 +42,7 @@ import org.yuzu.yuzu_emu.databinding.ActivityEmulationBinding | |||
| 42 | import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting | 42 | import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting |
| 43 | import org.yuzu.yuzu_emu.features.settings.model.IntSetting | 43 | import org.yuzu.yuzu_emu.features.settings.model.IntSetting |
| 44 | import org.yuzu.yuzu_emu.features.settings.model.Settings | 44 | import org.yuzu.yuzu_emu.features.settings.model.Settings |
| 45 | import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel | 45 | import org.yuzu.yuzu_emu.model.EmulationViewModel |
| 46 | import org.yuzu.yuzu_emu.model.Game | 46 | import org.yuzu.yuzu_emu.model.Game |
| 47 | import org.yuzu.yuzu_emu.utils.ControllerMappingHelper | 47 | import org.yuzu.yuzu_emu.utils.ControllerMappingHelper |
| 48 | import org.yuzu.yuzu_emu.utils.ForegroundService | 48 | import org.yuzu.yuzu_emu.utils.ForegroundService |
| @@ -72,18 +72,17 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener { | |||
| 72 | private val actionMute = "ACTION_EMULATOR_MUTE" | 72 | private val actionMute = "ACTION_EMULATOR_MUTE" |
| 73 | private val actionUnmute = "ACTION_EMULATOR_UNMUTE" | 73 | private val actionUnmute = "ACTION_EMULATOR_UNMUTE" |
| 74 | 74 | ||
| 75 | private val settingsViewModel: SettingsViewModel by viewModels() | 75 | private val emulationViewModel: EmulationViewModel by viewModels() |
| 76 | 76 | ||
| 77 | override fun onDestroy() { | 77 | override fun onDestroy() { |
| 78 | stopForegroundService(this) | 78 | stopForegroundService(this) |
| 79 | emulationViewModel.clear() | ||
| 79 | super.onDestroy() | 80 | super.onDestroy() |
| 80 | } | 81 | } |
| 81 | 82 | ||
| 82 | override fun onCreate(savedInstanceState: Bundle?) { | 83 | override fun onCreate(savedInstanceState: Bundle?) { |
| 83 | ThemeHelper.setTheme(this) | 84 | ThemeHelper.setTheme(this) |
| 84 | 85 | ||
| 85 | settingsViewModel.settings.loadSettings() | ||
| 86 | |||
| 87 | super.onCreate(savedInstanceState) | 86 | super.onCreate(savedInstanceState) |
| 88 | 87 | ||
| 89 | binding = ActivityEmulationBinding.inflate(layoutInflater) | 88 | binding = ActivityEmulationBinding.inflate(layoutInflater) |
| @@ -91,9 +90,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener { | |||
| 91 | 90 | ||
| 92 | val navHostFragment = | 91 | val navHostFragment = |
| 93 | supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment | 92 | supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment |
| 94 | val navController = navHostFragment.navController | 93 | navHostFragment.navController.setGraph(R.navigation.emulation_navigation, intent.extras) |
| 95 | navController | ||
| 96 | .setGraph(R.navigation.emulation_navigation, intent.extras) | ||
| 97 | 94 | ||
| 98 | isActivityRecreated = savedInstanceState != null | 95 | isActivityRecreated = savedInstanceState != null |
| 99 | 96 | ||
| @@ -424,6 +421,16 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener { | |||
| 424 | } | 421 | } |
| 425 | } | 422 | } |
| 426 | 423 | ||
| 424 | fun onEmulationStarted() { | ||
| 425 | emulationViewModel.setEmulationStarted(true) | ||
| 426 | } | ||
| 427 | |||
| 428 | fun onEmulationStopped(status: Int) { | ||
| 429 | if (status == 0) { | ||
| 430 | finish() | ||
| 431 | } | ||
| 432 | } | ||
| 433 | |||
| 427 | private fun startMotionSensorListener() { | 434 | private fun startMotionSensorListener() { |
| 428 | val sensorManager = this.getSystemService(Context.SENSOR_SERVICE) as SensorManager | 435 | val sensorManager = this.getSystemService(Context.SENSOR_SERVICE) as SensorManager |
| 429 | val gyroSensor = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE) | 436 | val gyroSensor = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE) |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt index e91277d35..0013e8512 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt | |||
| @@ -3,8 +3,8 @@ | |||
| 3 | 3 | ||
| 4 | package org.yuzu.yuzu_emu.adapters | 4 | package org.yuzu.yuzu_emu.adapters |
| 5 | 5 | ||
| 6 | import android.graphics.Bitmap | 6 | import android.content.Intent |
| 7 | import android.graphics.BitmapFactory | 7 | import android.graphics.drawable.BitmapDrawable |
| 8 | import android.net.Uri | 8 | import android.net.Uri |
| 9 | import android.text.TextUtils | 9 | import android.text.TextUtils |
| 10 | import android.view.LayoutInflater | 10 | import android.view.LayoutInflater |
| @@ -13,25 +13,26 @@ import android.view.ViewGroup | |||
| 13 | import android.widget.ImageView | 13 | import android.widget.ImageView |
| 14 | import android.widget.Toast | 14 | import android.widget.Toast |
| 15 | import androidx.appcompat.app.AppCompatActivity | 15 | import androidx.appcompat.app.AppCompatActivity |
| 16 | import androidx.core.content.pm.ShortcutInfoCompat | ||
| 17 | import androidx.core.content.pm.ShortcutManagerCompat | ||
| 18 | import androidx.core.graphics.drawable.IconCompat | ||
| 16 | import androidx.documentfile.provider.DocumentFile | 19 | import androidx.documentfile.provider.DocumentFile |
| 17 | import androidx.lifecycle.ViewModelProvider | 20 | import androidx.lifecycle.ViewModelProvider |
| 18 | import androidx.lifecycle.lifecycleScope | ||
| 19 | import androidx.navigation.findNavController | 21 | import androidx.navigation.findNavController |
| 20 | import androidx.preference.PreferenceManager | 22 | import androidx.preference.PreferenceManager |
| 21 | import androidx.recyclerview.widget.AsyncDifferConfig | 23 | import androidx.recyclerview.widget.AsyncDifferConfig |
| 22 | import androidx.recyclerview.widget.DiffUtil | 24 | import androidx.recyclerview.widget.DiffUtil |
| 23 | import androidx.recyclerview.widget.ListAdapter | 25 | import androidx.recyclerview.widget.ListAdapter |
| 24 | import androidx.recyclerview.widget.RecyclerView | 26 | import androidx.recyclerview.widget.RecyclerView |
| 25 | import coil.load | ||
| 26 | import kotlinx.coroutines.launch | ||
| 27 | import org.yuzu.yuzu_emu.HomeNavigationDirections | 27 | import org.yuzu.yuzu_emu.HomeNavigationDirections |
| 28 | import org.yuzu.yuzu_emu.NativeLibrary | ||
| 29 | import org.yuzu.yuzu_emu.R | 28 | import org.yuzu.yuzu_emu.R |
| 30 | import org.yuzu.yuzu_emu.YuzuApplication | 29 | import org.yuzu.yuzu_emu.YuzuApplication |
| 30 | import org.yuzu.yuzu_emu.activities.EmulationActivity | ||
| 31 | import org.yuzu.yuzu_emu.adapters.GameAdapter.GameViewHolder | 31 | import org.yuzu.yuzu_emu.adapters.GameAdapter.GameViewHolder |
| 32 | import org.yuzu.yuzu_emu.databinding.CardGameBinding | 32 | import org.yuzu.yuzu_emu.databinding.CardGameBinding |
| 33 | import org.yuzu.yuzu_emu.model.Game | 33 | import org.yuzu.yuzu_emu.model.Game |
| 34 | import org.yuzu.yuzu_emu.model.GamesViewModel | 34 | import org.yuzu.yuzu_emu.model.GamesViewModel |
| 35 | import org.yuzu.yuzu_emu.utils.GameIconUtils | ||
| 35 | 36 | ||
| 36 | class GameAdapter(private val activity: AppCompatActivity) : | 37 | class GameAdapter(private val activity: AppCompatActivity) : |
| 37 | ListAdapter<Game, GameViewHolder>(AsyncDifferConfig.Builder(DiffCallback()).build()), | 38 | ListAdapter<Game, GameViewHolder>(AsyncDifferConfig.Builder(DiffCallback()).build()), |
| @@ -82,6 +83,21 @@ class GameAdapter(private val activity: AppCompatActivity) : | |||
| 82 | ) | 83 | ) |
| 83 | .apply() | 84 | .apply() |
| 84 | 85 | ||
| 86 | val openIntent = Intent(YuzuApplication.appContext, EmulationActivity::class.java).apply { | ||
| 87 | action = Intent.ACTION_VIEW | ||
| 88 | data = Uri.parse(holder.game.path) | ||
| 89 | } | ||
| 90 | val shortcut = ShortcutInfoCompat.Builder(YuzuApplication.appContext, holder.game.path) | ||
| 91 | .setShortLabel(holder.game.title) | ||
| 92 | .setIcon( | ||
| 93 | IconCompat.createWithBitmap( | ||
| 94 | (holder.binding.imageGameScreen.drawable as BitmapDrawable).bitmap | ||
| 95 | ) | ||
| 96 | ) | ||
| 97 | .setIntent(openIntent) | ||
| 98 | .build() | ||
| 99 | ShortcutManagerCompat.pushDynamicShortcut(YuzuApplication.appContext, shortcut) | ||
| 100 | |||
| 85 | val action = HomeNavigationDirections.actionGlobalEmulationActivity(holder.game) | 101 | val action = HomeNavigationDirections.actionGlobalEmulationActivity(holder.game) |
| 86 | view.findNavController().navigate(action) | 102 | view.findNavController().navigate(action) |
| 87 | } | 103 | } |
| @@ -98,12 +114,7 @@ class GameAdapter(private val activity: AppCompatActivity) : | |||
| 98 | this.game = game | 114 | this.game = game |
| 99 | 115 | ||
| 100 | binding.imageGameScreen.scaleType = ImageView.ScaleType.CENTER_CROP | 116 | binding.imageGameScreen.scaleType = ImageView.ScaleType.CENTER_CROP |
| 101 | activity.lifecycleScope.launch { | 117 | GameIconUtils.loadGameIcon(game, binding.imageGameScreen) |
| 102 | val bitmap = decodeGameIcon(game.path) | ||
| 103 | binding.imageGameScreen.load(bitmap) { | ||
| 104 | error(R.drawable.default_icon) | ||
| 105 | } | ||
| 106 | } | ||
| 107 | 118 | ||
| 108 | binding.textGameTitle.text = game.title.replace("[\\t\\n\\r]+".toRegex(), " ") | 119 | binding.textGameTitle.text = game.title.replace("[\\t\\n\\r]+".toRegex(), " ") |
| 109 | 120 | ||
| @@ -126,14 +137,4 @@ class GameAdapter(private val activity: AppCompatActivity) : | |||
| 126 | return oldItem == newItem | 137 | return oldItem == newItem |
| 127 | } | 138 | } |
| 128 | } | 139 | } |
| 129 | |||
| 130 | private fun decodeGameIcon(uri: String): Bitmap? { | ||
| 131 | val data = NativeLibrary.getIcon(uri) | ||
| 132 | return BitmapFactory.decodeByteArray( | ||
| 133 | data, | ||
| 134 | 0, | ||
| 135 | data.size, | ||
| 136 | BitmapFactory.Options() | ||
| 137 | ) | ||
| 138 | } | ||
| 139 | } | 140 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt index 9f859b442..8d87d3bd7 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt | |||
| @@ -45,8 +45,8 @@ class HomeSettingAdapter( | |||
| 45 | holder.option.onClick.invoke() | 45 | holder.option.onClick.invoke() |
| 46 | } else { | 46 | } else { |
| 47 | MessageDialogFragment.newInstance( | 47 | MessageDialogFragment.newInstance( |
| 48 | holder.option.disabledTitleId, | 48 | titleId = holder.option.disabledTitleId, |
| 49 | holder.option.disabledMessageId | 49 | descriptionId = holder.option.disabledMessageId |
| 50 | ).show(activity.supportFragmentManager, MessageDialogFragment.TAG) | 50 | ).show(activity.supportFragmentManager, MessageDialogFragment.TAG) |
| 51 | } | 51 | } |
| 52 | } | 52 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/DiskShaderCacheProgress.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/DiskShaderCacheProgress.kt index a18efef19..6f4b5b13f 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/DiskShaderCacheProgress.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/DiskShaderCacheProgress.kt | |||
| @@ -4,43 +4,43 @@ | |||
| 4 | package org.yuzu.yuzu_emu.disk_shader_cache | 4 | package org.yuzu.yuzu_emu.disk_shader_cache |
| 5 | 5 | ||
| 6 | import androidx.annotation.Keep | 6 | import androidx.annotation.Keep |
| 7 | import androidx.lifecycle.ViewModelProvider | ||
| 7 | import org.yuzu.yuzu_emu.NativeLibrary | 8 | import org.yuzu.yuzu_emu.NativeLibrary |
| 8 | import org.yuzu.yuzu_emu.R | 9 | import org.yuzu.yuzu_emu.R |
| 9 | import org.yuzu.yuzu_emu.disk_shader_cache.ui.ShaderProgressDialogFragment | 10 | import org.yuzu.yuzu_emu.activities.EmulationActivity |
| 11 | import org.yuzu.yuzu_emu.model.EmulationViewModel | ||
| 12 | import org.yuzu.yuzu_emu.utils.Log | ||
| 10 | 13 | ||
| 11 | @Keep | 14 | @Keep |
| 12 | object DiskShaderCacheProgress { | 15 | object DiskShaderCacheProgress { |
| 13 | val finishLock = Object() | 16 | private lateinit var emulationViewModel: EmulationViewModel |
| 14 | private lateinit var fragment: ShaderProgressDialogFragment | ||
| 15 | 17 | ||
| 16 | private fun prepareDialog() { | 18 | private fun prepareViewModel() { |
| 17 | val emulationActivity = NativeLibrary.sEmulationActivity.get()!! | 19 | emulationViewModel = |
| 18 | emulationActivity.runOnUiThread { | 20 | ViewModelProvider( |
| 19 | fragment = ShaderProgressDialogFragment.newInstance( | 21 | NativeLibrary.sEmulationActivity.get() as EmulationActivity |
| 20 | emulationActivity.getString(R.string.loading), | 22 | )[EmulationViewModel::class.java] |
| 21 | emulationActivity.getString(R.string.preparing_shaders) | ||
| 22 | ) | ||
| 23 | fragment.show( | ||
| 24 | emulationActivity.supportFragmentManager, | ||
| 25 | ShaderProgressDialogFragment.TAG | ||
| 26 | ) | ||
| 27 | } | ||
| 28 | synchronized(finishLock) { finishLock.wait() } | ||
| 29 | } | 23 | } |
| 30 | 24 | ||
| 31 | @JvmStatic | 25 | @JvmStatic |
| 32 | fun loadProgress(stage: Int, progress: Int, max: Int) { | 26 | fun loadProgress(stage: Int, progress: Int, max: Int) { |
| 33 | val emulationActivity = NativeLibrary.sEmulationActivity.get() | 27 | val emulationActivity = NativeLibrary.sEmulationActivity.get() |
| 34 | ?: error("[DiskShaderCacheProgress] EmulationActivity not present") | 28 | if (emulationActivity == null) { |
| 35 | 29 | Log.error("[DiskShaderCacheProgress] EmulationActivity not present") | |
| 36 | when (LoadCallbackStage.values()[stage]) { | 30 | return |
| 37 | LoadCallbackStage.Prepare -> prepareDialog() | 31 | } |
| 38 | LoadCallbackStage.Build -> fragment.onUpdateProgress( | 32 | |
| 39 | emulationActivity.getString(R.string.building_shaders), | 33 | emulationActivity.runOnUiThread { |
| 40 | progress, | 34 | when (LoadCallbackStage.values()[stage]) { |
| 41 | max | 35 | LoadCallbackStage.Prepare -> prepareViewModel() |
| 42 | ) | 36 | LoadCallbackStage.Build -> emulationViewModel.updateProgress( |
| 43 | LoadCallbackStage.Complete -> fragment.dismiss() | 37 | emulationActivity.getString(R.string.building_shaders), |
| 38 | progress, | ||
| 39 | max | ||
| 40 | ) | ||
| 41 | |||
| 42 | LoadCallbackStage.Complete -> {} | ||
| 43 | } | ||
| 44 | } | 44 | } |
| 45 | } | 45 | } |
| 46 | 46 | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/ShaderProgressViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/ShaderProgressViewModel.kt deleted file mode 100644 index bf6f0366d..000000000 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/ShaderProgressViewModel.kt +++ /dev/null | |||
| @@ -1,31 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | package org.yuzu.yuzu_emu.disk_shader_cache | ||
| 5 | |||
| 6 | import androidx.lifecycle.LiveData | ||
| 7 | import androidx.lifecycle.MutableLiveData | ||
| 8 | import androidx.lifecycle.ViewModel | ||
| 9 | |||
| 10 | class ShaderProgressViewModel : ViewModel() { | ||
| 11 | private val _progress = MutableLiveData(0) | ||
| 12 | val progress: LiveData<Int> get() = _progress | ||
| 13 | |||
| 14 | private val _max = MutableLiveData(0) | ||
| 15 | val max: LiveData<Int> get() = _max | ||
| 16 | |||
| 17 | private val _message = MutableLiveData("") | ||
| 18 | val message: LiveData<String> get() = _message | ||
| 19 | |||
| 20 | fun setProgress(progress: Int) { | ||
| 21 | _progress.postValue(progress) | ||
| 22 | } | ||
| 23 | |||
| 24 | fun setMax(max: Int) { | ||
| 25 | _max.postValue(max) | ||
| 26 | } | ||
| 27 | |||
| 28 | fun setMessage(msg: String) { | ||
| 29 | _message.postValue(msg) | ||
| 30 | } | ||
| 31 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/ui/ShaderProgressDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/ui/ShaderProgressDialogFragment.kt deleted file mode 100644 index 8a8e0a6e8..000000000 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/ui/ShaderProgressDialogFragment.kt +++ /dev/null | |||
| @@ -1,103 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | package org.yuzu.yuzu_emu.disk_shader_cache.ui | ||
| 5 | |||
| 6 | import android.app.Dialog | ||
| 7 | import android.os.Bundle | ||
| 8 | import android.view.LayoutInflater | ||
| 9 | import android.view.View | ||
| 10 | import android.view.ViewGroup | ||
| 11 | import androidx.appcompat.app.AlertDialog | ||
| 12 | import androidx.fragment.app.DialogFragment | ||
| 13 | import androidx.lifecycle.ViewModelProvider | ||
| 14 | import com.google.android.material.dialog.MaterialAlertDialogBuilder | ||
| 15 | import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding | ||
| 16 | import org.yuzu.yuzu_emu.disk_shader_cache.DiskShaderCacheProgress | ||
| 17 | import org.yuzu.yuzu_emu.disk_shader_cache.ShaderProgressViewModel | ||
| 18 | |||
| 19 | class ShaderProgressDialogFragment : DialogFragment() { | ||
| 20 | private var _binding: DialogProgressBarBinding? = null | ||
| 21 | private val binding get() = _binding!! | ||
| 22 | |||
| 23 | private lateinit var alertDialog: AlertDialog | ||
| 24 | |||
| 25 | private lateinit var shaderProgressViewModel: ShaderProgressViewModel | ||
| 26 | |||
| 27 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { | ||
| 28 | _binding = DialogProgressBarBinding.inflate(layoutInflater) | ||
| 29 | shaderProgressViewModel = | ||
| 30 | ViewModelProvider(requireActivity())[ShaderProgressViewModel::class.java] | ||
| 31 | |||
| 32 | val title = requireArguments().getString(TITLE) | ||
| 33 | val message = requireArguments().getString(MESSAGE) | ||
| 34 | |||
| 35 | isCancelable = false | ||
| 36 | alertDialog = MaterialAlertDialogBuilder(requireActivity()) | ||
| 37 | .setView(binding.root) | ||
| 38 | .setTitle(title) | ||
| 39 | .setMessage(message) | ||
| 40 | .create() | ||
| 41 | return alertDialog | ||
| 42 | } | ||
| 43 | |||
| 44 | override fun onCreateView( | ||
| 45 | inflater: LayoutInflater, | ||
| 46 | container: ViewGroup?, | ||
| 47 | savedInstanceState: Bundle? | ||
| 48 | ): View { | ||
| 49 | return binding.root | ||
| 50 | } | ||
| 51 | |||
| 52 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | ||
| 53 | super.onViewCreated(view, savedInstanceState) | ||
| 54 | shaderProgressViewModel.progress.observe(viewLifecycleOwner) { progress -> | ||
| 55 | binding.progressBar.progress = progress | ||
| 56 | setUpdateText() | ||
| 57 | } | ||
| 58 | shaderProgressViewModel.max.observe(viewLifecycleOwner) { max -> | ||
| 59 | binding.progressBar.max = max | ||
| 60 | setUpdateText() | ||
| 61 | } | ||
| 62 | shaderProgressViewModel.message.observe(viewLifecycleOwner) { msg -> | ||
| 63 | alertDialog.setMessage(msg) | ||
| 64 | } | ||
| 65 | synchronized(DiskShaderCacheProgress.finishLock) { | ||
| 66 | DiskShaderCacheProgress.finishLock.notifyAll() | ||
| 67 | } | ||
| 68 | } | ||
| 69 | |||
| 70 | override fun onDestroyView() { | ||
| 71 | super.onDestroyView() | ||
| 72 | _binding = null | ||
| 73 | } | ||
| 74 | |||
| 75 | fun onUpdateProgress(msg: String, progress: Int, max: Int) { | ||
| 76 | shaderProgressViewModel.setProgress(progress) | ||
| 77 | shaderProgressViewModel.setMax(max) | ||
| 78 | shaderProgressViewModel.setMessage(msg) | ||
| 79 | } | ||
| 80 | |||
| 81 | private fun setUpdateText() { | ||
| 82 | binding.progressText.text = String.format( | ||
| 83 | "%d/%d", | ||
| 84 | shaderProgressViewModel.progress.value, | ||
| 85 | shaderProgressViewModel.max.value | ||
| 86 | ) | ||
| 87 | } | ||
| 88 | |||
| 89 | companion object { | ||
| 90 | const val TAG = "ProgressDialogFragment" | ||
| 91 | const val TITLE = "title" | ||
| 92 | const val MESSAGE = "message" | ||
| 93 | |||
| 94 | fun newInstance(title: String, message: String): ShaderProgressDialogFragment { | ||
| 95 | val frag = ShaderProgressDialogFragment() | ||
| 96 | val args = Bundle() | ||
| 97 | args.putString(TITLE, title) | ||
| 98 | args.putString(MESSAGE, message) | ||
| 99 | frag.arguments = args | ||
| 100 | return frag | ||
| 101 | } | ||
| 102 | } | ||
| 103 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractBooleanSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractBooleanSetting.kt index a6e9833ee..aeda8d222 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractBooleanSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractBooleanSetting.kt | |||
| @@ -4,5 +4,7 @@ | |||
| 4 | package org.yuzu.yuzu_emu.features.settings.model | 4 | package org.yuzu.yuzu_emu.features.settings.model |
| 5 | 5 | ||
| 6 | interface AbstractBooleanSetting : AbstractSetting { | 6 | interface AbstractBooleanSetting : AbstractSetting { |
| 7 | var boolean: Boolean | 7 | val boolean: Boolean |
| 8 | |||
| 9 | fun setBoolean(value: Boolean) | ||
| 8 | } | 10 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/SettingsViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractByteSetting.kt index bd9233d62..606519ad8 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/SettingsViewModel.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractByteSetting.kt | |||
| @@ -3,8 +3,8 @@ | |||
| 3 | 3 | ||
| 4 | package org.yuzu.yuzu_emu.features.settings.model | 4 | package org.yuzu.yuzu_emu.features.settings.model |
| 5 | 5 | ||
| 6 | import androidx.lifecycle.ViewModel | 6 | interface AbstractByteSetting : AbstractSetting { |
| 7 | val byte: Byte | ||
| 7 | 8 | ||
| 8 | class SettingsViewModel : ViewModel() { | 9 | fun setByte(value: Byte) |
| 9 | val settings = Settings() | ||
| 10 | } | 10 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractFloatSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractFloatSetting.kt index 6fe4bc263..974925eed 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractFloatSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractFloatSetting.kt | |||
| @@ -4,5 +4,7 @@ | |||
| 4 | package org.yuzu.yuzu_emu.features.settings.model | 4 | package org.yuzu.yuzu_emu.features.settings.model |
| 5 | 5 | ||
| 6 | interface AbstractFloatSetting : AbstractSetting { | 6 | interface AbstractFloatSetting : AbstractSetting { |
| 7 | var float: Float | 7 | val float: Float |
| 8 | |||
| 9 | fun setFloat(value: Float) | ||
| 8 | } | 10 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractIntSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractIntSetting.kt index 892b7dcfe..89b285b10 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractIntSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractIntSetting.kt | |||
| @@ -4,5 +4,7 @@ | |||
| 4 | package org.yuzu.yuzu_emu.features.settings.model | 4 | package org.yuzu.yuzu_emu.features.settings.model |
| 5 | 5 | ||
| 6 | interface AbstractIntSetting : AbstractSetting { | 6 | interface AbstractIntSetting : AbstractSetting { |
| 7 | var int: Int | 7 | val int: Int |
| 8 | |||
| 9 | fun setInt(value: Int) | ||
| 8 | } | 10 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractLongSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractLongSetting.kt new file mode 100644 index 000000000..4873942db --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractLongSetting.kt | |||
| @@ -0,0 +1,10 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | package org.yuzu.yuzu_emu.features.settings.model | ||
| 5 | |||
| 6 | interface AbstractLongSetting : AbstractSetting { | ||
| 7 | val long: Long | ||
| 8 | |||
| 9 | fun setLong(value: Long) | ||
| 10 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractSetting.kt index 258580209..8b6d29fe5 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractSetting.kt | |||
| @@ -3,10 +3,22 @@ | |||
| 3 | 3 | ||
| 4 | package org.yuzu.yuzu_emu.features.settings.model | 4 | package org.yuzu.yuzu_emu.features.settings.model |
| 5 | 5 | ||
| 6 | import org.yuzu.yuzu_emu.utils.NativeConfig | ||
| 7 | |||
| 6 | interface AbstractSetting { | 8 | interface AbstractSetting { |
| 7 | val key: String? | 9 | val key: String |
| 8 | val section: String? | 10 | val category: Settings.Category |
| 9 | val isRuntimeEditable: Boolean | ||
| 10 | val valueAsString: String | ||
| 11 | val defaultValue: Any | 11 | val defaultValue: Any |
| 12 | val androidDefault: Any? | ||
| 13 | get() = null | ||
| 14 | val valueAsString: String | ||
| 15 | get() = "" | ||
| 16 | |||
| 17 | val isRuntimeModifiable: Boolean | ||
| 18 | get() = NativeConfig.getIsRuntimeModifiable(key) | ||
| 19 | |||
| 20 | val pairedSettingKey: String | ||
| 21 | get() = NativeConfig.getPairedSettingKey(key) | ||
| 22 | |||
| 23 | fun reset() | ||
| 12 | } | 24 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractShortSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractShortSetting.kt new file mode 100644 index 000000000..91407ccbb --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractShortSetting.kt | |||
| @@ -0,0 +1,10 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | package org.yuzu.yuzu_emu.features.settings.model | ||
| 5 | |||
| 6 | interface AbstractShortSetting : AbstractSetting { | ||
| 7 | val short: Short | ||
| 8 | |||
| 9 | fun setShort(value: Short) | ||
| 10 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractStringSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractStringSetting.kt index 0d02c5997..c8935cc48 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractStringSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractStringSetting.kt | |||
| @@ -4,5 +4,7 @@ | |||
| 4 | package org.yuzu.yuzu_emu.features.settings.model | 4 | package org.yuzu.yuzu_emu.features.settings.model |
| 5 | 5 | ||
| 6 | interface AbstractStringSetting : AbstractSetting { | 6 | interface AbstractStringSetting : AbstractSetting { |
| 7 | var string: String | 7 | val string: String |
| 8 | |||
| 9 | fun setString(value: String) | ||
| 8 | } | 10 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt index d41933766..e0c0538c7 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt | |||
| @@ -3,41 +3,37 @@ | |||
| 3 | 3 | ||
| 4 | package org.yuzu.yuzu_emu.features.settings.model | 4 | package org.yuzu.yuzu_emu.features.settings.model |
| 5 | 5 | ||
| 6 | import org.yuzu.yuzu_emu.utils.NativeConfig | ||
| 7 | |||
| 6 | enum class BooleanSetting( | 8 | enum class BooleanSetting( |
| 7 | override val key: String, | 9 | override val key: String, |
| 8 | override val section: String, | 10 | override val category: Settings.Category, |
| 9 | override val defaultValue: Boolean | 11 | override val androidDefault: Boolean? = null |
| 10 | ) : AbstractBooleanSetting { | 12 | ) : AbstractBooleanSetting { |
| 11 | CPU_DEBUG_MODE("cpu_debug_mode", Settings.SECTION_CPU, false), | 13 | CPU_DEBUG_MODE("cpu_debug_mode", Settings.Category.Cpu), |
| 12 | FASTMEM("cpuopt_fastmem", Settings.SECTION_CPU, true), | 14 | FASTMEM("cpuopt_fastmem", Settings.Category.Cpu), |
| 13 | FASTMEM_EXCLUSIVES("cpuopt_fastmem_exclusives", Settings.SECTION_CPU, true), | 15 | FASTMEM_EXCLUSIVES("cpuopt_fastmem_exclusives", Settings.Category.Cpu), |
| 14 | PICTURE_IN_PICTURE("picture_in_picture", Settings.SECTION_GENERAL, true), | 16 | RENDERER_USE_SPEED_LIMIT("use_speed_limit", Settings.Category.Core), |
| 15 | USE_CUSTOM_RTC("custom_rtc_enabled", Settings.SECTION_SYSTEM, false); | 17 | USE_DOCKED_MODE("use_docked_mode", Settings.Category.System, false), |
| 16 | 18 | RENDERER_USE_DISK_SHADER_CACHE("use_disk_shader_cache", Settings.Category.Renderer), | |
| 17 | override var boolean: Boolean = defaultValue | 19 | RENDERER_FORCE_MAX_CLOCK("force_max_clock", Settings.Category.Renderer), |
| 20 | RENDERER_ASYNCHRONOUS_SHADERS("use_asynchronous_shaders", Settings.Category.Renderer), | ||
| 21 | RENDERER_REACTIVE_FLUSHING("use_reactive_flushing", Settings.Category.Renderer, false), | ||
| 22 | RENDERER_DEBUG("debug", Settings.Category.Renderer), | ||
| 23 | PICTURE_IN_PICTURE("picture_in_picture", Settings.Category.Android), | ||
| 24 | USE_CUSTOM_RTC("custom_rtc_enabled", Settings.Category.System); | ||
| 25 | |||
| 26 | override val boolean: Boolean | ||
| 27 | get() = NativeConfig.getBoolean(key, false) | ||
| 28 | |||
| 29 | override fun setBoolean(value: Boolean) = NativeConfig.setBoolean(key, value) | ||
| 30 | |||
| 31 | override val defaultValue: Boolean by lazy { | ||
| 32 | androidDefault ?: NativeConfig.getBoolean(key, true) | ||
| 33 | } | ||
| 18 | 34 | ||
| 19 | override val valueAsString: String | 35 | override val valueAsString: String |
| 20 | get() = boolean.toString() | 36 | get() = if (boolean) "1" else "0" |
| 21 | 37 | ||
| 22 | override val isRuntimeEditable: Boolean | 38 | override fun reset() = NativeConfig.setBoolean(key, defaultValue) |
| 23 | get() { | ||
| 24 | for (setting in NOT_RUNTIME_EDITABLE) { | ||
| 25 | if (setting == this) { | ||
| 26 | return false | ||
| 27 | } | ||
| 28 | } | ||
| 29 | return true | ||
| 30 | } | ||
| 31 | |||
| 32 | companion object { | ||
| 33 | private val NOT_RUNTIME_EDITABLE = listOf( | ||
| 34 | PICTURE_IN_PICTURE, | ||
| 35 | USE_CUSTOM_RTC | ||
| 36 | ) | ||
| 37 | |||
| 38 | fun from(key: String): BooleanSetting? = | ||
| 39 | BooleanSetting.values().firstOrNull { it.key == key } | ||
| 40 | |||
| 41 | fun clear() = BooleanSetting.values().forEach { it.boolean = it.defaultValue } | ||
| 42 | } | ||
| 43 | } | 39 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ByteSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ByteSetting.kt new file mode 100644 index 000000000..6ec0a765e --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ByteSetting.kt | |||
| @@ -0,0 +1,25 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | package org.yuzu.yuzu_emu.features.settings.model | ||
| 5 | |||
| 6 | import org.yuzu.yuzu_emu.utils.NativeConfig | ||
| 7 | |||
| 8 | enum class ByteSetting( | ||
| 9 | override val key: String, | ||
| 10 | override val category: Settings.Category | ||
| 11 | ) : AbstractByteSetting { | ||
| 12 | AUDIO_VOLUME("volume", Settings.Category.Audio); | ||
| 13 | |||
| 14 | override val byte: Byte | ||
| 15 | get() = NativeConfig.getByte(key, false) | ||
| 16 | |||
| 17 | override fun setByte(value: Byte) = NativeConfig.setByte(key, value) | ||
| 18 | |||
| 19 | override val defaultValue: Byte by lazy { NativeConfig.getByte(key, true) } | ||
| 20 | |||
| 21 | override val valueAsString: String | ||
| 22 | get() = byte.toString() | ||
| 23 | |||
| 24 | override fun reset() = NativeConfig.setByte(key, defaultValue) | ||
| 25 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/FloatSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/FloatSetting.kt index e5545a916..0181d06f2 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/FloatSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/FloatSetting.kt | |||
| @@ -3,34 +3,24 @@ | |||
| 3 | 3 | ||
| 4 | package org.yuzu.yuzu_emu.features.settings.model | 4 | package org.yuzu.yuzu_emu.features.settings.model |
| 5 | 5 | ||
| 6 | import org.yuzu.yuzu_emu.utils.NativeConfig | ||
| 7 | |||
| 6 | enum class FloatSetting( | 8 | enum class FloatSetting( |
| 7 | override val key: String, | 9 | override val key: String, |
| 8 | override val section: String, | 10 | override val category: Settings.Category |
| 9 | override val defaultValue: Float | ||
| 10 | ) : AbstractFloatSetting { | 11 | ) : AbstractFloatSetting { |
| 11 | // No float settings currently exist | 12 | // No float settings currently exist |
| 12 | EMPTY_SETTING("", "", 0f); | 13 | EMPTY_SETTING("", Settings.Category.UiGeneral); |
| 13 | |||
| 14 | override var float: Float = defaultValue | ||
| 15 | 14 | ||
| 16 | override val valueAsString: String | 15 | override val float: Float |
| 17 | get() = float.toString() | 16 | get() = NativeConfig.getFloat(key, false) |
| 18 | 17 | ||
| 19 | override val isRuntimeEditable: Boolean | 18 | override fun setFloat(value: Float) = NativeConfig.setFloat(key, value) |
| 20 | get() { | ||
| 21 | for (setting in NOT_RUNTIME_EDITABLE) { | ||
| 22 | if (setting == this) { | ||
| 23 | return false | ||
| 24 | } | ||
| 25 | } | ||
| 26 | return true | ||
| 27 | } | ||
| 28 | 19 | ||
| 29 | companion object { | 20 | override val defaultValue: Float by lazy { NativeConfig.getFloat(key, true) } |
| 30 | private val NOT_RUNTIME_EDITABLE = emptyList<FloatSetting>() | ||
| 31 | 21 | ||
| 32 | fun from(key: String): FloatSetting? = FloatSetting.values().firstOrNull { it.key == key } | 22 | override val valueAsString: String |
| 23 | get() = float.toString() | ||
| 33 | 24 | ||
| 34 | fun clear() = FloatSetting.values().forEach { it.float = it.defaultValue } | 25 | override fun reset() = NativeConfig.setFloat(key, defaultValue) |
| 35 | } | ||
| 36 | } | 26 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt index 4427a7d9d..151362124 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt | |||
| @@ -3,139 +3,37 @@ | |||
| 3 | 3 | ||
| 4 | package org.yuzu.yuzu_emu.features.settings.model | 4 | package org.yuzu.yuzu_emu.features.settings.model |
| 5 | 5 | ||
| 6 | import org.yuzu.yuzu_emu.utils.NativeConfig | ||
| 7 | |||
| 6 | enum class IntSetting( | 8 | enum class IntSetting( |
| 7 | override val key: String, | 9 | override val key: String, |
| 8 | override val section: String, | 10 | override val category: Settings.Category, |
| 9 | override val defaultValue: Int | 11 | override val androidDefault: Int? = null |
| 10 | ) : AbstractIntSetting { | 12 | ) : AbstractIntSetting { |
| 11 | RENDERER_USE_SPEED_LIMIT( | 13 | CPU_ACCURACY("cpu_accuracy", Settings.Category.Cpu), |
| 12 | "use_speed_limit", | 14 | REGION_INDEX("region_index", Settings.Category.System), |
| 13 | Settings.SECTION_RENDERER, | 15 | LANGUAGE_INDEX("language_index", Settings.Category.System), |
| 14 | 1 | 16 | RENDERER_BACKEND("backend", Settings.Category.Renderer), |
| 15 | ), | 17 | RENDERER_ACCURACY("gpu_accuracy", Settings.Category.Renderer, 0), |
| 16 | USE_DOCKED_MODE( | 18 | RENDERER_RESOLUTION("resolution_setup", Settings.Category.Renderer), |
| 17 | "use_docked_mode", | 19 | RENDERER_VSYNC("use_vsync", Settings.Category.Renderer), |
| 18 | Settings.SECTION_SYSTEM, | 20 | RENDERER_SCALING_FILTER("scaling_filter", Settings.Category.Renderer), |
| 19 | 0 | 21 | RENDERER_ANTI_ALIASING("anti_aliasing", Settings.Category.Renderer), |
| 20 | ), | 22 | RENDERER_SCREEN_LAYOUT("screen_layout", Settings.Category.Android), |
| 21 | RENDERER_USE_DISK_SHADER_CACHE( | 23 | RENDERER_ASPECT_RATIO("aspect_ratio", Settings.Category.Renderer), |
| 22 | "use_disk_shader_cache", | 24 | AUDIO_OUTPUT_ENGINE("output_engine", Settings.Category.Audio); |
| 23 | Settings.SECTION_RENDERER, | 25 | |
| 24 | 1 | 26 | override val int: Int |
| 25 | ), | 27 | get() = NativeConfig.getInt(key, false) |
| 26 | RENDERER_FORCE_MAX_CLOCK( | 28 | |
| 27 | "force_max_clock", | 29 | override fun setInt(value: Int) = NativeConfig.setInt(key, value) |
| 28 | Settings.SECTION_RENDERER, | 30 | |
| 29 | 0 | 31 | override val defaultValue: Int by lazy { |
| 30 | ), | 32 | androidDefault ?: NativeConfig.getInt(key, true) |
| 31 | RENDERER_ASYNCHRONOUS_SHADERS( | 33 | } |
| 32 | "use_asynchronous_shaders", | ||
| 33 | Settings.SECTION_RENDERER, | ||
| 34 | 0 | ||
| 35 | ), | ||
| 36 | RENDERER_REACTIVE_FLUSHING( | ||
| 37 | "use_reactive_flushing", | ||
| 38 | Settings.SECTION_RENDERER, | ||
| 39 | 0 | ||
| 40 | ), | ||
| 41 | RENDERER_DEBUG( | ||
| 42 | "debug", | ||
| 43 | Settings.SECTION_RENDERER, | ||
| 44 | 0 | ||
| 45 | ), | ||
| 46 | RENDERER_SPEED_LIMIT( | ||
| 47 | "speed_limit", | ||
| 48 | Settings.SECTION_RENDERER, | ||
| 49 | 100 | ||
| 50 | ), | ||
| 51 | CPU_ACCURACY( | ||
| 52 | "cpu_accuracy", | ||
| 53 | Settings.SECTION_CPU, | ||
| 54 | 0 | ||
| 55 | ), | ||
| 56 | REGION_INDEX( | ||
| 57 | "region_index", | ||
| 58 | Settings.SECTION_SYSTEM, | ||
| 59 | -1 | ||
| 60 | ), | ||
| 61 | LANGUAGE_INDEX( | ||
| 62 | "language_index", | ||
| 63 | Settings.SECTION_SYSTEM, | ||
| 64 | 1 | ||
| 65 | ), | ||
| 66 | RENDERER_BACKEND( | ||
| 67 | "backend", | ||
| 68 | Settings.SECTION_RENDERER, | ||
| 69 | 1 | ||
| 70 | ), | ||
| 71 | RENDERER_ACCURACY( | ||
| 72 | "gpu_accuracy", | ||
| 73 | Settings.SECTION_RENDERER, | ||
| 74 | 0 | ||
| 75 | ), | ||
| 76 | RENDERER_RESOLUTION( | ||
| 77 | "resolution_setup", | ||
| 78 | Settings.SECTION_RENDERER, | ||
| 79 | 2 | ||
| 80 | ), | ||
| 81 | RENDERER_VSYNC( | ||
| 82 | "use_vsync", | ||
| 83 | Settings.SECTION_RENDERER, | ||
| 84 | 0 | ||
| 85 | ), | ||
| 86 | RENDERER_SCALING_FILTER( | ||
| 87 | "scaling_filter", | ||
| 88 | Settings.SECTION_RENDERER, | ||
| 89 | 1 | ||
| 90 | ), | ||
| 91 | RENDERER_ANTI_ALIASING( | ||
| 92 | "anti_aliasing", | ||
| 93 | Settings.SECTION_RENDERER, | ||
| 94 | 0 | ||
| 95 | ), | ||
| 96 | RENDERER_SCREEN_LAYOUT( | ||
| 97 | "screen_layout", | ||
| 98 | Settings.SECTION_RENDERER, | ||
| 99 | Settings.LayoutOption_MobileLandscape | ||
| 100 | ), | ||
| 101 | RENDERER_ASPECT_RATIO( | ||
| 102 | "aspect_ratio", | ||
| 103 | Settings.SECTION_RENDERER, | ||
| 104 | 0 | ||
| 105 | ), | ||
| 106 | AUDIO_VOLUME( | ||
| 107 | "volume", | ||
| 108 | Settings.SECTION_AUDIO, | ||
| 109 | 100 | ||
| 110 | ); | ||
| 111 | |||
| 112 | override var int: Int = defaultValue | ||
| 113 | 34 | ||
| 114 | override val valueAsString: String | 35 | override val valueAsString: String |
| 115 | get() = int.toString() | 36 | get() = int.toString() |
| 116 | 37 | ||
| 117 | override val isRuntimeEditable: Boolean | 38 | override fun reset() = NativeConfig.setInt(key, defaultValue) |
| 118 | get() { | ||
| 119 | for (setting in NOT_RUNTIME_EDITABLE) { | ||
| 120 | if (setting == this) { | ||
| 121 | return false | ||
| 122 | } | ||
| 123 | } | ||
| 124 | return true | ||
| 125 | } | ||
| 126 | |||
| 127 | companion object { | ||
| 128 | private val NOT_RUNTIME_EDITABLE = listOf( | ||
| 129 | RENDERER_USE_DISK_SHADER_CACHE, | ||
| 130 | RENDERER_ASYNCHRONOUS_SHADERS, | ||
| 131 | RENDERER_DEBUG, | ||
| 132 | RENDERER_BACKEND, | ||
| 133 | RENDERER_RESOLUTION, | ||
| 134 | RENDERER_VSYNC | ||
| 135 | ) | ||
| 136 | |||
| 137 | fun from(key: String): IntSetting? = IntSetting.values().firstOrNull { it.key == key } | ||
| 138 | |||
| 139 | fun clear() = IntSetting.values().forEach { it.int = it.defaultValue } | ||
| 140 | } | ||
| 141 | } | 39 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/LongSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/LongSetting.kt new file mode 100644 index 000000000..c526fc4cf --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/LongSetting.kt | |||
| @@ -0,0 +1,25 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | package org.yuzu.yuzu_emu.features.settings.model | ||
| 5 | |||
| 6 | import org.yuzu.yuzu_emu.utils.NativeConfig | ||
| 7 | |||
| 8 | enum class LongSetting( | ||
| 9 | override val key: String, | ||
| 10 | override val category: Settings.Category | ||
| 11 | ) : AbstractLongSetting { | ||
| 12 | CUSTOM_RTC("custom_rtc", Settings.Category.System); | ||
| 13 | |||
| 14 | override val long: Long | ||
| 15 | get() = NativeConfig.getLong(key, false) | ||
| 16 | |||
| 17 | override fun setLong(value: Long) = NativeConfig.setLong(key, value) | ||
| 18 | |||
| 19 | override val defaultValue: Long by lazy { NativeConfig.getLong(key, true) } | ||
| 20 | |||
| 21 | override val valueAsString: String | ||
| 22 | get() = long.toString() | ||
| 23 | |||
| 24 | override fun reset() = NativeConfig.setLong(key, defaultValue) | ||
| 25 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/SettingSection.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/SettingSection.kt deleted file mode 100644 index 474f598a9..000000000 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/SettingSection.kt +++ /dev/null | |||
| @@ -1,37 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | package org.yuzu.yuzu_emu.features.settings.model | ||
| 5 | |||
| 6 | /** | ||
| 7 | * A semantically-related group of Settings objects. These Settings are | ||
| 8 | * internally stored as a HashMap. | ||
| 9 | */ | ||
| 10 | class SettingSection(val name: String) { | ||
| 11 | val settings = HashMap<String, AbstractSetting>() | ||
| 12 | |||
| 13 | /** | ||
| 14 | * Convenience method; inserts a value directly into the backing HashMap. | ||
| 15 | * | ||
| 16 | * @param setting The Setting to be inserted. | ||
| 17 | */ | ||
| 18 | fun putSetting(setting: AbstractSetting) { | ||
| 19 | settings[setting.key!!] = setting | ||
| 20 | } | ||
| 21 | |||
| 22 | /** | ||
| 23 | * Convenience method; gets a value directly from the backing HashMap. | ||
| 24 | * | ||
| 25 | * @param key Used to retrieve the Setting. | ||
| 26 | * @return A Setting object (you should probably cast this before using) | ||
| 27 | */ | ||
| 28 | fun getSetting(key: String): AbstractSetting? { | ||
| 29 | return settings[key] | ||
| 30 | } | ||
| 31 | |||
| 32 | fun mergeSection(settingSection: SettingSection) { | ||
| 33 | for (setting in settingSection.settings.values) { | ||
| 34 | putSetting(setting) | ||
| 35 | } | ||
| 36 | } | ||
| 37 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt index a6251bafd..0702236e8 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt | |||
| @@ -4,195 +4,151 @@ | |||
| 4 | package org.yuzu.yuzu_emu.features.settings.model | 4 | package org.yuzu.yuzu_emu.features.settings.model |
| 5 | 5 | ||
| 6 | import android.text.TextUtils | 6 | import android.text.TextUtils |
| 7 | import java.util.* | 7 | import android.widget.Toast |
| 8 | import org.yuzu.yuzu_emu.R | 8 | import org.yuzu.yuzu_emu.R |
| 9 | import org.yuzu.yuzu_emu.YuzuApplication | 9 | import org.yuzu.yuzu_emu.YuzuApplication |
| 10 | import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivityView | ||
| 11 | import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile | 10 | import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile |
| 12 | 11 | ||
| 13 | class Settings { | 12 | object Settings { |
| 14 | private var gameId: String? = null | 13 | private val context get() = YuzuApplication.appContext |
| 15 | 14 | ||
| 16 | var isLoaded = false | 15 | fun saveSettings(gameId: String = "") { |
| 17 | |||
| 18 | /** | ||
| 19 | * A HashMap<String></String>, SettingSection> that constructs a new SettingSection instead of returning null | ||
| 20 | * when getting a key not already in the map | ||
| 21 | */ | ||
| 22 | class SettingsSectionMap : HashMap<String, SettingSection?>() { | ||
| 23 | override operator fun get(key: String): SettingSection? { | ||
| 24 | if (!super.containsKey(key)) { | ||
| 25 | val section = SettingSection(key) | ||
| 26 | super.put(key, section) | ||
| 27 | return section | ||
| 28 | } | ||
| 29 | return super.get(key) | ||
| 30 | } | ||
| 31 | } | ||
| 32 | |||
| 33 | var sections: HashMap<String, SettingSection?> = SettingsSectionMap() | ||
| 34 | |||
| 35 | fun getSection(sectionName: String): SettingSection? { | ||
| 36 | return sections[sectionName] | ||
| 37 | } | ||
| 38 | |||
| 39 | val isEmpty: Boolean | ||
| 40 | get() = sections.isEmpty() | ||
| 41 | |||
| 42 | fun loadSettings(view: SettingsActivityView? = null) { | ||
| 43 | sections = SettingsSectionMap() | ||
| 44 | loadYuzuSettings(view) | ||
| 45 | if (!TextUtils.isEmpty(gameId)) { | ||
| 46 | loadCustomGameSettings(gameId!!, view) | ||
| 47 | } | ||
| 48 | isLoaded = true | ||
| 49 | } | ||
| 50 | |||
| 51 | private fun loadYuzuSettings(view: SettingsActivityView?) { | ||
| 52 | for ((fileName) in configFileSectionsMap) { | ||
| 53 | sections.putAll(SettingsFile.readFile(fileName, view)) | ||
| 54 | } | ||
| 55 | } | ||
| 56 | |||
| 57 | private fun loadCustomGameSettings(gameId: String, view: SettingsActivityView?) { | ||
| 58 | // Custom game settings | ||
| 59 | mergeSections(SettingsFile.readCustomGameSettings(gameId, view)) | ||
| 60 | } | ||
| 61 | |||
| 62 | private fun mergeSections(updatedSections: HashMap<String, SettingSection?>) { | ||
| 63 | for ((key, updatedSection) in updatedSections) { | ||
| 64 | if (sections.containsKey(key)) { | ||
| 65 | val originalSection = sections[key] | ||
| 66 | originalSection!!.mergeSection(updatedSection!!) | ||
| 67 | } else { | ||
| 68 | sections[key] = updatedSection | ||
| 69 | } | ||
| 70 | } | ||
| 71 | } | ||
| 72 | |||
| 73 | fun loadSettings(gameId: String, view: SettingsActivityView) { | ||
| 74 | this.gameId = gameId | ||
| 75 | loadSettings(view) | ||
| 76 | } | ||
| 77 | |||
| 78 | fun saveSettings(view: SettingsActivityView) { | ||
| 79 | if (TextUtils.isEmpty(gameId)) { | 16 | if (TextUtils.isEmpty(gameId)) { |
| 80 | view.showToastMessage( | 17 | Toast.makeText( |
| 81 | YuzuApplication.appContext.getString(R.string.ini_saved), | 18 | context, |
| 82 | false | 19 | context.getString(R.string.ini_saved), |
| 83 | ) | 20 | Toast.LENGTH_SHORT |
| 84 | 21 | ).show() | |
| 85 | for ((fileName, sectionNames) in configFileSectionsMap) { | 22 | SettingsFile.saveFile(SettingsFile.FILE_NAME_CONFIG) |
| 86 | val iniSections = TreeMap<String, SettingSection>() | ||
| 87 | for (section in sectionNames) { | ||
| 88 | iniSections[section] = sections[section]!! | ||
| 89 | } | ||
| 90 | |||
| 91 | SettingsFile.saveFile(fileName, iniSections, view) | ||
| 92 | } | ||
| 93 | } else { | 23 | } else { |
| 94 | // Custom game settings | 24 | // TODO: Save custom game settings |
| 95 | view.showToastMessage( | 25 | Toast.makeText( |
| 96 | YuzuApplication.appContext.getString(R.string.gameid_saved, gameId), | 26 | context, |
| 97 | false | 27 | context.getString(R.string.gameid_saved, gameId), |
| 98 | ) | 28 | Toast.LENGTH_SHORT |
| 99 | 29 | ).show() | |
| 100 | SettingsFile.saveCustomGameSettings(gameId, sections) | ||
| 101 | } | 30 | } |
| 102 | } | 31 | } |
| 103 | 32 | ||
| 104 | companion object { | 33 | enum class Category { |
| 105 | const val SECTION_GENERAL = "General" | 34 | Android, |
| 106 | const val SECTION_SYSTEM = "System" | 35 | Audio, |
| 107 | const val SECTION_RENDERER = "Renderer" | 36 | Core, |
| 108 | const val SECTION_AUDIO = "Audio" | 37 | Cpu, |
| 109 | const val SECTION_CPU = "Cpu" | 38 | CpuDebug, |
| 110 | const val SECTION_THEME = "Theme" | 39 | CpuUnsafe, |
| 111 | const val SECTION_DEBUG = "Debug" | 40 | Renderer, |
| 112 | 41 | RendererAdvanced, | |
| 113 | const val PREF_MEMORY_WARNING_SHOWN = "MemoryWarningShown" | 42 | RendererDebug, |
| 114 | 43 | System, | |
| 115 | const val PREF_OVERLAY_VERSION = "OverlayVersion" | 44 | SystemAudio, |
| 116 | const val PREF_LANDSCAPE_OVERLAY_VERSION = "LandscapeOverlayVersion" | 45 | DataStorage, |
| 117 | const val PREF_PORTRAIT_OVERLAY_VERSION = "PortraitOverlayVersion" | 46 | Debugging, |
| 118 | const val PREF_FOLDABLE_OVERLAY_VERSION = "FoldableOverlayVersion" | 47 | DebuggingGraphics, |
| 119 | val overlayLayoutPrefs = listOf( | 48 | Miscellaneous, |
| 120 | PREF_LANDSCAPE_OVERLAY_VERSION, | 49 | Network, |
| 121 | PREF_PORTRAIT_OVERLAY_VERSION, | 50 | WebService, |
| 122 | PREF_FOLDABLE_OVERLAY_VERSION | 51 | AddOns, |
| 123 | ) | 52 | Controls, |
| 124 | 53 | Ui, | |
| 125 | const val PREF_CONTROL_SCALE = "controlScale" | 54 | UiGeneral, |
| 126 | const val PREF_CONTROL_OPACITY = "controlOpacity" | 55 | UiLayout, |
| 127 | const val PREF_TOUCH_ENABLED = "isTouchEnabled" | 56 | UiGameList, |
| 128 | const val PREF_BUTTON_A = "buttonToggle0" | 57 | Screenshots, |
| 129 | const val PREF_BUTTON_B = "buttonToggle1" | 58 | Shortcuts, |
| 130 | const val PREF_BUTTON_X = "buttonToggle2" | 59 | Multiplayer, |
| 131 | const val PREF_BUTTON_Y = "buttonToggle3" | 60 | Services, |
| 132 | const val PREF_BUTTON_L = "buttonToggle4" | 61 | Paths, |
| 133 | const val PREF_BUTTON_R = "buttonToggle5" | 62 | MaxEnum |
| 134 | const val PREF_BUTTON_ZL = "buttonToggle6" | ||
| 135 | const val PREF_BUTTON_ZR = "buttonToggle7" | ||
| 136 | const val PREF_BUTTON_PLUS = "buttonToggle8" | ||
| 137 | const val PREF_BUTTON_MINUS = "buttonToggle9" | ||
| 138 | const val PREF_BUTTON_DPAD = "buttonToggle10" | ||
| 139 | const val PREF_STICK_L = "buttonToggle11" | ||
| 140 | const val PREF_STICK_R = "buttonToggle12" | ||
| 141 | const val PREF_BUTTON_STICK_L = "buttonToggle13" | ||
| 142 | const val PREF_BUTTON_STICK_R = "buttonToggle14" | ||
| 143 | const val PREF_BUTTON_HOME = "buttonToggle15" | ||
| 144 | const val PREF_BUTTON_SCREENSHOT = "buttonToggle16" | ||
| 145 | |||
| 146 | const val PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER = "EmulationMenuSettings_JoystickRelCenter" | ||
| 147 | const val PREF_MENU_SETTINGS_DPAD_SLIDE = "EmulationMenuSettings_DpadSlideEnable" | ||
| 148 | const val PREF_MENU_SETTINGS_HAPTICS = "EmulationMenuSettings_Haptics" | ||
| 149 | const val PREF_MENU_SETTINGS_SHOW_FPS = "EmulationMenuSettings_ShowFps" | ||
| 150 | const val PREF_MENU_SETTINGS_SHOW_OVERLAY = "EmulationMenuSettings_ShowOverlay" | ||
| 151 | |||
| 152 | const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch" | ||
| 153 | const val PREF_THEME = "Theme" | ||
| 154 | const val PREF_THEME_MODE = "ThemeMode" | ||
| 155 | const val PREF_BLACK_BACKGROUNDS = "BlackBackgrounds" | ||
| 156 | |||
| 157 | private val configFileSectionsMap: MutableMap<String, List<String>> = HashMap() | ||
| 158 | |||
| 159 | val overlayPreferences = listOf( | ||
| 160 | PREF_OVERLAY_VERSION, | ||
| 161 | PREF_CONTROL_SCALE, | ||
| 162 | PREF_CONTROL_OPACITY, | ||
| 163 | PREF_TOUCH_ENABLED, | ||
| 164 | PREF_BUTTON_A, | ||
| 165 | PREF_BUTTON_B, | ||
| 166 | PREF_BUTTON_X, | ||
| 167 | PREF_BUTTON_Y, | ||
| 168 | PREF_BUTTON_L, | ||
| 169 | PREF_BUTTON_R, | ||
| 170 | PREF_BUTTON_ZL, | ||
| 171 | PREF_BUTTON_ZR, | ||
| 172 | PREF_BUTTON_PLUS, | ||
| 173 | PREF_BUTTON_MINUS, | ||
| 174 | PREF_BUTTON_DPAD, | ||
| 175 | PREF_STICK_L, | ||
| 176 | PREF_STICK_R, | ||
| 177 | PREF_BUTTON_HOME, | ||
| 178 | PREF_BUTTON_SCREENSHOT, | ||
| 179 | PREF_BUTTON_STICK_L, | ||
| 180 | PREF_BUTTON_STICK_R | ||
| 181 | ) | ||
| 182 | |||
| 183 | const val LayoutOption_Unspecified = 0 | ||
| 184 | const val LayoutOption_MobilePortrait = 4 | ||
| 185 | const val LayoutOption_MobileLandscape = 5 | ||
| 186 | |||
| 187 | init { | ||
| 188 | configFileSectionsMap[SettingsFile.FILE_NAME_CONFIG] = | ||
| 189 | listOf( | ||
| 190 | SECTION_GENERAL, | ||
| 191 | SECTION_SYSTEM, | ||
| 192 | SECTION_RENDERER, | ||
| 193 | SECTION_AUDIO, | ||
| 194 | SECTION_CPU | ||
| 195 | ) | ||
| 196 | } | ||
| 197 | } | 63 | } |
| 64 | |||
| 65 | val settingsList = listOf<AbstractSetting>( | ||
| 66 | *BooleanSetting.values(), | ||
| 67 | *ByteSetting.values(), | ||
| 68 | *ShortSetting.values(), | ||
| 69 | *IntSetting.values(), | ||
| 70 | *FloatSetting.values(), | ||
| 71 | *LongSetting.values(), | ||
| 72 | *StringSetting.values() | ||
| 73 | ) | ||
| 74 | |||
| 75 | const val SECTION_GENERAL = "General" | ||
| 76 | const val SECTION_SYSTEM = "System" | ||
| 77 | const val SECTION_RENDERER = "Renderer" | ||
| 78 | const val SECTION_AUDIO = "Audio" | ||
| 79 | const val SECTION_CPU = "Cpu" | ||
| 80 | const val SECTION_THEME = "Theme" | ||
| 81 | const val SECTION_DEBUG = "Debug" | ||
| 82 | |||
| 83 | const val PREF_MEMORY_WARNING_SHOWN = "MemoryWarningShown" | ||
| 84 | |||
| 85 | const val PREF_OVERLAY_VERSION = "OverlayVersion" | ||
| 86 | const val PREF_LANDSCAPE_OVERLAY_VERSION = "LandscapeOverlayVersion" | ||
| 87 | const val PREF_PORTRAIT_OVERLAY_VERSION = "PortraitOverlayVersion" | ||
| 88 | const val PREF_FOLDABLE_OVERLAY_VERSION = "FoldableOverlayVersion" | ||
| 89 | val overlayLayoutPrefs = listOf( | ||
| 90 | PREF_LANDSCAPE_OVERLAY_VERSION, | ||
| 91 | PREF_PORTRAIT_OVERLAY_VERSION, | ||
| 92 | PREF_FOLDABLE_OVERLAY_VERSION | ||
| 93 | ) | ||
| 94 | |||
| 95 | const val PREF_CONTROL_SCALE = "controlScale" | ||
| 96 | const val PREF_CONTROL_OPACITY = "controlOpacity" | ||
| 97 | const val PREF_TOUCH_ENABLED = "isTouchEnabled" | ||
| 98 | const val PREF_BUTTON_A = "buttonToggle0" | ||
| 99 | const val PREF_BUTTON_B = "buttonToggle1" | ||
| 100 | const val PREF_BUTTON_X = "buttonToggle2" | ||
| 101 | const val PREF_BUTTON_Y = "buttonToggle3" | ||
| 102 | const val PREF_BUTTON_L = "buttonToggle4" | ||
| 103 | const val PREF_BUTTON_R = "buttonToggle5" | ||
| 104 | const val PREF_BUTTON_ZL = "buttonToggle6" | ||
| 105 | const val PREF_BUTTON_ZR = "buttonToggle7" | ||
| 106 | const val PREF_BUTTON_PLUS = "buttonToggle8" | ||
| 107 | const val PREF_BUTTON_MINUS = "buttonToggle9" | ||
| 108 | const val PREF_BUTTON_DPAD = "buttonToggle10" | ||
| 109 | const val PREF_STICK_L = "buttonToggle11" | ||
| 110 | const val PREF_STICK_R = "buttonToggle12" | ||
| 111 | const val PREF_BUTTON_STICK_L = "buttonToggle13" | ||
| 112 | const val PREF_BUTTON_STICK_R = "buttonToggle14" | ||
| 113 | const val PREF_BUTTON_HOME = "buttonToggle15" | ||
| 114 | const val PREF_BUTTON_SCREENSHOT = "buttonToggle16" | ||
| 115 | |||
| 116 | const val PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER = "EmulationMenuSettings_JoystickRelCenter" | ||
| 117 | const val PREF_MENU_SETTINGS_DPAD_SLIDE = "EmulationMenuSettings_DpadSlideEnable" | ||
| 118 | const val PREF_MENU_SETTINGS_HAPTICS = "EmulationMenuSettings_Haptics" | ||
| 119 | const val PREF_MENU_SETTINGS_SHOW_FPS = "EmulationMenuSettings_ShowFps" | ||
| 120 | const val PREF_MENU_SETTINGS_SHOW_OVERLAY = "EmulationMenuSettings_ShowOverlay" | ||
| 121 | |||
| 122 | const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch" | ||
| 123 | const val PREF_THEME = "Theme" | ||
| 124 | const val PREF_THEME_MODE = "ThemeMode" | ||
| 125 | const val PREF_BLACK_BACKGROUNDS = "BlackBackgrounds" | ||
| 126 | |||
| 127 | val overlayPreferences = listOf( | ||
| 128 | PREF_OVERLAY_VERSION, | ||
| 129 | PREF_CONTROL_SCALE, | ||
| 130 | PREF_CONTROL_OPACITY, | ||
| 131 | PREF_TOUCH_ENABLED, | ||
| 132 | PREF_BUTTON_A, | ||
| 133 | PREF_BUTTON_B, | ||
| 134 | PREF_BUTTON_X, | ||
| 135 | PREF_BUTTON_Y, | ||
| 136 | PREF_BUTTON_L, | ||
| 137 | PREF_BUTTON_R, | ||
| 138 | PREF_BUTTON_ZL, | ||
| 139 | PREF_BUTTON_ZR, | ||
| 140 | PREF_BUTTON_PLUS, | ||
| 141 | PREF_BUTTON_MINUS, | ||
| 142 | PREF_BUTTON_DPAD, | ||
| 143 | PREF_STICK_L, | ||
| 144 | PREF_STICK_R, | ||
| 145 | PREF_BUTTON_HOME, | ||
| 146 | PREF_BUTTON_SCREENSHOT, | ||
| 147 | PREF_BUTTON_STICK_L, | ||
| 148 | PREF_BUTTON_STICK_R | ||
| 149 | ) | ||
| 150 | |||
| 151 | const val LayoutOption_Unspecified = 0 | ||
| 152 | const val LayoutOption_MobilePortrait = 4 | ||
| 153 | const val LayoutOption_MobileLandscape = 5 | ||
| 198 | } | 154 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ShortSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ShortSetting.kt new file mode 100644 index 000000000..c9a0c664c --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ShortSetting.kt | |||
| @@ -0,0 +1,25 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | package org.yuzu.yuzu_emu.features.settings.model | ||
| 5 | |||
| 6 | import org.yuzu.yuzu_emu.utils.NativeConfig | ||
| 7 | |||
| 8 | enum class ShortSetting( | ||
| 9 | override val key: String, | ||
| 10 | override val category: Settings.Category | ||
| 11 | ) : AbstractShortSetting { | ||
| 12 | RENDERER_SPEED_LIMIT("speed_limit", Settings.Category.Core); | ||
| 13 | |||
| 14 | override val short: Short | ||
| 15 | get() = NativeConfig.getShort(key, false) | ||
| 16 | |||
| 17 | override fun setShort(value: Short) = NativeConfig.setShort(key, value) | ||
| 18 | |||
| 19 | override val defaultValue: Short by lazy { NativeConfig.getShort(key, true) } | ||
| 20 | |||
| 21 | override val valueAsString: String | ||
| 22 | get() = short.toString() | ||
| 23 | |||
| 24 | override fun reset() = NativeConfig.setShort(key, defaultValue) | ||
| 25 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/StringSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/StringSetting.kt index 6621289fd..9bb3e66d4 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/StringSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/StringSetting.kt | |||
| @@ -3,36 +3,24 @@ | |||
| 3 | 3 | ||
| 4 | package org.yuzu.yuzu_emu.features.settings.model | 4 | package org.yuzu.yuzu_emu.features.settings.model |
| 5 | 5 | ||
| 6 | import org.yuzu.yuzu_emu.utils.NativeConfig | ||
| 7 | |||
| 6 | enum class StringSetting( | 8 | enum class StringSetting( |
| 7 | override val key: String, | 9 | override val key: String, |
| 8 | override val section: String, | 10 | override val category: Settings.Category |
| 9 | override val defaultValue: String | ||
| 10 | ) : AbstractStringSetting { | 11 | ) : AbstractStringSetting { |
| 11 | AUDIO_OUTPUT_ENGINE("output_engine", Settings.SECTION_AUDIO, "auto"), | 12 | // No string settings currently exist |
| 12 | CUSTOM_RTC("custom_rtc", Settings.SECTION_SYSTEM, "0"); | 13 | EMPTY_SETTING("", Settings.Category.UiGeneral); |
| 14 | |||
| 15 | override val string: String | ||
| 16 | get() = NativeConfig.getString(key, false) | ||
| 17 | |||
| 18 | override fun setString(value: String) = NativeConfig.setString(key, value) | ||
| 13 | 19 | ||
| 14 | override var string: String = defaultValue | 20 | override val defaultValue: String by lazy { NativeConfig.getString(key, true) } |
| 15 | 21 | ||
| 16 | override val valueAsString: String | 22 | override val valueAsString: String |
| 17 | get() = string | 23 | get() = string |
| 18 | 24 | ||
| 19 | override val isRuntimeEditable: Boolean | 25 | override fun reset() = NativeConfig.setString(key, defaultValue) |
| 20 | get() { | ||
| 21 | for (setting in NOT_RUNTIME_EDITABLE) { | ||
| 22 | if (setting == this) { | ||
| 23 | return false | ||
| 24 | } | ||
| 25 | } | ||
| 26 | return true | ||
| 27 | } | ||
| 28 | |||
| 29 | companion object { | ||
| 30 | private val NOT_RUNTIME_EDITABLE = listOf( | ||
| 31 | CUSTOM_RTC | ||
| 32 | ) | ||
| 33 | |||
| 34 | fun from(key: String): StringSetting? = StringSetting.values().firstOrNull { it.key == key } | ||
| 35 | |||
| 36 | fun clear() = StringSetting.values().forEach { it.string = it.defaultValue } | ||
| 37 | } | ||
| 38 | } | 26 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/DateTimeSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/DateTimeSetting.kt index bc0bf7788..8bc164197 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/DateTimeSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/DateTimeSetting.kt | |||
| @@ -3,29 +3,16 @@ | |||
| 3 | 3 | ||
| 4 | package org.yuzu.yuzu_emu.features.settings.model.view | 4 | package org.yuzu.yuzu_emu.features.settings.model.view |
| 5 | 5 | ||
| 6 | import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting | 6 | import org.yuzu.yuzu_emu.features.settings.model.AbstractLongSetting |
| 7 | import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting | ||
| 8 | 7 | ||
| 9 | class DateTimeSetting( | 8 | class DateTimeSetting( |
| 10 | setting: AbstractSetting?, | 9 | private val longSetting: AbstractLongSetting, |
| 11 | titleId: Int, | 10 | titleId: Int, |
| 12 | descriptionId: Int, | 11 | descriptionId: Int |
| 13 | val key: String? = null, | 12 | ) : SettingsItem(longSetting, titleId, descriptionId) { |
| 14 | private val defaultValue: String? = null | ||
| 15 | ) : SettingsItem(setting, titleId, descriptionId) { | ||
| 16 | override val type = TYPE_DATETIME_SETTING | 13 | override val type = TYPE_DATETIME_SETTING |
| 17 | 14 | ||
| 18 | val value: String | 15 | var value: Long |
| 19 | get() = if (setting != null) { | 16 | get() = longSetting.long |
| 20 | val setting = setting as AbstractStringSetting | 17 | set(value) = (setting as AbstractLongSetting).setLong(value) |
| 21 | setting.string | ||
| 22 | } else { | ||
| 23 | defaultValue!! | ||
| 24 | } | ||
| 25 | |||
| 26 | fun setSelectedValue(datetime: String): AbstractStringSetting { | ||
| 27 | val stringSetting = setting as AbstractStringSetting | ||
| 28 | stringSetting.string = datetime | ||
| 29 | return stringSetting | ||
| 30 | } | ||
| 31 | } | 18 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/HeaderSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/HeaderSetting.kt index a67001311..d31ce1c31 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/HeaderSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/HeaderSetting.kt | |||
| @@ -5,6 +5,6 @@ package org.yuzu.yuzu_emu.features.settings.model.view | |||
| 5 | 5 | ||
| 6 | class HeaderSetting( | 6 | class HeaderSetting( |
| 7 | titleId: Int | 7 | titleId: Int |
| 8 | ) : SettingsItem(null, titleId, 0) { | 8 | ) : SettingsItem(emptySetting, titleId, 0) { |
| 9 | override val type = TYPE_HEADER | 9 | override val type = TYPE_HEADER |
| 10 | } | 10 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/RunnableSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/RunnableSetting.kt index caaab50d8..522cc49df 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/RunnableSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/RunnableSetting.kt | |||
| @@ -8,6 +8,6 @@ class RunnableSetting( | |||
| 8 | descriptionId: Int, | 8 | descriptionId: Int, |
| 9 | val isRuntimeRunnable: Boolean, | 9 | val isRuntimeRunnable: Boolean, |
| 10 | val runnable: () -> Unit | 10 | val runnable: () -> Unit |
| 11 | ) : SettingsItem(null, titleId, descriptionId) { | 11 | ) : SettingsItem(emptySetting, titleId, descriptionId) { |
| 12 | override val type = TYPE_RUNNABLE | 12 | override val type = TYPE_RUNNABLE |
| 13 | } | 13 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt index 07520849e..b3b3fc209 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt | |||
| @@ -4,7 +4,15 @@ | |||
| 4 | package org.yuzu.yuzu_emu.features.settings.model.view | 4 | package org.yuzu.yuzu_emu.features.settings.model.view |
| 5 | 5 | ||
| 6 | import org.yuzu.yuzu_emu.NativeLibrary | 6 | import org.yuzu.yuzu_emu.NativeLibrary |
| 7 | import org.yuzu.yuzu_emu.R | ||
| 8 | import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting | ||
| 7 | import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting | 9 | import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting |
| 10 | import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting | ||
| 11 | import org.yuzu.yuzu_emu.features.settings.model.ByteSetting | ||
| 12 | import org.yuzu.yuzu_emu.features.settings.model.IntSetting | ||
| 13 | import org.yuzu.yuzu_emu.features.settings.model.LongSetting | ||
| 14 | import org.yuzu.yuzu_emu.features.settings.model.Settings | ||
| 15 | import org.yuzu.yuzu_emu.features.settings.model.ShortSetting | ||
| 8 | 16 | ||
| 9 | /** | 17 | /** |
| 10 | * ViewModel abstraction for an Item in the RecyclerView powering SettingsFragments. | 18 | * ViewModel abstraction for an Item in the RecyclerView powering SettingsFragments. |
| @@ -14,7 +22,7 @@ import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting | |||
| 14 | * file.) | 22 | * file.) |
| 15 | */ | 23 | */ |
| 16 | abstract class SettingsItem( | 24 | abstract class SettingsItem( |
| 17 | var setting: AbstractSetting?, | 25 | val setting: AbstractSetting, |
| 18 | val nameId: Int, | 26 | val nameId: Int, |
| 19 | val descriptionId: Int | 27 | val descriptionId: Int |
| 20 | ) { | 28 | ) { |
| @@ -23,7 +31,7 @@ abstract class SettingsItem( | |||
| 23 | val isEditable: Boolean | 31 | val isEditable: Boolean |
| 24 | get() { | 32 | get() { |
| 25 | if (!NativeLibrary.isRunning()) return true | 33 | if (!NativeLibrary.isRunning()) return true |
| 26 | return setting?.isRuntimeEditable ?: false | 34 | return setting.isRuntimeModifiable |
| 27 | } | 35 | } |
| 28 | 36 | ||
| 29 | companion object { | 37 | companion object { |
| @@ -35,5 +43,240 @@ abstract class SettingsItem( | |||
| 35 | const val TYPE_STRING_SINGLE_CHOICE = 5 | 43 | const val TYPE_STRING_SINGLE_CHOICE = 5 |
| 36 | const val TYPE_DATETIME_SETTING = 6 | 44 | const val TYPE_DATETIME_SETTING = 6 |
| 37 | const val TYPE_RUNNABLE = 7 | 45 | const val TYPE_RUNNABLE = 7 |
| 46 | |||
| 47 | const val FASTMEM_COMBINED = "fastmem_combined" | ||
| 48 | |||
| 49 | val emptySetting = object : AbstractSetting { | ||
| 50 | override val key: String = "" | ||
| 51 | override val category: Settings.Category = Settings.Category.Ui | ||
| 52 | override val defaultValue: Any = false | ||
| 53 | override fun reset() {} | ||
| 54 | } | ||
| 55 | |||
| 56 | // Extension for putting SettingsItems into a hashmap without repeating yourself | ||
| 57 | fun HashMap<String, SettingsItem>.put(item: SettingsItem) { | ||
| 58 | put(item.setting.key, item) | ||
| 59 | } | ||
| 60 | |||
| 61 | // List of all general | ||
| 62 | val settingsItems = HashMap<String, SettingsItem>().apply { | ||
| 63 | put( | ||
| 64 | SwitchSetting( | ||
| 65 | BooleanSetting.RENDERER_USE_SPEED_LIMIT, | ||
| 66 | R.string.frame_limit_enable, | ||
| 67 | R.string.frame_limit_enable_description | ||
| 68 | ) | ||
| 69 | ) | ||
| 70 | put( | ||
| 71 | SliderSetting( | ||
| 72 | ShortSetting.RENDERER_SPEED_LIMIT, | ||
| 73 | R.string.frame_limit_slider, | ||
| 74 | R.string.frame_limit_slider_description, | ||
| 75 | 1, | ||
| 76 | 200, | ||
| 77 | "%" | ||
| 78 | ) | ||
| 79 | ) | ||
| 80 | put( | ||
| 81 | SingleChoiceSetting( | ||
| 82 | IntSetting.CPU_ACCURACY, | ||
| 83 | R.string.cpu_accuracy, | ||
| 84 | 0, | ||
| 85 | R.array.cpuAccuracyNames, | ||
| 86 | R.array.cpuAccuracyValues | ||
| 87 | ) | ||
| 88 | ) | ||
| 89 | put( | ||
| 90 | SwitchSetting( | ||
| 91 | BooleanSetting.PICTURE_IN_PICTURE, | ||
| 92 | R.string.picture_in_picture, | ||
| 93 | R.string.picture_in_picture_description | ||
| 94 | ) | ||
| 95 | ) | ||
| 96 | put( | ||
| 97 | SwitchSetting( | ||
| 98 | BooleanSetting.USE_DOCKED_MODE, | ||
| 99 | R.string.use_docked_mode, | ||
| 100 | R.string.use_docked_mode_description | ||
| 101 | ) | ||
| 102 | ) | ||
| 103 | put( | ||
| 104 | SingleChoiceSetting( | ||
| 105 | IntSetting.REGION_INDEX, | ||
| 106 | R.string.emulated_region, | ||
| 107 | 0, | ||
| 108 | R.array.regionNames, | ||
| 109 | R.array.regionValues | ||
| 110 | ) | ||
| 111 | ) | ||
| 112 | put( | ||
| 113 | SingleChoiceSetting( | ||
| 114 | IntSetting.LANGUAGE_INDEX, | ||
| 115 | R.string.emulated_language, | ||
| 116 | 0, | ||
| 117 | R.array.languageNames, | ||
| 118 | R.array.languageValues | ||
| 119 | ) | ||
| 120 | ) | ||
| 121 | put( | ||
| 122 | SwitchSetting( | ||
| 123 | BooleanSetting.USE_CUSTOM_RTC, | ||
| 124 | R.string.use_custom_rtc, | ||
| 125 | R.string.use_custom_rtc_description | ||
| 126 | ) | ||
| 127 | ) | ||
| 128 | put(DateTimeSetting(LongSetting.CUSTOM_RTC, R.string.set_custom_rtc, 0)) | ||
| 129 | put( | ||
| 130 | SingleChoiceSetting( | ||
| 131 | IntSetting.RENDERER_ACCURACY, | ||
| 132 | R.string.renderer_accuracy, | ||
| 133 | 0, | ||
| 134 | R.array.rendererAccuracyNames, | ||
| 135 | R.array.rendererAccuracyValues | ||
| 136 | ) | ||
| 137 | ) | ||
| 138 | put( | ||
| 139 | SingleChoiceSetting( | ||
| 140 | IntSetting.RENDERER_RESOLUTION, | ||
| 141 | R.string.renderer_resolution, | ||
| 142 | 0, | ||
| 143 | R.array.rendererResolutionNames, | ||
| 144 | R.array.rendererResolutionValues | ||
| 145 | ) | ||
| 146 | ) | ||
| 147 | put( | ||
| 148 | SingleChoiceSetting( | ||
| 149 | IntSetting.RENDERER_VSYNC, | ||
| 150 | R.string.renderer_vsync, | ||
| 151 | 0, | ||
| 152 | R.array.rendererVSyncNames, | ||
| 153 | R.array.rendererVSyncValues | ||
| 154 | ) | ||
| 155 | ) | ||
| 156 | put( | ||
| 157 | SingleChoiceSetting( | ||
| 158 | IntSetting.RENDERER_SCALING_FILTER, | ||
| 159 | R.string.renderer_scaling_filter, | ||
| 160 | 0, | ||
| 161 | R.array.rendererScalingFilterNames, | ||
| 162 | R.array.rendererScalingFilterValues | ||
| 163 | ) | ||
| 164 | ) | ||
| 165 | put( | ||
| 166 | SingleChoiceSetting( | ||
| 167 | IntSetting.RENDERER_ANTI_ALIASING, | ||
| 168 | R.string.renderer_anti_aliasing, | ||
| 169 | 0, | ||
| 170 | R.array.rendererAntiAliasingNames, | ||
| 171 | R.array.rendererAntiAliasingValues | ||
| 172 | ) | ||
| 173 | ) | ||
| 174 | put( | ||
| 175 | SingleChoiceSetting( | ||
| 176 | IntSetting.RENDERER_SCREEN_LAYOUT, | ||
| 177 | R.string.renderer_screen_layout, | ||
| 178 | 0, | ||
| 179 | R.array.rendererScreenLayoutNames, | ||
| 180 | R.array.rendererScreenLayoutValues | ||
| 181 | ) | ||
| 182 | ) | ||
| 183 | put( | ||
| 184 | SingleChoiceSetting( | ||
| 185 | IntSetting.RENDERER_ASPECT_RATIO, | ||
| 186 | R.string.renderer_aspect_ratio, | ||
| 187 | 0, | ||
| 188 | R.array.rendererAspectRatioNames, | ||
| 189 | R.array.rendererAspectRatioValues | ||
| 190 | ) | ||
| 191 | ) | ||
| 192 | put( | ||
| 193 | SwitchSetting( | ||
| 194 | BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE, | ||
| 195 | R.string.use_disk_shader_cache, | ||
| 196 | R.string.use_disk_shader_cache_description | ||
| 197 | ) | ||
| 198 | ) | ||
| 199 | put( | ||
| 200 | SwitchSetting( | ||
| 201 | BooleanSetting.RENDERER_FORCE_MAX_CLOCK, | ||
| 202 | R.string.renderer_force_max_clock, | ||
| 203 | R.string.renderer_force_max_clock_description | ||
| 204 | ) | ||
| 205 | ) | ||
| 206 | put( | ||
| 207 | SwitchSetting( | ||
| 208 | BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS, | ||
| 209 | R.string.renderer_asynchronous_shaders, | ||
| 210 | R.string.renderer_asynchronous_shaders_description | ||
| 211 | ) | ||
| 212 | ) | ||
| 213 | put( | ||
| 214 | SwitchSetting( | ||
| 215 | BooleanSetting.RENDERER_REACTIVE_FLUSHING, | ||
| 216 | R.string.renderer_reactive_flushing, | ||
| 217 | R.string.renderer_reactive_flushing_description | ||
| 218 | ) | ||
| 219 | ) | ||
| 220 | put( | ||
| 221 | SingleChoiceSetting( | ||
| 222 | IntSetting.AUDIO_OUTPUT_ENGINE, | ||
| 223 | R.string.audio_output_engine, | ||
| 224 | 0, | ||
| 225 | R.array.outputEngineEntries, | ||
| 226 | R.array.outputEngineValues | ||
| 227 | ) | ||
| 228 | ) | ||
| 229 | put( | ||
| 230 | SliderSetting( | ||
| 231 | ByteSetting.AUDIO_VOLUME, | ||
| 232 | R.string.audio_volume, | ||
| 233 | R.string.audio_volume_description, | ||
| 234 | 0, | ||
| 235 | 100, | ||
| 236 | "%" | ||
| 237 | ) | ||
| 238 | ) | ||
| 239 | put( | ||
| 240 | SingleChoiceSetting( | ||
| 241 | IntSetting.RENDERER_BACKEND, | ||
| 242 | R.string.renderer_api, | ||
| 243 | 0, | ||
| 244 | R.array.rendererApiNames, | ||
| 245 | R.array.rendererApiValues | ||
| 246 | ) | ||
| 247 | ) | ||
| 248 | put( | ||
| 249 | SwitchSetting( | ||
| 250 | BooleanSetting.RENDERER_DEBUG, | ||
| 251 | R.string.renderer_debug, | ||
| 252 | R.string.renderer_debug_description | ||
| 253 | ) | ||
| 254 | ) | ||
| 255 | put( | ||
| 256 | SwitchSetting( | ||
| 257 | BooleanSetting.CPU_DEBUG_MODE, | ||
| 258 | R.string.cpu_debug_mode, | ||
| 259 | R.string.cpu_debug_mode_description | ||
| 260 | ) | ||
| 261 | ) | ||
| 262 | |||
| 263 | val fastmem = object : AbstractBooleanSetting { | ||
| 264 | override val boolean: Boolean | ||
| 265 | get() = | ||
| 266 | BooleanSetting.FASTMEM.boolean && BooleanSetting.FASTMEM_EXCLUSIVES.boolean | ||
| 267 | |||
| 268 | override fun setBoolean(value: Boolean) { | ||
| 269 | BooleanSetting.FASTMEM.setBoolean(value) | ||
| 270 | BooleanSetting.FASTMEM_EXCLUSIVES.setBoolean(value) | ||
| 271 | } | ||
| 272 | |||
| 273 | override val key: String = FASTMEM_COMBINED | ||
| 274 | override val category = Settings.Category.Cpu | ||
| 275 | override val isRuntimeModifiable: Boolean = false | ||
| 276 | override val defaultValue: Boolean = true | ||
| 277 | override fun reset() = setBoolean(defaultValue) | ||
| 278 | } | ||
| 279 | put(SwitchSetting(fastmem, R.string.fastmem, 0)) | ||
| 280 | } | ||
| 38 | } | 281 | } |
| 39 | } | 282 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SingleChoiceSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SingleChoiceSetting.kt index 7306ec458..705527a73 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SingleChoiceSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SingleChoiceSetting.kt | |||
| @@ -4,36 +4,27 @@ | |||
| 4 | package org.yuzu.yuzu_emu.features.settings.model.view | 4 | package org.yuzu.yuzu_emu.features.settings.model.view |
| 5 | 5 | ||
| 6 | import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting | 6 | import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting |
| 7 | import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting | ||
| 7 | 8 | ||
| 8 | class SingleChoiceSetting( | 9 | class SingleChoiceSetting( |
| 9 | setting: AbstractIntSetting?, | 10 | setting: AbstractSetting, |
| 10 | titleId: Int, | 11 | titleId: Int, |
| 11 | descriptionId: Int, | 12 | descriptionId: Int, |
| 12 | val choicesId: Int, | 13 | val choicesId: Int, |
| 13 | val valuesId: Int, | 14 | val valuesId: Int |
| 14 | val key: String? = null, | ||
| 15 | val defaultValue: Int? = null | ||
| 16 | ) : SettingsItem(setting, titleId, descriptionId) { | 15 | ) : SettingsItem(setting, titleId, descriptionId) { |
| 17 | override val type = TYPE_SINGLE_CHOICE | 16 | override val type = TYPE_SINGLE_CHOICE |
| 18 | 17 | ||
| 19 | val selectedValue: Int | 18 | var selectedValue: Int |
| 20 | get() = if (setting != null) { | 19 | get() { |
| 21 | val setting = setting as AbstractIntSetting | 20 | return when (setting) { |
| 22 | setting.int | 21 | is AbstractIntSetting -> setting.int |
| 23 | } else { | 22 | else -> -1 |
| 24 | defaultValue!! | 23 | } |
| 24 | } | ||
| 25 | set(value) { | ||
| 26 | when (setting) { | ||
| 27 | is AbstractIntSetting -> setting.setInt(value) | ||
| 28 | } | ||
| 25 | } | 29 | } |
| 26 | |||
| 27 | /** | ||
| 28 | * Write a value to the backing int. If that int was previously null, | ||
| 29 | * initializes a new one and returns it, so it can be added to the Hashmap. | ||
| 30 | * | ||
| 31 | * @param selection New value of the int. | ||
| 32 | * @return the existing setting with the new value applied. | ||
| 33 | */ | ||
| 34 | fun setSelectedValue(selection: Int): AbstractIntSetting { | ||
| 35 | val intSetting = setting as AbstractIntSetting | ||
| 36 | intSetting.int = selection | ||
| 37 | return intSetting | ||
| 38 | } | ||
| 39 | } | 30 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SliderSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SliderSetting.kt index 92d0167ae..c3b5df02c 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SliderSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SliderSetting.kt | |||
| @@ -3,60 +3,39 @@ | |||
| 3 | 3 | ||
| 4 | package org.yuzu.yuzu_emu.features.settings.model.view | 4 | package org.yuzu.yuzu_emu.features.settings.model.view |
| 5 | 5 | ||
| 6 | import kotlin.math.roundToInt | 6 | import org.yuzu.yuzu_emu.features.settings.model.AbstractByteSetting |
| 7 | import org.yuzu.yuzu_emu.features.settings.model.AbstractFloatSetting | 7 | import org.yuzu.yuzu_emu.features.settings.model.AbstractFloatSetting |
| 8 | import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting | 8 | import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting |
| 9 | import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting | 9 | import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting |
| 10 | import org.yuzu.yuzu_emu.utils.Log | 10 | import org.yuzu.yuzu_emu.features.settings.model.AbstractShortSetting |
| 11 | import kotlin.math.roundToInt | ||
| 11 | 12 | ||
| 12 | class SliderSetting( | 13 | class SliderSetting( |
| 13 | setting: AbstractSetting?, | 14 | setting: AbstractSetting, |
| 14 | titleId: Int, | 15 | titleId: Int, |
| 15 | descriptionId: Int, | 16 | descriptionId: Int, |
| 16 | val min: Int, | 17 | val min: Int, |
| 17 | val max: Int, | 18 | val max: Int, |
| 18 | val units: String, | 19 | val units: String |
| 19 | val key: String? = null, | ||
| 20 | val defaultValue: Int? = null | ||
| 21 | ) : SettingsItem(setting, titleId, descriptionId) { | 20 | ) : SettingsItem(setting, titleId, descriptionId) { |
| 22 | override val type = TYPE_SLIDER | 21 | override val type = TYPE_SLIDER |
| 23 | 22 | ||
| 24 | val selectedValue: Int | 23 | var selectedValue: Int |
| 25 | get() { | 24 | get() { |
| 26 | val setting = setting ?: return defaultValue!! | ||
| 27 | return when (setting) { | 25 | return when (setting) { |
| 26 | is AbstractByteSetting -> setting.byte.toInt() | ||
| 27 | is AbstractShortSetting -> setting.short.toInt() | ||
| 28 | is AbstractIntSetting -> setting.int | 28 | is AbstractIntSetting -> setting.int |
| 29 | is AbstractFloatSetting -> setting.float.roundToInt() | 29 | is AbstractFloatSetting -> setting.float.roundToInt() |
| 30 | else -> { | 30 | else -> -1 |
| 31 | Log.error("[SliderSetting] Error casting setting type.") | 31 | } |
| 32 | -1 | 32 | } |
| 33 | } | 33 | set(value) { |
| 34 | when (setting) { | ||
| 35 | is AbstractByteSetting -> setting.setByte(value.toByte()) | ||
| 36 | is AbstractShortSetting -> setting.setShort(value.toShort()) | ||
| 37 | is AbstractIntSetting -> setting.setInt(value) | ||
| 38 | is AbstractFloatSetting -> setting.setFloat(value.toFloat()) | ||
| 34 | } | 39 | } |
| 35 | } | 40 | } |
| 36 | |||
| 37 | /** | ||
| 38 | * Write a value to the backing int. If that int was previously null, | ||
| 39 | * initializes a new one and returns it, so it can be added to the Hashmap. | ||
| 40 | * | ||
| 41 | * @param selection New value of the int. | ||
| 42 | * @return the existing setting with the new value applied. | ||
| 43 | */ | ||
| 44 | fun setSelectedValue(selection: Int): AbstractIntSetting { | ||
| 45 | val intSetting = setting as AbstractIntSetting | ||
| 46 | intSetting.int = selection | ||
| 47 | return intSetting | ||
| 48 | } | ||
| 49 | |||
| 50 | /** | ||
| 51 | * Write a value to the backing float. If that float was previously null, | ||
| 52 | * initializes a new one and returns it, so it can be added to the Hashmap. | ||
| 53 | * | ||
| 54 | * @param selection New value of the float. | ||
| 55 | * @return the existing setting with the new value applied. | ||
| 56 | */ | ||
| 57 | fun setSelectedValue(selection: Float): AbstractFloatSetting { | ||
| 58 | val floatSetting = setting as AbstractFloatSetting | ||
| 59 | floatSetting.float = selection | ||
| 60 | return floatSetting | ||
| 61 | } | ||
| 62 | } | 41 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/StringSingleChoiceSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/StringSingleChoiceSetting.kt index 3b6731dcd..871dab4f3 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/StringSingleChoiceSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/StringSingleChoiceSetting.kt | |||
| @@ -3,57 +3,31 @@ | |||
| 3 | 3 | ||
| 4 | package org.yuzu.yuzu_emu.features.settings.model.view | 4 | package org.yuzu.yuzu_emu.features.settings.model.view |
| 5 | 5 | ||
| 6 | import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting | ||
| 7 | import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting | 6 | import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting |
| 8 | 7 | ||
| 9 | class StringSingleChoiceSetting( | 8 | class StringSingleChoiceSetting( |
| 10 | setting: AbstractSetting?, | 9 | private val stringSetting: AbstractStringSetting, |
| 11 | titleId: Int, | 10 | titleId: Int, |
| 12 | descriptionId: Int, | 11 | descriptionId: Int, |
| 13 | val choices: Array<String>, | 12 | val choices: Array<String>, |
| 14 | val values: Array<String>?, | 13 | val values: Array<String> |
| 15 | val key: String? = null, | 14 | ) : SettingsItem(stringSetting, titleId, descriptionId) { |
| 16 | private val defaultValue: String? = null | ||
| 17 | ) : SettingsItem(setting, titleId, descriptionId) { | ||
| 18 | override val type = TYPE_STRING_SINGLE_CHOICE | 15 | override val type = TYPE_STRING_SINGLE_CHOICE |
| 19 | 16 | ||
| 20 | fun getValueAt(index: Int): String? { | 17 | fun getValueAt(index: Int): String = |
| 21 | if (values == null) return null | 18 | if (index >= 0 && index < values.size) values[index] else "" |
| 22 | return if (index >= 0 && index < values.size) { | 19 | |
| 23 | values[index] | 20 | var selectedValue: String |
| 24 | } else { | 21 | get() = stringSetting.string |
| 25 | "" | 22 | set(value) = stringSetting.setString(value) |
| 26 | } | ||
| 27 | } | ||
| 28 | 23 | ||
| 29 | val selectedValue: String | ||
| 30 | get() = if (setting != null) { | ||
| 31 | val setting = setting as AbstractStringSetting | ||
| 32 | setting.string | ||
| 33 | } else { | ||
| 34 | defaultValue!! | ||
| 35 | } | ||
| 36 | val selectValueIndex: Int | 24 | val selectValueIndex: Int |
| 37 | get() { | 25 | get() { |
| 38 | val selectedValue = selectedValue | 26 | for (i in values.indices) { |
| 39 | for (i in values!!.indices) { | ||
| 40 | if (values[i] == selectedValue) { | 27 | if (values[i] == selectedValue) { |
| 41 | return i | 28 | return i |
| 42 | } | 29 | } |
| 43 | } | 30 | } |
| 44 | return -1 | 31 | return -1 |
| 45 | } | 32 | } |
| 46 | |||
| 47 | /** | ||
| 48 | * Write a value to the backing int. If that int was previously null, | ||
| 49 | * initializes a new one and returns it, so it can be added to the Hashmap. | ||
| 50 | * | ||
| 51 | * @param selection New value of the int. | ||
| 52 | * @return the existing setting with the new value applied. | ||
| 53 | */ | ||
| 54 | fun setSelectedValue(selection: String): AbstractStringSetting { | ||
| 55 | val stringSetting = setting as AbstractStringSetting | ||
| 56 | stringSetting.string = selection | ||
| 57 | return stringSetting | ||
| 58 | } | ||
| 59 | } | 33 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt index 8a9d13a92..91c273964 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt | |||
| @@ -7,6 +7,6 @@ class SubmenuSetting( | |||
| 7 | titleId: Int, | 7 | titleId: Int, |
| 8 | descriptionId: Int, | 8 | descriptionId: Int, |
| 9 | val menuKey: String | 9 | val menuKey: String |
| 10 | ) : SettingsItem(null, titleId, descriptionId) { | 10 | ) : SettingsItem(emptySetting, titleId, descriptionId) { |
| 11 | override val type = TYPE_SUBMENU | 11 | override val type = TYPE_SUBMENU |
| 12 | } | 12 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SwitchSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SwitchSetting.kt index 90b198718..416967e64 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SwitchSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SwitchSetting.kt | |||
| @@ -10,53 +10,22 @@ import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting | |||
| 10 | class SwitchSetting( | 10 | class SwitchSetting( |
| 11 | setting: AbstractSetting, | 11 | setting: AbstractSetting, |
| 12 | titleId: Int, | 12 | titleId: Int, |
| 13 | descriptionId: Int, | 13 | descriptionId: Int |
| 14 | val key: String? = null, | ||
| 15 | val defaultValue: Any? = null | ||
| 16 | ) : SettingsItem(setting, titleId, descriptionId) { | 14 | ) : SettingsItem(setting, titleId, descriptionId) { |
| 17 | override val type = TYPE_SWITCH | 15 | override val type = TYPE_SWITCH |
| 18 | 16 | ||
| 19 | val isChecked: Boolean | 17 | var checked: Boolean |
| 20 | get() { | 18 | get() { |
| 21 | if (setting == null) { | 19 | return when (setting) { |
| 22 | return defaultValue as Boolean | 20 | is AbstractIntSetting -> setting.int == 1 |
| 21 | is AbstractBooleanSetting -> setting.boolean | ||
| 22 | else -> false | ||
| 23 | } | 23 | } |
| 24 | |||
| 25 | // Try integer setting | ||
| 26 | try { | ||
| 27 | val setting = setting as AbstractIntSetting | ||
| 28 | return setting.int == 1 | ||
| 29 | } catch (_: ClassCastException) { | ||
| 30 | } | ||
| 31 | |||
| 32 | // Try boolean setting | ||
| 33 | try { | ||
| 34 | val setting = setting as AbstractBooleanSetting | ||
| 35 | return setting.boolean | ||
| 36 | } catch (_: ClassCastException) { | ||
| 37 | } | ||
| 38 | return defaultValue as Boolean | ||
| 39 | } | 24 | } |
| 40 | 25 | set(value) { | |
| 41 | /** | 26 | when (setting) { |
| 42 | * Write a value to the backing boolean. If that boolean was previously null, | 27 | is AbstractIntSetting -> setting.setInt(if (value) 1 else 0) |
| 43 | * initializes a new one and returns it, so it can be added to the Hashmap. | 28 | is AbstractBooleanSetting -> setting.setBoolean(value) |
| 44 | * | 29 | } |
| 45 | * @param checked Pretty self explanatory. | ||
| 46 | * @return the existing setting with the new value applied. | ||
| 47 | */ | ||
| 48 | fun setChecked(checked: Boolean): AbstractSetting { | ||
| 49 | // Try integer setting | ||
| 50 | try { | ||
| 51 | val setting = setting as AbstractIntSetting | ||
| 52 | setting.int = if (checked) 1 else 0 | ||
| 53 | return setting | ||
| 54 | } catch (_: ClassCastException) { | ||
| 55 | } | 30 | } |
| 56 | |||
| 57 | // Try boolean setting | ||
| 58 | val setting = setting as AbstractBooleanSetting | ||
| 59 | setting.boolean = checked | ||
| 60 | return setting | ||
| 61 | } | ||
| 62 | } | 31 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt index a5af5a7ae..908c01265 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt | |||
| @@ -3,42 +3,34 @@ | |||
| 3 | 3 | ||
| 4 | package org.yuzu.yuzu_emu.features.settings.ui | 4 | package org.yuzu.yuzu_emu.features.settings.ui |
| 5 | 5 | ||
| 6 | import android.content.Context | ||
| 7 | import android.content.Intent | ||
| 8 | import android.os.Bundle | 6 | import android.os.Bundle |
| 9 | import android.view.Menu | ||
| 10 | import android.view.View | 7 | import android.view.View |
| 11 | import android.view.ViewGroup.MarginLayoutParams | 8 | import android.view.ViewGroup.MarginLayoutParams |
| 12 | import android.widget.Toast | 9 | import android.widget.Toast |
| 13 | import androidx.activity.OnBackPressedCallback | 10 | import androidx.activity.OnBackPressedCallback |
| 14 | import androidx.activity.result.ActivityResultLauncher | ||
| 15 | import androidx.activity.viewModels | 11 | import androidx.activity.viewModels |
| 16 | import androidx.appcompat.app.AppCompatActivity | 12 | import androidx.appcompat.app.AppCompatActivity |
| 17 | import androidx.core.view.ViewCompat | 13 | import androidx.core.view.ViewCompat |
| 18 | import androidx.core.view.WindowCompat | 14 | import androidx.core.view.WindowCompat |
| 19 | import androidx.core.view.WindowInsetsCompat | 15 | import androidx.core.view.WindowInsetsCompat |
| 20 | import androidx.core.view.updatePadding | 16 | import androidx.navigation.fragment.NavHostFragment |
| 17 | import androidx.navigation.navArgs | ||
| 21 | import com.google.android.material.color.MaterialColors | 18 | import com.google.android.material.color.MaterialColors |
| 22 | import java.io.IOException | 19 | import java.io.IOException |
| 23 | import org.yuzu.yuzu_emu.R | 20 | import org.yuzu.yuzu_emu.R |
| 24 | import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding | 21 | import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding |
| 25 | import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting | ||
| 26 | import org.yuzu.yuzu_emu.features.settings.model.FloatSetting | ||
| 27 | import org.yuzu.yuzu_emu.features.settings.model.IntSetting | ||
| 28 | import org.yuzu.yuzu_emu.features.settings.model.Settings | 22 | import org.yuzu.yuzu_emu.features.settings.model.Settings |
| 29 | import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel | ||
| 30 | import org.yuzu.yuzu_emu.features.settings.model.StringSetting | ||
| 31 | import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile | 23 | import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile |
| 24 | import org.yuzu.yuzu_emu.fragments.ResetSettingsDialogFragment | ||
| 25 | import org.yuzu.yuzu_emu.model.SettingsViewModel | ||
| 32 | import org.yuzu.yuzu_emu.utils.* | 26 | import org.yuzu.yuzu_emu.utils.* |
| 33 | 27 | ||
| 34 | class SettingsActivity : AppCompatActivity(), SettingsActivityView { | 28 | class SettingsActivity : AppCompatActivity() { |
| 35 | private val presenter = SettingsActivityPresenter(this) | ||
| 36 | |||
| 37 | private lateinit var binding: ActivitySettingsBinding | 29 | private lateinit var binding: ActivitySettingsBinding |
| 38 | 30 | ||
| 39 | private val settingsViewModel: SettingsViewModel by viewModels() | 31 | private val args by navArgs<SettingsActivityArgs>() |
| 40 | 32 | ||
| 41 | override val settings: Settings get() = settingsViewModel.settings | 33 | private val settingsViewModel: SettingsViewModel by viewModels() |
| 42 | 34 | ||
| 43 | override fun onCreate(savedInstanceState: Bundle?) { | 35 | override fun onCreate(savedInstanceState: Bundle?) { |
| 44 | ThemeHelper.setTheme(this) | 36 | ThemeHelper.setTheme(this) |
| @@ -48,16 +40,17 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView { | |||
| 48 | binding = ActivitySettingsBinding.inflate(layoutInflater) | 40 | binding = ActivitySettingsBinding.inflate(layoutInflater) |
| 49 | setContentView(binding.root) | 41 | setContentView(binding.root) |
| 50 | 42 | ||
| 51 | WindowCompat.setDecorFitsSystemWindows(window, false) | 43 | settingsViewModel.game = args.game |
| 52 | 44 | ||
| 53 | val launcher = intent | 45 | val navHostFragment = |
| 54 | val gameID = launcher.getStringExtra(ARG_GAME_ID) | 46 | supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment |
| 55 | val menuTag = launcher.getStringExtra(ARG_MENU_TAG) | 47 | navHostFragment.navController.setGraph(R.navigation.settings_navigation, intent.extras) |
| 56 | presenter.onCreate(savedInstanceState, menuTag!!, gameID!!) | ||
| 57 | 48 | ||
| 58 | // Show "Back" button in the action bar for navigation | 49 | WindowCompat.setDecorFitsSystemWindows(window, false) |
| 59 | setSupportActionBar(binding.toolbarSettings) | 50 | |
| 60 | supportActionBar!!.setDisplayHomeAsUpEnabled(true) | 51 | if (savedInstanceState != null) { |
| 52 | settingsViewModel.shouldSave = savedInstanceState.getBoolean(KEY_SHOULD_SAVE) | ||
| 53 | } | ||
| 61 | 54 | ||
| 62 | if (InsetsHelper.getSystemGestureType(applicationContext) != | 55 | if (InsetsHelper.getSystemGestureType(applicationContext) != |
| 63 | InsetsHelper.GESTURE_NAVIGATION | 56 | InsetsHelper.GESTURE_NAVIGATION |
| @@ -73,6 +66,28 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView { | |||
| 73 | ) | 66 | ) |
| 74 | } | 67 | } |
| 75 | 68 | ||
| 69 | settingsViewModel.shouldRecreate.observe(this) { | ||
| 70 | if (it) { | ||
| 71 | settingsViewModel.setShouldRecreate(false) | ||
| 72 | recreate() | ||
| 73 | } | ||
| 74 | } | ||
| 75 | settingsViewModel.shouldNavigateBack.observe(this) { | ||
| 76 | if (it) { | ||
| 77 | settingsViewModel.setShouldNavigateBack(false) | ||
| 78 | navigateBack() | ||
| 79 | } | ||
| 80 | } | ||
| 81 | settingsViewModel.shouldShowResetSettingsDialog.observe(this) { | ||
| 82 | if (it) { | ||
| 83 | settingsViewModel.setShouldShowResetSettingsDialog(false) | ||
| 84 | ResetSettingsDialogFragment().show( | ||
| 85 | supportFragmentManager, | ||
| 86 | ResetSettingsDialogFragment.TAG | ||
| 87 | ) | ||
| 88 | } | ||
| 89 | } | ||
| 90 | |||
| 76 | onBackPressedDispatcher.addCallback( | 91 | onBackPressedDispatcher.addCallback( |
| 77 | this, | 92 | this, |
| 78 | object : OnBackPressedCallback(true) { | 93 | object : OnBackPressedCallback(true) { |
| @@ -83,34 +98,28 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView { | |||
| 83 | setInsets() | 98 | setInsets() |
| 84 | } | 99 | } |
| 85 | 100 | ||
| 86 | override fun onSupportNavigateUp(): Boolean { | 101 | fun navigateBack() { |
| 87 | navigateBack() | 102 | val navHostFragment = |
| 88 | return true | 103 | supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment |
| 89 | } | 104 | if (navHostFragment.childFragmentManager.backStackEntryCount > 0) { |
| 90 | 105 | navHostFragment.navController.popBackStack() | |
| 91 | private fun navigateBack() { | ||
| 92 | if (supportFragmentManager.backStackEntryCount > 0) { | ||
| 93 | supportFragmentManager.popBackStack() | ||
| 94 | } else { | 106 | } else { |
| 95 | finish() | 107 | finish() |
| 96 | } | 108 | } |
| 97 | } | 109 | } |
| 98 | 110 | ||
| 99 | override fun onCreateOptionsMenu(menu: Menu): Boolean { | ||
| 100 | val inflater = menuInflater | ||
| 101 | inflater.inflate(R.menu.menu_settings, menu) | ||
| 102 | return true | ||
| 103 | } | ||
| 104 | |||
| 105 | override fun onSaveInstanceState(outState: Bundle) { | 111 | override fun onSaveInstanceState(outState: Bundle) { |
| 106 | // Critical: If super method is not called, rotations will be busted. | 112 | // Critical: If super method is not called, rotations will be busted. |
| 107 | super.onSaveInstanceState(outState) | 113 | super.onSaveInstanceState(outState) |
| 108 | presenter.saveState(outState) | 114 | outState.putBoolean(KEY_SHOULD_SAVE, settingsViewModel.shouldSave) |
| 109 | } | 115 | } |
| 110 | 116 | ||
| 111 | override fun onStart() { | 117 | override fun onStart() { |
| 112 | super.onStart() | 118 | super.onStart() |
| 113 | presenter.onStart() | 119 | // TODO: Load custom settings contextually |
| 120 | if (!DirectoryInitialization.areDirectoriesReady) { | ||
| 121 | DirectoryInitialization.start() | ||
| 122 | } | ||
| 114 | } | 123 | } |
| 115 | 124 | ||
| 116 | /** | 125 | /** |
| @@ -120,143 +129,51 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView { | |||
| 120 | */ | 129 | */ |
| 121 | override fun onStop() { | 130 | override fun onStop() { |
| 122 | super.onStop() | 131 | super.onStop() |
| 123 | presenter.onStop(isFinishing) | 132 | if (isFinishing && settingsViewModel.shouldSave) { |
| 124 | } | 133 | Log.debug("[SettingsActivity] Settings activity stopping. Saving settings to INI...") |
| 125 | 134 | Settings.saveSettings() | |
| 126 | override fun showSettingsFragment(menuTag: String, addToStack: Boolean, gameId: String) { | ||
| 127 | if (!addToStack && settingsFragment != null) { | ||
| 128 | return | ||
| 129 | } | ||
| 130 | |||
| 131 | val transaction = supportFragmentManager.beginTransaction() | ||
| 132 | if (addToStack) { | ||
| 133 | if (areSystemAnimationsEnabled()) { | ||
| 134 | transaction.setCustomAnimations( | ||
| 135 | R.anim.anim_settings_fragment_in, | ||
| 136 | R.anim.anim_settings_fragment_out, | ||
| 137 | 0, | ||
| 138 | R.anim.anim_pop_settings_fragment_out | ||
| 139 | ) | ||
| 140 | } | ||
| 141 | transaction.addToBackStack(null) | ||
| 142 | } | 135 | } |
| 143 | transaction.replace( | ||
| 144 | R.id.frame_content, | ||
| 145 | SettingsFragment.newInstance(menuTag, gameId), | ||
| 146 | FRAGMENT_TAG | ||
| 147 | ) | ||
| 148 | transaction.commit() | ||
| 149 | } | 136 | } |
| 150 | 137 | ||
| 151 | private fun areSystemAnimationsEnabled(): Boolean { | 138 | override fun onDestroy() { |
| 152 | val duration = android.provider.Settings.Global.getFloat( | 139 | settingsViewModel.clear() |
| 153 | contentResolver, | 140 | super.onDestroy() |
| 154 | android.provider.Settings.Global.ANIMATOR_DURATION_SCALE, | ||
| 155 | 1f | ||
| 156 | ) | ||
| 157 | val transition = android.provider.Settings.Global.getFloat( | ||
| 158 | contentResolver, | ||
| 159 | android.provider.Settings.Global.TRANSITION_ANIMATION_SCALE, | ||
| 160 | 1f | ||
| 161 | ) | ||
| 162 | return duration != 0f && transition != 0f | ||
| 163 | } | ||
| 164 | |||
| 165 | override fun onSettingsFileLoaded() { | ||
| 166 | val fragment: SettingsFragmentView? = settingsFragment | ||
| 167 | fragment?.loadSettingsList() | ||
| 168 | } | ||
| 169 | |||
| 170 | override fun onSettingsFileNotFound() { | ||
| 171 | val fragment: SettingsFragmentView? = settingsFragment | ||
| 172 | fragment?.loadSettingsList() | ||
| 173 | } | ||
| 174 | |||
| 175 | override fun showToastMessage(message: String, is_long: Boolean) { | ||
| 176 | Toast.makeText( | ||
| 177 | this, | ||
| 178 | message, | ||
| 179 | if (is_long) Toast.LENGTH_LONG else Toast.LENGTH_SHORT | ||
| 180 | ).show() | ||
| 181 | } | ||
| 182 | |||
| 183 | override fun onSettingChanged() { | ||
| 184 | presenter.onSettingChanged() | ||
| 185 | } | 141 | } |
| 186 | 142 | ||
| 187 | fun onSettingsReset() { | 143 | fun onSettingsReset() { |
| 188 | // Prevents saving to a non-existent settings file | 144 | // Prevents saving to a non-existent settings file |
| 189 | presenter.onSettingsReset() | 145 | settingsViewModel.shouldSave = false |
| 190 | |||
| 191 | // Reset the static memory representation of each setting | ||
| 192 | BooleanSetting.clear() | ||
| 193 | FloatSetting.clear() | ||
| 194 | IntSetting.clear() | ||
| 195 | StringSetting.clear() | ||
| 196 | 146 | ||
| 197 | // Delete settings file because the user may have changed values that do not exist in the UI | 147 | // Delete settings file because the user may have changed values that do not exist in the UI |
| 198 | val settingsFile = SettingsFile.getSettingsFile(SettingsFile.FILE_NAME_CONFIG) | 148 | val settingsFile = SettingsFile.getSettingsFile(SettingsFile.FILE_NAME_CONFIG) |
| 199 | if (!settingsFile.delete()) { | 149 | if (!settingsFile.delete()) { |
| 200 | throw IOException("Failed to delete $settingsFile") | 150 | throw IOException("Failed to delete $settingsFile") |
| 201 | } | 151 | } |
| 152 | Settings.settingsList.forEach { it.reset() } | ||
| 202 | 153 | ||
| 203 | showToastMessage(getString(R.string.settings_reset), true) | 154 | Toast.makeText( |
| 155 | applicationContext, | ||
| 156 | getString(R.string.settings_reset), | ||
| 157 | Toast.LENGTH_LONG | ||
| 158 | ).show() | ||
| 204 | finish() | 159 | finish() |
| 205 | } | 160 | } |
| 206 | 161 | ||
| 207 | fun setToolbarTitle(title: String) { | ||
| 208 | binding.toolbarSettingsLayout.title = title | ||
| 209 | } | ||
| 210 | |||
| 211 | private val settingsFragment: SettingsFragment? | ||
| 212 | get() = supportFragmentManager.findFragmentByTag(FRAGMENT_TAG) as SettingsFragment? | ||
| 213 | |||
| 214 | private fun setInsets() { | 162 | private fun setInsets() { |
| 215 | ViewCompat.setOnApplyWindowInsetsListener( | 163 | ViewCompat.setOnApplyWindowInsetsListener( |
| 216 | binding.frameContent | 164 | binding.navigationBarShade |
| 217 | ) { view: View, windowInsets: WindowInsetsCompat -> | 165 | ) { view: View, windowInsets: WindowInsetsCompat -> |
| 218 | val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) | 166 | val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) |
| 219 | val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) | ||
| 220 | view.updatePadding( | ||
| 221 | left = barInsets.left + cutoutInsets.left, | ||
| 222 | right = barInsets.right + cutoutInsets.right | ||
| 223 | ) | ||
| 224 | 167 | ||
| 225 | val mlpAppBar = binding.appbarSettings.layoutParams as MarginLayoutParams | 168 | val mlpShade = view.layoutParams as MarginLayoutParams |
| 226 | mlpAppBar.leftMargin = barInsets.left + cutoutInsets.left | ||
| 227 | mlpAppBar.rightMargin = barInsets.right + cutoutInsets.right | ||
| 228 | binding.appbarSettings.layoutParams = mlpAppBar | ||
| 229 | |||
| 230 | val mlpShade = binding.navigationBarShade.layoutParams as MarginLayoutParams | ||
| 231 | mlpShade.height = barInsets.bottom | 169 | mlpShade.height = barInsets.bottom |
| 232 | binding.navigationBarShade.layoutParams = mlpShade | 170 | view.layoutParams = mlpShade |
| 233 | 171 | ||
| 234 | windowInsets | 172 | windowInsets |
| 235 | } | 173 | } |
| 236 | } | 174 | } |
| 237 | 175 | ||
| 238 | companion object { | 176 | companion object { |
| 239 | private const val ARG_MENU_TAG = "menu_tag" | 177 | private const val KEY_SHOULD_SAVE = "should_save" |
| 240 | private const val ARG_GAME_ID = "game_id" | ||
| 241 | private const val FRAGMENT_TAG = "settings" | ||
| 242 | |||
| 243 | fun launch(context: Context, menuTag: String?, gameId: String?) { | ||
| 244 | val settings = Intent(context, SettingsActivity::class.java) | ||
| 245 | settings.putExtra(ARG_MENU_TAG, menuTag) | ||
| 246 | settings.putExtra(ARG_GAME_ID, gameId) | ||
| 247 | context.startActivity(settings) | ||
| 248 | } | ||
| 249 | |||
| 250 | fun launch( | ||
| 251 | context: Context, | ||
| 252 | launcher: ActivityResultLauncher<Intent>, | ||
| 253 | menuTag: String?, | ||
| 254 | gameId: String? | ||
| 255 | ) { | ||
| 256 | val settings = Intent(context, SettingsActivity::class.java) | ||
| 257 | settings.putExtra(ARG_MENU_TAG, menuTag) | ||
| 258 | settings.putExtra(ARG_GAME_ID, gameId) | ||
| 259 | launcher.launch(settings) | ||
| 260 | } | ||
| 261 | } | 178 | } |
| 262 | } | 179 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityPresenter.kt deleted file mode 100644 index 93e677b21..000000000 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityPresenter.kt +++ /dev/null | |||
| @@ -1,90 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | package org.yuzu.yuzu_emu.features.settings.ui | ||
| 5 | |||
| 6 | import android.content.Context | ||
| 7 | import android.os.Bundle | ||
| 8 | import android.text.TextUtils | ||
| 9 | import java.io.File | ||
| 10 | import org.yuzu.yuzu_emu.NativeLibrary | ||
| 11 | import org.yuzu.yuzu_emu.features.settings.model.Settings | ||
| 12 | import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile | ||
| 13 | import org.yuzu.yuzu_emu.utils.DirectoryInitialization | ||
| 14 | import org.yuzu.yuzu_emu.utils.Log | ||
| 15 | |||
| 16 | class SettingsActivityPresenter(private val activityView: SettingsActivityView) { | ||
| 17 | val settings: Settings get() = activityView.settings | ||
| 18 | |||
| 19 | private var shouldSave = false | ||
| 20 | private lateinit var menuTag: String | ||
| 21 | private lateinit var gameId: String | ||
| 22 | |||
| 23 | fun onCreate(savedInstanceState: Bundle?, menuTag: String, gameId: String) { | ||
| 24 | this.menuTag = menuTag | ||
| 25 | this.gameId = gameId | ||
| 26 | if (savedInstanceState != null) { | ||
| 27 | shouldSave = savedInstanceState.getBoolean(KEY_SHOULD_SAVE) | ||
| 28 | } | ||
| 29 | } | ||
| 30 | |||
| 31 | fun onStart() { | ||
| 32 | prepareDirectoriesIfNeeded() | ||
| 33 | } | ||
| 34 | |||
| 35 | private fun loadSettingsUI() { | ||
| 36 | if (!settings.isLoaded) { | ||
| 37 | if (!TextUtils.isEmpty(gameId)) { | ||
| 38 | settings.loadSettings(gameId, activityView) | ||
| 39 | } else { | ||
| 40 | settings.loadSettings(activityView) | ||
| 41 | } | ||
| 42 | } | ||
| 43 | activityView.showSettingsFragment(menuTag, false, gameId) | ||
| 44 | activityView.onSettingsFileLoaded() | ||
| 45 | } | ||
| 46 | |||
| 47 | private fun prepareDirectoriesIfNeeded() { | ||
| 48 | val configFile = | ||
| 49 | File( | ||
| 50 | "${DirectoryInitialization.userDirectory}/config/" + | ||
| 51 | "${SettingsFile.FILE_NAME_CONFIG}.ini" | ||
| 52 | ) | ||
| 53 | if (!configFile.exists()) { | ||
| 54 | Log.error( | ||
| 55 | "${DirectoryInitialization.userDirectory}/config/" + | ||
| 56 | "${SettingsFile.FILE_NAME_CONFIG}.ini" | ||
| 57 | ) | ||
| 58 | Log.error("yuzu config file could not be found!") | ||
| 59 | } | ||
| 60 | |||
| 61 | if (!DirectoryInitialization.areDirectoriesReady) { | ||
| 62 | DirectoryInitialization.start(activityView as Context) | ||
| 63 | } | ||
| 64 | loadSettingsUI() | ||
| 65 | } | ||
| 66 | |||
| 67 | fun onStop(finishing: Boolean) { | ||
| 68 | if (finishing && shouldSave) { | ||
| 69 | Log.debug("[SettingsActivity] Settings activity stopping. Saving settings to INI...") | ||
| 70 | settings.saveSettings(activityView) | ||
| 71 | } | ||
| 72 | NativeLibrary.reloadSettings() | ||
| 73 | } | ||
| 74 | |||
| 75 | fun onSettingChanged() { | ||
| 76 | shouldSave = true | ||
| 77 | } | ||
| 78 | |||
| 79 | fun onSettingsReset() { | ||
| 80 | shouldSave = false | ||
| 81 | } | ||
| 82 | |||
| 83 | fun saveState(outState: Bundle) { | ||
| 84 | outState.putBoolean(KEY_SHOULD_SAVE, shouldSave) | ||
| 85 | } | ||
| 86 | |||
| 87 | companion object { | ||
| 88 | private const val KEY_SHOULD_SAVE = "should_save" | ||
| 89 | } | ||
| 90 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityView.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityView.kt deleted file mode 100644 index c186fc388..000000000 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityView.kt +++ /dev/null | |||
| @@ -1,57 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | package org.yuzu.yuzu_emu.features.settings.ui | ||
| 5 | |||
| 6 | import org.yuzu.yuzu_emu.features.settings.model.Settings | ||
| 7 | |||
| 8 | /** | ||
| 9 | * Abstraction for the Activity that manages SettingsFragments. | ||
| 10 | */ | ||
| 11 | interface SettingsActivityView { | ||
| 12 | /** | ||
| 13 | * Show a new SettingsFragment. | ||
| 14 | * | ||
| 15 | * @param menuTag Identifier for the settings group that should be displayed. | ||
| 16 | * @param addToStack Whether or not this fragment should replace a previous one. | ||
| 17 | */ | ||
| 18 | fun showSettingsFragment(menuTag: String, addToStack: Boolean, gameId: String) | ||
| 19 | |||
| 20 | /** | ||
| 21 | * Called by a contained Fragment to get access to the Setting HashMap | ||
| 22 | * loaded from disk, so that each Fragment doesn't need to perform its own | ||
| 23 | * read operation. | ||
| 24 | * | ||
| 25 | * @return A HashMap of Settings. | ||
| 26 | */ | ||
| 27 | val settings: Settings | ||
| 28 | |||
| 29 | /** | ||
| 30 | * Called when a load operation completes. | ||
| 31 | */ | ||
| 32 | fun onSettingsFileLoaded() | ||
| 33 | |||
| 34 | /** | ||
| 35 | * Called when a load operation fails. | ||
| 36 | */ | ||
| 37 | fun onSettingsFileNotFound() | ||
| 38 | |||
| 39 | /** | ||
| 40 | * Display a popup text message on screen. | ||
| 41 | * | ||
| 42 | * @param message The contents of the onscreen message. | ||
| 43 | * @param is_long Whether this should be a long Toast or short one. | ||
| 44 | */ | ||
| 45 | fun showToastMessage(message: String, is_long: Boolean) | ||
| 46 | |||
| 47 | /** | ||
| 48 | * End the activity. | ||
| 49 | */ | ||
| 50 | fun finish() | ||
| 51 | |||
| 52 | /** | ||
| 53 | * Called by a containing Fragment to tell the Activity that a setting was changed; | ||
| 54 | * unless this has been called, the Activity will not save to disk. | ||
| 55 | */ | ||
| 56 | fun onSettingChanged() | ||
| 57 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt index 9711e2c51..a7a029fc1 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt | |||
| @@ -4,51 +4,54 @@ | |||
| 4 | package org.yuzu.yuzu_emu.features.settings.ui | 4 | package org.yuzu.yuzu_emu.features.settings.ui |
| 5 | 5 | ||
| 6 | import android.content.Context | 6 | import android.content.Context |
| 7 | import android.content.DialogInterface | ||
| 8 | import android.icu.util.Calendar | 7 | import android.icu.util.Calendar |
| 9 | import android.icu.util.TimeZone | 8 | import android.icu.util.TimeZone |
| 10 | import android.text.format.DateFormat | 9 | import android.text.format.DateFormat |
| 11 | import android.view.LayoutInflater | 10 | import android.view.LayoutInflater |
| 12 | import android.view.ViewGroup | 11 | import android.view.ViewGroup |
| 13 | import android.widget.TextView | 12 | import androidx.fragment.app.Fragment |
| 14 | import androidx.appcompat.app.AlertDialog | 13 | import androidx.lifecycle.Lifecycle |
| 15 | import androidx.appcompat.app.AppCompatActivity | 14 | import androidx.lifecycle.ViewModelProvider |
| 16 | import androidx.recyclerview.widget.RecyclerView | 15 | import androidx.lifecycle.lifecycleScope |
| 16 | import androidx.lifecycle.repeatOnLifecycle | ||
| 17 | import androidx.navigation.findNavController | ||
| 18 | import androidx.recyclerview.widget.AsyncDifferConfig | ||
| 19 | import androidx.recyclerview.widget.DiffUtil | ||
| 20 | import androidx.recyclerview.widget.ListAdapter | ||
| 17 | import com.google.android.material.datepicker.MaterialDatePicker | 21 | import com.google.android.material.datepicker.MaterialDatePicker |
| 18 | import com.google.android.material.dialog.MaterialAlertDialogBuilder | ||
| 19 | import com.google.android.material.slider.Slider | ||
| 20 | import com.google.android.material.timepicker.MaterialTimePicker | 22 | import com.google.android.material.timepicker.MaterialTimePicker |
| 21 | import com.google.android.material.timepicker.TimeFormat | 23 | import com.google.android.material.timepicker.TimeFormat |
| 24 | import kotlinx.coroutines.launch | ||
| 22 | import org.yuzu.yuzu_emu.R | 25 | import org.yuzu.yuzu_emu.R |
| 23 | import org.yuzu.yuzu_emu.databinding.DialogSliderBinding | 26 | import org.yuzu.yuzu_emu.SettingsNavigationDirections |
| 24 | import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding | 27 | import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding |
| 25 | import org.yuzu.yuzu_emu.databinding.ListItemSettingSwitchBinding | 28 | import org.yuzu.yuzu_emu.databinding.ListItemSettingSwitchBinding |
| 26 | import org.yuzu.yuzu_emu.databinding.ListItemSettingsHeaderBinding | 29 | import org.yuzu.yuzu_emu.databinding.ListItemSettingsHeaderBinding |
| 27 | import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting | ||
| 28 | import org.yuzu.yuzu_emu.features.settings.model.AbstractFloatSetting | ||
| 29 | import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting | ||
| 30 | import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting | ||
| 31 | import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting | ||
| 32 | import org.yuzu.yuzu_emu.features.settings.model.FloatSetting | ||
| 33 | import org.yuzu.yuzu_emu.features.settings.model.view.* | 30 | import org.yuzu.yuzu_emu.features.settings.model.view.* |
| 34 | import org.yuzu.yuzu_emu.features.settings.ui.viewholder.* | 31 | import org.yuzu.yuzu_emu.features.settings.ui.viewholder.* |
| 32 | import org.yuzu.yuzu_emu.fragments.SettingsDialogFragment | ||
| 33 | import org.yuzu.yuzu_emu.model.SettingsViewModel | ||
| 35 | 34 | ||
| 36 | class SettingsAdapter( | 35 | class SettingsAdapter( |
| 37 | private val fragmentView: SettingsFragmentView, | 36 | private val fragment: Fragment, |
| 38 | private val context: Context | 37 | private val context: Context |
| 39 | ) : RecyclerView.Adapter<SettingViewHolder?>(), DialogInterface.OnClickListener { | 38 | ) : ListAdapter<SettingsItem, SettingViewHolder>( |
| 40 | private var settings: ArrayList<SettingsItem>? = null | 39 | AsyncDifferConfig.Builder(DiffCallback()).build() |
| 41 | private var clickedItem: SettingsItem? = null | 40 | ) { |
| 42 | private var clickedPosition: Int | 41 | private val settingsViewModel: SettingsViewModel |
| 43 | private var dialog: AlertDialog? = null | 42 | get() = ViewModelProvider(fragment.requireActivity())[SettingsViewModel::class.java] |
| 44 | private var sliderProgress = 0 | ||
| 45 | private var textSliderValue: TextView? = null | ||
| 46 | |||
| 47 | private var defaultCancelListener = | ||
| 48 | DialogInterface.OnClickListener { _: DialogInterface?, _: Int -> closeDialog() } | ||
| 49 | 43 | ||
| 50 | init { | 44 | init { |
| 51 | clickedPosition = -1 | 45 | fragment.viewLifecycleOwner.lifecycleScope.launch { |
| 46 | fragment.repeatOnLifecycle(Lifecycle.State.STARTED) { | ||
| 47 | settingsViewModel.adapterItemChanged.collect { | ||
| 48 | if (it != -1) { | ||
| 49 | notifyItemChanged(it) | ||
| 50 | settingsViewModel.setAdapterItemChanged(-1) | ||
| 51 | } | ||
| 52 | } | ||
| 53 | } | ||
| 54 | } | ||
| 52 | } | 55 | } |
| 53 | 56 | ||
| 54 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SettingViewHolder { | 57 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SettingViewHolder { |
| @@ -90,67 +93,41 @@ class SettingsAdapter( | |||
| 90 | } | 93 | } |
| 91 | 94 | ||
| 92 | override fun onBindViewHolder(holder: SettingViewHolder, position: Int) { | 95 | override fun onBindViewHolder(holder: SettingViewHolder, position: Int) { |
| 93 | holder.bind(getItem(position)) | 96 | holder.bind(currentList[position]) |
| 94 | } | 97 | } |
| 95 | 98 | ||
| 96 | private fun getItem(position: Int): SettingsItem { | 99 | override fun getItemCount(): Int = currentList.size |
| 97 | return settings!![position] | ||
| 98 | } | ||
| 99 | |||
| 100 | override fun getItemCount(): Int { | ||
| 101 | return if (settings != null) { | ||
| 102 | settings!!.size | ||
| 103 | } else { | ||
| 104 | 0 | ||
| 105 | } | ||
| 106 | } | ||
| 107 | 100 | ||
| 108 | override fun getItemViewType(position: Int): Int { | 101 | override fun getItemViewType(position: Int): Int { |
| 109 | return getItem(position).type | 102 | return currentList[position].type |
| 110 | } | ||
| 111 | |||
| 112 | fun setSettingsList(settings: ArrayList<SettingsItem>?) { | ||
| 113 | this.settings = settings | ||
| 114 | notifyDataSetChanged() | ||
| 115 | } | ||
| 116 | |||
| 117 | fun onBooleanClick(item: SwitchSetting, position: Int, checked: Boolean) { | ||
| 118 | val setting = item.setChecked(checked) | ||
| 119 | fragmentView.putSetting(setting) | ||
| 120 | fragmentView.onSettingChanged() | ||
| 121 | } | 103 | } |
| 122 | 104 | ||
| 123 | private fun onSingleChoiceClick(item: SingleChoiceSetting) { | 105 | fun onBooleanClick(item: SwitchSetting, checked: Boolean) { |
| 124 | clickedItem = item | 106 | item.checked = checked |
| 125 | val value = getSelectionForSingleChoiceValue(item) | 107 | settingsViewModel.setShouldReloadSettingsList(true) |
| 126 | dialog = MaterialAlertDialogBuilder(context) | 108 | settingsViewModel.shouldSave = true |
| 127 | .setTitle(item.nameId) | ||
| 128 | .setSingleChoiceItems(item.choicesId, value, this) | ||
| 129 | .show() | ||
| 130 | } | 109 | } |
| 131 | 110 | ||
| 132 | fun onSingleChoiceClick(item: SingleChoiceSetting, position: Int) { | 111 | fun onSingleChoiceClick(item: SingleChoiceSetting, position: Int) { |
| 133 | clickedPosition = position | 112 | SettingsDialogFragment.newInstance( |
| 134 | onSingleChoiceClick(item) | 113 | settingsViewModel, |
| 135 | } | 114 | item, |
| 136 | 115 | SettingsItem.TYPE_SINGLE_CHOICE, | |
| 137 | private fun onStringSingleChoiceClick(item: StringSingleChoiceSetting) { | 116 | position |
| 138 | clickedItem = item | 117 | ).show(fragment.childFragmentManager, SettingsDialogFragment.TAG) |
| 139 | dialog = MaterialAlertDialogBuilder(context) | ||
| 140 | .setTitle(item.nameId) | ||
| 141 | .setSingleChoiceItems(item.choices, item.selectValueIndex, this) | ||
| 142 | .show() | ||
| 143 | } | 118 | } |
| 144 | 119 | ||
| 145 | fun onStringSingleChoiceClick(item: StringSingleChoiceSetting, position: Int) { | 120 | fun onStringSingleChoiceClick(item: StringSingleChoiceSetting, position: Int) { |
| 146 | clickedPosition = position | 121 | SettingsDialogFragment.newInstance( |
| 147 | onStringSingleChoiceClick(item) | 122 | settingsViewModel, |
| 123 | item, | ||
| 124 | SettingsItem.TYPE_STRING_SINGLE_CHOICE, | ||
| 125 | position | ||
| 126 | ).show(fragment.childFragmentManager, SettingsDialogFragment.TAG) | ||
| 148 | } | 127 | } |
| 149 | 128 | ||
| 150 | fun onDateTimeClick(item: DateTimeSetting, position: Int) { | 129 | fun onDateTimeClick(item: DateTimeSetting, position: Int) { |
| 151 | clickedItem = item | 130 | val storedTime = item.value * 1000 |
| 152 | clickedPosition = position | ||
| 153 | val storedTime = java.lang.Long.decode(item.value) * 1000 | ||
| 154 | 131 | ||
| 155 | // Helper to extract hour and minute from epoch time | 132 | // Helper to extract hour and minute from epoch time |
| 156 | val calendar: Calendar = Calendar.getInstance() | 133 | val calendar: Calendar = Calendar.getInstance() |
| @@ -158,7 +135,7 @@ class SettingsAdapter( | |||
| 158 | calendar.timeZone = TimeZone.getTimeZone("UTC") | 135 | calendar.timeZone = TimeZone.getTimeZone("UTC") |
| 159 | 136 | ||
| 160 | var timeFormat: Int = TimeFormat.CLOCK_12H | 137 | var timeFormat: Int = TimeFormat.CLOCK_12H |
| 161 | if (DateFormat.is24HourFormat(fragmentView.activityView as AppCompatActivity)) { | 138 | if (DateFormat.is24HourFormat(context)) { |
| 162 | timeFormat = TimeFormat.CLOCK_24H | 139 | timeFormat = TimeFormat.CLOCK_24H |
| 163 | } | 140 | } |
| 164 | 141 | ||
| @@ -175,7 +152,7 @@ class SettingsAdapter( | |||
| 175 | 152 | ||
| 176 | datePicker.addOnPositiveButtonClickListener { | 153 | datePicker.addOnPositiveButtonClickListener { |
| 177 | timePicker.show( | 154 | timePicker.show( |
| 178 | (fragmentView.activityView as AppCompatActivity).supportFragmentManager, | 155 | fragment.childFragmentManager, |
| 179 | "TimePicker" | 156 | "TimePicker" |
| 180 | ) | 157 | ) |
| 181 | } | 158 | } |
| @@ -183,160 +160,50 @@ class SettingsAdapter( | |||
| 183 | var epochTime: Long = datePicker.selection!! / 1000 | 160 | var epochTime: Long = datePicker.selection!! / 1000 |
| 184 | epochTime += timePicker.hour.toLong() * 60 * 60 | 161 | epochTime += timePicker.hour.toLong() * 60 * 60 |
| 185 | epochTime += timePicker.minute.toLong() * 60 | 162 | epochTime += timePicker.minute.toLong() * 60 |
| 186 | val rtcString = epochTime.toString() | 163 | if (item.value != epochTime) { |
| 187 | if (item.value != rtcString) { | 164 | settingsViewModel.shouldSave = true |
| 188 | fragmentView.onSettingChanged() | 165 | notifyItemChanged(position) |
| 166 | item.value = epochTime | ||
| 189 | } | 167 | } |
| 190 | notifyItemChanged(clickedPosition) | ||
| 191 | val setting = item.setSelectedValue(rtcString) | ||
| 192 | fragmentView.putSetting(setting) | ||
| 193 | clickedItem = null | ||
| 194 | } | 168 | } |
| 195 | datePicker.show( | 169 | datePicker.show( |
| 196 | (fragmentView.activityView as AppCompatActivity).supportFragmentManager, | 170 | fragment.childFragmentManager, |
| 197 | "DatePicker" | 171 | "DatePicker" |
| 198 | ) | 172 | ) |
| 199 | } | 173 | } |
| 200 | 174 | ||
| 201 | fun onSliderClick(item: SliderSetting, position: Int) { | 175 | fun onSliderClick(item: SliderSetting, position: Int) { |
| 202 | clickedItem = item | 176 | SettingsDialogFragment.newInstance( |
| 203 | clickedPosition = position | 177 | settingsViewModel, |
| 204 | sliderProgress = item.selectedValue | 178 | item, |
| 205 | 179 | SettingsItem.TYPE_SLIDER, | |
| 206 | val inflater = LayoutInflater.from(context) | 180 | position |
| 207 | val sliderBinding = DialogSliderBinding.inflate(inflater) | 181 | ).show(fragment.childFragmentManager, SettingsDialogFragment.TAG) |
| 208 | |||
| 209 | textSliderValue = sliderBinding.textValue | ||
| 210 | textSliderValue!!.text = String.format( | ||
| 211 | context.getString(R.string.value_with_units), | ||
| 212 | sliderProgress.toString(), | ||
| 213 | item.units | ||
| 214 | ) | ||
| 215 | |||
| 216 | sliderBinding.slider.apply { | ||
| 217 | valueFrom = item.min.toFloat() | ||
| 218 | valueTo = item.max.toFloat() | ||
| 219 | value = sliderProgress.toFloat() | ||
| 220 | addOnChangeListener { _: Slider, value: Float, _: Boolean -> | ||
| 221 | sliderProgress = value.toInt() | ||
| 222 | textSliderValue!!.text = String.format( | ||
| 223 | context.getString(R.string.value_with_units), | ||
| 224 | sliderProgress.toString(), | ||
| 225 | item.units | ||
| 226 | ) | ||
| 227 | } | ||
| 228 | } | ||
| 229 | |||
| 230 | dialog = MaterialAlertDialogBuilder(context) | ||
| 231 | .setTitle(item.nameId) | ||
| 232 | .setView(sliderBinding.root) | ||
| 233 | .setPositiveButton(android.R.string.ok, this) | ||
| 234 | .setNegativeButton(android.R.string.cancel, defaultCancelListener) | ||
| 235 | .show() | ||
| 236 | } | 182 | } |
| 237 | 183 | ||
| 238 | fun onSubmenuClick(item: SubmenuSetting) { | 184 | fun onSubmenuClick(item: SubmenuSetting) { |
| 239 | fragmentView.loadSubMenu(item.menuKey) | 185 | val action = SettingsNavigationDirections.actionGlobalSettingsFragment(item.menuKey, null) |
| 186 | fragment.view?.findNavController()?.navigate(action) | ||
| 240 | } | 187 | } |
| 241 | 188 | ||
| 242 | override fun onClick(dialog: DialogInterface, which: Int) { | 189 | fun onLongClick(item: SettingsItem, position: Int): Boolean { |
| 243 | when (clickedItem) { | 190 | SettingsDialogFragment.newInstance( |
| 244 | is SingleChoiceSetting -> { | 191 | settingsViewModel, |
| 245 | val scSetting = clickedItem as SingleChoiceSetting | 192 | item, |
| 246 | val value = getValueForSingleChoiceSelection(scSetting, which) | 193 | SettingsDialogFragment.TYPE_RESET_SETTING, |
| 247 | if (scSetting.selectedValue != value) { | 194 | position |
| 248 | fragmentView.onSettingChanged() | 195 | ).show(fragment.childFragmentManager, SettingsDialogFragment.TAG) |
| 249 | } | ||
| 250 | |||
| 251 | // Get the backing Setting, which may be null (if for example it was missing from the file) | ||
| 252 | val setting = scSetting.setSelectedValue(value) | ||
| 253 | fragmentView.putSetting(setting) | ||
| 254 | closeDialog() | ||
| 255 | } | ||
| 256 | |||
| 257 | is StringSingleChoiceSetting -> { | ||
| 258 | val scSetting = clickedItem as StringSingleChoiceSetting | ||
| 259 | val value = scSetting.getValueAt(which) | ||
| 260 | if (scSetting.selectedValue != value) fragmentView.onSettingChanged() | ||
| 261 | val setting = scSetting.setSelectedValue(value!!) | ||
| 262 | fragmentView.putSetting(setting) | ||
| 263 | closeDialog() | ||
| 264 | } | ||
| 265 | |||
| 266 | is SliderSetting -> { | ||
| 267 | val sliderSetting = clickedItem as SliderSetting | ||
| 268 | if (sliderSetting.selectedValue != sliderProgress) { | ||
| 269 | fragmentView.onSettingChanged() | ||
| 270 | } | ||
| 271 | if (sliderSetting.setting is FloatSetting) { | ||
| 272 | val value = sliderProgress.toFloat() | ||
| 273 | val setting = sliderSetting.setSelectedValue(value) | ||
| 274 | fragmentView.putSetting(setting) | ||
| 275 | } else { | ||
| 276 | val setting = sliderSetting.setSelectedValue(sliderProgress) | ||
| 277 | fragmentView.putSetting(setting) | ||
| 278 | } | ||
| 279 | closeDialog() | ||
| 280 | } | ||
| 281 | } | ||
| 282 | clickedItem = null | ||
| 283 | sliderProgress = -1 | ||
| 284 | } | ||
| 285 | |||
| 286 | fun onLongClick(setting: AbstractSetting, position: Int): Boolean { | ||
| 287 | MaterialAlertDialogBuilder(context) | ||
| 288 | .setMessage(R.string.reset_setting_confirmation) | ||
| 289 | .setPositiveButton(android.R.string.ok) { dialog: DialogInterface, which: Int -> | ||
| 290 | when (setting) { | ||
| 291 | is AbstractBooleanSetting -> setting.boolean = setting.defaultValue as Boolean | ||
| 292 | is AbstractFloatSetting -> setting.float = setting.defaultValue as Float | ||
| 293 | is AbstractIntSetting -> setting.int = setting.defaultValue as Int | ||
| 294 | is AbstractStringSetting -> setting.string = setting.defaultValue as String | ||
| 295 | } | ||
| 296 | notifyItemChanged(position) | ||
| 297 | fragmentView.onSettingChanged() | ||
| 298 | } | ||
| 299 | .setNegativeButton(android.R.string.cancel, null) | ||
| 300 | .show() | ||
| 301 | 196 | ||
| 302 | return true | 197 | return true |
| 303 | } | 198 | } |
| 304 | 199 | ||
| 305 | fun closeDialog() { | 200 | private class DiffCallback : DiffUtil.ItemCallback<SettingsItem>() { |
| 306 | if (dialog != null) { | 201 | override fun areItemsTheSame(oldItem: SettingsItem, newItem: SettingsItem): Boolean { |
| 307 | if (clickedPosition != -1) { | 202 | return oldItem.setting.key == newItem.setting.key |
| 308 | notifyItemChanged(clickedPosition) | ||
| 309 | clickedPosition = -1 | ||
| 310 | } | ||
| 311 | dialog!!.dismiss() | ||
| 312 | dialog = null | ||
| 313 | } | 203 | } |
| 314 | } | ||
| 315 | 204 | ||
| 316 | private fun getValueForSingleChoiceSelection(item: SingleChoiceSetting, which: Int): Int { | 205 | override fun areContentsTheSame(oldItem: SettingsItem, newItem: SettingsItem): Boolean { |
| 317 | val valuesId = item.valuesId | 206 | return oldItem.setting.key == newItem.setting.key |
| 318 | return if (valuesId > 0) { | ||
| 319 | val valuesArray = context.resources.getIntArray(valuesId) | ||
| 320 | valuesArray[which] | ||
| 321 | } else { | ||
| 322 | which | ||
| 323 | } | ||
| 324 | } | ||
| 325 | |||
| 326 | private fun getSelectionForSingleChoiceValue(item: SingleChoiceSetting): Int { | ||
| 327 | val value = item.selectedValue | ||
| 328 | val valuesId = item.valuesId | ||
| 329 | if (valuesId > 0) { | ||
| 330 | val valuesArray = context.resources.getIntArray(valuesId) | ||
| 331 | for (index in valuesArray.indices) { | ||
| 332 | val current = valuesArray[index] | ||
| 333 | if (current == value) { | ||
| 334 | return index | ||
| 335 | } | ||
| 336 | } | ||
| 337 | } else { | ||
| 338 | return value | ||
| 339 | } | 207 | } |
| 340 | return -1 | ||
| 341 | } | 208 | } |
| 342 | } | 209 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt index 70a74c4dd..bc319714c 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt | |||
| @@ -3,40 +3,43 @@ | |||
| 3 | 3 | ||
| 4 | package org.yuzu.yuzu_emu.features.settings.ui | 4 | package org.yuzu.yuzu_emu.features.settings.ui |
| 5 | 5 | ||
| 6 | import android.content.Context | ||
| 7 | import android.os.Bundle | 6 | import android.os.Bundle |
| 8 | import android.view.LayoutInflater | 7 | import android.view.LayoutInflater |
| 9 | import android.view.View | 8 | import android.view.View |
| 10 | import android.view.ViewGroup | 9 | import android.view.ViewGroup |
| 10 | import android.view.ViewGroup.MarginLayoutParams | ||
| 11 | import androidx.core.view.ViewCompat | 11 | import androidx.core.view.ViewCompat |
| 12 | import androidx.core.view.WindowInsetsCompat | 12 | import androidx.core.view.WindowInsetsCompat |
| 13 | import androidx.core.view.updatePadding | 13 | import androidx.core.view.updatePadding |
| 14 | import androidx.fragment.app.Fragment | 14 | import androidx.fragment.app.Fragment |
| 15 | import androidx.fragment.app.activityViewModels | ||
| 16 | import androidx.navigation.findNavController | ||
| 17 | import androidx.navigation.fragment.navArgs | ||
| 15 | import androidx.recyclerview.widget.LinearLayoutManager | 18 | import androidx.recyclerview.widget.LinearLayoutManager |
| 16 | import com.google.android.material.divider.MaterialDividerItemDecoration | 19 | import com.google.android.material.divider.MaterialDividerItemDecoration |
| 20 | import com.google.android.material.transition.MaterialSharedAxis | ||
| 21 | import org.yuzu.yuzu_emu.R | ||
| 17 | import org.yuzu.yuzu_emu.databinding.FragmentSettingsBinding | 22 | import org.yuzu.yuzu_emu.databinding.FragmentSettingsBinding |
| 18 | import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting | 23 | import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile |
| 19 | import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem | 24 | import org.yuzu.yuzu_emu.model.SettingsViewModel |
| 20 | 25 | ||
| 21 | class SettingsFragment : Fragment(), SettingsFragmentView { | 26 | class SettingsFragment : Fragment() { |
| 22 | override var activityView: SettingsActivityView? = null | 27 | private lateinit var presenter: SettingsFragmentPresenter |
| 23 | |||
| 24 | private val fragmentPresenter = SettingsFragmentPresenter(this) | ||
| 25 | private var settingsAdapter: SettingsAdapter? = null | 28 | private var settingsAdapter: SettingsAdapter? = null |
| 26 | 29 | ||
| 27 | private var _binding: FragmentSettingsBinding? = null | 30 | private var _binding: FragmentSettingsBinding? = null |
| 28 | private val binding get() = _binding!! | 31 | private val binding get() = _binding!! |
| 29 | 32 | ||
| 30 | override fun onAttach(context: Context) { | 33 | private val args by navArgs<SettingsFragmentArgs>() |
| 31 | super.onAttach(context) | 34 | |
| 32 | activityView = requireActivity() as SettingsActivityView | 35 | private val settingsViewModel: SettingsViewModel by activityViewModels() |
| 33 | } | ||
| 34 | 36 | ||
| 35 | override fun onCreate(savedInstanceState: Bundle?) { | 37 | override fun onCreate(savedInstanceState: Bundle?) { |
| 36 | super.onCreate(savedInstanceState) | 38 | super.onCreate(savedInstanceState) |
| 37 | val menuTag = requireArguments().getString(ARGUMENT_MENU_TAG) | 39 | enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) |
| 38 | val gameId = requireArguments().getString(ARGUMENT_GAME_ID) | 40 | returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false) |
| 39 | fragmentPresenter.onCreate(menuTag!!, gameId!!) | 41 | reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false) |
| 42 | exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) | ||
| 40 | } | 43 | } |
| 41 | 44 | ||
| 42 | override fun onCreateView( | 45 | override fun onCreateView( |
| @@ -49,7 +52,14 @@ class SettingsFragment : Fragment(), SettingsFragmentView { | |||
| 49 | } | 52 | } |
| 50 | 53 | ||
| 51 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | 54 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { |
| 52 | settingsAdapter = SettingsAdapter(this, requireActivity()) | 55 | settingsAdapter = SettingsAdapter(this, requireContext()) |
| 56 | presenter = SettingsFragmentPresenter( | ||
| 57 | settingsViewModel, | ||
| 58 | settingsAdapter!!, | ||
| 59 | args.menuTag, | ||
| 60 | args.game?.gameId ?: "" | ||
| 61 | ) | ||
| 62 | |||
| 53 | val dividerDecoration = MaterialDividerItemDecoration( | 63 | val dividerDecoration = MaterialDividerItemDecoration( |
| 54 | requireContext(), | 64 | requireContext(), |
| 55 | LinearLayoutManager.VERTICAL | 65 | LinearLayoutManager.VERTICAL |
| @@ -57,71 +67,86 @@ class SettingsFragment : Fragment(), SettingsFragmentView { | |||
| 57 | dividerDecoration.isLastItemDecorated = false | 67 | dividerDecoration.isLastItemDecorated = false |
| 58 | binding.listSettings.apply { | 68 | binding.listSettings.apply { |
| 59 | adapter = settingsAdapter | 69 | adapter = settingsAdapter |
| 60 | layoutManager = LinearLayoutManager(activity) | 70 | layoutManager = LinearLayoutManager(requireContext()) |
| 61 | addItemDecoration(dividerDecoration) | 71 | addItemDecoration(dividerDecoration) |
| 62 | } | 72 | } |
| 63 | fragmentPresenter.onViewCreated() | ||
| 64 | 73 | ||
| 65 | setInsets() | 74 | binding.toolbarSettings.setNavigationOnClickListener { |
| 66 | } | 75 | settingsViewModel.setShouldNavigateBack(true) |
| 76 | } | ||
| 67 | 77 | ||
| 68 | override fun onDetach() { | 78 | settingsViewModel.toolbarTitle.observe(viewLifecycleOwner) { |
| 69 | super.onDetach() | 79 | if (it.isNotEmpty()) binding.toolbarSettingsLayout.title = it |
| 70 | activityView = null | ||
| 71 | if (settingsAdapter != null) { | ||
| 72 | settingsAdapter!!.closeDialog() | ||
| 73 | } | 80 | } |
| 74 | } | ||
| 75 | 81 | ||
| 76 | override fun showSettingsList(settingsList: ArrayList<SettingsItem>) { | 82 | settingsViewModel.shouldReloadSettingsList.observe(viewLifecycleOwner) { |
| 77 | settingsAdapter!!.setSettingsList(settingsList) | 83 | if (it) { |
| 78 | } | 84 | settingsViewModel.setShouldReloadSettingsList(false) |
| 85 | presenter.loadSettingsList() | ||
| 86 | } | ||
| 87 | } | ||
| 79 | 88 | ||
| 80 | override fun loadSettingsList() { | 89 | settingsViewModel.isUsingSearch.observe(viewLifecycleOwner) { |
| 81 | fragmentPresenter.loadSettingsList() | 90 | if (it) { |
| 82 | } | 91 | reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true) |
| 92 | exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false) | ||
| 93 | } else { | ||
| 94 | reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false) | ||
| 95 | exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) | ||
| 96 | } | ||
| 97 | } | ||
| 83 | 98 | ||
| 84 | override fun loadSubMenu(menuKey: String) { | 99 | if (args.menuTag == SettingsFile.FILE_NAME_CONFIG) { |
| 85 | activityView!!.showSettingsFragment( | 100 | binding.toolbarSettings.inflateMenu(R.menu.menu_settings) |
| 86 | menuKey, | 101 | binding.toolbarSettings.setOnMenuItemClickListener { |
| 87 | true, | 102 | when (it.itemId) { |
| 88 | requireArguments().getString(ARGUMENT_GAME_ID)!! | 103 | R.id.action_search -> { |
| 89 | ) | 104 | reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true) |
| 90 | } | 105 | exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false) |
| 106 | view.findNavController() | ||
| 107 | .navigate(R.id.action_settingsFragment_to_settingsSearchFragment) | ||
| 108 | true | ||
| 109 | } | ||
| 110 | |||
| 111 | else -> false | ||
| 112 | } | ||
| 113 | } | ||
| 114 | } | ||
| 91 | 115 | ||
| 92 | override fun showToastMessage(message: String?, is_long: Boolean) { | 116 | presenter.onViewCreated() |
| 93 | activityView!!.showToastMessage(message!!, is_long) | ||
| 94 | } | ||
| 95 | 117 | ||
| 96 | override fun putSetting(setting: AbstractSetting) { | 118 | setInsets() |
| 97 | fragmentPresenter.putSetting(setting) | ||
| 98 | } | 119 | } |
| 99 | 120 | ||
| 100 | override fun onSettingChanged() { | 121 | override fun onResume() { |
| 101 | activityView!!.onSettingChanged() | 122 | super.onResume() |
| 123 | settingsViewModel.setIsUsingSearch(false) | ||
| 102 | } | 124 | } |
| 103 | 125 | ||
| 104 | private fun setInsets() { | 126 | private fun setInsets() { |
| 105 | ViewCompat.setOnApplyWindowInsetsListener( | 127 | ViewCompat.setOnApplyWindowInsetsListener( |
| 106 | binding.listSettings | 128 | binding.root |
| 107 | ) { view: View, windowInsets: WindowInsetsCompat -> | 129 | ) { _: View, windowInsets: WindowInsetsCompat -> |
| 108 | val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) | 130 | val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) |
| 109 | view.updatePadding(bottom = insets.bottom) | 131 | val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) |
| 132 | |||
| 133 | val leftInsets = barInsets.left + cutoutInsets.left | ||
| 134 | val rightInsets = barInsets.right + cutoutInsets.right | ||
| 135 | |||
| 136 | val sideMargin = resources.getDimensionPixelSize(R.dimen.spacing_medlarge) | ||
| 137 | val mlpSettingsList = binding.listSettings.layoutParams as MarginLayoutParams | ||
| 138 | mlpSettingsList.leftMargin = sideMargin + leftInsets | ||
| 139 | mlpSettingsList.rightMargin = sideMargin + rightInsets | ||
| 140 | binding.listSettings.layoutParams = mlpSettingsList | ||
| 141 | binding.listSettings.updatePadding( | ||
| 142 | bottom = barInsets.bottom | ||
| 143 | ) | ||
| 144 | |||
| 145 | val mlpAppBar = binding.appbarSettings.layoutParams as MarginLayoutParams | ||
| 146 | mlpAppBar.leftMargin = leftInsets | ||
| 147 | mlpAppBar.rightMargin = rightInsets | ||
| 148 | binding.appbarSettings.layoutParams = mlpAppBar | ||
| 110 | windowInsets | 149 | windowInsets |
| 111 | } | 150 | } |
| 112 | } | 151 | } |
| 113 | |||
| 114 | companion object { | ||
| 115 | private const val ARGUMENT_MENU_TAG = "menu_tag" | ||
| 116 | private const val ARGUMENT_GAME_ID = "game_id" | ||
| 117 | |||
| 118 | fun newInstance(menuTag: String?, gameId: String?): Fragment { | ||
| 119 | val fragment = SettingsFragment() | ||
| 120 | val arguments = Bundle() | ||
| 121 | arguments.putString(ARGUMENT_MENU_TAG, menuTag) | ||
| 122 | arguments.putString(ARGUMENT_GAME_ID, gameId) | ||
| 123 | fragment.arguments = arguments | ||
| 124 | return fragment | ||
| 125 | } | ||
| 126 | } | ||
| 127 | } | 152 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt index 59c1d9d54..22a529b1b 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt | |||
| @@ -3,63 +3,66 @@ | |||
| 3 | 3 | ||
| 4 | package org.yuzu.yuzu_emu.features.settings.ui | 4 | package org.yuzu.yuzu_emu.features.settings.ui |
| 5 | 5 | ||
| 6 | import android.content.Context | ||
| 6 | import android.content.SharedPreferences | 7 | import android.content.SharedPreferences |
| 7 | import android.os.Build | 8 | import android.os.Build |
| 8 | import android.text.TextUtils | 9 | import android.text.TextUtils |
| 10 | import android.widget.Toast | ||
| 9 | import androidx.preference.PreferenceManager | 11 | import androidx.preference.PreferenceManager |
| 10 | import org.yuzu.yuzu_emu.R | 12 | import org.yuzu.yuzu_emu.R |
| 11 | import org.yuzu.yuzu_emu.YuzuApplication | 13 | import org.yuzu.yuzu_emu.YuzuApplication |
| 12 | import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting | 14 | import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting |
| 13 | import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting | 15 | import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting |
| 14 | import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting | ||
| 15 | import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting | 16 | import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting |
| 17 | import org.yuzu.yuzu_emu.features.settings.model.ByteSetting | ||
| 16 | import org.yuzu.yuzu_emu.features.settings.model.IntSetting | 18 | import org.yuzu.yuzu_emu.features.settings.model.IntSetting |
| 19 | import org.yuzu.yuzu_emu.features.settings.model.LongSetting | ||
| 17 | import org.yuzu.yuzu_emu.features.settings.model.Settings | 20 | import org.yuzu.yuzu_emu.features.settings.model.Settings |
| 18 | import org.yuzu.yuzu_emu.features.settings.model.StringSetting | 21 | import org.yuzu.yuzu_emu.features.settings.model.ShortSetting |
| 19 | import org.yuzu.yuzu_emu.features.settings.model.view.* | 22 | import org.yuzu.yuzu_emu.features.settings.model.view.* |
| 20 | import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile | 23 | import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile |
| 21 | import org.yuzu.yuzu_emu.fragments.ResetSettingsDialogFragment | 24 | import org.yuzu.yuzu_emu.model.SettingsViewModel |
| 22 | import org.yuzu.yuzu_emu.utils.ThemeHelper | 25 | import org.yuzu.yuzu_emu.utils.NativeConfig |
| 23 | 26 | ||
| 24 | class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) { | 27 | class SettingsFragmentPresenter( |
| 25 | private var menuTag: String? = null | 28 | private val settingsViewModel: SettingsViewModel, |
| 26 | private lateinit var gameId: String | 29 | private val adapter: SettingsAdapter, |
| 27 | private var settingsList: ArrayList<SettingsItem>? = null | 30 | private var menuTag: String, |
| 31 | private var gameId: String | ||
| 32 | ) { | ||
| 33 | private var settingsList = ArrayList<SettingsItem>() | ||
| 28 | 34 | ||
| 29 | private val settingsActivity get() = fragmentView.activityView as SettingsActivity | 35 | private val preferences: SharedPreferences |
| 30 | private val settings get() = fragmentView.activityView!!.settings | 36 | get() = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) |
| 31 | 37 | ||
| 32 | private lateinit var preferences: SharedPreferences | 38 | private val context: Context get() = YuzuApplication.appContext |
| 33 | 39 | ||
| 34 | fun onCreate(menuTag: String, gameId: String) { | 40 | // Extension for populating settings list based on paired settings |
| 35 | this.gameId = gameId | 41 | fun ArrayList<SettingsItem>.add(key: String) { |
| 36 | this.menuTag = menuTag | 42 | val item = SettingsItem.settingsItems[key]!! |
| 43 | val pairedSettingKey = item.setting.pairedSettingKey | ||
| 44 | if (pairedSettingKey.isNotEmpty()) { | ||
| 45 | val pairedSettingValue = NativeConfig.getBoolean(pairedSettingKey, false) | ||
| 46 | if (!pairedSettingValue) return | ||
| 47 | } | ||
| 48 | add(item) | ||
| 37 | } | 49 | } |
| 38 | 50 | ||
| 39 | fun onViewCreated() { | 51 | fun onViewCreated() { |
| 40 | preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) | ||
| 41 | loadSettingsList() | 52 | loadSettingsList() |
| 42 | } | 53 | } |
| 43 | 54 | ||
| 44 | fun putSetting(setting: AbstractSetting) { | ||
| 45 | if (setting.section == null || setting.key == null) { | ||
| 46 | return | ||
| 47 | } | ||
| 48 | |||
| 49 | val section = settings.getSection(setting.section!!)!! | ||
| 50 | if (section.getSetting(setting.key!!) == null) { | ||
| 51 | section.putSetting(setting) | ||
| 52 | } | ||
| 53 | } | ||
| 54 | |||
| 55 | fun loadSettingsList() { | 55 | fun loadSettingsList() { |
| 56 | if (!TextUtils.isEmpty(gameId)) { | 56 | if (!TextUtils.isEmpty(gameId)) { |
| 57 | settingsActivity.setToolbarTitle("Game Settings: $gameId") | 57 | settingsViewModel.setToolbarTitle( |
| 58 | context.getString( | ||
| 59 | R.string.advanced_settings_game, | ||
| 60 | gameId | ||
| 61 | ) | ||
| 62 | ) | ||
| 58 | } | 63 | } |
| 64 | |||
| 59 | val sl = ArrayList<SettingsItem>() | 65 | val sl = ArrayList<SettingsItem>() |
| 60 | if (menuTag == null) { | ||
| 61 | return | ||
| 62 | } | ||
| 63 | when (menuTag) { | 66 | when (menuTag) { |
| 64 | SettingsFile.FILE_NAME_CONFIG -> addConfigSettings(sl) | 67 | SettingsFile.FILE_NAME_CONFIG -> addConfigSettings(sl) |
| 65 | Settings.SECTION_GENERAL -> addGeneralSettings(sl) | 68 | Settings.SECTION_GENERAL -> addGeneralSettings(sl) |
| @@ -69,335 +72,104 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) | |||
| 69 | Settings.SECTION_THEME -> addThemeSettings(sl) | 72 | Settings.SECTION_THEME -> addThemeSettings(sl) |
| 70 | Settings.SECTION_DEBUG -> addDebugSettings(sl) | 73 | Settings.SECTION_DEBUG -> addDebugSettings(sl) |
| 71 | else -> { | 74 | else -> { |
| 72 | fragmentView.showToastMessage("Unimplemented menu", false) | 75 | val context = YuzuApplication.appContext |
| 76 | Toast.makeText( | ||
| 77 | context, | ||
| 78 | context.getString(R.string.unimplemented_menu), | ||
| 79 | Toast.LENGTH_SHORT | ||
| 80 | ).show() | ||
| 73 | return | 81 | return |
| 74 | } | 82 | } |
| 75 | } | 83 | } |
| 76 | settingsList = sl | 84 | settingsList = sl |
| 77 | fragmentView.showSettingsList(settingsList!!) | 85 | adapter.submitList(settingsList) |
| 78 | } | 86 | } |
| 79 | 87 | ||
| 80 | private fun addConfigSettings(sl: ArrayList<SettingsItem>) { | 88 | private fun addConfigSettings(sl: ArrayList<SettingsItem>) { |
| 81 | settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.advanced_settings)) | 89 | settingsViewModel.setToolbarTitle(context.getString(R.string.advanced_settings)) |
| 82 | sl.apply { | 90 | sl.apply { |
| 83 | add( | 91 | add(SubmenuSetting(R.string.preferences_general, 0, Settings.SECTION_GENERAL)) |
| 84 | SubmenuSetting( | 92 | add(SubmenuSetting(R.string.preferences_system, 0, Settings.SECTION_SYSTEM)) |
| 85 | R.string.preferences_general, | 93 | add(SubmenuSetting(R.string.preferences_graphics, 0, Settings.SECTION_RENDERER)) |
| 86 | 0, | 94 | add(SubmenuSetting(R.string.preferences_audio, 0, Settings.SECTION_AUDIO)) |
| 87 | Settings.SECTION_GENERAL | 95 | add(SubmenuSetting(R.string.preferences_debug, 0, Settings.SECTION_DEBUG)) |
| 88 | ) | 96 | add( |
| 89 | ) | 97 | RunnableSetting(R.string.reset_to_default, 0, false) { |
| 90 | add( | 98 | settingsViewModel.setShouldShowResetSettingsDialog(true) |
| 91 | SubmenuSetting( | ||
| 92 | R.string.preferences_system, | ||
| 93 | 0, | ||
| 94 | Settings.SECTION_SYSTEM | ||
| 95 | ) | ||
| 96 | ) | ||
| 97 | add( | ||
| 98 | SubmenuSetting( | ||
| 99 | R.string.preferences_graphics, | ||
| 100 | 0, | ||
| 101 | Settings.SECTION_RENDERER | ||
| 102 | ) | ||
| 103 | ) | ||
| 104 | add( | ||
| 105 | SubmenuSetting( | ||
| 106 | R.string.preferences_audio, | ||
| 107 | 0, | ||
| 108 | Settings.SECTION_AUDIO | ||
| 109 | ) | ||
| 110 | ) | ||
| 111 | add( | ||
| 112 | SubmenuSetting( | ||
| 113 | R.string.preferences_debug, | ||
| 114 | 0, | ||
| 115 | Settings.SECTION_DEBUG | ||
| 116 | ) | ||
| 117 | ) | ||
| 118 | add( | ||
| 119 | RunnableSetting( | ||
| 120 | R.string.reset_to_default, | ||
| 121 | 0, | ||
| 122 | false | ||
| 123 | ) { | ||
| 124 | ResetSettingsDialogFragment().show( | ||
| 125 | settingsActivity.supportFragmentManager, | ||
| 126 | ResetSettingsDialogFragment.TAG | ||
| 127 | ) | ||
| 128 | } | 99 | } |
| 129 | ) | 100 | ) |
| 130 | } | 101 | } |
| 131 | } | 102 | } |
| 132 | 103 | ||
| 133 | private fun addGeneralSettings(sl: ArrayList<SettingsItem>) { | 104 | private fun addGeneralSettings(sl: ArrayList<SettingsItem>) { |
| 134 | settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_general)) | 105 | settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_general)) |
| 135 | sl.apply { | 106 | sl.apply { |
| 136 | add( | 107 | add(BooleanSetting.RENDERER_USE_SPEED_LIMIT.key) |
| 137 | SwitchSetting( | 108 | add(ShortSetting.RENDERER_SPEED_LIMIT.key) |
| 138 | IntSetting.RENDERER_USE_SPEED_LIMIT, | 109 | add(IntSetting.CPU_ACCURACY.key) |
| 139 | R.string.frame_limit_enable, | 110 | add(BooleanSetting.PICTURE_IN_PICTURE.key) |
| 140 | R.string.frame_limit_enable_description, | ||
| 141 | IntSetting.RENDERER_USE_SPEED_LIMIT.key, | ||
| 142 | IntSetting.RENDERER_USE_SPEED_LIMIT.defaultValue | ||
| 143 | ) | ||
| 144 | ) | ||
| 145 | add( | ||
| 146 | SliderSetting( | ||
| 147 | IntSetting.RENDERER_SPEED_LIMIT, | ||
| 148 | R.string.frame_limit_slider, | ||
| 149 | R.string.frame_limit_slider_description, | ||
| 150 | 1, | ||
| 151 | 200, | ||
| 152 | "%", | ||
| 153 | IntSetting.RENDERER_SPEED_LIMIT.key, | ||
| 154 | IntSetting.RENDERER_SPEED_LIMIT.defaultValue | ||
| 155 | ) | ||
| 156 | ) | ||
| 157 | add( | ||
| 158 | SingleChoiceSetting( | ||
| 159 | IntSetting.CPU_ACCURACY, | ||
| 160 | R.string.cpu_accuracy, | ||
| 161 | 0, | ||
| 162 | R.array.cpuAccuracyNames, | ||
| 163 | R.array.cpuAccuracyValues, | ||
| 164 | IntSetting.CPU_ACCURACY.key, | ||
| 165 | IntSetting.CPU_ACCURACY.defaultValue | ||
| 166 | ) | ||
| 167 | ) | ||
| 168 | add( | ||
| 169 | SwitchSetting( | ||
| 170 | BooleanSetting.PICTURE_IN_PICTURE, | ||
| 171 | R.string.picture_in_picture, | ||
| 172 | R.string.picture_in_picture_description, | ||
| 173 | BooleanSetting.PICTURE_IN_PICTURE.key, | ||
| 174 | BooleanSetting.PICTURE_IN_PICTURE.defaultValue | ||
| 175 | ) | ||
| 176 | ) | ||
| 177 | } | 111 | } |
| 178 | } | 112 | } |
| 179 | 113 | ||
| 180 | private fun addSystemSettings(sl: ArrayList<SettingsItem>) { | 114 | private fun addSystemSettings(sl: ArrayList<SettingsItem>) { |
| 181 | settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_system)) | 115 | settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_system)) |
| 182 | sl.apply { | 116 | sl.apply { |
| 183 | add( | 117 | add(BooleanSetting.USE_DOCKED_MODE.key) |
| 184 | SwitchSetting( | 118 | add(IntSetting.REGION_INDEX.key) |
| 185 | IntSetting.USE_DOCKED_MODE, | 119 | add(IntSetting.LANGUAGE_INDEX.key) |
| 186 | R.string.use_docked_mode, | 120 | add(BooleanSetting.USE_CUSTOM_RTC.key) |
| 187 | R.string.use_docked_mode_description, | 121 | add(LongSetting.CUSTOM_RTC.key) |
| 188 | IntSetting.USE_DOCKED_MODE.key, | ||
| 189 | IntSetting.USE_DOCKED_MODE.defaultValue | ||
| 190 | ) | ||
| 191 | ) | ||
| 192 | add( | ||
| 193 | SingleChoiceSetting( | ||
| 194 | IntSetting.REGION_INDEX, | ||
| 195 | R.string.emulated_region, | ||
| 196 | 0, | ||
| 197 | R.array.regionNames, | ||
| 198 | R.array.regionValues, | ||
| 199 | IntSetting.REGION_INDEX.key, | ||
| 200 | IntSetting.REGION_INDEX.defaultValue | ||
| 201 | ) | ||
| 202 | ) | ||
| 203 | add( | ||
| 204 | SingleChoiceSetting( | ||
| 205 | IntSetting.LANGUAGE_INDEX, | ||
| 206 | R.string.emulated_language, | ||
| 207 | 0, | ||
| 208 | R.array.languageNames, | ||
| 209 | R.array.languageValues, | ||
| 210 | IntSetting.LANGUAGE_INDEX.key, | ||
| 211 | IntSetting.LANGUAGE_INDEX.defaultValue | ||
| 212 | ) | ||
| 213 | ) | ||
| 214 | add( | ||
| 215 | SwitchSetting( | ||
| 216 | BooleanSetting.USE_CUSTOM_RTC, | ||
| 217 | R.string.use_custom_rtc, | ||
| 218 | R.string.use_custom_rtc_description, | ||
| 219 | BooleanSetting.USE_CUSTOM_RTC.key, | ||
| 220 | BooleanSetting.USE_CUSTOM_RTC.defaultValue | ||
| 221 | ) | ||
| 222 | ) | ||
| 223 | add( | ||
| 224 | DateTimeSetting( | ||
| 225 | StringSetting.CUSTOM_RTC, | ||
| 226 | R.string.set_custom_rtc, | ||
| 227 | 0, | ||
| 228 | StringSetting.CUSTOM_RTC.key, | ||
| 229 | StringSetting.CUSTOM_RTC.defaultValue | ||
| 230 | ) | ||
| 231 | ) | ||
| 232 | } | 122 | } |
| 233 | } | 123 | } |
| 234 | 124 | ||
| 235 | private fun addGraphicsSettings(sl: ArrayList<SettingsItem>) { | 125 | private fun addGraphicsSettings(sl: ArrayList<SettingsItem>) { |
| 236 | settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_graphics)) | 126 | settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_graphics)) |
| 237 | sl.apply { | 127 | sl.apply { |
| 238 | add( | 128 | add(IntSetting.RENDERER_ACCURACY.key) |
| 239 | SingleChoiceSetting( | 129 | add(IntSetting.RENDERER_RESOLUTION.key) |
| 240 | IntSetting.RENDERER_ACCURACY, | 130 | add(IntSetting.RENDERER_VSYNC.key) |
| 241 | R.string.renderer_accuracy, | 131 | add(IntSetting.RENDERER_SCALING_FILTER.key) |
| 242 | 0, | 132 | add(IntSetting.RENDERER_ANTI_ALIASING.key) |
| 243 | R.array.rendererAccuracyNames, | 133 | add(IntSetting.RENDERER_SCREEN_LAYOUT.key) |
| 244 | R.array.rendererAccuracyValues, | 134 | add(IntSetting.RENDERER_ASPECT_RATIO.key) |
| 245 | IntSetting.RENDERER_ACCURACY.key, | 135 | add(BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE.key) |
| 246 | IntSetting.RENDERER_ACCURACY.defaultValue | 136 | add(BooleanSetting.RENDERER_FORCE_MAX_CLOCK.key) |
| 247 | ) | 137 | add(BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS.key) |
| 248 | ) | 138 | add(BooleanSetting.RENDERER_REACTIVE_FLUSHING.key) |
| 249 | add( | ||
| 250 | SingleChoiceSetting( | ||
| 251 | IntSetting.RENDERER_RESOLUTION, | ||
| 252 | R.string.renderer_resolution, | ||
| 253 | 0, | ||
| 254 | R.array.rendererResolutionNames, | ||
| 255 | R.array.rendererResolutionValues, | ||
| 256 | IntSetting.RENDERER_RESOLUTION.key, | ||
| 257 | IntSetting.RENDERER_RESOLUTION.defaultValue | ||
| 258 | ) | ||
| 259 | ) | ||
| 260 | add( | ||
| 261 | SingleChoiceSetting( | ||
| 262 | IntSetting.RENDERER_VSYNC, | ||
| 263 | R.string.renderer_vsync, | ||
| 264 | 0, | ||
| 265 | R.array.rendererVSyncNames, | ||
| 266 | R.array.rendererVSyncValues, | ||
| 267 | IntSetting.RENDERER_VSYNC.key, | ||
| 268 | IntSetting.RENDERER_VSYNC.defaultValue | ||
| 269 | ) | ||
| 270 | ) | ||
| 271 | add( | ||
| 272 | SingleChoiceSetting( | ||
| 273 | IntSetting.RENDERER_SCALING_FILTER, | ||
| 274 | R.string.renderer_scaling_filter, | ||
| 275 | 0, | ||
| 276 | R.array.rendererScalingFilterNames, | ||
| 277 | R.array.rendererScalingFilterValues, | ||
| 278 | IntSetting.RENDERER_SCALING_FILTER.key, | ||
| 279 | IntSetting.RENDERER_SCALING_FILTER.defaultValue | ||
| 280 | ) | ||
| 281 | ) | ||
| 282 | add( | ||
| 283 | SingleChoiceSetting( | ||
| 284 | IntSetting.RENDERER_ANTI_ALIASING, | ||
| 285 | R.string.renderer_anti_aliasing, | ||
| 286 | 0, | ||
| 287 | R.array.rendererAntiAliasingNames, | ||
| 288 | R.array.rendererAntiAliasingValues, | ||
| 289 | IntSetting.RENDERER_ANTI_ALIASING.key, | ||
| 290 | IntSetting.RENDERER_ANTI_ALIASING.defaultValue | ||
| 291 | ) | ||
| 292 | ) | ||
| 293 | add( | ||
| 294 | SingleChoiceSetting( | ||
| 295 | IntSetting.RENDERER_SCREEN_LAYOUT, | ||
| 296 | R.string.renderer_screen_layout, | ||
| 297 | 0, | ||
| 298 | R.array.rendererScreenLayoutNames, | ||
| 299 | R.array.rendererScreenLayoutValues, | ||
| 300 | IntSetting.RENDERER_SCREEN_LAYOUT.key, | ||
| 301 | IntSetting.RENDERER_SCREEN_LAYOUT.defaultValue | ||
| 302 | ) | ||
| 303 | ) | ||
| 304 | add( | ||
| 305 | SingleChoiceSetting( | ||
| 306 | IntSetting.RENDERER_ASPECT_RATIO, | ||
| 307 | R.string.renderer_aspect_ratio, | ||
| 308 | 0, | ||
| 309 | R.array.rendererAspectRatioNames, | ||
| 310 | R.array.rendererAspectRatioValues, | ||
| 311 | IntSetting.RENDERER_ASPECT_RATIO.key, | ||
| 312 | IntSetting.RENDERER_ASPECT_RATIO.defaultValue | ||
| 313 | ) | ||
| 314 | ) | ||
| 315 | add( | ||
| 316 | SwitchSetting( | ||
| 317 | IntSetting.RENDERER_USE_DISK_SHADER_CACHE, | ||
| 318 | R.string.use_disk_shader_cache, | ||
| 319 | R.string.use_disk_shader_cache_description, | ||
| 320 | IntSetting.RENDERER_USE_DISK_SHADER_CACHE.key, | ||
| 321 | IntSetting.RENDERER_USE_DISK_SHADER_CACHE.defaultValue | ||
| 322 | ) | ||
| 323 | ) | ||
| 324 | add( | ||
| 325 | SwitchSetting( | ||
| 326 | IntSetting.RENDERER_FORCE_MAX_CLOCK, | ||
| 327 | R.string.renderer_force_max_clock, | ||
| 328 | R.string.renderer_force_max_clock_description, | ||
| 329 | IntSetting.RENDERER_FORCE_MAX_CLOCK.key, | ||
| 330 | IntSetting.RENDERER_FORCE_MAX_CLOCK.defaultValue | ||
| 331 | ) | ||
| 332 | ) | ||
| 333 | add( | ||
| 334 | SwitchSetting( | ||
| 335 | IntSetting.RENDERER_ASYNCHRONOUS_SHADERS, | ||
| 336 | R.string.renderer_asynchronous_shaders, | ||
| 337 | R.string.renderer_asynchronous_shaders_description, | ||
| 338 | IntSetting.RENDERER_ASYNCHRONOUS_SHADERS.key, | ||
| 339 | IntSetting.RENDERER_ASYNCHRONOUS_SHADERS.defaultValue | ||
| 340 | ) | ||
| 341 | ) | ||
| 342 | add( | ||
| 343 | SwitchSetting( | ||
| 344 | IntSetting.RENDERER_REACTIVE_FLUSHING, | ||
| 345 | R.string.renderer_reactive_flushing, | ||
| 346 | R.string.renderer_reactive_flushing_description, | ||
| 347 | IntSetting.RENDERER_REACTIVE_FLUSHING.key, | ||
| 348 | IntSetting.RENDERER_REACTIVE_FLUSHING.defaultValue | ||
| 349 | ) | ||
| 350 | ) | ||
| 351 | } | 139 | } |
| 352 | } | 140 | } |
| 353 | 141 | ||
| 354 | private fun addAudioSettings(sl: ArrayList<SettingsItem>) { | 142 | private fun addAudioSettings(sl: ArrayList<SettingsItem>) { |
| 355 | settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_audio)) | 143 | settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_audio)) |
| 356 | sl.apply { | 144 | sl.apply { |
| 357 | add( | 145 | add(IntSetting.AUDIO_OUTPUT_ENGINE.key) |
| 358 | StringSingleChoiceSetting( | 146 | add(ByteSetting.AUDIO_VOLUME.key) |
| 359 | StringSetting.AUDIO_OUTPUT_ENGINE, | ||
| 360 | R.string.audio_output_engine, | ||
| 361 | 0, | ||
| 362 | settingsActivity.resources.getStringArray(R.array.outputEngineEntries), | ||
| 363 | settingsActivity.resources.getStringArray(R.array.outputEngineValues), | ||
| 364 | StringSetting.AUDIO_OUTPUT_ENGINE.key, | ||
| 365 | StringSetting.AUDIO_OUTPUT_ENGINE.defaultValue | ||
| 366 | ) | ||
| 367 | ) | ||
| 368 | add( | ||
| 369 | SliderSetting( | ||
| 370 | IntSetting.AUDIO_VOLUME, | ||
| 371 | R.string.audio_volume, | ||
| 372 | R.string.audio_volume_description, | ||
| 373 | 0, | ||
| 374 | 100, | ||
| 375 | "%", | ||
| 376 | IntSetting.AUDIO_VOLUME.key, | ||
| 377 | IntSetting.AUDIO_VOLUME.defaultValue | ||
| 378 | ) | ||
| 379 | ) | ||
| 380 | } | 147 | } |
| 381 | } | 148 | } |
| 382 | 149 | ||
| 383 | private fun addThemeSettings(sl: ArrayList<SettingsItem>) { | 150 | private fun addThemeSettings(sl: ArrayList<SettingsItem>) { |
| 384 | settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_theme)) | 151 | settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_theme)) |
| 385 | sl.apply { | 152 | sl.apply { |
| 386 | val theme: AbstractIntSetting = object : AbstractIntSetting { | 153 | val theme: AbstractIntSetting = object : AbstractIntSetting { |
| 387 | override var int: Int | 154 | override val int: Int |
| 388 | get() = preferences.getInt(Settings.PREF_THEME, 0) | 155 | get() = preferences.getInt(Settings.PREF_THEME, 0) |
| 389 | set(value) { | 156 | |
| 390 | preferences.edit() | 157 | override fun setInt(value: Int) { |
| 391 | .putInt(Settings.PREF_THEME, value) | 158 | preferences.edit() |
| 392 | .apply() | 159 | .putInt(Settings.PREF_THEME, value) |
| 393 | settingsActivity.recreate() | 160 | .apply() |
| 394 | } | 161 | settingsViewModel.setShouldRecreate(true) |
| 395 | override val key: String? = null | 162 | } |
| 396 | override val section: String? = null | 163 | |
| 397 | override val isRuntimeEditable: Boolean = false | 164 | override val key: String = Settings.PREF_THEME |
| 398 | override val valueAsString: String | 165 | override val category = Settings.Category.UiGeneral |
| 399 | get() = preferences.getInt(Settings.PREF_THEME, 0).toString() | 166 | override val isRuntimeModifiable: Boolean = false |
| 400 | override val defaultValue: Any = 0 | 167 | override val defaultValue: Int = 0 |
| 168 | override fun reset() { | ||
| 169 | preferences.edit() | ||
| 170 | .putInt(Settings.PREF_THEME, defaultValue) | ||
| 171 | .apply() | ||
| 172 | } | ||
| 401 | } | 173 | } |
| 402 | 174 | ||
| 403 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { | 175 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { |
| @@ -423,20 +195,26 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) | |||
| 423 | } | 195 | } |
| 424 | 196 | ||
| 425 | val themeMode: AbstractIntSetting = object : AbstractIntSetting { | 197 | val themeMode: AbstractIntSetting = object : AbstractIntSetting { |
| 426 | override var int: Int | 198 | override val int: Int |
| 427 | get() = preferences.getInt(Settings.PREF_THEME_MODE, -1) | 199 | get() = preferences.getInt(Settings.PREF_THEME_MODE, -1) |
| 428 | set(value) { | 200 | |
| 429 | preferences.edit() | 201 | override fun setInt(value: Int) { |
| 430 | .putInt(Settings.PREF_THEME_MODE, value) | 202 | preferences.edit() |
| 431 | .apply() | 203 | .putInt(Settings.PREF_THEME_MODE, value) |
| 432 | ThemeHelper.setThemeMode(settingsActivity) | 204 | .apply() |
| 433 | } | 205 | settingsViewModel.setShouldRecreate(true) |
| 434 | override val key: String? = null | 206 | } |
| 435 | override val section: String? = null | 207 | |
| 436 | override val isRuntimeEditable: Boolean = false | 208 | override val key: String = Settings.PREF_THEME_MODE |
| 437 | override val valueAsString: String | 209 | override val category = Settings.Category.UiGeneral |
| 438 | get() = preferences.getInt(Settings.PREF_THEME_MODE, -1).toString() | 210 | override val isRuntimeModifiable: Boolean = false |
| 439 | override val defaultValue: Any = -1 | 211 | override val defaultValue: Int = -1 |
| 212 | override fun reset() { | ||
| 213 | preferences.edit() | ||
| 214 | .putInt(Settings.PREF_BLACK_BACKGROUNDS, defaultValue) | ||
| 215 | .apply() | ||
| 216 | settingsViewModel.setShouldRecreate(true) | ||
| 217 | } | ||
| 440 | } | 218 | } |
| 441 | 219 | ||
| 442 | add( | 220 | add( |
| @@ -450,21 +228,26 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) | |||
| 450 | ) | 228 | ) |
| 451 | 229 | ||
| 452 | val blackBackgrounds: AbstractBooleanSetting = object : AbstractBooleanSetting { | 230 | val blackBackgrounds: AbstractBooleanSetting = object : AbstractBooleanSetting { |
| 453 | override var boolean: Boolean | 231 | override val boolean: Boolean |
| 454 | get() = preferences.getBoolean(Settings.PREF_BLACK_BACKGROUNDS, false) | ||
| 455 | set(value) { | ||
| 456 | preferences.edit() | ||
| 457 | .putBoolean(Settings.PREF_BLACK_BACKGROUNDS, value) | ||
| 458 | .apply() | ||
| 459 | settingsActivity.recreate() | ||
| 460 | } | ||
| 461 | override val key: String? = null | ||
| 462 | override val section: String? = null | ||
| 463 | override val isRuntimeEditable: Boolean = false | ||
| 464 | override val valueAsString: String | ||
| 465 | get() = preferences.getBoolean(Settings.PREF_BLACK_BACKGROUNDS, false) | 232 | get() = preferences.getBoolean(Settings.PREF_BLACK_BACKGROUNDS, false) |
| 466 | .toString() | 233 | |
| 467 | override val defaultValue: Any = false | 234 | override fun setBoolean(value: Boolean) { |
| 235 | preferences.edit() | ||
| 236 | .putBoolean(Settings.PREF_BLACK_BACKGROUNDS, value) | ||
| 237 | .apply() | ||
| 238 | settingsViewModel.setShouldRecreate(true) | ||
| 239 | } | ||
| 240 | |||
| 241 | override val key: String = Settings.PREF_BLACK_BACKGROUNDS | ||
| 242 | override val category = Settings.Category.UiGeneral | ||
| 243 | override val isRuntimeModifiable: Boolean = false | ||
| 244 | override val defaultValue: Boolean = false | ||
| 245 | override fun reset() { | ||
| 246 | preferences.edit() | ||
| 247 | .putBoolean(Settings.PREF_BLACK_BACKGROUNDS, defaultValue) | ||
| 248 | .apply() | ||
| 249 | settingsViewModel.setShouldRecreate(true) | ||
| 250 | } | ||
| 468 | } | 251 | } |
| 469 | 252 | ||
| 470 | add( | 253 | add( |
| @@ -478,62 +261,15 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) | |||
| 478 | } | 261 | } |
| 479 | 262 | ||
| 480 | private fun addDebugSettings(sl: ArrayList<SettingsItem>) { | 263 | private fun addDebugSettings(sl: ArrayList<SettingsItem>) { |
| 481 | settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_debug)) | 264 | settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_debug)) |
| 482 | sl.apply { | 265 | sl.apply { |
| 483 | add(HeaderSetting(R.string.gpu)) | 266 | add(HeaderSetting(R.string.gpu)) |
| 484 | add( | 267 | add(IntSetting.RENDERER_BACKEND.key) |
| 485 | SingleChoiceSetting( | 268 | add(BooleanSetting.RENDERER_DEBUG.key) |
| 486 | IntSetting.RENDERER_BACKEND, | ||
| 487 | R.string.renderer_api, | ||
| 488 | 0, | ||
| 489 | R.array.rendererApiNames, | ||
| 490 | R.array.rendererApiValues, | ||
| 491 | IntSetting.RENDERER_BACKEND.key, | ||
| 492 | IntSetting.RENDERER_BACKEND.defaultValue | ||
| 493 | ) | ||
| 494 | ) | ||
| 495 | add( | ||
| 496 | SwitchSetting( | ||
| 497 | IntSetting.RENDERER_DEBUG, | ||
| 498 | R.string.renderer_debug, | ||
| 499 | R.string.renderer_debug_description, | ||
| 500 | IntSetting.RENDERER_DEBUG.key, | ||
| 501 | IntSetting.RENDERER_DEBUG.defaultValue | ||
| 502 | ) | ||
| 503 | ) | ||
| 504 | 269 | ||
| 505 | add(HeaderSetting(R.string.cpu)) | 270 | add(HeaderSetting(R.string.cpu)) |
| 506 | add( | 271 | add(BooleanSetting.CPU_DEBUG_MODE.key) |
| 507 | SwitchSetting( | 272 | add(SettingsItem.FASTMEM_COMBINED) |
| 508 | BooleanSetting.CPU_DEBUG_MODE, | ||
| 509 | R.string.cpu_debug_mode, | ||
| 510 | R.string.cpu_debug_mode_description, | ||
| 511 | BooleanSetting.CPU_DEBUG_MODE.key, | ||
| 512 | BooleanSetting.CPU_DEBUG_MODE.defaultValue | ||
| 513 | ) | ||
| 514 | ) | ||
| 515 | |||
| 516 | val fastmem = object : AbstractBooleanSetting { | ||
| 517 | override var boolean: Boolean | ||
| 518 | get() = | ||
| 519 | BooleanSetting.FASTMEM.boolean && BooleanSetting.FASTMEM_EXCLUSIVES.boolean | ||
| 520 | set(value) { | ||
| 521 | BooleanSetting.FASTMEM.boolean = value | ||
| 522 | BooleanSetting.FASTMEM_EXCLUSIVES.boolean = value | ||
| 523 | } | ||
| 524 | override val key: String? = null | ||
| 525 | override val section: String = Settings.SECTION_CPU | ||
| 526 | override val isRuntimeEditable: Boolean = false | ||
| 527 | override val valueAsString: String = "" | ||
| 528 | override val defaultValue: Any = true | ||
| 529 | } | ||
| 530 | add( | ||
| 531 | SwitchSetting( | ||
| 532 | fastmem, | ||
| 533 | R.string.fastmem, | ||
| 534 | 0 | ||
| 535 | ) | ||
| 536 | ) | ||
| 537 | } | 273 | } |
| 538 | } | 274 | } |
| 539 | } | 275 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentView.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentView.kt deleted file mode 100644 index 1ebe35eaa..000000000 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentView.kt +++ /dev/null | |||
| @@ -1,58 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | package org.yuzu.yuzu_emu.features.settings.ui | ||
| 5 | |||
| 6 | import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting | ||
| 7 | import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem | ||
| 8 | |||
| 9 | /** | ||
| 10 | * Abstraction for a screen showing a list of settings. Instances of | ||
| 11 | * this type of view will each display a layer of the setting hierarchy. | ||
| 12 | */ | ||
| 13 | interface SettingsFragmentView { | ||
| 14 | /** | ||
| 15 | * Pass an ArrayList to the View so that it can be displayed on screen. | ||
| 16 | * | ||
| 17 | * @param settingsList The result of converting the HashMap to an ArrayList | ||
| 18 | */ | ||
| 19 | fun showSettingsList(settingsList: ArrayList<SettingsItem>) | ||
| 20 | |||
| 21 | /** | ||
| 22 | * Instructs the Fragment to load the settings screen. | ||
| 23 | */ | ||
| 24 | fun loadSettingsList() | ||
| 25 | |||
| 26 | /** | ||
| 27 | * @return The Fragment's containing activity. | ||
| 28 | */ | ||
| 29 | val activityView: SettingsActivityView? | ||
| 30 | |||
| 31 | /** | ||
| 32 | * Tell the Fragment to tell the containing Activity to show a new | ||
| 33 | * Fragment containing a submenu of settings. | ||
| 34 | * | ||
| 35 | * @param menuKey Identifier for the settings group that should be shown. | ||
| 36 | */ | ||
| 37 | fun loadSubMenu(menuKey: String) | ||
| 38 | |||
| 39 | /** | ||
| 40 | * Tell the Fragment to tell the containing activity to display a toast message. | ||
| 41 | * | ||
| 42 | * @param message Text to be shown in the Toast | ||
| 43 | * @param is_long Whether this should be a long Toast or short one. | ||
| 44 | */ | ||
| 45 | fun showToastMessage(message: String?, is_long: Boolean) | ||
| 46 | |||
| 47 | /** | ||
| 48 | * Have the fragment add a setting to the HashMap. | ||
| 49 | * | ||
| 50 | * @param setting The (possibly previously missing) new setting. | ||
| 51 | */ | ||
| 52 | fun putSetting(setting: AbstractSetting) | ||
| 53 | |||
| 54 | /** | ||
| 55 | * Have the fragment tell the containing Activity that a setting was modified. | ||
| 56 | */ | ||
| 57 | fun onSettingChanged() | ||
| 58 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt index 79572fc06..525f013f8 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt | |||
| @@ -29,7 +29,7 @@ class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA | |||
| 29 | } | 29 | } |
| 30 | 30 | ||
| 31 | binding.textSettingValue.visibility = View.VISIBLE | 31 | binding.textSettingValue.visibility = View.VISIBLE |
| 32 | val epochTime = setting.value.toLong() | 32 | val epochTime = setting.value |
| 33 | val instant = Instant.ofEpochMilli(epochTime * 1000) | 33 | val instant = Instant.ofEpochMilli(epochTime * 1000) |
| 34 | val zonedTime = ZonedDateTime.ofInstant(instant, ZoneId.of("UTC")) | 34 | val zonedTime = ZonedDateTime.ofInstant(instant, ZoneId.of("UTC")) |
| 35 | val dateFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM) | 35 | val dateFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM) |
| @@ -46,7 +46,7 @@ class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA | |||
| 46 | 46 | ||
| 47 | override fun onLongClick(clicked: View): Boolean { | 47 | override fun onLongClick(clicked: View): Boolean { |
| 48 | if (setting.isEditable) { | 48 | if (setting.isEditable) { |
| 49 | return adapter.onLongClick(setting.setting!!, bindingAdapterPosition) | 49 | return adapter.onLongClick(setting, bindingAdapterPosition) |
| 50 | } | 50 | } |
| 51 | return false | 51 | return false |
| 52 | } | 52 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt index b42d955aa..80d1b22c1 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt | |||
| @@ -35,7 +35,7 @@ class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: Setti | |||
| 35 | } | 35 | } |
| 36 | } | 36 | } |
| 37 | } else if (item is StringSingleChoiceSetting) { | 37 | } else if (item is StringSingleChoiceSetting) { |
| 38 | for (i in item.values!!.indices) { | 38 | for (i in item.values.indices) { |
| 39 | if (item.values[i] == item.selectedValue) { | 39 | if (item.values[i] == item.selectedValue) { |
| 40 | binding.textSettingValue.text = item.choices[i] | 40 | binding.textSettingValue.text = item.choices[i] |
| 41 | break | 41 | break |
| @@ -66,7 +66,7 @@ class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: Setti | |||
| 66 | 66 | ||
| 67 | override fun onLongClick(clicked: View): Boolean { | 67 | override fun onLongClick(clicked: View): Boolean { |
| 68 | if (setting.isEditable) { | 68 | if (setting.isEditable) { |
| 69 | return adapter.onLongClick(setting.setting!!, bindingAdapterPosition) | 69 | return adapter.onLongClick(setting, bindingAdapterPosition) |
| 70 | } | 70 | } |
| 71 | return false | 71 | return false |
| 72 | } | 72 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SliderViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SliderViewHolder.kt index a23b5d109..b83c90100 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SliderViewHolder.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SliderViewHolder.kt | |||
| @@ -41,7 +41,7 @@ class SliderViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAda | |||
| 41 | 41 | ||
| 42 | override fun onLongClick(clicked: View): Boolean { | 42 | override fun onLongClick(clicked: View): Boolean { |
| 43 | if (setting.isEditable) { | 43 | if (setting.isEditable) { |
| 44 | return adapter.onLongClick(setting.setting!!, bindingAdapterPosition) | 44 | return adapter.onLongClick(setting, bindingAdapterPosition) |
| 45 | } | 45 | } |
| 46 | return false | 46 | return false |
| 47 | } | 47 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt index ef34bf5f4..57fdeaa20 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt | |||
| @@ -25,10 +25,12 @@ class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter | |||
| 25 | binding.textSettingDescription.text = "" | 25 | binding.textSettingDescription.text = "" |
| 26 | binding.textSettingDescription.visibility = View.GONE | 26 | binding.textSettingDescription.visibility = View.GONE |
| 27 | } | 27 | } |
| 28 | |||
| 29 | binding.switchWidget.setOnCheckedChangeListener(null) | ||
| 30 | binding.switchWidget.isChecked = setting.checked | ||
| 28 | binding.switchWidget.setOnCheckedChangeListener { _: CompoundButton, _: Boolean -> | 31 | binding.switchWidget.setOnCheckedChangeListener { _: CompoundButton, _: Boolean -> |
| 29 | adapter.onBooleanClick(item, bindingAdapterPosition, binding.switchWidget.isChecked) | 32 | adapter.onBooleanClick(item, binding.switchWidget.isChecked) |
| 30 | } | 33 | } |
| 31 | binding.switchWidget.isChecked = setting.isChecked | ||
| 32 | 34 | ||
| 33 | setStyle(setting.isEditable, binding) | 35 | setStyle(setting.isEditable, binding) |
| 34 | } | 36 | } |
| @@ -41,7 +43,7 @@ class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter | |||
| 41 | 43 | ||
| 42 | override fun onLongClick(clicked: View): Boolean { | 44 | override fun onLongClick(clicked: View): Boolean { |
| 43 | if (setting.isEditable) { | 45 | if (setting.isEditable) { |
| 44 | return adapter.onLongClick(setting.setting!!, bindingAdapterPosition) | 46 | return adapter.onLongClick(setting, bindingAdapterPosition) |
| 45 | } | 47 | } |
| 46 | return false | 48 | return false |
| 47 | } | 49 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.kt index 70a52df5d..2b04d666a 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.kt | |||
| @@ -3,18 +3,15 @@ | |||
| 3 | 3 | ||
| 4 | package org.yuzu.yuzu_emu.features.settings.utils | 4 | package org.yuzu.yuzu_emu.features.settings.utils |
| 5 | 5 | ||
| 6 | import android.widget.Toast | ||
| 6 | import java.io.* | 7 | import java.io.* |
| 7 | import java.util.* | ||
| 8 | import org.ini4j.Wini | 8 | import org.ini4j.Wini |
| 9 | import org.yuzu.yuzu_emu.NativeLibrary | ||
| 10 | import org.yuzu.yuzu_emu.R | 9 | import org.yuzu.yuzu_emu.R |
| 11 | import org.yuzu.yuzu_emu.YuzuApplication | 10 | import org.yuzu.yuzu_emu.YuzuApplication |
| 12 | import org.yuzu.yuzu_emu.features.settings.model.* | 11 | import org.yuzu.yuzu_emu.features.settings.model.* |
| 13 | import org.yuzu.yuzu_emu.features.settings.model.Settings.SettingsSectionMap | ||
| 14 | import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivityView | ||
| 15 | import org.yuzu.yuzu_emu.utils.BiMap | ||
| 16 | import org.yuzu.yuzu_emu.utils.DirectoryInitialization | 12 | import org.yuzu.yuzu_emu.utils.DirectoryInitialization |
| 17 | import org.yuzu.yuzu_emu.utils.Log | 13 | import org.yuzu.yuzu_emu.utils.Log |
| 14 | import org.yuzu.yuzu_emu.utils.NativeConfig | ||
| 18 | 15 | ||
| 19 | /** | 16 | /** |
| 20 | * Contains static methods for interacting with .ini files in which settings are stored. | 17 | * Contains static methods for interacting with .ini files in which settings are stored. |
| @@ -22,243 +19,41 @@ import org.yuzu.yuzu_emu.utils.Log | |||
| 22 | object SettingsFile { | 19 | object SettingsFile { |
| 23 | const val FILE_NAME_CONFIG = "config" | 20 | const val FILE_NAME_CONFIG = "config" |
| 24 | 21 | ||
| 25 | private var sectionsMap = BiMap<String?, String?>() | ||
| 26 | |||
| 27 | /** | ||
| 28 | * Reads a given .ini file from disk and returns it as a HashMap of Settings, themselves | ||
| 29 | * effectively a HashMap of key/value settings. If unsuccessful, outputs an error telling why it | ||
| 30 | * failed. | ||
| 31 | * | ||
| 32 | * @param ini The ini file to load the settings from | ||
| 33 | * @param isCustomGame | ||
| 34 | * @param view The current view. | ||
| 35 | * @return An Observable that emits a HashMap of the file's contents, then completes. | ||
| 36 | */ | ||
| 37 | private fun readFile( | ||
| 38 | ini: File?, | ||
| 39 | isCustomGame: Boolean, | ||
| 40 | view: SettingsActivityView? = null | ||
| 41 | ): HashMap<String, SettingSection?> { | ||
| 42 | val sections: HashMap<String, SettingSection?> = SettingsSectionMap() | ||
| 43 | var reader: BufferedReader? = null | ||
| 44 | try { | ||
| 45 | reader = BufferedReader(FileReader(ini)) | ||
| 46 | var current: SettingSection? = null | ||
| 47 | var line: String? | ||
| 48 | while (reader.readLine().also { line = it } != null) { | ||
| 49 | if (line!!.startsWith("[") && line!!.endsWith("]")) { | ||
| 50 | current = sectionFromLine(line!!, isCustomGame) | ||
| 51 | sections[current.name] = current | ||
| 52 | } else if (current != null) { | ||
| 53 | val setting = settingFromLine(line!!) | ||
| 54 | if (setting != null) { | ||
| 55 | current.putSetting(setting) | ||
| 56 | } | ||
| 57 | } | ||
| 58 | } | ||
| 59 | } catch (e: FileNotFoundException) { | ||
| 60 | Log.error("[SettingsFile] File not found: " + e.message) | ||
| 61 | view?.onSettingsFileNotFound() | ||
| 62 | } catch (e: IOException) { | ||
| 63 | Log.error("[SettingsFile] Error reading from: " + e.message) | ||
| 64 | view?.onSettingsFileNotFound() | ||
| 65 | } finally { | ||
| 66 | if (reader != null) { | ||
| 67 | try { | ||
| 68 | reader.close() | ||
| 69 | } catch (e: IOException) { | ||
| 70 | Log.error("[SettingsFile] Error closing: " + e.message) | ||
| 71 | } | ||
| 72 | } | ||
| 73 | } | ||
| 74 | return sections | ||
| 75 | } | ||
| 76 | |||
| 77 | fun readFile(fileName: String, view: SettingsActivityView?): HashMap<String, SettingSection?> { | ||
| 78 | return readFile(getSettingsFile(fileName), false, view) | ||
| 79 | } | ||
| 80 | |||
| 81 | fun readFile(fileName: String): HashMap<String, SettingSection?> = | ||
| 82 | readFile(getSettingsFile(fileName), false) | ||
| 83 | |||
| 84 | /** | ||
| 85 | * Reads a given .ini file from disk and returns it as a HashMap of SettingSections, themselves | ||
| 86 | * effectively a HashMap of key/value settings. If unsuccessful, outputs an error telling why it | ||
| 87 | * failed. | ||
| 88 | * | ||
| 89 | * @param gameId the id of the game to load it's settings. | ||
| 90 | * @param view The current view. | ||
| 91 | */ | ||
| 92 | fun readCustomGameSettings( | ||
| 93 | gameId: String, | ||
| 94 | view: SettingsActivityView? | ||
| 95 | ): HashMap<String, SettingSection?> { | ||
| 96 | return readFile(getCustomGameSettingsFile(gameId), true, view) | ||
| 97 | } | ||
| 98 | |||
| 99 | /** | 22 | /** |
| 100 | * Saves a Settings HashMap to a given .ini file on disk. If unsuccessful, outputs an error | 23 | * Saves a Settings HashMap to a given .ini file on disk. If unsuccessful, outputs an error |
| 101 | * telling why it failed. | 24 | * telling why it failed. |
| 102 | * | 25 | * |
| 103 | * @param fileName The target filename without a path or extension. | 26 | * @param fileName The target filename without a path or extension. |
| 104 | * @param sections The HashMap containing the Settings we want to serialize. | ||
| 105 | * @param view The current view. | ||
| 106 | */ | 27 | */ |
| 107 | fun saveFile( | 28 | fun saveFile(fileName: String) { |
| 108 | fileName: String, | ||
| 109 | sections: TreeMap<String, SettingSection>, | ||
| 110 | view: SettingsActivityView | ||
| 111 | ) { | ||
| 112 | val ini = getSettingsFile(fileName) | 29 | val ini = getSettingsFile(fileName) |
| 113 | try { | 30 | try { |
| 114 | val writer = Wini(ini) | 31 | val wini = Wini(ini) |
| 115 | val keySet: Set<String> = sections.keys | 32 | for (specificCategory in Settings.Category.values()) { |
| 116 | for (key in keySet) { | 33 | val categoryHeader = NativeConfig.getConfigHeader(specificCategory.ordinal) |
| 117 | val section = sections[key] | 34 | for (setting in Settings.settingsList) { |
| 118 | writeSection(writer, section!!) | 35 | if (setting.key!!.isEmpty()) continue |
| 36 | |||
| 37 | val settingCategoryHeader = | ||
| 38 | NativeConfig.getConfigHeader(setting.category.ordinal) | ||
| 39 | val iniSetting: String? = wini.get(categoryHeader, setting.key) | ||
| 40 | if (iniSetting != null || settingCategoryHeader == categoryHeader) { | ||
| 41 | wini.put(settingCategoryHeader, setting.key, setting.valueAsString) | ||
| 42 | } | ||
| 43 | } | ||
| 119 | } | 44 | } |
| 120 | writer.store() | 45 | wini.store() |
| 121 | } catch (e: IOException) { | 46 | } catch (e: IOException) { |
| 122 | Log.error("[SettingsFile] File not found: " + fileName + ".ini: " + e.message) | 47 | Log.error("[SettingsFile] File not found: " + fileName + ".ini: " + e.message) |
| 123 | view.showToastMessage( | 48 | val context = YuzuApplication.appContext |
| 124 | YuzuApplication.appContext | 49 | Toast.makeText( |
| 125 | .getString(R.string.error_saving, fileName, e.message), | 50 | context, |
| 126 | false | 51 | context.getString(R.string.error_saving, fileName, e.message), |
| 127 | ) | 52 | Toast.LENGTH_SHORT |
| 128 | } | 53 | ).show() |
| 129 | } | ||
| 130 | |||
| 131 | fun saveCustomGameSettings(gameId: String?, sections: HashMap<String, SettingSection?>) { | ||
| 132 | val sortedSections: Set<String> = TreeSet(sections.keys) | ||
| 133 | for (sectionKey in sortedSections) { | ||
| 134 | val section = sections[sectionKey] | ||
| 135 | val settings = section!!.settings | ||
| 136 | val sortedKeySet: Set<String> = TreeSet(settings.keys) | ||
| 137 | for (settingKey in sortedKeySet) { | ||
| 138 | val setting = settings[settingKey] | ||
| 139 | NativeLibrary.setUserSetting( | ||
| 140 | gameId, | ||
| 141 | mapSectionNameFromIni( | ||
| 142 | section.name | ||
| 143 | ), | ||
| 144 | setting!!.key, | ||
| 145 | setting.valueAsString | ||
| 146 | ) | ||
| 147 | } | ||
| 148 | } | ||
| 149 | } | ||
| 150 | |||
| 151 | private fun mapSectionNameFromIni(generalSectionName: String): String? { | ||
| 152 | return if (sectionsMap.getForward(generalSectionName) != null) { | ||
| 153 | sectionsMap.getForward(generalSectionName) | ||
| 154 | } else { | ||
| 155 | generalSectionName | ||
| 156 | } | ||
| 157 | } | ||
| 158 | |||
| 159 | private fun mapSectionNameToIni(generalSectionName: String): String { | ||
| 160 | return if (sectionsMap.getBackward(generalSectionName) != null) { | ||
| 161 | sectionsMap.getBackward(generalSectionName).toString() | ||
| 162 | } else { | ||
| 163 | generalSectionName | ||
| 164 | } | ||
| 165 | } | ||
| 166 | |||
| 167 | fun getSettingsFile(fileName: String): File { | ||
| 168 | return File( | ||
| 169 | DirectoryInitialization.userDirectory + "/config/" + fileName + ".ini" | ||
| 170 | ) | ||
| 171 | } | ||
| 172 | |||
| 173 | private fun getCustomGameSettingsFile(gameId: String): File { | ||
| 174 | return File(DirectoryInitialization.userDirectory + "/GameSettings/" + gameId + ".ini") | ||
| 175 | } | ||
| 176 | |||
| 177 | private fun sectionFromLine(line: String, isCustomGame: Boolean): SettingSection { | ||
| 178 | var sectionName: String = line.substring(1, line.length - 1) | ||
| 179 | if (isCustomGame) { | ||
| 180 | sectionName = mapSectionNameToIni(sectionName) | ||
| 181 | } | 54 | } |
| 182 | return SettingSection(sectionName) | ||
| 183 | } | 55 | } |
| 184 | 56 | ||
| 185 | /** | 57 | fun getSettingsFile(fileName: String): File = |
| 186 | * For a line of text, determines what type of data is being represented, and returns | 58 | File(DirectoryInitialization.userDirectory + "/config/" + fileName + ".ini") |
| 187 | * a Setting object containing this data. | ||
| 188 | * | ||
| 189 | * @param line The line of text being parsed. | ||
| 190 | * @return A typed Setting containing the key/value contained in the line. | ||
| 191 | */ | ||
| 192 | private fun settingFromLine(line: String): AbstractSetting? { | ||
| 193 | val splitLine = line.split("=".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() | ||
| 194 | if (splitLine.size != 2) { | ||
| 195 | return null | ||
| 196 | } | ||
| 197 | val key = splitLine[0].trim { it <= ' ' } | ||
| 198 | val value = splitLine[1].trim { it <= ' ' } | ||
| 199 | if (value.isEmpty()) { | ||
| 200 | return null | ||
| 201 | } | ||
| 202 | |||
| 203 | val booleanSetting = BooleanSetting.from(key) | ||
| 204 | if (booleanSetting != null) { | ||
| 205 | booleanSetting.boolean = value.toBoolean() | ||
| 206 | return booleanSetting | ||
| 207 | } | ||
| 208 | |||
| 209 | val intSetting = IntSetting.from(key) | ||
| 210 | if (intSetting != null) { | ||
| 211 | intSetting.int = value.toInt() | ||
| 212 | return intSetting | ||
| 213 | } | ||
| 214 | |||
| 215 | val floatSetting = FloatSetting.from(key) | ||
| 216 | if (floatSetting != null) { | ||
| 217 | floatSetting.float = value.toFloat() | ||
| 218 | return floatSetting | ||
| 219 | } | ||
| 220 | |||
| 221 | val stringSetting = StringSetting.from(key) | ||
| 222 | if (stringSetting != null) { | ||
| 223 | stringSetting.string = value | ||
| 224 | return stringSetting | ||
| 225 | } | ||
| 226 | |||
| 227 | return null | ||
| 228 | } | ||
| 229 | |||
| 230 | /** | ||
| 231 | * Writes the contents of a Section HashMap to disk. | ||
| 232 | * | ||
| 233 | * @param parser A Wini pointed at a file on disk. | ||
| 234 | * @param section A section containing settings to be written to the file. | ||
| 235 | */ | ||
| 236 | private fun writeSection(parser: Wini, section: SettingSection) { | ||
| 237 | // Write the section header. | ||
| 238 | val header = section.name | ||
| 239 | |||
| 240 | // Write this section's values. | ||
| 241 | val settings = section.settings | ||
| 242 | val keySet: Set<String> = settings.keys | ||
| 243 | for (key in keySet) { | ||
| 244 | val setting = settings[key] | ||
| 245 | parser.put(header, setting!!.key, setting.valueAsString) | ||
| 246 | } | ||
| 247 | |||
| 248 | BooleanSetting.values().forEach { | ||
| 249 | if (!keySet.contains(it.key)) { | ||
| 250 | parser.put(header, it.key, it.valueAsString) | ||
| 251 | } | ||
| 252 | } | ||
| 253 | IntSetting.values().forEach { | ||
| 254 | if (!keySet.contains(it.key)) { | ||
| 255 | parser.put(header, it.key, it.valueAsString) | ||
| 256 | } | ||
| 257 | } | ||
| 258 | StringSetting.values().forEach { | ||
| 259 | if (!keySet.contains(it.key)) { | ||
| 260 | parser.put(header, it.key, it.valueAsString) | ||
| 261 | } | ||
| 262 | } | ||
| 263 | } | ||
| 264 | } | 59 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt index 25b9d4018..944ae652e 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt | |||
| @@ -7,11 +7,11 @@ import android.annotation.SuppressLint | |||
| 7 | import android.app.AlertDialog | 7 | import android.app.AlertDialog |
| 8 | import android.content.Context | 8 | import android.content.Context |
| 9 | import android.content.DialogInterface | 9 | import android.content.DialogInterface |
| 10 | import android.content.Intent | ||
| 11 | import android.content.SharedPreferences | 10 | import android.content.SharedPreferences |
| 12 | import android.content.pm.ActivityInfo | 11 | import android.content.pm.ActivityInfo |
| 13 | import android.content.res.Configuration | 12 | import android.content.res.Configuration |
| 14 | import android.graphics.Color | 13 | import android.graphics.Color |
| 14 | import android.net.Uri | ||
| 15 | import android.os.Bundle | 15 | import android.os.Bundle |
| 16 | import android.os.Handler | 16 | import android.os.Handler |
| 17 | import android.os.Looper | 17 | import android.os.Looper |
| @@ -19,18 +19,18 @@ import android.util.Rational | |||
| 19 | import android.view.* | 19 | import android.view.* |
| 20 | import android.widget.TextView | 20 | import android.widget.TextView |
| 21 | import androidx.activity.OnBackPressedCallback | 21 | import androidx.activity.OnBackPressedCallback |
| 22 | import androidx.activity.result.ActivityResultLauncher | ||
| 23 | import androidx.activity.result.contract.ActivityResultContracts | ||
| 24 | import androidx.appcompat.widget.PopupMenu | 22 | import androidx.appcompat.widget.PopupMenu |
| 25 | import androidx.core.content.res.ResourcesCompat | 23 | import androidx.core.content.res.ResourcesCompat |
| 26 | import androidx.core.graphics.Insets | 24 | import androidx.core.graphics.Insets |
| 27 | import androidx.core.view.ViewCompat | 25 | import androidx.core.view.ViewCompat |
| 28 | import androidx.core.view.WindowInsetsCompat | 26 | import androidx.core.view.WindowInsetsCompat |
| 29 | import androidx.core.view.isVisible | 27 | import androidx.drawerlayout.widget.DrawerLayout |
| 30 | import androidx.fragment.app.Fragment | 28 | import androidx.fragment.app.Fragment |
| 29 | import androidx.fragment.app.activityViewModels | ||
| 31 | import androidx.lifecycle.Lifecycle | 30 | import androidx.lifecycle.Lifecycle |
| 32 | import androidx.lifecycle.lifecycleScope | 31 | import androidx.lifecycle.lifecycleScope |
| 33 | import androidx.lifecycle.repeatOnLifecycle | 32 | import androidx.lifecycle.repeatOnLifecycle |
| 33 | import androidx.navigation.findNavController | ||
| 34 | import androidx.navigation.fragment.navArgs | 34 | import androidx.navigation.fragment.navArgs |
| 35 | import androidx.preference.PreferenceManager | 35 | import androidx.preference.PreferenceManager |
| 36 | import androidx.window.layout.FoldingFeature | 36 | import androidx.window.layout.FoldingFeature |
| @@ -40,6 +40,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder | |||
| 40 | import com.google.android.material.slider.Slider | 40 | import com.google.android.material.slider.Slider |
| 41 | import kotlinx.coroutines.Dispatchers | 41 | import kotlinx.coroutines.Dispatchers |
| 42 | import kotlinx.coroutines.launch | 42 | import kotlinx.coroutines.launch |
| 43 | import org.yuzu.yuzu_emu.HomeNavigationDirections | ||
| 43 | import org.yuzu.yuzu_emu.NativeLibrary | 44 | import org.yuzu.yuzu_emu.NativeLibrary |
| 44 | import org.yuzu.yuzu_emu.R | 45 | import org.yuzu.yuzu_emu.R |
| 45 | import org.yuzu.yuzu_emu.YuzuApplication | 46 | import org.yuzu.yuzu_emu.YuzuApplication |
| @@ -48,8 +49,9 @@ import org.yuzu.yuzu_emu.databinding.DialogOverlayAdjustBinding | |||
| 48 | import org.yuzu.yuzu_emu.databinding.FragmentEmulationBinding | 49 | import org.yuzu.yuzu_emu.databinding.FragmentEmulationBinding |
| 49 | import org.yuzu.yuzu_emu.features.settings.model.IntSetting | 50 | import org.yuzu.yuzu_emu.features.settings.model.IntSetting |
| 50 | import org.yuzu.yuzu_emu.features.settings.model.Settings | 51 | import org.yuzu.yuzu_emu.features.settings.model.Settings |
| 51 | import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity | ||
| 52 | import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile | 52 | import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile |
| 53 | import org.yuzu.yuzu_emu.model.Game | ||
| 54 | import org.yuzu.yuzu_emu.model.EmulationViewModel | ||
| 53 | import org.yuzu.yuzu_emu.overlay.InputOverlay | 55 | import org.yuzu.yuzu_emu.overlay.InputOverlay |
| 54 | import org.yuzu.yuzu_emu.utils.* | 56 | import org.yuzu.yuzu_emu.utils.* |
| 55 | 57 | ||
| @@ -62,11 +64,13 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 62 | private var _binding: FragmentEmulationBinding? = null | 64 | private var _binding: FragmentEmulationBinding? = null |
| 63 | private val binding get() = _binding!! | 65 | private val binding get() = _binding!! |
| 64 | 66 | ||
| 65 | val args by navArgs<EmulationFragmentArgs>() | 67 | private val args by navArgs<EmulationFragmentArgs>() |
| 66 | 68 | ||
| 67 | private var isInFoldableLayout = false | 69 | private lateinit var game: Game |
| 70 | |||
| 71 | private val emulationViewModel: EmulationViewModel by activityViewModels() | ||
| 68 | 72 | ||
| 69 | private lateinit var onReturnFromSettings: ActivityResultLauncher<Intent> | 73 | private var isInFoldableLayout = false |
| 70 | 74 | ||
| 71 | override fun onAttach(context: Context) { | 75 | override fun onAttach(context: Context) { |
| 72 | super.onAttach(context) | 76 | super.onAttach(context) |
| @@ -81,11 +85,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 81 | .collect { updateFoldableLayout(context, it) } | 85 | .collect { updateFoldableLayout(context, it) } |
| 82 | } | 86 | } |
| 83 | } | 87 | } |
| 84 | |||
| 85 | onReturnFromSettings = context.activityResultRegistry.register( | ||
| 86 | "SettingsResult", | ||
| 87 | ActivityResultContracts.StartActivityForResult() | ||
| 88 | ) { updateScreenLayout() } | ||
| 89 | } else { | 88 | } else { |
| 90 | throw IllegalStateException("EmulationFragment must have EmulationActivity parent") | 89 | throw IllegalStateException("EmulationFragment must have EmulationActivity parent") |
| 91 | } | 90 | } |
| @@ -97,10 +96,25 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 97 | override fun onCreate(savedInstanceState: Bundle?) { | 96 | override fun onCreate(savedInstanceState: Bundle?) { |
| 98 | super.onCreate(savedInstanceState) | 97 | super.onCreate(savedInstanceState) |
| 99 | 98 | ||
| 99 | val intentUri: Uri? = requireActivity().intent.data | ||
| 100 | var intentGame: Game? = null | ||
| 101 | if (intentUri != null) { | ||
| 102 | intentGame = if (Game.extensions.contains(FileUtil.getExtension(intentUri))) { | ||
| 103 | GameHelper.getGame(requireActivity().intent.data!!, false) | ||
| 104 | } else { | ||
| 105 | null | ||
| 106 | } | ||
| 107 | } | ||
| 108 | game = if (args.game != null) { | ||
| 109 | args.game!! | ||
| 110 | } else { | ||
| 111 | intentGame ?: error("[EmulationFragment] No bootable game present!") | ||
| 112 | } | ||
| 113 | |||
| 100 | // So this fragment doesn't restart on configuration changes; i.e. rotation. | 114 | // So this fragment doesn't restart on configuration changes; i.e. rotation. |
| 101 | retainInstance = true | 115 | retainInstance = true |
| 102 | preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) | 116 | preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) |
| 103 | emulationState = EmulationState(args.game.path) | 117 | emulationState = EmulationState(game.path) |
| 104 | } | 118 | } |
| 105 | 119 | ||
| 106 | /** | 120 | /** |
| @@ -120,11 +134,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 120 | binding.showFpsText.setTextColor(Color.YELLOW) | 134 | binding.showFpsText.setTextColor(Color.YELLOW) |
| 121 | binding.doneControlConfig.setOnClickListener { stopConfiguringControls() } | 135 | binding.doneControlConfig.setOnClickListener { stopConfiguringControls() } |
| 122 | 136 | ||
| 123 | // Setup overlay. | 137 | binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED) |
| 124 | updateShowFpsOverlay() | ||
| 125 | |||
| 126 | binding.inGameMenu.getHeaderView(0).findViewById<TextView>(R.id.text_game_title).text = | 138 | binding.inGameMenu.getHeaderView(0).findViewById<TextView>(R.id.text_game_title).text = |
| 127 | args.game.title | 139 | game.title |
| 128 | binding.inGameMenu.setNavigationItemSelectedListener { | 140 | binding.inGameMenu.setNavigationItemSelectedListener { |
| 129 | when (it.itemId) { | 141 | when (it.itemId) { |
| 130 | R.id.menu_pause_emulation -> { | 142 | R.id.menu_pause_emulation -> { |
| @@ -149,12 +161,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 149 | } | 161 | } |
| 150 | 162 | ||
| 151 | R.id.menu_settings -> { | 163 | R.id.menu_settings -> { |
| 152 | SettingsActivity.launch( | 164 | val action = HomeNavigationDirections.actionGlobalSettingsActivity( |
| 153 | requireContext(), | 165 | null, |
| 154 | onReturnFromSettings, | 166 | SettingsFile.FILE_NAME_CONFIG |
| 155 | SettingsFile.FILE_NAME_CONFIG, | ||
| 156 | "" | ||
| 157 | ) | 167 | ) |
| 168 | binding.root.findNavController().navigate(action) | ||
| 158 | true | 169 | true |
| 159 | } | 170 | } |
| 160 | 171 | ||
| @@ -165,7 +176,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 165 | 176 | ||
| 166 | R.id.menu_exit -> { | 177 | R.id.menu_exit -> { |
| 167 | emulationState.stop() | 178 | emulationState.stop() |
| 168 | requireActivity().finish() | 179 | emulationViewModel.setIsEmulationStopping(true) |
| 180 | binding.drawerLayout.close() | ||
| 181 | binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED) | ||
| 169 | true | 182 | true |
| 170 | } | 183 | } |
| 171 | 184 | ||
| @@ -179,6 +192,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 179 | requireActivity(), | 192 | requireActivity(), |
| 180 | object : OnBackPressedCallback(true) { | 193 | object : OnBackPressedCallback(true) { |
| 181 | override fun handleOnBackPressed() { | 194 | override fun handleOnBackPressed() { |
| 195 | if (!NativeLibrary.isRunning()) { | ||
| 196 | return | ||
| 197 | } | ||
| 198 | |||
| 182 | if (binding.drawerLayout.isOpen) { | 199 | if (binding.drawerLayout.isOpen) { |
| 183 | binding.drawerLayout.close() | 200 | binding.drawerLayout.close() |
| 184 | } else { | 201 | } else { |
| @@ -195,6 +212,54 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 195 | .collect { updateFoldableLayout(requireActivity() as EmulationActivity, it) } | 212 | .collect { updateFoldableLayout(requireActivity() as EmulationActivity, it) } |
| 196 | } | 213 | } |
| 197 | } | 214 | } |
| 215 | |||
| 216 | GameIconUtils.loadGameIcon(game, binding.loadingImage) | ||
| 217 | binding.loadingTitle.text = game.title | ||
| 218 | binding.loadingTitle.isSelected = true | ||
| 219 | binding.loadingText.isSelected = true | ||
| 220 | |||
| 221 | emulationViewModel.shaderProgress.observe(viewLifecycleOwner) { | ||
| 222 | if (it > 0 && it != emulationViewModel.totalShaders.value!!) { | ||
| 223 | binding.loadingProgressIndicator.isIndeterminate = false | ||
| 224 | |||
| 225 | if (it < binding.loadingProgressIndicator.max) { | ||
| 226 | binding.loadingProgressIndicator.progress = it | ||
| 227 | } | ||
| 228 | } | ||
| 229 | |||
| 230 | if (it == emulationViewModel.totalShaders.value!!) { | ||
| 231 | binding.loadingText.setText(R.string.loading) | ||
| 232 | binding.loadingProgressIndicator.isIndeterminate = true | ||
| 233 | } | ||
| 234 | } | ||
| 235 | emulationViewModel.totalShaders.observe(viewLifecycleOwner) { | ||
| 236 | binding.loadingProgressIndicator.max = it | ||
| 237 | } | ||
| 238 | emulationViewModel.shaderMessage.observe(viewLifecycleOwner) { | ||
| 239 | if (it.isNotEmpty()) { | ||
| 240 | binding.loadingText.text = it | ||
| 241 | } | ||
| 242 | } | ||
| 243 | |||
| 244 | emulationViewModel.emulationStarted.observe(viewLifecycleOwner) { started -> | ||
| 245 | if (started) { | ||
| 246 | binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED) | ||
| 247 | ViewUtils.showView(binding.surfaceInputOverlay) | ||
| 248 | ViewUtils.hideView(binding.loadingIndicator) | ||
| 249 | |||
| 250 | // Setup overlay | ||
| 251 | updateShowFpsOverlay() | ||
| 252 | } | ||
| 253 | } | ||
| 254 | |||
| 255 | emulationViewModel.isEmulationStopping.observe(viewLifecycleOwner) { | ||
| 256 | if (it) { | ||
| 257 | binding.loadingText.setText(R.string.shutting_down) | ||
| 258 | ViewUtils.showView(binding.loadingIndicator) | ||
| 259 | ViewUtils.hideView(binding.inputContainer) | ||
| 260 | ViewUtils.hideView(binding.showFpsText) | ||
| 261 | } | ||
| 262 | } | ||
| 198 | } | 263 | } |
| 199 | 264 | ||
| 200 | override fun onConfigurationChanged(newConfig: Configuration) { | 265 | override fun onConfigurationChanged(newConfig: Configuration) { |
| @@ -204,11 +269,21 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 204 | binding.drawerLayout.close() | 269 | binding.drawerLayout.close() |
| 205 | } | 270 | } |
| 206 | if (EmulationMenuSettings.showOverlay) { | 271 | if (EmulationMenuSettings.showOverlay) { |
| 207 | binding.surfaceInputOverlay.post { binding.surfaceInputOverlay.isVisible = false } | 272 | binding.surfaceInputOverlay.post { |
| 273 | binding.surfaceInputOverlay.visibility = View.VISIBLE | ||
| 274 | } | ||
| 208 | } | 275 | } |
| 209 | } else { | 276 | } else { |
| 210 | if (EmulationMenuSettings.showOverlay) { | 277 | if (EmulationMenuSettings.showOverlay && |
| 211 | binding.surfaceInputOverlay.post { binding.surfaceInputOverlay.isVisible = true } | 278 | emulationViewModel.emulationStarted.value == true |
| 279 | ) { | ||
| 280 | binding.surfaceInputOverlay.post { | ||
| 281 | binding.surfaceInputOverlay.visibility = View.VISIBLE | ||
| 282 | } | ||
| 283 | } else { | ||
| 284 | binding.surfaceInputOverlay.post { | ||
| 285 | binding.surfaceInputOverlay.visibility = View.INVISIBLE | ||
| 286 | } | ||
| 212 | } | 287 | } |
| 213 | if (!isInFoldableLayout) { | 288 | if (!isInFoldableLayout) { |
| 214 | if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) { | 289 | if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) { |
| @@ -217,16 +292,13 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 217 | binding.surfaceInputOverlay.layout = InputOverlay.LANDSCAPE | 292 | binding.surfaceInputOverlay.layout = InputOverlay.LANDSCAPE |
| 218 | } | 293 | } |
| 219 | } | 294 | } |
| 220 | if (!binding.surfaceInputOverlay.isInEditMode) { | ||
| 221 | refreshInputOverlay() | ||
| 222 | } | ||
| 223 | } | 295 | } |
| 224 | } | 296 | } |
| 225 | 297 | ||
| 226 | override fun onResume() { | 298 | override fun onResume() { |
| 227 | super.onResume() | 299 | super.onResume() |
| 228 | if (!DirectoryInitialization.areDirectoriesReady) { | 300 | if (!DirectoryInitialization.areDirectoriesReady) { |
| 229 | DirectoryInitialization.start(requireContext()) | 301 | DirectoryInitialization.start() |
| 230 | } | 302 | } |
| 231 | 303 | ||
| 232 | updateScreenLayout() | 304 | updateScreenLayout() |
| @@ -251,10 +323,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 251 | super.onDetach() | 323 | super.onDetach() |
| 252 | } | 324 | } |
| 253 | 325 | ||
| 254 | private fun refreshInputOverlay() { | ||
| 255 | binding.surfaceInputOverlay.refreshControls() | ||
| 256 | } | ||
| 257 | |||
| 258 | private fun resetInputOverlay() { | 326 | private fun resetInputOverlay() { |
| 259 | preferences.edit() | 327 | preferences.edit() |
| 260 | .remove(Settings.PREF_CONTROL_SCALE) | 328 | .remove(Settings.PREF_CONTROL_SCALE) |
| @@ -272,17 +340,15 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 272 | val FRAMETIME = 2 | 340 | val FRAMETIME = 2 |
| 273 | val SPEED = 3 | 341 | val SPEED = 3 |
| 274 | perfStatsUpdater = { | 342 | perfStatsUpdater = { |
| 275 | val perfStats = NativeLibrary.getPerfStats() | 343 | if (emulationViewModel.emulationStarted.value == true) { |
| 276 | if (perfStats[FPS] > 0 && _binding != null) { | 344 | val perfStats = NativeLibrary.getPerfStats() |
| 277 | binding.showFpsText.text = String.format("FPS: %.1f", perfStats[FPS]) | 345 | if (perfStats[FPS] > 0 && _binding != null) { |
| 278 | } | 346 | binding.showFpsText.text = String.format("FPS: %.1f", perfStats[FPS]) |
| 279 | 347 | } | |
| 280 | if (!emulationState.isStopped) { | ||
| 281 | perfStatsUpdateHandler.postDelayed(perfStatsUpdater!!, 100) | 348 | perfStatsUpdateHandler.postDelayed(perfStatsUpdater!!, 100) |
| 282 | } | 349 | } |
| 283 | } | 350 | } |
| 284 | perfStatsUpdateHandler.post(perfStatsUpdater!!) | 351 | perfStatsUpdateHandler.post(perfStatsUpdater!!) |
| 285 | binding.showFpsText.text = resources.getString(R.string.emulation_game_loading) | ||
| 286 | binding.showFpsText.visibility = View.VISIBLE | 352 | binding.showFpsText.visibility = View.VISIBLE |
| 287 | } else { | 353 | } else { |
| 288 | if (perfStatsUpdater != null) { | 354 | if (perfStatsUpdater != null) { |
| @@ -340,7 +406,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 340 | 406 | ||
| 341 | isInFoldableLayout = true | 407 | isInFoldableLayout = true |
| 342 | binding.surfaceInputOverlay.layout = InputOverlay.FOLDABLE | 408 | binding.surfaceInputOverlay.layout = InputOverlay.FOLDABLE |
| 343 | refreshInputOverlay() | ||
| 344 | } | 409 | } |
| 345 | } | 410 | } |
| 346 | it.isSeparating | 411 | it.isSeparating |
| @@ -428,7 +493,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 428 | .apply() | 493 | .apply() |
| 429 | } | 494 | } |
| 430 | .setPositiveButton(android.R.string.ok) { _, _ -> | 495 | .setPositiveButton(android.R.string.ok) { _, _ -> |
| 431 | refreshInputOverlay() | 496 | binding.surfaceInputOverlay.refreshControls() |
| 432 | } | 497 | } |
| 433 | .setNegativeButton(android.R.string.cancel, null) | 498 | .setNegativeButton(android.R.string.cancel, null) |
| 434 | .setNeutralButton(R.string.emulation_toggle_all) { _, _ -> } | 499 | .setNeutralButton(R.string.emulation_toggle_all) { _, _ -> } |
| @@ -452,7 +517,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 452 | R.id.menu_show_overlay -> { | 517 | R.id.menu_show_overlay -> { |
| 453 | it.isChecked = !it.isChecked | 518 | it.isChecked = !it.isChecked |
| 454 | EmulationMenuSettings.showOverlay = it.isChecked | 519 | EmulationMenuSettings.showOverlay = it.isChecked |
| 455 | refreshInputOverlay() | 520 | binding.surfaceInputOverlay.refreshControls() |
| 456 | true | 521 | true |
| 457 | } | 522 | } |
| 458 | 523 | ||
| @@ -558,14 +623,14 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 558 | preferences.edit() | 623 | preferences.edit() |
| 559 | .putInt(Settings.PREF_CONTROL_SCALE, scale) | 624 | .putInt(Settings.PREF_CONTROL_SCALE, scale) |
| 560 | .apply() | 625 | .apply() |
| 561 | refreshInputOverlay() | 626 | binding.surfaceInputOverlay.refreshControls() |
| 562 | } | 627 | } |
| 563 | 628 | ||
| 564 | private fun setControlOpacity(opacity: Int) { | 629 | private fun setControlOpacity(opacity: Int) { |
| 565 | preferences.edit() | 630 | preferences.edit() |
| 566 | .putInt(Settings.PREF_CONTROL_OPACITY, opacity) | 631 | .putInt(Settings.PREF_CONTROL_OPACITY, opacity) |
| 567 | .apply() | 632 | .apply() |
| 568 | refreshInputOverlay() | 633 | binding.surfaceInputOverlay.refreshControls() |
| 569 | } | 634 | } |
| 570 | 635 | ||
| 571 | private fun setInsets() { | 636 | private fun setInsets() { |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt index d5e793491..cbbe14d22 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt | |||
| @@ -25,17 +25,18 @@ import androidx.core.view.updatePadding | |||
| 25 | import androidx.documentfile.provider.DocumentFile | 25 | import androidx.documentfile.provider.DocumentFile |
| 26 | import androidx.fragment.app.Fragment | 26 | import androidx.fragment.app.Fragment |
| 27 | import androidx.fragment.app.activityViewModels | 27 | import androidx.fragment.app.activityViewModels |
| 28 | import androidx.navigation.findNavController | ||
| 28 | import androidx.navigation.fragment.findNavController | 29 | import androidx.navigation.fragment.findNavController |
| 29 | import androidx.recyclerview.widget.LinearLayoutManager | 30 | import androidx.recyclerview.widget.LinearLayoutManager |
| 30 | import com.google.android.material.dialog.MaterialAlertDialogBuilder | 31 | import com.google.android.material.dialog.MaterialAlertDialogBuilder |
| 31 | import com.google.android.material.transition.MaterialSharedAxis | 32 | import com.google.android.material.transition.MaterialSharedAxis |
| 32 | import org.yuzu.yuzu_emu.BuildConfig | 33 | import org.yuzu.yuzu_emu.BuildConfig |
| 34 | import org.yuzu.yuzu_emu.HomeNavigationDirections | ||
| 33 | import org.yuzu.yuzu_emu.R | 35 | import org.yuzu.yuzu_emu.R |
| 34 | import org.yuzu.yuzu_emu.adapters.HomeSettingAdapter | 36 | import org.yuzu.yuzu_emu.adapters.HomeSettingAdapter |
| 35 | import org.yuzu.yuzu_emu.databinding.FragmentHomeSettingsBinding | 37 | import org.yuzu.yuzu_emu.databinding.FragmentHomeSettingsBinding |
| 36 | import org.yuzu.yuzu_emu.features.DocumentProvider | 38 | import org.yuzu.yuzu_emu.features.DocumentProvider |
| 37 | import org.yuzu.yuzu_emu.features.settings.model.Settings | 39 | import org.yuzu.yuzu_emu.features.settings.model.Settings |
| 38 | import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity | ||
| 39 | import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile | 40 | import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile |
| 40 | import org.yuzu.yuzu_emu.model.HomeSetting | 41 | import org.yuzu.yuzu_emu.model.HomeSetting |
| 41 | import org.yuzu.yuzu_emu.model.HomeViewModel | 42 | import org.yuzu.yuzu_emu.model.HomeViewModel |
| @@ -74,7 +75,13 @@ class HomeSettingsFragment : Fragment() { | |||
| 74 | R.string.advanced_settings, | 75 | R.string.advanced_settings, |
| 75 | R.string.settings_description, | 76 | R.string.settings_description, |
| 76 | R.drawable.ic_settings, | 77 | R.drawable.ic_settings, |
| 77 | { SettingsActivity.launch(requireContext(), SettingsFile.FILE_NAME_CONFIG, "") } | 78 | { |
| 79 | val action = HomeNavigationDirections.actionGlobalSettingsActivity( | ||
| 80 | null, | ||
| 81 | SettingsFile.FILE_NAME_CONFIG | ||
| 82 | ) | ||
| 83 | binding.root.findNavController().navigate(action) | ||
| 84 | } | ||
| 78 | ) | 85 | ) |
| 79 | ) | 86 | ) |
| 80 | add( | 87 | add( |
| @@ -90,7 +97,13 @@ class HomeSettingsFragment : Fragment() { | |||
| 90 | R.string.preferences_theme, | 97 | R.string.preferences_theme, |
| 91 | R.string.theme_and_color_description, | 98 | R.string.theme_and_color_description, |
| 92 | R.drawable.ic_palette, | 99 | R.drawable.ic_palette, |
| 93 | { SettingsActivity.launch(requireContext(), Settings.SECTION_THEME, "") } | 100 | { |
| 101 | val action = HomeNavigationDirections.actionGlobalSettingsActivity( | ||
| 102 | null, | ||
| 103 | Settings.SECTION_THEME | ||
| 104 | ) | ||
| 105 | binding.root.findNavController().navigate(action) | ||
| 106 | } | ||
| 94 | ) | 107 | ) |
| 95 | ) | 108 | ) |
| 96 | add( | 109 | add( |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ImportExportSavesFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ImportExportSavesFragment.kt index e1495ee8c..f38aeea53 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ImportExportSavesFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ImportExportSavesFragment.kt | |||
| @@ -187,8 +187,8 @@ class ImportExportSavesFragment : DialogFragment() { | |||
| 187 | withContext(Dispatchers.Main) { | 187 | withContext(Dispatchers.Main) { |
| 188 | if (!validZip) { | 188 | if (!validZip) { |
| 189 | MessageDialogFragment.newInstance( | 189 | MessageDialogFragment.newInstance( |
| 190 | R.string.save_file_invalid_zip_structure, | 190 | titleId = R.string.save_file_invalid_zip_structure, |
| 191 | R.string.save_file_invalid_zip_structure_description | 191 | descriptionId = R.string.save_file_invalid_zip_structure_description |
| 192 | ).show(activity.supportFragmentManager, MessageDialogFragment.TAG) | 192 | ).show(activity.supportFragmentManager, MessageDialogFragment.TAG) |
| 193 | return@withContext | 193 | return@withContext |
| 194 | } | 194 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt index 739b26f99..181bd983a 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt | |||
| @@ -34,7 +34,7 @@ class IndeterminateProgressDialogFragment : DialogFragment() { | |||
| 34 | when (val result = taskViewModel.result.value) { | 34 | when (val result = taskViewModel.result.value) { |
| 35 | is String -> Toast.makeText(requireContext(), result, Toast.LENGTH_LONG).show() | 35 | is String -> Toast.makeText(requireContext(), result, Toast.LENGTH_LONG).show() |
| 36 | is MessageDialogFragment -> result.show( | 36 | is MessageDialogFragment -> result.show( |
| 37 | parentFragmentManager, | 37 | requireActivity().supportFragmentManager, |
| 38 | MessageDialogFragment.TAG | 38 | MessageDialogFragment.TAG |
| 39 | ) | 39 | ) |
| 40 | } | 40 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LongMessageDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LongMessageDialogFragment.kt deleted file mode 100644 index b29b627e9..000000000 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LongMessageDialogFragment.kt +++ /dev/null | |||
| @@ -1,62 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | package org.yuzu.yuzu_emu.fragments | ||
| 5 | |||
| 6 | import android.app.Dialog | ||
| 7 | import android.content.Intent | ||
| 8 | import android.net.Uri | ||
| 9 | import android.os.Bundle | ||
| 10 | import androidx.fragment.app.DialogFragment | ||
| 11 | import com.google.android.material.dialog.MaterialAlertDialogBuilder | ||
| 12 | import org.yuzu.yuzu_emu.R | ||
| 13 | |||
| 14 | class LongMessageDialogFragment : DialogFragment() { | ||
| 15 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { | ||
| 16 | val titleId = requireArguments().getInt(TITLE) | ||
| 17 | val description = requireArguments().getString(DESCRIPTION) | ||
| 18 | val helpLinkId = requireArguments().getInt(HELP_LINK) | ||
| 19 | |||
| 20 | val dialog = MaterialAlertDialogBuilder(requireContext()) | ||
| 21 | .setPositiveButton(R.string.close, null) | ||
| 22 | .setTitle(titleId) | ||
| 23 | .setMessage(description) | ||
| 24 | |||
| 25 | if (helpLinkId != 0) { | ||
| 26 | dialog.setNeutralButton(R.string.learn_more) { _, _ -> | ||
| 27 | openLink(getString(helpLinkId)) | ||
| 28 | } | ||
| 29 | } | ||
| 30 | |||
| 31 | return dialog.show() | ||
| 32 | } | ||
| 33 | |||
| 34 | private fun openLink(link: String) { | ||
| 35 | val intent = Intent(Intent.ACTION_VIEW, Uri.parse(link)) | ||
| 36 | startActivity(intent) | ||
| 37 | } | ||
| 38 | |||
| 39 | companion object { | ||
| 40 | const val TAG = "LongMessageDialogFragment" | ||
| 41 | |||
| 42 | private const val TITLE = "Title" | ||
| 43 | private const val DESCRIPTION = "Description" | ||
| 44 | private const val HELP_LINK = "Link" | ||
| 45 | |||
| 46 | fun newInstance( | ||
| 47 | titleId: Int, | ||
| 48 | description: String, | ||
| 49 | helpLinkId: Int = 0 | ||
| 50 | ): LongMessageDialogFragment { | ||
| 51 | val dialog = LongMessageDialogFragment() | ||
| 52 | val bundle = Bundle() | ||
| 53 | bundle.apply { | ||
| 54 | putInt(TITLE, titleId) | ||
| 55 | putString(DESCRIPTION, description) | ||
| 56 | putInt(HELP_LINK, helpLinkId) | ||
| 57 | } | ||
| 58 | dialog.arguments = bundle | ||
| 59 | return dialog | ||
| 60 | } | ||
| 61 | } | ||
| 62 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/MessageDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/MessageDialogFragment.kt index 2db38fdc2..7d1c2c8dd 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/MessageDialogFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/MessageDialogFragment.kt | |||
| @@ -13,14 +13,20 @@ import org.yuzu.yuzu_emu.R | |||
| 13 | 13 | ||
| 14 | class MessageDialogFragment : DialogFragment() { | 14 | class MessageDialogFragment : DialogFragment() { |
| 15 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { | 15 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { |
| 16 | val titleId = requireArguments().getInt(TITLE) | 16 | val titleId = requireArguments().getInt(TITLE_ID) |
| 17 | val descriptionId = requireArguments().getInt(DESCRIPTION) | 17 | val titleString = requireArguments().getString(TITLE_STRING)!! |
| 18 | val descriptionId = requireArguments().getInt(DESCRIPTION_ID) | ||
| 19 | val descriptionString = requireArguments().getString(DESCRIPTION_STRING)!! | ||
| 18 | val helpLinkId = requireArguments().getInt(HELP_LINK) | 20 | val helpLinkId = requireArguments().getInt(HELP_LINK) |
| 19 | 21 | ||
| 20 | val dialog = MaterialAlertDialogBuilder(requireContext()) | 22 | val dialog = MaterialAlertDialogBuilder(requireContext()) |
| 21 | .setPositiveButton(R.string.close, null) | 23 | .setPositiveButton(R.string.close, null) |
| 22 | .setTitle(titleId) | 24 | |
| 23 | .setMessage(descriptionId) | 25 | if (titleId != 0) dialog.setTitle(titleId) |
| 26 | if (titleString.isNotEmpty()) dialog.setTitle(titleString) | ||
| 27 | |||
| 28 | if (descriptionId != 0) dialog.setMessage(descriptionId) | ||
| 29 | if (descriptionString.isNotEmpty()) dialog.setMessage(descriptionString) | ||
| 24 | 30 | ||
| 25 | if (helpLinkId != 0) { | 31 | if (helpLinkId != 0) { |
| 26 | dialog.setNeutralButton(R.string.learn_more) { _, _ -> | 32 | dialog.setNeutralButton(R.string.learn_more) { _, _ -> |
| @@ -39,20 +45,26 @@ class MessageDialogFragment : DialogFragment() { | |||
| 39 | companion object { | 45 | companion object { |
| 40 | const val TAG = "MessageDialogFragment" | 46 | const val TAG = "MessageDialogFragment" |
| 41 | 47 | ||
| 42 | private const val TITLE = "Title" | 48 | private const val TITLE_ID = "Title" |
| 43 | private const val DESCRIPTION = "Description" | 49 | private const val TITLE_STRING = "TitleString" |
| 50 | private const val DESCRIPTION_ID = "DescriptionId" | ||
| 51 | private const val DESCRIPTION_STRING = "DescriptionString" | ||
| 44 | private const val HELP_LINK = "Link" | 52 | private const val HELP_LINK = "Link" |
| 45 | 53 | ||
| 46 | fun newInstance( | 54 | fun newInstance( |
| 47 | titleId: Int, | 55 | titleId: Int = 0, |
| 48 | descriptionId: Int, | 56 | titleString: String = "", |
| 57 | descriptionId: Int = 0, | ||
| 58 | descriptionString: String = "", | ||
| 49 | helpLinkId: Int = 0 | 59 | helpLinkId: Int = 0 |
| 50 | ): MessageDialogFragment { | 60 | ): MessageDialogFragment { |
| 51 | val dialog = MessageDialogFragment() | 61 | val dialog = MessageDialogFragment() |
| 52 | val bundle = Bundle() | 62 | val bundle = Bundle() |
| 53 | bundle.apply { | 63 | bundle.apply { |
| 54 | putInt(TITLE, titleId) | 64 | putInt(TITLE_ID, titleId) |
| 55 | putInt(DESCRIPTION, descriptionId) | 65 | putString(TITLE_STRING, titleString) |
| 66 | putInt(DESCRIPTION_ID, descriptionId) | ||
| 67 | putString(DESCRIPTION_STRING, descriptionString) | ||
| 56 | putInt(HELP_LINK, helpLinkId) | 68 | putInt(HELP_LINK, helpLinkId) |
| 57 | } | 69 | } |
| 58 | dialog.arguments = bundle | 70 | dialog.arguments = bundle |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsDialogFragment.kt new file mode 100644 index 000000000..d18ec6974 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsDialogFragment.kt | |||
| @@ -0,0 +1,235 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | package org.yuzu.yuzu_emu.fragments | ||
| 5 | |||
| 6 | import android.app.Dialog | ||
| 7 | import android.content.DialogInterface | ||
| 8 | import android.os.Bundle | ||
| 9 | import android.view.LayoutInflater | ||
| 10 | import android.view.View | ||
| 11 | import android.view.ViewGroup | ||
| 12 | import androidx.fragment.app.DialogFragment | ||
| 13 | import androidx.fragment.app.activityViewModels | ||
| 14 | import androidx.lifecycle.Lifecycle | ||
| 15 | import androidx.lifecycle.lifecycleScope | ||
| 16 | import androidx.lifecycle.repeatOnLifecycle | ||
| 17 | import com.google.android.material.dialog.MaterialAlertDialogBuilder | ||
| 18 | import com.google.android.material.slider.Slider | ||
| 19 | import kotlinx.coroutines.launch | ||
| 20 | import org.yuzu.yuzu_emu.R | ||
| 21 | import org.yuzu.yuzu_emu.databinding.DialogSliderBinding | ||
| 22 | import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem | ||
| 23 | import org.yuzu.yuzu_emu.features.settings.model.view.SingleChoiceSetting | ||
| 24 | import org.yuzu.yuzu_emu.features.settings.model.view.SliderSetting | ||
| 25 | import org.yuzu.yuzu_emu.features.settings.model.view.StringSingleChoiceSetting | ||
| 26 | import org.yuzu.yuzu_emu.model.SettingsViewModel | ||
| 27 | |||
| 28 | class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener { | ||
| 29 | private var type = 0 | ||
| 30 | private var position = 0 | ||
| 31 | |||
| 32 | private var defaultCancelListener = | ||
| 33 | DialogInterface.OnClickListener { _: DialogInterface?, _: Int -> closeDialog() } | ||
| 34 | |||
| 35 | private val settingsViewModel: SettingsViewModel by activityViewModels() | ||
| 36 | |||
| 37 | private lateinit var sliderBinding: DialogSliderBinding | ||
| 38 | |||
| 39 | override fun onCreate(savedInstanceState: Bundle?) { | ||
| 40 | super.onCreate(savedInstanceState) | ||
| 41 | type = requireArguments().getInt(TYPE) | ||
| 42 | position = requireArguments().getInt(POSITION) | ||
| 43 | |||
| 44 | if (settingsViewModel.clickedItem == null) dismiss() | ||
| 45 | } | ||
| 46 | |||
| 47 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { | ||
| 48 | return when (type) { | ||
| 49 | TYPE_RESET_SETTING -> { | ||
| 50 | MaterialAlertDialogBuilder(requireContext()) | ||
| 51 | .setMessage(R.string.reset_setting_confirmation) | ||
| 52 | .setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int -> | ||
| 53 | settingsViewModel.clickedItem!!.setting.reset() | ||
| 54 | settingsViewModel.setAdapterItemChanged(position) | ||
| 55 | settingsViewModel.shouldSave = true | ||
| 56 | } | ||
| 57 | .setNegativeButton(android.R.string.cancel, null) | ||
| 58 | .create() | ||
| 59 | } | ||
| 60 | |||
| 61 | SettingsItem.TYPE_SINGLE_CHOICE -> { | ||
| 62 | val item = settingsViewModel.clickedItem as SingleChoiceSetting | ||
| 63 | val value = getSelectionForSingleChoiceValue(item) | ||
| 64 | MaterialAlertDialogBuilder(requireContext()) | ||
| 65 | .setTitle(item.nameId) | ||
| 66 | .setSingleChoiceItems(item.choicesId, value, this) | ||
| 67 | .create() | ||
| 68 | } | ||
| 69 | |||
| 70 | SettingsItem.TYPE_SLIDER -> { | ||
| 71 | sliderBinding = DialogSliderBinding.inflate(layoutInflater) | ||
| 72 | val item = settingsViewModel.clickedItem as SliderSetting | ||
| 73 | |||
| 74 | settingsViewModel.setSliderTextValue(item.selectedValue.toFloat(), item.units) | ||
| 75 | sliderBinding.slider.apply { | ||
| 76 | valueFrom = item.min.toFloat() | ||
| 77 | valueTo = item.max.toFloat() | ||
| 78 | value = settingsViewModel.sliderProgress.value.toFloat() | ||
| 79 | addOnChangeListener { _: Slider, value: Float, _: Boolean -> | ||
| 80 | settingsViewModel.setSliderTextValue(value, item.units) | ||
| 81 | } | ||
| 82 | } | ||
| 83 | |||
| 84 | MaterialAlertDialogBuilder(requireContext()) | ||
| 85 | .setTitle(item.nameId) | ||
| 86 | .setView(sliderBinding.root) | ||
| 87 | .setPositiveButton(android.R.string.ok, this) | ||
| 88 | .setNegativeButton(android.R.string.cancel, defaultCancelListener) | ||
| 89 | .create() | ||
| 90 | } | ||
| 91 | |||
| 92 | SettingsItem.TYPE_STRING_SINGLE_CHOICE -> { | ||
| 93 | val item = settingsViewModel.clickedItem as StringSingleChoiceSetting | ||
| 94 | MaterialAlertDialogBuilder(requireContext()) | ||
| 95 | .setTitle(item.nameId) | ||
| 96 | .setSingleChoiceItems(item.choices, item.selectValueIndex, this) | ||
| 97 | .create() | ||
| 98 | } | ||
| 99 | |||
| 100 | else -> super.onCreateDialog(savedInstanceState) | ||
| 101 | } | ||
| 102 | } | ||
| 103 | |||
| 104 | override fun onCreateView( | ||
| 105 | inflater: LayoutInflater, | ||
| 106 | container: ViewGroup?, | ||
| 107 | savedInstanceState: Bundle? | ||
| 108 | ): View? { | ||
| 109 | return when (type) { | ||
| 110 | SettingsItem.TYPE_SLIDER -> sliderBinding.root | ||
| 111 | else -> super.onCreateView(inflater, container, savedInstanceState) | ||
| 112 | } | ||
| 113 | } | ||
| 114 | |||
| 115 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | ||
| 116 | super.onViewCreated(view, savedInstanceState) | ||
| 117 | when (type) { | ||
| 118 | SettingsItem.TYPE_SLIDER -> { | ||
| 119 | viewLifecycleOwner.lifecycleScope.launch { | ||
| 120 | repeatOnLifecycle(Lifecycle.State.CREATED) { | ||
| 121 | settingsViewModel.sliderTextValue.collect { | ||
| 122 | sliderBinding.textValue.text = it | ||
| 123 | } | ||
| 124 | } | ||
| 125 | repeatOnLifecycle(Lifecycle.State.CREATED) { | ||
| 126 | settingsViewModel.sliderProgress.collect { | ||
| 127 | sliderBinding.slider.value = it.toFloat() | ||
| 128 | } | ||
| 129 | } | ||
| 130 | } | ||
| 131 | } | ||
| 132 | } | ||
| 133 | } | ||
| 134 | |||
| 135 | override fun onClick(dialog: DialogInterface, which: Int) { | ||
| 136 | when (settingsViewModel.clickedItem) { | ||
| 137 | is SingleChoiceSetting -> { | ||
| 138 | val scSetting = settingsViewModel.clickedItem as SingleChoiceSetting | ||
| 139 | val value = getValueForSingleChoiceSelection(scSetting, which) | ||
| 140 | if (scSetting.selectedValue != value) { | ||
| 141 | settingsViewModel.shouldSave = true | ||
| 142 | } | ||
| 143 | scSetting.selectedValue = value | ||
| 144 | } | ||
| 145 | |||
| 146 | is StringSingleChoiceSetting -> { | ||
| 147 | val scSetting = settingsViewModel.clickedItem as StringSingleChoiceSetting | ||
| 148 | val value = scSetting.getValueAt(which) | ||
| 149 | if (scSetting.selectedValue != value) settingsViewModel.shouldSave = true | ||
| 150 | scSetting.selectedValue = value | ||
| 151 | } | ||
| 152 | |||
| 153 | is SliderSetting -> { | ||
| 154 | val sliderSetting = settingsViewModel.clickedItem as SliderSetting | ||
| 155 | if (sliderSetting.selectedValue != settingsViewModel.sliderProgress.value) { | ||
| 156 | settingsViewModel.shouldSave = true | ||
| 157 | } | ||
| 158 | sliderSetting.selectedValue = settingsViewModel.sliderProgress.value | ||
| 159 | } | ||
| 160 | } | ||
| 161 | closeDialog() | ||
| 162 | } | ||
| 163 | |||
| 164 | private fun closeDialog() { | ||
| 165 | settingsViewModel.setAdapterItemChanged(position) | ||
| 166 | settingsViewModel.clickedItem = null | ||
| 167 | settingsViewModel.setSliderProgress(-1f) | ||
| 168 | dismiss() | ||
| 169 | } | ||
| 170 | |||
| 171 | private fun getValueForSingleChoiceSelection(item: SingleChoiceSetting, which: Int): Int { | ||
| 172 | val valuesId = item.valuesId | ||
| 173 | return if (valuesId > 0) { | ||
| 174 | val valuesArray = requireContext().resources.getIntArray(valuesId) | ||
| 175 | valuesArray[which] | ||
| 176 | } else { | ||
| 177 | which | ||
| 178 | } | ||
| 179 | } | ||
| 180 | |||
| 181 | private fun getSelectionForSingleChoiceValue(item: SingleChoiceSetting): Int { | ||
| 182 | val value = item.selectedValue | ||
| 183 | val valuesId = item.valuesId | ||
| 184 | if (valuesId > 0) { | ||
| 185 | val valuesArray = requireContext().resources.getIntArray(valuesId) | ||
| 186 | for (index in valuesArray.indices) { | ||
| 187 | val current = valuesArray[index] | ||
| 188 | if (current == value) { | ||
| 189 | return index | ||
| 190 | } | ||
| 191 | } | ||
| 192 | } else { | ||
| 193 | return value | ||
| 194 | } | ||
| 195 | return -1 | ||
| 196 | } | ||
| 197 | |||
| 198 | companion object { | ||
| 199 | const val TAG = "SettingsDialogFragment" | ||
| 200 | |||
| 201 | const val TYPE_RESET_SETTING = -1 | ||
| 202 | |||
| 203 | const val TITLE = "Title" | ||
| 204 | const val TYPE = "Type" | ||
| 205 | const val POSITION = "Position" | ||
| 206 | |||
| 207 | fun newInstance( | ||
| 208 | settingsViewModel: SettingsViewModel, | ||
| 209 | clickedItem: SettingsItem, | ||
| 210 | type: Int, | ||
| 211 | position: Int | ||
| 212 | ): SettingsDialogFragment { | ||
| 213 | when (type) { | ||
| 214 | SettingsItem.TYPE_HEADER, | ||
| 215 | SettingsItem.TYPE_SWITCH, | ||
| 216 | SettingsItem.TYPE_SUBMENU, | ||
| 217 | SettingsItem.TYPE_DATETIME_SETTING, | ||
| 218 | SettingsItem.TYPE_RUNNABLE -> | ||
| 219 | throw IllegalArgumentException("[SettingsDialogFragment] Incompatible type!") | ||
| 220 | |||
| 221 | SettingsItem.TYPE_SLIDER -> settingsViewModel.setSliderProgress( | ||
| 222 | (clickedItem as SliderSetting).selectedValue.toFloat() | ||
| 223 | ) | ||
| 224 | } | ||
| 225 | settingsViewModel.clickedItem = clickedItem | ||
| 226 | |||
| 227 | val args = Bundle() | ||
| 228 | args.putInt(TYPE, type) | ||
| 229 | args.putInt(POSITION, position) | ||
| 230 | val fragment = SettingsDialogFragment() | ||
| 231 | fragment.arguments = args | ||
| 232 | return fragment | ||
| 233 | } | ||
| 234 | } | ||
| 235 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsSearchFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsSearchFragment.kt new file mode 100644 index 000000000..55b6a0367 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsSearchFragment.kt | |||
| @@ -0,0 +1,184 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | package org.yuzu.yuzu_emu.fragments | ||
| 5 | |||
| 6 | import android.content.Context | ||
| 7 | import android.os.Bundle | ||
| 8 | import android.view.LayoutInflater | ||
| 9 | import android.view.View | ||
| 10 | import android.view.ViewGroup | ||
| 11 | import android.view.inputmethod.InputMethodManager | ||
| 12 | import androidx.core.view.ViewCompat | ||
| 13 | import androidx.core.view.WindowInsetsCompat | ||
| 14 | import androidx.core.view.updatePadding | ||
| 15 | import androidx.core.widget.doOnTextChanged | ||
| 16 | import androidx.fragment.app.Fragment | ||
| 17 | import androidx.fragment.app.activityViewModels | ||
| 18 | import androidx.recyclerview.widget.LinearLayoutManager | ||
| 19 | import com.google.android.material.divider.MaterialDividerItemDecoration | ||
| 20 | import com.google.android.material.transition.MaterialSharedAxis | ||
| 21 | import info.debatty.java.stringsimilarity.Cosine | ||
| 22 | import org.yuzu.yuzu_emu.R | ||
| 23 | import org.yuzu.yuzu_emu.databinding.FragmentSettingsSearchBinding | ||
| 24 | import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem | ||
| 25 | import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter | ||
| 26 | import org.yuzu.yuzu_emu.model.SettingsViewModel | ||
| 27 | import org.yuzu.yuzu_emu.utils.NativeConfig | ||
| 28 | |||
| 29 | class SettingsSearchFragment : Fragment() { | ||
| 30 | private var _binding: FragmentSettingsSearchBinding? = null | ||
| 31 | private val binding get() = _binding!! | ||
| 32 | |||
| 33 | private var settingsAdapter: SettingsAdapter? = null | ||
| 34 | |||
| 35 | private val settingsViewModel: SettingsViewModel by activityViewModels() | ||
| 36 | |||
| 37 | override fun onCreate(savedInstanceState: Bundle?) { | ||
| 38 | super.onCreate(savedInstanceState) | ||
| 39 | enterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false) | ||
| 40 | returnTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true) | ||
| 41 | } | ||
| 42 | |||
| 43 | override fun onCreateView( | ||
| 44 | inflater: LayoutInflater, | ||
| 45 | container: ViewGroup?, | ||
| 46 | savedInstanceState: Bundle? | ||
| 47 | ): View { | ||
| 48 | _binding = FragmentSettingsSearchBinding.inflate(layoutInflater) | ||
| 49 | return binding.root | ||
| 50 | } | ||
| 51 | |||
| 52 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | ||
| 53 | super.onViewCreated(view, savedInstanceState) | ||
| 54 | settingsViewModel.setIsUsingSearch(true) | ||
| 55 | |||
| 56 | if (savedInstanceState != null) { | ||
| 57 | binding.searchText.setText(savedInstanceState.getString(SEARCH_TEXT)) | ||
| 58 | } | ||
| 59 | |||
| 60 | settingsAdapter = SettingsAdapter(this, requireContext()) | ||
| 61 | |||
| 62 | val dividerDecoration = MaterialDividerItemDecoration( | ||
| 63 | requireContext(), | ||
| 64 | LinearLayoutManager.VERTICAL | ||
| 65 | ) | ||
| 66 | dividerDecoration.isLastItemDecorated = false | ||
| 67 | binding.settingsList.apply { | ||
| 68 | adapter = settingsAdapter | ||
| 69 | layoutManager = LinearLayoutManager(requireContext()) | ||
| 70 | addItemDecoration(dividerDecoration) | ||
| 71 | } | ||
| 72 | |||
| 73 | focusSearch() | ||
| 74 | |||
| 75 | binding.backButton.setOnClickListener { settingsViewModel.setShouldNavigateBack(true) } | ||
| 76 | binding.searchBackground.setOnClickListener { focusSearch() } | ||
| 77 | binding.clearButton.setOnClickListener { binding.searchText.setText("") } | ||
| 78 | binding.searchText.doOnTextChanged { _, _, _, _ -> | ||
| 79 | search() | ||
| 80 | binding.settingsList.smoothScrollToPosition(0) | ||
| 81 | } | ||
| 82 | settingsViewModel.shouldReloadSettingsList.observe(viewLifecycleOwner) { | ||
| 83 | if (it) { | ||
| 84 | settingsViewModel.setShouldReloadSettingsList(false) | ||
| 85 | search() | ||
| 86 | } | ||
| 87 | } | ||
| 88 | |||
| 89 | search() | ||
| 90 | |||
| 91 | setInsets() | ||
| 92 | } | ||
| 93 | |||
| 94 | override fun onSaveInstanceState(outState: Bundle) { | ||
| 95 | super.onSaveInstanceState(outState) | ||
| 96 | outState.putString(SEARCH_TEXT, binding.searchText.text.toString()) | ||
| 97 | } | ||
| 98 | |||
| 99 | private fun search() { | ||
| 100 | val searchTerm = binding.searchText.text.toString().lowercase() | ||
| 101 | binding.clearButton.visibility = | ||
| 102 | if (searchTerm.isEmpty()) View.INVISIBLE else View.VISIBLE | ||
| 103 | if (searchTerm.isEmpty()) { | ||
| 104 | binding.noResultsView.visibility = View.VISIBLE | ||
| 105 | settingsAdapter?.submitList(emptyList()) | ||
| 106 | return | ||
| 107 | } | ||
| 108 | |||
| 109 | val baseList = SettingsItem.settingsItems | ||
| 110 | val similarityAlgorithm = if (searchTerm.length > 2) Cosine() else Cosine(1) | ||
| 111 | val sortedList: List<SettingsItem> = baseList.mapNotNull { item -> | ||
| 112 | val title = getString(item.value.nameId).lowercase() | ||
| 113 | val similarity = similarityAlgorithm.similarity(searchTerm, title) | ||
| 114 | if (similarity > 0.08) { | ||
| 115 | Pair(similarity, item) | ||
| 116 | } else { | ||
| 117 | null | ||
| 118 | } | ||
| 119 | }.sortedByDescending { it.first }.mapNotNull { | ||
| 120 | val item = it.second.value | ||
| 121 | val pairedSettingKey = item.setting.pairedSettingKey | ||
| 122 | val optionalSetting: SettingsItem? = if (pairedSettingKey.isNotEmpty()) { | ||
| 123 | val pairedSettingValue = NativeConfig.getBoolean(pairedSettingKey, false) | ||
| 124 | if (pairedSettingValue) it.second.value else null | ||
| 125 | } else { | ||
| 126 | it.second.value | ||
| 127 | } | ||
| 128 | optionalSetting | ||
| 129 | } | ||
| 130 | settingsAdapter?.submitList(sortedList) | ||
| 131 | binding.noResultsView.visibility = | ||
| 132 | if (sortedList.isEmpty()) View.VISIBLE else View.INVISIBLE | ||
| 133 | } | ||
| 134 | |||
| 135 | private fun focusSearch() { | ||
| 136 | binding.searchText.requestFocus() | ||
| 137 | val imm = requireActivity() | ||
| 138 | .getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager? | ||
| 139 | imm?.showSoftInput(binding.searchText, InputMethodManager.SHOW_IMPLICIT) | ||
| 140 | } | ||
| 141 | |||
| 142 | private fun setInsets() = | ||
| 143 | ViewCompat.setOnApplyWindowInsetsListener( | ||
| 144 | binding.root | ||
| 145 | ) { _: View, windowInsets: WindowInsetsCompat -> | ||
| 146 | val extraListSpacing = resources.getDimensionPixelSize(R.dimen.spacing_med) | ||
| 147 | val sideMargin = resources.getDimensionPixelSize(R.dimen.spacing_medlarge) | ||
| 148 | val topMargin = resources.getDimensionPixelSize(R.dimen.spacing_chip) | ||
| 149 | |||
| 150 | val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) | ||
| 151 | val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) | ||
| 152 | |||
| 153 | val leftInsets = barInsets.left + cutoutInsets.left | ||
| 154 | val rightInsets = barInsets.right + cutoutInsets.right | ||
| 155 | |||
| 156 | binding.settingsList.updatePadding(bottom = barInsets.bottom + extraListSpacing) | ||
| 157 | binding.frameSearch.updatePadding( | ||
| 158 | left = leftInsets + sideMargin, | ||
| 159 | top = barInsets.top + topMargin, | ||
| 160 | right = rightInsets + sideMargin | ||
| 161 | ) | ||
| 162 | binding.noResultsView.updatePadding( | ||
| 163 | left = leftInsets, | ||
| 164 | right = rightInsets, | ||
| 165 | bottom = barInsets.bottom | ||
| 166 | ) | ||
| 167 | |||
| 168 | val mlpSettingsList = binding.settingsList.layoutParams as ViewGroup.MarginLayoutParams | ||
| 169 | mlpSettingsList.leftMargin = leftInsets + sideMargin | ||
| 170 | mlpSettingsList.rightMargin = rightInsets + sideMargin | ||
| 171 | binding.settingsList.layoutParams = mlpSettingsList | ||
| 172 | |||
| 173 | val mlpDivider = binding.divider.layoutParams as ViewGroup.MarginLayoutParams | ||
| 174 | mlpDivider.leftMargin = leftInsets + sideMargin | ||
| 175 | mlpDivider.rightMargin = rightInsets + sideMargin | ||
| 176 | binding.divider.layoutParams = mlpDivider | ||
| 177 | |||
| 178 | windowInsets | ||
| 179 | } | ||
| 180 | |||
| 181 | companion object { | ||
| 182 | const val SEARCH_TEXT = "SearchText" | ||
| 183 | } | ||
| 184 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/EmulationViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/EmulationViewModel.kt new file mode 100644 index 000000000..e35f51bc3 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/EmulationViewModel.kt | |||
| @@ -0,0 +1,59 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-3.0-or-later | ||
| 3 | |||
| 4 | package org.yuzu.yuzu_emu.model | ||
| 5 | |||
| 6 | import androidx.lifecycle.LiveData | ||
| 7 | import androidx.lifecycle.MutableLiveData | ||
| 8 | import androidx.lifecycle.ViewModel | ||
| 9 | |||
| 10 | class EmulationViewModel : ViewModel() { | ||
| 11 | private val _emulationStarted = MutableLiveData(false) | ||
| 12 | val emulationStarted: LiveData<Boolean> get() = _emulationStarted | ||
| 13 | |||
| 14 | private val _isEmulationStopping = MutableLiveData(false) | ||
| 15 | val isEmulationStopping: LiveData<Boolean> get() = _isEmulationStopping | ||
| 16 | |||
| 17 | private val _shaderProgress = MutableLiveData(0) | ||
| 18 | val shaderProgress: LiveData<Int> get() = _shaderProgress | ||
| 19 | |||
| 20 | private val _totalShaders = MutableLiveData(0) | ||
| 21 | val totalShaders: LiveData<Int> get() = _totalShaders | ||
| 22 | |||
| 23 | private val _shaderMessage = MutableLiveData("") | ||
| 24 | val shaderMessage: LiveData<String> get() = _shaderMessage | ||
| 25 | |||
| 26 | fun setEmulationStarted(started: Boolean) { | ||
| 27 | _emulationStarted.postValue(started) | ||
| 28 | } | ||
| 29 | |||
| 30 | fun setIsEmulationStopping(value: Boolean) { | ||
| 31 | _isEmulationStopping.value = value | ||
| 32 | } | ||
| 33 | |||
| 34 | fun setShaderProgress(progress: Int) { | ||
| 35 | _shaderProgress.value = progress | ||
| 36 | } | ||
| 37 | |||
| 38 | fun setTotalShaders(max: Int) { | ||
| 39 | _totalShaders.value = max | ||
| 40 | } | ||
| 41 | |||
| 42 | fun setShaderMessage(msg: String) { | ||
| 43 | _shaderMessage.value = msg | ||
| 44 | } | ||
| 45 | |||
| 46 | fun updateProgress(msg: String, progress: Int, max: Int) { | ||
| 47 | setShaderMessage(msg) | ||
| 48 | setShaderProgress(progress) | ||
| 49 | setTotalShaders(max) | ||
| 50 | } | ||
| 51 | |||
| 52 | fun clear() { | ||
| 53 | _emulationStarted.value = false | ||
| 54 | _isEmulationStopping.value = false | ||
| 55 | _shaderProgress.value = 0 | ||
| 56 | _totalShaders.value = 0 | ||
| 57 | _shaderMessage.value = "" | ||
| 58 | } | ||
| 59 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt new file mode 100644 index 000000000..d16d15fa6 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt | |||
| @@ -0,0 +1,96 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | package org.yuzu.yuzu_emu.model | ||
| 5 | |||
| 6 | import androidx.lifecycle.LiveData | ||
| 7 | import androidx.lifecycle.MutableLiveData | ||
| 8 | import androidx.lifecycle.SavedStateHandle | ||
| 9 | import androidx.lifecycle.ViewModel | ||
| 10 | import org.yuzu.yuzu_emu.R | ||
| 11 | import org.yuzu.yuzu_emu.YuzuApplication | ||
| 12 | import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem | ||
| 13 | |||
| 14 | class SettingsViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() { | ||
| 15 | var game: Game? = null | ||
| 16 | |||
| 17 | var shouldSave = false | ||
| 18 | |||
| 19 | var clickedItem: SettingsItem? = null | ||
| 20 | |||
| 21 | private val _toolbarTitle = MutableLiveData("") | ||
| 22 | val toolbarTitle: LiveData<String> get() = _toolbarTitle | ||
| 23 | |||
| 24 | private val _shouldRecreate = MutableLiveData(false) | ||
| 25 | val shouldRecreate: LiveData<Boolean> get() = _shouldRecreate | ||
| 26 | |||
| 27 | private val _shouldNavigateBack = MutableLiveData(false) | ||
| 28 | val shouldNavigateBack: LiveData<Boolean> get() = _shouldNavigateBack | ||
| 29 | |||
| 30 | private val _shouldShowResetSettingsDialog = MutableLiveData(false) | ||
| 31 | val shouldShowResetSettingsDialog: LiveData<Boolean> get() = _shouldShowResetSettingsDialog | ||
| 32 | |||
| 33 | private val _shouldReloadSettingsList = MutableLiveData(false) | ||
| 34 | val shouldReloadSettingsList: LiveData<Boolean> get() = _shouldReloadSettingsList | ||
| 35 | |||
| 36 | private val _isUsingSearch = MutableLiveData(false) | ||
| 37 | val isUsingSearch: LiveData<Boolean> get() = _isUsingSearch | ||
| 38 | |||
| 39 | val sliderProgress = savedStateHandle.getStateFlow(KEY_SLIDER_PROGRESS, -1) | ||
| 40 | |||
| 41 | val sliderTextValue = savedStateHandle.getStateFlow(KEY_SLIDER_TEXT_VALUE, "") | ||
| 42 | |||
| 43 | val adapterItemChanged = savedStateHandle.getStateFlow(KEY_ADAPTER_ITEM_CHANGED, -1) | ||
| 44 | |||
| 45 | fun setToolbarTitle(value: String) { | ||
| 46 | _toolbarTitle.value = value | ||
| 47 | } | ||
| 48 | |||
| 49 | fun setShouldRecreate(value: Boolean) { | ||
| 50 | _shouldRecreate.value = value | ||
| 51 | } | ||
| 52 | |||
| 53 | fun setShouldNavigateBack(value: Boolean) { | ||
| 54 | _shouldNavigateBack.value = value | ||
| 55 | } | ||
| 56 | |||
| 57 | fun setShouldShowResetSettingsDialog(value: Boolean) { | ||
| 58 | _shouldShowResetSettingsDialog.value = value | ||
| 59 | } | ||
| 60 | |||
| 61 | fun setShouldReloadSettingsList(value: Boolean) { | ||
| 62 | _shouldReloadSettingsList.value = value | ||
| 63 | } | ||
| 64 | |||
| 65 | fun setIsUsingSearch(value: Boolean) { | ||
| 66 | _isUsingSearch.value = value | ||
| 67 | } | ||
| 68 | |||
| 69 | fun setSliderTextValue(value: Float, units: String) { | ||
| 70 | savedStateHandle[KEY_SLIDER_PROGRESS] = value | ||
| 71 | savedStateHandle[KEY_SLIDER_TEXT_VALUE] = String.format( | ||
| 72 | YuzuApplication.appContext.getString(R.string.value_with_units), | ||
| 73 | value.toInt().toString(), | ||
| 74 | units | ||
| 75 | ) | ||
| 76 | } | ||
| 77 | |||
| 78 | fun setSliderProgress(value: Float) { | ||
| 79 | savedStateHandle[KEY_SLIDER_PROGRESS] = value | ||
| 80 | } | ||
| 81 | |||
| 82 | fun setAdapterItemChanged(value: Int) { | ||
| 83 | savedStateHandle[KEY_ADAPTER_ITEM_CHANGED] = value | ||
| 84 | } | ||
| 85 | |||
| 86 | fun clear() { | ||
| 87 | game = null | ||
| 88 | shouldSave = false | ||
| 89 | } | ||
| 90 | |||
| 91 | companion object { | ||
| 92 | const val KEY_SLIDER_TEXT_VALUE = "SliderTextValue" | ||
| 93 | const val KEY_SLIDER_PROGRESS = "SliderProgress" | ||
| 94 | const val KEY_ADAPTER_ITEM_CHANGED = "AdapterItemChanged" | ||
| 95 | } | ||
| 96 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt index aaf3a0ec1..7d8e06ad8 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt | |||
| @@ -33,17 +33,15 @@ import java.io.IOException | |||
| 33 | import kotlinx.coroutines.Dispatchers | 33 | import kotlinx.coroutines.Dispatchers |
| 34 | import kotlinx.coroutines.launch | 34 | import kotlinx.coroutines.launch |
| 35 | import kotlinx.coroutines.withContext | 35 | import kotlinx.coroutines.withContext |
| 36 | import org.yuzu.yuzu_emu.HomeNavigationDirections | ||
| 36 | import org.yuzu.yuzu_emu.NativeLibrary | 37 | import org.yuzu.yuzu_emu.NativeLibrary |
| 37 | import org.yuzu.yuzu_emu.R | 38 | import org.yuzu.yuzu_emu.R |
| 38 | import org.yuzu.yuzu_emu.activities.EmulationActivity | 39 | import org.yuzu.yuzu_emu.activities.EmulationActivity |
| 39 | import org.yuzu.yuzu_emu.databinding.ActivityMainBinding | 40 | import org.yuzu.yuzu_emu.databinding.ActivityMainBinding |
| 40 | import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding | 41 | import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding |
| 41 | import org.yuzu.yuzu_emu.features.settings.model.Settings | 42 | import org.yuzu.yuzu_emu.features.settings.model.Settings |
| 42 | import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel | ||
| 43 | import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity | ||
| 44 | import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile | 43 | import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile |
| 45 | import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment | 44 | import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment |
| 46 | import org.yuzu.yuzu_emu.fragments.LongMessageDialogFragment | ||
| 47 | import org.yuzu.yuzu_emu.fragments.MessageDialogFragment | 45 | import org.yuzu.yuzu_emu.fragments.MessageDialogFragment |
| 48 | import org.yuzu.yuzu_emu.model.GamesViewModel | 46 | import org.yuzu.yuzu_emu.model.GamesViewModel |
| 49 | import org.yuzu.yuzu_emu.model.HomeViewModel | 47 | import org.yuzu.yuzu_emu.model.HomeViewModel |
| @@ -54,7 +52,6 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 54 | 52 | ||
| 55 | private val homeViewModel: HomeViewModel by viewModels() | 53 | private val homeViewModel: HomeViewModel by viewModels() |
| 56 | private val gamesViewModel: GamesViewModel by viewModels() | 54 | private val gamesViewModel: GamesViewModel by viewModels() |
| 57 | private val settingsViewModel: SettingsViewModel by viewModels() | ||
| 58 | 55 | ||
| 59 | override var themeId: Int = 0 | 56 | override var themeId: Int = 0 |
| 60 | 57 | ||
| @@ -62,8 +59,6 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 62 | val splashScreen = installSplashScreen() | 59 | val splashScreen = installSplashScreen() |
| 63 | splashScreen.setKeepOnScreenCondition { !DirectoryInitialization.areDirectoriesReady } | 60 | splashScreen.setKeepOnScreenCondition { !DirectoryInitialization.areDirectoriesReady } |
| 64 | 61 | ||
| 65 | settingsViewModel.settings.loadSettings() | ||
| 66 | |||
| 67 | ThemeHelper.setTheme(this) | 62 | ThemeHelper.setTheme(this) |
| 68 | 63 | ||
| 69 | super.onCreate(savedInstanceState) | 64 | super.onCreate(savedInstanceState) |
| @@ -109,11 +104,13 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 109 | when (it.itemId) { | 104 | when (it.itemId) { |
| 110 | R.id.gamesFragment -> gamesViewModel.setShouldScrollToTop(true) | 105 | R.id.gamesFragment -> gamesViewModel.setShouldScrollToTop(true) |
| 111 | R.id.searchFragment -> gamesViewModel.setSearchFocused(true) | 106 | R.id.searchFragment -> gamesViewModel.setSearchFocused(true) |
| 112 | R.id.homeSettingsFragment -> SettingsActivity.launch( | 107 | R.id.homeSettingsFragment -> { |
| 113 | this, | 108 | val action = HomeNavigationDirections.actionGlobalSettingsActivity( |
| 114 | SettingsFile.FILE_NAME_CONFIG, | 109 | null, |
| 115 | "" | 110 | SettingsFile.FILE_NAME_CONFIG |
| 116 | ) | 111 | ) |
| 112 | navHostFragment.navController.navigate(action) | ||
| 113 | } | ||
| 117 | } | 114 | } |
| 118 | } | 115 | } |
| 119 | 116 | ||
| @@ -303,8 +300,8 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 303 | fun processKey(result: Uri): Boolean { | 300 | fun processKey(result: Uri): Boolean { |
| 304 | if (FileUtil.getExtension(result) != "keys") { | 301 | if (FileUtil.getExtension(result) != "keys") { |
| 305 | MessageDialogFragment.newInstance( | 302 | MessageDialogFragment.newInstance( |
| 306 | R.string.reading_keys_failure, | 303 | titleId = R.string.reading_keys_failure, |
| 307 | R.string.install_prod_keys_failure_extension_description | 304 | descriptionId = R.string.install_prod_keys_failure_extension_description |
| 308 | ).show(supportFragmentManager, MessageDialogFragment.TAG) | 305 | ).show(supportFragmentManager, MessageDialogFragment.TAG) |
| 309 | return false | 306 | return false |
| 310 | } | 307 | } |
| @@ -332,9 +329,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 332 | return true | 329 | return true |
| 333 | } else { | 330 | } else { |
| 334 | MessageDialogFragment.newInstance( | 331 | MessageDialogFragment.newInstance( |
| 335 | R.string.invalid_keys_error, | 332 | titleId = R.string.invalid_keys_error, |
| 336 | R.string.install_keys_failure_description, | 333 | descriptionId = R.string.install_keys_failure_description, |
| 337 | R.string.dumping_keys_quickstart_link | 334 | helpLinkId = R.string.dumping_keys_quickstart_link |
| 338 | ).show(supportFragmentManager, MessageDialogFragment.TAG) | 335 | ).show(supportFragmentManager, MessageDialogFragment.TAG) |
| 339 | return false | 336 | return false |
| 340 | } | 337 | } |
| @@ -372,8 +369,8 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 372 | val filteredNumOfFiles = cacheFirmwareDir.list(filterNCA)?.size ?: -2 | 369 | val filteredNumOfFiles = cacheFirmwareDir.list(filterNCA)?.size ?: -2 |
| 373 | messageToShow = if (unfilteredNumOfFiles != filteredNumOfFiles) { | 370 | messageToShow = if (unfilteredNumOfFiles != filteredNumOfFiles) { |
| 374 | MessageDialogFragment.newInstance( | 371 | MessageDialogFragment.newInstance( |
| 375 | R.string.firmware_installed_failure, | 372 | titleId = R.string.firmware_installed_failure, |
| 376 | R.string.firmware_installed_failure_description | 373 | descriptionId = R.string.firmware_installed_failure_description |
| 377 | ) | 374 | ) |
| 378 | } else { | 375 | } else { |
| 379 | firmwarePath.deleteRecursively() | 376 | firmwarePath.deleteRecursively() |
| @@ -403,8 +400,8 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 403 | 400 | ||
| 404 | if (FileUtil.getExtension(result) != "bin") { | 401 | if (FileUtil.getExtension(result) != "bin") { |
| 405 | MessageDialogFragment.newInstance( | 402 | MessageDialogFragment.newInstance( |
| 406 | R.string.reading_keys_failure, | 403 | titleId = R.string.reading_keys_failure, |
| 407 | R.string.install_amiibo_keys_failure_extension_description | 404 | descriptionId = R.string.install_amiibo_keys_failure_extension_description |
| 408 | ).show(supportFragmentManager, MessageDialogFragment.TAG) | 405 | ).show(supportFragmentManager, MessageDialogFragment.TAG) |
| 409 | return@registerForActivityResult | 406 | return@registerForActivityResult |
| 410 | } | 407 | } |
| @@ -430,9 +427,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 430 | ).show() | 427 | ).show() |
| 431 | } else { | 428 | } else { |
| 432 | MessageDialogFragment.newInstance( | 429 | MessageDialogFragment.newInstance( |
| 433 | R.string.invalid_keys_error, | 430 | titleId = R.string.invalid_keys_error, |
| 434 | R.string.install_keys_failure_description, | 431 | descriptionId = R.string.install_keys_failure_description, |
| 435 | R.string.dumping_keys_quickstart_link | 432 | helpLinkId = R.string.dumping_keys_quickstart_link |
| 436 | ).show(supportFragmentManager, MessageDialogFragment.TAG) | 433 | ).show(supportFragmentManager, MessageDialogFragment.TAG) |
| 437 | } | 434 | } |
| 438 | } | 435 | } |
| @@ -504,96 +501,91 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 504 | var errorBaseGame = 0 | 501 | var errorBaseGame = 0 |
| 505 | var errorExtension = 0 | 502 | var errorExtension = 0 |
| 506 | var errorOther = 0 | 503 | var errorOther = 0 |
| 507 | var errorTotal = 0 | 504 | documents.forEach { |
| 508 | lifecycleScope.launch { | 505 | when (NativeLibrary.installFileToNand(it.toString())) { |
| 509 | documents.forEach { | 506 | NativeLibrary.InstallFileToNandResult.Success -> { |
| 510 | when (NativeLibrary.installFileToNand(it.toString())) { | 507 | installSuccess += 1 |
| 511 | NativeLibrary.InstallFileToNandResult.Success -> { | ||
| 512 | installSuccess += 1 | ||
| 513 | } | ||
| 514 | |||
| 515 | NativeLibrary.InstallFileToNandResult.SuccessFileOverwritten -> { | ||
| 516 | installOverwrite += 1 | ||
| 517 | } | ||
| 518 | |||
| 519 | NativeLibrary.InstallFileToNandResult.ErrorBaseGame -> { | ||
| 520 | errorBaseGame += 1 | ||
| 521 | } | ||
| 522 | |||
| 523 | NativeLibrary.InstallFileToNandResult.ErrorFilenameExtension -> { | ||
| 524 | errorExtension += 1 | ||
| 525 | } | ||
| 526 | |||
| 527 | else -> { | ||
| 528 | errorOther += 1 | ||
| 529 | } | ||
| 530 | } | 508 | } |
| 531 | } | 509 | |
| 532 | withContext(Dispatchers.Main) { | 510 | NativeLibrary.InstallFileToNandResult.SuccessFileOverwritten -> { |
| 533 | val separator = System.getProperty("line.separator") ?: "\n" | 511 | installOverwrite += 1 |
| 534 | val installResult = StringBuilder() | ||
| 535 | if (installSuccess > 0) { | ||
| 536 | installResult.append( | ||
| 537 | getString( | ||
| 538 | R.string.install_game_content_success_install, | ||
| 539 | installSuccess | ||
| 540 | ) | ||
| 541 | ) | ||
| 542 | installResult.append(separator) | ||
| 543 | } | 512 | } |
| 544 | if (installOverwrite > 0) { | 513 | |
| 545 | installResult.append( | 514 | NativeLibrary.InstallFileToNandResult.ErrorBaseGame -> { |
| 546 | getString( | 515 | errorBaseGame += 1 |
| 547 | R.string.install_game_content_success_overwrite, | ||
| 548 | installOverwrite | ||
| 549 | ) | ||
| 550 | ) | ||
| 551 | installResult.append(separator) | ||
| 552 | } | 516 | } |
| 553 | errorTotal = errorBaseGame + errorExtension + errorOther | 517 | |
| 554 | if (errorTotal > 0) { | 518 | NativeLibrary.InstallFileToNandResult.ErrorFilenameExtension -> { |
| 555 | installResult.append(separator) | 519 | errorExtension += 1 |
| 556 | installResult.append( | 520 | } |
| 557 | getString( | 521 | |
| 558 | R.string.install_game_content_failed_count, | 522 | else -> { |
| 559 | errorTotal | 523 | errorOther += 1 |
| 560 | ) | ||
| 561 | ) | ||
| 562 | installResult.append(separator) | ||
| 563 | if (errorBaseGame > 0) { | ||
| 564 | installResult.append(separator) | ||
| 565 | installResult.append( | ||
| 566 | getString(R.string.install_game_content_failure_base) | ||
| 567 | ) | ||
| 568 | installResult.append(separator) | ||
| 569 | } | ||
| 570 | if (errorExtension > 0) { | ||
| 571 | installResult.append(separator) | ||
| 572 | installResult.append( | ||
| 573 | getString(R.string.install_game_content_failure_file_extension) | ||
| 574 | ) | ||
| 575 | installResult.append(separator) | ||
| 576 | } | ||
| 577 | if (errorOther > 0) { | ||
| 578 | installResult.append( | ||
| 579 | getString(R.string.install_game_content_failure_description) | ||
| 580 | ) | ||
| 581 | installResult.append(separator) | ||
| 582 | } | ||
| 583 | LongMessageDialogFragment.newInstance( | ||
| 584 | R.string.install_game_content_failure, | ||
| 585 | installResult.toString().trim(), | ||
| 586 | R.string.install_game_content_help_link | ||
| 587 | ).show(supportFragmentManager, LongMessageDialogFragment.TAG) | ||
| 588 | } else { | ||
| 589 | LongMessageDialogFragment.newInstance( | ||
| 590 | R.string.install_game_content_success, | ||
| 591 | installResult.toString().trim() | ||
| 592 | ).show(supportFragmentManager, LongMessageDialogFragment.TAG) | ||
| 593 | } | 524 | } |
| 594 | } | 525 | } |
| 595 | } | 526 | } |
| 596 | return@newInstance installSuccess + installOverwrite + errorTotal | 527 | |
| 528 | val separator = System.getProperty("line.separator") ?: "\n" | ||
| 529 | val installResult = StringBuilder() | ||
| 530 | if (installSuccess > 0) { | ||
| 531 | installResult.append( | ||
| 532 | getString( | ||
| 533 | R.string.install_game_content_success_install, | ||
| 534 | installSuccess | ||
| 535 | ) | ||
| 536 | ) | ||
| 537 | installResult.append(separator) | ||
| 538 | } | ||
| 539 | if (installOverwrite > 0) { | ||
| 540 | installResult.append( | ||
| 541 | getString( | ||
| 542 | R.string.install_game_content_success_overwrite, | ||
| 543 | installOverwrite | ||
| 544 | ) | ||
| 545 | ) | ||
| 546 | installResult.append(separator) | ||
| 547 | } | ||
| 548 | val errorTotal: Int = errorBaseGame + errorExtension + errorOther | ||
| 549 | if (errorTotal > 0) { | ||
| 550 | installResult.append(separator) | ||
| 551 | installResult.append( | ||
| 552 | getString( | ||
| 553 | R.string.install_game_content_failed_count, | ||
| 554 | errorTotal | ||
| 555 | ) | ||
| 556 | ) | ||
| 557 | installResult.append(separator) | ||
| 558 | if (errorBaseGame > 0) { | ||
| 559 | installResult.append(separator) | ||
| 560 | installResult.append( | ||
| 561 | getString(R.string.install_game_content_failure_base) | ||
| 562 | ) | ||
| 563 | installResult.append(separator) | ||
| 564 | } | ||
| 565 | if (errorExtension > 0) { | ||
| 566 | installResult.append(separator) | ||
| 567 | installResult.append( | ||
| 568 | getString(R.string.install_game_content_failure_file_extension) | ||
| 569 | ) | ||
| 570 | installResult.append(separator) | ||
| 571 | } | ||
| 572 | if (errorOther > 0) { | ||
| 573 | installResult.append( | ||
| 574 | getString(R.string.install_game_content_failure_description) | ||
| 575 | ) | ||
| 576 | installResult.append(separator) | ||
| 577 | } | ||
| 578 | return@newInstance MessageDialogFragment.newInstance( | ||
| 579 | titleId = R.string.install_game_content_failure, | ||
| 580 | descriptionString = installResult.toString().trim(), | ||
| 581 | helpLinkId = R.string.install_game_content_help_link | ||
| 582 | ) | ||
| 583 | } else { | ||
| 584 | return@newInstance MessageDialogFragment.newInstance( | ||
| 585 | titleId = R.string.install_game_content_success, | ||
| 586 | descriptionString = installResult.toString().trim() | ||
| 587 | ) | ||
| 588 | } | ||
| 597 | }.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG) | 589 | }.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG) |
| 598 | } | 590 | } |
| 599 | } | 591 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/BiMap.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/BiMap.kt deleted file mode 100644 index 9cfda74ee..000000000 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/BiMap.kt +++ /dev/null | |||
| @@ -1,25 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | package org.yuzu.yuzu_emu.utils | ||
| 5 | |||
| 6 | class BiMap<K, V> { | ||
| 7 | private val forward: MutableMap<K, V> = HashMap() | ||
| 8 | private val backward: MutableMap<V, K> = HashMap() | ||
| 9 | |||
| 10 | @Synchronized | ||
| 11 | fun add(key: K, value: V) { | ||
| 12 | forward[key] = value | ||
| 13 | backward[value] = key | ||
| 14 | } | ||
| 15 | |||
| 16 | @Synchronized | ||
| 17 | fun getForward(key: K): V? { | ||
| 18 | return forward[key] | ||
| 19 | } | ||
| 20 | |||
| 21 | @Synchronized | ||
| 22 | fun getBackward(key: V): K? { | ||
| 23 | return backward[key] | ||
| 24 | } | ||
| 25 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt index 2ee63697e..3c9f6bad0 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt | |||
| @@ -3,18 +3,18 @@ | |||
| 3 | 3 | ||
| 4 | package org.yuzu.yuzu_emu.utils | 4 | package org.yuzu.yuzu_emu.utils |
| 5 | 5 | ||
| 6 | import android.content.Context | ||
| 7 | import java.io.IOException | 6 | import java.io.IOException |
| 8 | import org.yuzu.yuzu_emu.NativeLibrary | 7 | import org.yuzu.yuzu_emu.NativeLibrary |
| 8 | import org.yuzu.yuzu_emu.YuzuApplication | ||
| 9 | 9 | ||
| 10 | object DirectoryInitialization { | 10 | object DirectoryInitialization { |
| 11 | private var userPath: String? = null | 11 | private var userPath: String? = null |
| 12 | 12 | ||
| 13 | var areDirectoriesReady: Boolean = false | 13 | var areDirectoriesReady: Boolean = false |
| 14 | 14 | ||
| 15 | fun start(context: Context) { | 15 | fun start() { |
| 16 | if (!areDirectoriesReady) { | 16 | if (!areDirectoriesReady) { |
| 17 | initializeInternalStorage(context) | 17 | initializeInternalStorage() |
| 18 | NativeLibrary.initializeEmulation() | 18 | NativeLibrary.initializeEmulation() |
| 19 | areDirectoriesReady = true | 19 | areDirectoriesReady = true |
| 20 | } | 20 | } |
| @@ -26,9 +26,9 @@ object DirectoryInitialization { | |||
| 26 | return userPath | 26 | return userPath |
| 27 | } | 27 | } |
| 28 | 28 | ||
| 29 | private fun initializeInternalStorage(context: Context) { | 29 | private fun initializeInternalStorage() { |
| 30 | try { | 30 | try { |
| 31 | userPath = context.getExternalFilesDir(null)!!.canonicalPath | 31 | userPath = YuzuApplication.appContext.getExternalFilesDir(null)!!.canonicalPath |
| 32 | NativeLibrary.setAppDirectory(userPath!!) | 32 | NativeLibrary.setAppDirectory(userPath!!) |
| 33 | } catch (e: IOException) { | 33 | } catch (e: IOException) { |
| 34 | e.printStackTrace() | 34 | e.printStackTrace() |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt index f71d0a098..e0ee29c9b 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt | |||
| @@ -63,13 +63,13 @@ object GameHelper { | |||
| 63 | ) | 63 | ) |
| 64 | } else { | 64 | } else { |
| 65 | if (Game.extensions.contains(FileUtil.getExtension(it.uri))) { | 65 | if (Game.extensions.contains(FileUtil.getExtension(it.uri))) { |
| 66 | games.add(getGame(it.uri)) | 66 | games.add(getGame(it.uri, true)) |
| 67 | } | 67 | } |
| 68 | } | 68 | } |
| 69 | } | 69 | } |
| 70 | } | 70 | } |
| 71 | 71 | ||
| 72 | private fun getGame(uri: Uri): Game { | 72 | fun getGame(uri: Uri, addedToLibrary: Boolean): Game { |
| 73 | val filePath = uri.toString() | 73 | val filePath = uri.toString() |
| 74 | var name = NativeLibrary.getTitle(filePath) | 74 | var name = NativeLibrary.getTitle(filePath) |
| 75 | 75 | ||
| @@ -94,11 +94,13 @@ object GameHelper { | |||
| 94 | NativeLibrary.isHomebrew(filePath) | 94 | NativeLibrary.isHomebrew(filePath) |
| 95 | ) | 95 | ) |
| 96 | 96 | ||
| 97 | val addedTime = preferences.getLong(newGame.keyAddedToLibraryTime, 0L) | 97 | if (addedToLibrary) { |
| 98 | if (addedTime == 0L) { | 98 | val addedTime = preferences.getLong(newGame.keyAddedToLibraryTime, 0L) |
| 99 | preferences.edit() | 99 | if (addedTime == 0L) { |
| 100 | .putLong(newGame.keyAddedToLibraryTime, System.currentTimeMillis()) | 100 | preferences.edit() |
| 101 | .apply() | 101 | .putLong(newGame.keyAddedToLibraryTime, System.currentTimeMillis()) |
| 102 | .apply() | ||
| 103 | } | ||
| 102 | } | 104 | } |
| 103 | 105 | ||
| 104 | return newGame | 106 | return newGame |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconUtils.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconUtils.kt new file mode 100644 index 000000000..c0fe596d7 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconUtils.kt | |||
| @@ -0,0 +1,77 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | package org.yuzu.yuzu_emu.utils | ||
| 5 | |||
| 6 | import android.graphics.Bitmap | ||
| 7 | import android.graphics.BitmapFactory | ||
| 8 | import android.widget.ImageView | ||
| 9 | import androidx.core.graphics.drawable.toDrawable | ||
| 10 | import coil.ImageLoader | ||
| 11 | import coil.decode.DataSource | ||
| 12 | import coil.fetch.DrawableResult | ||
| 13 | import coil.fetch.FetchResult | ||
| 14 | import coil.fetch.Fetcher | ||
| 15 | import coil.key.Keyer | ||
| 16 | import coil.memory.MemoryCache | ||
| 17 | import coil.request.ImageRequest | ||
| 18 | import coil.request.Options | ||
| 19 | import org.yuzu.yuzu_emu.NativeLibrary | ||
| 20 | import org.yuzu.yuzu_emu.R | ||
| 21 | import org.yuzu.yuzu_emu.YuzuApplication | ||
| 22 | import org.yuzu.yuzu_emu.model.Game | ||
| 23 | |||
| 24 | class GameIconFetcher( | ||
| 25 | private val game: Game, | ||
| 26 | private val options: Options | ||
| 27 | ) : Fetcher { | ||
| 28 | override suspend fun fetch(): FetchResult { | ||
| 29 | return DrawableResult( | ||
| 30 | drawable = decodeGameIcon(game.path)!!.toDrawable(options.context.resources), | ||
| 31 | isSampled = false, | ||
| 32 | dataSource = DataSource.DISK | ||
| 33 | ) | ||
| 34 | } | ||
| 35 | |||
| 36 | private fun decodeGameIcon(uri: String): Bitmap? { | ||
| 37 | val data = NativeLibrary.getIcon(uri) | ||
| 38 | return BitmapFactory.decodeByteArray( | ||
| 39 | data, | ||
| 40 | 0, | ||
| 41 | data.size, | ||
| 42 | BitmapFactory.Options() | ||
| 43 | ) | ||
| 44 | } | ||
| 45 | |||
| 46 | class Factory : Fetcher.Factory<Game> { | ||
| 47 | override fun create(data: Game, options: Options, imageLoader: ImageLoader): Fetcher = | ||
| 48 | GameIconFetcher(data, options) | ||
| 49 | } | ||
| 50 | } | ||
| 51 | |||
| 52 | class GameIconKeyer : Keyer<Game> { | ||
| 53 | override fun key(data: Game, options: Options): String = data.path | ||
| 54 | } | ||
| 55 | |||
| 56 | object GameIconUtils { | ||
| 57 | private val imageLoader = ImageLoader.Builder(YuzuApplication.appContext) | ||
| 58 | .components { | ||
| 59 | add(GameIconKeyer()) | ||
| 60 | add(GameIconFetcher.Factory()) | ||
| 61 | } | ||
| 62 | .memoryCache { | ||
| 63 | MemoryCache.Builder(YuzuApplication.appContext) | ||
| 64 | .maxSizePercent(0.25) | ||
| 65 | .build() | ||
| 66 | } | ||
| 67 | .build() | ||
| 68 | |||
| 69 | fun loadGameIcon(game: Game, imageView: ImageView) { | ||
| 70 | val request = ImageRequest.Builder(YuzuApplication.appContext) | ||
| 71 | .data(game) | ||
| 72 | .target(imageView) | ||
| 73 | .error(R.drawable.default_icon) | ||
| 74 | .build() | ||
| 75 | imageLoader.enqueue(request) | ||
| 76 | } | ||
| 77 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt new file mode 100644 index 000000000..9425f8b99 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt | |||
| @@ -0,0 +1,33 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | package org.yuzu.yuzu_emu.utils | ||
| 5 | |||
| 6 | object NativeConfig { | ||
| 7 | external fun getBoolean(key: String, getDefault: Boolean): Boolean | ||
| 8 | external fun setBoolean(key: String, value: Boolean) | ||
| 9 | |||
| 10 | external fun getByte(key: String, getDefault: Boolean): Byte | ||
| 11 | external fun setByte(key: String, value: Byte) | ||
| 12 | |||
| 13 | external fun getShort(key: String, getDefault: Boolean): Short | ||
| 14 | external fun setShort(key: String, value: Short) | ||
| 15 | |||
| 16 | external fun getInt(key: String, getDefault: Boolean): Int | ||
| 17 | external fun setInt(key: String, value: Int) | ||
| 18 | |||
| 19 | external fun getFloat(key: String, getDefault: Boolean): Float | ||
| 20 | external fun setFloat(key: String, value: Float) | ||
| 21 | |||
| 22 | external fun getLong(key: String, getDefault: Boolean): Long | ||
| 23 | external fun setLong(key: String, value: Long) | ||
| 24 | |||
| 25 | external fun getString(key: String, getDefault: Boolean): String | ||
| 26 | external fun setString(key: String, value: String) | ||
| 27 | |||
| 28 | external fun getIsRuntimeModifiable(key: String): Boolean | ||
| 29 | |||
| 30 | external fun getConfigHeader(category: Int): String | ||
| 31 | |||
| 32 | external fun getPairedSettingKey(key: String): String | ||
| 33 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/views/FixedRatioSurfaceView.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/views/FixedRatioSurfaceView.kt index 685ccaa76..2f0868c63 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/views/FixedRatioSurfaceView.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/views/FixedRatioSurfaceView.kt | |||
| @@ -7,7 +7,6 @@ import android.content.Context | |||
| 7 | import android.util.AttributeSet | 7 | import android.util.AttributeSet |
| 8 | import android.util.Rational | 8 | import android.util.Rational |
| 9 | import android.view.SurfaceView | 9 | import android.view.SurfaceView |
| 10 | import kotlin.math.roundToInt | ||
| 11 | 10 | ||
| 12 | class FixedRatioSurfaceView @JvmOverloads constructor( | 11 | class FixedRatioSurfaceView @JvmOverloads constructor( |
| 13 | context: Context, | 12 | context: Context, |
| @@ -22,27 +21,44 @@ class FixedRatioSurfaceView @JvmOverloads constructor( | |||
| 22 | */ | 21 | */ |
| 23 | fun setAspectRatio(ratio: Rational?) { | 22 | fun setAspectRatio(ratio: Rational?) { |
| 24 | aspectRatio = ratio?.toFloat() ?: 0f | 23 | aspectRatio = ratio?.toFloat() ?: 0f |
| 24 | requestLayout() | ||
| 25 | } | 25 | } |
| 26 | 26 | ||
| 27 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { | 27 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { |
| 28 | super.onMeasure(widthMeasureSpec, heightMeasureSpec) | 28 | val displayWidth: Float = MeasureSpec.getSize(widthMeasureSpec).toFloat() |
| 29 | val width = MeasureSpec.getSize(widthMeasureSpec) | 29 | val displayHeight: Float = MeasureSpec.getSize(heightMeasureSpec).toFloat() |
| 30 | val height = MeasureSpec.getSize(heightMeasureSpec) | ||
| 31 | if (aspectRatio != 0f) { | 30 | if (aspectRatio != 0f) { |
| 32 | val newWidth: Int | 31 | val displayAspect = displayWidth / displayHeight |
| 33 | val newHeight: Int | 32 | if (displayAspect < aspectRatio) { |
| 34 | if (height * aspectRatio < width) { | 33 | // Max out width |
| 35 | newWidth = (height * aspectRatio).roundToInt() | 34 | val halfHeight = displayHeight / 2 |
| 36 | newHeight = height | 35 | val surfaceHeight = displayWidth / aspectRatio |
| 36 | val newTop: Float = halfHeight - (surfaceHeight / 2) | ||
| 37 | val newBottom: Float = halfHeight + (surfaceHeight / 2) | ||
| 38 | super.onMeasure( | ||
| 39 | widthMeasureSpec, | ||
| 40 | MeasureSpec.makeMeasureSpec( | ||
| 41 | newBottom.toInt() - newTop.toInt(), | ||
| 42 | MeasureSpec.EXACTLY | ||
| 43 | ) | ||
| 44 | ) | ||
| 45 | return | ||
| 37 | } else { | 46 | } else { |
| 38 | newWidth = width | 47 | // Max out height |
| 39 | newHeight = (width / aspectRatio).roundToInt() | 48 | val halfWidth = displayWidth / 2 |
| 49 | val surfaceWidth = displayHeight * aspectRatio | ||
| 50 | val newLeft: Float = halfWidth - (surfaceWidth / 2) | ||
| 51 | val newRight: Float = halfWidth + (surfaceWidth / 2) | ||
| 52 | super.onMeasure( | ||
| 53 | MeasureSpec.makeMeasureSpec( | ||
| 54 | newRight.toInt() - newLeft.toInt(), | ||
| 55 | MeasureSpec.EXACTLY | ||
| 56 | ), | ||
| 57 | heightMeasureSpec | ||
| 58 | ) | ||
| 59 | return | ||
| 40 | } | 60 | } |
| 41 | val left = (width - newWidth) / 2 | ||
| 42 | val top = (height - newHeight) / 2 | ||
| 43 | setLeftTopRightBottom(left, top, left + newWidth, top + newHeight) | ||
| 44 | } else { | ||
| 45 | setLeftTopRightBottom(0, 0, width, height) | ||
| 46 | } | 61 | } |
| 62 | super.onMeasure(widthMeasureSpec, heightMeasureSpec) | ||
| 47 | } | 63 | } |
| 48 | } | 64 | } |
diff --git a/src/android/app/src/main/jni/CMakeLists.txt b/src/android/app/src/main/jni/CMakeLists.txt index e2ed08e9f..e15d1480b 100644 --- a/src/android/app/src/main/jni/CMakeLists.txt +++ b/src/android/app/src/main/jni/CMakeLists.txt | |||
| @@ -14,6 +14,8 @@ add_library(yuzu-android SHARED | |||
| 14 | id_cache.cpp | 14 | id_cache.cpp |
| 15 | id_cache.h | 15 | id_cache.h |
| 16 | native.cpp | 16 | native.cpp |
| 17 | native_config.cpp | ||
| 18 | uisettings.cpp | ||
| 17 | ) | 19 | ) |
| 18 | 20 | ||
| 19 | set_property(TARGET yuzu-android PROPERTY IMPORTED_LOCATION ${FFmpeg_LIBRARY_DIR}) | 21 | set_property(TARGET yuzu-android PROPERTY IMPORTED_LOCATION ${FFmpeg_LIBRARY_DIR}) |
diff --git a/src/android/app/src/main/jni/config.cpp b/src/android/app/src/main/jni/config.cpp index 9de9bd93e..34b425cb4 100644 --- a/src/android/app/src/main/jni/config.cpp +++ b/src/android/app/src/main/jni/config.cpp | |||
| @@ -16,18 +16,20 @@ | |||
| 16 | #include "input_common/main.h" | 16 | #include "input_common/main.h" |
| 17 | #include "jni/config.h" | 17 | #include "jni/config.h" |
| 18 | #include "jni/default_ini.h" | 18 | #include "jni/default_ini.h" |
| 19 | #include "uisettings.h" | ||
| 19 | 20 | ||
| 20 | namespace FS = Common::FS; | 21 | namespace FS = Common::FS; |
| 21 | 22 | ||
| 22 | Config::Config(std::optional<std::filesystem::path> config_path) | 23 | Config::Config(const std::string& config_name, ConfigType config_type) |
| 23 | : config_loc{config_path.value_or(FS::GetYuzuPath(FS::YuzuPath::ConfigDir) / "config.ini")}, | 24 | : type(config_type), global{config_type == ConfigType::GlobalConfig} { |
| 24 | config{std::make_unique<INIReader>(FS::PathToUTF8String(config_loc))} { | 25 | Initialize(config_name); |
| 25 | Reload(); | ||
| 26 | } | 26 | } |
| 27 | 27 | ||
| 28 | Config::~Config() = default; | 28 | Config::~Config() = default; |
| 29 | 29 | ||
| 30 | bool Config::LoadINI(const std::string& default_contents, bool retry) { | 30 | bool Config::LoadINI(const std::string& default_contents, bool retry) { |
| 31 | void(FS::CreateParentDir(config_loc)); | ||
| 32 | config = std::make_unique<INIReader>(FS::PathToUTF8String(config_loc)); | ||
| 31 | const auto config_loc_str = FS::PathToUTF8String(config_loc); | 33 | const auto config_loc_str = FS::PathToUTF8String(config_loc); |
| 32 | if (config->ParseError() < 0) { | 34 | if (config->ParseError() < 0) { |
| 33 | if (retry) { | 35 | if (retry) { |
| @@ -301,9 +303,28 @@ void Config::ReadValues() { | |||
| 301 | 303 | ||
| 302 | // Network | 304 | // Network |
| 303 | ReadSetting("Network", Settings::values.network_interface); | 305 | ReadSetting("Network", Settings::values.network_interface); |
| 306 | |||
| 307 | // Android | ||
| 308 | ReadSetting("Android", AndroidSettings::values.picture_in_picture); | ||
| 309 | ReadSetting("Android", AndroidSettings::values.screen_layout); | ||
| 304 | } | 310 | } |
| 305 | 311 | ||
| 306 | void Config::Reload() { | 312 | void Config::Initialize(const std::string& config_name) { |
| 313 | const auto fs_config_loc = FS::GetYuzuPath(FS::YuzuPath::ConfigDir); | ||
| 314 | const auto config_file = fmt::format("{}.ini", config_name); | ||
| 315 | |||
| 316 | switch (type) { | ||
| 317 | case ConfigType::GlobalConfig: | ||
| 318 | config_loc = FS::PathToUTF8String(fs_config_loc / config_file); | ||
| 319 | break; | ||
| 320 | case ConfigType::PerGameConfig: | ||
| 321 | config_loc = FS::PathToUTF8String(fs_config_loc / "custom" / FS::ToU8String(config_file)); | ||
| 322 | break; | ||
| 323 | case ConfigType::InputProfile: | ||
| 324 | config_loc = FS::PathToUTF8String(fs_config_loc / "input" / config_file); | ||
| 325 | LoadINI(DefaultINI::android_config_file); | ||
| 326 | return; | ||
| 327 | } | ||
| 307 | LoadINI(DefaultINI::android_config_file); | 328 | LoadINI(DefaultINI::android_config_file); |
| 308 | ReadValues(); | 329 | ReadValues(); |
| 309 | } | 330 | } |
diff --git a/src/android/app/src/main/jni/config.h b/src/android/app/src/main/jni/config.h index 0d7d6e94d..e1e8f47ed 100644 --- a/src/android/app/src/main/jni/config.h +++ b/src/android/app/src/main/jni/config.h | |||
| @@ -13,25 +13,35 @@ | |||
| 13 | class INIReader; | 13 | class INIReader; |
| 14 | 14 | ||
| 15 | class Config { | 15 | class Config { |
| 16 | std::filesystem::path config_loc; | ||
| 17 | std::unique_ptr<INIReader> config; | ||
| 18 | |||
| 19 | bool LoadINI(const std::string& default_contents = "", bool retry = true); | 16 | bool LoadINI(const std::string& default_contents = "", bool retry = true); |
| 20 | void ReadValues(); | ||
| 21 | 17 | ||
| 22 | public: | 18 | public: |
| 23 | explicit Config(std::optional<std::filesystem::path> config_path = std::nullopt); | 19 | enum class ConfigType { |
| 20 | GlobalConfig, | ||
| 21 | PerGameConfig, | ||
| 22 | InputProfile, | ||
| 23 | }; | ||
| 24 | |||
| 25 | explicit Config(const std::string& config_name = "config", | ||
| 26 | ConfigType config_type = ConfigType::GlobalConfig); | ||
| 24 | ~Config(); | 27 | ~Config(); |
| 25 | 28 | ||
| 26 | void Reload(); | 29 | void Initialize(const std::string& config_name); |
| 27 | 30 | ||
| 28 | private: | 31 | private: |
| 29 | /** | 32 | /** |
| 30 | * Applies a value read from the sdl2_config to a Setting. | 33 | * Applies a value read from the config to a Setting. |
| 31 | * | 34 | * |
| 32 | * @param group The name of the INI group | 35 | * @param group The name of the INI group |
| 33 | * @param setting The yuzu setting to modify | 36 | * @param setting The yuzu setting to modify |
| 34 | */ | 37 | */ |
| 35 | template <typename Type, bool ranged> | 38 | template <typename Type, bool ranged> |
| 36 | void ReadSetting(const std::string& group, Settings::Setting<Type, ranged>& setting); | 39 | void ReadSetting(const std::string& group, Settings::Setting<Type, ranged>& setting); |
| 40 | |||
| 41 | void ReadValues(); | ||
| 42 | |||
| 43 | const ConfigType type; | ||
| 44 | std::unique_ptr<INIReader> config; | ||
| 45 | std::string config_loc; | ||
| 46 | const bool global; | ||
| 37 | }; | 47 | }; |
diff --git a/src/android/app/src/main/jni/id_cache.cpp b/src/android/app/src/main/jni/id_cache.cpp index 9cbbf23a3..960abf95a 100644 --- a/src/android/app/src/main/jni/id_cache.cpp +++ b/src/android/app/src/main/jni/id_cache.cpp | |||
| @@ -15,6 +15,8 @@ static jclass s_disk_cache_progress_class; | |||
| 15 | static jclass s_load_callback_stage_class; | 15 | static jclass s_load_callback_stage_class; |
| 16 | static jmethodID s_exit_emulation_activity; | 16 | static jmethodID s_exit_emulation_activity; |
| 17 | static jmethodID s_disk_cache_load_progress; | 17 | static jmethodID s_disk_cache_load_progress; |
| 18 | static jmethodID s_on_emulation_started; | ||
| 19 | static jmethodID s_on_emulation_stopped; | ||
| 18 | 20 | ||
| 19 | static constexpr jint JNI_VERSION = JNI_VERSION_1_6; | 21 | static constexpr jint JNI_VERSION = JNI_VERSION_1_6; |
| 20 | 22 | ||
| @@ -59,6 +61,14 @@ jmethodID GetDiskCacheLoadProgress() { | |||
| 59 | return s_disk_cache_load_progress; | 61 | return s_disk_cache_load_progress; |
| 60 | } | 62 | } |
| 61 | 63 | ||
| 64 | jmethodID GetOnEmulationStarted() { | ||
| 65 | return s_on_emulation_started; | ||
| 66 | } | ||
| 67 | |||
| 68 | jmethodID GetOnEmulationStopped() { | ||
| 69 | return s_on_emulation_stopped; | ||
| 70 | } | ||
| 71 | |||
| 62 | } // namespace IDCache | 72 | } // namespace IDCache |
| 63 | 73 | ||
| 64 | #ifdef __cplusplus | 74 | #ifdef __cplusplus |
| @@ -85,6 +95,10 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) { | |||
| 85 | env->GetStaticMethodID(s_native_library_class, "exitEmulationActivity", "(I)V"); | 95 | env->GetStaticMethodID(s_native_library_class, "exitEmulationActivity", "(I)V"); |
| 86 | s_disk_cache_load_progress = | 96 | s_disk_cache_load_progress = |
| 87 | env->GetStaticMethodID(s_disk_cache_progress_class, "loadProgress", "(III)V"); | 97 | env->GetStaticMethodID(s_disk_cache_progress_class, "loadProgress", "(III)V"); |
| 98 | s_on_emulation_started = | ||
| 99 | env->GetStaticMethodID(s_native_library_class, "onEmulationStarted", "()V"); | ||
| 100 | s_on_emulation_stopped = | ||
| 101 | env->GetStaticMethodID(s_native_library_class, "onEmulationStopped", "(I)V"); | ||
| 88 | 102 | ||
| 89 | // Initialize Android Storage | 103 | // Initialize Android Storage |
| 90 | Common::FS::Android::RegisterCallbacks(env, s_native_library_class); | 104 | Common::FS::Android::RegisterCallbacks(env, s_native_library_class); |
diff --git a/src/android/app/src/main/jni/id_cache.h b/src/android/app/src/main/jni/id_cache.h index be535fe1e..b76158928 100644 --- a/src/android/app/src/main/jni/id_cache.h +++ b/src/android/app/src/main/jni/id_cache.h | |||
| @@ -15,5 +15,7 @@ jclass GetDiskCacheProgressClass(); | |||
| 15 | jclass GetDiskCacheLoadCallbackStageClass(); | 15 | jclass GetDiskCacheLoadCallbackStageClass(); |
| 16 | jmethodID GetExitEmulationActivity(); | 16 | jmethodID GetExitEmulationActivity(); |
| 17 | jmethodID GetDiskCacheLoadProgress(); | 17 | jmethodID GetDiskCacheLoadProgress(); |
| 18 | jmethodID GetOnEmulationStarted(); | ||
| 19 | jmethodID GetOnEmulationStopped(); | ||
| 18 | 20 | ||
| 19 | } // namespace IDCache | 21 | } // namespace IDCache |
diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index 7e17833a0..0f2a6d9e4 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp | |||
| @@ -203,12 +203,10 @@ public: | |||
| 203 | } | 203 | } |
| 204 | 204 | ||
| 205 | bool IsRunning() const { | 205 | bool IsRunning() const { |
| 206 | std::scoped_lock lock(m_mutex); | ||
| 207 | return m_is_running; | 206 | return m_is_running; |
| 208 | } | 207 | } |
| 209 | 208 | ||
| 210 | bool IsPaused() const { | 209 | bool IsPaused() const { |
| 211 | std::scoped_lock lock(m_mutex); | ||
| 212 | return m_is_running && m_is_paused; | 210 | return m_is_running && m_is_paused; |
| 213 | } | 211 | } |
| 214 | 212 | ||
| @@ -335,6 +333,8 @@ public: | |||
| 335 | 333 | ||
| 336 | // Tear down the render window. | 334 | // Tear down the render window. |
| 337 | m_window.reset(); | 335 | m_window.reset(); |
| 336 | |||
| 337 | OnEmulationStopped(m_load_result); | ||
| 338 | } | 338 | } |
| 339 | 339 | ||
| 340 | void PauseEmulation() { | 340 | void PauseEmulation() { |
| @@ -376,6 +376,8 @@ public: | |||
| 376 | m_system.InitializeDebugger(); | 376 | m_system.InitializeDebugger(); |
| 377 | } | 377 | } |
| 378 | 378 | ||
| 379 | OnEmulationStarted(); | ||
| 380 | |||
| 379 | while (true) { | 381 | while (true) { |
| 380 | { | 382 | { |
| 381 | [[maybe_unused]] std::unique_lock lock(m_mutex); | 383 | [[maybe_unused]] std::unique_lock lock(m_mutex); |
| @@ -511,6 +513,18 @@ private: | |||
| 511 | static_cast<jint>(progress), static_cast<jint>(max)); | 513 | static_cast<jint>(progress), static_cast<jint>(max)); |
| 512 | } | 514 | } |
| 513 | 515 | ||
| 516 | static void OnEmulationStarted() { | ||
| 517 | JNIEnv* env = IDCache::GetEnvForThread(); | ||
| 518 | env->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(), | ||
| 519 | IDCache::GetOnEmulationStarted()); | ||
| 520 | } | ||
| 521 | |||
| 522 | static void OnEmulationStopped(Core::SystemResultStatus result) { | ||
| 523 | JNIEnv* env = IDCache::GetEnvForThread(); | ||
| 524 | env->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(), | ||
| 525 | IDCache::GetOnEmulationStopped(), static_cast<jint>(result)); | ||
| 526 | } | ||
| 527 | |||
| 514 | private: | 528 | private: |
| 515 | static EmulationSession s_instance; | 529 | static EmulationSession s_instance; |
| 516 | 530 | ||
| @@ -528,8 +542,8 @@ private: | |||
| 528 | Core::PerfStatsResults m_perf_stats{}; | 542 | Core::PerfStatsResults m_perf_stats{}; |
| 529 | std::shared_ptr<FileSys::VfsFilesystem> m_vfs; | 543 | std::shared_ptr<FileSys::VfsFilesystem> m_vfs; |
| 530 | Core::SystemResultStatus m_load_result{Core::SystemResultStatus::ErrorNotInitialized}; | 544 | Core::SystemResultStatus m_load_result{Core::SystemResultStatus::ErrorNotInitialized}; |
| 531 | bool m_is_running{}; | 545 | std::atomic<bool> m_is_running = false; |
| 532 | bool m_is_paused{}; | 546 | std::atomic<bool> m_is_paused = false; |
| 533 | SoftwareKeyboard::AndroidKeyboard* m_software_keyboard{}; | 547 | SoftwareKeyboard::AndroidKeyboard* m_software_keyboard{}; |
| 534 | std::unique_ptr<Service::Account::ProfileManager> m_profile_manager; | 548 | std::unique_ptr<Service::Account::ProfileManager> m_profile_manager; |
| 535 | std::unique_ptr<FileSys::ManualContentProvider> m_manual_provider; | 549 | std::unique_ptr<FileSys::ManualContentProvider> m_manual_provider; |
| @@ -824,34 +838,6 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_reloadSettings(JNIEnv* env, jclass cl | |||
| 824 | Config{}; | 838 | Config{}; |
| 825 | } | 839 | } |
| 826 | 840 | ||
| 827 | jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getUserSetting(JNIEnv* env, jclass clazz, | ||
| 828 | jstring j_game_id, jstring j_section, | ||
| 829 | jstring j_key) { | ||
| 830 | std::string_view game_id = env->GetStringUTFChars(j_game_id, 0); | ||
| 831 | std::string_view section = env->GetStringUTFChars(j_section, 0); | ||
| 832 | std::string_view key = env->GetStringUTFChars(j_key, 0); | ||
| 833 | |||
| 834 | env->ReleaseStringUTFChars(j_game_id, game_id.data()); | ||
| 835 | env->ReleaseStringUTFChars(j_section, section.data()); | ||
| 836 | env->ReleaseStringUTFChars(j_key, key.data()); | ||
| 837 | |||
| 838 | return env->NewStringUTF(""); | ||
| 839 | } | ||
| 840 | |||
| 841 | void Java_org_yuzu_yuzu_1emu_NativeLibrary_setUserSetting(JNIEnv* env, jclass clazz, | ||
| 842 | jstring j_game_id, jstring j_section, | ||
| 843 | jstring j_key, jstring j_value) { | ||
| 844 | std::string_view game_id = env->GetStringUTFChars(j_game_id, 0); | ||
| 845 | std::string_view section = env->GetStringUTFChars(j_section, 0); | ||
| 846 | std::string_view key = env->GetStringUTFChars(j_key, 0); | ||
| 847 | std::string_view value = env->GetStringUTFChars(j_value, 0); | ||
| 848 | |||
| 849 | env->ReleaseStringUTFChars(j_game_id, game_id.data()); | ||
| 850 | env->ReleaseStringUTFChars(j_section, section.data()); | ||
| 851 | env->ReleaseStringUTFChars(j_key, key.data()); | ||
| 852 | env->ReleaseStringUTFChars(j_value, value.data()); | ||
| 853 | } | ||
| 854 | |||
| 855 | void Java_org_yuzu_yuzu_1emu_NativeLibrary_initGameIni(JNIEnv* env, jclass clazz, | 841 | void Java_org_yuzu_yuzu_1emu_NativeLibrary_initGameIni(JNIEnv* env, jclass clazz, |
| 856 | jstring j_game_id) { | 842 | jstring j_game_id) { |
| 857 | std::string_view game_id = env->GetStringUTFChars(j_game_id, 0); | 843 | std::string_view game_id = env->GetStringUTFChars(j_game_id, 0); |
diff --git a/src/android/app/src/main/jni/native_config.cpp b/src/android/app/src/main/jni/native_config.cpp new file mode 100644 index 000000000..8a704960c --- /dev/null +++ b/src/android/app/src/main/jni/native_config.cpp | |||
| @@ -0,0 +1,237 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include <string> | ||
| 5 | |||
| 6 | #include <jni.h> | ||
| 7 | |||
| 8 | #include "common/logging/log.h" | ||
| 9 | #include "common/settings.h" | ||
| 10 | #include "jni/android_common/android_common.h" | ||
| 11 | #include "jni/config.h" | ||
| 12 | #include "uisettings.h" | ||
| 13 | |||
| 14 | template <typename T> | ||
| 15 | Settings::Setting<T>* getSetting(JNIEnv* env, jstring jkey) { | ||
| 16 | auto key = GetJString(env, jkey); | ||
| 17 | auto basicSetting = Settings::values.linkage.by_key[key]; | ||
| 18 | auto basicAndroidSetting = AndroidSettings::values.linkage.by_key[key]; | ||
| 19 | if (basicSetting != 0) { | ||
| 20 | return static_cast<Settings::Setting<T>*>(basicSetting); | ||
| 21 | } | ||
| 22 | if (basicAndroidSetting != 0) { | ||
| 23 | return static_cast<Settings::Setting<T>*>(basicAndroidSetting); | ||
| 24 | } | ||
| 25 | LOG_ERROR(Frontend, "[Android Native] Could not find setting - {}", key); | ||
| 26 | return nullptr; | ||
| 27 | } | ||
| 28 | |||
| 29 | extern "C" { | ||
| 30 | |||
| 31 | jboolean Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getBoolean(JNIEnv* env, jobject obj, | ||
| 32 | jstring jkey, jboolean getDefault) { | ||
| 33 | auto setting = getSetting<bool>(env, jkey); | ||
| 34 | if (setting == nullptr) { | ||
| 35 | return false; | ||
| 36 | } | ||
| 37 | setting->SetGlobal(true); | ||
| 38 | |||
| 39 | if (static_cast<bool>(getDefault)) { | ||
| 40 | return setting->GetDefault(); | ||
| 41 | } | ||
| 42 | |||
| 43 | return setting->GetValue(); | ||
| 44 | } | ||
| 45 | |||
| 46 | void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setBoolean(JNIEnv* env, jobject obj, jstring jkey, | ||
| 47 | jboolean value) { | ||
| 48 | auto setting = getSetting<bool>(env, jkey); | ||
| 49 | if (setting == nullptr) { | ||
| 50 | return; | ||
| 51 | } | ||
| 52 | setting->SetGlobal(true); | ||
| 53 | setting->SetValue(static_cast<bool>(value)); | ||
| 54 | } | ||
| 55 | |||
| 56 | jbyte Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getByte(JNIEnv* env, jobject obj, jstring jkey, | ||
| 57 | jboolean getDefault) { | ||
| 58 | auto setting = getSetting<u8>(env, jkey); | ||
| 59 | if (setting == nullptr) { | ||
| 60 | return -1; | ||
| 61 | } | ||
| 62 | setting->SetGlobal(true); | ||
| 63 | |||
| 64 | if (static_cast<bool>(getDefault)) { | ||
| 65 | return setting->GetDefault(); | ||
| 66 | } | ||
| 67 | |||
| 68 | return setting->GetValue(); | ||
| 69 | } | ||
| 70 | |||
| 71 | void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setByte(JNIEnv* env, jobject obj, jstring jkey, | ||
| 72 | jbyte value) { | ||
| 73 | auto setting = getSetting<u8>(env, jkey); | ||
| 74 | if (setting == nullptr) { | ||
| 75 | return; | ||
| 76 | } | ||
| 77 | setting->SetGlobal(true); | ||
| 78 | setting->SetValue(value); | ||
| 79 | } | ||
| 80 | |||
| 81 | jshort Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getShort(JNIEnv* env, jobject obj, jstring jkey, | ||
| 82 | jboolean getDefault) { | ||
| 83 | auto setting = getSetting<u16>(env, jkey); | ||
| 84 | if (setting == nullptr) { | ||
| 85 | return -1; | ||
| 86 | } | ||
| 87 | setting->SetGlobal(true); | ||
| 88 | |||
| 89 | if (static_cast<bool>(getDefault)) { | ||
| 90 | return setting->GetDefault(); | ||
| 91 | } | ||
| 92 | |||
| 93 | return setting->GetValue(); | ||
| 94 | } | ||
| 95 | |||
| 96 | void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setShort(JNIEnv* env, jobject obj, jstring jkey, | ||
| 97 | jshort value) { | ||
| 98 | auto setting = getSetting<u16>(env, jkey); | ||
| 99 | if (setting == nullptr) { | ||
| 100 | return; | ||
| 101 | } | ||
| 102 | setting->SetGlobal(true); | ||
| 103 | setting->SetValue(value); | ||
| 104 | } | ||
| 105 | |||
| 106 | jint Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getInt(JNIEnv* env, jobject obj, jstring jkey, | ||
| 107 | jboolean getDefault) { | ||
| 108 | auto setting = getSetting<int>(env, jkey); | ||
| 109 | if (setting == nullptr) { | ||
| 110 | return -1; | ||
| 111 | } | ||
| 112 | setting->SetGlobal(true); | ||
| 113 | |||
| 114 | if (static_cast<bool>(getDefault)) { | ||
| 115 | return setting->GetDefault(); | ||
| 116 | } | ||
| 117 | |||
| 118 | return setting->GetValue(); | ||
| 119 | } | ||
| 120 | |||
| 121 | void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setInt(JNIEnv* env, jobject obj, jstring jkey, | ||
| 122 | jint value) { | ||
| 123 | auto setting = getSetting<int>(env, jkey); | ||
| 124 | if (setting == nullptr) { | ||
| 125 | return; | ||
| 126 | } | ||
| 127 | setting->SetGlobal(true); | ||
| 128 | setting->SetValue(value); | ||
| 129 | } | ||
| 130 | |||
| 131 | jfloat Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getFloat(JNIEnv* env, jobject obj, jstring jkey, | ||
| 132 | jboolean getDefault) { | ||
| 133 | auto setting = getSetting<float>(env, jkey); | ||
| 134 | if (setting == nullptr) { | ||
| 135 | return -1; | ||
| 136 | } | ||
| 137 | setting->SetGlobal(true); | ||
| 138 | |||
| 139 | if (static_cast<bool>(getDefault)) { | ||
| 140 | return setting->GetDefault(); | ||
| 141 | } | ||
| 142 | |||
| 143 | return setting->GetValue(); | ||
| 144 | } | ||
| 145 | |||
| 146 | void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setFloat(JNIEnv* env, jobject obj, jstring jkey, | ||
| 147 | jfloat value) { | ||
| 148 | auto setting = getSetting<float>(env, jkey); | ||
| 149 | if (setting == nullptr) { | ||
| 150 | return; | ||
| 151 | } | ||
| 152 | setting->SetGlobal(true); | ||
| 153 | setting->SetValue(value); | ||
| 154 | } | ||
| 155 | |||
| 156 | jlong Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getLong(JNIEnv* env, jobject obj, jstring jkey, | ||
| 157 | jboolean getDefault) { | ||
| 158 | auto setting = getSetting<long>(env, jkey); | ||
| 159 | if (setting == nullptr) { | ||
| 160 | return -1; | ||
| 161 | } | ||
| 162 | setting->SetGlobal(true); | ||
| 163 | |||
| 164 | if (static_cast<bool>(getDefault)) { | ||
| 165 | return setting->GetDefault(); | ||
| 166 | } | ||
| 167 | |||
| 168 | return setting->GetValue(); | ||
| 169 | } | ||
| 170 | |||
| 171 | void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setLong(JNIEnv* env, jobject obj, jstring jkey, | ||
| 172 | jlong value) { | ||
| 173 | auto setting = getSetting<long>(env, jkey); | ||
| 174 | if (setting == nullptr) { | ||
| 175 | return; | ||
| 176 | } | ||
| 177 | setting->SetGlobal(true); | ||
| 178 | setting->SetValue(value); | ||
| 179 | } | ||
| 180 | |||
| 181 | jstring Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getString(JNIEnv* env, jobject obj, jstring jkey, | ||
| 182 | jboolean getDefault) { | ||
| 183 | auto setting = getSetting<std::string>(env, jkey); | ||
| 184 | if (setting == nullptr) { | ||
| 185 | return ToJString(env, ""); | ||
| 186 | } | ||
| 187 | setting->SetGlobal(true); | ||
| 188 | |||
| 189 | if (static_cast<bool>(getDefault)) { | ||
| 190 | return ToJString(env, setting->GetDefault()); | ||
| 191 | } | ||
| 192 | |||
| 193 | return ToJString(env, setting->GetValue()); | ||
| 194 | } | ||
| 195 | |||
| 196 | void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setString(JNIEnv* env, jobject obj, jstring jkey, | ||
| 197 | jstring value) { | ||
| 198 | auto setting = getSetting<std::string>(env, jkey); | ||
| 199 | if (setting == nullptr) { | ||
| 200 | return; | ||
| 201 | } | ||
| 202 | |||
| 203 | setting->SetGlobal(true); | ||
| 204 | setting->SetValue(GetJString(env, value)); | ||
| 205 | } | ||
| 206 | |||
| 207 | jboolean Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getIsRuntimeModifiable(JNIEnv* env, jobject obj, | ||
| 208 | jstring jkey) { | ||
| 209 | auto key = GetJString(env, jkey); | ||
| 210 | auto setting = Settings::values.linkage.by_key[key]; | ||
| 211 | if (setting != 0) { | ||
| 212 | return setting->RuntimeModfiable(); | ||
| 213 | } | ||
| 214 | LOG_ERROR(Frontend, "[Android Native] Could not find setting - {}", key); | ||
| 215 | return true; | ||
| 216 | } | ||
| 217 | |||
| 218 | jstring Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getConfigHeader(JNIEnv* env, jobject obj, | ||
| 219 | jint jcategory) { | ||
| 220 | auto category = static_cast<Settings::Category>(jcategory); | ||
| 221 | return ToJString(env, Settings::TranslateCategory(category)); | ||
| 222 | } | ||
| 223 | |||
| 224 | jstring Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getPairedSettingKey(JNIEnv* env, jobject obj, | ||
| 225 | jstring jkey) { | ||
| 226 | auto setting = getSetting<std::string>(env, jkey); | ||
| 227 | if (setting == nullptr) { | ||
| 228 | return ToJString(env, ""); | ||
| 229 | } | ||
| 230 | if (setting->PairedSetting() == nullptr) { | ||
| 231 | return ToJString(env, ""); | ||
| 232 | } | ||
| 233 | |||
| 234 | return ToJString(env, setting->PairedSetting()->GetLabel()); | ||
| 235 | } | ||
| 236 | |||
| 237 | } // extern "C" | ||
diff --git a/src/android/app/src/main/jni/uisettings.cpp b/src/android/app/src/main/jni/uisettings.cpp new file mode 100644 index 000000000..f2f0bad50 --- /dev/null +++ b/src/android/app/src/main/jni/uisettings.cpp | |||
| @@ -0,0 +1,10 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "uisettings.h" | ||
| 5 | |||
| 6 | namespace AndroidSettings { | ||
| 7 | |||
| 8 | Values values; | ||
| 9 | |||
| 10 | } // namespace AndroidSettings | ||
diff --git a/src/android/app/src/main/jni/uisettings.h b/src/android/app/src/main/jni/uisettings.h new file mode 100644 index 000000000..494654af7 --- /dev/null +++ b/src/android/app/src/main/jni/uisettings.h | |||
| @@ -0,0 +1,29 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <common/settings_common.h> | ||
| 7 | #include "common/common_types.h" | ||
| 8 | #include "common/settings_setting.h" | ||
| 9 | |||
| 10 | namespace AndroidSettings { | ||
| 11 | |||
| 12 | struct Values { | ||
| 13 | Settings::Linkage linkage; | ||
| 14 | |||
| 15 | // Android | ||
| 16 | Settings::Setting<bool> picture_in_picture{linkage, true, "picture_in_picture", | ||
| 17 | Settings::Category::Android}; | ||
| 18 | Settings::Setting<s32> screen_layout{linkage, | ||
| 19 | 5, | ||
| 20 | "screen_layout", | ||
| 21 | Settings::Category::Android, | ||
| 22 | Settings::Specialization::Default, | ||
| 23 | true, | ||
| 24 | true}; | ||
| 25 | }; | ||
| 26 | |||
| 27 | extern Values values; | ||
| 28 | |||
| 29 | } // namespace AndroidSettings | ||
diff --git a/src/android/app/src/main/res/anim-ldrtl/anim_pop_settings_fragment_out.xml b/src/android/app/src/main/res/anim-ldrtl/anim_pop_settings_fragment_out.xml deleted file mode 100644 index 9f49c133a..000000000 --- a/src/android/app/src/main/res/anim-ldrtl/anim_pop_settings_fragment_out.xml +++ /dev/null | |||
| @@ -1,16 +0,0 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <set xmlns:android="http://schemas.android.com/apk/res/android"> | ||
| 3 | |||
| 4 | <alpha | ||
| 5 | android:duration="125" | ||
| 6 | android:interpolator="@android:anim/decelerate_interpolator" | ||
| 7 | android:fromAlpha="1" | ||
| 8 | android:toAlpha="0" /> | ||
| 9 | |||
| 10 | <translate | ||
| 11 | android:duration="125" | ||
| 12 | android:interpolator="@android:anim/decelerate_interpolator" | ||
| 13 | android:fromXDelta="0" | ||
| 14 | android:toXDelta="-75" /> | ||
| 15 | |||
| 16 | </set> | ||
diff --git a/src/android/app/src/main/res/anim-ldrtl/anim_settings_fragment_in.xml b/src/android/app/src/main/res/anim-ldrtl/anim_settings_fragment_in.xml deleted file mode 100644 index 82fd719db..000000000 --- a/src/android/app/src/main/res/anim-ldrtl/anim_settings_fragment_in.xml +++ /dev/null | |||
| @@ -1,16 +0,0 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <set xmlns:android="http://schemas.android.com/apk/res/android"> | ||
| 3 | |||
| 4 | <alpha | ||
| 5 | android:duration="@android:integer/config_shortAnimTime" | ||
| 6 | android:interpolator="@android:anim/decelerate_interpolator" | ||
| 7 | android:fromAlpha="0" | ||
| 8 | android:toAlpha="1" /> | ||
| 9 | |||
| 10 | <translate | ||
| 11 | android:duration="@android:integer/config_shortAnimTime" | ||
| 12 | android:interpolator="@android:anim/decelerate_interpolator" | ||
| 13 | android:fromXDelta="-200" | ||
| 14 | android:toXDelta="0" /> | ||
| 15 | |||
| 16 | </set> | ||
diff --git a/src/android/app/src/main/res/anim/anim_pop_settings_fragment_out.xml b/src/android/app/src/main/res/anim/anim_pop_settings_fragment_out.xml deleted file mode 100644 index 5892128f1..000000000 --- a/src/android/app/src/main/res/anim/anim_pop_settings_fragment_out.xml +++ /dev/null | |||
| @@ -1,16 +0,0 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <set xmlns:android="http://schemas.android.com/apk/res/android"> | ||
| 3 | |||
| 4 | <alpha | ||
| 5 | android:duration="125" | ||
| 6 | android:interpolator="@android:anim/decelerate_interpolator" | ||
| 7 | android:fromAlpha="1" | ||
| 8 | android:toAlpha="0" /> | ||
| 9 | |||
| 10 | <translate | ||
| 11 | android:duration="125" | ||
| 12 | android:interpolator="@android:anim/decelerate_interpolator" | ||
| 13 | android:fromXDelta="0" | ||
| 14 | android:toXDelta="75" /> | ||
| 15 | |||
| 16 | </set> | ||
diff --git a/src/android/app/src/main/res/anim/anim_settings_fragment_in.xml b/src/android/app/src/main/res/anim/anim_settings_fragment_in.xml deleted file mode 100644 index 98e0cf8bd..000000000 --- a/src/android/app/src/main/res/anim/anim_settings_fragment_in.xml +++ /dev/null | |||
| @@ -1,16 +0,0 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <set xmlns:android="http://schemas.android.com/apk/res/android"> | ||
| 3 | |||
| 4 | <alpha | ||
| 5 | android:duration="@android:integer/config_shortAnimTime" | ||
| 6 | android:interpolator="@android:anim/decelerate_interpolator" | ||
| 7 | android:fromAlpha="0" | ||
| 8 | android:toAlpha="1" /> | ||
| 9 | |||
| 10 | <translate | ||
| 11 | android:duration="@android:integer/config_shortAnimTime" | ||
| 12 | android:interpolator="@android:anim/decelerate_interpolator" | ||
| 13 | android:fromXDelta="200" | ||
| 14 | android:toXDelta="0" /> | ||
| 15 | |||
| 16 | </set> | ||
diff --git a/src/android/app/src/main/res/anim/anim_settings_fragment_out.xml b/src/android/app/src/main/res/anim/anim_settings_fragment_out.xml deleted file mode 100644 index 77a40a4d1..000000000 --- a/src/android/app/src/main/res/anim/anim_settings_fragment_out.xml +++ /dev/null | |||
| @@ -1,10 +0,0 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <set xmlns:android="http://schemas.android.com/apk/res/android"> | ||
| 3 | |||
| 4 | <alpha | ||
| 5 | android:duration="@android:integer/config_shortAnimTime" | ||
| 6 | android:interpolator="@android:anim/decelerate_interpolator" | ||
| 7 | android:fromAlpha="1" | ||
| 8 | android:toAlpha="0" /> | ||
| 9 | |||
| 10 | </set> | ||
diff --git a/src/android/app/src/main/res/animator/menu_slide_in_from_start.xml b/src/android/app/src/main/res/animator/menu_slide_in_from_start.xml deleted file mode 100644 index 4612aee13..000000000 --- a/src/android/app/src/main/res/animator/menu_slide_in_from_start.xml +++ /dev/null | |||
| @@ -1,20 +0,0 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <set xmlns:android="http://schemas.android.com/apk/res/android"> | ||
| 3 | |||
| 4 | <objectAnimator | ||
| 5 | android:propertyName="translationX" | ||
| 6 | android:valueType="floatType" | ||
| 7 | android:valueFrom="-1280dp" | ||
| 8 | android:valueTo="0" | ||
| 9 | android:interpolator="@android:interpolator/decelerate_quad" | ||
| 10 | android:duration="300"/> | ||
| 11 | |||
| 12 | <objectAnimator | ||
| 13 | android:propertyName="alpha" | ||
| 14 | android:valueType="floatType" | ||
| 15 | android:valueFrom="0" | ||
| 16 | android:valueTo="1" | ||
| 17 | android:interpolator="@android:interpolator/accelerate_quad" | ||
| 18 | android:duration="300"/> | ||
| 19 | |||
| 20 | </set> \ No newline at end of file | ||
diff --git a/src/android/app/src/main/res/animator/menu_slide_out_to_start.xml b/src/android/app/src/main/res/animator/menu_slide_out_to_start.xml deleted file mode 100644 index c00478946..000000000 --- a/src/android/app/src/main/res/animator/menu_slide_out_to_start.xml +++ /dev/null | |||
| @@ -1,21 +0,0 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <set xmlns:android="http://schemas.android.com/apk/res/android"> | ||
| 3 | |||
| 4 | <!-- This animation is used ONLY when a submenu is replaced. --> | ||
| 5 | <objectAnimator | ||
| 6 | android:propertyName="translationX" | ||
| 7 | android:valueType="floatType" | ||
| 8 | android:valueFrom="0" | ||
| 9 | android:valueTo="-1280dp" | ||
| 10 | android:interpolator="@android:interpolator/decelerate_quad" | ||
| 11 | android:duration="200"/> | ||
| 12 | |||
| 13 | <objectAnimator | ||
| 14 | android:propertyName="alpha" | ||
| 15 | android:valueType="floatType" | ||
| 16 | android:valueFrom="1" | ||
| 17 | android:valueTo="0" | ||
| 18 | android:interpolator="@android:interpolator/decelerate_quad" | ||
| 19 | android:duration="200"/> | ||
| 20 | |||
| 21 | </set> \ No newline at end of file | ||
diff --git a/src/android/app/src/main/res/layout/activity_settings.xml b/src/android/app/src/main/res/layout/activity_settings.xml index 14ae83b04..8a026a30a 100644 --- a/src/android/app/src/main/res/layout/activity_settings.xml +++ b/src/android/app/src/main/res/layout/activity_settings.xml | |||
| @@ -1,42 +1,24 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | 1 | <?xml version="1.0" encoding="utf-8"?> |
| 2 | <androidx.coordinatorlayout.widget.CoordinatorLayout | 2 | <androidx.constraintlayout.widget.ConstraintLayout |
| 3 | android:id="@+id/coordinator_main" | ||
| 4 | xmlns:android="http://schemas.android.com/apk/res/android" | 3 | xmlns:android="http://schemas.android.com/apk/res/android" |
| 5 | xmlns:app="http://schemas.android.com/apk/res-auto" | 4 | xmlns:app="http://schemas.android.com/apk/res-auto" |
| 5 | xmlns:tools="http://schemas.android.com/tools" | ||
| 6 | android:id="@+id/constraint_settings" | ||
| 6 | android:layout_width="match_parent" | 7 | android:layout_width="match_parent" |
| 7 | android:layout_height="match_parent" | 8 | android:layout_height="match_parent" |
| 8 | android:background="?attr/colorSurface"> | 9 | android:background="?attr/colorSurface"> |
| 9 | 10 | ||
| 10 | <com.google.android.material.appbar.AppBarLayout | 11 | <androidx.fragment.app.FragmentContainerView |
| 11 | android:id="@+id/appbar_settings" | 12 | android:id="@+id/fragment_container" |
| 12 | android:layout_width="match_parent" | 13 | android:name="androidx.navigation.fragment.NavHostFragment" |
| 13 | android:layout_height="wrap_content" | 14 | android:layout_width="0dp" |
| 14 | android:fitsSystemWindows="true" | 15 | android:layout_height="0dp" |
| 15 | app:elevation="0dp"> | 16 | app:defaultNavHost="true" |
| 16 | 17 | app:layout_constraintBottom_toBottomOf="parent" | |
| 17 | <com.google.android.material.appbar.CollapsingToolbarLayout | 18 | app:layout_constraintLeft_toLeftOf="parent" |
| 18 | style="?attr/collapsingToolbarLayoutMediumStyle" | 19 | app:layout_constraintRight_toRightOf="parent" |
| 19 | android:id="@+id/toolbar_settings_layout" | 20 | app:layout_constraintTop_toTopOf="parent" |
| 20 | android:layout_width="match_parent" | 21 | tools:layout="@layout/fragment_settings" /> |
| 21 | android:layout_height="?attr/collapsingToolbarLayoutMediumSize" | ||
| 22 | app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"> | ||
| 23 | |||
| 24 | <com.google.android.material.appbar.MaterialToolbar | ||
| 25 | android:id="@+id/toolbar_settings" | ||
| 26 | android:layout_width="match_parent" | ||
| 27 | android:layout_height="?attr/actionBarSize" | ||
| 28 | app:layout_collapseMode="pin" /> | ||
| 29 | |||
| 30 | </com.google.android.material.appbar.CollapsingToolbarLayout> | ||
| 31 | |||
| 32 | </com.google.android.material.appbar.AppBarLayout> | ||
| 33 | |||
| 34 | <FrameLayout | ||
| 35 | android:id="@+id/frame_content" | ||
| 36 | android:layout_width="match_parent" | ||
| 37 | android:layout_height="match_parent" | ||
| 38 | android:layout_marginHorizontal="12dp" | ||
| 39 | app:layout_behavior="@string/appbar_scrolling_view_behavior" /> | ||
| 40 | 22 | ||
| 41 | <View | 23 | <View |
| 42 | android:id="@+id/navigation_bar_shade" | 24 | android:id="@+id/navigation_bar_shade" |
| @@ -45,6 +27,8 @@ | |||
| 45 | android:background="@android:color/transparent" | 27 | android:background="@android:color/transparent" |
| 46 | android:clickable="false" | 28 | android:clickable="false" |
| 47 | android:focusable="false" | 29 | android:focusable="false" |
| 48 | android:layout_gravity="bottom|center_horizontal" /> | 30 | app:layout_constraintBottom_toBottomOf="parent" |
| 31 | app:layout_constraintEnd_toEndOf="parent" | ||
| 32 | app:layout_constraintStart_toStartOf="parent" /> | ||
| 49 | 33 | ||
| 50 | </androidx.coordinatorlayout.widget.CoordinatorLayout> | 34 | </androidx.constraintlayout.widget.ConstraintLayout> |
diff --git a/src/android/app/src/main/res/layout/fragment_emulation.xml b/src/android/app/src/main/res/layout/fragment_emulation.xml index e54a10e8f..da97d85c1 100644 --- a/src/android/app/src/main/res/layout/fragment_emulation.xml +++ b/src/android/app/src/main/res/layout/fragment_emulation.xml | |||
| @@ -26,6 +26,81 @@ | |||
| 26 | android:focusable="false" | 26 | android:focusable="false" |
| 27 | android:focusableInTouchMode="false" /> | 27 | android:focusableInTouchMode="false" /> |
| 28 | 28 | ||
| 29 | <com.google.android.material.card.MaterialCardView | ||
| 30 | android:id="@+id/loading_indicator" | ||
| 31 | style="?attr/materialCardViewOutlinedStyle" | ||
| 32 | android:layout_width="wrap_content" | ||
| 33 | android:layout_height="wrap_content" | ||
| 34 | android:layout_gravity="center" | ||
| 35 | android:focusable="false"> | ||
| 36 | |||
| 37 | <androidx.constraintlayout.widget.ConstraintLayout | ||
| 38 | android:id="@+id/loading_layout" | ||
| 39 | android:layout_width="wrap_content" | ||
| 40 | android:layout_height="wrap_content" | ||
| 41 | android:gravity="center_horizontal"> | ||
| 42 | |||
| 43 | <ImageView | ||
| 44 | android:id="@+id/loading_image" | ||
| 45 | android:layout_width="wrap_content" | ||
| 46 | android:layout_height="0dp" | ||
| 47 | android:adjustViewBounds="true" | ||
| 48 | app:layout_constraintBottom_toBottomOf="@+id/linearLayout" | ||
| 49 | app:layout_constraintStart_toStartOf="parent" | ||
| 50 | app:layout_constraintTop_toTopOf="@+id/linearLayout" | ||
| 51 | tools:src="@drawable/default_icon" /> | ||
| 52 | |||
| 53 | <LinearLayout | ||
| 54 | android:id="@+id/linearLayout" | ||
| 55 | android:layout_width="wrap_content" | ||
| 56 | android:layout_height="wrap_content" | ||
| 57 | android:orientation="vertical" | ||
| 58 | android:paddingHorizontal="24dp" | ||
| 59 | android:paddingVertical="36dp" | ||
| 60 | app:layout_constraintBottom_toBottomOf="parent" | ||
| 61 | app:layout_constraintEnd_toEndOf="parent" | ||
| 62 | app:layout_constraintStart_toEndOf="@id/loading_image" | ||
| 63 | app:layout_constraintTop_toTopOf="parent"> | ||
| 64 | |||
| 65 | <com.google.android.material.textview.MaterialTextView | ||
| 66 | android:id="@+id/loading_title" | ||
| 67 | style="@style/TextAppearance.Material3.TitleMedium" | ||
| 68 | android:layout_width="match_parent" | ||
| 69 | android:layout_height="wrap_content" | ||
| 70 | android:ellipsize="marquee" | ||
| 71 | android:marqueeRepeatLimit="marquee_forever" | ||
| 72 | android:requiresFadingEdge="horizontal" | ||
| 73 | android:singleLine="true" | ||
| 74 | android:textAlignment="viewStart" | ||
| 75 | tools:text="@string/games" /> | ||
| 76 | |||
| 77 | <com.google.android.material.textview.MaterialTextView | ||
| 78 | android:id="@+id/loading_text" | ||
| 79 | style="@style/TextAppearance.Material3.TitleSmall" | ||
| 80 | android:layout_width="match_parent" | ||
| 81 | android:layout_height="wrap_content" | ||
| 82 | android:layout_marginTop="4dp" | ||
| 83 | android:ellipsize="marquee" | ||
| 84 | android:marqueeRepeatLimit="marquee_forever" | ||
| 85 | android:requiresFadingEdge="horizontal" | ||
| 86 | android:singleLine="true" | ||
| 87 | android:text="@string/loading" | ||
| 88 | android:textAlignment="viewStart" /> | ||
| 89 | |||
| 90 | <com.google.android.material.progressindicator.LinearProgressIndicator | ||
| 91 | android:id="@+id/loading_progress_indicator" | ||
| 92 | android:layout_width="192dp" | ||
| 93 | android:layout_height="wrap_content" | ||
| 94 | android:layout_marginTop="12dp" | ||
| 95 | android:indeterminate="true" | ||
| 96 | app:trackCornerRadius="8dp" /> | ||
| 97 | |||
| 98 | </LinearLayout> | ||
| 99 | |||
| 100 | </androidx.constraintlayout.widget.ConstraintLayout> | ||
| 101 | |||
| 102 | </com.google.android.material.card.MaterialCardView> | ||
| 103 | |||
| 29 | </FrameLayout> | 104 | </FrameLayout> |
| 30 | 105 | ||
| 31 | <FrameLayout | 106 | <FrameLayout |
| @@ -41,11 +116,12 @@ | |||
| 41 | android:layout_height="match_parent" | 116 | android:layout_height="match_parent" |
| 42 | android:layout_gravity="center" | 117 | android:layout_gravity="center" |
| 43 | android:focusable="true" | 118 | android:focusable="true" |
| 44 | android:focusableInTouchMode="true" /> | 119 | android:focusableInTouchMode="true" |
| 120 | android:visibility="invisible" /> | ||
| 45 | 121 | ||
| 46 | <Button | 122 | <Button |
| 47 | style="@style/Widget.Material3.Button.ElevatedButton" | ||
| 48 | android:id="@+id/done_control_config" | 123 | android:id="@+id/done_control_config" |
| 124 | style="@style/Widget.Material3.Button.ElevatedButton" | ||
| 49 | android:layout_width="wrap_content" | 125 | android:layout_width="wrap_content" |
| 50 | android:layout_height="wrap_content" | 126 | android:layout_height="wrap_content" |
| 51 | android:layout_gravity="center" | 127 | android:layout_gravity="center" |
| @@ -81,6 +157,7 @@ | |||
| 81 | android:layout_height="match_parent" | 157 | android:layout_height="match_parent" |
| 82 | android:layout_gravity="start|bottom" | 158 | android:layout_gravity="start|bottom" |
| 83 | app:headerLayout="@layout/header_in_game" | 159 | app:headerLayout="@layout/header_in_game" |
| 84 | app:menu="@menu/menu_in_game" /> | 160 | app:menu="@menu/menu_in_game" |
| 161 | tools:visibility="gone" /> | ||
| 85 | 162 | ||
| 86 | </androidx.drawerlayout.widget.DrawerLayout> | 163 | </androidx.drawerlayout.widget.DrawerLayout> |
diff --git a/src/android/app/src/main/res/layout/fragment_settings.xml b/src/android/app/src/main/res/layout/fragment_settings.xml index 167720347..ebedbf1ec 100644 --- a/src/android/app/src/main/res/layout/fragment_settings.xml +++ b/src/android/app/src/main/res/layout/fragment_settings.xml | |||
| @@ -1,14 +1,41 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | 1 | <?xml version="1.0" encoding="utf-8"?> |
| 2 | <FrameLayout | 2 | <androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" |
| 3 | xmlns:android="http://schemas.android.com/apk/res/android" | 3 | xmlns:app="http://schemas.android.com/apk/res-auto" |
| 4 | android:id="@+id/coordinator_main" | ||
| 4 | android:layout_width="match_parent" | 5 | android:layout_width="match_parent" |
| 5 | android:layout_height="match_parent"> | 6 | android:layout_height="match_parent" |
| 7 | android:background="?attr/colorSurface"> | ||
| 8 | |||
| 9 | <com.google.android.material.appbar.AppBarLayout | ||
| 10 | android:id="@+id/appbar_settings" | ||
| 11 | android:layout_width="match_parent" | ||
| 12 | android:layout_height="wrap_content" | ||
| 13 | android:fitsSystemWindows="true" | ||
| 14 | app:elevation="0dp"> | ||
| 15 | |||
| 16 | <com.google.android.material.appbar.CollapsingToolbarLayout | ||
| 17 | android:id="@+id/toolbar_settings_layout" | ||
| 18 | style="?attr/collapsingToolbarLayoutMediumStyle" | ||
| 19 | android:layout_width="match_parent" | ||
| 20 | android:layout_height="?attr/collapsingToolbarLayoutMediumSize" | ||
| 21 | app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"> | ||
| 22 | |||
| 23 | <com.google.android.material.appbar.MaterialToolbar | ||
| 24 | android:id="@+id/toolbar_settings" | ||
| 25 | android:layout_width="match_parent" | ||
| 26 | android:layout_height="?attr/actionBarSize" | ||
| 27 | app:layout_collapseMode="pin" | ||
| 28 | app:navigationIcon="@drawable/ic_back" /> | ||
| 29 | |||
| 30 | </com.google.android.material.appbar.CollapsingToolbarLayout> | ||
| 31 | |||
| 32 | </com.google.android.material.appbar.AppBarLayout> | ||
| 6 | 33 | ||
| 7 | <androidx.recyclerview.widget.RecyclerView | 34 | <androidx.recyclerview.widget.RecyclerView |
| 8 | android:id="@+id/list_settings" | 35 | android:id="@+id/list_settings" |
| 9 | android:layout_width="match_parent" | 36 | android:layout_width="match_parent" |
| 10 | android:layout_height="match_parent" | 37 | android:layout_height="match_parent" |
| 11 | android:background="?attr/colorSurface" | 38 | android:clipToPadding="false" |
| 12 | android:clipToPadding="false" /> | 39 | app:layout_behavior="@string/appbar_scrolling_view_behavior" /> |
| 13 | 40 | ||
| 14 | </FrameLayout> | 41 | </androidx.coordinatorlayout.widget.CoordinatorLayout> |
diff --git a/src/android/app/src/main/res/layout/fragment_settings_search.xml b/src/android/app/src/main/res/layout/fragment_settings_search.xml new file mode 100644 index 000000000..c779ed2fc --- /dev/null +++ b/src/android/app/src/main/res/layout/fragment_settings_search.xml | |||
| @@ -0,0 +1,120 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||
| 3 | xmlns:app="http://schemas.android.com/apk/res-auto" | ||
| 4 | xmlns:tools="http://schemas.android.com/tools" | ||
| 5 | android:layout_width="match_parent" | ||
| 6 | android:layout_height="match_parent"> | ||
| 7 | |||
| 8 | <RelativeLayout | ||
| 9 | android:id="@+id/relativeLayout" | ||
| 10 | android:layout_width="0dp" | ||
| 11 | android:layout_height="0dp" | ||
| 12 | app:layout_constraintBottom_toBottomOf="parent" | ||
| 13 | app:layout_constraintEnd_toEndOf="parent" | ||
| 14 | app:layout_constraintStart_toStartOf="parent" | ||
| 15 | app:layout_constraintTop_toBottomOf="@+id/divider"> | ||
| 16 | |||
| 17 | <LinearLayout | ||
| 18 | android:id="@+id/no_results_view" | ||
| 19 | android:layout_width="match_parent" | ||
| 20 | android:layout_height="match_parent" | ||
| 21 | android:gravity="center" | ||
| 22 | android:orientation="vertical"> | ||
| 23 | |||
| 24 | <ImageView | ||
| 25 | android:id="@+id/icon_no_results" | ||
| 26 | android:layout_width="match_parent" | ||
| 27 | android:layout_height="80dp" | ||
| 28 | android:src="@drawable/ic_search" /> | ||
| 29 | |||
| 30 | <com.google.android.material.textview.MaterialTextView | ||
| 31 | android:id="@+id/notice_text" | ||
| 32 | style="@style/TextAppearance.Material3.TitleLarge" | ||
| 33 | android:layout_width="match_parent" | ||
| 34 | android:layout_height="wrap_content" | ||
| 35 | android:gravity="center" | ||
| 36 | android:paddingTop="8dp" | ||
| 37 | android:text="@string/search_settings" | ||
| 38 | tools:visibility="visible" /> | ||
| 39 | |||
| 40 | </LinearLayout> | ||
| 41 | |||
| 42 | <androidx.recyclerview.widget.RecyclerView | ||
| 43 | android:id="@+id/settings_list" | ||
| 44 | android:layout_width="match_parent" | ||
| 45 | android:layout_height="match_parent" | ||
| 46 | android:clipToPadding="false" /> | ||
| 47 | |||
| 48 | </RelativeLayout> | ||
| 49 | |||
| 50 | <FrameLayout | ||
| 51 | android:id="@+id/frame_search" | ||
| 52 | android:layout_width="match_parent" | ||
| 53 | android:layout_height="wrap_content" | ||
| 54 | android:clipToPadding="false" | ||
| 55 | app:layout_constraintEnd_toEndOf="parent" | ||
| 56 | app:layout_constraintStart_toStartOf="parent" | ||
| 57 | app:layout_constraintTop_toTopOf="parent"> | ||
| 58 | |||
| 59 | <com.google.android.material.card.MaterialCardView | ||
| 60 | android:id="@+id/search_background" | ||
| 61 | style="?attr/materialCardViewFilledStyle" | ||
| 62 | android:layout_width="match_parent" | ||
| 63 | android:layout_height="56dp" | ||
| 64 | app:cardCornerRadius="28dp"> | ||
| 65 | |||
| 66 | <LinearLayout | ||
| 67 | android:id="@+id/search_container" | ||
| 68 | android:layout_width="match_parent" | ||
| 69 | android:layout_height="match_parent" | ||
| 70 | android:layout_marginEnd="56dp" | ||
| 71 | android:orientation="horizontal"> | ||
| 72 | |||
| 73 | <Button | ||
| 74 | android:id="@+id/back_button" | ||
| 75 | style="?attr/materialIconButtonFilledTonalStyle" | ||
| 76 | android:layout_width="wrap_content" | ||
| 77 | android:layout_height="wrap_content" | ||
| 78 | android:layout_gravity="center_vertical" | ||
| 79 | android:layout_marginStart="8dp" | ||
| 80 | app:backgroundTint="@android:color/transparent" | ||
| 81 | app:icon="@drawable/ic_back" /> | ||
| 82 | |||
| 83 | <EditText | ||
| 84 | android:id="@+id/search_text" | ||
| 85 | android:layout_width="match_parent" | ||
| 86 | android:layout_height="match_parent" | ||
| 87 | android:background="@android:color/transparent" | ||
| 88 | android:hint="@string/search_settings" | ||
| 89 | android:imeOptions="flagNoFullscreen" | ||
| 90 | android:inputType="text" | ||
| 91 | android:maxLines="1" /> | ||
| 92 | |||
| 93 | </LinearLayout> | ||
| 94 | |||
| 95 | <Button | ||
| 96 | android:id="@+id/clear_button" | ||
| 97 | style="?attr/materialIconButtonFilledTonalStyle" | ||
| 98 | android:layout_width="wrap_content" | ||
| 99 | android:layout_height="wrap_content" | ||
| 100 | android:layout_gravity="center_vertical|end" | ||
| 101 | android:layout_marginEnd="8dp" | ||
| 102 | android:visibility="invisible" | ||
| 103 | app:backgroundTint="@android:color/transparent" | ||
| 104 | app:icon="@drawable/ic_clear" | ||
| 105 | tools:visibility="visible" /> | ||
| 106 | |||
| 107 | </com.google.android.material.card.MaterialCardView> | ||
| 108 | |||
| 109 | </FrameLayout> | ||
| 110 | |||
| 111 | <com.google.android.material.divider.MaterialDivider | ||
| 112 | android:id="@+id/divider" | ||
| 113 | android:layout_width="match_parent" | ||
| 114 | android:layout_height="wrap_content" | ||
| 115 | android:layout_marginTop="20dp" | ||
| 116 | app:layout_constraintEnd_toEndOf="parent" | ||
| 117 | app:layout_constraintStart_toStartOf="parent" | ||
| 118 | app:layout_constraintTop_toBottomOf="@+id/frame_search" /> | ||
| 119 | |||
| 120 | </androidx.constraintlayout.widget.ConstraintLayout> | ||
diff --git a/src/android/app/src/main/res/menu/menu_settings.xml b/src/android/app/src/main/res/menu/menu_settings.xml index 1fe7aa6d4..21501a471 100644 --- a/src/android/app/src/main/res/menu/menu_settings.xml +++ b/src/android/app/src/main/res/menu/menu_settings.xml | |||
| @@ -1,2 +1,11 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | 1 | <?xml version="1.0" encoding="utf-8"?> |
| 2 | <menu /> \ No newline at end of file | 2 | <menu xmlns:android="http://schemas.android.com/apk/res/android" |
| 3 | xmlns:app="http://schemas.android.com/apk/res-auto"> | ||
| 4 | |||
| 5 | <item | ||
| 6 | android:id="@+id/action_search" | ||
| 7 | android:icon="@drawable/ic_search" | ||
| 8 | android:title="@string/home_search" | ||
| 9 | app:showAsAction="always" /> | ||
| 10 | |||
| 11 | </menu> | ||
diff --git a/src/android/app/src/main/res/navigation/emulation_navigation.xml b/src/android/app/src/main/res/navigation/emulation_navigation.xml index 8208f4c2c..c7be37f9b 100644 --- a/src/android/app/src/main/res/navigation/emulation_navigation.xml +++ b/src/android/app/src/main/res/navigation/emulation_navigation.xml | |||
| @@ -12,7 +12,26 @@ | |||
| 12 | tools:layout="@layout/fragment_emulation" > | 12 | tools:layout="@layout/fragment_emulation" > |
| 13 | <argument | 13 | <argument |
| 14 | android:name="game" | 14 | android:name="game" |
| 15 | app:argType="org.yuzu.yuzu_emu.model.Game" /> | 15 | app:argType="org.yuzu.yuzu_emu.model.Game" |
| 16 | app:nullable="true" | ||
| 17 | android:defaultValue="@null" /> | ||
| 16 | </fragment> | 18 | </fragment> |
| 17 | 19 | ||
| 20 | <activity | ||
| 21 | android:id="@+id/settingsActivity" | ||
| 22 | android:name="org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity" | ||
| 23 | android:label="SettingsActivity"> | ||
| 24 | <argument | ||
| 25 | android:name="game" | ||
| 26 | app:argType="org.yuzu.yuzu_emu.model.Game" | ||
| 27 | app:nullable="true" /> | ||
| 28 | <argument | ||
| 29 | android:name="menuTag" | ||
| 30 | app:argType="string" /> | ||
| 31 | </activity> | ||
| 32 | |||
| 33 | <action | ||
| 34 | android:id="@+id/action_global_settingsActivity" | ||
| 35 | app:destination="@id/settingsActivity" /> | ||
| 36 | |||
| 18 | </navigation> | 37 | </navigation> |
diff --git a/src/android/app/src/main/res/navigation/home_navigation.xml b/src/android/app/src/main/res/navigation/home_navigation.xml index fcebba726..2085430bf 100644 --- a/src/android/app/src/main/res/navigation/home_navigation.xml +++ b/src/android/app/src/main/res/navigation/home_navigation.xml | |||
| @@ -62,7 +62,9 @@ | |||
| 62 | android:label="EmulationActivity"> | 62 | android:label="EmulationActivity"> |
| 63 | <argument | 63 | <argument |
| 64 | android:name="game" | 64 | android:name="game" |
| 65 | app:argType="org.yuzu.yuzu_emu.model.Game" /> | 65 | app:argType="org.yuzu.yuzu_emu.model.Game" |
| 66 | app:nullable="true" | ||
| 67 | android:defaultValue="@null" /> | ||
| 66 | </activity> | 68 | </activity> |
| 67 | 69 | ||
| 68 | <action | 70 | <action |
| @@ -70,4 +72,21 @@ | |||
| 70 | app:destination="@id/emulationActivity" | 72 | app:destination="@id/emulationActivity" |
| 71 | app:launchSingleTop="true" /> | 73 | app:launchSingleTop="true" /> |
| 72 | 74 | ||
| 75 | <activity | ||
| 76 | android:id="@+id/settingsActivity" | ||
| 77 | android:name="org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity" | ||
| 78 | android:label="SettingsActivity"> | ||
| 79 | <argument | ||
| 80 | android:name="game" | ||
| 81 | app:argType="org.yuzu.yuzu_emu.model.Game" | ||
| 82 | app:nullable="true" /> | ||
| 83 | <argument | ||
| 84 | android:name="menuTag" | ||
| 85 | app:argType="string" /> | ||
| 86 | </activity> | ||
| 87 | |||
| 88 | <action | ||
| 89 | android:id="@+id/action_global_settingsActivity" | ||
| 90 | app:destination="@id/settingsActivity" /> | ||
| 91 | |||
| 73 | </navigation> | 92 | </navigation> |
diff --git a/src/android/app/src/main/res/navigation/settings_navigation.xml b/src/android/app/src/main/res/navigation/settings_navigation.xml new file mode 100644 index 000000000..88e1b4587 --- /dev/null +++ b/src/android/app/src/main/res/navigation/settings_navigation.xml | |||
| @@ -0,0 +1,32 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <navigation xmlns:android="http://schemas.android.com/apk/res/android" | ||
| 3 | xmlns:app="http://schemas.android.com/apk/res-auto" | ||
| 4 | android:id="@+id/settings_navigation" | ||
| 5 | app:startDestination="@id/settingsFragment"> | ||
| 6 | |||
| 7 | <fragment | ||
| 8 | android:id="@+id/settingsFragment" | ||
| 9 | android:name="org.yuzu.yuzu_emu.features.settings.ui.SettingsFragment" | ||
| 10 | android:label="SettingsFragment"> | ||
| 11 | <argument | ||
| 12 | android:name="menuTag" | ||
| 13 | app:argType="string" /> | ||
| 14 | <argument | ||
| 15 | android:name="game" | ||
| 16 | app:argType="org.yuzu.yuzu_emu.model.Game" | ||
| 17 | app:nullable="true" /> | ||
| 18 | <action | ||
| 19 | android:id="@+id/action_settingsFragment_to_settingsSearchFragment" | ||
| 20 | app:destination="@id/settingsSearchFragment" /> | ||
| 21 | </fragment> | ||
| 22 | |||
| 23 | <action | ||
| 24 | android:id="@+id/action_global_settingsFragment" | ||
| 25 | app:destination="@id/settingsFragment" /> | ||
| 26 | |||
| 27 | <fragment | ||
| 28 | android:id="@+id/settingsSearchFragment" | ||
| 29 | android:name="org.yuzu.yuzu_emu.fragments.SettingsSearchFragment" | ||
| 30 | android:label="SettingsSearchFragment" /> | ||
| 31 | |||
| 32 | </navigation> | ||
diff --git a/src/android/app/src/main/res/values-de/strings.xml b/src/android/app/src/main/res/values-de/strings.xml index 0c1d91264..daaa7ffde 100644 --- a/src/android/app/src/main/res/values-de/strings.xml +++ b/src/android/app/src/main/res/values-de/strings.xml | |||
| @@ -209,7 +209,6 @@ | |||
| 209 | <string name="emulation_pause">Emulation pausieren</string> | 209 | <string name="emulation_pause">Emulation pausieren</string> |
| 210 | <string name="emulation_unpause">Emulation fortsetzen</string> | 210 | <string name="emulation_unpause">Emulation fortsetzen</string> |
| 211 | <string name="emulation_input_overlay">Overlay-Optionen</string> | 211 | <string name="emulation_input_overlay">Overlay-Optionen</string> |
| 212 | <string name="emulation_game_loading">Spiel lädt…</string> | ||
| 213 | 212 | ||
| 214 | <string name="load_settings">Lädt Einstellungen...</string> | 213 | <string name="load_settings">Lädt Einstellungen...</string> |
| 215 | 214 | ||
diff --git a/src/android/app/src/main/res/values-es/strings.xml b/src/android/app/src/main/res/values-es/strings.xml index 357f956d1..e9129cb00 100644 --- a/src/android/app/src/main/res/values-es/strings.xml +++ b/src/android/app/src/main/res/values-es/strings.xml | |||
| @@ -213,7 +213,6 @@ | |||
| 213 | <string name="emulation_pause">Pausar Emulación</string> | 213 | <string name="emulation_pause">Pausar Emulación</string> |
| 214 | <string name="emulation_unpause">Reanudar Emulación</string> | 214 | <string name="emulation_unpause">Reanudar Emulación</string> |
| 215 | <string name="emulation_input_overlay">Opciones de pantalla </string> | 215 | <string name="emulation_input_overlay">Opciones de pantalla </string> |
| 216 | <string name="emulation_game_loading">Cargando juego...</string> | ||
| 217 | 216 | ||
| 218 | <string name="load_settings">Cargando configuración...</string> | 217 | <string name="load_settings">Cargando configuración...</string> |
| 219 | 218 | ||
diff --git a/src/android/app/src/main/res/values-fr/strings.xml b/src/android/app/src/main/res/values-fr/strings.xml index dfca1c830..2d99d618e 100644 --- a/src/android/app/src/main/res/values-fr/strings.xml +++ b/src/android/app/src/main/res/values-fr/strings.xml | |||
| @@ -213,7 +213,6 @@ | |||
| 213 | <string name="emulation_pause">Mettre en pause l\'émulation</string> | 213 | <string name="emulation_pause">Mettre en pause l\'émulation</string> |
| 214 | <string name="emulation_unpause">Reprendre l\'émulation</string> | 214 | <string name="emulation_unpause">Reprendre l\'émulation</string> |
| 215 | <string name="emulation_input_overlay">Options de l\'overlay</string> | 215 | <string name="emulation_input_overlay">Options de l\'overlay</string> |
| 216 | <string name="emulation_game_loading">Chargement du jeu...</string> | ||
| 217 | 216 | ||
| 218 | <string name="load_settings">Chargement des paramètres…</string> | 217 | <string name="load_settings">Chargement des paramètres…</string> |
| 219 | 218 | ||
diff --git a/src/android/app/src/main/res/values-it/strings.xml b/src/android/app/src/main/res/values-it/strings.xml index 089d93ed6..d9c3de385 100644 --- a/src/android/app/src/main/res/values-it/strings.xml +++ b/src/android/app/src/main/res/values-it/strings.xml | |||
| @@ -213,7 +213,6 @@ | |||
| 213 | <string name="emulation_pause">Metti in pausa l\'emulazione</string> | 213 | <string name="emulation_pause">Metti in pausa l\'emulazione</string> |
| 214 | <string name="emulation_unpause">Riprendi Emulazione</string> | 214 | <string name="emulation_unpause">Riprendi Emulazione</string> |
| 215 | <string name="emulation_input_overlay">Impostazioni Overlay</string> | 215 | <string name="emulation_input_overlay">Impostazioni Overlay</string> |
| 216 | <string name="emulation_game_loading">Caricamento del gioco...</string> | ||
| 217 | 216 | ||
| 218 | <string name="load_settings">Caricamento delle impostazioni...</string> | 217 | <string name="load_settings">Caricamento delle impostazioni...</string> |
| 219 | 218 | ||
diff --git a/src/android/app/src/main/res/values-ja/strings.xml b/src/android/app/src/main/res/values-ja/strings.xml index 39b590bee..7a226cd5c 100644 --- a/src/android/app/src/main/res/values-ja/strings.xml +++ b/src/android/app/src/main/res/values-ja/strings.xml | |||
| @@ -211,7 +211,6 @@ | |||
| 211 | <string name="emulation_pause">エミュレーションを一時停止</string> | 211 | <string name="emulation_pause">エミュレーションを一時停止</string> |
| 212 | <string name="emulation_unpause">エミュレーションを再開</string> | 212 | <string name="emulation_unpause">エミュレーションを再開</string> |
| 213 | <string name="emulation_input_overlay">オーバーレイオプション</string> | 213 | <string name="emulation_input_overlay">オーバーレイオプション</string> |
| 214 | <string name="emulation_game_loading">ロード中…</string> | ||
| 215 | 214 | ||
| 216 | <string name="load_settings">設定をロード中…</string> | 215 | <string name="load_settings">設定をロード中…</string> |
| 217 | 216 | ||
diff --git a/src/android/app/src/main/res/values-ko/strings.xml b/src/android/app/src/main/res/values-ko/strings.xml index cbcb2873f..427b6e5a0 100644 --- a/src/android/app/src/main/res/values-ko/strings.xml +++ b/src/android/app/src/main/res/values-ko/strings.xml | |||
| @@ -213,7 +213,6 @@ | |||
| 213 | <string name="emulation_pause">에뮬레이션 일시 중지</string> | 213 | <string name="emulation_pause">에뮬레이션 일시 중지</string> |
| 214 | <string name="emulation_unpause">에뮬레이션 일시 중지 해제</string> | 214 | <string name="emulation_unpause">에뮬레이션 일시 중지 해제</string> |
| 215 | <string name="emulation_input_overlay">오버레이 옵션</string> | 215 | <string name="emulation_input_overlay">오버레이 옵션</string> |
| 216 | <string name="emulation_game_loading">게임 불러오기 중...</string> | ||
| 217 | 216 | ||
| 218 | <string name="load_settings">설정 불러오기 중...</string> | 217 | <string name="load_settings">설정 불러오기 중...</string> |
| 219 | 218 | ||
diff --git a/src/android/app/src/main/res/values-nb/strings.xml b/src/android/app/src/main/res/values-nb/strings.xml index e48a4be38..ce8d7a9e4 100644 --- a/src/android/app/src/main/res/values-nb/strings.xml +++ b/src/android/app/src/main/res/values-nb/strings.xml | |||
| @@ -213,7 +213,6 @@ | |||
| 213 | <string name="emulation_pause">Pause Emulering</string> | 213 | <string name="emulation_pause">Pause Emulering</string> |
| 214 | <string name="emulation_unpause">Opphev pausing av emulering</string> | 214 | <string name="emulation_unpause">Opphev pausing av emulering</string> |
| 215 | <string name="emulation_input_overlay">Alternativer for overlegg</string> | 215 | <string name="emulation_input_overlay">Alternativer for overlegg</string> |
| 216 | <string name="emulation_game_loading">Spillet lastes inn...</string> | ||
| 217 | 216 | ||
| 218 | <string name="load_settings">Laster inn innstillinger...</string> | 217 | <string name="load_settings">Laster inn innstillinger...</string> |
| 219 | 218 | ||
diff --git a/src/android/app/src/main/res/values-pl/strings.xml b/src/android/app/src/main/res/values-pl/strings.xml index bc9c0f7f4..c2c24b48f 100644 --- a/src/android/app/src/main/res/values-pl/strings.xml +++ b/src/android/app/src/main/res/values-pl/strings.xml | |||
| @@ -213,7 +213,6 @@ | |||
| 213 | <string name="emulation_pause">Wstrzymaj emulację</string> | 213 | <string name="emulation_pause">Wstrzymaj emulację</string> |
| 214 | <string name="emulation_unpause">Wznów emulację</string> | 214 | <string name="emulation_unpause">Wznów emulację</string> |
| 215 | <string name="emulation_input_overlay">Opcje nakładki</string> | 215 | <string name="emulation_input_overlay">Opcje nakładki</string> |
| 216 | <string name="emulation_game_loading">Wczytywanie gry...</string> | ||
| 217 | 216 | ||
| 218 | <string name="load_settings">Wczytywanie ustawień...</string> | 217 | <string name="load_settings">Wczytywanie ustawień...</string> |
| 219 | 218 | ||
diff --git a/src/android/app/src/main/res/values-pt-rBR/strings.xml b/src/android/app/src/main/res/values-pt-rBR/strings.xml index 75fe0edbf..04f276108 100644 --- a/src/android/app/src/main/res/values-pt-rBR/strings.xml +++ b/src/android/app/src/main/res/values-pt-rBR/strings.xml | |||
| @@ -213,7 +213,6 @@ | |||
| 213 | <string name="emulation_pause">Pausa emulação</string> | 213 | <string name="emulation_pause">Pausa emulação</string> |
| 214 | <string name="emulation_unpause">Retomar emulação</string> | 214 | <string name="emulation_unpause">Retomar emulação</string> |
| 215 | <string name="emulation_input_overlay">Opções de sobreposição </string> | 215 | <string name="emulation_input_overlay">Opções de sobreposição </string> |
| 216 | <string name="emulation_game_loading">Jogo a carregar...</string> | ||
| 217 | 216 | ||
| 218 | <string name="load_settings">Configurações a carregar...</string> | 217 | <string name="load_settings">Configurações a carregar...</string> |
| 219 | 218 | ||
diff --git a/src/android/app/src/main/res/values-pt-rPT/strings.xml b/src/android/app/src/main/res/values-pt-rPT/strings.xml index 96b040c66..66a3a1a2e 100644 --- a/src/android/app/src/main/res/values-pt-rPT/strings.xml +++ b/src/android/app/src/main/res/values-pt-rPT/strings.xml | |||
| @@ -213,7 +213,6 @@ | |||
| 213 | <string name="emulation_pause">Pausa emulação</string> | 213 | <string name="emulation_pause">Pausa emulação</string> |
| 214 | <string name="emulation_unpause">Retomar emulação</string> | 214 | <string name="emulation_unpause">Retomar emulação</string> |
| 215 | <string name="emulation_input_overlay">Opções de sobreposição </string> | 215 | <string name="emulation_input_overlay">Opções de sobreposição </string> |
| 216 | <string name="emulation_game_loading">Jogo a carregar...</string> | ||
| 217 | 216 | ||
| 218 | <string name="load_settings">Configurações a carregar...</string> | 217 | <string name="load_settings">Configurações a carregar...</string> |
| 219 | 218 | ||
diff --git a/src/android/app/src/main/res/values-ru/strings.xml b/src/android/app/src/main/res/values-ru/strings.xml index 8d954f59e..f770e954f 100644 --- a/src/android/app/src/main/res/values-ru/strings.xml +++ b/src/android/app/src/main/res/values-ru/strings.xml | |||
| @@ -213,7 +213,6 @@ | |||
| 213 | <string name="emulation_pause">Пауза эмуляции</string> | 213 | <string name="emulation_pause">Пауза эмуляции</string> |
| 214 | <string name="emulation_unpause">Возобновление эмуляции</string> | 214 | <string name="emulation_unpause">Возобновление эмуляции</string> |
| 215 | <string name="emulation_input_overlay">Настройки оверлея</string> | 215 | <string name="emulation_input_overlay">Настройки оверлея</string> |
| 216 | <string name="emulation_game_loading">Загрузка игры...</string> | ||
| 217 | 216 | ||
| 218 | <string name="load_settings">Загрузка настроек...</string> | 217 | <string name="load_settings">Загрузка настроек...</string> |
| 219 | 218 | ||
diff --git a/src/android/app/src/main/res/values-uk/strings.xml b/src/android/app/src/main/res/values-uk/strings.xml index 6c028535b..ea3ab1b15 100644 --- a/src/android/app/src/main/res/values-uk/strings.xml +++ b/src/android/app/src/main/res/values-uk/strings.xml | |||
| @@ -213,7 +213,6 @@ | |||
| 213 | <string name="emulation_pause">Пауза емуляції</string> | 213 | <string name="emulation_pause">Пауза емуляції</string> |
| 214 | <string name="emulation_unpause">Відновлення емуляції</string> | 214 | <string name="emulation_unpause">Відновлення емуляції</string> |
| 215 | <string name="emulation_input_overlay">Налаштування оверлея</string> | 215 | <string name="emulation_input_overlay">Налаштування оверлея</string> |
| 216 | <string name="emulation_game_loading">Завантаження гри...</string> | ||
| 217 | 216 | ||
| 218 | <string name="load_settings">Завантаження налаштувань...</string> | 217 | <string name="load_settings">Завантаження налаштувань...</string> |
| 219 | 218 | ||
diff --git a/src/android/app/src/main/res/values-zh-rCN/strings.xml b/src/android/app/src/main/res/values-zh-rCN/strings.xml index e4ad2ed07..b45a5a528 100644 --- a/src/android/app/src/main/res/values-zh-rCN/strings.xml +++ b/src/android/app/src/main/res/values-zh-rCN/strings.xml | |||
| @@ -213,7 +213,6 @@ | |||
| 213 | <string name="emulation_pause">暂停模拟</string> | 213 | <string name="emulation_pause">暂停模拟</string> |
| 214 | <string name="emulation_unpause">继续模拟</string> | 214 | <string name="emulation_unpause">继续模拟</string> |
| 215 | <string name="emulation_input_overlay">虚拟按键选项</string> | 215 | <string name="emulation_input_overlay">虚拟按键选项</string> |
| 216 | <string name="emulation_game_loading">载入游戏中…</string> | ||
| 217 | 216 | ||
| 218 | <string name="load_settings">正在载入设定…</string> | 217 | <string name="load_settings">正在载入设定…</string> |
| 219 | 218 | ||
diff --git a/src/android/app/src/main/res/values-zh-rTW/strings.xml b/src/android/app/src/main/res/values-zh-rTW/strings.xml index 0d32f23df..3aab889e4 100644 --- a/src/android/app/src/main/res/values-zh-rTW/strings.xml +++ b/src/android/app/src/main/res/values-zh-rTW/strings.xml | |||
| @@ -213,7 +213,6 @@ | |||
| 213 | <string name="emulation_pause">暫停模擬</string> | 213 | <string name="emulation_pause">暫停模擬</string> |
| 214 | <string name="emulation_unpause">取消暫停模擬</string> | 214 | <string name="emulation_unpause">取消暫停模擬</string> |
| 215 | <string name="emulation_input_overlay">覆疊選項</string> | 215 | <string name="emulation_input_overlay">覆疊選項</string> |
| 216 | <string name="emulation_game_loading">遊戲正在載入…</string> | ||
| 217 | 216 | ||
| 218 | <string name="load_settings">正在載入設定…</string> | 217 | <string name="load_settings">正在載入設定…</string> |
| 219 | 218 | ||
diff --git a/src/android/app/src/main/res/values/arrays.xml b/src/android/app/src/main/res/values/arrays.xml index 200b99185..dc10159c9 100644 --- a/src/android/app/src/main/res/values/arrays.xml +++ b/src/android/app/src/main/res/values/arrays.xml | |||
| @@ -243,10 +243,10 @@ | |||
| 243 | <item>@string/cubeb</item> | 243 | <item>@string/cubeb</item> |
| 244 | <item>@string/string_null</item> | 244 | <item>@string/string_null</item> |
| 245 | </string-array> | 245 | </string-array> |
| 246 | <string-array name="outputEngineValues"> | 246 | <integer-array name="outputEngineValues"> |
| 247 | <item>auto</item> | 247 | <item>0</item> |
| 248 | <item>cubeb</item> | 248 | <item>1</item> |
| 249 | <item>null</item> | 249 | <item>3</item> |
| 250 | </string-array> | 250 | </integer-array> |
| 251 | 251 | ||
| 252 | </resources> | 252 | </resources> |
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index de1b2909b..b163e6fc1 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml | |||
| @@ -43,6 +43,7 @@ | |||
| 43 | <string name="add_games_warning_description">Games won\'t be displayed in the Games list if a folder isn\'t selected.</string> | 43 | <string name="add_games_warning_description">Games won\'t be displayed in the Games list if a folder isn\'t selected.</string> |
| 44 | <string name="add_games_warning_help">https://yuzu-emu.org/help/quickstart/#dumping-games</string> | 44 | <string name="add_games_warning_help">https://yuzu-emu.org/help/quickstart/#dumping-games</string> |
| 45 | <string name="home_search_games">Search games</string> | 45 | <string name="home_search_games">Search games</string> |
| 46 | <string name="search_settings">Search settings</string> | ||
| 46 | <string name="games_dir_selected">Games directory selected</string> | 47 | <string name="games_dir_selected">Games directory selected</string> |
| 47 | <string name="install_prod_keys">Install prod.keys</string> | 48 | <string name="install_prod_keys">Install prod.keys</string> |
| 48 | <string name="install_prod_keys_description">Required to decrypt retail games</string> | 49 | <string name="install_prod_keys_description">Required to decrypt retail games</string> |
| @@ -74,6 +75,7 @@ | |||
| 74 | <string name="install_gpu_driver">Install GPU driver</string> | 75 | <string name="install_gpu_driver">Install GPU driver</string> |
| 75 | <string name="install_gpu_driver_description">Install alternative drivers for potentially better performance or accuracy</string> | 76 | <string name="install_gpu_driver_description">Install alternative drivers for potentially better performance or accuracy</string> |
| 76 | <string name="advanced_settings">Advanced settings</string> | 77 | <string name="advanced_settings">Advanced settings</string> |
| 78 | <string name="advanced_settings_game">Advanced settings: %1$s</string> | ||
| 77 | <string name="settings_description">Configure emulator settings</string> | 79 | <string name="settings_description">Configure emulator settings</string> |
| 78 | <string name="search_recently_played">Recently played</string> | 80 | <string name="search_recently_played">Recently played</string> |
| 79 | <string name="search_recently_added">Recently added</string> | 81 | <string name="search_recently_added">Recently added</string> |
| @@ -200,7 +202,9 @@ | |||
| 200 | <string name="ini_saved">Saved settings</string> | 202 | <string name="ini_saved">Saved settings</string> |
| 201 | <string name="gameid_saved">Saved settings for %1$s</string> | 203 | <string name="gameid_saved">Saved settings for %1$s</string> |
| 202 | <string name="error_saving">Error saving %1$s.ini: %2$s</string> | 204 | <string name="error_saving">Error saving %1$s.ini: %2$s</string> |
| 205 | <string name="unimplemented_menu">Unimplemented Menu</string> | ||
| 203 | <string name="loading">Loading…</string> | 206 | <string name="loading">Loading…</string> |
| 207 | <string name="shutting_down">Shutting down…</string> | ||
| 204 | <string name="reset_setting_confirmation">Do you want to reset this setting back to its default value?</string> | 208 | <string name="reset_setting_confirmation">Do you want to reset this setting back to its default value?</string> |
| 205 | <string name="reset_to_default">Reset to default</string> | 209 | <string name="reset_to_default">Reset to default</string> |
| 206 | <string name="reset_all_settings">Reset all settings?</string> | 210 | <string name="reset_all_settings">Reset all settings?</string> |
| @@ -259,7 +263,6 @@ | |||
| 259 | <string name="emulation_pause">Pause emulation</string> | 263 | <string name="emulation_pause">Pause emulation</string> |
| 260 | <string name="emulation_unpause">Unpause emulation</string> | 264 | <string name="emulation_unpause">Unpause emulation</string> |
| 261 | <string name="emulation_input_overlay">Overlay options</string> | 265 | <string name="emulation_input_overlay">Overlay options</string> |
| 262 | <string name="emulation_game_loading">Game loading…</string> | ||
| 263 | 266 | ||
| 264 | <string name="load_settings">Loading settings…</string> | 267 | <string name="load_settings">Loading settings…</string> |
| 265 | 268 | ||
diff --git a/src/audio_core/renderer/system.cpp b/src/audio_core/renderer/system.cpp index a23627472..6e07baa54 100644 --- a/src/audio_core/renderer/system.cpp +++ b/src/audio_core/renderer/system.cpp | |||
| @@ -778,7 +778,7 @@ u32 System::DropVoices(CommandBuffer& command_buffer, u32 estimated_process_time | |||
| 778 | while (i < command_buffer.count) { | 778 | while (i < command_buffer.count) { |
| 779 | const auto node_id{cmd->node_id}; | 779 | const auto node_id{cmd->node_id}; |
| 780 | const auto node_id_type{cmd->node_id >> 28}; | 780 | const auto node_id_type{cmd->node_id >> 28}; |
| 781 | const auto node_id_base{cmd->node_id & 0xFFF}; | 781 | const auto node_id_base{(cmd->node_id >> 16) & 0xFFF}; |
| 782 | 782 | ||
| 783 | // If the new estimated process time falls below the limit, we're done dropping. | 783 | // If the new estimated process time falls below the limit, we're done dropping. |
| 784 | if (estimated_process_time <= time_limit) { | 784 | if (estimated_process_time <= time_limit) { |
diff --git a/src/common/settings.cpp b/src/common/settings.cpp index 524056841..4ecaf550b 100644 --- a/src/common/settings.cpp +++ b/src/common/settings.cpp | |||
| @@ -159,6 +159,8 @@ float Volume() { | |||
| 159 | 159 | ||
| 160 | const char* TranslateCategory(Category category) { | 160 | const char* TranslateCategory(Category category) { |
| 161 | switch (category) { | 161 | switch (category) { |
| 162 | case Category::Android: | ||
| 163 | return "Android"; | ||
| 162 | case Category::Audio: | 164 | case Category::Audio: |
| 163 | return "Audio"; | 165 | return "Audio"; |
| 164 | case Category::Core: | 166 | case Category::Core: |
diff --git a/src/common/settings_common.cpp b/src/common/settings_common.cpp index 137b65d5f..5960b78aa 100644 --- a/src/common/settings_common.cpp +++ b/src/common/settings_common.cpp | |||
| @@ -14,6 +14,7 @@ BasicSetting::BasicSetting(Linkage& linkage, const std::string& name, enum Categ | |||
| 14 | : label{name}, category{category_}, id{linkage.count}, save{save_}, | 14 | : label{name}, category{category_}, id{linkage.count}, save{save_}, |
| 15 | runtime_modifiable{runtime_modifiable_}, specialization{specialization_}, | 15 | runtime_modifiable{runtime_modifiable_}, specialization{specialization_}, |
| 16 | other_setting{other_setting_} { | 16 | other_setting{other_setting_} { |
| 17 | linkage.by_key.insert({name, this}); | ||
| 17 | linkage.by_category[category].push_back(this); | 18 | linkage.by_category[category].push_back(this); |
| 18 | linkage.count++; | 19 | linkage.count++; |
| 19 | } | 20 | } |
diff --git a/src/common/settings_common.h b/src/common/settings_common.h index 3082e0ce1..5b170dfd5 100644 --- a/src/common/settings_common.h +++ b/src/common/settings_common.h | |||
| @@ -12,6 +12,7 @@ | |||
| 12 | namespace Settings { | 12 | namespace Settings { |
| 13 | 13 | ||
| 14 | enum class Category : u32 { | 14 | enum class Category : u32 { |
| 15 | Android, | ||
| 15 | Audio, | 16 | Audio, |
| 16 | Core, | 17 | Core, |
| 17 | Cpu, | 18 | Cpu, |
| @@ -68,6 +69,7 @@ public: | |||
| 68 | explicit Linkage(u32 initial_count = 0); | 69 | explicit Linkage(u32 initial_count = 0); |
| 69 | ~Linkage(); | 70 | ~Linkage(); |
| 70 | std::map<Category, std::vector<BasicSetting*>> by_category{}; | 71 | std::map<Category, std::vector<BasicSetting*>> by_category{}; |
| 72 | std::map<std::string, Settings::BasicSetting*> by_key{}; | ||
| 71 | std::vector<std::function<void()>> restore_functions{}; | 73 | std::vector<std::function<void()>> restore_functions{}; |
| 72 | u32 count; | 74 | u32 count; |
| 73 | }; | 75 | }; |
diff --git a/src/core/core.cpp b/src/core/core.cpp index 2f67e60a9..e95ae80da 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp | |||
| @@ -273,7 +273,8 @@ struct System::Impl { | |||
| 273 | time_manager.Initialize(); | 273 | time_manager.Initialize(); |
| 274 | 274 | ||
| 275 | is_powered_on = true; | 275 | is_powered_on = true; |
| 276 | exit_lock = false; | 276 | exit_locked = false; |
| 277 | exit_requested = false; | ||
| 277 | 278 | ||
| 278 | microprofile_cpu[0] = MICROPROFILE_TOKEN(ARM_CPU0); | 279 | microprofile_cpu[0] = MICROPROFILE_TOKEN(ARM_CPU0); |
| 279 | microprofile_cpu[1] = MICROPROFILE_TOKEN(ARM_CPU1); | 280 | microprofile_cpu[1] = MICROPROFILE_TOKEN(ARM_CPU1); |
| @@ -398,7 +399,8 @@ struct System::Impl { | |||
| 398 | } | 399 | } |
| 399 | 400 | ||
| 400 | is_powered_on = false; | 401 | is_powered_on = false; |
| 401 | exit_lock = false; | 402 | exit_locked = false; |
| 403 | exit_requested = false; | ||
| 402 | 404 | ||
| 403 | if (gpu_core != nullptr) { | 405 | if (gpu_core != nullptr) { |
| 404 | gpu_core->NotifyShutdown(); | 406 | gpu_core->NotifyShutdown(); |
| @@ -507,7 +509,8 @@ struct System::Impl { | |||
| 507 | 509 | ||
| 508 | CpuManager cpu_manager; | 510 | CpuManager cpu_manager; |
| 509 | std::atomic_bool is_powered_on{}; | 511 | std::atomic_bool is_powered_on{}; |
| 510 | bool exit_lock = false; | 512 | bool exit_locked = false; |
| 513 | bool exit_requested = false; | ||
| 511 | 514 | ||
| 512 | bool nvdec_active{}; | 515 | bool nvdec_active{}; |
| 513 | 516 | ||
| @@ -943,12 +946,20 @@ const Service::Time::TimeManager& System::GetTimeManager() const { | |||
| 943 | return impl->time_manager; | 946 | return impl->time_manager; |
| 944 | } | 947 | } |
| 945 | 948 | ||
| 946 | void System::SetExitLock(bool locked) { | 949 | void System::SetExitLocked(bool locked) { |
| 947 | impl->exit_lock = locked; | 950 | impl->exit_locked = locked; |
| 948 | } | 951 | } |
| 949 | 952 | ||
| 950 | bool System::GetExitLock() const { | 953 | bool System::GetExitLocked() const { |
| 951 | return impl->exit_lock; | 954 | return impl->exit_locked; |
| 955 | } | ||
| 956 | |||
| 957 | void System::SetExitRequested(bool requested) { | ||
| 958 | impl->exit_requested = requested; | ||
| 959 | } | ||
| 960 | |||
| 961 | bool System::GetExitRequested() const { | ||
| 962 | return impl->exit_requested; | ||
| 952 | } | 963 | } |
| 953 | 964 | ||
| 954 | void System::SetApplicationProcessBuildID(const CurrentBuildProcessID& id) { | 965 | void System::SetApplicationProcessBuildID(const CurrentBuildProcessID& id) { |
diff --git a/src/core/core.h b/src/core/core.h index c70ea1965..a9ff9315e 100644 --- a/src/core/core.h +++ b/src/core/core.h | |||
| @@ -412,8 +412,11 @@ public: | |||
| 412 | /// Gets an immutable reference to the Room Network. | 412 | /// Gets an immutable reference to the Room Network. |
| 413 | [[nodiscard]] const Network::RoomNetwork& GetRoomNetwork() const; | 413 | [[nodiscard]] const Network::RoomNetwork& GetRoomNetwork() const; |
| 414 | 414 | ||
| 415 | void SetExitLock(bool locked); | 415 | void SetExitLocked(bool locked); |
| 416 | [[nodiscard]] bool GetExitLock() const; | 416 | bool GetExitLocked() const; |
| 417 | |||
| 418 | void SetExitRequested(bool requested); | ||
| 419 | bool GetExitRequested() const; | ||
| 417 | 420 | ||
| 418 | void SetApplicationProcessBuildID(const CurrentBuildProcessID& id); | 421 | void SetApplicationProcessBuildID(const CurrentBuildProcessID& id); |
| 419 | [[nodiscard]] const CurrentBuildProcessID& GetApplicationProcessBuildID() const; | 422 | [[nodiscard]] const CurrentBuildProcessID& GetApplicationProcessBuildID() const; |
diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp index 44e6852fe..7d2f0abb8 100644 --- a/src/core/file_sys/content_archive.cpp +++ b/src/core/file_sys/content_archive.cpp | |||
| @@ -22,6 +22,10 @@ | |||
| 22 | 22 | ||
| 23 | namespace FileSys { | 23 | namespace FileSys { |
| 24 | 24 | ||
| 25 | static u8 MasterKeyIdForKeyGeneration(u8 key_generation) { | ||
| 26 | return std::max<u8>(key_generation, 1) - 1; | ||
| 27 | } | ||
| 28 | |||
| 25 | NCA::NCA(VirtualFile file_, const NCA* base_nca) | 29 | NCA::NCA(VirtualFile file_, const NCA* base_nca) |
| 26 | : file(std::move(file_)), keys{Core::Crypto::KeyManager::Instance()} { | 30 | : file(std::move(file_)), keys{Core::Crypto::KeyManager::Instance()} { |
| 27 | if (file == nullptr) { | 31 | if (file == nullptr) { |
| @@ -41,12 +45,17 @@ NCA::NCA(VirtualFile file_, const NCA* base_nca) | |||
| 41 | return; | 45 | return; |
| 42 | } | 46 | } |
| 43 | 47 | ||
| 48 | // Ensure we have the proper key area keys to continue. | ||
| 49 | const u8 master_key_id = MasterKeyIdForKeyGeneration(reader->GetKeyGeneration()); | ||
| 50 | if (!keys.HasKey(Core::Crypto::S128KeyType::KeyArea, master_key_id, reader->GetKeyIndex())) { | ||
| 51 | status = Loader::ResultStatus::ErrorMissingKeyAreaKey; | ||
| 52 | return; | ||
| 53 | } | ||
| 54 | |||
| 44 | RightsId rights_id{}; | 55 | RightsId rights_id{}; |
| 45 | reader->GetRightsId(rights_id.data(), rights_id.size()); | 56 | reader->GetRightsId(rights_id.data(), rights_id.size()); |
| 46 | if (rights_id != RightsId{}) { | 57 | if (rights_id != RightsId{}) { |
| 47 | // External decryption key required; provide it here. | 58 | // External decryption key required; provide it here. |
| 48 | const auto key_generation = std::max<s32>(reader->GetKeyGeneration(), 1) - 1; | ||
| 49 | |||
| 50 | u128 rights_id_u128; | 59 | u128 rights_id_u128; |
| 51 | std::memcpy(rights_id_u128.data(), rights_id.data(), sizeof(rights_id)); | 60 | std::memcpy(rights_id_u128.data(), rights_id.data(), sizeof(rights_id)); |
| 52 | 61 | ||
| @@ -57,12 +66,12 @@ NCA::NCA(VirtualFile file_, const NCA* base_nca) | |||
| 57 | return; | 66 | return; |
| 58 | } | 67 | } |
| 59 | 68 | ||
| 60 | if (!keys.HasKey(Core::Crypto::S128KeyType::Titlekek, key_generation)) { | 69 | if (!keys.HasKey(Core::Crypto::S128KeyType::Titlekek, master_key_id)) { |
| 61 | status = Loader::ResultStatus::ErrorMissingTitlekek; | 70 | status = Loader::ResultStatus::ErrorMissingTitlekek; |
| 62 | return; | 71 | return; |
| 63 | } | 72 | } |
| 64 | 73 | ||
| 65 | auto titlekek = keys.GetKey(Core::Crypto::S128KeyType::Titlekek, key_generation); | 74 | auto titlekek = keys.GetKey(Core::Crypto::S128KeyType::Titlekek, master_key_id); |
| 66 | Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(titlekek, Core::Crypto::Mode::ECB); | 75 | Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(titlekek, Core::Crypto::Mode::ECB); |
| 67 | cipher.Transcode(titlekey.data(), titlekey.size(), titlekey.data(), | 76 | cipher.Transcode(titlekey.data(), titlekey.size(), titlekey.data(), |
| 68 | Core::Crypto::Op::Decrypt); | 77 | Core::Crypto::Op::Decrypt); |
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index da33f0e44..e92f400de 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp | |||
| @@ -341,7 +341,7 @@ void ISelfController::Exit(HLERequestContext& ctx) { | |||
| 341 | void ISelfController::LockExit(HLERequestContext& ctx) { | 341 | void ISelfController::LockExit(HLERequestContext& ctx) { |
| 342 | LOG_DEBUG(Service_AM, "called"); | 342 | LOG_DEBUG(Service_AM, "called"); |
| 343 | 343 | ||
| 344 | system.SetExitLock(true); | 344 | system.SetExitLocked(true); |
| 345 | 345 | ||
| 346 | IPC::ResponseBuilder rb{ctx, 2}; | 346 | IPC::ResponseBuilder rb{ctx, 2}; |
| 347 | rb.Push(ResultSuccess); | 347 | rb.Push(ResultSuccess); |
| @@ -350,10 +350,14 @@ void ISelfController::LockExit(HLERequestContext& ctx) { | |||
| 350 | void ISelfController::UnlockExit(HLERequestContext& ctx) { | 350 | void ISelfController::UnlockExit(HLERequestContext& ctx) { |
| 351 | LOG_DEBUG(Service_AM, "called"); | 351 | LOG_DEBUG(Service_AM, "called"); |
| 352 | 352 | ||
| 353 | system.SetExitLock(false); | 353 | system.SetExitLocked(false); |
| 354 | 354 | ||
| 355 | IPC::ResponseBuilder rb{ctx, 2}; | 355 | IPC::ResponseBuilder rb{ctx, 2}; |
| 356 | rb.Push(ResultSuccess); | 356 | rb.Push(ResultSuccess); |
| 357 | |||
| 358 | if (system.GetExitRequested()) { | ||
| 359 | system.Exit(); | ||
| 360 | } | ||
| 357 | } | 361 | } |
| 358 | 362 | ||
| 359 | void ISelfController::EnterFatalSection(HLERequestContext& ctx) { | 363 | void ISelfController::EnterFatalSection(HLERequestContext& ctx) { |
diff --git a/src/core/hle/service/audio/hwopus.cpp b/src/core/hle/service/audio/hwopus.cpp index 4a8276ed1..1557e6088 100644 --- a/src/core/hle/service/audio/hwopus.cpp +++ b/src/core/hle/service/audio/hwopus.cpp | |||
| @@ -267,6 +267,10 @@ void HwOpus::GetWorkBufferSizeEx(HLERequestContext& ctx) { | |||
| 267 | GetWorkBufferSize(ctx); | 267 | GetWorkBufferSize(ctx); |
| 268 | } | 268 | } |
| 269 | 269 | ||
| 270 | void HwOpus::GetWorkBufferSizeExEx(HLERequestContext& ctx) { | ||
| 271 | GetWorkBufferSizeEx(ctx); | ||
| 272 | } | ||
| 273 | |||
| 270 | void HwOpus::GetWorkBufferSizeForMultiStreamEx(HLERequestContext& ctx) { | 274 | void HwOpus::GetWorkBufferSizeForMultiStreamEx(HLERequestContext& ctx) { |
| 271 | OpusMultiStreamParametersEx param; | 275 | OpusMultiStreamParametersEx param; |
| 272 | std::memcpy(¶m, ctx.ReadBuffer().data(), ctx.GetReadBufferSize()); | 276 | std::memcpy(¶m, ctx.ReadBuffer().data(), ctx.GetReadBufferSize()); |
| @@ -409,7 +413,7 @@ HwOpus::HwOpus(Core::System& system_) : ServiceFramework{system_, "hwopus"} { | |||
| 409 | {6, &HwOpus::OpenHardwareOpusDecoderForMultiStreamEx, | 413 | {6, &HwOpus::OpenHardwareOpusDecoderForMultiStreamEx, |
| 410 | "OpenHardwareOpusDecoderForMultiStreamEx"}, | 414 | "OpenHardwareOpusDecoderForMultiStreamEx"}, |
| 411 | {7, &HwOpus::GetWorkBufferSizeForMultiStreamEx, "GetWorkBufferSizeForMultiStreamEx"}, | 415 | {7, &HwOpus::GetWorkBufferSizeForMultiStreamEx, "GetWorkBufferSizeForMultiStreamEx"}, |
| 412 | {8, nullptr, "GetWorkBufferSizeExEx"}, | 416 | {8, &HwOpus::GetWorkBufferSizeExEx, "GetWorkBufferSizeExEx"}, |
| 413 | {9, nullptr, "GetWorkBufferSizeForMultiStreamExEx"}, | 417 | {9, nullptr, "GetWorkBufferSizeForMultiStreamExEx"}, |
| 414 | }; | 418 | }; |
| 415 | RegisterHandlers(functions); | 419 | RegisterHandlers(functions); |
diff --git a/src/core/hle/service/audio/hwopus.h b/src/core/hle/service/audio/hwopus.h index 91d9998ac..90867bf74 100644 --- a/src/core/hle/service/audio/hwopus.h +++ b/src/core/hle/service/audio/hwopus.h | |||
| @@ -34,6 +34,7 @@ private: | |||
| 34 | void OpenHardwareOpusDecoderForMultiStreamEx(HLERequestContext& ctx); | 34 | void OpenHardwareOpusDecoderForMultiStreamEx(HLERequestContext& ctx); |
| 35 | void GetWorkBufferSize(HLERequestContext& ctx); | 35 | void GetWorkBufferSize(HLERequestContext& ctx); |
| 36 | void GetWorkBufferSizeEx(HLERequestContext& ctx); | 36 | void GetWorkBufferSizeEx(HLERequestContext& ctx); |
| 37 | void GetWorkBufferSizeExEx(HLERequestContext& ctx); | ||
| 37 | void GetWorkBufferSizeForMultiStreamEx(HLERequestContext& ctx); | 38 | void GetWorkBufferSizeForMultiStreamEx(HLERequestContext& ctx); |
| 38 | }; | 39 | }; |
| 39 | 40 | ||
diff --git a/src/core/hle/service/sockets/nsd.cpp b/src/core/hle/service/sockets/nsd.cpp index bac21752a..491b76d48 100644 --- a/src/core/hle/service/sockets/nsd.cpp +++ b/src/core/hle/service/sockets/nsd.cpp | |||
| @@ -19,6 +19,12 @@ enum class ServerEnvironmentType : u8 { | |||
| 19 | Dp, | 19 | Dp, |
| 20 | }; | 20 | }; |
| 21 | 21 | ||
| 22 | // This is nn::nsd::EnvironmentIdentifier | ||
| 23 | struct EnvironmentIdentifier { | ||
| 24 | std::array<u8, 8> identifier; | ||
| 25 | }; | ||
| 26 | static_assert(sizeof(EnvironmentIdentifier) == 0x8); | ||
| 27 | |||
| 22 | NSD::NSD(Core::System& system_, const char* name) : ServiceFramework{system_, name} { | 28 | NSD::NSD(Core::System& system_, const char* name) : ServiceFramework{system_, name} { |
| 23 | // clang-format off | 29 | // clang-format off |
| 24 | static const FunctionInfo functions[] = { | 30 | static const FunctionInfo functions[] = { |
| @@ -101,8 +107,9 @@ void NSD::ResolveEx(HLERequestContext& ctx) { | |||
| 101 | } | 107 | } |
| 102 | 108 | ||
| 103 | void NSD::GetEnvironmentIdentifier(HLERequestContext& ctx) { | 109 | void NSD::GetEnvironmentIdentifier(HLERequestContext& ctx) { |
| 104 | const std::string environment_identifier = "lp1"; | 110 | constexpr EnvironmentIdentifier lp1 = { |
| 105 | ctx.WriteBuffer(environment_identifier); | 111 | .identifier = {'l', 'p', '1', '\0', '\0', '\0', '\0', '\0'}}; |
| 112 | ctx.WriteBuffer(lp1); | ||
| 106 | 113 | ||
| 107 | IPC::ResponseBuilder rb{ctx, 2}; | 114 | IPC::ResponseBuilder rb{ctx, 2}; |
| 108 | rb.Push(ResultSuccess); | 115 | rb.Push(ResultSuccess); |
diff --git a/src/core/hle/service/sockets/sfdnsres.cpp b/src/core/hle/service/sockets/sfdnsres.cpp index 22e4a6f49..c657c4efd 100644 --- a/src/core/hle/service/sockets/sfdnsres.cpp +++ b/src/core/hle/service/sockets/sfdnsres.cpp | |||
| @@ -150,6 +150,12 @@ static std::pair<u32, GetAddrInfoError> GetHostByNameRequestImpl(HLERequestConte | |||
| 150 | const std::string host = Common::StringFromBuffer(host_buffer); | 150 | const std::string host = Common::StringFromBuffer(host_buffer); |
| 151 | // For now, ignore options, which are in input buffer 1 for GetHostByNameRequestWithOptions. | 151 | // For now, ignore options, which are in input buffer 1 for GetHostByNameRequestWithOptions. |
| 152 | 152 | ||
| 153 | // Prevent resolution of Nintendo servers | ||
| 154 | if (host.find("srv.nintendo.net") != std::string::npos) { | ||
| 155 | LOG_WARNING(Network, "Resolution of hostname {} requested, returning EAI_AGAIN", host); | ||
| 156 | return {0, GetAddrInfoError::AGAIN}; | ||
| 157 | } | ||
| 158 | |||
| 153 | auto res = Network::GetAddressInfo(host, /*service*/ std::nullopt); | 159 | auto res = Network::GetAddressInfo(host, /*service*/ std::nullopt); |
| 154 | if (!res.has_value()) { | 160 | if (!res.has_value()) { |
| 155 | return {0, Translate(res.error())}; | 161 | return {0, Translate(res.error())}; |
| @@ -261,6 +267,12 @@ static std::pair<u32, GetAddrInfoError> GetAddrInfoRequestImpl(HLERequestContext | |||
| 261 | const auto host_buffer = ctx.ReadBuffer(0); | 267 | const auto host_buffer = ctx.ReadBuffer(0); |
| 262 | const std::string host = Common::StringFromBuffer(host_buffer); | 268 | const std::string host = Common::StringFromBuffer(host_buffer); |
| 263 | 269 | ||
| 270 | // Prevent resolution of Nintendo servers | ||
| 271 | if (host.find("srv.nintendo.net") != std::string::npos) { | ||
| 272 | LOG_WARNING(Network, "Resolution of hostname {} requested, returning EAI_AGAIN", host); | ||
| 273 | return {0, GetAddrInfoError::AGAIN}; | ||
| 274 | } | ||
| 275 | |||
| 264 | std::optional<std::string> service = std::nullopt; | 276 | std::optional<std::string> service = std::nullopt; |
| 265 | if (ctx.CanReadBuffer(1)) { | 277 | if (ctx.CanReadBuffer(1)) { |
| 266 | const std::span<const u8> service_buffer = ctx.ReadBuffer(1); | 278 | const std::span<const u8> service_buffer = ctx.ReadBuffer(1); |
diff --git a/src/shader_recompiler/backend/glsl/emit_glsl_image.cpp b/src/shader_recompiler/backend/glsl/emit_glsl_image.cpp index 3ad668a47..d9872ecc2 100644 --- a/src/shader_recompiler/backend/glsl/emit_glsl_image.cpp +++ b/src/shader_recompiler/backend/glsl/emit_glsl_image.cpp | |||
| @@ -558,7 +558,7 @@ void EmitImageGradient(EmitContext& ctx, IR::Inst& inst, const IR::Value& index, | |||
| 558 | if (multi_component) { | 558 | if (multi_component) { |
| 559 | if (info.num_derivates >= 3) { | 559 | if (info.num_derivates >= 3) { |
| 560 | const auto offset_vec{ctx.var_alloc.Consume(offset)}; | 560 | const auto offset_vec{ctx.var_alloc.Consume(offset)}; |
| 561 | ctx.Add("{}=textureGrad({},{},vec3({}.xz, {}.x),vec3({}.yz, {}.y));", texel, texture, | 561 | ctx.Add("{}=textureGrad({},{},vec3({}.xz, {}.x),vec3({}.yw, {}.y));", texel, texture, |
| 562 | coords, derivatives_vec, offset_vec, derivatives_vec, offset_vec); | 562 | coords, derivatives_vec, offset_vec, derivatives_vec, offset_vec); |
| 563 | return; | 563 | return; |
| 564 | } | 564 | } |
diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp index 7d901c04b..34240b36f 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp | |||
| @@ -91,6 +91,34 @@ public: | |||
| 91 | } | 91 | } |
| 92 | } | 92 | } |
| 93 | 93 | ||
| 94 | explicit ImageOperands(EmitContext& ctx, bool has_lod_clamp, Id derivates_1, Id derivates_2, | ||
| 95 | Id offset, Id lod_clamp) { | ||
| 96 | if (!Sirit::ValidId(derivates_1) || !Sirit::ValidId(derivates_2)) { | ||
| 97 | throw LogicError("Derivates must be present"); | ||
| 98 | } | ||
| 99 | boost::container::static_vector<Id, 3> deriv_1_accum{ | ||
| 100 | ctx.OpCompositeExtract(ctx.F32[1], derivates_1, 0), | ||
| 101 | ctx.OpCompositeExtract(ctx.F32[1], derivates_1, 2), | ||
| 102 | ctx.OpCompositeExtract(ctx.F32[1], derivates_2, 0), | ||
| 103 | }; | ||
| 104 | boost::container::static_vector<Id, 3> deriv_2_accum{ | ||
| 105 | ctx.OpCompositeExtract(ctx.F32[1], derivates_1, 1), | ||
| 106 | ctx.OpCompositeExtract(ctx.F32[1], derivates_1, 3), | ||
| 107 | ctx.OpCompositeExtract(ctx.F32[1], derivates_2, 1), | ||
| 108 | }; | ||
| 109 | const Id derivates_id1{ctx.OpCompositeConstruct( | ||
| 110 | ctx.F32[3], std::span{deriv_1_accum.data(), deriv_1_accum.size()})}; | ||
| 111 | const Id derivates_id2{ctx.OpCompositeConstruct( | ||
| 112 | ctx.F32[3], std::span{deriv_2_accum.data(), deriv_2_accum.size()})}; | ||
| 113 | Add(spv::ImageOperandsMask::Grad, derivates_id1, derivates_id2); | ||
| 114 | if (Sirit::ValidId(offset)) { | ||
| 115 | Add(spv::ImageOperandsMask::Offset, offset); | ||
| 116 | } | ||
| 117 | if (has_lod_clamp) { | ||
| 118 | Add(spv::ImageOperandsMask::MinLod, lod_clamp); | ||
| 119 | } | ||
| 120 | } | ||
| 121 | |||
| 94 | std::span<const Id> Span() const noexcept { | 122 | std::span<const Id> Span() const noexcept { |
| 95 | return std::span{operands.data(), operands.size()}; | 123 | return std::span{operands.data(), operands.size()}; |
| 96 | } | 124 | } |
| @@ -524,8 +552,11 @@ Id EmitImageQueryLod(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, I | |||
| 524 | Id EmitImageGradient(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords, | 552 | Id EmitImageGradient(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords, |
| 525 | Id derivates, Id offset, Id lod_clamp) { | 553 | Id derivates, Id offset, Id lod_clamp) { |
| 526 | const auto info{inst->Flags<IR::TextureInstInfo>()}; | 554 | const auto info{inst->Flags<IR::TextureInstInfo>()}; |
| 527 | const ImageOperands operands(ctx, info.has_lod_clamp != 0, derivates, info.num_derivates, | 555 | const auto operands = |
| 528 | offset, lod_clamp); | 556 | info.num_derivates == 3 |
| 557 | ? ImageOperands(ctx, info.has_lod_clamp != 0, derivates, offset, {}, lod_clamp) | ||
| 558 | : ImageOperands(ctx, info.has_lod_clamp != 0, derivates, info.num_derivates, offset, | ||
| 559 | lod_clamp); | ||
| 529 | return Emit(&EmitContext::OpImageSparseSampleExplicitLod, | 560 | return Emit(&EmitContext::OpImageSparseSampleExplicitLod, |
| 530 | &EmitContext::OpImageSampleExplicitLod, ctx, inst, ctx.F32[4], | 561 | &EmitContext::OpImageSampleExplicitLod, ctx, inst, ctx.F32[4], |
| 531 | Texture(ctx, info, index), coords, operands.Mask(), operands.Span()); | 562 | Texture(ctx, info, index), coords, operands.Mask(), operands.Span()); |
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/move_special_register.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/move_special_register.cpp index 753c62098..e593132e6 100644 --- a/src/shader_recompiler/frontend/maxwell/translate/impl/move_special_register.cpp +++ b/src/shader_recompiler/frontend/maxwell/translate/impl/move_special_register.cpp | |||
| @@ -161,7 +161,8 @@ enum class SpecialRegister : u64 { | |||
| 161 | LOG_WARNING(Shader, "(STUBBED) SR_AFFINITY"); | 161 | LOG_WARNING(Shader, "(STUBBED) SR_AFFINITY"); |
| 162 | return ir.Imm32(0); // This is the default value hardware returns. | 162 | return ir.Imm32(0); // This is the default value hardware returns. |
| 163 | default: | 163 | default: |
| 164 | throw NotImplementedException("S2R special register {}", special_register); | 164 | LOG_CRITICAL(Shader, "(STUBBED) Special register {}", special_register); |
| 165 | return ir.Imm32(0); // This is the default value hardware returns. | ||
| 165 | } | 166 | } |
| 166 | } | 167 | } |
| 167 | } // Anonymous namespace | 168 | } // Anonymous namespace |
diff --git a/src/video_core/dma_pusher.cpp b/src/video_core/dma_pusher.cpp index 9f1b340a9..58ce0d8c2 100644 --- a/src/video_core/dma_pusher.cpp +++ b/src/video_core/dma_pusher.cpp | |||
| @@ -14,6 +14,7 @@ | |||
| 14 | namespace Tegra { | 14 | namespace Tegra { |
| 15 | 15 | ||
| 16 | constexpr u32 MacroRegistersStart = 0xE00; | 16 | constexpr u32 MacroRegistersStart = 0xE00; |
| 17 | constexpr u32 ComputeInline = 0x6D; | ||
| 17 | 18 | ||
| 18 | DmaPusher::DmaPusher(Core::System& system_, GPU& gpu_, MemoryManager& memory_manager_, | 19 | DmaPusher::DmaPusher(Core::System& system_, GPU& gpu_, MemoryManager& memory_manager_, |
| 19 | Control::ChannelState& channel_state_) | 20 | Control::ChannelState& channel_state_) |
| @@ -83,12 +84,35 @@ bool DmaPusher::Step() { | |||
| 83 | dma_state.dma_get, command_list_header.size * sizeof(u32)); | 84 | dma_state.dma_get, command_list_header.size * sizeof(u32)); |
| 84 | } | 85 | } |
| 85 | } | 86 | } |
| 86 | Core::Memory::GpuGuestMemory<Tegra::CommandHeader, | 87 | const auto safe_process = [&] { |
| 87 | Core::Memory::GuestMemoryFlags::UnsafeRead> | 88 | Core::Memory::GpuGuestMemory<Tegra::CommandHeader, |
| 88 | headers(memory_manager, dma_state.dma_get, command_list_header.size, &command_headers); | 89 | Core::Memory::GuestMemoryFlags::SafeRead> |
| 89 | ProcessCommands(headers); | 90 | headers(memory_manager, dma_state.dma_get, command_list_header.size, |
| 91 | &command_headers); | ||
| 92 | ProcessCommands(headers); | ||
| 93 | }; | ||
| 94 | const auto unsafe_process = [&] { | ||
| 95 | Core::Memory::GpuGuestMemory<Tegra::CommandHeader, | ||
| 96 | Core::Memory::GuestMemoryFlags::UnsafeRead> | ||
| 97 | headers(memory_manager, dma_state.dma_get, command_list_header.size, | ||
| 98 | &command_headers); | ||
| 99 | ProcessCommands(headers); | ||
| 100 | }; | ||
| 101 | if (Settings::IsGPULevelHigh()) { | ||
| 102 | if (dma_state.method >= MacroRegistersStart) { | ||
| 103 | unsafe_process(); | ||
| 104 | return true; | ||
| 105 | } | ||
| 106 | if (subchannel_type[dma_state.subchannel] == Engines::EngineTypes::KeplerCompute && | ||
| 107 | dma_state.method == ComputeInline) { | ||
| 108 | unsafe_process(); | ||
| 109 | return true; | ||
| 110 | } | ||
| 111 | safe_process(); | ||
| 112 | return true; | ||
| 113 | } | ||
| 114 | unsafe_process(); | ||
| 90 | } | 115 | } |
| 91 | |||
| 92 | return true; | 116 | return true; |
| 93 | } | 117 | } |
| 94 | 118 | ||
diff --git a/src/video_core/dma_pusher.h b/src/video_core/dma_pusher.h index 8a2784cdc..c9fab2d90 100644 --- a/src/video_core/dma_pusher.h +++ b/src/video_core/dma_pusher.h | |||
| @@ -130,8 +130,10 @@ public: | |||
| 130 | 130 | ||
| 131 | void DispatchCalls(); | 131 | void DispatchCalls(); |
| 132 | 132 | ||
| 133 | void BindSubchannel(Engines::EngineInterface* engine, u32 subchannel_id) { | 133 | void BindSubchannel(Engines::EngineInterface* engine, u32 subchannel_id, |
| 134 | Engines::EngineTypes engine_type) { | ||
| 134 | subchannels[subchannel_id] = engine; | 135 | subchannels[subchannel_id] = engine; |
| 136 | subchannel_type[subchannel_id] = engine_type; | ||
| 135 | } | 137 | } |
| 136 | 138 | ||
| 137 | void BindRasterizer(VideoCore::RasterizerInterface* rasterizer); | 139 | void BindRasterizer(VideoCore::RasterizerInterface* rasterizer); |
| @@ -170,6 +172,7 @@ private: | |||
| 170 | const bool ib_enable{true}; ///< IB mode enabled | 172 | const bool ib_enable{true}; ///< IB mode enabled |
| 171 | 173 | ||
| 172 | std::array<Engines::EngineInterface*, max_subchannels> subchannels{}; | 174 | std::array<Engines::EngineInterface*, max_subchannels> subchannels{}; |
| 175 | std::array<Engines::EngineTypes, max_subchannels> subchannel_type; | ||
| 173 | 176 | ||
| 174 | GPU& gpu; | 177 | GPU& gpu; |
| 175 | Core::System& system; | 178 | Core::System& system; |
diff --git a/src/video_core/engines/engine_interface.h b/src/video_core/engines/engine_interface.h index 392322358..54631ee6c 100644 --- a/src/video_core/engines/engine_interface.h +++ b/src/video_core/engines/engine_interface.h | |||
| @@ -11,6 +11,14 @@ | |||
| 11 | 11 | ||
| 12 | namespace Tegra::Engines { | 12 | namespace Tegra::Engines { |
| 13 | 13 | ||
| 14 | enum class EngineTypes : u32 { | ||
| 15 | KeplerCompute, | ||
| 16 | Maxwell3D, | ||
| 17 | Fermi2D, | ||
| 18 | MaxwellDMA, | ||
| 19 | KeplerMemory, | ||
| 20 | }; | ||
| 21 | |||
| 14 | class EngineInterface { | 22 | class EngineInterface { |
| 15 | public: | 23 | public: |
| 16 | virtual ~EngineInterface() = default; | 24 | virtual ~EngineInterface() = default; |
diff --git a/src/video_core/engines/engine_upload.h b/src/video_core/engines/engine_upload.h index 7242d2529..21bf8aeb4 100644 --- a/src/video_core/engines/engine_upload.h +++ b/src/video_core/engines/engine_upload.h | |||
| @@ -69,6 +69,14 @@ public: | |||
| 69 | /// Binds a rasterizer to this engine. | 69 | /// Binds a rasterizer to this engine. |
| 70 | void BindRasterizer(VideoCore::RasterizerInterface* rasterizer); | 70 | void BindRasterizer(VideoCore::RasterizerInterface* rasterizer); |
| 71 | 71 | ||
| 72 | GPUVAddr ExecTargetAddress() const { | ||
| 73 | return regs.dest.Address(); | ||
| 74 | } | ||
| 75 | |||
| 76 | u32 GetUploadSize() const { | ||
| 77 | return copy_size; | ||
| 78 | } | ||
| 79 | |||
| 72 | private: | 80 | private: |
| 73 | void ProcessData(std::span<const u8> read_buffer); | 81 | void ProcessData(std::span<const u8> read_buffer); |
| 74 | 82 | ||
diff --git a/src/video_core/engines/kepler_compute.cpp b/src/video_core/engines/kepler_compute.cpp index a38d9528a..cd61ab222 100644 --- a/src/video_core/engines/kepler_compute.cpp +++ b/src/video_core/engines/kepler_compute.cpp | |||
| @@ -43,16 +43,33 @@ void KeplerCompute::CallMethod(u32 method, u32 method_argument, bool is_last_cal | |||
| 43 | 43 | ||
| 44 | switch (method) { | 44 | switch (method) { |
| 45 | case KEPLER_COMPUTE_REG_INDEX(exec_upload): { | 45 | case KEPLER_COMPUTE_REG_INDEX(exec_upload): { |
| 46 | UploadInfo info{.upload_address = upload_address, | ||
| 47 | .exec_address = upload_state.ExecTargetAddress(), | ||
| 48 | .copy_size = upload_state.GetUploadSize()}; | ||
| 49 | uploads.push_back(info); | ||
| 46 | upload_state.ProcessExec(regs.exec_upload.linear != 0); | 50 | upload_state.ProcessExec(regs.exec_upload.linear != 0); |
| 47 | break; | 51 | break; |
| 48 | } | 52 | } |
| 49 | case KEPLER_COMPUTE_REG_INDEX(data_upload): { | 53 | case KEPLER_COMPUTE_REG_INDEX(data_upload): { |
| 54 | upload_address = current_dma_segment; | ||
| 50 | upload_state.ProcessData(method_argument, is_last_call); | 55 | upload_state.ProcessData(method_argument, is_last_call); |
| 51 | break; | 56 | break; |
| 52 | } | 57 | } |
| 53 | case KEPLER_COMPUTE_REG_INDEX(launch): | 58 | case KEPLER_COMPUTE_REG_INDEX(launch): { |
| 59 | const GPUVAddr launch_desc_loc = regs.launch_desc_loc.Address(); | ||
| 60 | |||
| 61 | for (auto& data : uploads) { | ||
| 62 | const GPUVAddr offset = data.exec_address - launch_desc_loc; | ||
| 63 | if (offset / sizeof(u32) == LAUNCH_REG_INDEX(grid_dim_x) && | ||
| 64 | memory_manager.IsMemoryDirty(data.upload_address, data.copy_size)) { | ||
| 65 | indirect_compute = {data.upload_address}; | ||
| 66 | } | ||
| 67 | } | ||
| 68 | uploads.clear(); | ||
| 54 | ProcessLaunch(); | 69 | ProcessLaunch(); |
| 70 | indirect_compute = std::nullopt; | ||
| 55 | break; | 71 | break; |
| 72 | } | ||
| 56 | default: | 73 | default: |
| 57 | break; | 74 | break; |
| 58 | } | 75 | } |
| @@ -62,6 +79,7 @@ void KeplerCompute::CallMultiMethod(u32 method, const u32* base_start, u32 amoun | |||
| 62 | u32 methods_pending) { | 79 | u32 methods_pending) { |
| 63 | switch (method) { | 80 | switch (method) { |
| 64 | case KEPLER_COMPUTE_REG_INDEX(data_upload): | 81 | case KEPLER_COMPUTE_REG_INDEX(data_upload): |
| 82 | upload_address = current_dma_segment; | ||
| 65 | upload_state.ProcessData(base_start, amount); | 83 | upload_state.ProcessData(base_start, amount); |
| 66 | return; | 84 | return; |
| 67 | default: | 85 | default: |
diff --git a/src/video_core/engines/kepler_compute.h b/src/video_core/engines/kepler_compute.h index 2092e685f..735e05fb4 100644 --- a/src/video_core/engines/kepler_compute.h +++ b/src/video_core/engines/kepler_compute.h | |||
| @@ -5,6 +5,7 @@ | |||
| 5 | 5 | ||
| 6 | #include <array> | 6 | #include <array> |
| 7 | #include <cstddef> | 7 | #include <cstddef> |
| 8 | #include <optional> | ||
| 8 | #include <vector> | 9 | #include <vector> |
| 9 | #include "common/bit_field.h" | 10 | #include "common/bit_field.h" |
| 10 | #include "common/common_funcs.h" | 11 | #include "common/common_funcs.h" |
| @@ -36,6 +37,9 @@ namespace Tegra::Engines { | |||
| 36 | #define KEPLER_COMPUTE_REG_INDEX(field_name) \ | 37 | #define KEPLER_COMPUTE_REG_INDEX(field_name) \ |
| 37 | (offsetof(Tegra::Engines::KeplerCompute::Regs, field_name) / sizeof(u32)) | 38 | (offsetof(Tegra::Engines::KeplerCompute::Regs, field_name) / sizeof(u32)) |
| 38 | 39 | ||
| 40 | #define LAUNCH_REG_INDEX(field_name) \ | ||
| 41 | (offsetof(Tegra::Engines::KeplerCompute::LaunchParams, field_name) / sizeof(u32)) | ||
| 42 | |||
| 39 | class KeplerCompute final : public EngineInterface { | 43 | class KeplerCompute final : public EngineInterface { |
| 40 | public: | 44 | public: |
| 41 | explicit KeplerCompute(Core::System& system, MemoryManager& memory_manager); | 45 | explicit KeplerCompute(Core::System& system, MemoryManager& memory_manager); |
| @@ -201,6 +205,10 @@ public: | |||
| 201 | void CallMultiMethod(u32 method, const u32* base_start, u32 amount, | 205 | void CallMultiMethod(u32 method, const u32* base_start, u32 amount, |
| 202 | u32 methods_pending) override; | 206 | u32 methods_pending) override; |
| 203 | 207 | ||
| 208 | std::optional<GPUVAddr> GetIndirectComputeAddress() const { | ||
| 209 | return indirect_compute; | ||
| 210 | } | ||
| 211 | |||
| 204 | private: | 212 | private: |
| 205 | void ProcessLaunch(); | 213 | void ProcessLaunch(); |
| 206 | 214 | ||
| @@ -216,6 +224,15 @@ private: | |||
| 216 | MemoryManager& memory_manager; | 224 | MemoryManager& memory_manager; |
| 217 | VideoCore::RasterizerInterface* rasterizer = nullptr; | 225 | VideoCore::RasterizerInterface* rasterizer = nullptr; |
| 218 | Upload::State upload_state; | 226 | Upload::State upload_state; |
| 227 | GPUVAddr upload_address; | ||
| 228 | |||
| 229 | struct UploadInfo { | ||
| 230 | GPUVAddr upload_address; | ||
| 231 | GPUVAddr exec_address; | ||
| 232 | u32 copy_size; | ||
| 233 | }; | ||
| 234 | std::vector<UploadInfo> uploads; | ||
| 235 | std::optional<GPUVAddr> indirect_compute{}; | ||
| 219 | }; | 236 | }; |
| 220 | 237 | ||
| 221 | #define ASSERT_REG_POSITION(field_name, position) \ | 238 | #define ASSERT_REG_POSITION(field_name, position) \ |
diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp index c3696096d..06e349e43 100644 --- a/src/video_core/engines/maxwell_3d.cpp +++ b/src/video_core/engines/maxwell_3d.cpp | |||
| @@ -257,6 +257,7 @@ u32 Maxwell3D::GetMaxCurrentVertices() { | |||
| 257 | const u32 address_size = static_cast<u32>(gpu_addr_end - gpu_addr_begin); | 257 | const u32 address_size = static_cast<u32>(gpu_addr_end - gpu_addr_begin); |
| 258 | num_vertices = std::max( | 258 | num_vertices = std::max( |
| 259 | num_vertices, address_size / std::max(attribute.SizeInBytes(), array.stride.Value())); | 259 | num_vertices, address_size / std::max(attribute.SizeInBytes(), array.stride.Value())); |
| 260 | break; | ||
| 260 | } | 261 | } |
| 261 | return num_vertices; | 262 | return num_vertices; |
| 262 | } | 263 | } |
| @@ -269,10 +270,13 @@ size_t Maxwell3D::EstimateIndexBufferSize() { | |||
| 269 | std::numeric_limits<u32>::max()}; | 270 | std::numeric_limits<u32>::max()}; |
| 270 | const size_t byte_size = regs.index_buffer.FormatSizeInBytes(); | 271 | const size_t byte_size = regs.index_buffer.FormatSizeInBytes(); |
| 271 | const size_t log2_byte_size = Common::Log2Ceil64(byte_size); | 272 | const size_t log2_byte_size = Common::Log2Ceil64(byte_size); |
| 273 | const size_t cap{GetMaxCurrentVertices() * 3 * byte_size}; | ||
| 274 | const size_t lower_cap = | ||
| 275 | std::min<size_t>(static_cast<size_t>(end_address - start_address), cap); | ||
| 272 | return std::min<size_t>( | 276 | return std::min<size_t>( |
| 273 | memory_manager.GetMemoryLayoutSize(start_address, byte_size * max_sizes[log2_byte_size]) / | 277 | memory_manager.GetMemoryLayoutSize(start_address, byte_size * max_sizes[log2_byte_size]) / |
| 274 | byte_size, | 278 | byte_size, |
| 275 | static_cast<size_t>(end_address - start_address)); | 279 | lower_cap); |
| 276 | } | 280 | } |
| 277 | 281 | ||
| 278 | u32 Maxwell3D::ProcessShadowRam(u32 method, u32 argument) { | 282 | u32 Maxwell3D::ProcessShadowRam(u32 method, u32 argument) { |
diff --git a/src/video_core/engines/puller.cpp b/src/video_core/engines/puller.cpp index 7718a09b3..6de2543b7 100644 --- a/src/video_core/engines/puller.cpp +++ b/src/video_core/engines/puller.cpp | |||
| @@ -34,19 +34,24 @@ void Puller::ProcessBindMethod(const MethodCall& method_call) { | |||
| 34 | bound_engines[method_call.subchannel] = engine_id; | 34 | bound_engines[method_call.subchannel] = engine_id; |
| 35 | switch (engine_id) { | 35 | switch (engine_id) { |
| 36 | case EngineID::FERMI_TWOD_A: | 36 | case EngineID::FERMI_TWOD_A: |
| 37 | dma_pusher.BindSubchannel(channel_state.fermi_2d.get(), method_call.subchannel); | 37 | dma_pusher.BindSubchannel(channel_state.fermi_2d.get(), method_call.subchannel, |
| 38 | EngineTypes::Fermi2D); | ||
| 38 | break; | 39 | break; |
| 39 | case EngineID::MAXWELL_B: | 40 | case EngineID::MAXWELL_B: |
| 40 | dma_pusher.BindSubchannel(channel_state.maxwell_3d.get(), method_call.subchannel); | 41 | dma_pusher.BindSubchannel(channel_state.maxwell_3d.get(), method_call.subchannel, |
| 42 | EngineTypes::Maxwell3D); | ||
| 41 | break; | 43 | break; |
| 42 | case EngineID::KEPLER_COMPUTE_B: | 44 | case EngineID::KEPLER_COMPUTE_B: |
| 43 | dma_pusher.BindSubchannel(channel_state.kepler_compute.get(), method_call.subchannel); | 45 | dma_pusher.BindSubchannel(channel_state.kepler_compute.get(), method_call.subchannel, |
| 46 | EngineTypes::KeplerCompute); | ||
| 44 | break; | 47 | break; |
| 45 | case EngineID::MAXWELL_DMA_COPY_A: | 48 | case EngineID::MAXWELL_DMA_COPY_A: |
| 46 | dma_pusher.BindSubchannel(channel_state.maxwell_dma.get(), method_call.subchannel); | 49 | dma_pusher.BindSubchannel(channel_state.maxwell_dma.get(), method_call.subchannel, |
| 50 | EngineTypes::MaxwellDMA); | ||
| 47 | break; | 51 | break; |
| 48 | case EngineID::KEPLER_INLINE_TO_MEMORY_B: | 52 | case EngineID::KEPLER_INLINE_TO_MEMORY_B: |
| 49 | dma_pusher.BindSubchannel(channel_state.kepler_memory.get(), method_call.subchannel); | 53 | dma_pusher.BindSubchannel(channel_state.kepler_memory.get(), method_call.subchannel, |
| 54 | EngineTypes::KeplerMemory); | ||
| 50 | break; | 55 | break; |
| 51 | default: | 56 | default: |
| 52 | UNIMPLEMENTED_MSG("Unimplemented engine {:04X}", engine_id); | 57 | UNIMPLEMENTED_MSG("Unimplemented engine {:04X}", engine_id); |
diff --git a/src/video_core/host1x/codecs/codec.cpp b/src/video_core/host1x/codecs/codec.cpp index 220cce28a..8d7da50fc 100644 --- a/src/video_core/host1x/codecs/codec.cpp +++ b/src/video_core/host1x/codecs/codec.cpp | |||
| @@ -319,6 +319,7 @@ void Codec::Decode() { | |||
| 319 | LOG_WARNING(Service_NVDRV, "Zero width or height in frame"); | 319 | LOG_WARNING(Service_NVDRV, "Zero width or height in frame"); |
| 320 | return; | 320 | return; |
| 321 | } | 321 | } |
| 322 | bool is_interlaced = initial_frame->interlaced_frame != 0; | ||
| 322 | if (av_codec_ctx->hw_device_ctx) { | 323 | if (av_codec_ctx->hw_device_ctx) { |
| 323 | final_frame = AVFramePtr{av_frame_alloc(), AVFrameDeleter}; | 324 | final_frame = AVFramePtr{av_frame_alloc(), AVFrameDeleter}; |
| 324 | ASSERT_MSG(final_frame, "av_frame_alloc final_frame failed"); | 325 | ASSERT_MSG(final_frame, "av_frame_alloc final_frame failed"); |
| @@ -334,7 +335,7 @@ void Codec::Decode() { | |||
| 334 | UNIMPLEMENTED_MSG("Unexpected video format: {}", final_frame->format); | 335 | UNIMPLEMENTED_MSG("Unexpected video format: {}", final_frame->format); |
| 335 | return; | 336 | return; |
| 336 | } | 337 | } |
| 337 | if (!final_frame->interlaced_frame) { | 338 | if (!is_interlaced) { |
| 338 | av_frames.push(std::move(final_frame)); | 339 | av_frames.push(std::move(final_frame)); |
| 339 | } else { | 340 | } else { |
| 340 | if (!filters_initialized) { | 341 | if (!filters_initialized) { |
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index 1ba31be88..dd03efecd 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp | |||
| @@ -380,6 +380,17 @@ void RasterizerOpenGL::DispatchCompute() { | |||
| 380 | pipeline->SetEngine(kepler_compute, gpu_memory); | 380 | pipeline->SetEngine(kepler_compute, gpu_memory); |
| 381 | pipeline->Configure(); | 381 | pipeline->Configure(); |
| 382 | const auto& qmd{kepler_compute->launch_description}; | 382 | const auto& qmd{kepler_compute->launch_description}; |
| 383 | auto indirect_address = kepler_compute->GetIndirectComputeAddress(); | ||
| 384 | if (indirect_address) { | ||
| 385 | // DispatchIndirect | ||
| 386 | static constexpr auto sync_info = VideoCommon::ObtainBufferSynchronize::FullSynchronize; | ||
| 387 | const auto post_op = VideoCommon::ObtainBufferOperation::DiscardWrite; | ||
| 388 | const auto [buffer, offset] = | ||
| 389 | buffer_cache.ObtainBuffer(*indirect_address, 12, sync_info, post_op); | ||
| 390 | glBindBuffer(GL_DISPATCH_INDIRECT_BUFFER, buffer->Handle()); | ||
| 391 | glDispatchComputeIndirect(static_cast<GLintptr>(offset)); | ||
| 392 | return; | ||
| 393 | } | ||
| 383 | glDispatchCompute(qmd.grid_dim_x, qmd.grid_dim_y, qmd.grid_dim_z); | 394 | glDispatchCompute(qmd.grid_dim_x, qmd.grid_dim_y, qmd.grid_dim_z); |
| 384 | ++num_queued_commands; | 395 | ++num_queued_commands; |
| 385 | has_written_global_memory |= pipeline->WritesGlobalMemory(); | 396 | has_written_global_memory |= pipeline->WritesGlobalMemory(); |
diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index fe432dfe1..4f83a88e1 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp | |||
| @@ -665,6 +665,19 @@ std::unique_ptr<GraphicsPipeline> PipelineCache::CreateGraphicsPipeline( | |||
| 665 | std::move(modules), infos); | 665 | std::move(modules), infos); |
| 666 | 666 | ||
| 667 | } catch (const Shader::Exception& exception) { | 667 | } catch (const Shader::Exception& exception) { |
| 668 | auto hash = key.Hash(); | ||
| 669 | size_t env_index{0}; | ||
| 670 | for (size_t index = 0; index < Maxwell::MaxShaderProgram; ++index) { | ||
| 671 | if (key.unique_hashes[index] == 0) { | ||
| 672 | continue; | ||
| 673 | } | ||
| 674 | Shader::Environment& env{*envs[env_index]}; | ||
| 675 | ++env_index; | ||
| 676 | |||
| 677 | const u32 cfg_offset{static_cast<u32>(env.StartAddress() + sizeof(Shader::ProgramHeader))}; | ||
| 678 | Shader::Maxwell::Flow::CFG cfg(env, pools.flow_block, cfg_offset, index == 0); | ||
| 679 | env.Dump(hash, key.unique_hashes[index]); | ||
| 680 | } | ||
| 668 | LOG_ERROR(Render_Vulkan, "{}", exception.what()); | 681 | LOG_ERROR(Render_Vulkan, "{}", exception.what()); |
| 669 | return nullptr; | 682 | return nullptr; |
| 670 | } | 683 | } |
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 032f694bc..01e76a82c 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp | |||
| @@ -463,6 +463,20 @@ void RasterizerVulkan::DispatchCompute() { | |||
| 463 | pipeline->Configure(*kepler_compute, *gpu_memory, scheduler, buffer_cache, texture_cache); | 463 | pipeline->Configure(*kepler_compute, *gpu_memory, scheduler, buffer_cache, texture_cache); |
| 464 | 464 | ||
| 465 | const auto& qmd{kepler_compute->launch_description}; | 465 | const auto& qmd{kepler_compute->launch_description}; |
| 466 | auto indirect_address = kepler_compute->GetIndirectComputeAddress(); | ||
| 467 | if (indirect_address) { | ||
| 468 | // DispatchIndirect | ||
| 469 | static constexpr auto sync_info = VideoCommon::ObtainBufferSynchronize::FullSynchronize; | ||
| 470 | const auto post_op = VideoCommon::ObtainBufferOperation::DiscardWrite; | ||
| 471 | const auto [buffer, offset] = | ||
| 472 | buffer_cache.ObtainBuffer(*indirect_address, 12, sync_info, post_op); | ||
| 473 | scheduler.RequestOutsideRenderPassOperationContext(); | ||
| 474 | scheduler.Record([indirect_buffer = buffer->Handle(), | ||
| 475 | indirect_offset = offset](vk::CommandBuffer cmdbuf) { | ||
| 476 | cmdbuf.DispatchIndirect(indirect_buffer, indirect_offset); | ||
| 477 | }); | ||
| 478 | return; | ||
| 479 | } | ||
| 466 | const std::array<u32, 3> dim{qmd.grid_dim_x, qmd.grid_dim_y, qmd.grid_dim_z}; | 480 | const std::array<u32, 3> dim{qmd.grid_dim_x, qmd.grid_dim_y, qmd.grid_dim_z}; |
| 467 | scheduler.RequestOutsideRenderPassOperationContext(); | 481 | scheduler.RequestOutsideRenderPassOperationContext(); |
| 468 | scheduler.Record([dim](vk::CommandBuffer cmdbuf) { cmdbuf.Dispatch(dim[0], dim[1], dim[2]); }); | 482 | scheduler.Record([dim](vk::CommandBuffer cmdbuf) { cmdbuf.Dispatch(dim[0], dim[1], dim[2]); }); |
diff --git a/src/video_core/vulkan_common/vulkan_wrapper.cpp b/src/video_core/vulkan_common/vulkan_wrapper.cpp index 78e5a248f..c3f388d89 100644 --- a/src/video_core/vulkan_common/vulkan_wrapper.cpp +++ b/src/video_core/vulkan_common/vulkan_wrapper.cpp | |||
| @@ -92,6 +92,7 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept { | |||
| 92 | X(vkCmdCopyImage); | 92 | X(vkCmdCopyImage); |
| 93 | X(vkCmdCopyImageToBuffer); | 93 | X(vkCmdCopyImageToBuffer); |
| 94 | X(vkCmdDispatch); | 94 | X(vkCmdDispatch); |
| 95 | X(vkCmdDispatchIndirect); | ||
| 95 | X(vkCmdDraw); | 96 | X(vkCmdDraw); |
| 96 | X(vkCmdDrawIndexed); | 97 | X(vkCmdDrawIndexed); |
| 97 | X(vkCmdDrawIndirect); | 98 | X(vkCmdDrawIndirect); |
diff --git a/src/video_core/vulkan_common/vulkan_wrapper.h b/src/video_core/vulkan_common/vulkan_wrapper.h index c226a2a29..049fa8038 100644 --- a/src/video_core/vulkan_common/vulkan_wrapper.h +++ b/src/video_core/vulkan_common/vulkan_wrapper.h | |||
| @@ -203,6 +203,7 @@ struct DeviceDispatch : InstanceDispatch { | |||
| 203 | PFN_vkCmdCopyImage vkCmdCopyImage{}; | 203 | PFN_vkCmdCopyImage vkCmdCopyImage{}; |
| 204 | PFN_vkCmdCopyImageToBuffer vkCmdCopyImageToBuffer{}; | 204 | PFN_vkCmdCopyImageToBuffer vkCmdCopyImageToBuffer{}; |
| 205 | PFN_vkCmdDispatch vkCmdDispatch{}; | 205 | PFN_vkCmdDispatch vkCmdDispatch{}; |
| 206 | PFN_vkCmdDispatchIndirect vkCmdDispatchIndirect{}; | ||
| 206 | PFN_vkCmdDraw vkCmdDraw{}; | 207 | PFN_vkCmdDraw vkCmdDraw{}; |
| 207 | PFN_vkCmdDrawIndexed vkCmdDrawIndexed{}; | 208 | PFN_vkCmdDrawIndexed vkCmdDrawIndexed{}; |
| 208 | PFN_vkCmdDrawIndirect vkCmdDrawIndirect{}; | 209 | PFN_vkCmdDrawIndirect vkCmdDrawIndirect{}; |
| @@ -1209,6 +1210,10 @@ public: | |||
| 1209 | dld->vkCmdDispatch(handle, x, y, z); | 1210 | dld->vkCmdDispatch(handle, x, y, z); |
| 1210 | } | 1211 | } |
| 1211 | 1212 | ||
| 1213 | void DispatchIndirect(VkBuffer indirect_buffer, VkDeviceSize offset) const noexcept { | ||
| 1214 | dld->vkCmdDispatchIndirect(handle, indirect_buffer, offset); | ||
| 1215 | } | ||
| 1216 | |||
| 1212 | void PipelineBarrier(VkPipelineStageFlags src_stage_mask, VkPipelineStageFlags dst_stage_mask, | 1217 | void PipelineBarrier(VkPipelineStageFlags src_stage_mask, VkPipelineStageFlags dst_stage_mask, |
| 1213 | VkDependencyFlags dependency_flags, Span<VkMemoryBarrier> memory_barriers, | 1218 | VkDependencyFlags dependency_flags, Span<VkMemoryBarrier> memory_barriers, |
| 1214 | Span<VkBufferMemoryBarrier> buffer_barriers, | 1219 | Span<VkBufferMemoryBarrier> buffer_barriers, |
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index ba4084840..f2e6c03f0 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp | |||
| @@ -2010,8 +2010,16 @@ bool GMainWindow::OnShutdownBegin() { | |||
| 2010 | 2010 | ||
| 2011 | emit EmulationStopping(); | 2011 | emit EmulationStopping(); |
| 2012 | 2012 | ||
| 2013 | int shutdown_time = 1000; | ||
| 2014 | |||
| 2015 | if (system->DebuggerEnabled()) { | ||
| 2016 | shutdown_time = 0; | ||
| 2017 | } else if (system->GetExitLocked()) { | ||
| 2018 | shutdown_time = 5000; | ||
| 2019 | } | ||
| 2020 | |||
| 2013 | shutdown_timer.setSingleShot(true); | 2021 | shutdown_timer.setSingleShot(true); |
| 2014 | shutdown_timer.start(system->DebuggerEnabled() ? 0 : 5000); | 2022 | shutdown_timer.start(shutdown_time); |
| 2015 | connect(&shutdown_timer, &QTimer::timeout, this, &GMainWindow::OnEmulationStopTimeExpired); | 2023 | connect(&shutdown_timer, &QTimer::timeout, this, &GMainWindow::OnEmulationStopTimeExpired); |
| 2016 | connect(emu_thread.get(), &QThread::finished, this, &GMainWindow::OnEmulationStopped); | 2024 | connect(emu_thread.get(), &QThread::finished, this, &GMainWindow::OnEmulationStopped); |
| 2017 | 2025 | ||
| @@ -3265,7 +3273,7 @@ void GMainWindow::OnPauseContinueGame() { | |||
| 3265 | } | 3273 | } |
| 3266 | 3274 | ||
| 3267 | void GMainWindow::OnStopGame() { | 3275 | void GMainWindow::OnStopGame() { |
| 3268 | if (system->GetExitLock() && !ConfirmForceLockedExit()) { | 3276 | if (system->GetExitLocked() && !ConfirmForceLockedExit()) { |
| 3269 | return; | 3277 | return; |
| 3270 | } | 3278 | } |
| 3271 | 3279 | ||
| @@ -4535,6 +4543,8 @@ void GMainWindow::RequestGameExit() { | |||
| 4535 | auto applet_ae = sm.GetService<Service::AM::AppletAE>("appletAE"); | 4543 | auto applet_ae = sm.GetService<Service::AM::AppletAE>("appletAE"); |
| 4536 | bool has_signalled = false; | 4544 | bool has_signalled = false; |
| 4537 | 4545 | ||
| 4546 | system->SetExitRequested(true); | ||
| 4547 | |||
| 4538 | if (applet_oe != nullptr) { | 4548 | if (applet_oe != nullptr) { |
| 4539 | applet_oe->GetMessageQueue()->RequestExit(); | 4549 | applet_oe->GetMessageQueue()->RequestExit(); |
| 4540 | has_signalled = true; | 4550 | has_signalled = true; |