diff options
75 files changed, 1313 insertions, 541 deletions
diff --git a/.gitignore b/.gitignore index a5f7248c7..fbadb208b 100644 --- a/.gitignore +++ b/.gitignore | |||
| @@ -26,6 +26,8 @@ CMakeSettings.json | |||
| 26 | # OSX global filetypes | 26 | # OSX global filetypes |
| 27 | # Created by Finder or Spotlight in directories for various OS functionality (indexing, etc) | 27 | # Created by Finder or Spotlight in directories for various OS functionality (indexing, etc) |
| 28 | .DS_Store | 28 | .DS_Store |
| 29 | .DS_Store? | ||
| 30 | ._* | ||
| 29 | .AppleDouble | 31 | .AppleDouble |
| 30 | .LSOverride | 32 | .LSOverride |
| 31 | .Spotlight-V100 | 33 | .Spotlight-V100 |
diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index e48137080..7cce27d51 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt | |||
| @@ -63,8 +63,9 @@ if (YUZU_USE_EXTERNAL_SDL2) | |||
| 63 | # Yuzu itself needs: Atomic Audio Events Joystick Haptic Sensor Threads Timers | 63 | # Yuzu itself needs: Atomic Audio Events Joystick Haptic Sensor Threads Timers |
| 64 | # Since 2.0.18 Atomic+Threads required for HIDAPI/libusb (see https://github.com/libsdl-org/SDL/issues/5095) | 64 | # Since 2.0.18 Atomic+Threads required for HIDAPI/libusb (see https://github.com/libsdl-org/SDL/issues/5095) |
| 65 | # Yuzu-cmd also needs: Video (depends on Loadso/Dlopen) | 65 | # Yuzu-cmd also needs: Video (depends on Loadso/Dlopen) |
| 66 | # CPUinfo also required for SDL Audio, at least until 2.28.0 (see https://github.com/libsdl-org/SDL/issues/7809) | ||
| 66 | set(SDL_UNUSED_SUBSYSTEMS | 67 | set(SDL_UNUSED_SUBSYSTEMS |
| 67 | CPUinfo File Filesystem | 68 | File Filesystem |
| 68 | Locale Power Render) | 69 | Locale Power Render) |
| 69 | foreach(_SUB ${SDL_UNUSED_SUBSYSTEMS}) | 70 | foreach(_SUB ${SDL_UNUSED_SUBSYSTEMS}) |
| 70 | string(TOUPPER ${_SUB} _OPT) | 71 | string(TOUPPER ${_SUB} _OPT) |
diff --git a/src/android/app/build.gradle.kts b/src/android/app/build.gradle.kts index a637db78a..7ae538cf9 100644 --- a/src/android/app/build.gradle.kts +++ b/src/android/app/build.gradle.kts | |||
| @@ -2,7 +2,9 @@ | |||
| 2 | // SPDX-License-Identifier: GPL-3.0-or-later | 2 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 3 | 3 | ||
| 4 | import android.annotation.SuppressLint | 4 | import android.annotation.SuppressLint |
| 5 | import kotlin.collections.setOf | ||
| 5 | import org.jetbrains.kotlin.konan.properties.Properties | 6 | import org.jetbrains.kotlin.konan.properties.Properties |
| 7 | import org.jlleitschuh.gradle.ktlint.reporter.ReporterType | ||
| 6 | 8 | ||
| 7 | plugins { | 9 | plugins { |
| 8 | id("com.android.application") | 10 | id("com.android.application") |
| @@ -10,6 +12,7 @@ plugins { | |||
| 10 | id("kotlin-parcelize") | 12 | id("kotlin-parcelize") |
| 11 | kotlin("plugin.serialization") version "1.8.21" | 13 | kotlin("plugin.serialization") version "1.8.21" |
| 12 | id("androidx.navigation.safeargs.kotlin") | 14 | id("androidx.navigation.safeargs.kotlin") |
| 15 | id("org.jlleitschuh.gradle.ktlint") version "11.4.0" | ||
| 13 | } | 16 | } |
| 14 | 17 | ||
| 15 | /** | 18 | /** |
| @@ -44,16 +47,6 @@ android { | |||
| 44 | jniLibs.useLegacyPackaging = true | 47 | jniLibs.useLegacyPackaging = true |
| 45 | } | 48 | } |
| 46 | 49 | ||
| 47 | lint { | ||
| 48 | // This is important as it will run lint but not abort on error | ||
| 49 | // Lint has some overly obnoxious "errors" that should really be warnings | ||
| 50 | abortOnError = false | ||
| 51 | |||
| 52 | //Uncomment disable lines for test builds... | ||
| 53 | //disable 'MissingTranslation'bin | ||
| 54 | //disable 'ExtraTranslation' | ||
| 55 | } | ||
| 56 | |||
| 57 | defaultConfig { | 50 | defaultConfig { |
| 58 | // TODO If this is ever modified, change application_id in strings.xml | 51 | // TODO If this is ever modified, change application_id in strings.xml |
| 59 | applicationId = "org.yuzu.yuzu_emu" | 52 | applicationId = "org.yuzu.yuzu_emu" |
| @@ -167,6 +160,23 @@ android { | |||
| 167 | } | 160 | } |
| 168 | } | 161 | } |
| 169 | 162 | ||
| 163 | tasks.getByPath("preBuild").dependsOn("ktlintCheck") | ||
| 164 | |||
| 165 | ktlint { | ||
| 166 | version.set("0.47.0") | ||
| 167 | android.set(true) | ||
| 168 | ignoreFailures.set(false) | ||
| 169 | disabledRules.set( | ||
| 170 | setOf( | ||
| 171 | "no-wildcard-imports", | ||
| 172 | "package-name" | ||
| 173 | ) | ||
| 174 | ) | ||
| 175 | reporters { | ||
| 176 | reporter(ReporterType.CHECKSTYLE) | ||
| 177 | } | ||
| 178 | } | ||
| 179 | |||
| 170 | dependencies { | 180 | dependencies { |
| 171 | implementation("androidx.core:core-ktx:1.10.1") | 181 | implementation("androidx.core:core-ktx:1.10.1") |
| 172 | implementation("androidx.appcompat:appcompat:1.6.1") | 182 | implementation("androidx.appcompat:appcompat:1.6.1") |
diff --git a/src/android/app/src/main/AndroidManifest.xml b/src/android/app/src/main/AndroidManifest.xml index b474ddb0b..e31ad69e2 100644 --- a/src/android/app/src/main/AndroidManifest.xml +++ b/src/android/app/src/main/AndroidManifest.xml | |||
| @@ -54,6 +54,8 @@ SPDX-License-Identifier: GPL-3.0-or-later | |||
| 54 | android:name="org.yuzu.yuzu_emu.activities.EmulationActivity" | 54 | android:name="org.yuzu.yuzu_emu.activities.EmulationActivity" |
| 55 | android:theme="@style/Theme.Yuzu.Main" | 55 | android:theme="@style/Theme.Yuzu.Main" |
| 56 | android:screenOrientation="userLandscape" | 56 | android:screenOrientation="userLandscape" |
| 57 | android:supportsPictureInPicture="true" | ||
| 58 | android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|uiMode" | ||
| 57 | android:exported="true"> | 59 | android:exported="true"> |
| 58 | 60 | ||
| 59 | <intent-filter> | 61 | <intent-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 4be9ade14..f860cdd4b 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 | |||
| @@ -14,16 +14,18 @@ import android.widget.TextView | |||
| 14 | import androidx.annotation.Keep | 14 | import androidx.annotation.Keep |
| 15 | import androidx.fragment.app.DialogFragment | 15 | import androidx.fragment.app.DialogFragment |
| 16 | import com.google.android.material.dialog.MaterialAlertDialogBuilder | 16 | import com.google.android.material.dialog.MaterialAlertDialogBuilder |
| 17 | import java.lang.ref.WeakReference | ||
| 17 | import org.yuzu.yuzu_emu.YuzuApplication.Companion.appContext | 18 | import org.yuzu.yuzu_emu.YuzuApplication.Companion.appContext |
| 18 | import org.yuzu.yuzu_emu.activities.EmulationActivity | 19 | import org.yuzu.yuzu_emu.activities.EmulationActivity |
| 19 | import org.yuzu.yuzu_emu.utils.DocumentsTree.Companion.isNativePath | 20 | import org.yuzu.yuzu_emu.utils.DocumentsTree.Companion.isNativePath |
| 21 | import org.yuzu.yuzu_emu.utils.FileUtil.exists | ||
| 20 | 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 | ||
| 21 | import org.yuzu.yuzu_emu.utils.FileUtil.openContentUri | 24 | import org.yuzu.yuzu_emu.utils.FileUtil.openContentUri |
| 22 | import org.yuzu.yuzu_emu.utils.Log.error | 25 | import org.yuzu.yuzu_emu.utils.Log.error |
| 23 | import org.yuzu.yuzu_emu.utils.Log.verbose | 26 | import org.yuzu.yuzu_emu.utils.Log.verbose |
| 24 | import org.yuzu.yuzu_emu.utils.Log.warning | 27 | import org.yuzu.yuzu_emu.utils.Log.warning |
| 25 | import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable | 28 | import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable |
| 26 | import java.lang.ref.WeakReference | ||
| 27 | 29 | ||
| 28 | /** | 30 | /** |
| 29 | * Class which contains methods that interact | 31 | * Class which contains methods that interact |
| @@ -74,7 +76,9 @@ object NativeLibrary { | |||
| 74 | fun openContentUri(path: String?, openmode: String?): Int { | 76 | fun openContentUri(path: String?, openmode: String?): Int { |
| 75 | return if (isNativePath(path!!)) { | 77 | return if (isNativePath(path!!)) { |
| 76 | YuzuApplication.documentsTree!!.openContentUri(path, openmode) | 78 | YuzuApplication.documentsTree!!.openContentUri(path, openmode) |
| 77 | } else openContentUri(appContext, path, openmode) | 79 | } else { |
| 80 | openContentUri(appContext, path, openmode) | ||
| 81 | } | ||
| 78 | } | 82 | } |
| 79 | 83 | ||
| 80 | @Keep | 84 | @Keep |
| @@ -82,7 +86,29 @@ object NativeLibrary { | |||
| 82 | fun getSize(path: String?): Long { | 86 | fun getSize(path: String?): Long { |
| 83 | return if (isNativePath(path!!)) { | 87 | return if (isNativePath(path!!)) { |
| 84 | YuzuApplication.documentsTree!!.getFileSize(path) | 88 | YuzuApplication.documentsTree!!.getFileSize(path) |
| 85 | } else getFileSize(appContext, path) | 89 | } else { |
| 90 | getFileSize(appContext, path) | ||
| 91 | } | ||
| 92 | } | ||
| 93 | |||
| 94 | @Keep | ||
| 95 | @JvmStatic | ||
| 96 | fun exists(path: String?): Boolean { | ||
| 97 | return if (isNativePath(path!!)) { | ||
| 98 | YuzuApplication.documentsTree!!.exists(path) | ||
| 99 | } else { | ||
| 100 | exists(appContext, path) | ||
| 101 | } | ||
| 102 | } | ||
| 103 | |||
| 104 | @Keep | ||
| 105 | @JvmStatic | ||
| 106 | fun isDirectory(path: String?): Boolean { | ||
| 107 | return if (isNativePath(path!!)) { | ||
| 108 | YuzuApplication.documentsTree!!.isDirectory(path) | ||
| 109 | } else { | ||
| 110 | isDirectory(appContext, path) | ||
| 111 | } | ||
| 86 | } | 112 | } |
| 87 | 113 | ||
| 88 | /** | 114 | /** |
| @@ -283,6 +309,11 @@ object NativeLibrary { | |||
| 283 | external fun isRunning(): Boolean | 309 | external fun isRunning(): Boolean |
| 284 | 310 | ||
| 285 | /** | 311 | /** |
| 312 | * Returns true if emulation is paused. | ||
| 313 | */ | ||
| 314 | external fun isPaused(): Boolean | ||
| 315 | |||
| 316 | /** | ||
| 286 | * Returns the performance stats for the current game | 317 | * Returns the performance stats for the current game |
| 287 | */ | 318 | */ |
| 288 | external fun getPerfStats(): DoubleArray | 319 | external fun getPerfStats(): DoubleArray |
| @@ -431,7 +462,9 @@ object NativeLibrary { | |||
| 431 | Html.FROM_HTML_MODE_LEGACY | 462 | Html.FROM_HTML_MODE_LEGACY |
| 432 | ) | 463 | ) |
| 433 | ) | 464 | ) |
| 434 | .setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int -> emulationActivity.finish() } | 465 | .setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int -> |
| 466 | emulationActivity.finish() | ||
| 467 | } | ||
| 435 | .setOnDismissListener { emulationActivity.finish() } | 468 | .setOnDismissListener { emulationActivity.finish() } |
| 436 | emulationActivity.runOnUiThread { | 469 | emulationActivity.runOnUiThread { |
| 437 | val alert = builder.create() | 470 | val alert = builder.create() |
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 4c947b786..04ab6a220 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 | |||
| @@ -7,12 +7,12 @@ import android.app.Application | |||
| 7 | import android.app.NotificationChannel | 7 | import android.app.NotificationChannel |
| 8 | import android.app.NotificationManager | 8 | import android.app.NotificationManager |
| 9 | import android.content.Context | 9 | import android.content.Context |
| 10 | import java.io.File | ||
| 10 | import org.yuzu.yuzu_emu.utils.DirectoryInitialization | 11 | import org.yuzu.yuzu_emu.utils.DirectoryInitialization |
| 11 | import org.yuzu.yuzu_emu.utils.DocumentsTree | 12 | import org.yuzu.yuzu_emu.utils.DocumentsTree |
| 12 | import org.yuzu.yuzu_emu.utils.GpuDriverHelper | 13 | import org.yuzu.yuzu_emu.utils.GpuDriverHelper |
| 13 | import java.io.File | ||
| 14 | 14 | ||
| 15 | fun Context.getPublicFilesDir() : File = getExternalFilesDir(null) ?: filesDir | 15 | fun Context.getPublicFilesDir(): File = getExternalFilesDir(null) ?: filesDir |
| 16 | 16 | ||
| 17 | class YuzuApplication : Application() { | 17 | class YuzuApplication : Application() { |
| 18 | private fun createNotificationChannels() { | 18 | private fun createNotificationChannels() { |
| @@ -21,7 +21,9 @@ class YuzuApplication : Application() { | |||
| 21 | getString(R.string.emulation_notification_channel_name), | 21 | getString(R.string.emulation_notification_channel_name), |
| 22 | NotificationManager.IMPORTANCE_LOW | 22 | NotificationManager.IMPORTANCE_LOW |
| 23 | ) | 23 | ) |
| 24 | emulationChannel.description = getString(R.string.emulation_notification_channel_description) | 24 | emulationChannel.description = getString( |
| 25 | R.string.emulation_notification_channel_description | ||
| 26 | ) | ||
| 25 | emulationChannel.setSound(null, null) | 27 | emulationChannel.setSound(null, null) |
| 26 | emulationChannel.vibrationPattern = null | 28 | emulationChannel.vibrationPattern = null |
| 27 | 29 | ||
| @@ -48,7 +50,7 @@ class YuzuApplication : Application() { | |||
| 48 | GpuDriverHelper.initializeDriverParameters(applicationContext) | 50 | GpuDriverHelper.initializeDriverParameters(applicationContext) |
| 49 | NativeLibrary.logDeviceInfo() | 51 | NativeLibrary.logDeviceInfo() |
| 50 | 52 | ||
| 51 | createNotificationChannels(); | 53 | createNotificationChannels() |
| 52 | } | 54 | } |
| 53 | 55 | ||
| 54 | companion object { | 56 | companion object { |
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 caf660348..f0a6753a9 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 | |||
| @@ -4,14 +4,23 @@ | |||
| 4 | package org.yuzu.yuzu_emu.activities | 4 | package org.yuzu.yuzu_emu.activities |
| 5 | 5 | ||
| 6 | import android.app.Activity | 6 | import android.app.Activity |
| 7 | import android.app.PendingIntent | ||
| 8 | import android.app.PictureInPictureParams | ||
| 9 | import android.app.RemoteAction | ||
| 10 | import android.content.BroadcastReceiver | ||
| 7 | import android.content.Context | 11 | import android.content.Context |
| 8 | import android.content.Intent | 12 | import android.content.Intent |
| 13 | import android.content.IntentFilter | ||
| 14 | import android.content.res.Configuration | ||
| 9 | import android.graphics.Rect | 15 | import android.graphics.Rect |
| 16 | import android.graphics.drawable.Icon | ||
| 10 | import android.hardware.Sensor | 17 | import android.hardware.Sensor |
| 11 | import android.hardware.SensorEvent | 18 | import android.hardware.SensorEvent |
| 12 | import android.hardware.SensorEventListener | 19 | import android.hardware.SensorEventListener |
| 13 | import android.hardware.SensorManager | 20 | import android.hardware.SensorManager |
| 21 | import android.os.Build | ||
| 14 | import android.os.Bundle | 22 | import android.os.Bundle |
| 23 | import android.util.Rational | ||
| 15 | import android.view.InputDevice | 24 | import android.view.InputDevice |
| 16 | import android.view.KeyEvent | 25 | import android.view.KeyEvent |
| 17 | import android.view.MotionEvent | 26 | import android.view.MotionEvent |
| @@ -24,9 +33,12 @@ import androidx.core.view.WindowCompat | |||
| 24 | import androidx.core.view.WindowInsetsCompat | 33 | import androidx.core.view.WindowInsetsCompat |
| 25 | import androidx.core.view.WindowInsetsControllerCompat | 34 | import androidx.core.view.WindowInsetsControllerCompat |
| 26 | import androidx.navigation.fragment.NavHostFragment | 35 | import androidx.navigation.fragment.NavHostFragment |
| 36 | import kotlin.math.roundToInt | ||
| 27 | import org.yuzu.yuzu_emu.NativeLibrary | 37 | import org.yuzu.yuzu_emu.NativeLibrary |
| 28 | import org.yuzu.yuzu_emu.R | 38 | import org.yuzu.yuzu_emu.R |
| 29 | import org.yuzu.yuzu_emu.databinding.ActivityEmulationBinding | 39 | import org.yuzu.yuzu_emu.databinding.ActivityEmulationBinding |
| 40 | import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting | ||
| 41 | import org.yuzu.yuzu_emu.features.settings.model.IntSetting | ||
| 30 | import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel | 42 | import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel |
| 31 | import org.yuzu.yuzu_emu.model.Game | 43 | import org.yuzu.yuzu_emu.model.Game |
| 32 | import org.yuzu.yuzu_emu.utils.ControllerMappingHelper | 44 | import org.yuzu.yuzu_emu.utils.ControllerMappingHelper |
| @@ -34,7 +46,6 @@ import org.yuzu.yuzu_emu.utils.ForegroundService | |||
| 34 | import org.yuzu.yuzu_emu.utils.InputHandler | 46 | import org.yuzu.yuzu_emu.utils.InputHandler |
| 35 | import org.yuzu.yuzu_emu.utils.NfcReader | 47 | import org.yuzu.yuzu_emu.utils.NfcReader |
| 36 | import org.yuzu.yuzu_emu.utils.ThemeHelper | 48 | import org.yuzu.yuzu_emu.utils.ThemeHelper |
| 37 | import kotlin.math.roundToInt | ||
| 38 | 49 | ||
| 39 | class EmulationActivity : AppCompatActivity(), SensorEventListener { | 50 | class EmulationActivity : AppCompatActivity(), SensorEventListener { |
| 40 | private lateinit var binding: ActivityEmulationBinding | 51 | private lateinit var binding: ActivityEmulationBinding |
| @@ -50,6 +61,9 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener { | |||
| 50 | private var motionTimestamp: Long = 0 | 61 | private var motionTimestamp: Long = 0 |
| 51 | private var flipMotionOrientation: Boolean = false | 62 | private var flipMotionOrientation: Boolean = false |
| 52 | 63 | ||
| 64 | private val actionPause = "ACTION_EMULATOR_PAUSE" | ||
| 65 | private val actionPlay = "ACTION_EMULATOR_PLAY" | ||
| 66 | |||
| 53 | private val settingsViewModel: SettingsViewModel by viewModels() | 67 | private val settingsViewModel: SettingsViewModel by viewModels() |
| 54 | 68 | ||
| 55 | override fun onDestroy() { | 69 | override fun onDestroy() { |
| @@ -120,6 +134,8 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener { | |||
| 120 | super.onResume() | 134 | super.onResume() |
| 121 | nfcReader.startScanning() | 135 | nfcReader.startScanning() |
| 122 | startMotionSensorListener() | 136 | startMotionSensorListener() |
| 137 | |||
| 138 | buildPictureInPictureParams() | ||
| 123 | } | 139 | } |
| 124 | 140 | ||
| 125 | override fun onPause() { | 141 | override fun onPause() { |
| @@ -128,6 +144,16 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener { | |||
| 128 | stopMotionSensorListener() | 144 | stopMotionSensorListener() |
| 129 | } | 145 | } |
| 130 | 146 | ||
| 147 | override fun onUserLeaveHint() { | ||
| 148 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { | ||
| 149 | if (BooleanSetting.PICTURE_IN_PICTURE.boolean && !isInPictureInPictureMode) { | ||
| 150 | val pictureInPictureParamsBuilder = PictureInPictureParams.Builder() | ||
| 151 | .getPictureInPictureActionsBuilder().getPictureInPictureAspectBuilder() | ||
| 152 | enterPictureInPictureMode(pictureInPictureParamsBuilder.build()) | ||
| 153 | } | ||
| 154 | } | ||
| 155 | } | ||
| 156 | |||
| 131 | override fun onNewIntent(intent: Intent) { | 157 | override fun onNewIntent(intent: Intent) { |
| 132 | super.onNewIntent(intent) | 158 | super.onNewIntent(intent) |
| 133 | setIntent(intent) | 159 | setIntent(intent) |
| @@ -230,6 +256,100 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener { | |||
| 230 | } | 256 | } |
| 231 | } | 257 | } |
| 232 | 258 | ||
| 259 | private fun PictureInPictureParams.Builder.getPictureInPictureAspectBuilder(): | ||
| 260 | PictureInPictureParams.Builder { | ||
| 261 | val aspectRatio = when (IntSetting.RENDERER_ASPECT_RATIO.int) { | ||
| 262 | 0 -> Rational(16, 9) | ||
| 263 | 1 -> Rational(4, 3) | ||
| 264 | 2 -> Rational(21, 9) | ||
| 265 | 3 -> Rational(16, 10) | ||
| 266 | else -> null // Best fit | ||
| 267 | } | ||
| 268 | return this.apply { aspectRatio?.let { setAspectRatio(it) } } | ||
| 269 | } | ||
| 270 | |||
| 271 | private fun PictureInPictureParams.Builder.getPictureInPictureActionsBuilder(): | ||
| 272 | PictureInPictureParams.Builder { | ||
| 273 | val pictureInPictureActions: MutableList<RemoteAction> = mutableListOf() | ||
| 274 | val pendingFlags = PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE | ||
| 275 | |||
| 276 | if (NativeLibrary.isPaused()) { | ||
| 277 | val playIcon = Icon.createWithResource(this@EmulationActivity, R.drawable.ic_pip_play) | ||
| 278 | val playPendingIntent = PendingIntent.getBroadcast( | ||
| 279 | this@EmulationActivity, | ||
| 280 | R.drawable.ic_pip_play, | ||
| 281 | Intent(actionPlay), | ||
| 282 | pendingFlags | ||
| 283 | ) | ||
| 284 | val playRemoteAction = RemoteAction( | ||
| 285 | playIcon, | ||
| 286 | getString(R.string.play), | ||
| 287 | getString(R.string.play), | ||
| 288 | playPendingIntent | ||
| 289 | ) | ||
| 290 | pictureInPictureActions.add(playRemoteAction) | ||
| 291 | } else { | ||
| 292 | val pauseIcon = Icon.createWithResource(this@EmulationActivity, R.drawable.ic_pip_pause) | ||
| 293 | val pausePendingIntent = PendingIntent.getBroadcast( | ||
| 294 | this@EmulationActivity, | ||
| 295 | R.drawable.ic_pip_pause, | ||
| 296 | Intent(actionPause), | ||
| 297 | pendingFlags | ||
| 298 | ) | ||
| 299 | val pauseRemoteAction = RemoteAction( | ||
| 300 | pauseIcon, | ||
| 301 | getString(R.string.pause), | ||
| 302 | getString(R.string.pause), | ||
| 303 | pausePendingIntent | ||
| 304 | ) | ||
| 305 | pictureInPictureActions.add(pauseRemoteAction) | ||
| 306 | } | ||
| 307 | |||
| 308 | return this.apply { setActions(pictureInPictureActions) } | ||
| 309 | } | ||
| 310 | |||
| 311 | fun buildPictureInPictureParams() { | ||
| 312 | val pictureInPictureParamsBuilder = PictureInPictureParams.Builder() | ||
| 313 | .getPictureInPictureActionsBuilder().getPictureInPictureAspectBuilder() | ||
| 314 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { | ||
| 315 | pictureInPictureParamsBuilder.setAutoEnterEnabled( | ||
| 316 | BooleanSetting.PICTURE_IN_PICTURE.boolean | ||
| 317 | ) | ||
| 318 | } | ||
| 319 | setPictureInPictureParams(pictureInPictureParamsBuilder.build()) | ||
| 320 | } | ||
| 321 | |||
| 322 | private var pictureInPictureReceiver = object : BroadcastReceiver() { | ||
| 323 | override fun onReceive(context: Context?, intent: Intent) { | ||
| 324 | if (intent.action == actionPlay) { | ||
| 325 | if (NativeLibrary.isPaused()) NativeLibrary.unPauseEmulation() | ||
| 326 | } else if (intent.action == actionPause) { | ||
| 327 | if (!NativeLibrary.isPaused()) NativeLibrary.pauseEmulation() | ||
| 328 | } | ||
| 329 | buildPictureInPictureParams() | ||
| 330 | } | ||
| 331 | } | ||
| 332 | |||
| 333 | override fun onPictureInPictureModeChanged( | ||
| 334 | isInPictureInPictureMode: Boolean, | ||
| 335 | newConfig: Configuration | ||
| 336 | ) { | ||
| 337 | super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig) | ||
| 338 | if (isInPictureInPictureMode) { | ||
| 339 | IntentFilter().apply { | ||
| 340 | addAction(actionPause) | ||
| 341 | addAction(actionPlay) | ||
| 342 | }.also { | ||
| 343 | registerReceiver(pictureInPictureReceiver, it) | ||
| 344 | } | ||
| 345 | } else { | ||
| 346 | try { | ||
| 347 | unregisterReceiver(pictureInPictureReceiver) | ||
| 348 | } catch (ignored: Exception) { | ||
| 349 | } | ||
| 350 | } | ||
| 351 | } | ||
| 352 | |||
| 233 | private fun startMotionSensorListener() { | 353 | private fun startMotionSensorListener() { |
| 234 | val sensorManager = this.getSystemService(Context.SENSOR_SERVICE) as SensorManager | 354 | val sensorManager = this.getSystemService(Context.SENSOR_SERVICE) as SensorManager |
| 235 | val gyroSensor = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE) | 355 | 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 83d08841b..e91277d35 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 | |||
| @@ -28,10 +28,9 @@ import org.yuzu.yuzu_emu.HomeNavigationDirections | |||
| 28 | import org.yuzu.yuzu_emu.NativeLibrary | 28 | import org.yuzu.yuzu_emu.NativeLibrary |
| 29 | import org.yuzu.yuzu_emu.R | 29 | import org.yuzu.yuzu_emu.R |
| 30 | import org.yuzu.yuzu_emu.YuzuApplication | 30 | import org.yuzu.yuzu_emu.YuzuApplication |
| 31 | import org.yuzu.yuzu_emu.adapters.GameAdapter.GameViewHolder | ||
| 31 | import org.yuzu.yuzu_emu.databinding.CardGameBinding | 32 | import org.yuzu.yuzu_emu.databinding.CardGameBinding |
| 32 | import org.yuzu.yuzu_emu.activities.EmulationActivity | ||
| 33 | import org.yuzu.yuzu_emu.model.Game | 33 | import org.yuzu.yuzu_emu.model.Game |
| 34 | import org.yuzu.yuzu_emu.adapters.GameAdapter.GameViewHolder | ||
| 35 | import org.yuzu.yuzu_emu.model.GamesViewModel | 34 | import org.yuzu.yuzu_emu.model.GamesViewModel |
| 36 | 35 | ||
| 37 | class GameAdapter(private val activity: AppCompatActivity) : | 36 | class GameAdapter(private val activity: AppCompatActivity) : |
| @@ -60,7 +59,10 @@ class GameAdapter(private val activity: AppCompatActivity) : | |||
| 60 | override fun onClick(view: View) { | 59 | override fun onClick(view: View) { |
| 61 | val holder = view.tag as GameViewHolder | 60 | val holder = view.tag as GameViewHolder |
| 62 | 61 | ||
| 63 | val gameExists = DocumentFile.fromSingleUri(YuzuApplication.appContext, Uri.parse(holder.game.path))?.exists() == true | 62 | val gameExists = DocumentFile.fromSingleUri( |
| 63 | YuzuApplication.appContext, | ||
| 64 | Uri.parse(holder.game.path) | ||
| 65 | )?.exists() == true | ||
| 64 | if (!gameExists) { | 66 | if (!gameExists) { |
| 65 | Toast.makeText( | 67 | Toast.makeText( |
| 66 | YuzuApplication.appContext, | 68 | YuzuApplication.appContext, |
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 b719dd539..d3df3bc81 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 | |||
| @@ -58,11 +58,12 @@ class HomeSettingAdapter(private val activity: AppCompatActivity, var options: L | |||
| 58 | ) | 58 | ) |
| 59 | 59 | ||
| 60 | when (option.titleId) { | 60 | when (option.titleId) { |
| 61 | R.string.get_early_access -> binding.optionLayout.background = | 61 | R.string.get_early_access -> |
| 62 | ContextCompat.getDrawable( | 62 | binding.optionLayout.background = |
| 63 | binding.optionCard.context, | 63 | ContextCompat.getDrawable( |
| 64 | R.drawable.premium_background | 64 | binding.optionCard.context, |
| 65 | ) | 65 | R.drawable.premium_background |
| 66 | ) | ||
| 66 | } | 67 | } |
| 67 | } | 68 | } |
| 68 | } | 69 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/applets/keyboard/SoftwareKeyboard.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/applets/keyboard/SoftwareKeyboard.kt index 82a6712b6..e058067c9 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/applets/keyboard/SoftwareKeyboard.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/applets/keyboard/SoftwareKeyboard.kt | |||
| @@ -12,10 +12,10 @@ import android.view.WindowInsets | |||
| 12 | import android.view.inputmethod.InputMethodManager | 12 | import android.view.inputmethod.InputMethodManager |
| 13 | import androidx.annotation.Keep | 13 | import androidx.annotation.Keep |
| 14 | import androidx.core.view.ViewCompat | 14 | import androidx.core.view.ViewCompat |
| 15 | import java.io.Serializable | ||
| 15 | import org.yuzu.yuzu_emu.NativeLibrary | 16 | import org.yuzu.yuzu_emu.NativeLibrary |
| 16 | import org.yuzu.yuzu_emu.R | 17 | import org.yuzu.yuzu_emu.R |
| 17 | import org.yuzu.yuzu_emu.applets.keyboard.ui.KeyboardDialogFragment | 18 | import org.yuzu.yuzu_emu.applets.keyboard.ui.KeyboardDialogFragment |
| 18 | import java.io.Serializable | ||
| 19 | 19 | ||
| 20 | @Keep | 20 | @Keep |
| 21 | object SoftwareKeyboard { | 21 | object SoftwareKeyboard { |
| @@ -40,19 +40,22 @@ object SoftwareKeyboard { | |||
| 40 | // There isn't a good way to know that the IMM is dismissed, so poll every 500ms to submit inline keyboard result. | 40 | // There isn't a good way to know that the IMM is dismissed, so poll every 500ms to submit inline keyboard result. |
| 41 | val handler = Handler(Looper.myLooper()!!) | 41 | val handler = Handler(Looper.myLooper()!!) |
| 42 | val delayMs = 500 | 42 | val delayMs = 500 |
| 43 | handler.postDelayed(object : Runnable { | 43 | handler.postDelayed( |
| 44 | override fun run() { | 44 | object : Runnable { |
| 45 | val insets = ViewCompat.getRootWindowInsets(overlayView) | 45 | override fun run() { |
| 46 | val isKeyboardVisible = insets!!.isVisible(WindowInsets.Type.ime()) | 46 | val insets = ViewCompat.getRootWindowInsets(overlayView) |
| 47 | if (isKeyboardVisible) { | 47 | val isKeyboardVisible = insets!!.isVisible(WindowInsets.Type.ime()) |
| 48 | handler.postDelayed(this, delayMs.toLong()) | 48 | if (isKeyboardVisible) { |
| 49 | return | 49 | handler.postDelayed(this, delayMs.toLong()) |
| 50 | } | 50 | return |
| 51 | } | ||
| 51 | 52 | ||
| 52 | // No longer visible, submit the result. | 53 | // No longer visible, submit the result. |
| 53 | NativeLibrary.submitInlineKeyboardInput(KeyEvent.KEYCODE_ENTER) | 54 | NativeLibrary.submitInlineKeyboardInput(KeyEvent.KEYCODE_ENTER) |
| 54 | } | 55 | } |
| 55 | }, delayMs.toLong()) | 56 | }, |
| 57 | delayMs.toLong() | ||
| 58 | ) | ||
| 56 | } | 59 | } |
| 57 | 60 | ||
| 58 | @JvmStatic | 61 | @JvmStatic |
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 3b1559c80..a18efef19 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 | |||
| @@ -20,7 +20,10 @@ object DiskShaderCacheProgress { | |||
| 20 | emulationActivity.getString(R.string.loading), | 20 | emulationActivity.getString(R.string.loading), |
| 21 | emulationActivity.getString(R.string.preparing_shaders) | 21 | emulationActivity.getString(R.string.preparing_shaders) |
| 22 | ) | 22 | ) |
| 23 | fragment.show(emulationActivity.supportFragmentManager, ShaderProgressDialogFragment.TAG) | 23 | fragment.show( |
| 24 | emulationActivity.supportFragmentManager, | ||
| 25 | ShaderProgressDialogFragment.TAG | ||
| 26 | ) | ||
| 24 | } | 27 | } |
| 25 | synchronized(finishLock) { finishLock.wait() } | 28 | synchronized(finishLock) { finishLock.wait() } |
| 26 | } | 29 | } |
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 index 2c68c9ac3..8a8e0a6e8 100644 --- 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 | |||
| @@ -62,7 +62,9 @@ class ShaderProgressDialogFragment : DialogFragment() { | |||
| 62 | shaderProgressViewModel.message.observe(viewLifecycleOwner) { msg -> | 62 | shaderProgressViewModel.message.observe(viewLifecycleOwner) { msg -> |
| 63 | alertDialog.setMessage(msg) | 63 | alertDialog.setMessage(msg) |
| 64 | } | 64 | } |
| 65 | synchronized(DiskShaderCacheProgress.finishLock) { DiskShaderCacheProgress.finishLock.notifyAll() } | 65 | synchronized(DiskShaderCacheProgress.finishLock) { |
| 66 | DiskShaderCacheProgress.finishLock.notifyAll() | ||
| 67 | } | ||
| 66 | } | 68 | } |
| 67 | 69 | ||
| 68 | override fun onDestroyView() { | 70 | override fun onDestroyView() { |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/DocumentProvider.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/DocumentProvider.kt index 4c3a9ca80..f3be156b5 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/DocumentProvider.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/DocumentProvider.kt | |||
| @@ -13,11 +13,11 @@ import android.os.ParcelFileDescriptor | |||
| 13 | import android.provider.DocumentsContract | 13 | import android.provider.DocumentsContract |
| 14 | import android.provider.DocumentsProvider | 14 | import android.provider.DocumentsProvider |
| 15 | import android.webkit.MimeTypeMap | 15 | import android.webkit.MimeTypeMap |
| 16 | import java.io.* | ||
| 16 | import org.yuzu.yuzu_emu.BuildConfig | 17 | import org.yuzu.yuzu_emu.BuildConfig |
| 17 | import org.yuzu.yuzu_emu.R | 18 | import org.yuzu.yuzu_emu.R |
| 18 | import org.yuzu.yuzu_emu.YuzuApplication | 19 | import org.yuzu.yuzu_emu.YuzuApplication |
| 19 | import org.yuzu.yuzu_emu.getPublicFilesDir | 20 | import org.yuzu.yuzu_emu.getPublicFilesDir |
| 20 | import java.io.* | ||
| 21 | 21 | ||
| 22 | class DocumentProvider : DocumentsProvider() { | 22 | class DocumentProvider : DocumentsProvider() { |
| 23 | private val baseDirectory: File | 23 | private val baseDirectory: File |
| @@ -44,7 +44,7 @@ class DocumentProvider : DocumentsProvider() { | |||
| 44 | DocumentsContract.Document.COLUMN_SIZE | 44 | DocumentsContract.Document.COLUMN_SIZE |
| 45 | ) | 45 | ) |
| 46 | 46 | ||
| 47 | const val AUTHORITY : String = BuildConfig.APPLICATION_ID + ".user" | 47 | const val AUTHORITY: String = BuildConfig.APPLICATION_ID + ".user" |
| 48 | const val ROOT_ID: String = "root" | 48 | const val ROOT_ID: String = "root" |
| 49 | } | 49 | } |
| 50 | 50 | ||
| @@ -58,7 +58,11 @@ class DocumentProvider : DocumentsProvider() { | |||
| 58 | private fun getFile(documentId: String): File { | 58 | private fun getFile(documentId: String): File { |
| 59 | if (documentId.startsWith(ROOT_ID)) { | 59 | if (documentId.startsWith(ROOT_ID)) { |
| 60 | val file = baseDirectory.resolve(documentId.drop(ROOT_ID.length + 1)) | 60 | val file = baseDirectory.resolve(documentId.drop(ROOT_ID.length + 1)) |
| 61 | if (!file.exists()) throw FileNotFoundException("${file.absolutePath} ($documentId) not found") | 61 | if (!file.exists()) { |
| 62 | throw FileNotFoundException( | ||
| 63 | "${file.absolutePath} ($documentId) not found" | ||
| 64 | ) | ||
| 65 | } | ||
| 62 | return file | 66 | return file |
| 63 | } else { | 67 | } else { |
| 64 | throw FileNotFoundException("'$documentId' is not in any known root") | 68 | throw FileNotFoundException("'$documentId' is not in any known root") |
| @@ -80,7 +84,8 @@ class DocumentProvider : DocumentsProvider() { | |||
| 80 | add(DocumentsContract.Root.COLUMN_SUMMARY, null) | 84 | add(DocumentsContract.Root.COLUMN_SUMMARY, null) |
| 81 | add( | 85 | add( |
| 82 | DocumentsContract.Root.COLUMN_FLAGS, | 86 | DocumentsContract.Root.COLUMN_FLAGS, |
| 83 | DocumentsContract.Root.FLAG_SUPPORTS_CREATE or DocumentsContract.Root.FLAG_SUPPORTS_IS_CHILD | 87 | DocumentsContract.Root.FLAG_SUPPORTS_CREATE or |
| 88 | DocumentsContract.Root.FLAG_SUPPORTS_IS_CHILD | ||
| 84 | ) | 89 | ) |
| 85 | add(DocumentsContract.Root.COLUMN_TITLE, context!!.getString(R.string.app_name)) | 90 | add(DocumentsContract.Root.COLUMN_TITLE, context!!.getString(R.string.app_name)) |
| 86 | add(DocumentsContract.Root.COLUMN_DOCUMENT_ID, getDocumentId(baseDirectory)) | 91 | add(DocumentsContract.Root.COLUMN_DOCUMENT_ID, getDocumentId(baseDirectory)) |
| @@ -127,11 +132,13 @@ class DocumentProvider : DocumentsProvider() { | |||
| 127 | 132 | ||
| 128 | try { | 133 | try { |
| 129 | if (DocumentsContract.Document.MIME_TYPE_DIR == mimeType) { | 134 | if (DocumentsContract.Document.MIME_TYPE_DIR == mimeType) { |
| 130 | if (!newFile.mkdir()) | 135 | if (!newFile.mkdir()) { |
| 131 | throw IOException("Failed to create directory") | 136 | throw IOException("Failed to create directory") |
| 137 | } | ||
| 132 | } else { | 138 | } else { |
| 133 | if (!newFile.createNewFile()) | 139 | if (!newFile.createNewFile()) { |
| 134 | throw IOException("Failed to create file") | 140 | throw IOException("Failed to create file") |
| 141 | } | ||
| 135 | } | 142 | } |
| 136 | } catch (e: IOException) { | 143 | } catch (e: IOException) { |
| 137 | throw FileNotFoundException("Couldn't create document '${newFile.path}': ${e.message}") | 144 | throw FileNotFoundException("Couldn't create document '${newFile.path}': ${e.message}") |
| @@ -142,8 +149,9 @@ class DocumentProvider : DocumentsProvider() { | |||
| 142 | 149 | ||
| 143 | override fun deleteDocument(documentId: String?) { | 150 | override fun deleteDocument(documentId: String?) { |
| 144 | val file = getFile(documentId!!) | 151 | val file = getFile(documentId!!) |
| 145 | if (!file.delete()) | 152 | if (!file.delete()) { |
| 146 | throw FileNotFoundException("Couldn't delete document with ID '$documentId'") | 153 | throw FileNotFoundException("Couldn't delete document with ID '$documentId'") |
| 154 | } | ||
| 147 | } | 155 | } |
| 148 | 156 | ||
| 149 | override fun removeDocument(documentId: String, parentDocumentId: String?) { | 157 | override fun removeDocument(documentId: String, parentDocumentId: String?) { |
| @@ -151,38 +159,55 @@ class DocumentProvider : DocumentsProvider() { | |||
| 151 | val file = getFile(documentId) | 159 | val file = getFile(documentId) |
| 152 | 160 | ||
| 153 | if (parent == file || file.parentFile == null || file.parentFile!! == parent) { | 161 | if (parent == file || file.parentFile == null || file.parentFile!! == parent) { |
| 154 | if (!file.delete()) | 162 | if (!file.delete()) { |
| 155 | throw FileNotFoundException("Couldn't delete document with ID '$documentId'") | 163 | throw FileNotFoundException("Couldn't delete document with ID '$documentId'") |
| 164 | } | ||
| 156 | } else { | 165 | } else { |
| 157 | throw FileNotFoundException("Couldn't delete document with ID '$documentId'") | 166 | throw FileNotFoundException("Couldn't delete document with ID '$documentId'") |
| 158 | } | 167 | } |
| 159 | } | 168 | } |
| 160 | 169 | ||
| 161 | override fun renameDocument(documentId: String?, displayName: String?): String { | 170 | override fun renameDocument(documentId: String?, displayName: String?): String { |
| 162 | if (displayName == null) | 171 | if (displayName == null) { |
| 163 | throw FileNotFoundException("Couldn't rename document '$documentId' as the new name is null") | 172 | throw FileNotFoundException( |
| 173 | "Couldn't rename document '$documentId' as the new name is null" | ||
| 174 | ) | ||
| 175 | } | ||
| 164 | 176 | ||
| 165 | val sourceFile = getFile(documentId!!) | 177 | val sourceFile = getFile(documentId!!) |
| 166 | val sourceParentFile = sourceFile.parentFile | 178 | val sourceParentFile = sourceFile.parentFile |
| 167 | ?: throw FileNotFoundException("Couldn't rename document '$documentId' as it has no parent") | 179 | ?: throw FileNotFoundException( |
| 180 | "Couldn't rename document '$documentId' as it has no parent" | ||
| 181 | ) | ||
| 168 | val destFile = sourceParentFile.resolve(displayName) | 182 | val destFile = sourceParentFile.resolve(displayName) |
| 169 | 183 | ||
| 170 | try { | 184 | try { |
| 171 | if (!sourceFile.renameTo(destFile)) | 185 | if (!sourceFile.renameTo(destFile)) { |
| 172 | throw FileNotFoundException("Couldn't rename document from '${sourceFile.name}' to '${destFile.name}'") | 186 | throw FileNotFoundException( |
| 187 | "Couldn't rename document from '${sourceFile.name}' to '${destFile.name}'" | ||
| 188 | ) | ||
| 189 | } | ||
| 173 | } catch (e: Exception) { | 190 | } catch (e: Exception) { |
| 174 | throw FileNotFoundException("Couldn't rename document from '${sourceFile.name}' to '${destFile.name}': ${e.message}") | 191 | throw FileNotFoundException( |
| 192 | "Couldn't rename document from '${sourceFile.name}' to '${destFile.name}': " + | ||
| 193 | "${e.message}" | ||
| 194 | ) | ||
| 175 | } | 195 | } |
| 176 | 196 | ||
| 177 | return getDocumentId(destFile) | 197 | return getDocumentId(destFile) |
| 178 | } | 198 | } |
| 179 | 199 | ||
| 180 | private fun copyDocument( | 200 | private fun copyDocument( |
| 181 | sourceDocumentId: String, sourceParentDocumentId: String, | 201 | sourceDocumentId: String, |
| 202 | sourceParentDocumentId: String, | ||
| 182 | targetParentDocumentId: String? | 203 | targetParentDocumentId: String? |
| 183 | ): String { | 204 | ): String { |
| 184 | if (!isChildDocument(sourceParentDocumentId, sourceDocumentId)) | 205 | if (!isChildDocument(sourceParentDocumentId, sourceDocumentId)) { |
| 185 | throw FileNotFoundException("Couldn't copy document '$sourceDocumentId' as its parent is not '$sourceParentDocumentId'") | 206 | throw FileNotFoundException( |
| 207 | "Couldn't copy document '$sourceDocumentId' as its parent is not " + | ||
| 208 | "'$sourceParentDocumentId'" | ||
| 209 | ) | ||
| 210 | } | ||
| 186 | 211 | ||
| 187 | return copyDocument(sourceDocumentId, targetParentDocumentId) | 212 | return copyDocument(sourceDocumentId, targetParentDocumentId) |
| 188 | } | 213 | } |
| @@ -193,8 +218,13 @@ class DocumentProvider : DocumentsProvider() { | |||
| 193 | val newFile = parent.resolveWithoutConflict(oldFile.name) | 218 | val newFile = parent.resolveWithoutConflict(oldFile.name) |
| 194 | 219 | ||
| 195 | try { | 220 | try { |
| 196 | if (!(newFile.createNewFile() && newFile.setWritable(true) && newFile.setReadable(true))) | 221 | if (!( |
| 222 | newFile.createNewFile() && newFile.setWritable(true) && | ||
| 223 | newFile.setReadable(true) | ||
| 224 | ) | ||
| 225 | ) { | ||
| 197 | throw IOException("Couldn't create new file") | 226 | throw IOException("Couldn't create new file") |
| 227 | } | ||
| 198 | 228 | ||
| 199 | FileInputStream(oldFile).use { inStream -> | 229 | FileInputStream(oldFile).use { inStream -> |
| 200 | FileOutputStream(newFile).use { outStream -> | 230 | FileOutputStream(newFile).use { outStream -> |
| @@ -209,12 +239,14 @@ class DocumentProvider : DocumentsProvider() { | |||
| 209 | } | 239 | } |
| 210 | 240 | ||
| 211 | override fun moveDocument( | 241 | override fun moveDocument( |
| 212 | sourceDocumentId: String, sourceParentDocumentId: String?, | 242 | sourceDocumentId: String, |
| 243 | sourceParentDocumentId: String?, | ||
| 213 | targetParentDocumentId: String? | 244 | targetParentDocumentId: String? |
| 214 | ): String { | 245 | ): String { |
| 215 | try { | 246 | try { |
| 216 | val newDocumentId = copyDocument( | 247 | val newDocumentId = copyDocument( |
| 217 | sourceDocumentId, sourceParentDocumentId!!, | 248 | sourceDocumentId, |
| 249 | sourceParentDocumentId!!, | ||
| 218 | targetParentDocumentId | 250 | targetParentDocumentId |
| 219 | ) | 251 | ) |
| 220 | removeDocument(sourceDocumentId, sourceParentDocumentId) | 252 | removeDocument(sourceDocumentId, sourceParentDocumentId) |
| @@ -245,24 +277,30 @@ class DocumentProvider : DocumentsProvider() { | |||
| 245 | add(DocumentsContract.Document.COLUMN_DOCUMENT_ID, localDocumentId) | 277 | add(DocumentsContract.Document.COLUMN_DOCUMENT_ID, localDocumentId) |
| 246 | add( | 278 | add( |
| 247 | DocumentsContract.Document.COLUMN_DISPLAY_NAME, | 279 | DocumentsContract.Document.COLUMN_DISPLAY_NAME, |
| 248 | if (localFile == baseDirectory) context!!.getString(R.string.app_name) else localFile.name | 280 | if (localFile == baseDirectory) { |
| 281 | context!!.getString(R.string.app_name) | ||
| 282 | } else { | ||
| 283 | localFile.name | ||
| 284 | } | ||
| 249 | ) | 285 | ) |
| 250 | add(DocumentsContract.Document.COLUMN_SIZE, localFile.length()) | 286 | add(DocumentsContract.Document.COLUMN_SIZE, localFile.length()) |
| 251 | add(DocumentsContract.Document.COLUMN_MIME_TYPE, getTypeForFile(localFile)) | 287 | add(DocumentsContract.Document.COLUMN_MIME_TYPE, getTypeForFile(localFile)) |
| 252 | add(DocumentsContract.Document.COLUMN_LAST_MODIFIED, localFile.lastModified()) | 288 | add(DocumentsContract.Document.COLUMN_LAST_MODIFIED, localFile.lastModified()) |
| 253 | add(DocumentsContract.Document.COLUMN_FLAGS, flags) | 289 | add(DocumentsContract.Document.COLUMN_FLAGS, flags) |
| 254 | if (localFile == baseDirectory) | 290 | if (localFile == baseDirectory) { |
| 255 | add(DocumentsContract.Root.COLUMN_ICON, R.drawable.ic_yuzu) | 291 | add(DocumentsContract.Root.COLUMN_ICON, R.drawable.ic_yuzu) |
| 292 | } | ||
| 256 | } | 293 | } |
| 257 | 294 | ||
| 258 | return cursor | 295 | return cursor |
| 259 | } | 296 | } |
| 260 | 297 | ||
| 261 | private fun getTypeForFile(file: File): Any { | 298 | private fun getTypeForFile(file: File): Any { |
| 262 | return if (file.isDirectory) | 299 | return if (file.isDirectory) { |
| 263 | DocumentsContract.Document.MIME_TYPE_DIR | 300 | DocumentsContract.Document.MIME_TYPE_DIR |
| 264 | else | 301 | } else { |
| 265 | getTypeForName(file.name) | 302 | getTypeForName(file.name) |
| 303 | } | ||
| 266 | } | 304 | } |
| 267 | 305 | ||
| 268 | private fun getTypeForName(name: String): Any { | 306 | private fun getTypeForName(name: String): Any { |
| @@ -270,8 +308,9 @@ class DocumentProvider : DocumentsProvider() { | |||
| 270 | if (lastDot >= 0) { | 308 | if (lastDot >= 0) { |
| 271 | val extension = name.substring(lastDot + 1) | 309 | val extension = name.substring(lastDot + 1) |
| 272 | val mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension) | 310 | val mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension) |
| 273 | if (mime != null) | 311 | if (mime != null) { |
| 274 | return mime | 312 | return mime |
| 313 | } | ||
| 275 | } | 314 | } |
| 276 | return "application/octect-stream" | 315 | return "application/octect-stream" |
| 277 | } | 316 | } |
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 3dfd66779..63b4df273 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 | |||
| @@ -8,6 +8,7 @@ enum class BooleanSetting( | |||
| 8 | override val section: String, | 8 | override val section: String, |
| 9 | override val defaultValue: Boolean | 9 | override val defaultValue: Boolean |
| 10 | ) : AbstractBooleanSetting { | 10 | ) : AbstractBooleanSetting { |
| 11 | PICTURE_IN_PICTURE("picture_in_picture", Settings.SECTION_GENERAL, true), | ||
| 11 | USE_CUSTOM_RTC("custom_rtc_enabled", Settings.SECTION_SYSTEM, false); | 12 | USE_CUSTOM_RTC("custom_rtc_enabled", Settings.SECTION_SYSTEM, false); |
| 12 | 13 | ||
| 13 | override var boolean: Boolean = defaultValue | 14 | override var boolean: Boolean = defaultValue |
| @@ -27,6 +28,7 @@ enum class BooleanSetting( | |||
| 27 | 28 | ||
| 28 | companion object { | 29 | companion object { |
| 29 | private val NOT_RUNTIME_EDITABLE = listOf( | 30 | private val NOT_RUNTIME_EDITABLE = listOf( |
| 31 | PICTURE_IN_PICTURE, | ||
| 30 | USE_CUSTOM_RTC | 32 | USE_CUSTOM_RTC |
| 31 | ) | 33 | ) |
| 32 | 34 | ||
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 fa84f94f5..4427a7d9d 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 | |||
| @@ -93,6 +93,11 @@ enum class IntSetting( | |||
| 93 | Settings.SECTION_RENDERER, | 93 | Settings.SECTION_RENDERER, |
| 94 | 0 | 94 | 0 |
| 95 | ), | 95 | ), |
| 96 | RENDERER_SCREEN_LAYOUT( | ||
| 97 | "screen_layout", | ||
| 98 | Settings.SECTION_RENDERER, | ||
| 99 | Settings.LayoutOption_MobileLandscape | ||
| 100 | ), | ||
| 96 | RENDERER_ASPECT_RATIO( | 101 | RENDERER_ASPECT_RATIO( |
| 97 | "aspect_ratio", | 102 | "aspect_ratio", |
| 98 | Settings.SECTION_RENDERER, | 103 | Settings.SECTION_RENDERER, |
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 8df20b928..88afb2223 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,11 +4,11 @@ | |||
| 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 org.yuzu.yuzu_emu.R | 8 | import org.yuzu.yuzu_emu.R |
| 8 | import org.yuzu.yuzu_emu.YuzuApplication | 9 | import org.yuzu.yuzu_emu.YuzuApplication |
| 9 | import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivityView | 10 | import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivityView |
| 10 | import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile | 11 | import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile |
| 11 | import java.util.* | ||
| 12 | 12 | ||
| 13 | class Settings { | 13 | class Settings { |
| 14 | private var gameId: String? = null | 14 | private var gameId: String? = null |
| @@ -133,7 +133,6 @@ class Settings { | |||
| 133 | const val PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER = "EmulationMenuSettings_JoystickRelCenter" | 133 | const val PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER = "EmulationMenuSettings_JoystickRelCenter" |
| 134 | const val PREF_MENU_SETTINGS_DPAD_SLIDE = "EmulationMenuSettings_DpadSlideEnable" | 134 | const val PREF_MENU_SETTINGS_DPAD_SLIDE = "EmulationMenuSettings_DpadSlideEnable" |
| 135 | const val PREF_MENU_SETTINGS_HAPTICS = "EmulationMenuSettings_Haptics" | 135 | const val PREF_MENU_SETTINGS_HAPTICS = "EmulationMenuSettings_Haptics" |
| 136 | const val PREF_MENU_SETTINGS_LANDSCAPE = "EmulationMenuSettings_LandscapeScreenLayout" | ||
| 137 | const val PREF_MENU_SETTINGS_SHOW_FPS = "EmulationMenuSettings_ShowFps" | 136 | const val PREF_MENU_SETTINGS_SHOW_FPS = "EmulationMenuSettings_ShowFps" |
| 138 | const val PREF_MENU_SETTINGS_SHOW_OVERLAY = "EmulationMenuSettings_ShowOverlay" | 137 | const val PREF_MENU_SETTINGS_SHOW_OVERLAY = "EmulationMenuSettings_ShowOverlay" |
| 139 | 138 | ||
| @@ -144,6 +143,10 @@ class Settings { | |||
| 144 | 143 | ||
| 145 | private val configFileSectionsMap: MutableMap<String, List<String>> = HashMap() | 144 | private val configFileSectionsMap: MutableMap<String, List<String>> = HashMap() |
| 146 | 145 | ||
| 146 | const val LayoutOption_Unspecified = 0 | ||
| 147 | const val LayoutOption_MobilePortrait = 4 | ||
| 148 | const val LayoutOption_MobileLandscape = 5 | ||
| 149 | |||
| 147 | init { | 150 | init { |
| 148 | configFileSectionsMap[SettingsFile.FILE_NAME_CONFIG] = | 151 | configFileSectionsMap[SettingsFile.FILE_NAME_CONFIG] = |
| 149 | listOf( | 152 | listOf( |
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 9eac9904e..7306ec458 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,7 +4,6 @@ | |||
| 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.IntSetting | ||
| 8 | 7 | ||
| 9 | class SingleChoiceSetting( | 8 | class SingleChoiceSetting( |
| 10 | setting: AbstractIntSetting?, | 9 | setting: AbstractIntSetting?, |
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 842648ce4..92d0167ae 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,13 +3,11 @@ | |||
| 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.AbstractFloatSetting | 7 | import org.yuzu.yuzu_emu.features.settings.model.AbstractFloatSetting |
| 7 | import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting | 8 | import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting |
| 8 | import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting | 9 | import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting |
| 9 | import org.yuzu.yuzu_emu.features.settings.model.FloatSetting | ||
| 10 | import org.yuzu.yuzu_emu.features.settings.model.IntSetting | ||
| 11 | import org.yuzu.yuzu_emu.utils.Log | 10 | import org.yuzu.yuzu_emu.utils.Log |
| 12 | import kotlin.math.roundToInt | ||
| 13 | 11 | ||
| 14 | class SliderSetting( | 12 | class SliderSetting( |
| 15 | setting: AbstractSetting?, | 13 | setting: AbstractSetting?, |
| @@ -19,7 +17,7 @@ class SliderSetting( | |||
| 19 | val max: Int, | 17 | val max: Int, |
| 20 | val units: String, | 18 | val units: String, |
| 21 | val key: String? = null, | 19 | val key: String? = null, |
| 22 | val defaultValue: Int? = null, | 20 | val defaultValue: Int? = null |
| 23 | ) : SettingsItem(setting, titleId, descriptionId) { | 21 | ) : SettingsItem(setting, titleId, descriptionId) { |
| 24 | override val type = TYPE_SLIDER | 22 | override val type = TYPE_SLIDER |
| 25 | 23 | ||
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 9e9b00d10..bad34fd88 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 | |||
| @@ -5,7 +5,6 @@ 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.AbstractSetting |
| 7 | import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting | 7 | import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting |
| 8 | import org.yuzu.yuzu_emu.features.settings.model.StringSetting | ||
| 9 | 8 | ||
| 10 | class StringSingleChoiceSetting( | 9 | class StringSingleChoiceSetting( |
| 11 | val key: String? = null, | 10 | val key: String? = null, |
| @@ -22,7 +21,9 @@ class StringSingleChoiceSetting( | |||
| 22 | if (valuesId == null) return null | 21 | if (valuesId == null) return null |
| 23 | return if (index >= 0 && index < valuesId.size) { | 22 | return if (index >= 0 && index < valuesId.size) { |
| 24 | valuesId[index] | 23 | valuesId[index] |
| 25 | } else "" | 24 | } else { |
| 25 | "" | ||
| 26 | } | ||
| 26 | } | 27 | } |
| 27 | 28 | ||
| 28 | val selectedValue: String | 29 | val selectedValue: String |
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 a3ef59c2f..8a9d13a92 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt | |||
| @@ -3,8 +3,6 @@ | |||
| 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 | |||
| 8 | class SubmenuSetting( | 6 | class SubmenuSetting( |
| 9 | titleId: Int, | 7 | titleId: Int, |
| 10 | descriptionId: Int, | 8 | descriptionId: Int, |
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 72e2cce2a..a5af5a7ae 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 | |||
| @@ -8,17 +8,18 @@ import android.content.Intent | |||
| 8 | import android.os.Bundle | 8 | import android.os.Bundle |
| 9 | import android.view.Menu | 9 | import android.view.Menu |
| 10 | import android.view.View | 10 | import android.view.View |
| 11 | import android.view.ViewGroup.MarginLayoutParams | ||
| 11 | import android.widget.Toast | 12 | import android.widget.Toast |
| 13 | import androidx.activity.OnBackPressedCallback | ||
| 14 | import androidx.activity.result.ActivityResultLauncher | ||
| 12 | import androidx.activity.viewModels | 15 | import androidx.activity.viewModels |
| 13 | import androidx.appcompat.app.AppCompatActivity | 16 | import androidx.appcompat.app.AppCompatActivity |
| 14 | import androidx.core.view.ViewCompat | 17 | import androidx.core.view.ViewCompat |
| 15 | import androidx.core.view.WindowCompat | 18 | import androidx.core.view.WindowCompat |
| 16 | import androidx.core.view.WindowInsetsCompat | 19 | import androidx.core.view.WindowInsetsCompat |
| 17 | import android.view.ViewGroup.MarginLayoutParams | ||
| 18 | import androidx.activity.OnBackPressedCallback | ||
| 19 | import androidx.core.view.updatePadding | 20 | import androidx.core.view.updatePadding |
| 20 | import com.google.android.material.color.MaterialColors | 21 | import com.google.android.material.color.MaterialColors |
| 21 | import org.yuzu.yuzu_emu.NativeLibrary | 22 | import java.io.IOException |
| 22 | import org.yuzu.yuzu_emu.R | 23 | import org.yuzu.yuzu_emu.R |
| 23 | import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding | 24 | import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding |
| 24 | import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting | 25 | import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting |
| @@ -29,7 +30,6 @@ import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel | |||
| 29 | import org.yuzu.yuzu_emu.features.settings.model.StringSetting | 30 | import org.yuzu.yuzu_emu.features.settings.model.StringSetting |
| 30 | import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile | 31 | import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile |
| 31 | import org.yuzu.yuzu_emu.utils.* | 32 | import org.yuzu.yuzu_emu.utils.* |
| 32 | import java.io.IOException | ||
| 33 | 33 | ||
| 34 | class SettingsActivity : AppCompatActivity(), SettingsActivityView { | 34 | class SettingsActivity : AppCompatActivity(), SettingsActivityView { |
| 35 | private val presenter = SettingsActivityPresenter(this) | 35 | private val presenter = SettingsActivityPresenter(this) |
| @@ -59,7 +59,9 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView { | |||
| 59 | setSupportActionBar(binding.toolbarSettings) | 59 | setSupportActionBar(binding.toolbarSettings) |
| 60 | supportActionBar!!.setDisplayHomeAsUpEnabled(true) | 60 | supportActionBar!!.setDisplayHomeAsUpEnabled(true) |
| 61 | 61 | ||
| 62 | if (InsetsHelper.getSystemGestureType(applicationContext) != InsetsHelper.GESTURE_NAVIGATION) { | 62 | if (InsetsHelper.getSystemGestureType(applicationContext) != |
| 63 | InsetsHelper.GESTURE_NAVIGATION | ||
| 64 | ) { | ||
| 63 | binding.navigationBarShade.setBackgroundColor( | 65 | binding.navigationBarShade.setBackgroundColor( |
| 64 | ThemeHelper.getColorWithOpacity( | 66 | ThemeHelper.getColorWithOpacity( |
| 65 | MaterialColors.getColor( | 67 | MaterialColors.getColor( |
| @@ -75,7 +77,8 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView { | |||
| 75 | this, | 77 | this, |
| 76 | object : OnBackPressedCallback(true) { | 78 | object : OnBackPressedCallback(true) { |
| 77 | override fun handleOnBackPressed() = navigateBack() | 79 | override fun handleOnBackPressed() = navigateBack() |
| 78 | }) | 80 | } |
| 81 | ) | ||
| 79 | 82 | ||
| 80 | setInsets() | 83 | setInsets() |
| 81 | } | 84 | } |
| @@ -148,11 +151,13 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView { | |||
| 148 | private fun areSystemAnimationsEnabled(): Boolean { | 151 | private fun areSystemAnimationsEnabled(): Boolean { |
| 149 | val duration = android.provider.Settings.Global.getFloat( | 152 | val duration = android.provider.Settings.Global.getFloat( |
| 150 | contentResolver, | 153 | contentResolver, |
| 151 | android.provider.Settings.Global.ANIMATOR_DURATION_SCALE, 1f | 154 | android.provider.Settings.Global.ANIMATOR_DURATION_SCALE, |
| 155 | 1f | ||
| 152 | ) | 156 | ) |
| 153 | val transition = android.provider.Settings.Global.getFloat( | 157 | val transition = android.provider.Settings.Global.getFloat( |
| 154 | contentResolver, | 158 | contentResolver, |
| 155 | android.provider.Settings.Global.TRANSITION_ANIMATION_SCALE, 1f | 159 | android.provider.Settings.Global.TRANSITION_ANIMATION_SCALE, |
| 160 | 1f | ||
| 156 | ) | 161 | ) |
| 157 | return duration != 0f && transition != 0f | 162 | return duration != 0f && transition != 0f |
| 158 | } | 163 | } |
| @@ -207,7 +212,9 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView { | |||
| 207 | get() = supportFragmentManager.findFragmentByTag(FRAGMENT_TAG) as SettingsFragment? | 212 | get() = supportFragmentManager.findFragmentByTag(FRAGMENT_TAG) as SettingsFragment? |
| 208 | 213 | ||
| 209 | private fun setInsets() { | 214 | private fun setInsets() { |
| 210 | ViewCompat.setOnApplyWindowInsetsListener(binding.frameContent) { view: View, windowInsets: WindowInsetsCompat -> | 215 | ViewCompat.setOnApplyWindowInsetsListener( |
| 216 | binding.frameContent | ||
| 217 | ) { view: View, windowInsets: WindowInsetsCompat -> | ||
| 211 | val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) | 218 | val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) |
| 212 | val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) | 219 | val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) |
| 213 | view.updatePadding( | 220 | view.updatePadding( |
| @@ -239,5 +246,17 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView { | |||
| 239 | settings.putExtra(ARG_GAME_ID, gameId) | 246 | settings.putExtra(ARG_GAME_ID, gameId) |
| 240 | context.startActivity(settings) | 247 | context.startActivity(settings) |
| 241 | } | 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 | } | ||
| 242 | } | 261 | } |
| 243 | } | 262 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityPresenter.kt index 4361d95fb..93e677b21 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityPresenter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityPresenter.kt | |||
| @@ -6,12 +6,12 @@ package org.yuzu.yuzu_emu.features.settings.ui | |||
| 6 | import android.content.Context | 6 | import android.content.Context |
| 7 | import android.os.Bundle | 7 | import android.os.Bundle |
| 8 | import android.text.TextUtils | 8 | import android.text.TextUtils |
| 9 | import java.io.File | ||
| 9 | import org.yuzu.yuzu_emu.NativeLibrary | 10 | import org.yuzu.yuzu_emu.NativeLibrary |
| 10 | import org.yuzu.yuzu_emu.features.settings.model.Settings | 11 | import org.yuzu.yuzu_emu.features.settings.model.Settings |
| 11 | import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile | 12 | import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile |
| 12 | import org.yuzu.yuzu_emu.utils.DirectoryInitialization | 13 | import org.yuzu.yuzu_emu.utils.DirectoryInitialization |
| 13 | import org.yuzu.yuzu_emu.utils.Log | 14 | import org.yuzu.yuzu_emu.utils.Log |
| 14 | import java.io.File | ||
| 15 | 15 | ||
| 16 | class SettingsActivityPresenter(private val activityView: SettingsActivityView) { | 16 | class SettingsActivityPresenter(private val activityView: SettingsActivityView) { |
| 17 | val settings: Settings get() = activityView.settings | 17 | val settings: Settings get() = activityView.settings |
| @@ -46,9 +46,15 @@ class SettingsActivityPresenter(private val activityView: SettingsActivityView) | |||
| 46 | 46 | ||
| 47 | private fun prepareDirectoriesIfNeeded() { | 47 | private fun prepareDirectoriesIfNeeded() { |
| 48 | val configFile = | 48 | val configFile = |
| 49 | File(DirectoryInitialization.userDirectory + "/config/" + SettingsFile.FILE_NAME_CONFIG + ".ini") | 49 | File( |
| 50 | "${DirectoryInitialization.userDirectory}/config/" + | ||
| 51 | "${SettingsFile.FILE_NAME_CONFIG}.ini" | ||
| 52 | ) | ||
| 50 | if (!configFile.exists()) { | 53 | if (!configFile.exists()) { |
| 51 | Log.error(DirectoryInitialization.userDirectory + "/config/" + SettingsFile.FILE_NAME_CONFIG + ".ini") | 54 | Log.error( |
| 55 | "${DirectoryInitialization.userDirectory}/config/" + | ||
| 56 | "${SettingsFile.FILE_NAME_CONFIG}.ini" | ||
| 57 | ) | ||
| 52 | Log.error("yuzu config file could not be found!") | 58 | Log.error("yuzu config file could not be found!") |
| 53 | } | 59 | } |
| 54 | 60 | ||
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 1eb4899fc..eac6a134b 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 | |||
| @@ -13,7 +13,6 @@ import android.view.ViewGroup | |||
| 13 | import android.widget.TextView | 13 | import android.widget.TextView |
| 14 | import androidx.appcompat.app.AlertDialog | 14 | import androidx.appcompat.app.AlertDialog |
| 15 | import androidx.appcompat.app.AppCompatActivity | 15 | import androidx.appcompat.app.AppCompatActivity |
| 16 | import androidx.fragment.app.setFragmentResultListener | ||
| 17 | import androidx.recyclerview.widget.RecyclerView | 16 | import androidx.recyclerview.widget.RecyclerView |
| 18 | import com.google.android.material.datepicker.MaterialDatePicker | 17 | import com.google.android.material.datepicker.MaterialDatePicker |
| 19 | import com.google.android.material.dialog.MaterialAlertDialogBuilder | 18 | import com.google.android.material.dialog.MaterialAlertDialogBuilder |
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 867147950..70a74c4dd 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 | |||
| @@ -50,7 +50,10 @@ class SettingsFragment : Fragment(), SettingsFragmentView { | |||
| 50 | 50 | ||
| 51 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | 51 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { |
| 52 | settingsAdapter = SettingsAdapter(this, requireActivity()) | 52 | settingsAdapter = SettingsAdapter(this, requireActivity()) |
| 53 | val dividerDecoration = MaterialDividerItemDecoration(requireContext(), LinearLayoutManager.VERTICAL) | 53 | val dividerDecoration = MaterialDividerItemDecoration( |
| 54 | requireContext(), | ||
| 55 | LinearLayoutManager.VERTICAL | ||
| 56 | ) | ||
| 54 | dividerDecoration.isLastItemDecorated = false | 57 | dividerDecoration.isLastItemDecorated = false |
| 55 | binding.listSettings.apply { | 58 | binding.listSettings.apply { |
| 56 | adapter = settingsAdapter | 59 | adapter = settingsAdapter |
| @@ -99,7 +102,9 @@ class SettingsFragment : Fragment(), SettingsFragmentView { | |||
| 99 | } | 102 | } |
| 100 | 103 | ||
| 101 | private fun setInsets() { | 104 | private fun setInsets() { |
| 102 | ViewCompat.setOnApplyWindowInsetsListener(binding.listSettings) { view: View, windowInsets: WindowInsetsCompat -> | 105 | ViewCompat.setOnApplyWindowInsetsListener( |
| 106 | binding.listSettings | ||
| 107 | ) { view: View, windowInsets: WindowInsetsCompat -> | ||
| 103 | val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) | 108 | val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) |
| 104 | view.updatePadding(bottom = insets.bottom) | 109 | view.updatePadding(bottom = insets.bottom) |
| 105 | windowInsets | 110 | windowInsets |
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 1ceaa6fb4..c8c85dd7a 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 | |||
| @@ -7,7 +7,6 @@ import android.content.SharedPreferences | |||
| 7 | import android.os.Build | 7 | import android.os.Build |
| 8 | import android.text.TextUtils | 8 | import android.text.TextUtils |
| 9 | import androidx.preference.PreferenceManager | 9 | import androidx.preference.PreferenceManager |
| 10 | import com.google.android.material.dialog.MaterialAlertDialogBuilder | ||
| 11 | import org.yuzu.yuzu_emu.R | 10 | import org.yuzu.yuzu_emu.R |
| 12 | import org.yuzu.yuzu_emu.YuzuApplication | 11 | import org.yuzu.yuzu_emu.YuzuApplication |
| 13 | import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting | 12 | import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting |
| @@ -166,6 +165,15 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) | |||
| 166 | IntSetting.CPU_ACCURACY.defaultValue | 165 | IntSetting.CPU_ACCURACY.defaultValue |
| 167 | ) | 166 | ) |
| 168 | ) | 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 | ) | ||
| 169 | } | 177 | } |
| 170 | } | 178 | } |
| 171 | 179 | ||
| @@ -227,7 +235,6 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) | |||
| 227 | private fun addGraphicsSettings(sl: ArrayList<SettingsItem>) { | 235 | private fun addGraphicsSettings(sl: ArrayList<SettingsItem>) { |
| 228 | settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_graphics)) | 236 | settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_graphics)) |
| 229 | sl.apply { | 237 | sl.apply { |
| 230 | |||
| 231 | add( | 238 | add( |
| 232 | SingleChoiceSetting( | 239 | SingleChoiceSetting( |
| 233 | IntSetting.RENDERER_ACCURACY, | 240 | IntSetting.RENDERER_ACCURACY, |
| @@ -285,6 +292,17 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) | |||
| 285 | ) | 292 | ) |
| 286 | add( | 293 | add( |
| 287 | SingleChoiceSetting( | 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( | ||
| 288 | IntSetting.RENDERER_ASPECT_RATIO, | 306 | IntSetting.RENDERER_ASPECT_RATIO, |
| 289 | R.string.renderer_aspect_ratio, | 307 | R.string.renderer_aspect_ratio, |
| 290 | 0, | 308 | 0, |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt index 04c045e77..7955532ee 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 | |||
| @@ -4,15 +4,15 @@ | |||
| 4 | package org.yuzu.yuzu_emu.features.settings.ui.viewholder | 4 | package org.yuzu.yuzu_emu.features.settings.ui.viewholder |
| 5 | 5 | ||
| 6 | import android.view.View | 6 | import android.view.View |
| 7 | import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding | ||
| 8 | import org.yuzu.yuzu_emu.features.settings.model.view.DateTimeSetting | ||
| 9 | import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem | ||
| 10 | import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter | ||
| 11 | import java.time.Instant | 7 | import java.time.Instant |
| 12 | import java.time.ZoneId | 8 | import java.time.ZoneId |
| 13 | import java.time.ZonedDateTime | 9 | import java.time.ZonedDateTime |
| 14 | import java.time.format.DateTimeFormatter | 10 | import java.time.format.DateTimeFormatter |
| 15 | import java.time.format.FormatStyle | 11 | import java.time.format.FormatStyle |
| 12 | import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding | ||
| 13 | import org.yuzu.yuzu_emu.features.settings.model.view.DateTimeSetting | ||
| 14 | import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem | ||
| 15 | import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter | ||
| 16 | 16 | ||
| 17 | class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) : | 17 | class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) : |
| 18 | SettingViewHolder(binding.root, adapter) { | 18 | SettingViewHolder(binding.root, adapter) { |
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 b163bd6ca..54f531795 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 | |||
| @@ -6,8 +6,8 @@ package org.yuzu.yuzu_emu.features.settings.ui.viewholder | |||
| 6 | import android.view.View | 6 | import android.view.View |
| 7 | import android.widget.CompoundButton | 7 | import android.widget.CompoundButton |
| 8 | import org.yuzu.yuzu_emu.databinding.ListItemSettingSwitchBinding | 8 | import org.yuzu.yuzu_emu.databinding.ListItemSettingSwitchBinding |
| 9 | import org.yuzu.yuzu_emu.features.settings.model.view.SwitchSetting | ||
| 10 | import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem | 9 | import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem |
| 10 | import org.yuzu.yuzu_emu.features.settings.model.view.SwitchSetting | ||
| 11 | import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter | 11 | import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter |
| 12 | 12 | ||
| 13 | class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter: SettingsAdapter) : | 13 | class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter: SettingsAdapter) : |
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 e29bca11d..20a0636df 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,6 +3,8 @@ | |||
| 3 | 3 | ||
| 4 | package org.yuzu.yuzu_emu.features.settings.utils | 4 | package org.yuzu.yuzu_emu.features.settings.utils |
| 5 | 5 | ||
| 6 | import java.io.* | ||
| 7 | import java.util.* | ||
| 6 | import org.ini4j.Wini | 8 | import org.ini4j.Wini |
| 7 | import org.yuzu.yuzu_emu.NativeLibrary | 9 | import org.yuzu.yuzu_emu.NativeLibrary |
| 8 | import org.yuzu.yuzu_emu.R | 10 | import org.yuzu.yuzu_emu.R |
| @@ -13,8 +15,6 @@ import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivityView | |||
| 13 | import org.yuzu.yuzu_emu.utils.BiMap | 15 | import org.yuzu.yuzu_emu.utils.BiMap |
| 14 | import org.yuzu.yuzu_emu.utils.DirectoryInitialization | 16 | import org.yuzu.yuzu_emu.utils.DirectoryInitialization |
| 15 | import org.yuzu.yuzu_emu.utils.Log | 17 | import org.yuzu.yuzu_emu.utils.Log |
| 16 | import java.io.* | ||
| 17 | import java.util.* | ||
| 18 | 18 | ||
| 19 | /** | 19 | /** |
| 20 | * Contains static methods for interacting with .ini files in which settings are stored. | 20 | * Contains static methods for interacting with .ini files in which settings are stored. |
| @@ -137,9 +137,12 @@ object SettingsFile { | |||
| 137 | for (settingKey in sortedKeySet) { | 137 | for (settingKey in sortedKeySet) { |
| 138 | val setting = settings[settingKey] | 138 | val setting = settings[settingKey] |
| 139 | NativeLibrary.setUserSetting( | 139 | NativeLibrary.setUserSetting( |
| 140 | gameId, mapSectionNameFromIni( | 140 | gameId, |
| 141 | mapSectionNameFromIni( | ||
| 141 | section.name | 142 | section.name |
| 142 | ), setting!!.key, setting.valueAsString | 143 | ), |
| 144 | setting!!.key, | ||
| 145 | setting.valueAsString | ||
| 143 | ) | 146 | ) |
| 144 | } | 147 | } |
| 145 | } | 148 | } |
| @@ -148,13 +151,17 @@ object SettingsFile { | |||
| 148 | private fun mapSectionNameFromIni(generalSectionName: String): String? { | 151 | private fun mapSectionNameFromIni(generalSectionName: String): String? { |
| 149 | return if (sectionsMap.getForward(generalSectionName) != null) { | 152 | return if (sectionsMap.getForward(generalSectionName) != null) { |
| 150 | sectionsMap.getForward(generalSectionName) | 153 | sectionsMap.getForward(generalSectionName) |
| 151 | } else generalSectionName | 154 | } else { |
| 155 | generalSectionName | ||
| 156 | } | ||
| 152 | } | 157 | } |
| 153 | 158 | ||
| 154 | private fun mapSectionNameToIni(generalSectionName: String): String { | 159 | private fun mapSectionNameToIni(generalSectionName: String): String { |
| 155 | return if (sectionsMap.getBackward(generalSectionName) != null) { | 160 | return if (sectionsMap.getBackward(generalSectionName) != null) { |
| 156 | sectionsMap.getBackward(generalSectionName).toString() | 161 | sectionsMap.getBackward(generalSectionName).toString() |
| 157 | } else generalSectionName | 162 | } else { |
| 163 | generalSectionName | ||
| 164 | } | ||
| 158 | } | 165 | } |
| 159 | 166 | ||
| 160 | fun getSettingsFile(fileName: String): File { | 167 | fun getSettingsFile(fileName: String): File { |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AboutFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AboutFragment.kt index c92e2755c..2ff827c6b 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AboutFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AboutFragment.kt | |||
| @@ -66,7 +66,11 @@ class AboutFragment : Fragment() { | |||
| 66 | true | 66 | true |
| 67 | } | 67 | } |
| 68 | 68 | ||
| 69 | binding.buttonContributors.setOnClickListener { openLink(getString(R.string.contributors_link)) } | 69 | binding.buttonContributors.setOnClickListener { |
| 70 | openLink( | ||
| 71 | getString(R.string.contributors_link) | ||
| 72 | ) | ||
| 73 | } | ||
| 70 | binding.buttonLicenses.setOnClickListener { | 74 | binding.buttonLicenses.setOnClickListener { |
| 71 | exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) | 75 | exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) |
| 72 | binding.root.findNavController().navigate(R.id.action_aboutFragment_to_licensesFragment) | 76 | binding.root.findNavController().navigate(R.id.action_aboutFragment_to_licensesFragment) |
| @@ -101,7 +105,9 @@ class AboutFragment : Fragment() { | |||
| 101 | } | 105 | } |
| 102 | 106 | ||
| 103 | private fun setInsets() = | 107 | private fun setInsets() = |
| 104 | ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _: View, windowInsets: WindowInsetsCompat -> | 108 | ViewCompat.setOnApplyWindowInsetsListener( |
| 109 | binding.root | ||
| 110 | ) { _: View, windowInsets: WindowInsetsCompat -> | ||
| 105 | val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) | 111 | val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) |
| 106 | val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) | 112 | val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) |
| 107 | 113 | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EarlyAccessFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EarlyAccessFragment.kt index d8bbc1ce4..dbc16da4a 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EarlyAccessFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EarlyAccessFragment.kt | |||
| @@ -49,7 +49,11 @@ class EarlyAccessFragment : Fragment() { | |||
| 49 | parentFragmentManager.primaryNavigationFragment?.findNavController()?.popBackStack() | 49 | parentFragmentManager.primaryNavigationFragment?.findNavController()?.popBackStack() |
| 50 | } | 50 | } |
| 51 | 51 | ||
| 52 | binding.getEarlyAccessButton.setOnClickListener { openLink(getString(R.string.play_store_link)) } | 52 | binding.getEarlyAccessButton.setOnClickListener { |
| 53 | openLink( | ||
| 54 | getString(R.string.play_store_link) | ||
| 55 | ) | ||
| 56 | } | ||
| 53 | 57 | ||
| 54 | setInsets() | 58 | setInsets() |
| 55 | } | 59 | } |
| @@ -60,7 +64,9 @@ class EarlyAccessFragment : Fragment() { | |||
| 60 | } | 64 | } |
| 61 | 65 | ||
| 62 | private fun setInsets() = | 66 | private fun setInsets() = |
| 63 | ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _: View, windowInsets: WindowInsetsCompat -> | 67 | ViewCompat.setOnApplyWindowInsetsListener( |
| 68 | binding.root | ||
| 69 | ) { _: View, windowInsets: WindowInsetsCompat -> | ||
| 64 | val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) | 70 | val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) |
| 65 | val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) | 71 | val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) |
| 66 | 72 | ||
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 02bfcdb1e..4643418c1 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,24 +7,26 @@ 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 | ||
| 10 | import android.content.SharedPreferences | 11 | import android.content.SharedPreferences |
| 11 | import android.content.pm.ActivityInfo | 12 | import android.content.pm.ActivityInfo |
| 12 | import android.content.res.Resources | 13 | import android.content.res.Configuration |
| 13 | import android.graphics.Color | 14 | import android.graphics.Color |
| 14 | import android.os.Bundle | 15 | import android.os.Bundle |
| 15 | import android.os.Handler | 16 | import android.os.Handler |
| 16 | import android.os.Looper | 17 | import android.os.Looper |
| 17 | import android.util.Rational | 18 | import android.util.Rational |
| 18 | import android.util.TypedValue | ||
| 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 | ||
| 22 | import androidx.appcompat.widget.PopupMenu | 24 | import androidx.appcompat.widget.PopupMenu |
| 23 | import androidx.core.content.res.ResourcesCompat | 25 | import androidx.core.content.res.ResourcesCompat |
| 24 | import androidx.core.graphics.Insets | 26 | import androidx.core.graphics.Insets |
| 25 | import androidx.core.view.ViewCompat | 27 | import androidx.core.view.ViewCompat |
| 26 | import androidx.core.view.WindowInsetsCompat | 28 | import androidx.core.view.WindowInsetsCompat |
| 27 | import androidx.core.view.updatePadding | 29 | import androidx.core.view.isVisible |
| 28 | import androidx.fragment.app.Fragment | 30 | import androidx.fragment.app.Fragment |
| 29 | import androidx.lifecycle.Lifecycle | 31 | import androidx.lifecycle.Lifecycle |
| 30 | import androidx.lifecycle.lifecycleScope | 32 | import androidx.lifecycle.lifecycleScope |
| @@ -48,6 +50,7 @@ import org.yuzu.yuzu_emu.features.settings.model.IntSetting | |||
| 48 | import org.yuzu.yuzu_emu.features.settings.model.Settings | 50 | import org.yuzu.yuzu_emu.features.settings.model.Settings |
| 49 | import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity | 51 | import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity |
| 50 | 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.overlay.InputOverlay | ||
| 51 | import org.yuzu.yuzu_emu.utils.* | 54 | import org.yuzu.yuzu_emu.utils.* |
| 52 | 55 | ||
| 53 | class EmulationFragment : Fragment(), SurfaceHolder.Callback { | 56 | class EmulationFragment : Fragment(), SurfaceHolder.Callback { |
| @@ -61,11 +64,28 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 61 | 64 | ||
| 62 | val args by navArgs<EmulationFragmentArgs>() | 65 | val args by navArgs<EmulationFragmentArgs>() |
| 63 | 66 | ||
| 67 | private var isInFoldableLayout = false | ||
| 68 | |||
| 69 | private lateinit var onReturnFromSettings: ActivityResultLauncher<Intent> | ||
| 70 | |||
| 64 | override fun onAttach(context: Context) { | 71 | override fun onAttach(context: Context) { |
| 65 | super.onAttach(context) | 72 | super.onAttach(context) |
| 66 | if (context is EmulationActivity) { | 73 | if (context is EmulationActivity) { |
| 67 | emulationActivity = context | 74 | emulationActivity = context |
| 68 | NativeLibrary.setEmulationActivity(context) | 75 | NativeLibrary.setEmulationActivity(context) |
| 76 | |||
| 77 | lifecycleScope.launch(Dispatchers.Main) { | ||
| 78 | lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { | ||
| 79 | WindowInfoTracker.getOrCreate(context) | ||
| 80 | .windowLayoutInfo(context) | ||
| 81 | .collect { updateFoldableLayout(context, it) } | ||
| 82 | } | ||
| 83 | } | ||
| 84 | |||
| 85 | onReturnFromSettings = context.activityResultRegistry.register( | ||
| 86 | "SettingsResult", | ||
| 87 | ActivityResultContracts.StartActivityForResult() | ||
| 88 | ) { updateScreenLayout() } | ||
| 69 | } else { | 89 | } else { |
| 70 | throw IllegalStateException("EmulationFragment must have EmulationActivity parent") | 90 | throw IllegalStateException("EmulationFragment must have EmulationActivity parent") |
| 71 | } | 91 | } |
| @@ -129,7 +149,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 129 | } | 149 | } |
| 130 | 150 | ||
| 131 | R.id.menu_settings -> { | 151 | R.id.menu_settings -> { |
| 132 | SettingsActivity.launch(requireContext(), SettingsFile.FILE_NAME_CONFIG, "") | 152 | SettingsActivity.launch( |
| 153 | requireContext(), | ||
| 154 | onReturnFromSettings, | ||
| 155 | SettingsFile.FILE_NAME_CONFIG, | ||
| 156 | "" | ||
| 157 | ) | ||
| 133 | true | 158 | true |
| 134 | } | 159 | } |
| 135 | 160 | ||
| @@ -154,15 +179,46 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 154 | requireActivity(), | 179 | requireActivity(), |
| 155 | object : OnBackPressedCallback(true) { | 180 | object : OnBackPressedCallback(true) { |
| 156 | override fun handleOnBackPressed() { | 181 | override fun handleOnBackPressed() { |
| 157 | if (binding.drawerLayout.isOpen) binding.drawerLayout.close() else binding.drawerLayout.open() | 182 | if (binding.drawerLayout.isOpen) { |
| 183 | binding.drawerLayout.close() | ||
| 184 | } else { | ||
| 185 | binding.drawerLayout.open() | ||
| 186 | } | ||
| 158 | } | 187 | } |
| 159 | }) | 188 | } |
| 189 | ) | ||
| 160 | 190 | ||
| 161 | viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Main) { | 191 | viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Main) { |
| 162 | lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { | 192 | lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { |
| 163 | WindowInfoTracker.getOrCreate(requireContext()) | 193 | WindowInfoTracker.getOrCreate(requireContext()) |
| 164 | .windowLayoutInfo(requireActivity()) | 194 | .windowLayoutInfo(requireActivity()) |
| 165 | .collect { updateCurrentLayout(requireActivity() as EmulationActivity, it) } | 195 | .collect { updateFoldableLayout(requireActivity() as EmulationActivity, it) } |
| 196 | } | ||
| 197 | } | ||
| 198 | } | ||
| 199 | |||
| 200 | override fun onConfigurationChanged(newConfig: Configuration) { | ||
| 201 | super.onConfigurationChanged(newConfig) | ||
| 202 | if (emulationActivity?.isInPictureInPictureMode == true) { | ||
| 203 | if (binding.drawerLayout.isOpen) { | ||
| 204 | binding.drawerLayout.close() | ||
| 205 | } | ||
| 206 | if (EmulationMenuSettings.showOverlay) { | ||
| 207 | binding.surfaceInputOverlay.post { binding.surfaceInputOverlay.isVisible = false } | ||
| 208 | } | ||
| 209 | } else { | ||
| 210 | if (EmulationMenuSettings.showOverlay) { | ||
| 211 | binding.surfaceInputOverlay.post { binding.surfaceInputOverlay.isVisible = true } | ||
| 212 | } | ||
| 213 | if (!isInFoldableLayout) { | ||
| 214 | if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) { | ||
| 215 | binding.surfaceInputOverlay.orientation = InputOverlay.PORTRAIT | ||
| 216 | } else { | ||
| 217 | binding.surfaceInputOverlay.orientation = InputOverlay.LANDSCAPE | ||
| 218 | } | ||
| 219 | } | ||
| 220 | if (!binding.surfaceInputOverlay.isInEditMode) { | ||
| 221 | refreshInputOverlay() | ||
| 166 | } | 222 | } |
| 167 | } | 223 | } |
| 168 | } | 224 | } |
| @@ -173,16 +229,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 173 | DirectoryInitialization.start(requireContext()) | 229 | DirectoryInitialization.start(requireContext()) |
| 174 | } | 230 | } |
| 175 | 231 | ||
| 176 | binding.surfaceEmulation.setAspectRatio( | 232 | updateScreenLayout() |
| 177 | when (IntSetting.RENDERER_ASPECT_RATIO.int) { | ||
| 178 | 0 -> Rational(16, 9) | ||
| 179 | 1 -> Rational(4, 3) | ||
| 180 | 2 -> Rational(21, 9) | ||
| 181 | 3 -> Rational(16, 10) | ||
| 182 | 4 -> null // Stretch | ||
| 183 | else -> Rational(16, 9) | ||
| 184 | } | ||
| 185 | ) | ||
| 186 | 233 | ||
| 187 | emulationState.run(emulationActivity!!.isActivityRecreated) | 234 | emulationState.run(emulationActivity!!.isActivityRecreated) |
| 188 | } | 235 | } |
| @@ -243,31 +290,72 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 243 | } | 290 | } |
| 244 | } | 291 | } |
| 245 | 292 | ||
| 246 | private val Number.toPx get() = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, this.toFloat(), Resources.getSystem().displayMetrics).toInt() | 293 | @SuppressLint("SourceLockedOrientationActivity") |
| 247 | 294 | private fun updateOrientation() { | |
| 248 | fun updateCurrentLayout(emulationActivity: EmulationActivity, newLayoutInfo: WindowLayoutInfo) { | 295 | emulationActivity?.let { |
| 249 | val isFolding = (newLayoutInfo.displayFeatures.find { it is FoldingFeature } as? FoldingFeature)?.let { | 296 | it.requestedOrientation = when (IntSetting.RENDERER_SCREEN_LAYOUT.int) { |
| 250 | if (it.isSeparating) { | 297 | Settings.LayoutOption_MobileLandscape -> |
| 251 | emulationActivity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED | 298 | ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE |
| 252 | if (it.orientation == FoldingFeature.Orientation.HORIZONTAL) { | 299 | Settings.LayoutOption_MobilePortrait -> |
| 253 | binding.surfaceEmulation.layoutParams.height = it.bounds.top | 300 | ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT |
| 254 | binding.inGameMenu.layoutParams.height = it.bounds.bottom | 301 | Settings.LayoutOption_Unspecified -> ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED |
| 255 | binding.overlayContainer.layoutParams.height = it.bounds.bottom - 48.toPx | 302 | else -> ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE |
| 256 | binding.overlayContainer.updatePadding(0, 0, 0, 24.toPx) | ||
| 257 | } | ||
| 258 | } | 303 | } |
| 259 | it.isSeparating | 304 | } |
| 260 | } ?: false | 305 | } |
| 306 | |||
| 307 | private fun updateScreenLayout() { | ||
| 308 | binding.surfaceEmulation.setAspectRatio( | ||
| 309 | when (IntSetting.RENDERER_ASPECT_RATIO.int) { | ||
| 310 | 0 -> Rational(16, 9) | ||
| 311 | 1 -> Rational(4, 3) | ||
| 312 | 2 -> Rational(21, 9) | ||
| 313 | 3 -> Rational(16, 10) | ||
| 314 | 4 -> null // Stretch | ||
| 315 | else -> Rational(16, 9) | ||
| 316 | } | ||
| 317 | ) | ||
| 318 | emulationActivity?.buildPictureInPictureParams() | ||
| 319 | updateOrientation() | ||
| 320 | } | ||
| 321 | |||
| 322 | private fun updateFoldableLayout( | ||
| 323 | emulationActivity: EmulationActivity, | ||
| 324 | newLayoutInfo: WindowLayoutInfo | ||
| 325 | ) { | ||
| 326 | val isFolding = | ||
| 327 | (newLayoutInfo.displayFeatures.find { it is FoldingFeature } as? FoldingFeature)?.let { | ||
| 328 | if (it.isSeparating) { | ||
| 329 | emulationActivity.requestedOrientation = | ||
| 330 | ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED | ||
| 331 | if (it.orientation == FoldingFeature.Orientation.HORIZONTAL) { | ||
| 332 | // Restrict emulation and overlays to the top of the screen | ||
| 333 | binding.emulationContainer.layoutParams.height = it.bounds.top | ||
| 334 | binding.overlayContainer.layoutParams.height = it.bounds.top | ||
| 335 | // Restrict input and menu drawer to the bottom of the screen | ||
| 336 | binding.inputContainer.layoutParams.height = it.bounds.bottom | ||
| 337 | binding.inGameMenu.layoutParams.height = it.bounds.bottom | ||
| 338 | |||
| 339 | isInFoldableLayout = true | ||
| 340 | binding.surfaceInputOverlay.orientation = InputOverlay.FOLDABLE | ||
| 341 | refreshInputOverlay() | ||
| 342 | } | ||
| 343 | } | ||
| 344 | it.isSeparating | ||
| 345 | } ?: false | ||
| 261 | if (!isFolding) { | 346 | if (!isFolding) { |
| 262 | binding.surfaceEmulation.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT | 347 | binding.emulationContainer.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT |
| 263 | binding.inGameMenu.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT | 348 | binding.inputContainer.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT |
| 264 | binding.overlayContainer.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT | 349 | binding.overlayContainer.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT |
| 265 | binding.overlayContainer.updatePadding(0, 0, 0, 0) | 350 | binding.inGameMenu.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT |
| 266 | emulationActivity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE | 351 | isInFoldableLayout = false |
| 352 | updateOrientation() | ||
| 353 | onConfigurationChanged(resources.configuration) | ||
| 267 | } | 354 | } |
| 268 | binding.surfaceInputOverlay.requestLayout() | 355 | binding.emulationContainer.requestLayout() |
| 269 | binding.inGameMenu.requestLayout() | 356 | binding.inputContainer.requestLayout() |
| 270 | binding.overlayContainer.requestLayout() | 357 | binding.overlayContainer.requestLayout() |
| 358 | binding.inGameMenu.requestLayout() | ||
| 271 | } | 359 | } |
| 272 | 360 | ||
| 273 | override fun surfaceCreated(holder: SurfaceHolder) { | 361 | override fun surfaceCreated(holder: SurfaceHolder) { |
| @@ -397,7 +485,19 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 397 | popup.show() | 485 | popup.show() |
| 398 | } | 486 | } |
| 399 | 487 | ||
| 488 | @SuppressLint("SourceLockedOrientationActivity") | ||
| 400 | private fun startConfiguringControls() { | 489 | private fun startConfiguringControls() { |
| 490 | // Lock the current orientation to prevent editing inconsistencies | ||
| 491 | if (IntSetting.RENDERER_SCREEN_LAYOUT.int == Settings.LayoutOption_Unspecified) { | ||
| 492 | emulationActivity?.let { | ||
| 493 | it.requestedOrientation = | ||
| 494 | if (resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) { | ||
| 495 | ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT | ||
| 496 | } else { | ||
| 497 | ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE | ||
| 498 | } | ||
| 499 | } | ||
| 500 | } | ||
| 401 | binding.doneControlConfig.visibility = View.VISIBLE | 501 | binding.doneControlConfig.visibility = View.VISIBLE |
| 402 | binding.surfaceInputOverlay.setIsInEditMode(true) | 502 | binding.surfaceInputOverlay.setIsInEditMode(true) |
| 403 | } | 503 | } |
| @@ -405,6 +505,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 405 | private fun stopConfiguringControls() { | 505 | private fun stopConfiguringControls() { |
| 406 | binding.doneControlConfig.visibility = View.GONE | 506 | binding.doneControlConfig.visibility = View.GONE |
| 407 | binding.surfaceInputOverlay.setIsInEditMode(false) | 507 | binding.surfaceInputOverlay.setIsInEditMode(false) |
| 508 | // Unlock the orientation if it was locked for editing | ||
| 509 | if (IntSetting.RENDERER_SCREEN_LAYOUT.int == Settings.LayoutOption_Unspecified) { | ||
| 510 | emulationActivity?.let { | ||
| 511 | it.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED | ||
| 512 | } | ||
| 513 | } | ||
| 408 | } | 514 | } |
| 409 | 515 | ||
| 410 | @SuppressLint("SetTextI18n") | 516 | @SuppressLint("SetTextI18n") |
| @@ -414,18 +520,22 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 414 | inputScaleSlider.apply { | 520 | inputScaleSlider.apply { |
| 415 | valueTo = 150F | 521 | valueTo = 150F |
| 416 | value = preferences.getInt(Settings.PREF_CONTROL_SCALE, 50).toFloat() | 522 | value = preferences.getInt(Settings.PREF_CONTROL_SCALE, 50).toFloat() |
| 417 | addOnChangeListener(Slider.OnChangeListener { _, value, _ -> | 523 | addOnChangeListener( |
| 418 | inputScaleValue.text = "${value.toInt()}%" | 524 | Slider.OnChangeListener { _, value, _ -> |
| 419 | setControlScale(value.toInt()) | 525 | inputScaleValue.text = "${value.toInt()}%" |
| 420 | }) | 526 | setControlScale(value.toInt()) |
| 527 | } | ||
| 528 | ) | ||
| 421 | } | 529 | } |
| 422 | inputOpacitySlider.apply { | 530 | inputOpacitySlider.apply { |
| 423 | valueTo = 100F | 531 | valueTo = 100F |
| 424 | value = preferences.getInt(Settings.PREF_CONTROL_OPACITY, 100).toFloat() | 532 | value = preferences.getInt(Settings.PREF_CONTROL_OPACITY, 100).toFloat() |
| 425 | addOnChangeListener(Slider.OnChangeListener { _, value, _ -> | 533 | addOnChangeListener( |
| 426 | inputOpacityValue.text = "${value.toInt()}%" | 534 | Slider.OnChangeListener { _, value, _ -> |
| 427 | setControlOpacity(value.toInt()) | 535 | inputOpacityValue.text = "${value.toInt()}%" |
| 428 | }) | 536 | setControlOpacity(value.toInt()) |
| 537 | } | ||
| 538 | ) | ||
| 429 | } | 539 | } |
| 430 | inputScaleValue.text = "${inputScaleSlider.value.toInt()}%" | 540 | inputScaleValue.text = "${inputScaleSlider.value.toInt()}%" |
| 431 | inputOpacityValue.text = "${inputOpacitySlider.value.toInt()}%" | 541 | inputOpacityValue.text = "${inputOpacitySlider.value.toInt()}%" |
| @@ -457,7 +567,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 457 | } | 567 | } |
| 458 | 568 | ||
| 459 | private fun setInsets() { | 569 | private fun setInsets() { |
| 460 | ViewCompat.setOnApplyWindowInsetsListener(binding.inGameMenu) { v: View, windowInsets: WindowInsetsCompat -> | 570 | ViewCompat.setOnApplyWindowInsetsListener( |
| 571 | binding.inGameMenu | ||
| 572 | ) { v: View, windowInsets: WindowInsetsCompat -> | ||
| 461 | val cutInsets: Insets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) | 573 | val cutInsets: Insets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) |
| 462 | var left = 0 | 574 | var left = 0 |
| 463 | var right = 0 | 575 | var right = 0 |
| @@ -577,8 +689,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 577 | state = State.PAUSED | 689 | state = State.PAUSED |
| 578 | } | 690 | } |
| 579 | 691 | ||
| 580 | State.PAUSED -> Log.warning("[EmulationFragment] Surface cleared while emulation paused.") | 692 | State.PAUSED -> Log.warning( |
| 581 | else -> Log.warning("[EmulationFragment] Surface cleared while emulation stopped.") | 693 | "[EmulationFragment] Surface cleared while emulation paused." |
| 694 | ) | ||
| 695 | else -> Log.warning( | ||
| 696 | "[EmulationFragment] Surface cleared while emulation stopped." | ||
| 697 | ) | ||
| 582 | } | 698 | } |
| 583 | } | 699 | } |
| 584 | } | 700 | } |
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 536163eb6..6f8adbba5 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 | |||
| @@ -103,7 +103,9 @@ class HomeSettingsFragment : Fragment() { | |||
| 103 | R.string.select_games_folder, | 103 | R.string.select_games_folder, |
| 104 | R.string.select_games_folder_description, | 104 | R.string.select_games_folder_description, |
| 105 | R.drawable.ic_add | 105 | R.drawable.ic_add |
| 106 | ) { mainActivity.getGamesDirectory.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data) }, | 106 | ) { |
| 107 | mainActivity.getGamesDirectory.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data) | ||
| 108 | }, | ||
| 107 | HomeSetting( | 109 | HomeSetting( |
| 108 | R.string.manage_save_data, | 110 | R.string.manage_save_data, |
| 109 | R.string.import_export_saves_description, | 111 | R.string.import_export_saves_description, |
| @@ -225,7 +227,11 @@ class HomeSettingsFragment : Fragment() { | |||
| 225 | val intent = Intent(action) | 227 | val intent = Intent(action) |
| 226 | intent.addCategory(Intent.CATEGORY_DEFAULT) | 228 | intent.addCategory(Intent.CATEGORY_DEFAULT) |
| 227 | intent.data = DocumentsContract.buildRootUri(authority, DocumentProvider.ROOT_ID) | 229 | intent.data = DocumentsContract.buildRootUri(authority, DocumentProvider.ROOT_ID) |
| 228 | intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION or Intent.FLAG_GRANT_PREFIX_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION) | 230 | intent.addFlags( |
| 231 | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION or | ||
| 232 | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION or | ||
| 233 | Intent.FLAG_GRANT_WRITE_URI_PERMISSION | ||
| 234 | ) | ||
| 229 | return intent | 235 | return intent |
| 230 | } | 236 | } |
| 231 | 237 | ||
| @@ -307,7 +313,9 @@ class HomeSettingsFragment : Fragment() { | |||
| 307 | } | 313 | } |
| 308 | 314 | ||
| 309 | private fun setInsets() = | 315 | private fun setInsets() = |
| 310 | ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view: View, windowInsets: WindowInsetsCompat -> | 316 | ViewCompat.setOnApplyWindowInsetsListener( |
| 317 | binding.root | ||
| 318 | ) { view: View, windowInsets: WindowInsetsCompat -> | ||
| 311 | val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) | 319 | val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) |
| 312 | val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) | 320 | val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) |
| 313 | val spacingNavigation = resources.getDimensionPixelSize(R.dimen.spacing_navigation) | 321 | val spacingNavigation = resources.getDimensionPixelSize(R.dimen.spacing_navigation) |
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 36e63bb9e..e1495ee8c 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 | |||
| @@ -15,6 +15,14 @@ import androidx.appcompat.app.AppCompatActivity | |||
| 15 | import androidx.documentfile.provider.DocumentFile | 15 | import androidx.documentfile.provider.DocumentFile |
| 16 | import androidx.fragment.app.DialogFragment | 16 | import androidx.fragment.app.DialogFragment |
| 17 | import com.google.android.material.dialog.MaterialAlertDialogBuilder | 17 | import com.google.android.material.dialog.MaterialAlertDialogBuilder |
| 18 | import java.io.BufferedOutputStream | ||
| 19 | import java.io.File | ||
| 20 | import java.io.FileOutputStream | ||
| 21 | import java.io.FilenameFilter | ||
| 22 | import java.time.LocalDateTime | ||
| 23 | import java.time.format.DateTimeFormatter | ||
| 24 | import java.util.zip.ZipEntry | ||
| 25 | import java.util.zip.ZipOutputStream | ||
| 18 | import kotlinx.coroutines.CoroutineScope | 26 | import kotlinx.coroutines.CoroutineScope |
| 19 | import kotlinx.coroutines.Dispatchers | 27 | import kotlinx.coroutines.Dispatchers |
| 20 | import kotlinx.coroutines.launch | 28 | import kotlinx.coroutines.launch |
| @@ -24,14 +32,6 @@ import org.yuzu.yuzu_emu.YuzuApplication | |||
| 24 | import org.yuzu.yuzu_emu.features.DocumentProvider | 32 | import org.yuzu.yuzu_emu.features.DocumentProvider |
| 25 | import org.yuzu.yuzu_emu.getPublicFilesDir | 33 | import org.yuzu.yuzu_emu.getPublicFilesDir |
| 26 | import org.yuzu.yuzu_emu.utils.FileUtil | 34 | import org.yuzu.yuzu_emu.utils.FileUtil |
| 27 | import java.io.BufferedOutputStream | ||
| 28 | import java.io.File | ||
| 29 | import java.io.FileOutputStream | ||
| 30 | import java.io.FilenameFilter | ||
| 31 | import java.time.LocalDateTime | ||
| 32 | import java.time.format.DateTimeFormatter | ||
| 33 | import java.util.zip.ZipEntry | ||
| 34 | import java.util.zip.ZipOutputStream | ||
| 35 | 35 | ||
| 36 | class ImportExportSavesFragment : DialogFragment() { | 36 | class ImportExportSavesFragment : DialogFragment() { |
| 37 | private val context = YuzuApplication.appContext | 37 | private val context = YuzuApplication.appContext |
| @@ -98,7 +98,7 @@ class ImportExportSavesFragment : DialogFragment() { | |||
| 98 | val outputZipFile = File( | 98 | val outputZipFile = File( |
| 99 | tempFolder, | 99 | tempFolder, |
| 100 | "yuzu saves - ${ | 100 | "yuzu saves - ${ |
| 101 | LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")) | 101 | LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")) |
| 102 | }.zip" | 102 | }.zip" |
| 103 | ) | 103 | ) |
| 104 | outputZipFile.createNewFile() | 104 | outputZipFile.createNewFile() |
| @@ -106,12 +106,14 @@ class ImportExportSavesFragment : DialogFragment() { | |||
| 106 | saveFolder.walkTopDown().forEach { file -> | 106 | saveFolder.walkTopDown().forEach { file -> |
| 107 | val zipFileName = | 107 | val zipFileName = |
| 108 | file.absolutePath.removePrefix(savesFolderRoot).removePrefix("/") | 108 | file.absolutePath.removePrefix(savesFolderRoot).removePrefix("/") |
| 109 | if (zipFileName == "") | 109 | if (zipFileName == "") { |
| 110 | return@forEach | 110 | return@forEach |
| 111 | } | ||
| 111 | val entry = ZipEntry("$zipFileName${(if (file.isDirectory) "/" else "")}") | 112 | val entry = ZipEntry("$zipFileName${(if (file.isDirectory) "/" else "")}") |
| 112 | zos.putNextEntry(entry) | 113 | zos.putNextEntry(entry) |
| 113 | if (file.isFile) | 114 | if (file.isFile) { |
| 114 | file.inputStream().use { fis -> fis.copyTo(zos) } | 115 | file.inputStream().use { fis -> fis.copyTo(zos) } |
| 116 | } | ||
| 115 | } | 117 | } |
| 116 | } | 118 | } |
| 117 | lastZipCreated = outputZipFile | 119 | lastZipCreated = outputZipFile |
| @@ -137,7 +139,8 @@ class ImportExportSavesFragment : DialogFragment() { | |||
| 137 | 139 | ||
| 138 | withContext(Dispatchers.Main) { | 140 | withContext(Dispatchers.Main) { |
| 139 | val file = DocumentFile.fromSingleUri( | 141 | val file = DocumentFile.fromSingleUri( |
| 140 | context, DocumentsContract.buildDocumentUri( | 142 | context, |
| 143 | DocumentsContract.buildDocumentUri( | ||
| 141 | DocumentProvider.AUTHORITY, | 144 | DocumentProvider.AUTHORITY, |
| 142 | "${DocumentProvider.ROOT_ID}/temp/${lastZipFile.name}" | 145 | "${DocumentProvider.ROOT_ID}/temp/${lastZipFile.name}" |
| 143 | ) | 146 | ) |
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 c7880d8cc..739b26f99 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 | |||
| @@ -14,7 +14,6 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder | |||
| 14 | import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding | 14 | import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding |
| 15 | import org.yuzu.yuzu_emu.model.TaskViewModel | 15 | import org.yuzu.yuzu_emu.model.TaskViewModel |
| 16 | 16 | ||
| 17 | |||
| 18 | class IndeterminateProgressDialogFragment : DialogFragment() { | 17 | class IndeterminateProgressDialogFragment : DialogFragment() { |
| 19 | private val taskViewModel: TaskViewModel by activityViewModels() | 18 | private val taskViewModel: TaskViewModel by activityViewModels() |
| 20 | 19 | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LicensesFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LicensesFragment.kt index 59141e823..b6e9129f7 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LicensesFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LicensesFragment.kt | |||
| @@ -113,7 +113,9 @@ class LicensesFragment : Fragment() { | |||
| 113 | } | 113 | } |
| 114 | 114 | ||
| 115 | private fun setInsets() = | 115 | private fun setInsets() = |
| 116 | ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _: View, windowInsets: WindowInsetsCompat -> | 116 | ViewCompat.setOnApplyWindowInsetsListener( |
| 117 | binding.root | ||
| 118 | ) { _: View, windowInsets: WindowInsetsCompat -> | ||
| 117 | val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) | 119 | val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) |
| 118 | val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) | 120 | val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) |
| 119 | 121 | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt index adbe3696b..dd6c895fd 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt | |||
| @@ -20,6 +20,7 @@ import androidx.fragment.app.activityViewModels | |||
| 20 | import androidx.preference.PreferenceManager | 20 | import androidx.preference.PreferenceManager |
| 21 | import info.debatty.java.stringsimilarity.Jaccard | 21 | import info.debatty.java.stringsimilarity.Jaccard |
| 22 | import info.debatty.java.stringsimilarity.JaroWinkler | 22 | import info.debatty.java.stringsimilarity.JaroWinkler |
| 23 | import java.util.Locale | ||
| 23 | import org.yuzu.yuzu_emu.R | 24 | import org.yuzu.yuzu_emu.R |
| 24 | import org.yuzu.yuzu_emu.YuzuApplication | 25 | import org.yuzu.yuzu_emu.YuzuApplication |
| 25 | import org.yuzu.yuzu_emu.adapters.GameAdapter | 26 | import org.yuzu.yuzu_emu.adapters.GameAdapter |
| @@ -29,8 +30,6 @@ import org.yuzu.yuzu_emu.model.Game | |||
| 29 | import org.yuzu.yuzu_emu.model.GamesViewModel | 30 | import org.yuzu.yuzu_emu.model.GamesViewModel |
| 30 | import org.yuzu.yuzu_emu.model.HomeViewModel | 31 | import org.yuzu.yuzu_emu.model.HomeViewModel |
| 31 | import org.yuzu.yuzu_emu.utils.FileUtil | 32 | import org.yuzu.yuzu_emu.utils.FileUtil |
| 32 | import org.yuzu.yuzu_emu.utils.Log | ||
| 33 | import java.util.Locale | ||
| 34 | 33 | ||
| 35 | class SearchFragment : Fragment() { | 34 | class SearchFragment : Fragment() { |
| 36 | private var _binding: FragmentSearchBinding? = null | 35 | private var _binding: FragmentSearchBinding? = null |
| @@ -130,15 +129,15 @@ class SearchFragment : Fragment() { | |||
| 130 | R.id.chip_homebrew -> baseList.filter { it.isHomebrew } | 129 | R.id.chip_homebrew -> baseList.filter { it.isHomebrew } |
| 131 | 130 | ||
| 132 | R.id.chip_retail -> baseList.filter { | 131 | R.id.chip_retail -> baseList.filter { |
| 133 | FileUtil.hasExtension(it.path, "xci") | 132 | FileUtil.hasExtension(it.path, "xci") || |
| 134 | || FileUtil.hasExtension(it.path, "nsp") | 133 | FileUtil.hasExtension(it.path, "nsp") |
| 135 | } | 134 | } |
| 136 | 135 | ||
| 137 | else -> baseList | 136 | else -> baseList |
| 138 | } | 137 | } |
| 139 | 138 | ||
| 140 | if (binding.searchText.text.toString().isEmpty() | 139 | if (binding.searchText.text.toString().isEmpty() && |
| 141 | && binding.chipGroup.checkedChipId != View.NO_ID | 140 | binding.chipGroup.checkedChipId != View.NO_ID |
| 142 | ) { | 141 | ) { |
| 143 | gamesViewModel.setSearchedGames(filteredList) | 142 | gamesViewModel.setSearchedGames(filteredList) |
| 144 | return | 143 | return |
| @@ -173,14 +172,16 @@ class SearchFragment : Fragment() { | |||
| 173 | private fun focusSearch() { | 172 | private fun focusSearch() { |
| 174 | if (_binding != null) { | 173 | if (_binding != null) { |
| 175 | binding.searchText.requestFocus() | 174 | binding.searchText.requestFocus() |
| 176 | val imm = | 175 | val imm = requireActivity() |
| 177 | requireActivity().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager? | 176 | .getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager? |
| 178 | imm?.showSoftInput(binding.searchText, InputMethodManager.SHOW_IMPLICIT) | 177 | imm?.showSoftInput(binding.searchText, InputMethodManager.SHOW_IMPLICIT) |
| 179 | } | 178 | } |
| 180 | } | 179 | } |
| 181 | 180 | ||
| 182 | private fun setInsets() = | 181 | private fun setInsets() = |
| 183 | ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view: View, windowInsets: WindowInsetsCompat -> | 182 | ViewCompat.setOnApplyWindowInsetsListener( |
| 183 | binding.root | ||
| 184 | ) { view: View, windowInsets: WindowInsetsCompat -> | ||
| 184 | val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) | 185 | val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) |
| 185 | val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) | 186 | val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) |
| 186 | val extraListSpacing = resources.getDimensionPixelSize(R.dimen.spacing_med) | 187 | val extraListSpacing = resources.getDimensionPixelSize(R.dimen.spacing_med) |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt index 258773380..6c4ddaf6b 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt | |||
| @@ -25,6 +25,7 @@ import androidx.navigation.findNavController | |||
| 25 | import androidx.preference.PreferenceManager | 25 | import androidx.preference.PreferenceManager |
| 26 | import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback | 26 | import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback |
| 27 | import com.google.android.material.transition.MaterialFadeThrough | 27 | import com.google.android.material.transition.MaterialFadeThrough |
| 28 | import java.io.File | ||
| 28 | import org.yuzu.yuzu_emu.R | 29 | import org.yuzu.yuzu_emu.R |
| 29 | import org.yuzu.yuzu_emu.YuzuApplication | 30 | import org.yuzu.yuzu_emu.YuzuApplication |
| 30 | import org.yuzu.yuzu_emu.adapters.SetupAdapter | 31 | import org.yuzu.yuzu_emu.adapters.SetupAdapter |
| @@ -35,7 +36,6 @@ import org.yuzu.yuzu_emu.model.SetupPage | |||
| 35 | import org.yuzu.yuzu_emu.ui.main.MainActivity | 36 | import org.yuzu.yuzu_emu.ui.main.MainActivity |
| 36 | import org.yuzu.yuzu_emu.utils.DirectoryInitialization | 37 | import org.yuzu.yuzu_emu.utils.DirectoryInitialization |
| 37 | import org.yuzu.yuzu_emu.utils.GameHelper | 38 | import org.yuzu.yuzu_emu.utils.GameHelper |
| 38 | import java.io.File | ||
| 39 | 39 | ||
| 40 | class SetupFragment : Fragment() { | 40 | class SetupFragment : Fragment() { |
| 41 | private var _binding: FragmentSetupBinding? = null | 41 | private var _binding: FragmentSetupBinding? = null |
| @@ -82,7 +82,8 @@ class SetupFragment : Fragment() { | |||
| 82 | requireActivity().finish() | 82 | requireActivity().finish() |
| 83 | } | 83 | } |
| 84 | } | 84 | } |
| 85 | }) | 85 | } |
| 86 | ) | ||
| 86 | 87 | ||
| 87 | requireActivity().window.navigationBarColor = | 88 | requireActivity().window.navigationBarColor = |
| 88 | ContextCompat.getColor(requireContext(), android.R.color.transparent) | 89 | ContextCompat.getColor(requireContext(), android.R.color.transparent) |
| @@ -148,14 +149,20 @@ class SetupFragment : Fragment() { | |||
| 148 | R.drawable.ic_add, | 149 | R.drawable.ic_add, |
| 149 | true, | 150 | true, |
| 150 | R.string.add_games, | 151 | R.string.add_games, |
| 151 | { mainActivity.getGamesDirectory.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data) }, | 152 | { |
| 153 | mainActivity.getGamesDirectory.launch( | ||
| 154 | Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data | ||
| 155 | ) | ||
| 156 | }, | ||
| 152 | true, | 157 | true, |
| 153 | R.string.add_games_warning, | 158 | R.string.add_games_warning, |
| 154 | R.string.add_games_warning_description, | 159 | R.string.add_games_warning_description, |
| 155 | R.string.add_games_warning_help, | 160 | R.string.add_games_warning_help, |
| 156 | { | 161 | { |
| 157 | val preferences = | 162 | val preferences = |
| 158 | PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) | 163 | PreferenceManager.getDefaultSharedPreferences( |
| 164 | YuzuApplication.appContext | ||
| 165 | ) | ||
| 159 | preferences.getString(GameHelper.KEY_GAME_PATH, "")!!.isNotEmpty() | 166 | preferences.getString(GameHelper.KEY_GAME_PATH, "")!!.isNotEmpty() |
| 160 | } | 167 | } |
| 161 | ) | 168 | ) |
| @@ -260,7 +267,9 @@ class SetupFragment : Fragment() { | |||
| 260 | @RequiresApi(Build.VERSION_CODES.TIRAMISU) | 267 | @RequiresApi(Build.VERSION_CODES.TIRAMISU) |
| 261 | private val permissionLauncher = | 268 | private val permissionLauncher = |
| 262 | registerForActivityResult(ActivityResultContracts.RequestPermission()) { | 269 | registerForActivityResult(ActivityResultContracts.RequestPermission()) { |
| 263 | if (!it && !shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS)) { | 270 | if (!it && |
| 271 | !shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS) | ||
| 272 | ) { | ||
| 264 | PermissionDeniedDialogFragment().show( | 273 | PermissionDeniedDialogFragment().show( |
| 265 | childFragmentManager, | 274 | childFragmentManager, |
| 266 | PermissionDeniedDialogFragment.TAG | 275 | PermissionDeniedDialogFragment.TAG |
| @@ -315,7 +324,9 @@ class SetupFragment : Fragment() { | |||
| 315 | } | 324 | } |
| 316 | 325 | ||
| 317 | private fun setInsets() = | 326 | private fun setInsets() = |
| 318 | ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view: View, windowInsets: WindowInsetsCompat -> | 327 | ViewCompat.setOnApplyWindowInsetsListener( |
| 328 | binding.root | ||
| 329 | ) { view: View, windowInsets: WindowInsetsCompat -> | ||
| 319 | val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) | 330 | val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) |
| 320 | val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) | 331 | val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) |
| 321 | view.setPadding( | 332 | view.setPadding( |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/layout/AutofitGridLayoutManager.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/layout/AutofitGridLayoutManager.kt index be5e4c86c..bdd6ea628 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/layout/AutofitGridLayoutManager.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/layout/AutofitGridLayoutManager.kt | |||
| @@ -44,7 +44,9 @@ class AutofitGridLayoutManager( | |||
| 44 | override fun onLayoutChildren(recycler: Recycler, state: RecyclerView.State) { | 44 | override fun onLayoutChildren(recycler: Recycler, state: RecyclerView.State) { |
| 45 | val width = width | 45 | val width = width |
| 46 | val height = height | 46 | val height = height |
| 47 | if (columnWidth > 0 && width > 0 && height > 0 && (isColumnWidthChanged || lastWidth != width || lastHeight != height)) { | 47 | if (columnWidth > 0 && width > 0 && height > 0 && |
| 48 | (isColumnWidthChanged || lastWidth != width || lastHeight != height) | ||
| 49 | ) { | ||
| 48 | val totalSpace: Int = if (orientation == VERTICAL) { | 50 | val totalSpace: Int = if (orientation == VERTICAL) { |
| 49 | width - paddingRight - paddingLeft | 51 | width - paddingRight - paddingLeft |
| 50 | } else { | 52 | } else { |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt index 35d8000c5..6a048e39f 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt | |||
| @@ -4,9 +4,9 @@ | |||
| 4 | package org.yuzu.yuzu_emu.model | 4 | package org.yuzu.yuzu_emu.model |
| 5 | 5 | ||
| 6 | import android.os.Parcelable | 6 | import android.os.Parcelable |
| 7 | import java.util.HashSet | ||
| 7 | import kotlinx.parcelize.Parcelize | 8 | import kotlinx.parcelize.Parcelize |
| 8 | import kotlinx.serialization.Serializable | 9 | import kotlinx.serialization.Serializable |
| 9 | import java.util.HashSet | ||
| 10 | 10 | ||
| 11 | @Parcelize | 11 | @Parcelize |
| 12 | @Serializable | 12 | @Serializable |
| @@ -23,8 +23,9 @@ class Game( | |||
| 23 | val keyLastPlayedTime get() = "${gameId}_LastPlayed" | 23 | val keyLastPlayedTime get() = "${gameId}_LastPlayed" |
| 24 | 24 | ||
| 25 | override fun equals(other: Any?): Boolean { | 25 | override fun equals(other: Any?): Boolean { |
| 26 | if (other !is Game) | 26 | if (other !is Game) { |
| 27 | return false | 27 | return false |
| 28 | } | ||
| 28 | 29 | ||
| 29 | return hashCode() == other.hashCode() | 30 | return hashCode() == other.hashCode() |
| 30 | } | 31 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt index d9b301210..1fe42f922 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt | |||
| @@ -10,6 +10,7 @@ import androidx.lifecycle.MutableLiveData | |||
| 10 | import androidx.lifecycle.ViewModel | 10 | import androidx.lifecycle.ViewModel |
| 11 | import androidx.lifecycle.viewModelScope | 11 | import androidx.lifecycle.viewModelScope |
| 12 | import androidx.preference.PreferenceManager | 12 | import androidx.preference.PreferenceManager |
| 13 | import java.util.Locale | ||
| 13 | import kotlinx.coroutines.Dispatchers | 14 | import kotlinx.coroutines.Dispatchers |
| 14 | import kotlinx.coroutines.launch | 15 | import kotlinx.coroutines.launch |
| 15 | import kotlinx.coroutines.withContext | 16 | import kotlinx.coroutines.withContext |
| @@ -20,7 +21,6 @@ import kotlinx.serialization.json.Json | |||
| 20 | import org.yuzu.yuzu_emu.NativeLibrary | 21 | import org.yuzu.yuzu_emu.NativeLibrary |
| 21 | import org.yuzu.yuzu_emu.YuzuApplication | 22 | import org.yuzu.yuzu_emu.YuzuApplication |
| 22 | import org.yuzu.yuzu_emu.utils.GameHelper | 23 | import org.yuzu.yuzu_emu.utils.GameHelper |
| 23 | import java.util.Locale | ||
| 24 | 24 | ||
| 25 | @OptIn(ExperimentalSerializationApi::class) | 25 | @OptIn(ExperimentalSerializationApi::class) |
| 26 | class GamesViewModel : ViewModel() { | 26 | class GamesViewModel : ViewModel() { |
| @@ -99,8 +99,9 @@ class GamesViewModel : ViewModel() { | |||
| 99 | } | 99 | } |
| 100 | 100 | ||
| 101 | fun reloadGames(directoryChanged: Boolean) { | 101 | fun reloadGames(directoryChanged: Boolean) { |
| 102 | if (isReloading.value == true) | 102 | if (isReloading.value == true) { |
| 103 | return | 103 | return |
| 104 | } | ||
| 104 | _isReloading.postValue(true) | 105 | _isReloading.postValue(true) |
| 105 | 106 | ||
| 106 | viewModelScope.launch { | 107 | viewModelScope.launch { |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt index aa424c768..6251ec783 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt | |||
| @@ -6,7 +6,6 @@ package org.yuzu.yuzu_emu.overlay | |||
| 6 | import android.app.Activity | 6 | import android.app.Activity |
| 7 | import android.content.Context | 7 | import android.content.Context |
| 8 | import android.content.SharedPreferences | 8 | import android.content.SharedPreferences |
| 9 | import android.content.res.Configuration | ||
| 10 | import android.graphics.Bitmap | 9 | import android.graphics.Bitmap |
| 11 | import android.graphics.Canvas | 10 | import android.graphics.Canvas |
| 12 | import android.graphics.Point | 11 | import android.graphics.Point |
| @@ -24,6 +23,8 @@ import android.view.WindowInsets | |||
| 24 | import androidx.core.content.ContextCompat | 23 | import androidx.core.content.ContextCompat |
| 25 | import androidx.preference.PreferenceManager | 24 | import androidx.preference.PreferenceManager |
| 26 | import androidx.window.layout.WindowMetricsCalculator | 25 | import androidx.window.layout.WindowMetricsCalculator |
| 26 | import kotlin.math.max | ||
| 27 | import kotlin.math.min | ||
| 27 | import org.yuzu.yuzu_emu.NativeLibrary | 28 | import org.yuzu.yuzu_emu.NativeLibrary |
| 28 | import org.yuzu.yuzu_emu.NativeLibrary.ButtonType | 29 | import org.yuzu.yuzu_emu.NativeLibrary.ButtonType |
| 29 | import org.yuzu.yuzu_emu.NativeLibrary.StickType | 30 | import org.yuzu.yuzu_emu.NativeLibrary.StickType |
| @@ -31,14 +32,13 @@ import org.yuzu.yuzu_emu.R | |||
| 31 | import org.yuzu.yuzu_emu.YuzuApplication | 32 | import org.yuzu.yuzu_emu.YuzuApplication |
| 32 | import org.yuzu.yuzu_emu.features.settings.model.Settings | 33 | import org.yuzu.yuzu_emu.features.settings.model.Settings |
| 33 | import org.yuzu.yuzu_emu.utils.EmulationMenuSettings | 34 | import org.yuzu.yuzu_emu.utils.EmulationMenuSettings |
| 34 | import kotlin.math.max | ||
| 35 | import kotlin.math.min | ||
| 36 | 35 | ||
| 37 | /** | 36 | /** |
| 38 | * Draws the interactive input overlay on top of the | 37 | * Draws the interactive input overlay on top of the |
| 39 | * [SurfaceView] that is rendering emulation. | 38 | * [SurfaceView] that is rendering emulation. |
| 40 | */ | 39 | */ |
| 41 | class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context, attrs), | 40 | class InputOverlay(context: Context, attrs: AttributeSet?) : |
| 41 | SurfaceView(context, attrs), | ||
| 42 | OnTouchListener { | 42 | OnTouchListener { |
| 43 | private val overlayButtons: MutableSet<InputOverlayDrawableButton> = HashSet() | 43 | private val overlayButtons: MutableSet<InputOverlayDrawableButton> = HashSet() |
| 44 | private val overlayDpads: MutableSet<InputOverlayDrawableDpad> = HashSet() | 44 | private val overlayDpads: MutableSet<InputOverlayDrawableDpad> = HashSet() |
| @@ -51,12 +51,14 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context | |||
| 51 | 51 | ||
| 52 | private lateinit var windowInsets: WindowInsets | 52 | private lateinit var windowInsets: WindowInsets |
| 53 | 53 | ||
| 54 | var orientation = LANDSCAPE | ||
| 55 | |||
| 54 | override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { | 56 | override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { |
| 55 | super.onLayout(changed, left, top, right, bottom) | 57 | super.onLayout(changed, left, top, right, bottom) |
| 56 | 58 | ||
| 57 | windowInsets = rootWindowInsets | 59 | windowInsets = rootWindowInsets |
| 58 | 60 | ||
| 59 | if (!preferences.getBoolean(Settings.PREF_OVERLAY_INIT, false)) { | 61 | if (!preferences.getBoolean("${Settings.PREF_OVERLAY_INIT}$orientation", false)) { |
| 60 | defaultOverlay() | 62 | defaultOverlay() |
| 61 | } | 63 | } |
| 62 | 64 | ||
| @@ -93,7 +95,11 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context | |||
| 93 | 95 | ||
| 94 | var shouldUpdateView = false | 96 | var shouldUpdateView = false |
| 95 | val playerIndex = | 97 | val playerIndex = |
| 96 | if (NativeLibrary.isHandheldOnly()) NativeLibrary.ConsoleDevice else NativeLibrary.Player1Device | 98 | if (NativeLibrary.isHandheldOnly()) { |
| 99 | NativeLibrary.ConsoleDevice | ||
| 100 | } else { | ||
| 101 | NativeLibrary.Player1Device | ||
| 102 | } | ||
| 97 | 103 | ||
| 98 | for (button in overlayButtons) { | 104 | for (button in overlayButtons) { |
| 99 | if (!button.updateStatus(event)) { | 105 | if (!button.updateStatus(event)) { |
| @@ -156,8 +162,9 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context | |||
| 156 | shouldUpdateView = true | 162 | shouldUpdateView = true |
| 157 | } | 163 | } |
| 158 | 164 | ||
| 159 | if (shouldUpdateView) | 165 | if (shouldUpdateView) { |
| 160 | invalidate() | 166 | invalidate() |
| 167 | } | ||
| 161 | 168 | ||
| 162 | if (!preferences.getBoolean(Settings.PREF_TOUCH_ENABLED, true)) { | 169 | if (!preferences.getBoolean(Settings.PREF_TOUCH_ENABLED, true)) { |
| 163 | return true | 170 | return true |
| @@ -233,10 +240,6 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context | |||
| 233 | val fingerPositionX = event.getX(pointerIndex).toInt() | 240 | val fingerPositionX = event.getX(pointerIndex).toInt() |
| 234 | val fingerPositionY = event.getY(pointerIndex).toInt() | 241 | val fingerPositionY = event.getY(pointerIndex).toInt() |
| 235 | 242 | ||
| 236 | // TODO: Provide support for portrait layout | ||
| 237 | //val orientation = | ||
| 238 | // if (resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) "-Portrait" else "" | ||
| 239 | |||
| 240 | for (button in overlayButtons) { | 243 | for (button in overlayButtons) { |
| 241 | // Determine the button state to apply based on the MotionEvent action flag. | 244 | // Determine the button state to apply based on the MotionEvent action flag. |
| 242 | when (event.action and MotionEvent.ACTION_MASK) { | 245 | when (event.action and MotionEvent.ACTION_MASK) { |
| @@ -245,9 +248,9 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context | |||
| 245 | // If no button is being moved now, remember the currently touched button to move. | 248 | // If no button is being moved now, remember the currently touched button to move. |
| 246 | if (buttonBeingConfigured == null && | 249 | if (buttonBeingConfigured == null && |
| 247 | button.bounds.contains( | 250 | button.bounds.contains( |
| 248 | fingerPositionX, | 251 | fingerPositionX, |
| 249 | fingerPositionY | 252 | fingerPositionY |
| 250 | ) | 253 | ) |
| 251 | ) { | 254 | ) { |
| 252 | buttonBeingConfigured = button | 255 | buttonBeingConfigured = button |
| 253 | buttonBeingConfigured!!.onConfigureTouch(event) | 256 | buttonBeingConfigured!!.onConfigureTouch(event) |
| @@ -266,7 +269,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context | |||
| 266 | buttonBeingConfigured!!.buttonId, | 269 | buttonBeingConfigured!!.buttonId, |
| 267 | buttonBeingConfigured!!.bounds.centerX(), | 270 | buttonBeingConfigured!!.bounds.centerX(), |
| 268 | buttonBeingConfigured!!.bounds.centerY(), | 271 | buttonBeingConfigured!!.bounds.centerY(), |
| 269 | "" | 272 | orientation |
| 270 | ) | 273 | ) |
| 271 | buttonBeingConfigured = null | 274 | buttonBeingConfigured = null |
| 272 | } | 275 | } |
| @@ -299,7 +302,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context | |||
| 299 | dpadBeingConfigured!!.upId, | 302 | dpadBeingConfigured!!.upId, |
| 300 | dpadBeingConfigured!!.bounds.centerX(), | 303 | dpadBeingConfigured!!.bounds.centerX(), |
| 301 | dpadBeingConfigured!!.bounds.centerY(), | 304 | dpadBeingConfigured!!.bounds.centerY(), |
| 302 | "" | 305 | orientation |
| 303 | ) | 306 | ) |
| 304 | dpadBeingConfigured = null | 307 | dpadBeingConfigured = null |
| 305 | } | 308 | } |
| @@ -311,9 +314,9 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context | |||
| 311 | MotionEvent.ACTION_DOWN, | 314 | MotionEvent.ACTION_DOWN, |
| 312 | MotionEvent.ACTION_POINTER_DOWN -> if (joystickBeingConfigured == null && | 315 | MotionEvent.ACTION_POINTER_DOWN -> if (joystickBeingConfigured == null && |
| 313 | joystick.bounds.contains( | 316 | joystick.bounds.contains( |
| 314 | fingerPositionX, | 317 | fingerPositionX, |
| 315 | fingerPositionY | 318 | fingerPositionY |
| 316 | ) | 319 | ) |
| 317 | ) { | 320 | ) { |
| 318 | joystickBeingConfigured = joystick | 321 | joystickBeingConfigured = joystick |
| 319 | joystickBeingConfigured!!.onConfigureTouch(event) | 322 | joystickBeingConfigured!!.onConfigureTouch(event) |
| @@ -330,7 +333,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context | |||
| 330 | joystickBeingConfigured!!.buttonId, | 333 | joystickBeingConfigured!!.buttonId, |
| 331 | joystickBeingConfigured!!.bounds.centerX(), | 334 | joystickBeingConfigured!!.bounds.centerX(), |
| 332 | joystickBeingConfigured!!.bounds.centerY(), | 335 | joystickBeingConfigured!!.bounds.centerY(), |
| 333 | "" | 336 | orientation |
| 334 | ) | 337 | ) |
| 335 | joystickBeingConfigured = null | 338 | joystickBeingConfigured = null |
| 336 | } | 339 | } |
| @@ -533,8 +536,6 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context | |||
| 533 | overlayButtons.clear() | 536 | overlayButtons.clear() |
| 534 | overlayDpads.clear() | 537 | overlayDpads.clear() |
| 535 | overlayJoysticks.clear() | 538 | overlayJoysticks.clear() |
| 536 | val orientation = | ||
| 537 | if (resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) "-Portrait" else "" | ||
| 538 | 539 | ||
| 539 | // Add all the enabled overlay items back to the HashSet. | 540 | // Add all the enabled overlay items back to the HashSet. |
| 540 | if (EmulationMenuSettings.showOverlay) { | 541 | if (EmulationMenuSettings.showOverlay) { |
| @@ -548,8 +549,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context | |||
| 548 | val min = windowSize.first | 549 | val min = windowSize.first |
| 549 | val max = windowSize.second | 550 | val max = windowSize.second |
| 550 | PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext).edit() | 551 | PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext).edit() |
| 551 | .putFloat("$sharedPrefsId$orientation-X", (x - min.x).toFloat() / max.x) | 552 | .putFloat("$sharedPrefsId-X$orientation", (x - min.x).toFloat() / max.x) |
| 552 | .putFloat("$sharedPrefsId$orientation-Y", (y - min.y).toFloat() / max.y) | 553 | .putFloat("$sharedPrefsId-Y$orientation", (y - min.y).toFloat() / max.y) |
| 553 | .apply() | 554 | .apply() |
| 554 | } | 555 | } |
| 555 | 556 | ||
| @@ -558,145 +559,250 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context | |||
| 558 | } | 559 | } |
| 559 | 560 | ||
| 560 | private fun defaultOverlay() { | 561 | private fun defaultOverlay() { |
| 561 | if (!preferences.getBoolean(Settings.PREF_OVERLAY_INIT, false)) { | 562 | if (!preferences.getBoolean("${Settings.PREF_OVERLAY_INIT}$orientation", false)) { |
| 562 | defaultOverlayLandscape() | 563 | defaultOverlayByLayout(orientation) |
| 563 | } | 564 | } |
| 564 | 565 | ||
| 565 | resetButtonPlacement() | 566 | resetButtonPlacement() |
| 566 | preferences.edit() | 567 | preferences.edit() |
| 567 | .putBoolean(Settings.PREF_OVERLAY_INIT, true) | 568 | .putBoolean("${Settings.PREF_OVERLAY_INIT}$orientation", true) |
| 568 | .apply() | 569 | .apply() |
| 569 | } | 570 | } |
| 570 | 571 | ||
| 571 | fun resetButtonPlacement() { | 572 | fun resetButtonPlacement() { |
| 572 | defaultOverlayLandscape() | 573 | defaultOverlayByLayout(orientation) |
| 573 | refreshControls() | 574 | refreshControls() |
| 574 | } | 575 | } |
| 575 | 576 | ||
| 576 | private fun defaultOverlayLandscape() { | 577 | private val landscapeResources = arrayOf( |
| 578 | R.integer.SWITCH_BUTTON_A_X, | ||
| 579 | R.integer.SWITCH_BUTTON_A_Y, | ||
| 580 | R.integer.SWITCH_BUTTON_B_X, | ||
| 581 | R.integer.SWITCH_BUTTON_B_Y, | ||
| 582 | R.integer.SWITCH_BUTTON_X_X, | ||
| 583 | R.integer.SWITCH_BUTTON_X_Y, | ||
| 584 | R.integer.SWITCH_BUTTON_Y_X, | ||
| 585 | R.integer.SWITCH_BUTTON_Y_Y, | ||
| 586 | R.integer.SWITCH_TRIGGER_ZL_X, | ||
| 587 | R.integer.SWITCH_TRIGGER_ZL_Y, | ||
| 588 | R.integer.SWITCH_TRIGGER_ZR_X, | ||
| 589 | R.integer.SWITCH_TRIGGER_ZR_Y, | ||
| 590 | R.integer.SWITCH_BUTTON_DPAD_X, | ||
| 591 | R.integer.SWITCH_BUTTON_DPAD_Y, | ||
| 592 | R.integer.SWITCH_TRIGGER_L_X, | ||
| 593 | R.integer.SWITCH_TRIGGER_L_Y, | ||
| 594 | R.integer.SWITCH_TRIGGER_R_X, | ||
| 595 | R.integer.SWITCH_TRIGGER_R_Y, | ||
| 596 | R.integer.SWITCH_BUTTON_PLUS_X, | ||
| 597 | R.integer.SWITCH_BUTTON_PLUS_Y, | ||
| 598 | R.integer.SWITCH_BUTTON_MINUS_X, | ||
| 599 | R.integer.SWITCH_BUTTON_MINUS_Y, | ||
| 600 | R.integer.SWITCH_BUTTON_HOME_X, | ||
| 601 | R.integer.SWITCH_BUTTON_HOME_Y, | ||
| 602 | R.integer.SWITCH_BUTTON_CAPTURE_X, | ||
| 603 | R.integer.SWITCH_BUTTON_CAPTURE_Y, | ||
| 604 | R.integer.SWITCH_STICK_R_X, | ||
| 605 | R.integer.SWITCH_STICK_R_Y, | ||
| 606 | R.integer.SWITCH_STICK_L_X, | ||
| 607 | R.integer.SWITCH_STICK_L_Y | ||
| 608 | ) | ||
| 609 | |||
| 610 | private val portraitResources = arrayOf( | ||
| 611 | R.integer.SWITCH_BUTTON_A_X_PORTRAIT, | ||
| 612 | R.integer.SWITCH_BUTTON_A_Y_PORTRAIT, | ||
| 613 | R.integer.SWITCH_BUTTON_B_X_PORTRAIT, | ||
| 614 | R.integer.SWITCH_BUTTON_B_Y_PORTRAIT, | ||
| 615 | R.integer.SWITCH_BUTTON_X_X_PORTRAIT, | ||
| 616 | R.integer.SWITCH_BUTTON_X_Y_PORTRAIT, | ||
| 617 | R.integer.SWITCH_BUTTON_Y_X_PORTRAIT, | ||
| 618 | R.integer.SWITCH_BUTTON_Y_Y_PORTRAIT, | ||
| 619 | R.integer.SWITCH_TRIGGER_ZL_X_PORTRAIT, | ||
| 620 | R.integer.SWITCH_TRIGGER_ZL_Y_PORTRAIT, | ||
| 621 | R.integer.SWITCH_TRIGGER_ZR_X_PORTRAIT, | ||
| 622 | R.integer.SWITCH_TRIGGER_ZR_Y_PORTRAIT, | ||
| 623 | R.integer.SWITCH_BUTTON_DPAD_X_PORTRAIT, | ||
| 624 | R.integer.SWITCH_BUTTON_DPAD_Y_PORTRAIT, | ||
| 625 | R.integer.SWITCH_TRIGGER_L_X_PORTRAIT, | ||
| 626 | R.integer.SWITCH_TRIGGER_L_Y_PORTRAIT, | ||
| 627 | R.integer.SWITCH_TRIGGER_R_X_PORTRAIT, | ||
| 628 | R.integer.SWITCH_TRIGGER_R_Y_PORTRAIT, | ||
| 629 | R.integer.SWITCH_BUTTON_PLUS_X_PORTRAIT, | ||
| 630 | R.integer.SWITCH_BUTTON_PLUS_Y_PORTRAIT, | ||
| 631 | R.integer.SWITCH_BUTTON_MINUS_X_PORTRAIT, | ||
| 632 | R.integer.SWITCH_BUTTON_MINUS_Y_PORTRAIT, | ||
| 633 | R.integer.SWITCH_BUTTON_HOME_X_PORTRAIT, | ||
| 634 | R.integer.SWITCH_BUTTON_HOME_Y_PORTRAIT, | ||
| 635 | R.integer.SWITCH_BUTTON_CAPTURE_X_PORTRAIT, | ||
| 636 | R.integer.SWITCH_BUTTON_CAPTURE_Y_PORTRAIT, | ||
| 637 | R.integer.SWITCH_STICK_R_X_PORTRAIT, | ||
| 638 | R.integer.SWITCH_STICK_R_Y_PORTRAIT, | ||
| 639 | R.integer.SWITCH_STICK_L_X_PORTRAIT, | ||
| 640 | R.integer.SWITCH_STICK_L_Y_PORTRAIT | ||
| 641 | ) | ||
| 642 | |||
| 643 | private val foldableResources = arrayOf( | ||
| 644 | R.integer.SWITCH_BUTTON_A_X_FOLDABLE, | ||
| 645 | R.integer.SWITCH_BUTTON_A_Y_FOLDABLE, | ||
| 646 | R.integer.SWITCH_BUTTON_B_X_FOLDABLE, | ||
| 647 | R.integer.SWITCH_BUTTON_B_Y_FOLDABLE, | ||
| 648 | R.integer.SWITCH_BUTTON_X_X_FOLDABLE, | ||
| 649 | R.integer.SWITCH_BUTTON_X_Y_FOLDABLE, | ||
| 650 | R.integer.SWITCH_BUTTON_Y_X_FOLDABLE, | ||
| 651 | R.integer.SWITCH_BUTTON_Y_Y_FOLDABLE, | ||
| 652 | R.integer.SWITCH_TRIGGER_ZL_X_FOLDABLE, | ||
| 653 | R.integer.SWITCH_TRIGGER_ZL_Y_FOLDABLE, | ||
| 654 | R.integer.SWITCH_TRIGGER_ZR_X_FOLDABLE, | ||
| 655 | R.integer.SWITCH_TRIGGER_ZR_Y_FOLDABLE, | ||
| 656 | R.integer.SWITCH_BUTTON_DPAD_X_FOLDABLE, | ||
| 657 | R.integer.SWITCH_BUTTON_DPAD_Y_FOLDABLE, | ||
| 658 | R.integer.SWITCH_TRIGGER_L_X_FOLDABLE, | ||
| 659 | R.integer.SWITCH_TRIGGER_L_Y_FOLDABLE, | ||
| 660 | R.integer.SWITCH_TRIGGER_R_X_FOLDABLE, | ||
| 661 | R.integer.SWITCH_TRIGGER_R_Y_FOLDABLE, | ||
| 662 | R.integer.SWITCH_BUTTON_PLUS_X_FOLDABLE, | ||
| 663 | R.integer.SWITCH_BUTTON_PLUS_Y_FOLDABLE, | ||
| 664 | R.integer.SWITCH_BUTTON_MINUS_X_FOLDABLE, | ||
| 665 | R.integer.SWITCH_BUTTON_MINUS_Y_FOLDABLE, | ||
| 666 | R.integer.SWITCH_BUTTON_HOME_X_FOLDABLE, | ||
| 667 | R.integer.SWITCH_BUTTON_HOME_Y_FOLDABLE, | ||
| 668 | R.integer.SWITCH_BUTTON_CAPTURE_X_FOLDABLE, | ||
| 669 | R.integer.SWITCH_BUTTON_CAPTURE_Y_FOLDABLE, | ||
| 670 | R.integer.SWITCH_STICK_R_X_FOLDABLE, | ||
| 671 | R.integer.SWITCH_STICK_R_Y_FOLDABLE, | ||
| 672 | R.integer.SWITCH_STICK_L_X_FOLDABLE, | ||
| 673 | R.integer.SWITCH_STICK_L_Y_FOLDABLE | ||
| 674 | ) | ||
| 675 | |||
| 676 | private fun getResourceValue(orientation: String, position: Int): Float { | ||
| 677 | return when (orientation) { | ||
| 678 | PORTRAIT -> resources.getInteger(portraitResources[position]).toFloat() / 1000 | ||
| 679 | FOLDABLE -> resources.getInteger(foldableResources[position]).toFloat() / 1000 | ||
| 680 | else -> resources.getInteger(landscapeResources[position]).toFloat() / 1000 | ||
| 681 | } | ||
| 682 | } | ||
| 683 | |||
| 684 | private fun defaultOverlayByLayout(orientation: String) { | ||
| 577 | // Each value represents the position of the button in relation to the screen size without insets. | 685 | // Each value represents the position of the button in relation to the screen size without insets. |
| 578 | preferences.edit() | 686 | preferences.edit() |
| 579 | .putFloat( | 687 | .putFloat( |
| 580 | ButtonType.BUTTON_A.toString() + "-X", | 688 | ButtonType.BUTTON_A.toString() + "-X$orientation", |
| 581 | resources.getInteger(R.integer.SWITCH_BUTTON_A_X).toFloat() / 1000 | 689 | getResourceValue(orientation, 0) |
| 582 | ) | 690 | ) |
| 583 | .putFloat( | 691 | .putFloat( |
| 584 | ButtonType.BUTTON_A.toString() + "-Y", | 692 | ButtonType.BUTTON_A.toString() + "-Y$orientation", |
| 585 | resources.getInteger(R.integer.SWITCH_BUTTON_A_Y).toFloat() / 1000 | 693 | getResourceValue(orientation, 1) |
| 586 | ) | 694 | ) |
| 587 | .putFloat( | 695 | .putFloat( |
| 588 | ButtonType.BUTTON_B.toString() + "-X", | 696 | ButtonType.BUTTON_B.toString() + "-X$orientation", |
| 589 | resources.getInteger(R.integer.SWITCH_BUTTON_B_X).toFloat() / 1000 | 697 | getResourceValue(orientation, 2) |
| 590 | ) | 698 | ) |
| 591 | .putFloat( | 699 | .putFloat( |
| 592 | ButtonType.BUTTON_B.toString() + "-Y", | 700 | ButtonType.BUTTON_B.toString() + "-Y$orientation", |
| 593 | resources.getInteger(R.integer.SWITCH_BUTTON_B_Y).toFloat() / 1000 | 701 | getResourceValue(orientation, 3) |
| 594 | ) | 702 | ) |
| 595 | .putFloat( | 703 | .putFloat( |
| 596 | ButtonType.BUTTON_X.toString() + "-X", | 704 | ButtonType.BUTTON_X.toString() + "-X$orientation", |
| 597 | resources.getInteger(R.integer.SWITCH_BUTTON_X_X).toFloat() / 1000 | 705 | getResourceValue(orientation, 4) |
| 598 | ) | 706 | ) |
| 599 | .putFloat( | 707 | .putFloat( |
| 600 | ButtonType.BUTTON_X.toString() + "-Y", | 708 | ButtonType.BUTTON_X.toString() + "-Y$orientation", |
| 601 | resources.getInteger(R.integer.SWITCH_BUTTON_X_Y).toFloat() / 1000 | 709 | getResourceValue(orientation, 5) |
| 602 | ) | 710 | ) |
| 603 | .putFloat( | 711 | .putFloat( |
| 604 | ButtonType.BUTTON_Y.toString() + "-X", | 712 | ButtonType.BUTTON_Y.toString() + "-X$orientation", |
| 605 | resources.getInteger(R.integer.SWITCH_BUTTON_Y_X).toFloat() / 1000 | 713 | getResourceValue(orientation, 6) |
| 606 | ) | 714 | ) |
| 607 | .putFloat( | 715 | .putFloat( |
| 608 | ButtonType.BUTTON_Y.toString() + "-Y", | 716 | ButtonType.BUTTON_Y.toString() + "-Y$orientation", |
| 609 | resources.getInteger(R.integer.SWITCH_BUTTON_Y_Y).toFloat() / 1000 | 717 | getResourceValue(orientation, 7) |
| 610 | ) | 718 | ) |
| 611 | .putFloat( | 719 | .putFloat( |
| 612 | ButtonType.TRIGGER_ZL.toString() + "-X", | 720 | ButtonType.TRIGGER_ZL.toString() + "-X$orientation", |
| 613 | resources.getInteger(R.integer.SWITCH_TRIGGER_ZL_X).toFloat() / 1000 | 721 | getResourceValue(orientation, 8) |
| 614 | ) | 722 | ) |
| 615 | .putFloat( | 723 | .putFloat( |
| 616 | ButtonType.TRIGGER_ZL.toString() + "-Y", | 724 | ButtonType.TRIGGER_ZL.toString() + "-Y$orientation", |
| 617 | resources.getInteger(R.integer.SWITCH_TRIGGER_ZL_Y).toFloat() / 1000 | 725 | getResourceValue(orientation, 9) |
| 618 | ) | 726 | ) |
| 619 | .putFloat( | 727 | .putFloat( |
| 620 | ButtonType.TRIGGER_ZR.toString() + "-X", | 728 | ButtonType.TRIGGER_ZR.toString() + "-X$orientation", |
| 621 | resources.getInteger(R.integer.SWITCH_TRIGGER_ZR_X).toFloat() / 1000 | 729 | getResourceValue(orientation, 10) |
| 622 | ) | 730 | ) |
| 623 | .putFloat( | 731 | .putFloat( |
| 624 | ButtonType.TRIGGER_ZR.toString() + "-Y", | 732 | ButtonType.TRIGGER_ZR.toString() + "-Y$orientation", |
| 625 | resources.getInteger(R.integer.SWITCH_TRIGGER_ZR_Y).toFloat() / 1000 | 733 | getResourceValue(orientation, 11) |
| 626 | ) | 734 | ) |
| 627 | .putFloat( | 735 | .putFloat( |
| 628 | ButtonType.DPAD_UP.toString() + "-X", | 736 | ButtonType.DPAD_UP.toString() + "-X$orientation", |
| 629 | resources.getInteger(R.integer.SWITCH_BUTTON_DPAD_X).toFloat() / 1000 | 737 | getResourceValue(orientation, 12) |
| 630 | ) | 738 | ) |
| 631 | .putFloat( | 739 | .putFloat( |
| 632 | ButtonType.DPAD_UP.toString() + "-Y", | 740 | ButtonType.DPAD_UP.toString() + "-Y$orientation", |
| 633 | resources.getInteger(R.integer.SWITCH_BUTTON_DPAD_Y).toFloat() / 1000 | 741 | getResourceValue(orientation, 13) |
| 634 | ) | 742 | ) |
| 635 | .putFloat( | 743 | .putFloat( |
| 636 | ButtonType.TRIGGER_L.toString() + "-X", | 744 | ButtonType.TRIGGER_L.toString() + "-X$orientation", |
| 637 | resources.getInteger(R.integer.SWITCH_TRIGGER_L_X).toFloat() / 1000 | 745 | getResourceValue(orientation, 14) |
| 638 | ) | 746 | ) |
| 639 | .putFloat( | 747 | .putFloat( |
| 640 | ButtonType.TRIGGER_L.toString() + "-Y", | 748 | ButtonType.TRIGGER_L.toString() + "-Y$orientation", |
| 641 | resources.getInteger(R.integer.SWITCH_TRIGGER_L_Y).toFloat() / 1000 | 749 | getResourceValue(orientation, 15) |
| 642 | ) | 750 | ) |
| 643 | .putFloat( | 751 | .putFloat( |
| 644 | ButtonType.TRIGGER_R.toString() + "-X", | 752 | ButtonType.TRIGGER_R.toString() + "-X$orientation", |
| 645 | resources.getInteger(R.integer.SWITCH_TRIGGER_R_X).toFloat() / 1000 | 753 | getResourceValue(orientation, 16) |
| 646 | ) | 754 | ) |
| 647 | .putFloat( | 755 | .putFloat( |
| 648 | ButtonType.TRIGGER_R.toString() + "-Y", | 756 | ButtonType.TRIGGER_R.toString() + "-Y$orientation", |
| 649 | resources.getInteger(R.integer.SWITCH_TRIGGER_R_Y).toFloat() / 1000 | 757 | getResourceValue(orientation, 17) |
| 650 | ) | 758 | ) |
| 651 | .putFloat( | 759 | .putFloat( |
| 652 | ButtonType.BUTTON_PLUS.toString() + "-X", | 760 | ButtonType.BUTTON_PLUS.toString() + "-X$orientation", |
| 653 | resources.getInteger(R.integer.SWITCH_BUTTON_PLUS_X).toFloat() / 1000 | 761 | getResourceValue(orientation, 18) |
| 654 | ) | 762 | ) |
| 655 | .putFloat( | 763 | .putFloat( |
| 656 | ButtonType.BUTTON_PLUS.toString() + "-Y", | 764 | ButtonType.BUTTON_PLUS.toString() + "-Y$orientation", |
| 657 | resources.getInteger(R.integer.SWITCH_BUTTON_PLUS_Y).toFloat() / 1000 | 765 | getResourceValue(orientation, 19) |
| 658 | ) | 766 | ) |
| 659 | .putFloat( | 767 | .putFloat( |
| 660 | ButtonType.BUTTON_MINUS.toString() + "-X", | 768 | ButtonType.BUTTON_MINUS.toString() + "-X$orientation", |
| 661 | resources.getInteger(R.integer.SWITCH_BUTTON_MINUS_X).toFloat() / 1000 | 769 | getResourceValue(orientation, 20) |
| 662 | ) | 770 | ) |
| 663 | .putFloat( | 771 | .putFloat( |
| 664 | ButtonType.BUTTON_MINUS.toString() + "-Y", | 772 | ButtonType.BUTTON_MINUS.toString() + "-Y$orientation", |
| 665 | resources.getInteger(R.integer.SWITCH_BUTTON_MINUS_Y).toFloat() / 1000 | 773 | getResourceValue(orientation, 21) |
| 666 | ) | 774 | ) |
| 667 | .putFloat( | 775 | .putFloat( |
| 668 | ButtonType.BUTTON_HOME.toString() + "-X", | 776 | ButtonType.BUTTON_HOME.toString() + "-X$orientation", |
| 669 | resources.getInteger(R.integer.SWITCH_BUTTON_HOME_X).toFloat() / 1000 | 777 | getResourceValue(orientation, 22) |
| 670 | ) | 778 | ) |
| 671 | .putFloat( | 779 | .putFloat( |
| 672 | ButtonType.BUTTON_HOME.toString() + "-Y", | 780 | ButtonType.BUTTON_HOME.toString() + "-Y$orientation", |
| 673 | resources.getInteger(R.integer.SWITCH_BUTTON_HOME_Y).toFloat() / 1000 | 781 | getResourceValue(orientation, 23) |
| 674 | ) | 782 | ) |
| 675 | .putFloat( | 783 | .putFloat( |
| 676 | ButtonType.BUTTON_CAPTURE.toString() + "-X", | 784 | ButtonType.BUTTON_CAPTURE.toString() + "-X$orientation", |
| 677 | resources.getInteger(R.integer.SWITCH_BUTTON_CAPTURE_X) | 785 | getResourceValue(orientation, 24) |
| 678 | .toFloat() / 1000 | ||
| 679 | ) | 786 | ) |
| 680 | .putFloat( | 787 | .putFloat( |
| 681 | ButtonType.BUTTON_CAPTURE.toString() + "-Y", | 788 | ButtonType.BUTTON_CAPTURE.toString() + "-Y$orientation", |
| 682 | resources.getInteger(R.integer.SWITCH_BUTTON_CAPTURE_Y) | 789 | getResourceValue(orientation, 25) |
| 683 | .toFloat() / 1000 | ||
| 684 | ) | 790 | ) |
| 685 | .putFloat( | 791 | .putFloat( |
| 686 | ButtonType.STICK_R.toString() + "-X", | 792 | ButtonType.STICK_R.toString() + "-X$orientation", |
| 687 | resources.getInteger(R.integer.SWITCH_STICK_R_X).toFloat() / 1000 | 793 | getResourceValue(orientation, 26) |
| 688 | ) | 794 | ) |
| 689 | .putFloat( | 795 | .putFloat( |
| 690 | ButtonType.STICK_R.toString() + "-Y", | 796 | ButtonType.STICK_R.toString() + "-Y$orientation", |
| 691 | resources.getInteger(R.integer.SWITCH_STICK_R_Y).toFloat() / 1000 | 797 | getResourceValue(orientation, 27) |
| 692 | ) | 798 | ) |
| 693 | .putFloat( | 799 | .putFloat( |
| 694 | ButtonType.STICK_L.toString() + "-X", | 800 | ButtonType.STICK_L.toString() + "-X$orientation", |
| 695 | resources.getInteger(R.integer.SWITCH_STICK_L_X).toFloat() / 1000 | 801 | getResourceValue(orientation, 28) |
| 696 | ) | 802 | ) |
| 697 | .putFloat( | 803 | .putFloat( |
| 698 | ButtonType.STICK_L.toString() + "-Y", | 804 | ButtonType.STICK_L.toString() + "-Y$orientation", |
| 699 | resources.getInteger(R.integer.SWITCH_STICK_L_Y).toFloat() / 1000 | 805 | getResourceValue(orientation, 29) |
| 700 | ) | 806 | ) |
| 701 | .apply() | 807 | .apply() |
| 702 | } | 808 | } |
| @@ -709,13 +815,17 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context | |||
| 709 | private val preferences: SharedPreferences = | 815 | private val preferences: SharedPreferences = |
| 710 | PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) | 816 | PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) |
| 711 | 817 | ||
| 818 | const val LANDSCAPE = "" | ||
| 819 | const val PORTRAIT = "_Portrait" | ||
| 820 | const val FOLDABLE = "_Foldable" | ||
| 821 | |||
| 712 | /** | 822 | /** |
| 713 | * Resizes a [Bitmap] by a given scale factor | 823 | * Resizes a [Bitmap] by a given scale factor |
| 714 | * | 824 | * |
| 715 | * @param context Context for getting the vector drawable | 825 | * @param context Context for getting the vector drawable |
| 716 | * @param drawableId The ID of the drawable to scale. | 826 | * @param drawableId The ID of the drawable to scale. |
| 717 | * @param scale The scale factor for the bitmap. | 827 | * @param scale The scale factor for the bitmap. |
| 718 | * @return The scaled [Bitmap] | 828 | * @return The scaled [Bitmap] |
| 719 | */ | 829 | */ |
| 720 | private fun getBitmap(context: Context, drawableId: Int, scale: Float): Bitmap { | 830 | private fun getBitmap(context: Context, drawableId: Int, scale: Float): Bitmap { |
| 721 | val vectorDrawable = ContextCompat.getDrawable(context, drawableId) as VectorDrawable | 831 | val vectorDrawable = ContextCompat.getDrawable(context, drawableId) as VectorDrawable |
| @@ -749,14 +859,13 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context | |||
| 749 | * Gets the safe screen size for drawing the overlay | 859 | * Gets the safe screen size for drawing the overlay |
| 750 | * | 860 | * |
| 751 | * @param context Context for getting the window metrics | 861 | * @param context Context for getting the window metrics |
| 752 | * @return A pair of points, the first being the top left corner of the safe area, | 862 | * @return A pair of points, the first being the top left corner of the safe area, |
| 753 | * the second being the bottom right corner of the safe area | 863 | * the second being the bottom right corner of the safe area |
| 754 | */ | 864 | */ |
| 755 | private fun getSafeScreenSize(context: Context): Pair<Point, Point> { | 865 | private fun getSafeScreenSize(context: Context): Pair<Point, Point> { |
| 756 | // Get screen size | 866 | // Get screen size |
| 757 | val windowMetrics = | 867 | val windowMetrics = WindowMetricsCalculator.getOrCreate() |
| 758 | WindowMetricsCalculator.getOrCreate() | 868 | .computeCurrentWindowMetrics(context as Activity) |
| 759 | .computeCurrentWindowMetrics(context as Activity) | ||
| 760 | var maxY = windowMetrics.bounds.height().toFloat() | 869 | var maxY = windowMetrics.bounds.height().toFloat() |
| 761 | var maxX = windowMetrics.bounds.width().toFloat() | 870 | var maxX = windowMetrics.bounds.width().toFloat() |
| 762 | var minY = 0 | 871 | var minY = 0 |
| @@ -768,10 +877,16 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context | |||
| 768 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { | 877 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { |
| 769 | val insets = context.windowManager.currentWindowMetrics.windowInsets.displayCutout | 878 | val insets = context.windowManager.currentWindowMetrics.windowInsets.displayCutout |
| 770 | if (insets != null) { | 879 | if (insets != null) { |
| 771 | if (insets.boundingRectTop.bottom != 0 && insets.boundingRectTop.bottom > maxY / 2) | 880 | if (insets.boundingRectTop.bottom != 0 && |
| 772 | insets.boundingRectTop.bottom.toFloat() else maxY | 881 | insets.boundingRectTop.bottom > maxY / 2 |
| 773 | if (insets.boundingRectRight.left != 0 && insets.boundingRectRight.left > maxX / 2) | 882 | ) { |
| 774 | insets.boundingRectRight.left.toFloat() else maxX | 883 | maxY = insets.boundingRectTop.bottom.toFloat() |
| 884 | } | ||
| 885 | if (insets.boundingRectRight.left != 0 && | ||
| 886 | insets.boundingRectRight.left > maxX / 2 | ||
| 887 | ) { | ||
| 888 | maxX = insets.boundingRectRight.left.toFloat() | ||
| 889 | } | ||
| 775 | 890 | ||
| 776 | minX = insets.boundingRectLeft.right - insets.boundingRectLeft.left | 891 | minX = insets.boundingRectLeft.right - insets.boundingRectLeft.left |
| 777 | minY = insets.boundingRectBottom.top - insets.boundingRectBottom.bottom | 892 | minY = insets.boundingRectBottom.top - insets.boundingRectBottom.bottom |
| @@ -878,8 +993,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context | |||
| 878 | 993 | ||
| 879 | // The X and Y coordinates of the InputOverlayDrawableButton on the InputOverlay. | 994 | // The X and Y coordinates of the InputOverlayDrawableButton on the InputOverlay. |
| 880 | // These were set in the input overlay configuration menu. | 995 | // These were set in the input overlay configuration menu. |
| 881 | val xKey = "$buttonId$orientation-X" | 996 | val xKey = "$buttonId-X$orientation" |
| 882 | val yKey = "$buttonId$orientation-Y" | 997 | val yKey = "$buttonId-Y$orientation" |
| 883 | val drawableXPercent = sPrefs.getFloat(xKey, 0f) | 998 | val drawableXPercent = sPrefs.getFloat(xKey, 0f) |
| 884 | val drawableYPercent = sPrefs.getFloat(yKey, 0f) | 999 | val drawableYPercent = sPrefs.getFloat(yKey, 0f) |
| 885 | val drawableX = (drawableXPercent * max.x + min.x).toInt() | 1000 | val drawableX = (drawableXPercent * max.x + min.x).toInt() |
| @@ -959,8 +1074,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context | |||
| 959 | 1074 | ||
| 960 | // The X and Y coordinates of the InputOverlayDrawableDpad on the InputOverlay. | 1075 | // The X and Y coordinates of the InputOverlayDrawableDpad on the InputOverlay. |
| 961 | // These were set in the input overlay configuration menu. | 1076 | // These were set in the input overlay configuration menu. |
| 962 | val drawableXPercent = sPrefs.getFloat("${ButtonType.DPAD_UP}$orientation-X", 0f) | 1077 | val drawableXPercent = sPrefs.getFloat("${ButtonType.DPAD_UP}-X$orientation", 0f) |
| 963 | val drawableYPercent = sPrefs.getFloat("${ButtonType.DPAD_UP}$orientation-Y", 0f) | 1078 | val drawableYPercent = sPrefs.getFloat("${ButtonType.DPAD_UP}-Y$orientation", 0f) |
| 964 | val drawableX = (drawableXPercent * max.x + min.x).toInt() | 1079 | val drawableX = (drawableXPercent * max.x + min.x).toInt() |
| 965 | val drawableY = (drawableYPercent * max.y + min.y).toInt() | 1080 | val drawableY = (drawableYPercent * max.y + min.y).toInt() |
| 966 | val width = overlayDrawable.width | 1081 | val width = overlayDrawable.width |
| @@ -1026,8 +1141,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context | |||
| 1026 | 1141 | ||
| 1027 | // The X and Y coordinates of the InputOverlayDrawableButton on the InputOverlay. | 1142 | // The X and Y coordinates of the InputOverlayDrawableButton on the InputOverlay. |
| 1028 | // These were set in the input overlay configuration menu. | 1143 | // These were set in the input overlay configuration menu. |
| 1029 | val drawableXPercent = sPrefs.getFloat("$button$orientation-X", 0f) | 1144 | val drawableXPercent = sPrefs.getFloat("$button-X$orientation", 0f) |
| 1030 | val drawableYPercent = sPrefs.getFloat("$button$orientation-Y", 0f) | 1145 | val drawableYPercent = sPrefs.getFloat("$button-Y$orientation", 0f) |
| 1031 | val drawableX = (drawableXPercent * max.x + min.x).toInt() | 1146 | val drawableX = (drawableXPercent * max.x + min.x).toInt() |
| 1032 | val drawableY = (drawableYPercent * max.y + min.y).toInt() | 1147 | val drawableY = (drawableYPercent * max.y + min.y).toInt() |
| 1033 | val outerScale = 1.66f | 1148 | val outerScale = 1.66f |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableDpad.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableDpad.kt index 43d664d21..8aef6f5a5 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableDpad.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableDpad.kt | |||
| @@ -133,7 +133,10 @@ class InputOverlayDrawableDpad( | |||
| 133 | downButtonState = axisY > VIRT_AXIS_DEADZONE | 133 | downButtonState = axisY > VIRT_AXIS_DEADZONE |
| 134 | leftButtonState = axisX < -VIRT_AXIS_DEADZONE | 134 | leftButtonState = axisX < -VIRT_AXIS_DEADZONE |
| 135 | rightButtonState = axisX > VIRT_AXIS_DEADZONE | 135 | rightButtonState = axisX > VIRT_AXIS_DEADZONE |
| 136 | return oldUpState != upButtonState || oldDownState != downButtonState || oldLeftState != leftButtonState || oldRightState != rightButtonState | 136 | return oldUpState != upButtonState || |
| 137 | oldDownState != downButtonState || | ||
| 138 | oldLeftState != leftButtonState || | ||
| 139 | oldRightState != rightButtonState | ||
| 137 | } | 140 | } |
| 138 | return false | 141 | return false |
| 139 | } | 142 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt index f1d32192a..fb48f584d 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt | |||
| @@ -9,12 +9,12 @@ import android.graphics.Canvas | |||
| 9 | import android.graphics.Rect | 9 | import android.graphics.Rect |
| 10 | import android.graphics.drawable.BitmapDrawable | 10 | import android.graphics.drawable.BitmapDrawable |
| 11 | import android.view.MotionEvent | 11 | import android.view.MotionEvent |
| 12 | import org.yuzu.yuzu_emu.NativeLibrary | ||
| 13 | import org.yuzu.yuzu_emu.utils.EmulationMenuSettings | ||
| 14 | import kotlin.math.atan2 | 12 | import kotlin.math.atan2 |
| 15 | import kotlin.math.cos | 13 | import kotlin.math.cos |
| 16 | import kotlin.math.sin | 14 | import kotlin.math.sin |
| 17 | import kotlin.math.sqrt | 15 | import kotlin.math.sqrt |
| 16 | import org.yuzu.yuzu_emu.NativeLibrary | ||
| 17 | import org.yuzu.yuzu_emu.utils.EmulationMenuSettings | ||
| 18 | 18 | ||
| 19 | /** | 19 | /** |
| 20 | * Custom [BitmapDrawable] that is capable | 20 | * Custom [BitmapDrawable] that is capable |
| @@ -241,14 +241,22 @@ class InputOverlayDrawableJoystick( | |||
| 241 | private fun setInnerBounds() { | 241 | private fun setInnerBounds() { |
| 242 | var x = virtBounds.centerX() + (xAxis * (virtBounds.width() / 2)).toInt() | 242 | var x = virtBounds.centerX() + (xAxis * (virtBounds.width() / 2)).toInt() |
| 243 | var y = virtBounds.centerY() + (yAxis * (virtBounds.height() / 2)).toInt() | 243 | var y = virtBounds.centerY() + (yAxis * (virtBounds.height() / 2)).toInt() |
| 244 | if (x > virtBounds.centerX() + virtBounds.width() / 2) x = | 244 | if (x > virtBounds.centerX() + virtBounds.width() / 2) { |
| 245 | virtBounds.centerX() + virtBounds.width() / 2 | 245 | x = |
| 246 | if (x < virtBounds.centerX() - virtBounds.width() / 2) x = | 246 | virtBounds.centerX() + virtBounds.width() / 2 |
| 247 | virtBounds.centerX() - virtBounds.width() / 2 | 247 | } |
| 248 | if (y > virtBounds.centerY() + virtBounds.height() / 2) y = | 248 | if (x < virtBounds.centerX() - virtBounds.width() / 2) { |
| 249 | virtBounds.centerY() + virtBounds.height() / 2 | 249 | x = |
| 250 | if (y < virtBounds.centerY() - virtBounds.height() / 2) y = | 250 | virtBounds.centerX() - virtBounds.width() / 2 |
| 251 | virtBounds.centerY() - virtBounds.height() / 2 | 251 | } |
| 252 | if (y > virtBounds.centerY() + virtBounds.height() / 2) { | ||
| 253 | y = | ||
| 254 | virtBounds.centerY() + virtBounds.height() / 2 | ||
| 255 | } | ||
| 256 | if (y < virtBounds.centerY() - virtBounds.height() / 2) { | ||
| 257 | y = | ||
| 258 | virtBounds.centerY() - virtBounds.height() / 2 | ||
| 259 | } | ||
| 252 | val width = pressedStateInnerBitmap.bounds.width() / 2 | 260 | val width = pressedStateInnerBitmap.bounds.width() / 2 |
| 253 | val height = pressedStateInnerBitmap.bounds.height() / 2 | 261 | val height = pressedStateInnerBitmap.bounds.height() / 2 |
| 254 | defaultStateInnerBitmap.setBounds( | 262 | defaultStateInnerBitmap.setBounds( |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt index 97eef40d2..b0156dca5 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt | |||
| @@ -99,7 +99,9 @@ class GamesFragment : Fragment() { | |||
| 99 | } | 99 | } |
| 100 | shouldSwapData.observe(viewLifecycleOwner) { shouldSwapData -> | 100 | shouldSwapData.observe(viewLifecycleOwner) { shouldSwapData -> |
| 101 | if (shouldSwapData) { | 101 | if (shouldSwapData) { |
| 102 | (binding.gridGames.adapter as GameAdapter).submitList(gamesViewModel.games.value!!) | 102 | (binding.gridGames.adapter as GameAdapter).submitList( |
| 103 | gamesViewModel.games.value!! | ||
| 104 | ) | ||
| 103 | gamesViewModel.setShouldSwapData(false) | 105 | gamesViewModel.setShouldSwapData(false) |
| 104 | } | 106 | } |
| 105 | } | 107 | } |
| @@ -128,7 +130,9 @@ class GamesFragment : Fragment() { | |||
| 128 | } | 130 | } |
| 129 | 131 | ||
| 130 | private fun setInsets() = | 132 | private fun setInsets() = |
| 131 | ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view: View, windowInsets: WindowInsetsCompat -> | 133 | ViewCompat.setOnApplyWindowInsetsListener( |
| 134 | binding.root | ||
| 135 | ) { view: View, windowInsets: WindowInsetsCompat -> | ||
| 132 | val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) | 136 | val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) |
| 133 | val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) | 137 | val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) |
| 134 | val extraListSpacing = resources.getDimensionPixelSize(R.dimen.spacing_large) | 138 | val extraListSpacing = resources.getDimensionPixelSize(R.dimen.spacing_large) |
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 041d16f3a..cc1d87f1b 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 | |||
| @@ -26,6 +26,9 @@ import androidx.preference.PreferenceManager | |||
| 26 | import com.google.android.material.color.MaterialColors | 26 | import com.google.android.material.color.MaterialColors |
| 27 | import com.google.android.material.dialog.MaterialAlertDialogBuilder | 27 | import com.google.android.material.dialog.MaterialAlertDialogBuilder |
| 28 | import com.google.android.material.navigation.NavigationBarView | 28 | import com.google.android.material.navigation.NavigationBarView |
| 29 | import java.io.File | ||
| 30 | import java.io.FilenameFilter | ||
| 31 | import java.io.IOException | ||
| 29 | import kotlinx.coroutines.Dispatchers | 32 | import kotlinx.coroutines.Dispatchers |
| 30 | import kotlinx.coroutines.launch | 33 | import kotlinx.coroutines.launch |
| 31 | import kotlinx.coroutines.withContext | 34 | import kotlinx.coroutines.withContext |
| @@ -43,9 +46,6 @@ import org.yuzu.yuzu_emu.fragments.MessageDialogFragment | |||
| 43 | import org.yuzu.yuzu_emu.model.GamesViewModel | 46 | import org.yuzu.yuzu_emu.model.GamesViewModel |
| 44 | import org.yuzu.yuzu_emu.model.HomeViewModel | 47 | import org.yuzu.yuzu_emu.model.HomeViewModel |
| 45 | import org.yuzu.yuzu_emu.utils.* | 48 | import org.yuzu.yuzu_emu.utils.* |
| 46 | import java.io.File | ||
| 47 | import java.io.FilenameFilter | ||
| 48 | import java.io.IOException | ||
| 49 | 49 | ||
| 50 | class MainActivity : AppCompatActivity(), ThemeProvider { | 50 | class MainActivity : AppCompatActivity(), ThemeProvider { |
| 51 | private lateinit var binding: ActivityMainBinding | 51 | private lateinit var binding: ActivityMainBinding |
| @@ -86,7 +86,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 86 | ThemeHelper.SYSTEM_BAR_ALPHA | 86 | ThemeHelper.SYSTEM_BAR_ALPHA |
| 87 | ) | 87 | ) |
| 88 | ) | 88 | ) |
| 89 | if (InsetsHelper.getSystemGestureType(applicationContext) != InsetsHelper.GESTURE_NAVIGATION) { | 89 | if (InsetsHelper.getSystemGestureType(applicationContext) != |
| 90 | InsetsHelper.GESTURE_NAVIGATION | ||
| 91 | ) { | ||
| 90 | binding.navigationBarShade.setBackgroundColor( | 92 | binding.navigationBarShade.setBackgroundColor( |
| 91 | ThemeHelper.getColorWithOpacity( | 93 | ThemeHelper.getColorWithOpacity( |
| 92 | MaterialColors.getColor( | 94 | MaterialColors.getColor( |
| @@ -172,7 +174,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 172 | binding.navigationView.height.toFloat() * 2 | 174 | binding.navigationView.height.toFloat() * 2 |
| 173 | translationY(0f) | 175 | translationY(0f) |
| 174 | } else { | 176 | } else { |
| 175 | if (ViewCompat.getLayoutDirection(binding.navigationView) == ViewCompat.LAYOUT_DIRECTION_LTR) { | 177 | if (ViewCompat.getLayoutDirection(binding.navigationView) == |
| 178 | ViewCompat.LAYOUT_DIRECTION_LTR | ||
| 179 | ) { | ||
| 176 | binding.navigationView.translationX = | 180 | binding.navigationView.translationX = |
| 177 | binding.navigationView.width.toFloat() * -2 | 181 | binding.navigationView.width.toFloat() * -2 |
| 178 | translationX(0f) | 182 | translationX(0f) |
| @@ -189,7 +193,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 189 | if (smallLayout) { | 193 | if (smallLayout) { |
| 190 | translationY(binding.navigationView.height.toFloat() * 2) | 194 | translationY(binding.navigationView.height.toFloat() * 2) |
| 191 | } else { | 195 | } else { |
| 192 | if (ViewCompat.getLayoutDirection(binding.navigationView) == ViewCompat.LAYOUT_DIRECTION_LTR) { | 196 | if (ViewCompat.getLayoutDirection(binding.navigationView) == |
| 197 | ViewCompat.LAYOUT_DIRECTION_LTR | ||
| 198 | ) { | ||
| 193 | translationX(binding.navigationView.width.toFloat() * -2) | 199 | translationX(binding.navigationView.width.toFloat() * -2) |
| 194 | } else { | 200 | } else { |
| 195 | translationX(binding.navigationView.width.toFloat() * 2) | 201 | translationX(binding.navigationView.width.toFloat() * 2) |
| @@ -234,7 +240,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 234 | } | 240 | } |
| 235 | 241 | ||
| 236 | private fun setInsets() = | 242 | private fun setInsets() = |
| 237 | ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _: View, windowInsets: WindowInsetsCompat -> | 243 | ViewCompat.setOnApplyWindowInsetsListener( |
| 244 | binding.root | ||
| 245 | ) { _: View, windowInsets: WindowInsetsCompat -> | ||
| 238 | val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) | 246 | val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) |
| 239 | val mlpStatusShade = binding.statusBarShade.layoutParams as MarginLayoutParams | 247 | val mlpStatusShade = binding.statusBarShade.layoutParams as MarginLayoutParams |
| 240 | mlpStatusShade.height = insets.top | 248 | mlpStatusShade.height = insets.top |
| @@ -256,8 +264,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 256 | 264 | ||
| 257 | val getGamesDirectory = | 265 | val getGamesDirectory = |
| 258 | registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result -> | 266 | registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result -> |
| 259 | if (result == null) | 267 | if (result == null) { |
| 260 | return@registerForActivityResult | 268 | return@registerForActivityResult |
| 269 | } | ||
| 261 | 270 | ||
| 262 | contentResolver.takePersistableUriPermission( | 271 | contentResolver.takePersistableUriPermission( |
| 263 | result, | 272 | result, |
| @@ -281,8 +290,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 281 | 290 | ||
| 282 | val getProdKey = | 291 | val getProdKey = |
| 283 | registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> | 292 | registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> |
| 284 | if (result == null) | 293 | if (result == null) { |
| 285 | return@registerForActivityResult | 294 | return@registerForActivityResult |
| 295 | } | ||
| 286 | 296 | ||
| 287 | if (!FileUtil.hasExtension(result, "keys")) { | 297 | if (!FileUtil.hasExtension(result, "keys")) { |
| 288 | MessageDialogFragment.newInstance( | 298 | MessageDialogFragment.newInstance( |
| @@ -324,8 +334,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 324 | 334 | ||
| 325 | val getFirmware = | 335 | val getFirmware = |
| 326 | registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> | 336 | registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> |
| 327 | if (result == null) | 337 | if (result == null) { |
| 328 | return@registerForActivityResult | 338 | return@registerForActivityResult |
| 339 | } | ||
| 329 | 340 | ||
| 330 | val inputZip = contentResolver.openInputStream(result) | 341 | val inputZip = contentResolver.openInputStream(result) |
| 331 | if (inputZip == null) { | 342 | if (inputZip == null) { |
| @@ -376,8 +387,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 376 | 387 | ||
| 377 | val getAmiiboKey = | 388 | val getAmiiboKey = |
| 378 | registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> | 389 | registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> |
| 379 | if (result == null) | 390 | if (result == null) { |
| 380 | return@registerForActivityResult | 391 | return@registerForActivityResult |
| 392 | } | ||
| 381 | 393 | ||
| 382 | if (!FileUtil.hasExtension(result, "bin")) { | 394 | if (!FileUtil.hasExtension(result, "bin")) { |
| 383 | MessageDialogFragment.newInstance( | 395 | MessageDialogFragment.newInstance( |
| @@ -418,8 +430,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 418 | 430 | ||
| 419 | val getDriver = | 431 | val getDriver = |
| 420 | registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> | 432 | registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> |
| 421 | if (result == null) | 433 | if (result == null) { |
| 422 | return@registerForActivityResult | 434 | return@registerForActivityResult |
| 435 | } | ||
| 423 | 436 | ||
| 424 | val takeFlags = | 437 | val takeFlags = |
| 425 | Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION | 438 | Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION |
| @@ -470,8 +483,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 470 | 483 | ||
| 471 | val installGameUpdate = | 484 | val installGameUpdate = |
| 472 | registerForActivityResult(ActivityResultContracts.OpenDocument()) { | 485 | registerForActivityResult(ActivityResultContracts.OpenDocument()) { |
| 473 | if (it == null) | 486 | if (it == null) { |
| 474 | return@registerForActivityResult | 487 | return@registerForActivityResult |
| 488 | } | ||
| 475 | 489 | ||
| 476 | IndeterminateProgressDialogFragment.newInstance( | 490 | IndeterminateProgressDialogFragment.newInstance( |
| 477 | this@MainActivity, | 491 | this@MainActivity, |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ControllerMappingHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ControllerMappingHelper.kt index 791cea904..eeefcdf20 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ControllerMappingHelper.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ControllerMappingHelper.kt | |||
| @@ -19,7 +19,9 @@ class ControllerMappingHelper { | |||
| 19 | // The two analog triggers generate analog motion events as well as a keycode. | 19 | // The two analog triggers generate analog motion events as well as a keycode. |
| 20 | // We always prefer to use the analog values, so throw away the button press | 20 | // We always prefer to use the analog values, so throw away the button press |
| 21 | keyCode == KeyEvent.KEYCODE_BUTTON_L2 || keyCode == KeyEvent.KEYCODE_BUTTON_R2 | 21 | keyCode == KeyEvent.KEYCODE_BUTTON_L2 || keyCode == KeyEvent.KEYCODE_BUTTON_R2 |
| 22 | } else false | 22 | } else { |
| 23 | false | ||
| 24 | } | ||
| 23 | } | 25 | } |
| 24 | 26 | ||
| 25 | /** | 27 | /** |
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 36c479e6c..2ee63697e 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 | |||
| @@ -4,8 +4,8 @@ | |||
| 4 | package org.yuzu.yuzu_emu.utils | 4 | package org.yuzu.yuzu_emu.utils |
| 5 | 5 | ||
| 6 | import android.content.Context | 6 | import android.content.Context |
| 7 | import org.yuzu.yuzu_emu.NativeLibrary | ||
| 8 | import java.io.IOException | 7 | import java.io.IOException |
| 8 | import org.yuzu.yuzu_emu.NativeLibrary | ||
| 9 | 9 | ||
| 10 | object DirectoryInitialization { | 10 | object DirectoryInitialization { |
| 11 | private var userPath: String? = null | 11 | private var userPath: String? = null |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DocumentsTree.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DocumentsTree.kt index cc8ea6b9d..cf226ad94 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DocumentsTree.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DocumentsTree.kt | |||
| @@ -5,10 +5,10 @@ package org.yuzu.yuzu_emu.utils | |||
| 5 | 5 | ||
| 6 | import android.net.Uri | 6 | import android.net.Uri |
| 7 | import androidx.documentfile.provider.DocumentFile | 7 | import androidx.documentfile.provider.DocumentFile |
| 8 | import org.yuzu.yuzu_emu.YuzuApplication | ||
| 9 | import org.yuzu.yuzu_emu.model.MinimalDocumentFile | ||
| 10 | import java.io.File | 8 | import java.io.File |
| 11 | import java.util.* | 9 | import java.util.* |
| 10 | import org.yuzu.yuzu_emu.YuzuApplication | ||
| 11 | import org.yuzu.yuzu_emu.model.MinimalDocumentFile | ||
| 12 | 12 | ||
| 13 | class DocumentsTree { | 13 | class DocumentsTree { |
| 14 | private var root: DocumentsNode? = null | 14 | private var root: DocumentsNode? = null |
| @@ -29,13 +29,20 @@ class DocumentsTree { | |||
| 29 | val node = resolvePath(filepath) | 29 | val node = resolvePath(filepath) |
| 30 | return if (node == null || node.isDirectory) { | 30 | return if (node == null || node.isDirectory) { |
| 31 | 0 | 31 | 0 |
| 32 | } else FileUtil.getFileSize(YuzuApplication.appContext, node.uri.toString()) | 32 | } else { |
| 33 | FileUtil.getFileSize(YuzuApplication.appContext, node.uri.toString()) | ||
| 34 | } | ||
| 33 | } | 35 | } |
| 34 | 36 | ||
| 35 | fun exists(filepath: String): Boolean { | 37 | fun exists(filepath: String): Boolean { |
| 36 | return resolvePath(filepath) != null | 38 | return resolvePath(filepath) != null |
| 37 | } | 39 | } |
| 38 | 40 | ||
| 41 | fun isDirectory(filepath: String): Boolean { | ||
| 42 | val node = resolvePath(filepath) | ||
| 43 | return node != null && node.isDirectory | ||
| 44 | } | ||
| 45 | |||
| 39 | private fun resolvePath(filepath: String): DocumentsNode? { | 46 | private fun resolvePath(filepath: String): DocumentsNode? { |
| 40 | val tokens = StringTokenizer(filepath, File.separator, false) | 47 | val tokens = StringTokenizer(filepath, File.separator, false) |
| 41 | var iterator = root | 48 | var iterator = root |
| @@ -106,7 +113,9 @@ class DocumentsTree { | |||
| 106 | fun isNativePath(path: String): Boolean { | 113 | fun isNativePath(path: String): Boolean { |
| 107 | return if (path.isNotEmpty()) { | 114 | return if (path.isNotEmpty()) { |
| 108 | path[0] == '/' | 115 | path[0] == '/' |
| 109 | } else false | 116 | } else { |
| 117 | false | ||
| 118 | } | ||
| 110 | } | 119 | } |
| 111 | } | 120 | } |
| 112 | } | 121 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/EmulationMenuSettings.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/EmulationMenuSettings.kt index e1e7a59d7..7e8f058c1 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/EmulationMenuSettings.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/EmulationMenuSettings.kt | |||
| @@ -11,14 +11,6 @@ object EmulationMenuSettings { | |||
| 11 | private val preferences = | 11 | private val preferences = |
| 12 | PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) | 12 | PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) |
| 13 | 13 | ||
| 14 | // These must match what is defined in src/core/settings.h | ||
| 15 | const val LayoutOption_Default = 0 | ||
| 16 | const val LayoutOption_SingleScreen = 1 | ||
| 17 | const val LayoutOption_LargeScreen = 2 | ||
| 18 | const val LayoutOption_SideScreen = 3 | ||
| 19 | const val LayoutOption_MobilePortrait = 4 | ||
| 20 | const val LayoutOption_MobileLandscape = 5 | ||
| 21 | |||
| 22 | var joystickRelCenter: Boolean | 14 | var joystickRelCenter: Boolean |
| 23 | get() = preferences.getBoolean(Settings.PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER, true) | 15 | get() = preferences.getBoolean(Settings.PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER, true) |
| 24 | set(value) { | 16 | set(value) { |
| @@ -41,16 +33,6 @@ object EmulationMenuSettings { | |||
| 41 | .apply() | 33 | .apply() |
| 42 | } | 34 | } |
| 43 | 35 | ||
| 44 | var landscapeScreenLayout: Int | ||
| 45 | get() = preferences.getInt( | ||
| 46 | Settings.PREF_MENU_SETTINGS_LANDSCAPE, | ||
| 47 | LayoutOption_MobileLandscape | ||
| 48 | ) | ||
| 49 | set(value) { | ||
| 50 | preferences.edit() | ||
| 51 | .putInt(Settings.PREF_MENU_SETTINGS_LANDSCAPE, value) | ||
| 52 | .apply() | ||
| 53 | } | ||
| 54 | var showFps: Boolean | 36 | var showFps: Boolean |
| 55 | get() = preferences.getBoolean(Settings.PREF_MENU_SETTINGS_SHOW_FPS, false) | 37 | get() = preferences.getBoolean(Settings.PREF_MENU_SETTINGS_SHOW_FPS, false) |
| 56 | set(value) { | 38 | set(value) { |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt index 492b1ad91..9f3bbe56f 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt | |||
| @@ -9,8 +9,6 @@ import android.net.Uri | |||
| 9 | import android.provider.DocumentsContract | 9 | import android.provider.DocumentsContract |
| 10 | import android.provider.OpenableColumns | 10 | import android.provider.OpenableColumns |
| 11 | import androidx.documentfile.provider.DocumentFile | 11 | import androidx.documentfile.provider.DocumentFile |
| 12 | import org.yuzu.yuzu_emu.YuzuApplication | ||
| 13 | import org.yuzu.yuzu_emu.model.MinimalDocumentFile | ||
| 14 | import java.io.BufferedInputStream | 12 | import java.io.BufferedInputStream |
| 15 | import java.io.File | 13 | import java.io.File |
| 16 | import java.io.FileOutputStream | 14 | import java.io.FileOutputStream |
| @@ -19,6 +17,8 @@ import java.io.InputStream | |||
| 19 | import java.net.URLDecoder | 17 | import java.net.URLDecoder |
| 20 | import java.util.zip.ZipEntry | 18 | import java.util.zip.ZipEntry |
| 21 | import java.util.zip.ZipInputStream | 19 | import java.util.zip.ZipInputStream |
| 20 | import org.yuzu.yuzu_emu.YuzuApplication | ||
| 21 | import org.yuzu.yuzu_emu.model.MinimalDocumentFile | ||
| 22 | 22 | ||
| 23 | object FileUtil { | 23 | object FileUtil { |
| 24 | const val PATH_TREE = "tree" | 24 | const val PATH_TREE = "tree" |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ForegroundService.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ForegroundService.kt index dc9b7c744..086d17606 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ForegroundService.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ForegroundService.kt | |||
| @@ -54,7 +54,7 @@ class ForegroundService : Service() { | |||
| 54 | 54 | ||
| 55 | override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { | 55 | override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { |
| 56 | if (intent == null) { | 56 | if (intent == null) { |
| 57 | return START_NOT_STICKY; | 57 | return START_NOT_STICKY |
| 58 | } | 58 | } |
| 59 | if (intent.action == ACTION_STOP) { | 59 | if (intent.action == ACTION_STOP) { |
| 60 | NotificationManagerCompat.from(this).cancel(EMULATION_RUNNING_NOTIFICATION) | 60 | NotificationManagerCompat.from(this).cancel(EMULATION_RUNNING_NOTIFICATION) |
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 42b207618..ee9f3e570 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 | |||
| @@ -6,12 +6,12 @@ package org.yuzu.yuzu_emu.utils | |||
| 6 | import android.content.SharedPreferences | 6 | import android.content.SharedPreferences |
| 7 | import android.net.Uri | 7 | import android.net.Uri |
| 8 | import androidx.preference.PreferenceManager | 8 | import androidx.preference.PreferenceManager |
| 9 | import java.util.* | ||
| 9 | import kotlinx.serialization.encodeToString | 10 | import kotlinx.serialization.encodeToString |
| 10 | import kotlinx.serialization.json.Json | 11 | import kotlinx.serialization.json.Json |
| 11 | import org.yuzu.yuzu_emu.NativeLibrary | 12 | import org.yuzu.yuzu_emu.NativeLibrary |
| 12 | import org.yuzu.yuzu_emu.YuzuApplication | 13 | import org.yuzu.yuzu_emu.YuzuApplication |
| 13 | import org.yuzu.yuzu_emu.model.Game | 14 | import org.yuzu.yuzu_emu.model.Game |
| 14 | import java.util.* | ||
| 15 | 15 | ||
| 16 | object GameHelper { | 16 | object GameHelper { |
| 17 | const val KEY_GAME_PATH = "game_path" | 17 | const val KEY_GAME_PATH = "game_path" |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt index 528011d7f..dad159481 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt | |||
| @@ -5,14 +5,14 @@ package org.yuzu.yuzu_emu.utils | |||
| 5 | 5 | ||
| 6 | import android.content.Context | 6 | import android.content.Context |
| 7 | import android.net.Uri | 7 | import android.net.Uri |
| 8 | import org.yuzu.yuzu_emu.NativeLibrary | ||
| 9 | import org.yuzu.yuzu_emu.utils.FileUtil.copyUriToInternalStorage | ||
| 10 | import java.io.BufferedInputStream | 8 | import java.io.BufferedInputStream |
| 11 | import java.io.File | 9 | import java.io.File |
| 12 | import java.io.FileInputStream | 10 | import java.io.FileInputStream |
| 13 | import java.io.FileOutputStream | 11 | import java.io.FileOutputStream |
| 14 | import java.io.IOException | 12 | import java.io.IOException |
| 15 | import java.util.zip.ZipInputStream | 13 | import java.util.zip.ZipInputStream |
| 14 | import org.yuzu.yuzu_emu.NativeLibrary | ||
| 15 | import org.yuzu.yuzu_emu.utils.FileUtil.copyUriToInternalStorage | ||
| 16 | 16 | ||
| 17 | object GpuDriverHelper { | 17 | object GpuDriverHelper { |
| 18 | private const val META_JSON_FILENAME = "meta.json" | 18 | private const val META_JSON_FILENAME = "meta.json" |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverMetadata.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverMetadata.kt index 70bdb4097..a4e64070a 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverMetadata.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverMetadata.kt | |||
| @@ -3,12 +3,12 @@ | |||
| 3 | 3 | ||
| 4 | package org.yuzu.yuzu_emu.utils | 4 | package org.yuzu.yuzu_emu.utils |
| 5 | 5 | ||
| 6 | import org.json.JSONException | ||
| 7 | import org.json.JSONObject | ||
| 8 | import java.io.IOException | 6 | import java.io.IOException |
| 9 | import java.nio.charset.StandardCharsets | 7 | import java.nio.charset.StandardCharsets |
| 10 | import java.nio.file.Files | 8 | import java.nio.file.Files |
| 11 | import java.nio.file.Paths | 9 | import java.nio.file.Paths |
| 10 | import org.json.JSONException | ||
| 11 | import org.json.JSONObject | ||
| 12 | 12 | ||
| 13 | class GpuDriverMetadata(metadataFilePath: String) { | 13 | class GpuDriverMetadata(metadataFilePath: String) { |
| 14 | var name: String? = null | 14 | var name: String? = null |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InputHandler.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InputHandler.kt index 24e999b29..e963dfbc1 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InputHandler.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InputHandler.kt | |||
| @@ -5,8 +5,8 @@ package org.yuzu.yuzu_emu.utils | |||
| 5 | 5 | ||
| 6 | import android.view.KeyEvent | 6 | import android.view.KeyEvent |
| 7 | import android.view.MotionEvent | 7 | import android.view.MotionEvent |
| 8 | import org.yuzu.yuzu_emu.NativeLibrary | ||
| 9 | import kotlin.math.sqrt | 8 | import kotlin.math.sqrt |
| 9 | import org.yuzu.yuzu_emu.NativeLibrary | ||
| 10 | 10 | ||
| 11 | class InputHandler { | 11 | class InputHandler { |
| 12 | fun initialize() { | 12 | fun initialize() { |
| @@ -68,7 +68,11 @@ class InputHandler { | |||
| 68 | 6 -> NativeLibrary.Player6Device | 68 | 6 -> NativeLibrary.Player6Device |
| 69 | 7 -> NativeLibrary.Player7Device | 69 | 7 -> NativeLibrary.Player7Device |
| 70 | 8 -> NativeLibrary.Player8Device | 70 | 8 -> NativeLibrary.Player8Device |
| 71 | else -> if (NativeLibrary.isHandheldOnly()) NativeLibrary.ConsoleDevice else NativeLibrary.Player1Device | 71 | else -> if (NativeLibrary.isHandheldOnly()) { |
| 72 | NativeLibrary.ConsoleDevice | ||
| 73 | } else { | ||
| 74 | NativeLibrary.Player1Device | ||
| 75 | } | ||
| 72 | } | 76 | } |
| 73 | } | 77 | } |
| 74 | 78 | ||
| @@ -107,7 +111,11 @@ class InputHandler { | |||
| 107 | } | 111 | } |
| 108 | 112 | ||
| 109 | private fun getAxisToButton(axis: Float): Int { | 113 | private fun getAxisToButton(axis: Float): Int { |
| 110 | return if (axis > 0.5f) NativeLibrary.ButtonState.PRESSED else NativeLibrary.ButtonState.RELEASED | 114 | return if (axis > 0.5f) { |
| 115 | NativeLibrary.ButtonState.PRESSED | ||
| 116 | } else { | ||
| 117 | NativeLibrary.ButtonState.RELEASED | ||
| 118 | } | ||
| 111 | } | 119 | } |
| 112 | 120 | ||
| 113 | private fun setAxisDpadState(playerNumber: Int, xAxis: Float, yAxis: Float) { | 121 | private fun setAxisDpadState(playerNumber: Int, xAxis: Float, yAxis: Float) { |
| @@ -287,7 +295,6 @@ class InputHandler { | |||
| 287 | } | 295 | } |
| 288 | } | 296 | } |
| 289 | 297 | ||
| 290 | |||
| 291 | private fun setJoyconAxisInput(event: MotionEvent, axis: Int) { | 298 | private fun setJoyconAxisInput(event: MotionEvent, axis: Int) { |
| 292 | // Joycon support is half dead. Right joystick doesn't work | 299 | // Joycon support is half dead. Right joystick doesn't work |
| 293 | val playerNumber = getPlayerNumber(event.device.controllerNumber) | 300 | val playerNumber = getPlayerNumber(event.device.controllerNumber) |
| @@ -355,6 +362,4 @@ class InputHandler { | |||
| 355 | ) | 362 | ) |
| 356 | } | 363 | } |
| 357 | } | 364 | } |
| 358 | 365 | } | |
| 359 | |||
| 360 | } \ No newline at end of file | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InsetsHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InsetsHelper.kt index 19c53c481..595f0d284 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InsetsHelper.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InsetsHelper.kt | |||
| @@ -4,9 +4,7 @@ | |||
| 4 | package org.yuzu.yuzu_emu.utils | 4 | package org.yuzu.yuzu_emu.utils |
| 5 | 5 | ||
| 6 | import android.annotation.SuppressLint | 6 | import android.annotation.SuppressLint |
| 7 | import android.app.Activity | ||
| 8 | import android.content.Context | 7 | import android.content.Context |
| 9 | import android.graphics.Rect | ||
| 10 | 8 | ||
| 11 | object InsetsHelper { | 9 | object InsetsHelper { |
| 12 | const val THREE_BUTTON_NAVIGATION = 0 | 10 | const val THREE_BUTTON_NAVIGATION = 0 |
| @@ -20,12 +18,8 @@ object InsetsHelper { | |||
| 20 | resources.getIdentifier("config_navBarInteractionMode", "integer", "android") | 18 | resources.getIdentifier("config_navBarInteractionMode", "integer", "android") |
| 21 | return if (resourceId != 0) { | 19 | return if (resourceId != 0) { |
| 22 | resources.getInteger(resourceId) | 20 | resources.getInteger(resourceId) |
| 23 | } else 0 | 21 | } else { |
| 24 | } | 22 | 0 |
| 25 | 23 | } | |
| 26 | fun getBottomPaddingRequired(activity: Activity): Int { | ||
| 27 | val visibleFrame = Rect() | ||
| 28 | activity.window.decorView.getWindowVisibleDisplayFrame(visibleFrame) | ||
| 29 | return visibleFrame.bottom - visibleFrame.top - activity.resources.displayMetrics.heightPixels | ||
| 30 | } | 24 | } |
| 31 | } | 25 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NfcReader.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NfcReader.kt index 344dd8a0a..68ed66565 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NfcReader.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NfcReader.kt | |||
| @@ -13,8 +13,8 @@ import android.nfc.tech.NfcA | |||
| 13 | import android.os.Build | 13 | import android.os.Build |
| 14 | import android.os.Handler | 14 | import android.os.Handler |
| 15 | import android.os.Looper | 15 | import android.os.Looper |
| 16 | import org.yuzu.yuzu_emu.NativeLibrary | ||
| 17 | import java.io.IOException | 16 | import java.io.IOException |
| 17 | import org.yuzu.yuzu_emu.NativeLibrary | ||
| 18 | 18 | ||
| 19 | class NfcReader(private val activity: Activity) { | 19 | class NfcReader(private val activity: Activity) { |
| 20 | private var nfcAdapter: NfcAdapter? = null | 20 | private var nfcAdapter: NfcAdapter? = null |
| @@ -25,10 +25,13 @@ class NfcReader(private val activity: Activity) { | |||
| 25 | 25 | ||
| 26 | pendingIntent = PendingIntent.getActivity( | 26 | pendingIntent = PendingIntent.getActivity( |
| 27 | activity, | 27 | activity, |
| 28 | 0, Intent(activity, activity.javaClass), | 28 | 0, |
| 29 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) | 29 | Intent(activity, activity.javaClass), |
| 30 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { | ||
| 30 | PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE | 31 | PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE |
| 31 | else PendingIntent.FLAG_UPDATE_CURRENT | 32 | } else { |
| 33 | PendingIntent.FLAG_UPDATE_CURRENT | ||
| 34 | } | ||
| 32 | ) | 35 | ) |
| 33 | 36 | ||
| 34 | val tagDetected = IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED) | 37 | val tagDetected = IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED) |
| @@ -45,9 +48,9 @@ class NfcReader(private val activity: Activity) { | |||
| 45 | 48 | ||
| 46 | fun onNewIntent(intent: Intent) { | 49 | fun onNewIntent(intent: Intent) { |
| 47 | val action = intent.action | 50 | val action = intent.action |
| 48 | if (NfcAdapter.ACTION_TAG_DISCOVERED != action | 51 | if (NfcAdapter.ACTION_TAG_DISCOVERED != action && |
| 49 | && NfcAdapter.ACTION_TECH_DISCOVERED != action | 52 | NfcAdapter.ACTION_TECH_DISCOVERED != action && |
| 50 | && NfcAdapter.ACTION_NDEF_DISCOVERED != action | 53 | NfcAdapter.ACTION_NDEF_DISCOVERED != action |
| 51 | ) { | 54 | ) { |
| 52 | return | 55 | return |
| 53 | } | 56 | } |
| @@ -84,7 +87,7 @@ class NfcReader(private val activity: Activity) { | |||
| 84 | } | 87 | } |
| 85 | 88 | ||
| 86 | private fun ntag215ReadAll(amiibo: NfcA): ByteArray? { | 89 | private fun ntag215ReadAll(amiibo: NfcA): ByteArray? { |
| 87 | val bufferSize = amiibo.maxTransceiveLength; | 90 | val bufferSize = amiibo.maxTransceiveLength |
| 88 | val tagSize = 0x21C | 91 | val tagSize = 0x21C |
| 89 | val pageSize = 4 | 92 | val pageSize = 4 |
| 90 | val lastPage = tagSize / pageSize - 1 | 93 | val lastPage = tagSize / pageSize - 1 |
| @@ -103,7 +106,7 @@ class NfcReader(private val activity: Activity) { | |||
| 103 | val data = ntag215FastRead(amiibo, dataStart, dataEnd - 1) | 106 | val data = ntag215FastRead(amiibo, dataStart, dataEnd - 1) |
| 104 | System.arraycopy(data, 0, tagData, i, (dataEnd - dataStart) * pageSize) | 107 | System.arraycopy(data, 0, tagData, i, (dataEnd - dataStart) * pageSize) |
| 105 | } catch (e: IOException) { | 108 | } catch (e: IOException) { |
| 106 | return null; | 109 | return null |
| 107 | } | 110 | } |
| 108 | } | 111 | } |
| 109 | return tagData | 112 | return tagData |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/SerializableHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/SerializableHelper.kt index 87ee7f2e6..00e58faec 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/SerializableHelper.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/SerializableHelper.kt | |||
| @@ -11,30 +11,34 @@ import java.io.Serializable | |||
| 11 | 11 | ||
| 12 | object SerializableHelper { | 12 | object SerializableHelper { |
| 13 | inline fun <reified T : Serializable> Bundle.serializable(key: String): T? { | 13 | inline fun <reified T : Serializable> Bundle.serializable(key: String): T? { |
| 14 | return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) | 14 | return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { |
| 15 | getSerializable(key, T::class.java) | 15 | getSerializable(key, T::class.java) |
| 16 | else | 16 | } else { |
| 17 | getSerializable(key) as? T | 17 | getSerializable(key) as? T |
| 18 | } | ||
| 18 | } | 19 | } |
| 19 | 20 | ||
| 20 | inline fun <reified T : Serializable> Intent.serializable(key: String): T? { | 21 | inline fun <reified T : Serializable> Intent.serializable(key: String): T? { |
| 21 | return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) | 22 | return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { |
| 22 | getSerializableExtra(key, T::class.java) | 23 | getSerializableExtra(key, T::class.java) |
| 23 | else | 24 | } else { |
| 24 | getSerializableExtra(key) as? T | 25 | getSerializableExtra(key) as? T |
| 26 | } | ||
| 25 | } | 27 | } |
| 26 | 28 | ||
| 27 | inline fun <reified T : Parcelable> Bundle.parcelable(key: String): T? { | 29 | inline fun <reified T : Parcelable> Bundle.parcelable(key: String): T? { |
| 28 | return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) | 30 | return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { |
| 29 | getParcelable(key, T::class.java) | 31 | getParcelable(key, T::class.java) |
| 30 | else | 32 | } else { |
| 31 | getParcelable(key) as? T | 33 | getParcelable(key) as? T |
| 34 | } | ||
| 32 | } | 35 | } |
| 33 | 36 | ||
| 34 | inline fun <reified T : Parcelable> Intent.parcelable(key: String): T? { | 37 | inline fun <reified T : Parcelable> Intent.parcelable(key: String): T? { |
| 35 | return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) | 38 | return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { |
| 36 | getParcelableExtra(key, T::class.java) | 39 | getParcelableExtra(key, T::class.java) |
| 37 | else | 40 | } else { |
| 38 | getParcelableExtra(key) as? T | 41 | getParcelableExtra(key) as? T |
| 42 | } | ||
| 39 | } | 43 | } |
| 40 | } | 44 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ThemeHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ThemeHelper.kt index e55767c0f..f312e24cf 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ThemeHelper.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ThemeHelper.kt | |||
| @@ -3,21 +3,19 @@ | |||
| 3 | 3 | ||
| 4 | package org.yuzu.yuzu_emu.utils | 4 | package org.yuzu.yuzu_emu.utils |
| 5 | 5 | ||
| 6 | import android.app.Activity | ||
| 7 | import android.content.res.Configuration | 6 | import android.content.res.Configuration |
| 8 | import android.graphics.Color | 7 | import android.graphics.Color |
| 9 | import androidx.annotation.ColorInt | 8 | import androidx.annotation.ColorInt |
| 10 | import androidx.appcompat.app.AppCompatActivity | 9 | import androidx.appcompat.app.AppCompatActivity |
| 11 | import androidx.appcompat.app.AppCompatDelegate | 10 | import androidx.appcompat.app.AppCompatDelegate |
| 12 | import androidx.core.content.ContextCompat | ||
| 13 | import androidx.core.view.WindowCompat | 11 | import androidx.core.view.WindowCompat |
| 14 | import androidx.core.view.WindowInsetsControllerCompat | 12 | import androidx.core.view.WindowInsetsControllerCompat |
| 15 | import androidx.preference.PreferenceManager | 13 | import androidx.preference.PreferenceManager |
| 14 | import kotlin.math.roundToInt | ||
| 16 | import org.yuzu.yuzu_emu.R | 15 | import org.yuzu.yuzu_emu.R |
| 17 | import org.yuzu.yuzu_emu.YuzuApplication | 16 | import org.yuzu.yuzu_emu.YuzuApplication |
| 18 | import org.yuzu.yuzu_emu.features.settings.model.Settings | 17 | import org.yuzu.yuzu_emu.features.settings.model.Settings |
| 19 | import org.yuzu.yuzu_emu.ui.main.ThemeProvider | 18 | import org.yuzu.yuzu_emu.ui.main.ThemeProvider |
| 20 | import kotlin.math.roundToInt | ||
| 21 | 19 | ||
| 22 | object ThemeHelper { | 20 | object ThemeHelper { |
| 23 | const val SYSTEM_BAR_ALPHA = 0.9f | 21 | const val SYSTEM_BAR_ALPHA = 0.9f |
| @@ -36,8 +34,8 @@ object ThemeHelper { | |||
| 36 | // Using a specific night mode check because this could apply incorrectly when using the | 34 | // Using a specific night mode check because this could apply incorrectly when using the |
| 37 | // light app mode, dark system mode, and black backgrounds. Launching the settings activity | 35 | // light app mode, dark system mode, and black backgrounds. Launching the settings activity |
| 38 | // will then show light mode colors/navigation bars but with black backgrounds. | 36 | // will then show light mode colors/navigation bars but with black backgrounds. |
| 39 | if (preferences.getBoolean(Settings.PREF_BLACK_BACKGROUNDS, false) | 37 | if (preferences.getBoolean(Settings.PREF_BLACK_BACKGROUNDS, false) && |
| 40 | && isNightMode(activity) | 38 | isNightMode(activity) |
| 41 | ) { | 39 | ) { |
| 42 | activity.setTheme(R.style.ThemeOverlay_Yuzu_Dark) | 40 | activity.setTheme(R.style.ThemeOverlay_Yuzu_Dark) |
| 43 | } | 41 | } |
| @@ -46,8 +44,10 @@ object ThemeHelper { | |||
| 46 | @ColorInt | 44 | @ColorInt |
| 47 | fun getColorWithOpacity(@ColorInt color: Int, alphaFactor: Float): Int { | 45 | fun getColorWithOpacity(@ColorInt color: Int, alphaFactor: Float): Int { |
| 48 | return Color.argb( | 46 | return Color.argb( |
| 49 | (alphaFactor * Color.alpha(color)).roundToInt(), Color.red(color), | 47 | (alphaFactor * Color.alpha(color)).roundToInt(), |
| 50 | Color.green(color), Color.blue(color) | 48 | Color.red(color), |
| 49 | Color.green(color), | ||
| 50 | Color.blue(color) | ||
| 51 | ) | 51 | ) |
| 52 | } | 52 | } |
| 53 | 53 | ||
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 d89a89983..685ccaa76 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 | |||
| @@ -38,8 +38,8 @@ class FixedRatioSurfaceView @JvmOverloads constructor( | |||
| 38 | newWidth = width | 38 | newWidth = width |
| 39 | newHeight = (width / aspectRatio).roundToInt() | 39 | newHeight = (width / aspectRatio).roundToInt() |
| 40 | } | 40 | } |
| 41 | val left = (width - newWidth) / 2; | 41 | val left = (width - newWidth) / 2 |
| 42 | val top = (height - newHeight) / 2; | 42 | val top = (height - newHeight) / 2 |
| 43 | setLeftTopRightBottom(left, top, left + newWidth, top + newHeight) | 43 | setLeftTopRightBottom(left, top, left + newWidth, top + newHeight) |
| 44 | } else { | 44 | } else { |
| 45 | setLeftTopRightBottom(0, 0, width, height) | 45 | setLeftTopRightBottom(0, 0, width, height) |
diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index 4091c23d1..f9617202b 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp | |||
| @@ -202,6 +202,11 @@ public: | |||
| 202 | return m_is_running; | 202 | return m_is_running; |
| 203 | } | 203 | } |
| 204 | 204 | ||
| 205 | bool IsPaused() const { | ||
| 206 | std::scoped_lock lock(m_mutex); | ||
| 207 | return m_is_running && m_is_paused; | ||
| 208 | } | ||
| 209 | |||
| 205 | const Core::PerfStatsResults& PerfStats() const { | 210 | const Core::PerfStatsResults& PerfStats() const { |
| 206 | std::scoped_lock m_perf_stats_lock(m_perf_stats_mutex); | 211 | std::scoped_lock m_perf_stats_lock(m_perf_stats_mutex); |
| 207 | return m_perf_stats; | 212 | return m_perf_stats; |
| @@ -287,11 +292,13 @@ public: | |||
| 287 | void PauseEmulation() { | 292 | void PauseEmulation() { |
| 288 | std::scoped_lock lock(m_mutex); | 293 | std::scoped_lock lock(m_mutex); |
| 289 | m_system.Pause(); | 294 | m_system.Pause(); |
| 295 | m_is_paused = true; | ||
| 290 | } | 296 | } |
| 291 | 297 | ||
| 292 | void UnPauseEmulation() { | 298 | void UnPauseEmulation() { |
| 293 | std::scoped_lock lock(m_mutex); | 299 | std::scoped_lock lock(m_mutex); |
| 294 | m_system.Run(); | 300 | m_system.Run(); |
| 301 | m_is_paused = false; | ||
| 295 | } | 302 | } |
| 296 | 303 | ||
| 297 | void HaltEmulation() { | 304 | void HaltEmulation() { |
| @@ -473,6 +480,7 @@ private: | |||
| 473 | std::shared_ptr<FileSys::VfsFilesystem> m_vfs; | 480 | std::shared_ptr<FileSys::VfsFilesystem> m_vfs; |
| 474 | Core::SystemResultStatus m_load_result{Core::SystemResultStatus::ErrorNotInitialized}; | 481 | Core::SystemResultStatus m_load_result{Core::SystemResultStatus::ErrorNotInitialized}; |
| 475 | bool m_is_running{}; | 482 | bool m_is_running{}; |
| 483 | bool m_is_paused{}; | ||
| 476 | SoftwareKeyboard::AndroidKeyboard* m_software_keyboard{}; | 484 | SoftwareKeyboard::AndroidKeyboard* m_software_keyboard{}; |
| 477 | std::unique_ptr<Service::Account::ProfileManager> m_profile_manager; | 485 | std::unique_ptr<Service::Account::ProfileManager> m_profile_manager; |
| 478 | 486 | ||
| @@ -583,6 +591,11 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isRunning([[maybe_unused]] JNIEnv | |||
| 583 | return static_cast<jboolean>(EmulationSession::GetInstance().IsRunning()); | 591 | return static_cast<jboolean>(EmulationSession::GetInstance().IsRunning()); |
| 584 | } | 592 | } |
| 585 | 593 | ||
| 594 | jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isPaused([[maybe_unused]] JNIEnv* env, | ||
| 595 | [[maybe_unused]] jclass clazz) { | ||
| 596 | return static_cast<jboolean>(EmulationSession::GetInstance().IsPaused()); | ||
| 597 | } | ||
| 598 | |||
| 586 | jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isHandheldOnly([[maybe_unused]] JNIEnv* env, | 599 | jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isHandheldOnly([[maybe_unused]] JNIEnv* env, |
| 587 | [[maybe_unused]] jclass clazz) { | 600 | [[maybe_unused]] jclass clazz) { |
| 588 | return EmulationSession::GetInstance().IsHandheldOnly(); | 601 | return EmulationSession::GetInstance().IsHandheldOnly(); |
diff --git a/src/android/app/src/main/res/drawable/ic_pip_pause.xml b/src/android/app/src/main/res/drawable/ic_pip_pause.xml new file mode 100644 index 000000000..4a7d4ea03 --- /dev/null +++ b/src/android/app/src/main/res/drawable/ic_pip_pause.xml | |||
| @@ -0,0 +1,9 @@ | |||
| 1 | <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||
| 2 | android:width="24dp" | ||
| 3 | android:height="24dp" | ||
| 4 | android:viewportHeight="24" | ||
| 5 | android:viewportWidth="24"> | ||
| 6 | <path | ||
| 7 | android:fillColor="@android:color/white" | ||
| 8 | android:pathData="M6,19h4L10,5L6,5v14zM14,5v14h4L18,5h-4z" /> | ||
| 9 | </vector> | ||
diff --git a/src/android/app/src/main/res/drawable/ic_pip_play.xml b/src/android/app/src/main/res/drawable/ic_pip_play.xml new file mode 100644 index 000000000..2303a4623 --- /dev/null +++ b/src/android/app/src/main/res/drawable/ic_pip_play.xml | |||
| @@ -0,0 +1,9 @@ | |||
| 1 | <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||
| 2 | android:width="24dp" | ||
| 3 | android:height="24dp" | ||
| 4 | android:viewportHeight="24" | ||
| 5 | android:viewportWidth="24"> | ||
| 6 | <path | ||
| 7 | android:fillColor="@android:color/white" | ||
| 8 | android:pathData="M8,5v14l11,-7z" /> | ||
| 9 | </vector> | ||
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 09b789b6b..e54a10e8f 100644 --- a/src/android/app/src/main/res/layout/fragment_emulation.xml +++ b/src/android/app/src/main/res/layout/fragment_emulation.xml | |||
| @@ -12,49 +12,65 @@ | |||
| 12 | android:layout_width="match_parent" | 12 | android:layout_width="match_parent" |
| 13 | android:layout_height="match_parent"> | 13 | android:layout_height="match_parent"> |
| 14 | 14 | ||
| 15 | <!-- This is what everything is rendered to during emulation --> | 15 | <FrameLayout |
| 16 | <org.yuzu.yuzu_emu.views.FixedRatioSurfaceView | 16 | android:id="@+id/emulation_container" |
| 17 | android:id="@+id/surface_emulation" | ||
| 18 | android:layout_width="match_parent" | 17 | android:layout_width="match_parent" |
| 19 | android:layout_height="match_parent" | 18 | android:layout_height="match_parent"> |
| 20 | android:layout_gravity="center" | 19 | |
| 21 | android:focusable="false" | 20 | <!-- This is what everything is rendered to during emulation --> |
| 22 | android:focusableInTouchMode="false" /> | 21 | <org.yuzu.yuzu_emu.views.FixedRatioSurfaceView |
| 22 | android:id="@+id/surface_emulation" | ||
| 23 | android:layout_width="match_parent" | ||
| 24 | android:layout_height="match_parent" | ||
| 25 | android:layout_gravity="center" | ||
| 26 | android:focusable="false" | ||
| 27 | android:focusableInTouchMode="false" /> | ||
| 28 | |||
| 29 | </FrameLayout> | ||
| 23 | 30 | ||
| 24 | <FrameLayout | 31 | <FrameLayout |
| 25 | android:id="@+id/overlay_container" | 32 | android:id="@+id/input_container" |
| 26 | android:layout_width="match_parent" | 33 | android:layout_width="match_parent" |
| 27 | android:layout_height="match_parent" | 34 | android:layout_height="match_parent" |
| 28 | android:layout_gravity="bottom"> | 35 | android:layout_gravity="bottom"> |
| 29 | 36 | ||
| 30 | <!-- This is the onscreen input overlay --> | 37 | <!-- This is the onscreen input overlay --> |
| 31 | <org.yuzu.yuzu_emu.overlay.InputOverlay | 38 | <org.yuzu.yuzu_emu.overlay.InputOverlay |
| 32 | android:id="@+id/surface_input_overlay" | 39 | android:id="@+id/surface_input_overlay" |
| 40 | android:layout_width="match_parent" | ||
| 41 | android:layout_height="match_parent" | ||
| 42 | android:layout_gravity="center" | ||
| 43 | android:focusable="true" | ||
| 44 | android:focusableInTouchMode="true" /> | ||
| 45 | |||
| 46 | <Button | ||
| 47 | style="@style/Widget.Material3.Button.ElevatedButton" | ||
| 48 | android:id="@+id/done_control_config" | ||
| 49 | android:layout_width="wrap_content" | ||
| 50 | android:layout_height="wrap_content" | ||
| 51 | android:layout_gravity="center" | ||
| 52 | android:text="@string/emulation_done" | ||
| 53 | android:visibility="gone" /> | ||
| 54 | |||
| 55 | </FrameLayout> | ||
| 56 | |||
| 57 | <FrameLayout | ||
| 58 | android:id="@+id/overlay_container" | ||
| 33 | android:layout_width="match_parent" | 59 | android:layout_width="match_parent" |
| 34 | android:layout_height="match_parent" | 60 | android:layout_height="match_parent"> |
| 35 | android:focusable="true" | ||
| 36 | android:focusableInTouchMode="true" /> | ||
| 37 | 61 | ||
| 38 | <TextView | 62 | <TextView |
| 39 | android:id="@+id/show_fps_text" | 63 | android:id="@+id/show_fps_text" |
| 40 | android:layout_width="wrap_content" | 64 | android:layout_width="wrap_content" |
| 41 | android:layout_height="wrap_content" | 65 | android:layout_height="wrap_content" |
| 42 | android:layout_gravity="left" | 66 | android:layout_gravity="left" |
| 43 | android:clickable="false" | 67 | android:clickable="false" |
| 44 | android:focusable="false" | 68 | android:focusable="false" |
| 45 | android:shadowColor="@android:color/black" | 69 | android:shadowColor="@android:color/black" |
| 46 | android:textColor="@android:color/white" | 70 | android:textColor="@android:color/white" |
| 47 | android:textSize="12sp" | 71 | android:textSize="12sp" |
| 48 | tools:ignore="RtlHardcoded" /> | 72 | tools:ignore="RtlHardcoded" /> |
| 49 | 73 | ||
| 50 | <Button | ||
| 51 | style="@style/Widget.Material3.Button.ElevatedButton" | ||
| 52 | android:id="@+id/done_control_config" | ||
| 53 | android:layout_width="wrap_content" | ||
| 54 | android:layout_height="wrap_content" | ||
| 55 | android:layout_gravity="center" | ||
| 56 | android:text="@string/emulation_done" | ||
| 57 | android:visibility="gone" /> | ||
| 58 | </FrameLayout> | 74 | </FrameLayout> |
| 59 | 75 | ||
| 60 | </androidx.coordinatorlayout.widget.CoordinatorLayout> | 76 | </androidx.coordinatorlayout.widget.CoordinatorLayout> |
diff --git a/src/android/app/src/main/res/values/arrays.xml b/src/android/app/src/main/res/values/arrays.xml index ea20cb17c..7f7b1938c 100644 --- a/src/android/app/src/main/res/values/arrays.xml +++ b/src/android/app/src/main/res/values/arrays.xml | |||
| @@ -119,6 +119,18 @@ | |||
| 119 | <item>3</item> | 119 | <item>3</item> |
| 120 | </integer-array> | 120 | </integer-array> |
| 121 | 121 | ||
| 122 | <string-array name="rendererScreenLayoutNames"> | ||
| 123 | <item>@string/screen_layout_landscape</item> | ||
| 124 | <item>@string/screen_layout_portrait</item> | ||
| 125 | <item>@string/screen_layout_auto</item> | ||
| 126 | </string-array> | ||
| 127 | |||
| 128 | <integer-array name="rendererScreenLayoutValues"> | ||
| 129 | <item>5</item> | ||
| 130 | <item>4</item> | ||
| 131 | <item>0</item> | ||
| 132 | </integer-array> | ||
| 133 | |||
| 122 | <string-array name="rendererAspectRatioNames"> | 134 | <string-array name="rendererAspectRatioNames"> |
| 123 | <item>@string/ratio_default</item> | 135 | <item>@string/ratio_default</item> |
| 124 | <item>@string/ratio_force_four_three</item> | 136 | <item>@string/ratio_force_four_three</item> |
diff --git a/src/android/app/src/main/res/values/integers.xml b/src/android/app/src/main/res/values/integers.xml index bc614b81d..2e93b408c 100644 --- a/src/android/app/src/main/res/values/integers.xml +++ b/src/android/app/src/main/res/values/integers.xml | |||
| @@ -34,4 +34,68 @@ | |||
| 34 | <integer name="SWITCH_BUTTON_DPAD_X">260</integer> | 34 | <integer name="SWITCH_BUTTON_DPAD_X">260</integer> |
| 35 | <integer name="SWITCH_BUTTON_DPAD_Y">790</integer> | 35 | <integer name="SWITCH_BUTTON_DPAD_Y">790</integer> |
| 36 | 36 | ||
| 37 | <!-- Default SWITCH portrait layout --> | ||
| 38 | <integer name="SWITCH_BUTTON_A_X_PORTRAIT">840</integer> | ||
| 39 | <integer name="SWITCH_BUTTON_A_Y_PORTRAIT">840</integer> | ||
| 40 | <integer name="SWITCH_BUTTON_B_X_PORTRAIT">740</integer> | ||
| 41 | <integer name="SWITCH_BUTTON_B_Y_PORTRAIT">880</integer> | ||
| 42 | <integer name="SWITCH_BUTTON_X_X_PORTRAIT">740</integer> | ||
| 43 | <integer name="SWITCH_BUTTON_X_Y_PORTRAIT">800</integer> | ||
| 44 | <integer name="SWITCH_BUTTON_Y_X_PORTRAIT">640</integer> | ||
| 45 | <integer name="SWITCH_BUTTON_Y_Y_PORTRAIT">840</integer> | ||
| 46 | <integer name="SWITCH_STICK_L_X_PORTRAIT">180</integer> | ||
| 47 | <integer name="SWITCH_STICK_L_Y_PORTRAIT">660</integer> | ||
| 48 | <integer name="SWITCH_STICK_R_X_PORTRAIT">820</integer> | ||
| 49 | <integer name="SWITCH_STICK_R_Y_PORTRAIT">660</integer> | ||
| 50 | <integer name="SWITCH_TRIGGER_L_X_PORTRAIT">140</integer> | ||
| 51 | <integer name="SWITCH_TRIGGER_L_Y_PORTRAIT">260</integer> | ||
| 52 | <integer name="SWITCH_TRIGGER_R_X_PORTRAIT">860</integer> | ||
| 53 | <integer name="SWITCH_TRIGGER_R_Y_PORTRAIT">260</integer> | ||
| 54 | <integer name="SWITCH_TRIGGER_ZL_X_PORTRAIT">140</integer> | ||
| 55 | <integer name="SWITCH_TRIGGER_ZL_Y_PORTRAIT">200</integer> | ||
| 56 | <integer name="SWITCH_TRIGGER_ZR_X_PORTRAIT">860</integer> | ||
| 57 | <integer name="SWITCH_TRIGGER_ZR_Y_PORTRAIT">200</integer> | ||
| 58 | <integer name="SWITCH_BUTTON_MINUS_X_PORTRAIT">440</integer> | ||
| 59 | <integer name="SWITCH_BUTTON_MINUS_Y_PORTRAIT">950</integer> | ||
| 60 | <integer name="SWITCH_BUTTON_PLUS_X_PORTRAIT">560</integer> | ||
| 61 | <integer name="SWITCH_BUTTON_PLUS_Y_PORTRAIT">950</integer> | ||
| 62 | <integer name="SWITCH_BUTTON_HOME_X_PORTRAIT">680</integer> | ||
| 63 | <integer name="SWITCH_BUTTON_HOME_Y_PORTRAIT">950</integer> | ||
| 64 | <integer name="SWITCH_BUTTON_CAPTURE_X_PORTRAIT">320</integer> | ||
| 65 | <integer name="SWITCH_BUTTON_CAPTURE_Y_PORTRAIT">950</integer> | ||
| 66 | <integer name="SWITCH_BUTTON_DPAD_X_PORTRAIT">240</integer> | ||
| 67 | <integer name="SWITCH_BUTTON_DPAD_Y_PORTRAIT">840</integer> | ||
| 68 | |||
| 69 | <!-- Default SWITCH foldable layout --> | ||
| 70 | <integer name="SWITCH_BUTTON_A_X_FOLDABLE">840</integer> | ||
| 71 | <integer name="SWITCH_BUTTON_A_Y_FOLDABLE">390</integer> | ||
| 72 | <integer name="SWITCH_BUTTON_B_X_FOLDABLE">740</integer> | ||
| 73 | <integer name="SWITCH_BUTTON_B_Y_FOLDABLE">430</integer> | ||
| 74 | <integer name="SWITCH_BUTTON_X_X_FOLDABLE">740</integer> | ||
| 75 | <integer name="SWITCH_BUTTON_X_Y_FOLDABLE">350</integer> | ||
| 76 | <integer name="SWITCH_BUTTON_Y_X_FOLDABLE">640</integer> | ||
| 77 | <integer name="SWITCH_BUTTON_Y_Y_FOLDABLE">390</integer> | ||
| 78 | <integer name="SWITCH_STICK_L_X_FOLDABLE">180</integer> | ||
| 79 | <integer name="SWITCH_STICK_L_Y_FOLDABLE">250</integer> | ||
| 80 | <integer name="SWITCH_STICK_R_X_FOLDABLE">820</integer> | ||
| 81 | <integer name="SWITCH_STICK_R_Y_FOLDABLE">250</integer> | ||
| 82 | <integer name="SWITCH_TRIGGER_L_X_FOLDABLE">140</integer> | ||
| 83 | <integer name="SWITCH_TRIGGER_L_Y_FOLDABLE">130</integer> | ||
| 84 | <integer name="SWITCH_TRIGGER_R_X_FOLDABLE">860</integer> | ||
| 85 | <integer name="SWITCH_TRIGGER_R_Y_FOLDABLE">130</integer> | ||
| 86 | <integer name="SWITCH_TRIGGER_ZL_X_FOLDABLE">140</integer> | ||
| 87 | <integer name="SWITCH_TRIGGER_ZL_Y_FOLDABLE">70</integer> | ||
| 88 | <integer name="SWITCH_TRIGGER_ZR_X_FOLDABLE">860</integer> | ||
| 89 | <integer name="SWITCH_TRIGGER_ZR_Y_FOLDABLE">70</integer> | ||
| 90 | <integer name="SWITCH_BUTTON_MINUS_X_FOLDABLE">440</integer> | ||
| 91 | <integer name="SWITCH_BUTTON_MINUS_Y_FOLDABLE">470</integer> | ||
| 92 | <integer name="SWITCH_BUTTON_PLUS_X_FOLDABLE">560</integer> | ||
| 93 | <integer name="SWITCH_BUTTON_PLUS_Y_FOLDABLE">470</integer> | ||
| 94 | <integer name="SWITCH_BUTTON_HOME_X_FOLDABLE">680</integer> | ||
| 95 | <integer name="SWITCH_BUTTON_HOME_Y_FOLDABLE">470</integer> | ||
| 96 | <integer name="SWITCH_BUTTON_CAPTURE_X_FOLDABLE">320</integer> | ||
| 97 | <integer name="SWITCH_BUTTON_CAPTURE_Y_FOLDABLE">470</integer> | ||
| 98 | <integer name="SWITCH_BUTTON_DPAD_X_FOLDABLE">240</integer> | ||
| 99 | <integer name="SWITCH_BUTTON_DPAD_Y_FOLDABLE">390</integer> | ||
| 100 | |||
| 37 | </resources> | 101 | </resources> |
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index c236811fa..2f2059d42 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml | |||
| @@ -1,5 +1,5 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | 1 | <?xml version="1.0" encoding="utf-8"?> |
| 2 | <resources> | 2 | <resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation"> |
| 3 | 3 | ||
| 4 | <!-- General application strings --> | 4 | <!-- General application strings --> |
| 5 | <string name="app_name" translatable="false">yuzu</string> | 5 | <string name="app_name" translatable="false">yuzu</string> |
| @@ -162,6 +162,7 @@ | |||
| 162 | <string name="renderer_accuracy">Accuracy level</string> | 162 | <string name="renderer_accuracy">Accuracy level</string> |
| 163 | <string name="renderer_resolution">Resolution (Handheld/Docked)</string> | 163 | <string name="renderer_resolution">Resolution (Handheld/Docked)</string> |
| 164 | <string name="renderer_vsync">VSync mode</string> | 164 | <string name="renderer_vsync">VSync mode</string> |
| 165 | <string name="renderer_screen_layout">Orientation</string> | ||
| 165 | <string name="renderer_aspect_ratio">Aspect ratio</string> | 166 | <string name="renderer_aspect_ratio">Aspect ratio</string> |
| 166 | <string name="renderer_scaling_filter">Window adapting filter</string> | 167 | <string name="renderer_scaling_filter">Window adapting filter</string> |
| 167 | <string name="renderer_anti_aliasing">Anti-aliasing method</string> | 168 | <string name="renderer_anti_aliasing">Anti-aliasing method</string> |
| @@ -326,6 +327,11 @@ | |||
| 326 | <string name="anti_aliasing_fxaa">FXAA</string> | 327 | <string name="anti_aliasing_fxaa">FXAA</string> |
| 327 | <string name="anti_aliasing_smaa">SMAA</string> | 328 | <string name="anti_aliasing_smaa">SMAA</string> |
| 328 | 329 | ||
| 330 | <!-- Screen Layouts --> | ||
| 331 | <string name="screen_layout_landscape">Landscape</string> | ||
| 332 | <string name="screen_layout_portrait">Portrait</string> | ||
| 333 | <string name="screen_layout_auto">Auto</string> | ||
| 334 | |||
| 329 | <!-- Aspect Ratios --> | 335 | <!-- Aspect Ratios --> |
| 330 | <string name="ratio_default">Default (16:9)</string> | 336 | <string name="ratio_default">Default (16:9)</string> |
| 331 | <string name="ratio_force_four_three">Force 4:3</string> | 337 | <string name="ratio_force_four_three">Force 4:3</string> |
| @@ -364,6 +370,12 @@ | |||
| 364 | <string name="use_black_backgrounds">Black backgrounds</string> | 370 | <string name="use_black_backgrounds">Black backgrounds</string> |
| 365 | <string name="use_black_backgrounds_description">When using the dark theme, apply black backgrounds.</string> | 371 | <string name="use_black_backgrounds_description">When using the dark theme, apply black backgrounds.</string> |
| 366 | 372 | ||
| 373 | <!-- Picture-In-Picture --> | ||
| 374 | <string name="picture_in_picture">Picture in Picture</string> | ||
| 375 | <string name="picture_in_picture_description">Minimize window when placed in the background</string> | ||
| 376 | <string name="pause">Pause</string> | ||
| 377 | <string name="play">Play</string> | ||
| 378 | |||
| 367 | <!-- Licenses screen strings --> | 379 | <!-- Licenses screen strings --> |
| 368 | <string name="licenses">Licenses</string> | 380 | <string name="licenses">Licenses</string> |
| 369 | <string name="license_fidelityfx_fsr" translatable="false">FidelityFX-FSR</string> | 381 | <string name="license_fidelityfx_fsr" translatable="false">FidelityFX-FSR</string> |
diff --git a/src/common/fs/fs.cpp b/src/common/fs/fs.cpp index e1716c62d..6d66c926d 100644 --- a/src/common/fs/fs.cpp +++ b/src/common/fs/fs.cpp | |||
| @@ -3,6 +3,9 @@ | |||
| 3 | 3 | ||
| 4 | #include "common/fs/file.h" | 4 | #include "common/fs/file.h" |
| 5 | #include "common/fs/fs.h" | 5 | #include "common/fs/fs.h" |
| 6 | #ifdef ANDROID | ||
| 7 | #include "common/fs/fs_android.h" | ||
| 8 | #endif | ||
| 6 | #include "common/fs/path_util.h" | 9 | #include "common/fs/path_util.h" |
| 7 | #include "common/logging/log.h" | 10 | #include "common/logging/log.h" |
| 8 | 11 | ||
| @@ -525,15 +528,39 @@ void IterateDirEntriesRecursively(const std::filesystem::path& path, | |||
| 525 | // Generic Filesystem Operations | 528 | // Generic Filesystem Operations |
| 526 | 529 | ||
| 527 | bool Exists(const fs::path& path) { | 530 | bool Exists(const fs::path& path) { |
| 531 | #ifdef ANDROID | ||
| 532 | if (Android::IsContentUri(path)) { | ||
| 533 | return Android::Exists(path); | ||
| 534 | } else { | ||
| 535 | return fs::exists(path); | ||
| 536 | } | ||
| 537 | #else | ||
| 528 | return fs::exists(path); | 538 | return fs::exists(path); |
| 539 | #endif | ||
| 529 | } | 540 | } |
| 530 | 541 | ||
| 531 | bool IsFile(const fs::path& path) { | 542 | bool IsFile(const fs::path& path) { |
| 543 | #ifdef ANDROID | ||
| 544 | if (Android::IsContentUri(path)) { | ||
| 545 | return !Android::IsDirectory(path); | ||
| 546 | } else { | ||
| 547 | return fs::is_regular_file(path); | ||
| 548 | } | ||
| 549 | #else | ||
| 532 | return fs::is_regular_file(path); | 550 | return fs::is_regular_file(path); |
| 551 | #endif | ||
| 533 | } | 552 | } |
| 534 | 553 | ||
| 535 | bool IsDir(const fs::path& path) { | 554 | bool IsDir(const fs::path& path) { |
| 555 | #ifdef ANDROID | ||
| 556 | if (Android::IsContentUri(path)) { | ||
| 557 | return Android::IsDirectory(path); | ||
| 558 | } else { | ||
| 559 | return fs::is_directory(path); | ||
| 560 | } | ||
| 561 | #else | ||
| 536 | return fs::is_directory(path); | 562 | return fs::is_directory(path); |
| 563 | #endif | ||
| 537 | } | 564 | } |
| 538 | 565 | ||
| 539 | fs::path GetCurrentDir() { | 566 | fs::path GetCurrentDir() { |
diff --git a/src/common/fs/fs_android.h b/src/common/fs/fs_android.h index bb8a52648..b441c2a12 100644 --- a/src/common/fs/fs_android.h +++ b/src/common/fs/fs_android.h | |||
| @@ -12,7 +12,10 @@ | |||
| 12 | "openContentUri", "(Ljava/lang/String;Ljava/lang/String;)I") | 12 | "openContentUri", "(Ljava/lang/String;Ljava/lang/String;)I") |
| 13 | 13 | ||
| 14 | #define ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(V) \ | 14 | #define ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(V) \ |
| 15 | V(GetSize, std::uint64_t, get_size, CallStaticLongMethod, "getSize", "(Ljava/lang/String;)J") | 15 | V(GetSize, std::uint64_t, get_size, CallStaticLongMethod, "getSize", "(Ljava/lang/String;)J") \ |
| 16 | V(IsDirectory, bool, is_directory, CallStaticBooleanMethod, "isDirectory", \ | ||
| 17 | "(Ljava/lang/String;)Z") \ | ||
| 18 | V(Exists, bool, file_exists, CallStaticBooleanMethod, "exists", "(Ljava/lang/String;)Z") | ||
| 16 | 19 | ||
| 17 | namespace Common::FS::Android { | 20 | namespace Common::FS::Android { |
| 18 | 21 | ||
diff --git a/src/core/file_sys/vfs_real.cpp b/src/core/file_sys/vfs_real.cpp index cc0076238..7a15d8438 100644 --- a/src/core/file_sys/vfs_real.cpp +++ b/src/core/file_sys/vfs_real.cpp | |||
| @@ -25,6 +25,8 @@ namespace FS = Common::FS; | |||
| 25 | 25 | ||
| 26 | namespace { | 26 | namespace { |
| 27 | 27 | ||
| 28 | constexpr size_t MaxOpenFiles = 512; | ||
| 29 | |||
| 28 | constexpr FS::FileAccessMode ModeFlagsToFileAccessMode(Mode mode) { | 30 | constexpr FS::FileAccessMode ModeFlagsToFileAccessMode(Mode mode) { |
| 29 | switch (mode) { | 31 | switch (mode) { |
| 30 | case Mode::Read: | 32 | case Mode::Read: |
| @@ -73,28 +75,30 @@ VfsEntryType RealVfsFilesystem::GetEntryType(std::string_view path_) const { | |||
| 73 | VirtualFile RealVfsFilesystem::OpenFile(std::string_view path_, Mode perms) { | 75 | VirtualFile RealVfsFilesystem::OpenFile(std::string_view path_, Mode perms) { |
| 74 | const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); | 76 | const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); |
| 75 | 77 | ||
| 76 | if (const auto weak_iter = cache.find(path); weak_iter != cache.cend()) { | 78 | if (auto it = cache.find(path); it != cache.end()) { |
| 77 | const auto& weak = weak_iter->second; | 79 | if (auto file = it->second.lock(); file) { |
| 78 | 80 | return file; | |
| 79 | if (!weak.expired()) { | ||
| 80 | return std::shared_ptr<RealVfsFile>(new RealVfsFile(*this, weak.lock(), path, perms)); | ||
| 81 | } | 81 | } |
| 82 | } | 82 | } |
| 83 | 83 | ||
| 84 | auto backing = FS::FileOpen(path, ModeFlagsToFileAccessMode(perms), FS::FileType::BinaryFile); | 84 | if (!FS::Exists(path) || !FS::IsFile(path)) { |
| 85 | |||
| 86 | if (!backing) { | ||
| 87 | return nullptr; | 85 | return nullptr; |
| 88 | } | 86 | } |
| 89 | 87 | ||
| 90 | cache.insert_or_assign(path, std::move(backing)); | 88 | auto reference = std::make_unique<FileReference>(); |
| 89 | this->InsertReferenceIntoList(*reference); | ||
| 91 | 90 | ||
| 92 | // Cannot use make_shared as RealVfsFile constructor is private | 91 | auto file = |
| 93 | return std::shared_ptr<RealVfsFile>(new RealVfsFile(*this, backing, path, perms)); | 92 | std::shared_ptr<RealVfsFile>(new RealVfsFile(*this, std::move(reference), path, perms)); |
| 93 | cache[path] = file; | ||
| 94 | |||
| 95 | return file; | ||
| 94 | } | 96 | } |
| 95 | 97 | ||
| 96 | VirtualFile RealVfsFilesystem::CreateFile(std::string_view path_, Mode perms) { | 98 | VirtualFile RealVfsFilesystem::CreateFile(std::string_view path_, Mode perms) { |
| 97 | const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); | 99 | const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); |
| 100 | cache.erase(path); | ||
| 101 | |||
| 98 | // Current usages of CreateFile expect to delete the contents of an existing file. | 102 | // Current usages of CreateFile expect to delete the contents of an existing file. |
| 99 | if (FS::IsFile(path)) { | 103 | if (FS::IsFile(path)) { |
| 100 | FS::IOFile temp{path, FS::FileAccessMode::Write, FS::FileType::BinaryFile}; | 104 | FS::IOFile temp{path, FS::FileAccessMode::Write, FS::FileType::BinaryFile}; |
| @@ -123,51 +127,22 @@ VirtualFile RealVfsFilesystem::CopyFile(std::string_view old_path_, std::string_ | |||
| 123 | VirtualFile RealVfsFilesystem::MoveFile(std::string_view old_path_, std::string_view new_path_) { | 127 | VirtualFile RealVfsFilesystem::MoveFile(std::string_view old_path_, std::string_view new_path_) { |
| 124 | const auto old_path = FS::SanitizePath(old_path_, FS::DirectorySeparator::PlatformDefault); | 128 | const auto old_path = FS::SanitizePath(old_path_, FS::DirectorySeparator::PlatformDefault); |
| 125 | const auto new_path = FS::SanitizePath(new_path_, FS::DirectorySeparator::PlatformDefault); | 129 | const auto new_path = FS::SanitizePath(new_path_, FS::DirectorySeparator::PlatformDefault); |
| 126 | const auto cached_file_iter = cache.find(old_path); | 130 | cache.erase(old_path); |
| 127 | 131 | cache.erase(new_path); | |
| 128 | if (cached_file_iter != cache.cend()) { | 132 | if (!FS::RenameFile(old_path, new_path)) { |
| 129 | auto file = cached_file_iter->second.lock(); | ||
| 130 | |||
| 131 | if (!cached_file_iter->second.expired()) { | ||
| 132 | file->Close(); | ||
| 133 | } | ||
| 134 | |||
| 135 | if (!FS::RenameFile(old_path, new_path)) { | ||
| 136 | return nullptr; | ||
| 137 | } | ||
| 138 | |||
| 139 | cache.erase(old_path); | ||
| 140 | file->Open(new_path, FS::FileAccessMode::Read, FS::FileType::BinaryFile); | ||
| 141 | if (file->IsOpen()) { | ||
| 142 | cache.insert_or_assign(new_path, std::move(file)); | ||
| 143 | } else { | ||
| 144 | LOG_ERROR(Service_FS, "Failed to open path {} in order to re-cache it", new_path); | ||
| 145 | } | ||
| 146 | } else { | ||
| 147 | ASSERT(false); | ||
| 148 | return nullptr; | 133 | return nullptr; |
| 149 | } | 134 | } |
| 150 | |||
| 151 | return OpenFile(new_path, Mode::ReadWrite); | 135 | return OpenFile(new_path, Mode::ReadWrite); |
| 152 | } | 136 | } |
| 153 | 137 | ||
| 154 | bool RealVfsFilesystem::DeleteFile(std::string_view path_) { | 138 | bool RealVfsFilesystem::DeleteFile(std::string_view path_) { |
| 155 | const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); | 139 | const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); |
| 156 | const auto cached_iter = cache.find(path); | 140 | cache.erase(path); |
| 157 | |||
| 158 | if (cached_iter != cache.cend()) { | ||
| 159 | if (!cached_iter->second.expired()) { | ||
| 160 | cached_iter->second.lock()->Close(); | ||
| 161 | } | ||
| 162 | cache.erase(path); | ||
| 163 | } | ||
| 164 | |||
| 165 | return FS::RemoveFile(path); | 141 | return FS::RemoveFile(path); |
| 166 | } | 142 | } |
| 167 | 143 | ||
| 168 | VirtualDir RealVfsFilesystem::OpenDirectory(std::string_view path_, Mode perms) { | 144 | VirtualDir RealVfsFilesystem::OpenDirectory(std::string_view path_, Mode perms) { |
| 169 | const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); | 145 | const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); |
| 170 | // Cannot use make_shared as RealVfsDirectory constructor is private | ||
| 171 | return std::shared_ptr<RealVfsDirectory>(new RealVfsDirectory(*this, path, perms)); | 146 | return std::shared_ptr<RealVfsDirectory>(new RealVfsDirectory(*this, path, perms)); |
| 172 | } | 147 | } |
| 173 | 148 | ||
| @@ -176,7 +151,6 @@ VirtualDir RealVfsFilesystem::CreateDirectory(std::string_view path_, Mode perms | |||
| 176 | if (!FS::CreateDirs(path)) { | 151 | if (!FS::CreateDirs(path)) { |
| 177 | return nullptr; | 152 | return nullptr; |
| 178 | } | 153 | } |
| 179 | // Cannot use make_shared as RealVfsDirectory constructor is private | ||
| 180 | return std::shared_ptr<RealVfsDirectory>(new RealVfsDirectory(*this, path, perms)); | 154 | return std::shared_ptr<RealVfsDirectory>(new RealVfsDirectory(*this, path, perms)); |
| 181 | } | 155 | } |
| 182 | 156 | ||
| @@ -194,73 +168,102 @@ VirtualDir RealVfsFilesystem::MoveDirectory(std::string_view old_path_, | |||
| 194 | if (!FS::RenameDir(old_path, new_path)) { | 168 | if (!FS::RenameDir(old_path, new_path)) { |
| 195 | return nullptr; | 169 | return nullptr; |
| 196 | } | 170 | } |
| 171 | return OpenDirectory(new_path, Mode::ReadWrite); | ||
| 172 | } | ||
| 197 | 173 | ||
| 198 | for (auto& kv : cache) { | 174 | bool RealVfsFilesystem::DeleteDirectory(std::string_view path_) { |
| 199 | // If the path in the cache doesn't start with old_path, then bail on this file. | 175 | const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); |
| 200 | if (kv.first.rfind(old_path, 0) != 0) { | 176 | return FS::RemoveDirRecursively(path); |
| 201 | continue; | 177 | } |
| 202 | } | ||
| 203 | 178 | ||
| 204 | const auto file_old_path = | 179 | void RealVfsFilesystem::RefreshReference(const std::string& path, Mode perms, |
| 205 | FS::SanitizePath(kv.first, FS::DirectorySeparator::PlatformDefault); | 180 | FileReference& reference) { |
| 206 | auto file_new_path = FS::SanitizePath(new_path + '/' + kv.first.substr(old_path.size()), | 181 | // Temporarily remove from list. |
| 207 | FS::DirectorySeparator::PlatformDefault); | 182 | this->RemoveReferenceFromList(reference); |
| 208 | const auto& cached = cache[file_old_path]; | ||
| 209 | 183 | ||
| 210 | if (cached.expired()) { | 184 | // Restore file if needed. |
| 211 | continue; | 185 | if (!reference.file) { |
| 212 | } | 186 | this->EvictSingleReference(); |
| 213 | 187 | ||
| 214 | auto file = cached.lock(); | 188 | reference.file = |
| 215 | cache.erase(file_old_path); | 189 | FS::FileOpen(path, ModeFlagsToFileAccessMode(perms), FS::FileType::BinaryFile); |
| 216 | file->Open(file_new_path, FS::FileAccessMode::Read, FS::FileType::BinaryFile); | 190 | if (reference.file) { |
| 217 | if (file->IsOpen()) { | 191 | num_open_files++; |
| 218 | cache.insert_or_assign(std::move(file_new_path), std::move(file)); | ||
| 219 | } else { | ||
| 220 | LOG_ERROR(Service_FS, "Failed to open path {} in order to re-cache it", file_new_path); | ||
| 221 | } | 192 | } |
| 222 | } | 193 | } |
| 223 | 194 | ||
| 224 | return OpenDirectory(new_path, Mode::ReadWrite); | 195 | // Reinsert into list. |
| 196 | this->InsertReferenceIntoList(reference); | ||
| 225 | } | 197 | } |
| 226 | 198 | ||
| 227 | bool RealVfsFilesystem::DeleteDirectory(std::string_view path_) { | 199 | void RealVfsFilesystem::DropReference(std::unique_ptr<FileReference>&& reference) { |
| 228 | const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); | 200 | // Remove from list. |
| 201 | this->RemoveReferenceFromList(*reference); | ||
| 229 | 202 | ||
| 230 | for (auto& kv : cache) { | 203 | // Close the file. |
| 231 | // If the path in the cache doesn't start with path, then bail on this file. | 204 | if (reference->file) { |
| 232 | if (kv.first.rfind(path, 0) != 0) { | 205 | reference->file.reset(); |
| 233 | continue; | 206 | num_open_files--; |
| 234 | } | 207 | } |
| 208 | } | ||
| 235 | 209 | ||
| 236 | const auto& entry = cache[kv.first]; | 210 | void RealVfsFilesystem::EvictSingleReference() { |
| 237 | if (!entry.expired()) { | 211 | if (num_open_files < MaxOpenFiles || open_references.empty()) { |
| 238 | entry.lock()->Close(); | 212 | return; |
| 239 | } | 213 | } |
| 214 | |||
| 215 | // Get and remove from list. | ||
| 216 | auto& reference = open_references.back(); | ||
| 217 | this->RemoveReferenceFromList(reference); | ||
| 240 | 218 | ||
| 241 | cache.erase(kv.first); | 219 | // Close the file. |
| 220 | if (reference.file) { | ||
| 221 | reference.file.reset(); | ||
| 222 | num_open_files--; | ||
| 242 | } | 223 | } |
| 243 | 224 | ||
| 244 | return FS::RemoveDirRecursively(path); | 225 | // Reinsert into closed list. |
| 226 | this->InsertReferenceIntoList(reference); | ||
| 227 | } | ||
| 228 | |||
| 229 | void RealVfsFilesystem::InsertReferenceIntoList(FileReference& reference) { | ||
| 230 | if (reference.file) { | ||
| 231 | open_references.push_front(reference); | ||
| 232 | } else { | ||
| 233 | closed_references.push_front(reference); | ||
| 234 | } | ||
| 235 | } | ||
| 236 | |||
| 237 | void RealVfsFilesystem::RemoveReferenceFromList(FileReference& reference) { | ||
| 238 | if (reference.file) { | ||
| 239 | open_references.erase(open_references.iterator_to(reference)); | ||
| 240 | } else { | ||
| 241 | closed_references.erase(closed_references.iterator_to(reference)); | ||
| 242 | } | ||
| 245 | } | 243 | } |
| 246 | 244 | ||
| 247 | RealVfsFile::RealVfsFile(RealVfsFilesystem& base_, std::shared_ptr<FS::IOFile> backing_, | 245 | RealVfsFile::RealVfsFile(RealVfsFilesystem& base_, std::unique_ptr<FileReference> reference_, |
| 248 | const std::string& path_, Mode perms_) | 246 | const std::string& path_, Mode perms_) |
| 249 | : base(base_), backing(std::move(backing_)), path(path_), parent_path(FS::GetParentPath(path_)), | 247 | : base(base_), reference(std::move(reference_)), path(path_), |
| 250 | path_components(FS::SplitPathComponents(path_)), perms(perms_) {} | 248 | parent_path(FS::GetParentPath(path_)), path_components(FS::SplitPathComponents(path_)), |
| 249 | perms(perms_) {} | ||
| 251 | 250 | ||
| 252 | RealVfsFile::~RealVfsFile() = default; | 251 | RealVfsFile::~RealVfsFile() { |
| 252 | base.DropReference(std::move(reference)); | ||
| 253 | } | ||
| 253 | 254 | ||
| 254 | std::string RealVfsFile::GetName() const { | 255 | std::string RealVfsFile::GetName() const { |
| 255 | return path_components.back(); | 256 | return path_components.back(); |
| 256 | } | 257 | } |
| 257 | 258 | ||
| 258 | std::size_t RealVfsFile::GetSize() const { | 259 | std::size_t RealVfsFile::GetSize() const { |
| 259 | return backing->GetSize(); | 260 | base.RefreshReference(path, perms, *reference); |
| 261 | return reference->file ? reference->file->GetSize() : 0; | ||
| 260 | } | 262 | } |
| 261 | 263 | ||
| 262 | bool RealVfsFile::Resize(std::size_t new_size) { | 264 | bool RealVfsFile::Resize(std::size_t new_size) { |
| 263 | return backing->SetSize(new_size); | 265 | base.RefreshReference(path, perms, *reference); |
| 266 | return reference->file ? reference->file->SetSize(new_size) : false; | ||
| 264 | } | 267 | } |
| 265 | 268 | ||
| 266 | VirtualDir RealVfsFile::GetContainingDirectory() const { | 269 | VirtualDir RealVfsFile::GetContainingDirectory() const { |
| @@ -276,27 +279,25 @@ bool RealVfsFile::IsReadable() const { | |||
| 276 | } | 279 | } |
| 277 | 280 | ||
| 278 | std::size_t RealVfsFile::Read(u8* data, std::size_t length, std::size_t offset) const { | 281 | std::size_t RealVfsFile::Read(u8* data, std::size_t length, std::size_t offset) const { |
| 279 | if (!backing->Seek(static_cast<s64>(offset))) { | 282 | base.RefreshReference(path, perms, *reference); |
| 283 | if (!reference->file || !reference->file->Seek(static_cast<s64>(offset))) { | ||
| 280 | return 0; | 284 | return 0; |
| 281 | } | 285 | } |
| 282 | return backing->ReadSpan(std::span{data, length}); | 286 | return reference->file->ReadSpan(std::span{data, length}); |
| 283 | } | 287 | } |
| 284 | 288 | ||
| 285 | std::size_t RealVfsFile::Write(const u8* data, std::size_t length, std::size_t offset) { | 289 | std::size_t RealVfsFile::Write(const u8* data, std::size_t length, std::size_t offset) { |
| 286 | if (!backing->Seek(static_cast<s64>(offset))) { | 290 | base.RefreshReference(path, perms, *reference); |
| 291 | if (!reference->file || !reference->file->Seek(static_cast<s64>(offset))) { | ||
| 287 | return 0; | 292 | return 0; |
| 288 | } | 293 | } |
| 289 | return backing->WriteSpan(std::span{data, length}); | 294 | return reference->file->WriteSpan(std::span{data, length}); |
| 290 | } | 295 | } |
| 291 | 296 | ||
| 292 | bool RealVfsFile::Rename(std::string_view name) { | 297 | bool RealVfsFile::Rename(std::string_view name) { |
| 293 | return base.MoveFile(path, parent_path + '/' + std::string(name)) != nullptr; | 298 | return base.MoveFile(path, parent_path + '/' + std::string(name)) != nullptr; |
| 294 | } | 299 | } |
| 295 | 300 | ||
| 296 | void RealVfsFile::Close() { | ||
| 297 | backing->Close(); | ||
| 298 | } | ||
| 299 | |||
| 300 | // TODO(DarkLordZach): MSVC would not let me combine the following two functions using 'if | 301 | // TODO(DarkLordZach): MSVC would not let me combine the following two functions using 'if |
| 301 | // constexpr' because there is a compile error in the branch not used. | 302 | // constexpr' because there is a compile error in the branch not used. |
| 302 | 303 | ||
diff --git a/src/core/file_sys/vfs_real.h b/src/core/file_sys/vfs_real.h index b92c84316..d8c900e33 100644 --- a/src/core/file_sys/vfs_real.h +++ b/src/core/file_sys/vfs_real.h | |||
| @@ -3,8 +3,9 @@ | |||
| 3 | 3 | ||
| 4 | #pragma once | 4 | #pragma once |
| 5 | 5 | ||
| 6 | #include <map> | ||
| 6 | #include <string_view> | 7 | #include <string_view> |
| 7 | #include <boost/container/flat_map.hpp> | 8 | #include "common/intrusive_list.h" |
| 8 | #include "core/file_sys/mode.h" | 9 | #include "core/file_sys/mode.h" |
| 9 | #include "core/file_sys/vfs.h" | 10 | #include "core/file_sys/vfs.h" |
| 10 | 11 | ||
| @@ -14,6 +15,11 @@ class IOFile; | |||
| 14 | 15 | ||
| 15 | namespace FileSys { | 16 | namespace FileSys { |
| 16 | 17 | ||
| 18 | struct FileReference : public Common::IntrusiveListBaseNode<FileReference> { | ||
| 19 | std::shared_ptr<Common::FS::IOFile> file{}; | ||
| 20 | }; | ||
| 21 | |||
| 22 | class RealVfsFile; | ||
| 17 | class RealVfsFilesystem : public VfsFilesystem { | 23 | class RealVfsFilesystem : public VfsFilesystem { |
| 18 | public: | 24 | public: |
| 19 | RealVfsFilesystem(); | 25 | RealVfsFilesystem(); |
| @@ -35,7 +41,21 @@ public: | |||
| 35 | bool DeleteDirectory(std::string_view path) override; | 41 | bool DeleteDirectory(std::string_view path) override; |
| 36 | 42 | ||
| 37 | private: | 43 | private: |
| 38 | boost::container::flat_map<std::string, std::weak_ptr<Common::FS::IOFile>> cache; | 44 | using ReferenceListType = Common::IntrusiveListBaseTraits<FileReference>::ListType; |
| 45 | std::map<std::string, std::weak_ptr<VfsFile>, std::less<>> cache; | ||
| 46 | ReferenceListType open_references; | ||
| 47 | ReferenceListType closed_references; | ||
| 48 | size_t num_open_files{}; | ||
| 49 | |||
| 50 | private: | ||
| 51 | friend class RealVfsFile; | ||
| 52 | void RefreshReference(const std::string& path, Mode perms, FileReference& reference); | ||
| 53 | void DropReference(std::unique_ptr<FileReference>&& reference); | ||
| 54 | void EvictSingleReference(); | ||
| 55 | |||
| 56 | private: | ||
| 57 | void InsertReferenceIntoList(FileReference& reference); | ||
| 58 | void RemoveReferenceFromList(FileReference& reference); | ||
| 39 | }; | 59 | }; |
| 40 | 60 | ||
| 41 | // An implementation of VfsFile that represents a file on the user's computer. | 61 | // An implementation of VfsFile that represents a file on the user's computer. |
| @@ -57,13 +77,11 @@ public: | |||
| 57 | bool Rename(std::string_view name) override; | 77 | bool Rename(std::string_view name) override; |
| 58 | 78 | ||
| 59 | private: | 79 | private: |
| 60 | RealVfsFile(RealVfsFilesystem& base, std::shared_ptr<Common::FS::IOFile> backing, | 80 | RealVfsFile(RealVfsFilesystem& base, std::unique_ptr<FileReference> reference, |
| 61 | const std::string& path, Mode perms = Mode::Read); | 81 | const std::string& path, Mode perms = Mode::Read); |
| 62 | 82 | ||
| 63 | void Close(); | ||
| 64 | |||
| 65 | RealVfsFilesystem& base; | 83 | RealVfsFilesystem& base; |
| 66 | std::shared_ptr<Common::FS::IOFile> backing; | 84 | std::unique_ptr<FileReference> reference; |
| 67 | std::string path; | 85 | std::string path; |
| 68 | std::string parent_path; | 86 | std::string parent_path; |
| 69 | std::vector<std::string> path_components; | 87 | std::vector<std::string> path_components; |
diff --git a/src/input_common/drivers/virtual_amiibo.cpp b/src/input_common/drivers/virtual_amiibo.cpp index f8bafe553..6435b8af8 100644 --- a/src/input_common/drivers/virtual_amiibo.cpp +++ b/src/input_common/drivers/virtual_amiibo.cpp | |||
| @@ -82,6 +82,7 @@ VirtualAmiibo::Info VirtualAmiibo::LoadAmiibo(const std::string& filename) { | |||
| 82 | switch (nfc_file.GetSize()) { | 82 | switch (nfc_file.GetSize()) { |
| 83 | case AmiiboSize: | 83 | case AmiiboSize: |
| 84 | case AmiiboSizeWithoutPassword: | 84 | case AmiiboSizeWithoutPassword: |
| 85 | case AmiiboSizeWithSignature: | ||
| 85 | data.resize(AmiiboSize); | 86 | data.resize(AmiiboSize); |
| 86 | if (nfc_file.Read(data) < AmiiboSizeWithoutPassword) { | 87 | if (nfc_file.Read(data) < AmiiboSizeWithoutPassword) { |
| 87 | return Info::NotAnAmiibo; | 88 | return Info::NotAnAmiibo; |
| @@ -109,6 +110,7 @@ VirtualAmiibo::Info VirtualAmiibo::LoadAmiibo(std::span<u8> data) { | |||
| 109 | switch (data.size_bytes()) { | 110 | switch (data.size_bytes()) { |
| 110 | case AmiiboSize: | 111 | case AmiiboSize: |
| 111 | case AmiiboSizeWithoutPassword: | 112 | case AmiiboSizeWithoutPassword: |
| 113 | case AmiiboSizeWithSignature: | ||
| 112 | nfc_data.resize(AmiiboSize); | 114 | nfc_data.resize(AmiiboSize); |
| 113 | break; | 115 | break; |
| 114 | case MifareSize: | 116 | case MifareSize: |
diff --git a/src/input_common/drivers/virtual_amiibo.h b/src/input_common/drivers/virtual_amiibo.h index 34e97cd91..09ca09e68 100644 --- a/src/input_common/drivers/virtual_amiibo.h +++ b/src/input_common/drivers/virtual_amiibo.h | |||
| @@ -57,6 +57,7 @@ public: | |||
| 57 | private: | 57 | private: |
| 58 | static constexpr std::size_t AmiiboSize = 0x21C; | 58 | static constexpr std::size_t AmiiboSize = 0x21C; |
| 59 | static constexpr std::size_t AmiiboSizeWithoutPassword = AmiiboSize - 0x8; | 59 | static constexpr std::size_t AmiiboSizeWithoutPassword = AmiiboSize - 0x8; |
| 60 | static constexpr std::size_t AmiiboSizeWithSignature = AmiiboSize + 0x20; | ||
| 60 | static constexpr std::size_t MifareSize = 0x400; | 61 | static constexpr std::size_t MifareSize = 0x400; |
| 61 | 62 | ||
| 62 | std::string file_path{}; | 63 | std::string file_path{}; |
diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 5734f51e5..18e040a1b 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp | |||
| @@ -358,6 +358,7 @@ PipelineCache::PipelineCache(RasterizerVulkan& rasterizer_, const Device& device | |||
| 358 | .support_snorm_render_buffer = true, | 358 | .support_snorm_render_buffer = true, |
| 359 | .support_viewport_index_layer = device.IsExtShaderViewportIndexLayerSupported(), | 359 | .support_viewport_index_layer = device.IsExtShaderViewportIndexLayerSupported(), |
| 360 | .support_geometry_shader_passthrough = device.IsNvGeometryShaderPassthroughSupported(), | 360 | .support_geometry_shader_passthrough = device.IsNvGeometryShaderPassthroughSupported(), |
| 361 | .support_conditional_barrier = device.SupportsConditionalBarriers(), | ||
| 361 | }; | 362 | }; |
| 362 | 363 | ||
| 363 | if (device.GetMaxVertexInputAttributes() < Maxwell::NumVertexAttributes) { | 364 | if (device.GetMaxVertexInputAttributes() < Maxwell::NumVertexAttributes) { |
diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index a46f9beed..3d2e9a16a 100644 --- a/src/video_core/vulkan_common/vulkan_device.cpp +++ b/src/video_core/vulkan_common/vulkan_device.cpp | |||
| @@ -344,6 +344,7 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR | |||
| 344 | const bool is_qualcomm = driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY; | 344 | const bool is_qualcomm = driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY; |
| 345 | const bool is_turnip = driver_id == VK_DRIVER_ID_MESA_TURNIP; | 345 | const bool is_turnip = driver_id == VK_DRIVER_ID_MESA_TURNIP; |
| 346 | const bool is_s8gen2 = device_id == 0x43050a01; | 346 | const bool is_s8gen2 = device_id == 0x43050a01; |
| 347 | const bool is_arm = driver_id == VK_DRIVER_ID_ARM_PROPRIETARY; | ||
| 347 | 348 | ||
| 348 | if ((is_mvk || is_qualcomm || is_turnip) && !is_suitable) { | 349 | if ((is_mvk || is_qualcomm || is_turnip) && !is_suitable) { |
| 349 | LOG_WARNING(Render_Vulkan, "Unsuitable driver, continuing anyway"); | 350 | LOG_WARNING(Render_Vulkan, "Unsuitable driver, continuing anyway"); |
| @@ -391,7 +392,6 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR | |||
| 391 | CollectPhysicalMemoryInfo(); | 392 | CollectPhysicalMemoryInfo(); |
| 392 | CollectToolingInfo(); | 393 | CollectToolingInfo(); |
| 393 | 394 | ||
| 394 | #ifdef ANDROID | ||
| 395 | if (is_qualcomm || is_turnip) { | 395 | if (is_qualcomm || is_turnip) { |
| 396 | LOG_WARNING(Render_Vulkan, | 396 | LOG_WARNING(Render_Vulkan, |
| 397 | "Qualcomm and Turnip drivers have broken VK_EXT_custom_border_color"); | 397 | "Qualcomm and Turnip drivers have broken VK_EXT_custom_border_color"); |
| @@ -411,7 +411,7 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR | |||
| 411 | extensions.push_descriptor = false; | 411 | extensions.push_descriptor = false; |
| 412 | loaded_extensions.erase(VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME); | 412 | loaded_extensions.erase(VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME); |
| 413 | 413 | ||
| 414 | #ifdef ARCHITECTURE_arm64 | 414 | #if defined(ANDROID) && defined(ARCHITECTURE_arm64) |
| 415 | // Patch the driver to enable BCn textures. | 415 | // Patch the driver to enable BCn textures. |
| 416 | const auto major = (properties.properties.driverVersion >> 24) << 2; | 416 | const auto major = (properties.properties.driverVersion >> 24) << 2; |
| 417 | const auto minor = (properties.properties.driverVersion >> 12) & 0xFFFU; | 417 | const auto minor = (properties.properties.driverVersion >> 12) & 0xFFFU; |
| @@ -431,18 +431,23 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR | |||
| 431 | } else { | 431 | } else { |
| 432 | LOG_WARNING(Render_Vulkan, "Adreno driver can't be patched to enable BCn textures"); | 432 | LOG_WARNING(Render_Vulkan, "Adreno driver can't be patched to enable BCn textures"); |
| 433 | } | 433 | } |
| 434 | #endif // ARCHITECTURE_arm64 | 434 | #endif |
| 435 | } | 435 | } |
| 436 | 436 | ||
| 437 | const bool is_arm = driver_id == VK_DRIVER_ID_ARM_PROPRIETARY; | ||
| 438 | if (is_arm) { | 437 | if (is_arm) { |
| 439 | must_emulate_scaled_formats = true; | 438 | must_emulate_scaled_formats = true; |
| 440 | 439 | ||
| 441 | LOG_WARNING(Render_Vulkan, "ARM drivers have broken VK_EXT_extended_dynamic_state"); | 440 | LOG_WARNING(Render_Vulkan, "ARM drivers have broken VK_EXT_extended_dynamic_state"); |
| 442 | extensions.extended_dynamic_state = false; | 441 | extensions.extended_dynamic_state = false; |
| 443 | loaded_extensions.erase(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME); | 442 | loaded_extensions.erase(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME); |
| 443 | |||
| 444 | LOG_WARNING(Render_Vulkan, "ARM drivers have broken VK_EXT_extended_dynamic_state2"); | ||
| 445 | features.extended_dynamic_state2.extendedDynamicState2 = false; | ||
| 446 | features.extended_dynamic_state2.extendedDynamicState2LogicOp = false; | ||
| 447 | features.extended_dynamic_state2.extendedDynamicState2PatchControlPoints = false; | ||
| 448 | extensions.extended_dynamic_state2 = false; | ||
| 449 | loaded_extensions.erase(VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME); | ||
| 444 | } | 450 | } |
| 445 | #endif // ANDROID | ||
| 446 | 451 | ||
| 447 | if (is_nvidia) { | 452 | if (is_nvidia) { |
| 448 | const u32 nv_major_version = (properties.properties.driverVersion >> 22) & 0x3ff; | 453 | const u32 nv_major_version = (properties.properties.driverVersion >> 22) & 0x3ff; |