diff options
35 files changed, 887 insertions, 368 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/vcpkg b/externals/vcpkg | |||
| Subproject 656fcc6ab2b05c6d999b7eaca717027ac3738f7 | Subproject a487471068f4cb1cbb4eeb340763cdcc0a75fd6 | ||
diff --git a/src/android/app/build.gradle.kts b/src/android/app/build.gradle.kts index fe613d339..a637db78a 100644 --- a/src/android/app/build.gradle.kts +++ b/src/android/app/build.gradle.kts | |||
| @@ -9,6 +9,7 @@ plugins { | |||
| 9 | id("org.jetbrains.kotlin.android") | 9 | id("org.jetbrains.kotlin.android") |
| 10 | id("kotlin-parcelize") | 10 | id("kotlin-parcelize") |
| 11 | kotlin("plugin.serialization") version "1.8.21" | 11 | kotlin("plugin.serialization") version "1.8.21" |
| 12 | id("androidx.navigation.safeargs.kotlin") | ||
| 12 | } | 13 | } |
| 13 | 14 | ||
| 14 | /** | 15 | /** |
diff --git a/src/android/app/src/main/AndroidManifest.xml b/src/android/app/src/main/AndroidManifest.xml index 55f62b4b9..a6f87fc2e 100644 --- a/src/android/app/src/main/AndroidManifest.xml +++ b/src/android/app/src/main/AndroidManifest.xml | |||
| @@ -53,8 +53,8 @@ SPDX-License-Identifier: GPL-3.0-or-later | |||
| 53 | <activity | 53 | <activity |
| 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:launchMode="singleTop" | 56 | android:supportsPictureInPicture="true" |
| 57 | android:screenOrientation="userLandscape" | 57 | android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|uiMode" |
| 58 | android:exported="true"> | 58 | android:exported="true"> |
| 59 | 59 | ||
| 60 | <intent-filter> | 60 | <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..22f0a2646 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 | |||
| @@ -283,6 +283,11 @@ object NativeLibrary { | |||
| 283 | external fun isRunning(): Boolean | 283 | external fun isRunning(): Boolean |
| 284 | 284 | ||
| 285 | /** | 285 | /** |
| 286 | * Returns true if emulation is paused. | ||
| 287 | */ | ||
| 288 | external fun isPaused(): Boolean | ||
| 289 | |||
| 290 | /** | ||
| 286 | * Returns the performance stats for the current game | 291 | * Returns the performance stats for the current game |
| 287 | */ | 292 | */ |
| 288 | external fun getPerfStats(): DoubleArray | 293 | external fun getPerfStats(): DoubleArray |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt index 20a0394f5..5ca519f0a 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 |
| @@ -23,30 +32,27 @@ import androidx.appcompat.app.AppCompatActivity | |||
| 23 | import androidx.core.view.WindowCompat | 32 | 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.lifecycle.Lifecycle | 35 | import androidx.navigation.fragment.NavHostFragment |
| 27 | import androidx.lifecycle.lifecycleScope | ||
| 28 | import androidx.lifecycle.repeatOnLifecycle | ||
| 29 | import androidx.window.layout.WindowInfoTracker | ||
| 30 | import kotlinx.coroutines.Dispatchers | ||
| 31 | import kotlinx.coroutines.launch | ||
| 32 | import org.yuzu.yuzu_emu.NativeLibrary | 36 | import org.yuzu.yuzu_emu.NativeLibrary |
| 33 | import org.yuzu.yuzu_emu.R | 37 | import org.yuzu.yuzu_emu.R |
| 38 | import org.yuzu.yuzu_emu.databinding.ActivityEmulationBinding | ||
| 39 | import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting | ||
| 40 | import org.yuzu.yuzu_emu.features.settings.model.IntSetting | ||
| 34 | import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel | 41 | import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel |
| 35 | import org.yuzu.yuzu_emu.fragments.EmulationFragment | ||
| 36 | import org.yuzu.yuzu_emu.model.Game | 42 | import org.yuzu.yuzu_emu.model.Game |
| 37 | import org.yuzu.yuzu_emu.utils.ControllerMappingHelper | 43 | import org.yuzu.yuzu_emu.utils.ControllerMappingHelper |
| 38 | import org.yuzu.yuzu_emu.utils.ForegroundService | 44 | import org.yuzu.yuzu_emu.utils.ForegroundService |
| 39 | import org.yuzu.yuzu_emu.utils.InputHandler | 45 | import org.yuzu.yuzu_emu.utils.InputHandler |
| 40 | import org.yuzu.yuzu_emu.utils.NfcReader | 46 | import org.yuzu.yuzu_emu.utils.NfcReader |
| 41 | import org.yuzu.yuzu_emu.utils.SerializableHelper.parcelable | ||
| 42 | import org.yuzu.yuzu_emu.utils.ThemeHelper | 47 | import org.yuzu.yuzu_emu.utils.ThemeHelper |
| 43 | import kotlin.math.roundToInt | 48 | import kotlin.math.roundToInt |
| 44 | 49 | ||
| 45 | class EmulationActivity : AppCompatActivity(), SensorEventListener { | 50 | class EmulationActivity : AppCompatActivity(), SensorEventListener { |
| 51 | private lateinit var binding: ActivityEmulationBinding | ||
| 52 | |||
| 46 | private var controllerMappingHelper: ControllerMappingHelper? = null | 53 | private var controllerMappingHelper: ControllerMappingHelper? = null |
| 47 | 54 | ||
| 48 | var isActivityRecreated = false | 55 | var isActivityRecreated = false |
| 49 | private var emulationFragment: EmulationFragment? = null | ||
| 50 | private lateinit var nfcReader: NfcReader | 56 | private lateinit var nfcReader: NfcReader |
| 51 | private lateinit var inputHandler: InputHandler | 57 | private lateinit var inputHandler: InputHandler |
| 52 | 58 | ||
| @@ -55,7 +61,8 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener { | |||
| 55 | private var motionTimestamp: Long = 0 | 61 | private var motionTimestamp: Long = 0 |
| 56 | private var flipMotionOrientation: Boolean = false | 62 | private var flipMotionOrientation: Boolean = false |
| 57 | 63 | ||
| 58 | private lateinit var game: Game | 64 | private val actionPause = "ACTION_EMULATOR_PAUSE" |
| 65 | private val actionPlay = "ACTION_EMULATOR_PLAY" | ||
| 59 | 66 | ||
| 60 | private val settingsViewModel: SettingsViewModel by viewModels() | 67 | private val settingsViewModel: SettingsViewModel by viewModels() |
| 61 | 68 | ||
| @@ -70,47 +77,31 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener { | |||
| 70 | settingsViewModel.settings.loadSettings() | 77 | settingsViewModel.settings.loadSettings() |
| 71 | 78 | ||
| 72 | super.onCreate(savedInstanceState) | 79 | super.onCreate(savedInstanceState) |
| 73 | if (savedInstanceState == null) { | 80 | |
| 74 | // Get params we were passed | 81 | binding = ActivityEmulationBinding.inflate(layoutInflater) |
| 75 | game = intent.parcelable(EXTRA_SELECTED_GAME)!! | 82 | setContentView(binding.root) |
| 76 | isActivityRecreated = false | 83 | |
| 77 | } else { | 84 | val navHostFragment = |
| 78 | isActivityRecreated = true | 85 | supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment |
| 79 | restoreState(savedInstanceState) | 86 | val navController = navHostFragment.navController |
| 80 | } | 87 | navController |
| 88 | .setGraph(R.navigation.emulation_navigation, intent.extras) | ||
| 89 | |||
| 90 | isActivityRecreated = savedInstanceState != null | ||
| 91 | |||
| 81 | controllerMappingHelper = ControllerMappingHelper() | 92 | controllerMappingHelper = ControllerMappingHelper() |
| 82 | 93 | ||
| 83 | // Set these options now so that the SurfaceView the game renders into is the right size. | 94 | // Set these options now so that the SurfaceView the game renders into is the right size. |
| 84 | enableFullscreenImmersive() | 95 | enableFullscreenImmersive() |
| 85 | 96 | ||
| 86 | setContentView(R.layout.activity_emulation) | ||
| 87 | window.decorView.setBackgroundColor(getColor(android.R.color.black)) | 97 | window.decorView.setBackgroundColor(getColor(android.R.color.black)) |
| 88 | 98 | ||
| 89 | // Find or create the EmulationFragment | ||
| 90 | emulationFragment = | ||
| 91 | supportFragmentManager.findFragmentById(R.id.frame_emulation_fragment) as EmulationFragment? | ||
| 92 | if (emulationFragment == null) { | ||
| 93 | emulationFragment = EmulationFragment.newInstance(game) | ||
| 94 | supportFragmentManager.beginTransaction() | ||
| 95 | .add(R.id.frame_emulation_fragment, emulationFragment!!) | ||
| 96 | .commit() | ||
| 97 | } | ||
| 98 | title = game.title | ||
| 99 | |||
| 100 | nfcReader = NfcReader(this) | 99 | nfcReader = NfcReader(this) |
| 101 | nfcReader.initialize() | 100 | nfcReader.initialize() |
| 102 | 101 | ||
| 103 | inputHandler = InputHandler() | 102 | inputHandler = InputHandler() |
| 104 | inputHandler.initialize() | 103 | inputHandler.initialize() |
| 105 | 104 | ||
| 106 | lifecycleScope.launch(Dispatchers.Main) { | ||
| 107 | lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { | ||
| 108 | WindowInfoTracker.getOrCreate(this@EmulationActivity) | ||
| 109 | .windowLayoutInfo(this@EmulationActivity) | ||
| 110 | .collect { emulationFragment?.updateCurrentLayout(this@EmulationActivity, it) } | ||
| 111 | } | ||
| 112 | } | ||
| 113 | |||
| 114 | // Start a foreground service to prevent the app from getting killed in the background | 105 | // Start a foreground service to prevent the app from getting killed in the background |
| 115 | val startIntent = Intent(this, ForegroundService::class.java) | 106 | val startIntent = Intent(this, ForegroundService::class.java) |
| 116 | startForegroundService(startIntent) | 107 | startForegroundService(startIntent) |
| @@ -143,6 +134,8 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener { | |||
| 143 | super.onResume() | 134 | super.onResume() |
| 144 | nfcReader.startScanning() | 135 | nfcReader.startScanning() |
| 145 | startMotionSensorListener() | 136 | startMotionSensorListener() |
| 137 | |||
| 138 | buildPictureInPictureParams() | ||
| 146 | } | 139 | } |
| 147 | 140 | ||
| 148 | override fun onPause() { | 141 | override fun onPause() { |
| @@ -151,17 +144,22 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener { | |||
| 151 | stopMotionSensorListener() | 144 | stopMotionSensorListener() |
| 152 | } | 145 | } |
| 153 | 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 | |||
| 154 | override fun onNewIntent(intent: Intent) { | 157 | override fun onNewIntent(intent: Intent) { |
| 155 | super.onNewIntent(intent) | 158 | super.onNewIntent(intent) |
| 156 | setIntent(intent) | 159 | setIntent(intent) |
| 157 | nfcReader.onNewIntent(intent) | 160 | nfcReader.onNewIntent(intent) |
| 158 | } | 161 | } |
| 159 | 162 | ||
| 160 | override fun onSaveInstanceState(outState: Bundle) { | ||
| 161 | outState.putParcelable(EXTRA_SELECTED_GAME, game) | ||
| 162 | super.onSaveInstanceState(outState) | ||
| 163 | } | ||
| 164 | |||
| 165 | override fun dispatchKeyEvent(event: KeyEvent): Boolean { | 163 | override fun dispatchKeyEvent(event: KeyEvent): Boolean { |
| 166 | if (event.source and InputDevice.SOURCE_JOYSTICK != InputDevice.SOURCE_JOYSTICK && | 164 | if (event.source and InputDevice.SOURCE_JOYSTICK != InputDevice.SOURCE_JOYSTICK && |
| 167 | event.source and InputDevice.SOURCE_GAMEPAD != InputDevice.SOURCE_GAMEPAD | 165 | event.source and InputDevice.SOURCE_GAMEPAD != InputDevice.SOURCE_GAMEPAD |
| @@ -248,10 +246,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener { | |||
| 248 | 246 | ||
| 249 | override fun onAccuracyChanged(sensor: Sensor, i: Int) {} | 247 | override fun onAccuracyChanged(sensor: Sensor, i: Int) {} |
| 250 | 248 | ||
| 251 | private fun restoreState(savedInstanceState: Bundle) { | ||
| 252 | game = savedInstanceState.parcelable(EXTRA_SELECTED_GAME)!! | ||
| 253 | } | ||
| 254 | |||
| 255 | private fun enableFullscreenImmersive() { | 249 | private fun enableFullscreenImmersive() { |
| 256 | WindowCompat.setDecorFitsSystemWindows(window, false) | 250 | WindowCompat.setDecorFitsSystemWindows(window, false) |
| 257 | 251 | ||
| @@ -262,6 +256,96 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener { | |||
| 262 | } | 256 | } |
| 263 | } | 257 | } |
| 264 | 258 | ||
| 259 | private fun PictureInPictureParams.Builder.getPictureInPictureAspectBuilder(): PictureInPictureParams.Builder { | ||
| 260 | val aspectRatio = when (IntSetting.RENDERER_ASPECT_RATIO.int) { | ||
| 261 | 0 -> Rational(16, 9) | ||
| 262 | 1 -> Rational(4, 3) | ||
| 263 | 2 -> Rational(21, 9) | ||
| 264 | 3 -> Rational(16, 10) | ||
| 265 | else -> null // Best fit | ||
| 266 | } | ||
| 267 | return this.apply { aspectRatio?.let { setAspectRatio(it) } } | ||
| 268 | } | ||
| 269 | |||
| 270 | private fun PictureInPictureParams.Builder.getPictureInPictureActionsBuilder(): PictureInPictureParams.Builder { | ||
| 271 | val pictureInPictureActions: MutableList<RemoteAction> = mutableListOf() | ||
| 272 | val pendingFlags = PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE | ||
| 273 | |||
| 274 | if (NativeLibrary.isPaused()) { | ||
| 275 | val playIcon = Icon.createWithResource(this@EmulationActivity, R.drawable.ic_pip_play) | ||
| 276 | val playPendingIntent = PendingIntent.getBroadcast( | ||
| 277 | this@EmulationActivity, | ||
| 278 | R.drawable.ic_pip_play, | ||
| 279 | Intent(actionPlay), | ||
| 280 | pendingFlags | ||
| 281 | ) | ||
| 282 | val playRemoteAction = RemoteAction( | ||
| 283 | playIcon, | ||
| 284 | getString(R.string.play), | ||
| 285 | getString(R.string.play), | ||
| 286 | playPendingIntent | ||
| 287 | ) | ||
| 288 | pictureInPictureActions.add(playRemoteAction) | ||
| 289 | } else { | ||
| 290 | val pauseIcon = Icon.createWithResource(this@EmulationActivity, R.drawable.ic_pip_pause) | ||
| 291 | val pausePendingIntent = PendingIntent.getBroadcast( | ||
| 292 | this@EmulationActivity, | ||
| 293 | R.drawable.ic_pip_pause, | ||
| 294 | Intent(actionPause), | ||
| 295 | pendingFlags | ||
| 296 | ) | ||
| 297 | val pauseRemoteAction = RemoteAction( | ||
| 298 | pauseIcon, | ||
| 299 | getString(R.string.pause), | ||
| 300 | getString(R.string.pause), | ||
| 301 | pausePendingIntent | ||
| 302 | ) | ||
| 303 | pictureInPictureActions.add(pauseRemoteAction) | ||
| 304 | } | ||
| 305 | |||
| 306 | return this.apply { setActions(pictureInPictureActions) } | ||
| 307 | } | ||
| 308 | |||
| 309 | fun buildPictureInPictureParams() { | ||
| 310 | val pictureInPictureParamsBuilder = PictureInPictureParams.Builder() | ||
| 311 | .getPictureInPictureActionsBuilder().getPictureInPictureAspectBuilder() | ||
| 312 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { | ||
| 313 | pictureInPictureParamsBuilder.setAutoEnterEnabled(BooleanSetting.PICTURE_IN_PICTURE.boolean) | ||
| 314 | } | ||
| 315 | setPictureInPictureParams(pictureInPictureParamsBuilder.build()) | ||
| 316 | } | ||
| 317 | |||
| 318 | private var pictureInPictureReceiver = object : BroadcastReceiver() { | ||
| 319 | override fun onReceive(context: Context?, intent: Intent) { | ||
| 320 | if (intent.action == actionPlay) { | ||
| 321 | if (NativeLibrary.isPaused()) NativeLibrary.unPauseEmulation() | ||
| 322 | } else if (intent.action == actionPause) { | ||
| 323 | if (!NativeLibrary.isPaused()) NativeLibrary.pauseEmulation() | ||
| 324 | } | ||
| 325 | buildPictureInPictureParams() | ||
| 326 | } | ||
| 327 | } | ||
| 328 | |||
| 329 | override fun onPictureInPictureModeChanged( | ||
| 330 | isInPictureInPictureMode: Boolean, | ||
| 331 | newConfig: Configuration | ||
| 332 | ) { | ||
| 333 | super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig) | ||
| 334 | if (isInPictureInPictureMode) { | ||
| 335 | IntentFilter().apply { | ||
| 336 | addAction(actionPause) | ||
| 337 | addAction(actionPlay) | ||
| 338 | }.also { | ||
| 339 | registerReceiver(pictureInPictureReceiver, it) | ||
| 340 | } | ||
| 341 | } else { | ||
| 342 | try { | ||
| 343 | unregisterReceiver(pictureInPictureReceiver) | ||
| 344 | } catch (ignored : Exception) { | ||
| 345 | } | ||
| 346 | } | ||
| 347 | } | ||
| 348 | |||
| 265 | private fun startMotionSensorListener() { | 349 | private fun startMotionSensorListener() { |
| 266 | val sensorManager = this.getSystemService(Context.SENSOR_SERVICE) as SensorManager | 350 | val sensorManager = this.getSystemService(Context.SENSOR_SERVICE) as SensorManager |
| 267 | val gyroSensor = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE) | 351 | 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 7f9e2e2d4..83d08841b 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 | |||
| @@ -16,6 +16,7 @@ import androidx.appcompat.app.AppCompatActivity | |||
| 16 | import androidx.documentfile.provider.DocumentFile | 16 | import androidx.documentfile.provider.DocumentFile |
| 17 | import androidx.lifecycle.ViewModelProvider | 17 | import androidx.lifecycle.ViewModelProvider |
| 18 | import androidx.lifecycle.lifecycleScope | 18 | import androidx.lifecycle.lifecycleScope |
| 19 | import androidx.navigation.findNavController | ||
| 19 | import androidx.preference.PreferenceManager | 20 | import androidx.preference.PreferenceManager |
| 20 | import androidx.recyclerview.widget.AsyncDifferConfig | 21 | import androidx.recyclerview.widget.AsyncDifferConfig |
| 21 | import androidx.recyclerview.widget.DiffUtil | 22 | import androidx.recyclerview.widget.DiffUtil |
| @@ -23,6 +24,7 @@ import androidx.recyclerview.widget.ListAdapter | |||
| 23 | import androidx.recyclerview.widget.RecyclerView | 24 | import androidx.recyclerview.widget.RecyclerView |
| 24 | import coil.load | 25 | import coil.load |
| 25 | import kotlinx.coroutines.launch | 26 | import kotlinx.coroutines.launch |
| 27 | import org.yuzu.yuzu_emu.HomeNavigationDirections | ||
| 26 | import org.yuzu.yuzu_emu.NativeLibrary | 28 | import org.yuzu.yuzu_emu.NativeLibrary |
| 27 | import org.yuzu.yuzu_emu.R | 29 | import org.yuzu.yuzu_emu.R |
| 28 | import org.yuzu.yuzu_emu.YuzuApplication | 30 | import org.yuzu.yuzu_emu.YuzuApplication |
| @@ -78,7 +80,8 @@ class GameAdapter(private val activity: AppCompatActivity) : | |||
| 78 | ) | 80 | ) |
| 79 | .apply() | 81 | .apply() |
| 80 | 82 | ||
| 81 | EmulationActivity.launch(activity, holder.game) | 83 | val action = HomeNavigationDirections.actionGlobalEmulationActivity(holder.game) |
| 84 | view.findNavController().navigate(action) | ||
| 82 | } | 85 | } |
| 83 | 86 | ||
| 84 | inner class GameViewHolder(val binding: CardGameBinding) : | 87 | inner class GameViewHolder(val binding: CardGameBinding) : |
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..6bcb7bee0 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 | |||
| @@ -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/ui/SettingsActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt index 72e2cce2a..da7062b87 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 | |||
| @@ -16,6 +16,7 @@ import androidx.core.view.WindowCompat | |||
| 16 | import androidx.core.view.WindowInsetsCompat | 16 | import androidx.core.view.WindowInsetsCompat |
| 17 | import android.view.ViewGroup.MarginLayoutParams | 17 | import android.view.ViewGroup.MarginLayoutParams |
| 18 | import androidx.activity.OnBackPressedCallback | 18 | import androidx.activity.OnBackPressedCallback |
| 19 | import androidx.activity.result.ActivityResultLauncher | ||
| 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 org.yuzu.yuzu_emu.NativeLibrary |
| @@ -239,5 +240,17 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView { | |||
| 239 | settings.putExtra(ARG_GAME_ID, gameId) | 240 | settings.putExtra(ARG_GAME_ID, gameId) |
| 240 | context.startActivity(settings) | 241 | context.startActivity(settings) |
| 241 | } | 242 | } |
| 243 | |||
| 244 | fun launch( | ||
| 245 | context: Context, | ||
| 246 | launcher: ActivityResultLauncher<Intent>, | ||
| 247 | menuTag: String?, | ||
| 248 | gameId: String? | ||
| 249 | ) { | ||
| 250 | val settings = Intent(context, SettingsActivity::class.java) | ||
| 251 | settings.putExtra(ARG_MENU_TAG, menuTag) | ||
| 252 | settings.putExtra(ARG_GAME_ID, gameId) | ||
| 253 | launcher.launch(settings) | ||
| 254 | } | ||
| 242 | } | 255 | } |
| 243 | } | 256 | } |
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..b611389a1 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 | |||
| @@ -166,6 +166,15 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) | |||
| 166 | IntSetting.CPU_ACCURACY.defaultValue | 166 | IntSetting.CPU_ACCURACY.defaultValue |
| 167 | ) | 167 | ) |
| 168 | ) | 168 | ) |
| 169 | add( | ||
| 170 | SwitchSetting( | ||
| 171 | BooleanSetting.PICTURE_IN_PICTURE, | ||
| 172 | R.string.picture_in_picture, | ||
| 173 | R.string.picture_in_picture_description, | ||
| 174 | BooleanSetting.PICTURE_IN_PICTURE.key, | ||
| 175 | BooleanSetting.PICTURE_IN_PICTURE.defaultValue | ||
| 176 | ) | ||
| 177 | ) | ||
| 169 | } | 178 | } |
| 170 | } | 179 | } |
| 171 | 180 | ||
| @@ -285,6 +294,17 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) | |||
| 285 | ) | 294 | ) |
| 286 | add( | 295 | add( |
| 287 | SingleChoiceSetting( | 296 | SingleChoiceSetting( |
| 297 | IntSetting.RENDERER_SCREEN_LAYOUT, | ||
| 298 | R.string.renderer_screen_layout, | ||
| 299 | 0, | ||
| 300 | R.array.rendererScreenLayoutNames, | ||
| 301 | R.array.rendererScreenLayoutValues, | ||
| 302 | IntSetting.RENDERER_SCREEN_LAYOUT.key, | ||
| 303 | IntSetting.RENDERER_SCREEN_LAYOUT.defaultValue | ||
| 304 | ) | ||
| 305 | ) | ||
| 306 | add( | ||
| 307 | SingleChoiceSetting( | ||
| 288 | IntSetting.RENDERER_ASPECT_RATIO, | 308 | IntSetting.RENDERER_ASPECT_RATIO, |
| 289 | R.string.renderer_aspect_ratio, | 309 | R.string.renderer_aspect_ratio, |
| 290 | 0, | 310 | 0, |
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 9523381cd..4b2305892 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,30 +7,39 @@ 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 |
| 31 | import androidx.lifecycle.Lifecycle | ||
| 32 | import androidx.lifecycle.lifecycleScope | ||
| 33 | import androidx.lifecycle.repeatOnLifecycle | ||
| 34 | import androidx.navigation.fragment.navArgs | ||
| 29 | import androidx.preference.PreferenceManager | 35 | import androidx.preference.PreferenceManager |
| 30 | import androidx.window.layout.FoldingFeature | 36 | import androidx.window.layout.FoldingFeature |
| 37 | import androidx.window.layout.WindowInfoTracker | ||
| 31 | import androidx.window.layout.WindowLayoutInfo | 38 | import androidx.window.layout.WindowLayoutInfo |
| 32 | import com.google.android.material.dialog.MaterialAlertDialogBuilder | 39 | import com.google.android.material.dialog.MaterialAlertDialogBuilder |
| 33 | import com.google.android.material.slider.Slider | 40 | import com.google.android.material.slider.Slider |
| 41 | import kotlinx.coroutines.Dispatchers | ||
| 42 | import kotlinx.coroutines.launch | ||
| 34 | import org.yuzu.yuzu_emu.NativeLibrary | 43 | import org.yuzu.yuzu_emu.NativeLibrary |
| 35 | import org.yuzu.yuzu_emu.R | 44 | import org.yuzu.yuzu_emu.R |
| 36 | import org.yuzu.yuzu_emu.YuzuApplication | 45 | import org.yuzu.yuzu_emu.YuzuApplication |
| @@ -41,9 +50,8 @@ import org.yuzu.yuzu_emu.features.settings.model.IntSetting | |||
| 41 | import org.yuzu.yuzu_emu.features.settings.model.Settings | 50 | import org.yuzu.yuzu_emu.features.settings.model.Settings |
| 42 | import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity | 51 | import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity |
| 43 | import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile | 52 | import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile |
| 44 | import org.yuzu.yuzu_emu.model.Game | 53 | import org.yuzu.yuzu_emu.overlay.InputOverlay |
| 45 | import org.yuzu.yuzu_emu.utils.* | 54 | import org.yuzu.yuzu_emu.utils.* |
| 46 | import org.yuzu.yuzu_emu.utils.SerializableHelper.parcelable | ||
| 47 | 55 | ||
| 48 | class EmulationFragment : Fragment(), SurfaceHolder.Callback { | 56 | class EmulationFragment : Fragment(), SurfaceHolder.Callback { |
| 49 | private lateinit var preferences: SharedPreferences | 57 | private lateinit var preferences: SharedPreferences |
| @@ -54,13 +62,42 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 54 | private var _binding: FragmentEmulationBinding? = null | 62 | private var _binding: FragmentEmulationBinding? = null |
| 55 | private val binding get() = _binding!! | 63 | private val binding get() = _binding!! |
| 56 | 64 | ||
| 57 | private lateinit var game: Game | 65 | val args by navArgs<EmulationFragmentArgs>() |
| 66 | |||
| 67 | private var isInFoldableLayout = false | ||
| 68 | |||
| 69 | private lateinit var onReturnFromSettings: ActivityResultLauncher<Intent> | ||
| 58 | 70 | ||
| 59 | override fun onAttach(context: Context) { | 71 | override fun onAttach(context: Context) { |
| 60 | super.onAttach(context) | 72 | super.onAttach(context) |
| 61 | if (context is EmulationActivity) { | 73 | if (context is EmulationActivity) { |
| 62 | emulationActivity = context | 74 | emulationActivity = context |
| 63 | 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", ActivityResultContracts.StartActivityForResult() | ||
| 87 | ) { | ||
| 88 | binding.surfaceEmulation.setAspectRatio( | ||
| 89 | when (IntSetting.RENDERER_ASPECT_RATIO.int) { | ||
| 90 | 0 -> Rational(16, 9) | ||
| 91 | 1 -> Rational(4, 3) | ||
| 92 | 2 -> Rational(21, 9) | ||
| 93 | 3 -> Rational(16, 10) | ||
| 94 | 4 -> null // Stretch | ||
| 95 | else -> Rational(16, 9) | ||
| 96 | } | ||
| 97 | ) | ||
| 98 | emulationActivity?.buildPictureInPictureParams() | ||
| 99 | updateScreenLayout() | ||
| 100 | } | ||
| 64 | } else { | 101 | } else { |
| 65 | throw IllegalStateException("EmulationFragment must have EmulationActivity parent") | 102 | throw IllegalStateException("EmulationFragment must have EmulationActivity parent") |
| 66 | } | 103 | } |
| @@ -75,8 +112,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 75 | // So this fragment doesn't restart on configuration changes; i.e. rotation. | 112 | // So this fragment doesn't restart on configuration changes; i.e. rotation. |
| 76 | retainInstance = true | 113 | retainInstance = true |
| 77 | preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) | 114 | preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) |
| 78 | game = requireArguments().parcelable(EmulationActivity.EXTRA_SELECTED_GAME)!! | 115 | emulationState = EmulationState(args.game.path) |
| 79 | emulationState = EmulationState(game.path) | ||
| 80 | } | 116 | } |
| 81 | 117 | ||
| 82 | /** | 118 | /** |
| @@ -100,7 +136,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 100 | updateShowFpsOverlay() | 136 | updateShowFpsOverlay() |
| 101 | 137 | ||
| 102 | binding.inGameMenu.getHeaderView(0).findViewById<TextView>(R.id.text_game_title).text = | 138 | binding.inGameMenu.getHeaderView(0).findViewById<TextView>(R.id.text_game_title).text = |
| 103 | game.title | 139 | args.game.title |
| 104 | binding.inGameMenu.setNavigationItemSelectedListener { | 140 | binding.inGameMenu.setNavigationItemSelectedListener { |
| 105 | when (it.itemId) { | 141 | when (it.itemId) { |
| 106 | R.id.menu_pause_emulation -> { | 142 | R.id.menu_pause_emulation -> { |
| @@ -125,7 +161,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 125 | } | 161 | } |
| 126 | 162 | ||
| 127 | R.id.menu_settings -> { | 163 | R.id.menu_settings -> { |
| 128 | SettingsActivity.launch(requireContext(), SettingsFile.FILE_NAME_CONFIG, "") | 164 | SettingsActivity.launch( |
| 165 | requireContext(), | ||
| 166 | onReturnFromSettings, | ||
| 167 | SettingsFile.FILE_NAME_CONFIG, | ||
| 168 | "" | ||
| 169 | ) | ||
| 129 | true | 170 | true |
| 130 | } | 171 | } |
| 131 | 172 | ||
| @@ -153,6 +194,40 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 153 | if (binding.drawerLayout.isOpen) binding.drawerLayout.close() else binding.drawerLayout.open() | 194 | if (binding.drawerLayout.isOpen) binding.drawerLayout.close() else binding.drawerLayout.open() |
| 154 | } | 195 | } |
| 155 | }) | 196 | }) |
| 197 | |||
| 198 | viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Main) { | ||
| 199 | lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { | ||
| 200 | WindowInfoTracker.getOrCreate(requireContext()) | ||
| 201 | .windowLayoutInfo(requireActivity()) | ||
| 202 | .collect { updateFoldableLayout(requireActivity() as EmulationActivity, it) } | ||
| 203 | } | ||
| 204 | } | ||
| 205 | } | ||
| 206 | |||
| 207 | override fun onConfigurationChanged(newConfig: Configuration) { | ||
| 208 | super.onConfigurationChanged(newConfig) | ||
| 209 | if (emulationActivity?.isInPictureInPictureMode == true) { | ||
| 210 | if (binding.drawerLayout.isOpen) { | ||
| 211 | binding.drawerLayout.close() | ||
| 212 | } | ||
| 213 | if (EmulationMenuSettings.showOverlay) { | ||
| 214 | binding.surfaceInputOverlay.post { binding.surfaceInputOverlay.isVisible = false } | ||
| 215 | } | ||
| 216 | } else { | ||
| 217 | if (EmulationMenuSettings.showOverlay) { | ||
| 218 | binding.surfaceInputOverlay.post { binding.surfaceInputOverlay.isVisible = true } | ||
| 219 | } | ||
| 220 | if (!isInFoldableLayout) { | ||
| 221 | if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) { | ||
| 222 | binding.surfaceInputOverlay.orientation = InputOverlay.PORTRAIT | ||
| 223 | } else { | ||
| 224 | binding.surfaceInputOverlay.orientation = InputOverlay.LANDSCAPE | ||
| 225 | } | ||
| 226 | } | ||
| 227 | if (!binding.surfaceInputOverlay.isInEditMode) { | ||
| 228 | refreshInputOverlay() | ||
| 229 | } | ||
| 230 | } | ||
| 156 | } | 231 | } |
| 157 | 232 | ||
| 158 | override fun onResume() { | 233 | override fun onResume() { |
| @@ -172,6 +247,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 172 | } | 247 | } |
| 173 | ) | 248 | ) |
| 174 | 249 | ||
| 250 | updateScreenLayout() | ||
| 251 | |||
| 175 | emulationState.run(emulationActivity!!.isActivityRecreated) | 252 | emulationState.run(emulationActivity!!.isActivityRecreated) |
| 176 | } | 253 | } |
| 177 | 254 | ||
| @@ -231,31 +308,50 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 231 | } | 308 | } |
| 232 | } | 309 | } |
| 233 | 310 | ||
| 234 | private val Number.toPx get() = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, this.toFloat(), Resources.getSystem().displayMetrics).toInt() | 311 | @SuppressLint("SourceLockedOrientationActivity") |
| 312 | private fun updateScreenLayout() { | ||
| 313 | emulationActivity?.let { | ||
| 314 | it.requestedOrientation = when (IntSetting.RENDERER_SCREEN_LAYOUT.int) { | ||
| 315 | Settings.LayoutOption_MobileLandscape -> ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE | ||
| 316 | Settings.LayoutOption_MobilePortrait -> ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT | ||
| 317 | Settings.LayoutOption_Unspecified -> ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED | ||
| 318 | else -> ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE | ||
| 319 | } | ||
| 320 | } | ||
| 321 | onConfigurationChanged(resources.configuration) | ||
| 322 | } | ||
| 235 | 323 | ||
| 236 | fun updateCurrentLayout(emulationActivity: EmulationActivity, newLayoutInfo: WindowLayoutInfo) { | 324 | private fun updateFoldableLayout(emulationActivity: EmulationActivity, newLayoutInfo: WindowLayoutInfo) { |
| 237 | val isFolding = (newLayoutInfo.displayFeatures.find { it is FoldingFeature } as? FoldingFeature)?.let { | 325 | val isFolding = (newLayoutInfo.displayFeatures.find { it is FoldingFeature } as? FoldingFeature)?.let { |
| 238 | if (it.isSeparating) { | 326 | if (it.isSeparating) { |
| 239 | emulationActivity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED | 327 | emulationActivity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED |
| 240 | if (it.orientation == FoldingFeature.Orientation.HORIZONTAL) { | 328 | if (it.orientation == FoldingFeature.Orientation.HORIZONTAL) { |
| 241 | binding.surfaceEmulation.layoutParams.height = it.bounds.top | 329 | // Restrict emulation and overlays to the top of the screen |
| 330 | binding.emulationContainer.layoutParams.height = it.bounds.top | ||
| 331 | binding.overlayContainer.layoutParams.height = it.bounds.top | ||
| 332 | // Restrict input and menu drawer to the bottom of the screen | ||
| 333 | binding.inputContainer.layoutParams.height = it.bounds.bottom | ||
| 242 | binding.inGameMenu.layoutParams.height = it.bounds.bottom | 334 | binding.inGameMenu.layoutParams.height = it.bounds.bottom |
| 243 | binding.overlayContainer.layoutParams.height = it.bounds.bottom - 48.toPx | 335 | |
| 244 | binding.overlayContainer.updatePadding(0, 0, 0, 24.toPx) | 336 | isInFoldableLayout = true |
| 337 | binding.surfaceInputOverlay.orientation = InputOverlay.FOLDABLE | ||
| 338 | refreshInputOverlay() | ||
| 245 | } | 339 | } |
| 246 | } | 340 | } |
| 247 | it.isSeparating | 341 | it.isSeparating |
| 248 | } ?: false | 342 | } ?: false |
| 249 | if (!isFolding) { | 343 | if (!isFolding) { |
| 250 | binding.surfaceEmulation.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT | 344 | binding.emulationContainer.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT |
| 251 | binding.inGameMenu.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT | 345 | binding.inputContainer.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT |
| 252 | binding.overlayContainer.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT | 346 | binding.overlayContainer.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT |
| 253 | binding.overlayContainer.updatePadding(0, 0, 0, 0) | 347 | binding.inGameMenu.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT |
| 254 | emulationActivity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE | 348 | isInFoldableLayout = false |
| 349 | updateScreenLayout() | ||
| 255 | } | 350 | } |
| 256 | binding.surfaceInputOverlay.requestLayout() | 351 | binding.emulationContainer.requestLayout() |
| 257 | binding.inGameMenu.requestLayout() | 352 | binding.inputContainer.requestLayout() |
| 258 | binding.overlayContainer.requestLayout() | 353 | binding.overlayContainer.requestLayout() |
| 354 | binding.inGameMenu.requestLayout() | ||
| 259 | } | 355 | } |
| 260 | 356 | ||
| 261 | override fun surfaceCreated(holder: SurfaceHolder) { | 357 | override fun surfaceCreated(holder: SurfaceHolder) { |
| @@ -385,7 +481,19 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 385 | popup.show() | 481 | popup.show() |
| 386 | } | 482 | } |
| 387 | 483 | ||
| 484 | @SuppressLint("SourceLockedOrientationActivity") | ||
| 388 | private fun startConfiguringControls() { | 485 | private fun startConfiguringControls() { |
| 486 | // Lock the current orientation to prevent editing inconsistencies | ||
| 487 | if (IntSetting.RENDERER_SCREEN_LAYOUT.int == Settings.LayoutOption_Unspecified) { | ||
| 488 | emulationActivity?.let { | ||
| 489 | it.requestedOrientation = | ||
| 490 | if (resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) { | ||
| 491 | ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT | ||
| 492 | } else { | ||
| 493 | ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE | ||
| 494 | } | ||
| 495 | } | ||
| 496 | } | ||
| 389 | binding.doneControlConfig.visibility = View.VISIBLE | 497 | binding.doneControlConfig.visibility = View.VISIBLE |
| 390 | binding.surfaceInputOverlay.setIsInEditMode(true) | 498 | binding.surfaceInputOverlay.setIsInEditMode(true) |
| 391 | } | 499 | } |
| @@ -393,6 +501,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 393 | private fun stopConfiguringControls() { | 501 | private fun stopConfiguringControls() { |
| 394 | binding.doneControlConfig.visibility = View.GONE | 502 | binding.doneControlConfig.visibility = View.GONE |
| 395 | binding.surfaceInputOverlay.setIsInEditMode(false) | 503 | binding.surfaceInputOverlay.setIsInEditMode(false) |
| 504 | // Unlock the orientation if it was locked for editing | ||
| 505 | if (IntSetting.RENDERER_SCREEN_LAYOUT.int == Settings.LayoutOption_Unspecified) { | ||
| 506 | emulationActivity?.let { | ||
| 507 | it.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED | ||
| 508 | } | ||
| 509 | } | ||
| 396 | } | 510 | } |
| 397 | 511 | ||
| 398 | @SuppressLint("SetTextI18n") | 512 | @SuppressLint("SetTextI18n") |
| @@ -601,13 +715,5 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 601 | 715 | ||
| 602 | companion object { | 716 | companion object { |
| 603 | private val perfStatsUpdateHandler = Handler(Looper.myLooper()!!) | 717 | private val perfStatsUpdateHandler = Handler(Looper.myLooper()!!) |
| 604 | |||
| 605 | fun newInstance(game: Game): EmulationFragment { | ||
| 606 | val args = Bundle() | ||
| 607 | args.putParcelable(EmulationActivity.EXTRA_SELECTED_GAME, game) | ||
| 608 | val fragment = EmulationFragment() | ||
| 609 | fragment.arguments = args | ||
| 610 | return fragment | ||
| 611 | } | ||
| 612 | } | 718 | } |
| 613 | } | 719 | } |
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..d12d08e9f 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 | |||
| @@ -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 | ||
| @@ -233,10 +235,6 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context | |||
| 233 | val fingerPositionX = event.getX(pointerIndex).toInt() | 235 | val fingerPositionX = event.getX(pointerIndex).toInt() |
| 234 | val fingerPositionY = event.getY(pointerIndex).toInt() | 236 | val fingerPositionY = event.getY(pointerIndex).toInt() |
| 235 | 237 | ||
| 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) { | 238 | for (button in overlayButtons) { |
| 241 | // Determine the button state to apply based on the MotionEvent action flag. | 239 | // Determine the button state to apply based on the MotionEvent action flag. |
| 242 | when (event.action and MotionEvent.ACTION_MASK) { | 240 | when (event.action and MotionEvent.ACTION_MASK) { |
| @@ -266,7 +264,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context | |||
| 266 | buttonBeingConfigured!!.buttonId, | 264 | buttonBeingConfigured!!.buttonId, |
| 267 | buttonBeingConfigured!!.bounds.centerX(), | 265 | buttonBeingConfigured!!.bounds.centerX(), |
| 268 | buttonBeingConfigured!!.bounds.centerY(), | 266 | buttonBeingConfigured!!.bounds.centerY(), |
| 269 | "" | 267 | orientation |
| 270 | ) | 268 | ) |
| 271 | buttonBeingConfigured = null | 269 | buttonBeingConfigured = null |
| 272 | } | 270 | } |
| @@ -299,7 +297,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context | |||
| 299 | dpadBeingConfigured!!.upId, | 297 | dpadBeingConfigured!!.upId, |
| 300 | dpadBeingConfigured!!.bounds.centerX(), | 298 | dpadBeingConfigured!!.bounds.centerX(), |
| 301 | dpadBeingConfigured!!.bounds.centerY(), | 299 | dpadBeingConfigured!!.bounds.centerY(), |
| 302 | "" | 300 | orientation |
| 303 | ) | 301 | ) |
| 304 | dpadBeingConfigured = null | 302 | dpadBeingConfigured = null |
| 305 | } | 303 | } |
| @@ -330,7 +328,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context | |||
| 330 | joystickBeingConfigured!!.buttonId, | 328 | joystickBeingConfigured!!.buttonId, |
| 331 | joystickBeingConfigured!!.bounds.centerX(), | 329 | joystickBeingConfigured!!.bounds.centerX(), |
| 332 | joystickBeingConfigured!!.bounds.centerY(), | 330 | joystickBeingConfigured!!.bounds.centerY(), |
| 333 | "" | 331 | orientation |
| 334 | ) | 332 | ) |
| 335 | joystickBeingConfigured = null | 333 | joystickBeingConfigured = null |
| 336 | } | 334 | } |
| @@ -533,8 +531,6 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context | |||
| 533 | overlayButtons.clear() | 531 | overlayButtons.clear() |
| 534 | overlayDpads.clear() | 532 | overlayDpads.clear() |
| 535 | overlayJoysticks.clear() | 533 | overlayJoysticks.clear() |
| 536 | val orientation = | ||
| 537 | if (resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) "-Portrait" else "" | ||
| 538 | 534 | ||
| 539 | // Add all the enabled overlay items back to the HashSet. | 535 | // Add all the enabled overlay items back to the HashSet. |
| 540 | if (EmulationMenuSettings.showOverlay) { | 536 | if (EmulationMenuSettings.showOverlay) { |
| @@ -548,8 +544,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context | |||
| 548 | val min = windowSize.first | 544 | val min = windowSize.first |
| 549 | val max = windowSize.second | 545 | val max = windowSize.second |
| 550 | PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext).edit() | 546 | PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext).edit() |
| 551 | .putFloat("$sharedPrefsId$orientation-X", (x - min.x).toFloat() / max.x) | 547 | .putFloat("$sharedPrefsId-X$orientation", (x - min.x).toFloat() / max.x) |
| 552 | .putFloat("$sharedPrefsId$orientation-Y", (y - min.y).toFloat() / max.y) | 548 | .putFloat("$sharedPrefsId-Y$orientation", (y - min.y).toFloat() / max.y) |
| 553 | .apply() | 549 | .apply() |
| 554 | } | 550 | } |
| 555 | 551 | ||
| @@ -558,145 +554,250 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context | |||
| 558 | } | 554 | } |
| 559 | 555 | ||
| 560 | private fun defaultOverlay() { | 556 | private fun defaultOverlay() { |
| 561 | if (!preferences.getBoolean(Settings.PREF_OVERLAY_INIT, false)) { | 557 | if (!preferences.getBoolean("${Settings.PREF_OVERLAY_INIT}$orientation", false)) { |
| 562 | defaultOverlayLandscape() | 558 | defaultOverlayByLayout(orientation) |
| 563 | } | 559 | } |
| 564 | 560 | ||
| 565 | resetButtonPlacement() | 561 | resetButtonPlacement() |
| 566 | preferences.edit() | 562 | preferences.edit() |
| 567 | .putBoolean(Settings.PREF_OVERLAY_INIT, true) | 563 | .putBoolean("${Settings.PREF_OVERLAY_INIT}$orientation", true) |
| 568 | .apply() | 564 | .apply() |
| 569 | } | 565 | } |
| 570 | 566 | ||
| 571 | fun resetButtonPlacement() { | 567 | fun resetButtonPlacement() { |
| 572 | defaultOverlayLandscape() | 568 | defaultOverlayByLayout(orientation) |
| 573 | refreshControls() | 569 | refreshControls() |
| 574 | } | 570 | } |
| 575 | 571 | ||
| 576 | private fun defaultOverlayLandscape() { | 572 | private val landscapeResources = arrayOf( |
| 573 | R.integer.SWITCH_BUTTON_A_X, | ||
| 574 | R.integer.SWITCH_BUTTON_A_Y, | ||
| 575 | R.integer.SWITCH_BUTTON_B_X, | ||
| 576 | R.integer.SWITCH_BUTTON_B_Y, | ||
| 577 | R.integer.SWITCH_BUTTON_X_X, | ||
| 578 | R.integer.SWITCH_BUTTON_X_Y, | ||
| 579 | R.integer.SWITCH_BUTTON_Y_X, | ||
| 580 | R.integer.SWITCH_BUTTON_Y_Y, | ||
| 581 | R.integer.SWITCH_TRIGGER_ZL_X, | ||
| 582 | R.integer.SWITCH_TRIGGER_ZL_Y, | ||
| 583 | R.integer.SWITCH_TRIGGER_ZR_X, | ||
| 584 | R.integer.SWITCH_TRIGGER_ZR_Y, | ||
| 585 | R.integer.SWITCH_BUTTON_DPAD_X, | ||
| 586 | R.integer.SWITCH_BUTTON_DPAD_Y, | ||
| 587 | R.integer.SWITCH_TRIGGER_L_X, | ||
| 588 | R.integer.SWITCH_TRIGGER_L_Y, | ||
| 589 | R.integer.SWITCH_TRIGGER_R_X, | ||
| 590 | R.integer.SWITCH_TRIGGER_R_Y, | ||
| 591 | R.integer.SWITCH_BUTTON_PLUS_X, | ||
| 592 | R.integer.SWITCH_BUTTON_PLUS_Y, | ||
| 593 | R.integer.SWITCH_BUTTON_MINUS_X, | ||
| 594 | R.integer.SWITCH_BUTTON_MINUS_Y, | ||
| 595 | R.integer.SWITCH_BUTTON_HOME_X, | ||
| 596 | R.integer.SWITCH_BUTTON_HOME_Y, | ||
| 597 | R.integer.SWITCH_BUTTON_CAPTURE_X, | ||
| 598 | R.integer.SWITCH_BUTTON_CAPTURE_Y, | ||
| 599 | R.integer.SWITCH_STICK_R_X, | ||
| 600 | R.integer.SWITCH_STICK_R_Y, | ||
| 601 | R.integer.SWITCH_STICK_L_X, | ||
| 602 | R.integer.SWITCH_STICK_L_Y | ||
| 603 | ) | ||
| 604 | |||
| 605 | private val portraitResources = arrayOf( | ||
| 606 | R.integer.SWITCH_BUTTON_A_X_PORTRAIT, | ||
| 607 | R.integer.SWITCH_BUTTON_A_Y_PORTRAIT, | ||
| 608 | R.integer.SWITCH_BUTTON_B_X_PORTRAIT, | ||
| 609 | R.integer.SWITCH_BUTTON_B_Y_PORTRAIT, | ||
| 610 | R.integer.SWITCH_BUTTON_X_X_PORTRAIT, | ||
| 611 | R.integer.SWITCH_BUTTON_X_Y_PORTRAIT, | ||
| 612 | R.integer.SWITCH_BUTTON_Y_X_PORTRAIT, | ||
| 613 | R.integer.SWITCH_BUTTON_Y_Y_PORTRAIT, | ||
| 614 | R.integer.SWITCH_TRIGGER_ZL_X_PORTRAIT, | ||
| 615 | R.integer.SWITCH_TRIGGER_ZL_Y_PORTRAIT, | ||
| 616 | R.integer.SWITCH_TRIGGER_ZR_X_PORTRAIT, | ||
| 617 | R.integer.SWITCH_TRIGGER_ZR_Y_PORTRAIT, | ||
| 618 | R.integer.SWITCH_BUTTON_DPAD_X_PORTRAIT, | ||
| 619 | R.integer.SWITCH_BUTTON_DPAD_Y_PORTRAIT, | ||
| 620 | R.integer.SWITCH_TRIGGER_L_X_PORTRAIT, | ||
| 621 | R.integer.SWITCH_TRIGGER_L_Y_PORTRAIT, | ||
| 622 | R.integer.SWITCH_TRIGGER_R_X_PORTRAIT, | ||
| 623 | R.integer.SWITCH_TRIGGER_R_Y_PORTRAIT, | ||
| 624 | R.integer.SWITCH_BUTTON_PLUS_X_PORTRAIT, | ||
| 625 | R.integer.SWITCH_BUTTON_PLUS_Y_PORTRAIT, | ||
| 626 | R.integer.SWITCH_BUTTON_MINUS_X_PORTRAIT, | ||
| 627 | R.integer.SWITCH_BUTTON_MINUS_Y_PORTRAIT, | ||
| 628 | R.integer.SWITCH_BUTTON_HOME_X_PORTRAIT, | ||
| 629 | R.integer.SWITCH_BUTTON_HOME_Y_PORTRAIT, | ||
| 630 | R.integer.SWITCH_BUTTON_CAPTURE_X_PORTRAIT, | ||
| 631 | R.integer.SWITCH_BUTTON_CAPTURE_Y_PORTRAIT, | ||
| 632 | R.integer.SWITCH_STICK_R_X_PORTRAIT, | ||
| 633 | R.integer.SWITCH_STICK_R_Y_PORTRAIT, | ||
| 634 | R.integer.SWITCH_STICK_L_X_PORTRAIT, | ||
| 635 | R.integer.SWITCH_STICK_L_Y_PORTRAIT | ||
| 636 | ) | ||
| 637 | |||
| 638 | private val foldableResources = arrayOf( | ||
| 639 | R.integer.SWITCH_BUTTON_A_X_FOLDABLE, | ||
| 640 | R.integer.SWITCH_BUTTON_A_Y_FOLDABLE, | ||
| 641 | R.integer.SWITCH_BUTTON_B_X_FOLDABLE, | ||
| 642 | R.integer.SWITCH_BUTTON_B_Y_FOLDABLE, | ||
| 643 | R.integer.SWITCH_BUTTON_X_X_FOLDABLE, | ||
| 644 | R.integer.SWITCH_BUTTON_X_Y_FOLDABLE, | ||
| 645 | R.integer.SWITCH_BUTTON_Y_X_FOLDABLE, | ||
| 646 | R.integer.SWITCH_BUTTON_Y_Y_FOLDABLE, | ||
| 647 | R.integer.SWITCH_TRIGGER_ZL_X_FOLDABLE, | ||
| 648 | R.integer.SWITCH_TRIGGER_ZL_Y_FOLDABLE, | ||
| 649 | R.integer.SWITCH_TRIGGER_ZR_X_FOLDABLE, | ||
| 650 | R.integer.SWITCH_TRIGGER_ZR_Y_FOLDABLE, | ||
| 651 | R.integer.SWITCH_BUTTON_DPAD_X_FOLDABLE, | ||
| 652 | R.integer.SWITCH_BUTTON_DPAD_Y_FOLDABLE, | ||
| 653 | R.integer.SWITCH_TRIGGER_L_X_FOLDABLE, | ||
| 654 | R.integer.SWITCH_TRIGGER_L_Y_FOLDABLE, | ||
| 655 | R.integer.SWITCH_TRIGGER_R_X_FOLDABLE, | ||
| 656 | R.integer.SWITCH_TRIGGER_R_Y_FOLDABLE, | ||
| 657 | R.integer.SWITCH_BUTTON_PLUS_X_FOLDABLE, | ||
| 658 | R.integer.SWITCH_BUTTON_PLUS_Y_FOLDABLE, | ||
| 659 | R.integer.SWITCH_BUTTON_MINUS_X_FOLDABLE, | ||
| 660 | R.integer.SWITCH_BUTTON_MINUS_Y_FOLDABLE, | ||
| 661 | R.integer.SWITCH_BUTTON_HOME_X_FOLDABLE, | ||
| 662 | R.integer.SWITCH_BUTTON_HOME_Y_FOLDABLE, | ||
| 663 | R.integer.SWITCH_BUTTON_CAPTURE_X_FOLDABLE, | ||
| 664 | R.integer.SWITCH_BUTTON_CAPTURE_Y_FOLDABLE, | ||
| 665 | R.integer.SWITCH_STICK_R_X_FOLDABLE, | ||
| 666 | R.integer.SWITCH_STICK_R_Y_FOLDABLE, | ||
| 667 | R.integer.SWITCH_STICK_L_X_FOLDABLE, | ||
| 668 | R.integer.SWITCH_STICK_L_Y_FOLDABLE | ||
| 669 | ) | ||
| 670 | |||
| 671 | private fun getResourceValue(orientation: String, position: Int) : Float { | ||
| 672 | return when (orientation) { | ||
| 673 | PORTRAIT -> resources.getInteger(portraitResources[position]).toFloat() / 1000 | ||
| 674 | FOLDABLE -> resources.getInteger(foldableResources[position]).toFloat() / 1000 | ||
| 675 | else -> resources.getInteger(landscapeResources[position]).toFloat() / 1000 | ||
| 676 | } | ||
| 677 | } | ||
| 678 | |||
| 679 | private fun defaultOverlayByLayout(orientation: String) { | ||
| 577 | // Each value represents the position of the button in relation to the screen size without insets. | 680 | // Each value represents the position of the button in relation to the screen size without insets. |
| 578 | preferences.edit() | 681 | preferences.edit() |
| 579 | .putFloat( | 682 | .putFloat( |
| 580 | ButtonType.BUTTON_A.toString() + "-X", | 683 | ButtonType.BUTTON_A.toString() + "-X$orientation", |
| 581 | resources.getInteger(R.integer.SWITCH_BUTTON_A_X).toFloat() / 1000 | 684 | getResourceValue(orientation, 0) |
| 582 | ) | 685 | ) |
| 583 | .putFloat( | 686 | .putFloat( |
| 584 | ButtonType.BUTTON_A.toString() + "-Y", | 687 | ButtonType.BUTTON_A.toString() + "-Y$orientation", |
| 585 | resources.getInteger(R.integer.SWITCH_BUTTON_A_Y).toFloat() / 1000 | 688 | getResourceValue(orientation, 1) |
| 586 | ) | 689 | ) |
| 587 | .putFloat( | 690 | .putFloat( |
| 588 | ButtonType.BUTTON_B.toString() + "-X", | 691 | ButtonType.BUTTON_B.toString() + "-X$orientation", |
| 589 | resources.getInteger(R.integer.SWITCH_BUTTON_B_X).toFloat() / 1000 | 692 | getResourceValue(orientation, 2) |
| 590 | ) | 693 | ) |
| 591 | .putFloat( | 694 | .putFloat( |
| 592 | ButtonType.BUTTON_B.toString() + "-Y", | 695 | ButtonType.BUTTON_B.toString() + "-Y$orientation", |
| 593 | resources.getInteger(R.integer.SWITCH_BUTTON_B_Y).toFloat() / 1000 | 696 | getResourceValue(orientation, 3) |
| 594 | ) | 697 | ) |
| 595 | .putFloat( | 698 | .putFloat( |
| 596 | ButtonType.BUTTON_X.toString() + "-X", | 699 | ButtonType.BUTTON_X.toString() + "-X$orientation", |
| 597 | resources.getInteger(R.integer.SWITCH_BUTTON_X_X).toFloat() / 1000 | 700 | getResourceValue(orientation, 4) |
| 598 | ) | 701 | ) |
| 599 | .putFloat( | 702 | .putFloat( |
| 600 | ButtonType.BUTTON_X.toString() + "-Y", | 703 | ButtonType.BUTTON_X.toString() + "-Y$orientation", |
| 601 | resources.getInteger(R.integer.SWITCH_BUTTON_X_Y).toFloat() / 1000 | 704 | getResourceValue(orientation, 5) |
| 602 | ) | 705 | ) |
| 603 | .putFloat( | 706 | .putFloat( |
| 604 | ButtonType.BUTTON_Y.toString() + "-X", | 707 | ButtonType.BUTTON_Y.toString() + "-X$orientation", |
| 605 | resources.getInteger(R.integer.SWITCH_BUTTON_Y_X).toFloat() / 1000 | 708 | getResourceValue(orientation, 6) |
| 606 | ) | 709 | ) |
| 607 | .putFloat( | 710 | .putFloat( |
| 608 | ButtonType.BUTTON_Y.toString() + "-Y", | 711 | ButtonType.BUTTON_Y.toString() + "-Y$orientation", |
| 609 | resources.getInteger(R.integer.SWITCH_BUTTON_Y_Y).toFloat() / 1000 | 712 | getResourceValue(orientation, 7) |
| 610 | ) | 713 | ) |
| 611 | .putFloat( | 714 | .putFloat( |
| 612 | ButtonType.TRIGGER_ZL.toString() + "-X", | 715 | ButtonType.TRIGGER_ZL.toString() + "-X$orientation", |
| 613 | resources.getInteger(R.integer.SWITCH_TRIGGER_ZL_X).toFloat() / 1000 | 716 | getResourceValue(orientation, 8) |
| 614 | ) | 717 | ) |
| 615 | .putFloat( | 718 | .putFloat( |
| 616 | ButtonType.TRIGGER_ZL.toString() + "-Y", | 719 | ButtonType.TRIGGER_ZL.toString() + "-Y$orientation", |
| 617 | resources.getInteger(R.integer.SWITCH_TRIGGER_ZL_Y).toFloat() / 1000 | 720 | getResourceValue(orientation, 9) |
| 618 | ) | 721 | ) |
| 619 | .putFloat( | 722 | .putFloat( |
| 620 | ButtonType.TRIGGER_ZR.toString() + "-X", | 723 | ButtonType.TRIGGER_ZR.toString() + "-X$orientation", |
| 621 | resources.getInteger(R.integer.SWITCH_TRIGGER_ZR_X).toFloat() / 1000 | 724 | getResourceValue(orientation, 10) |
| 622 | ) | 725 | ) |
| 623 | .putFloat( | 726 | .putFloat( |
| 624 | ButtonType.TRIGGER_ZR.toString() + "-Y", | 727 | ButtonType.TRIGGER_ZR.toString() + "-Y$orientation", |
| 625 | resources.getInteger(R.integer.SWITCH_TRIGGER_ZR_Y).toFloat() / 1000 | 728 | getResourceValue(orientation, 11) |
| 626 | ) | 729 | ) |
| 627 | .putFloat( | 730 | .putFloat( |
| 628 | ButtonType.DPAD_UP.toString() + "-X", | 731 | ButtonType.DPAD_UP.toString() + "-X$orientation", |
| 629 | resources.getInteger(R.integer.SWITCH_BUTTON_DPAD_X).toFloat() / 1000 | 732 | getResourceValue(orientation, 12) |
| 630 | ) | 733 | ) |
| 631 | .putFloat( | 734 | .putFloat( |
| 632 | ButtonType.DPAD_UP.toString() + "-Y", | 735 | ButtonType.DPAD_UP.toString() + "-Y$orientation", |
| 633 | resources.getInteger(R.integer.SWITCH_BUTTON_DPAD_Y).toFloat() / 1000 | 736 | getResourceValue(orientation, 13) |
| 634 | ) | 737 | ) |
| 635 | .putFloat( | 738 | .putFloat( |
| 636 | ButtonType.TRIGGER_L.toString() + "-X", | 739 | ButtonType.TRIGGER_L.toString() + "-X$orientation", |
| 637 | resources.getInteger(R.integer.SWITCH_TRIGGER_L_X).toFloat() / 1000 | 740 | getResourceValue(orientation, 14) |
| 638 | ) | 741 | ) |
| 639 | .putFloat( | 742 | .putFloat( |
| 640 | ButtonType.TRIGGER_L.toString() + "-Y", | 743 | ButtonType.TRIGGER_L.toString() + "-Y$orientation", |
| 641 | resources.getInteger(R.integer.SWITCH_TRIGGER_L_Y).toFloat() / 1000 | 744 | getResourceValue(orientation, 15) |
| 642 | ) | 745 | ) |
| 643 | .putFloat( | 746 | .putFloat( |
| 644 | ButtonType.TRIGGER_R.toString() + "-X", | 747 | ButtonType.TRIGGER_R.toString() + "-X$orientation", |
| 645 | resources.getInteger(R.integer.SWITCH_TRIGGER_R_X).toFloat() / 1000 | 748 | getResourceValue(orientation, 16) |
| 646 | ) | 749 | ) |
| 647 | .putFloat( | 750 | .putFloat( |
| 648 | ButtonType.TRIGGER_R.toString() + "-Y", | 751 | ButtonType.TRIGGER_R.toString() + "-Y$orientation", |
| 649 | resources.getInteger(R.integer.SWITCH_TRIGGER_R_Y).toFloat() / 1000 | 752 | getResourceValue(orientation, 17) |
| 650 | ) | 753 | ) |
| 651 | .putFloat( | 754 | .putFloat( |
| 652 | ButtonType.BUTTON_PLUS.toString() + "-X", | 755 | ButtonType.BUTTON_PLUS.toString() + "-X$orientation", |
| 653 | resources.getInteger(R.integer.SWITCH_BUTTON_PLUS_X).toFloat() / 1000 | 756 | getResourceValue(orientation, 18) |
| 654 | ) | 757 | ) |
| 655 | .putFloat( | 758 | .putFloat( |
| 656 | ButtonType.BUTTON_PLUS.toString() + "-Y", | 759 | ButtonType.BUTTON_PLUS.toString() + "-Y$orientation", |
| 657 | resources.getInteger(R.integer.SWITCH_BUTTON_PLUS_Y).toFloat() / 1000 | 760 | getResourceValue(orientation, 19) |
| 658 | ) | 761 | ) |
| 659 | .putFloat( | 762 | .putFloat( |
| 660 | ButtonType.BUTTON_MINUS.toString() + "-X", | 763 | ButtonType.BUTTON_MINUS.toString() + "-X$orientation", |
| 661 | resources.getInteger(R.integer.SWITCH_BUTTON_MINUS_X).toFloat() / 1000 | 764 | getResourceValue(orientation, 20) |
| 662 | ) | 765 | ) |
| 663 | .putFloat( | 766 | .putFloat( |
| 664 | ButtonType.BUTTON_MINUS.toString() + "-Y", | 767 | ButtonType.BUTTON_MINUS.toString() + "-Y$orientation", |
| 665 | resources.getInteger(R.integer.SWITCH_BUTTON_MINUS_Y).toFloat() / 1000 | 768 | getResourceValue(orientation, 21) |
| 666 | ) | 769 | ) |
| 667 | .putFloat( | 770 | .putFloat( |
| 668 | ButtonType.BUTTON_HOME.toString() + "-X", | 771 | ButtonType.BUTTON_HOME.toString() + "-X$orientation", |
| 669 | resources.getInteger(R.integer.SWITCH_BUTTON_HOME_X).toFloat() / 1000 | 772 | getResourceValue(orientation, 22) |
| 670 | ) | 773 | ) |
| 671 | .putFloat( | 774 | .putFloat( |
| 672 | ButtonType.BUTTON_HOME.toString() + "-Y", | 775 | ButtonType.BUTTON_HOME.toString() + "-Y$orientation", |
| 673 | resources.getInteger(R.integer.SWITCH_BUTTON_HOME_Y).toFloat() / 1000 | 776 | getResourceValue(orientation, 23) |
| 674 | ) | 777 | ) |
| 675 | .putFloat( | 778 | .putFloat( |
| 676 | ButtonType.BUTTON_CAPTURE.toString() + "-X", | 779 | ButtonType.BUTTON_CAPTURE.toString() + "-X$orientation", |
| 677 | resources.getInteger(R.integer.SWITCH_BUTTON_CAPTURE_X) | 780 | getResourceValue(orientation, 24) |
| 678 | .toFloat() / 1000 | ||
| 679 | ) | 781 | ) |
| 680 | .putFloat( | 782 | .putFloat( |
| 681 | ButtonType.BUTTON_CAPTURE.toString() + "-Y", | 783 | ButtonType.BUTTON_CAPTURE.toString() + "-Y$orientation", |
| 682 | resources.getInteger(R.integer.SWITCH_BUTTON_CAPTURE_Y) | 784 | getResourceValue(orientation, 25) |
| 683 | .toFloat() / 1000 | ||
| 684 | ) | 785 | ) |
| 685 | .putFloat( | 786 | .putFloat( |
| 686 | ButtonType.STICK_R.toString() + "-X", | 787 | ButtonType.STICK_R.toString() + "-X$orientation", |
| 687 | resources.getInteger(R.integer.SWITCH_STICK_R_X).toFloat() / 1000 | 788 | getResourceValue(orientation, 26) |
| 688 | ) | 789 | ) |
| 689 | .putFloat( | 790 | .putFloat( |
| 690 | ButtonType.STICK_R.toString() + "-Y", | 791 | ButtonType.STICK_R.toString() + "-Y$orientation", |
| 691 | resources.getInteger(R.integer.SWITCH_STICK_R_Y).toFloat() / 1000 | 792 | getResourceValue(orientation, 27) |
| 692 | ) | 793 | ) |
| 693 | .putFloat( | 794 | .putFloat( |
| 694 | ButtonType.STICK_L.toString() + "-X", | 795 | ButtonType.STICK_L.toString() + "-X$orientation", |
| 695 | resources.getInteger(R.integer.SWITCH_STICK_L_X).toFloat() / 1000 | 796 | getResourceValue(orientation, 28) |
| 696 | ) | 797 | ) |
| 697 | .putFloat( | 798 | .putFloat( |
| 698 | ButtonType.STICK_L.toString() + "-Y", | 799 | ButtonType.STICK_L.toString() + "-Y$orientation", |
| 699 | resources.getInteger(R.integer.SWITCH_STICK_L_Y).toFloat() / 1000 | 800 | getResourceValue(orientation, 29) |
| 700 | ) | 801 | ) |
| 701 | .apply() | 802 | .apply() |
| 702 | } | 803 | } |
| @@ -709,6 +810,10 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context | |||
| 709 | private val preferences: SharedPreferences = | 810 | private val preferences: SharedPreferences = |
| 710 | PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) | 811 | PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) |
| 711 | 812 | ||
| 813 | const val LANDSCAPE = "" | ||
| 814 | const val PORTRAIT = "_Portrait" | ||
| 815 | const val FOLDABLE = "_Foldable" | ||
| 816 | |||
| 712 | /** | 817 | /** |
| 713 | * Resizes a [Bitmap] by a given scale factor | 818 | * Resizes a [Bitmap] by a given scale factor |
| 714 | * | 819 | * |
| @@ -754,9 +859,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context | |||
| 754 | */ | 859 | */ |
| 755 | private fun getSafeScreenSize(context: Context): Pair<Point, Point> { | 860 | private fun getSafeScreenSize(context: Context): Pair<Point, Point> { |
| 756 | // Get screen size | 861 | // Get screen size |
| 757 | val windowMetrics = | 862 | val windowMetrics = WindowMetricsCalculator.getOrCreate() |
| 758 | WindowMetricsCalculator.getOrCreate() | 863 | .computeCurrentWindowMetrics(context as Activity) |
| 759 | .computeCurrentWindowMetrics(context as Activity) | ||
| 760 | var maxY = windowMetrics.bounds.height().toFloat() | 864 | var maxY = windowMetrics.bounds.height().toFloat() |
| 761 | var maxX = windowMetrics.bounds.width().toFloat() | 865 | var maxX = windowMetrics.bounds.width().toFloat() |
| 762 | var minY = 0 | 866 | var minY = 0 |
| @@ -769,9 +873,9 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context | |||
| 769 | val insets = context.windowManager.currentWindowMetrics.windowInsets.displayCutout | 873 | val insets = context.windowManager.currentWindowMetrics.windowInsets.displayCutout |
| 770 | if (insets != null) { | 874 | if (insets != null) { |
| 771 | if (insets.boundingRectTop.bottom != 0 && insets.boundingRectTop.bottom > maxY / 2) | 875 | if (insets.boundingRectTop.bottom != 0 && insets.boundingRectTop.bottom > maxY / 2) |
| 772 | insets.boundingRectTop.bottom.toFloat() else maxY | 876 | maxY = insets.boundingRectTop.bottom.toFloat() |
| 773 | if (insets.boundingRectRight.left != 0 && insets.boundingRectRight.left > maxX / 2) | 877 | if (insets.boundingRectRight.left != 0 && insets.boundingRectRight.left > maxX / 2) |
| 774 | insets.boundingRectRight.left.toFloat() else maxX | 878 | maxX = insets.boundingRectRight.left.toFloat() |
| 775 | 879 | ||
| 776 | minX = insets.boundingRectLeft.right - insets.boundingRectLeft.left | 880 | minX = insets.boundingRectLeft.right - insets.boundingRectLeft.left |
| 777 | minY = insets.boundingRectBottom.top - insets.boundingRectBottom.bottom | 881 | minY = insets.boundingRectBottom.top - insets.boundingRectBottom.bottom |
| @@ -878,8 +982,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context | |||
| 878 | 982 | ||
| 879 | // The X and Y coordinates of the InputOverlayDrawableButton on the InputOverlay. | 983 | // The X and Y coordinates of the InputOverlayDrawableButton on the InputOverlay. |
| 880 | // These were set in the input overlay configuration menu. | 984 | // These were set in the input overlay configuration menu. |
| 881 | val xKey = "$buttonId$orientation-X" | 985 | val xKey = "$buttonId-X$orientation" |
| 882 | val yKey = "$buttonId$orientation-Y" | 986 | val yKey = "$buttonId-Y$orientation" |
| 883 | val drawableXPercent = sPrefs.getFloat(xKey, 0f) | 987 | val drawableXPercent = sPrefs.getFloat(xKey, 0f) |
| 884 | val drawableYPercent = sPrefs.getFloat(yKey, 0f) | 988 | val drawableYPercent = sPrefs.getFloat(yKey, 0f) |
| 885 | val drawableX = (drawableXPercent * max.x + min.x).toInt() | 989 | val drawableX = (drawableXPercent * max.x + min.x).toInt() |
| @@ -959,8 +1063,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context | |||
| 959 | 1063 | ||
| 960 | // The X and Y coordinates of the InputOverlayDrawableDpad on the InputOverlay. | 1064 | // The X and Y coordinates of the InputOverlayDrawableDpad on the InputOverlay. |
| 961 | // These were set in the input overlay configuration menu. | 1065 | // These were set in the input overlay configuration menu. |
| 962 | val drawableXPercent = sPrefs.getFloat("${ButtonType.DPAD_UP}$orientation-X", 0f) | 1066 | val drawableXPercent = sPrefs.getFloat("${ButtonType.DPAD_UP}-X$orientation", 0f) |
| 963 | val drawableYPercent = sPrefs.getFloat("${ButtonType.DPAD_UP}$orientation-Y", 0f) | 1067 | val drawableYPercent = sPrefs.getFloat("${ButtonType.DPAD_UP}-Y$orientation", 0f) |
| 964 | val drawableX = (drawableXPercent * max.x + min.x).toInt() | 1068 | val drawableX = (drawableXPercent * max.x + min.x).toInt() |
| 965 | val drawableY = (drawableYPercent * max.y + min.y).toInt() | 1069 | val drawableY = (drawableYPercent * max.y + min.y).toInt() |
| 966 | val width = overlayDrawable.width | 1070 | val width = overlayDrawable.width |
| @@ -1026,8 +1130,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context | |||
| 1026 | 1130 | ||
| 1027 | // The X and Y coordinates of the InputOverlayDrawableButton on the InputOverlay. | 1131 | // The X and Y coordinates of the InputOverlayDrawableButton on the InputOverlay. |
| 1028 | // These were set in the input overlay configuration menu. | 1132 | // These were set in the input overlay configuration menu. |
| 1029 | val drawableXPercent = sPrefs.getFloat("$button$orientation-X", 0f) | 1133 | val drawableXPercent = sPrefs.getFloat("$button-X$orientation", 0f) |
| 1030 | val drawableYPercent = sPrefs.getFloat("$button$orientation-Y", 0f) | 1134 | val drawableYPercent = sPrefs.getFloat("$button-Y$orientation", 0f) |
| 1031 | val drawableX = (drawableXPercent * max.x + min.x).toInt() | 1135 | val drawableX = (drawableXPercent * max.x + min.x).toInt() |
| 1032 | val drawableY = (drawableYPercent * max.y + min.y).toInt() | 1136 | val drawableY = (drawableYPercent * max.y + min.y).toInt() |
| 1033 | val outerScale = 1.66f | 1137 | val outerScale = 1.66f |
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/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/activity_emulation.xml b/src/android/app/src/main/res/layout/activity_emulation.xml index f6360a65b..139065d3d 100644 --- a/src/android/app/src/main/res/layout/activity_emulation.xml +++ b/src/android/app/src/main/res/layout/activity_emulation.xml | |||
| @@ -1,13 +1,9 @@ | |||
| 1 | <FrameLayout | 1 | <androidx.fragment.app.FragmentContainerView |
| 2 | xmlns:android="http://schemas.android.com/apk/res/android" | 2 | xmlns:android="http://schemas.android.com/apk/res/android" |
| 3 | android:id="@+id/frame_content" | 3 | xmlns:app="http://schemas.android.com/apk/res-auto" |
| 4 | android:id="@+id/fragment_container" | ||
| 5 | android:name="androidx.navigation.fragment.NavHostFragment" | ||
| 4 | android:layout_width="match_parent" | 6 | android:layout_width="match_parent" |
| 5 | android:layout_height="match_parent" | 7 | android:layout_height="match_parent" |
| 6 | android:keepScreenOn="true"> | 8 | android:keepScreenOn="true" |
| 7 | 9 | app:defaultNavHost="true" /> | |
| 8 | <FrameLayout | ||
| 9 | android:id="@+id/frame_emulation_fragment" | ||
| 10 | android:layout_width="match_parent" | ||
| 11 | android:layout_height="match_parent" /> | ||
| 12 | |||
| 13 | </FrameLayout> | ||
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/navigation/emulation_navigation.xml b/src/android/app/src/main/res/navigation/emulation_navigation.xml new file mode 100644 index 000000000..8208f4c2c --- /dev/null +++ b/src/android/app/src/main/res/navigation/emulation_navigation.xml | |||
| @@ -0,0 +1,18 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <navigation xmlns:android="http://schemas.android.com/apk/res/android" | ||
| 3 | xmlns:app="http://schemas.android.com/apk/res-auto" | ||
| 4 | xmlns:tools="http://schemas.android.com/tools" | ||
| 5 | android:id="@+id/emulation_navigation" | ||
| 6 | app:startDestination="@id/emulationFragment"> | ||
| 7 | |||
| 8 | <fragment | ||
| 9 | android:id="@+id/emulationFragment" | ||
| 10 | android:name="org.yuzu.yuzu_emu.fragments.EmulationFragment" | ||
| 11 | android:label="fragment_emulation" | ||
| 12 | tools:layout="@layout/fragment_emulation" > | ||
| 13 | <argument | ||
| 14 | android:name="game" | ||
| 15 | app:argType="org.yuzu.yuzu_emu.model.Game" /> | ||
| 16 | </fragment> | ||
| 17 | |||
| 18 | </navigation> | ||
diff --git a/src/android/app/src/main/res/navigation/home_navigation.xml b/src/android/app/src/main/res/navigation/home_navigation.xml index 48072683e..fcebba726 100644 --- a/src/android/app/src/main/res/navigation/home_navigation.xml +++ b/src/android/app/src/main/res/navigation/home_navigation.xml | |||
| @@ -56,4 +56,18 @@ | |||
| 56 | android:name="org.yuzu.yuzu_emu.fragments.LicensesFragment" | 56 | android:name="org.yuzu.yuzu_emu.fragments.LicensesFragment" |
| 57 | android:label="LicensesFragment" /> | 57 | android:label="LicensesFragment" /> |
| 58 | 58 | ||
| 59 | <activity | ||
| 60 | android:id="@+id/emulationActivity" | ||
| 61 | android:name="org.yuzu.yuzu_emu.activities.EmulationActivity" | ||
| 62 | android:label="EmulationActivity"> | ||
| 63 | <argument | ||
| 64 | android:name="game" | ||
| 65 | app:argType="org.yuzu.yuzu_emu.model.Game" /> | ||
| 66 | </activity> | ||
| 67 | |||
| 68 | <action | ||
| 69 | android:id="@+id/action_global_emulationActivity" | ||
| 70 | app:destination="@id/emulationActivity" | ||
| 71 | app:launchSingleTop="true" /> | ||
| 72 | |||
| 59 | </navigation> | 73 | </navigation> |
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..b5bc249d4 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml | |||
| @@ -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/android/build.gradle.kts b/src/android/build.gradle.kts index e19e8ce58..80f370c16 100644 --- a/src/android/build.gradle.kts +++ b/src/android/build.gradle.kts | |||
| @@ -11,3 +11,12 @@ plugins { | |||
| 11 | tasks.register("clean").configure { | 11 | tasks.register("clean").configure { |
| 12 | delete(rootProject.buildDir) | 12 | delete(rootProject.buildDir) |
| 13 | } | 13 | } |
| 14 | |||
| 15 | buildscript { | ||
| 16 | repositories { | ||
| 17 | google() | ||
| 18 | } | ||
| 19 | dependencies { | ||
| 20 | classpath("androidx.navigation:navigation-safe-args-gradle-plugin:2.6.0") | ||
| 21 | } | ||
| 22 | } | ||
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/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index 251a4a880..9bafd8cc0 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h | |||
| @@ -715,7 +715,7 @@ void BufferCache<P>::BindHostIndexBuffer() { | |||
| 715 | 715 | ||
| 716 | template <class P> | 716 | template <class P> |
| 717 | void BufferCache<P>::BindHostVertexBuffers() { | 717 | void BufferCache<P>::BindHostVertexBuffers() { |
| 718 | HostBindings host_bindings; | 718 | HostBindings<typename P::Buffer> host_bindings; |
| 719 | bool any_valid{false}; | 719 | bool any_valid{false}; |
| 720 | auto& flags = maxwell3d->dirty.flags; | 720 | auto& flags = maxwell3d->dirty.flags; |
| 721 | for (u32 index = 0; index < NUM_VERTEX_BUFFERS; ++index) { | 721 | for (u32 index = 0; index < NUM_VERTEX_BUFFERS; ++index) { |
| @@ -741,7 +741,7 @@ void BufferCache<P>::BindHostVertexBuffers() { | |||
| 741 | const u32 stride = maxwell3d->regs.vertex_streams[index].stride; | 741 | const u32 stride = maxwell3d->regs.vertex_streams[index].stride; |
| 742 | const u32 offset = buffer.Offset(binding.cpu_addr); | 742 | const u32 offset = buffer.Offset(binding.cpu_addr); |
| 743 | 743 | ||
| 744 | host_bindings.buffers.push_back(reinterpret_cast<void*>(&buffer)); | 744 | host_bindings.buffers.push_back(&buffer); |
| 745 | host_bindings.offsets.push_back(offset); | 745 | host_bindings.offsets.push_back(offset); |
| 746 | host_bindings.sizes.push_back(binding.size); | 746 | host_bindings.sizes.push_back(binding.size); |
| 747 | host_bindings.strides.push_back(stride); | 747 | host_bindings.strides.push_back(stride); |
| @@ -900,7 +900,7 @@ void BufferCache<P>::BindHostTransformFeedbackBuffers() { | |||
| 900 | if (maxwell3d->regs.transform_feedback_enabled == 0) { | 900 | if (maxwell3d->regs.transform_feedback_enabled == 0) { |
| 901 | return; | 901 | return; |
| 902 | } | 902 | } |
| 903 | HostBindings host_bindings; | 903 | HostBindings<typename P::Buffer> host_bindings; |
| 904 | for (u32 index = 0; index < NUM_TRANSFORM_FEEDBACK_BUFFERS; ++index) { | 904 | for (u32 index = 0; index < NUM_TRANSFORM_FEEDBACK_BUFFERS; ++index) { |
| 905 | const Binding& binding = channel_state->transform_feedback_buffers[index]; | 905 | const Binding& binding = channel_state->transform_feedback_buffers[index]; |
| 906 | if (maxwell3d->regs.transform_feedback.controls[index].varying_count == 0 && | 906 | if (maxwell3d->regs.transform_feedback.controls[index].varying_count == 0 && |
| @@ -913,7 +913,7 @@ void BufferCache<P>::BindHostTransformFeedbackBuffers() { | |||
| 913 | SynchronizeBuffer(buffer, binding.cpu_addr, size); | 913 | SynchronizeBuffer(buffer, binding.cpu_addr, size); |
| 914 | 914 | ||
| 915 | const u32 offset = buffer.Offset(binding.cpu_addr); | 915 | const u32 offset = buffer.Offset(binding.cpu_addr); |
| 916 | host_bindings.buffers.push_back(reinterpret_cast<void*>(&buffer)); | 916 | host_bindings.buffers.push_back(&buffer); |
| 917 | host_bindings.offsets.push_back(offset); | 917 | host_bindings.offsets.push_back(offset); |
| 918 | host_bindings.sizes.push_back(binding.size); | 918 | host_bindings.sizes.push_back(binding.size); |
| 919 | } | 919 | } |
diff --git a/src/video_core/buffer_cache/buffer_cache_base.h b/src/video_core/buffer_cache/buffer_cache_base.h index cf359e241..63a120f7a 100644 --- a/src/video_core/buffer_cache/buffer_cache_base.h +++ b/src/video_core/buffer_cache/buffer_cache_base.h | |||
| @@ -105,8 +105,9 @@ static constexpr Binding NULL_BINDING{ | |||
| 105 | .buffer_id = NULL_BUFFER_ID, | 105 | .buffer_id = NULL_BUFFER_ID, |
| 106 | }; | 106 | }; |
| 107 | 107 | ||
| 108 | template <typename Buffer> | ||
| 108 | struct HostBindings { | 109 | struct HostBindings { |
| 109 | boost::container::small_vector<void*, NUM_VERTEX_BUFFERS> buffers; | 110 | boost::container::small_vector<Buffer*, NUM_VERTEX_BUFFERS> buffers; |
| 110 | boost::container::small_vector<u64, NUM_VERTEX_BUFFERS> offsets; | 111 | boost::container::small_vector<u64, NUM_VERTEX_BUFFERS> offsets; |
| 111 | boost::container::small_vector<u64, NUM_VERTEX_BUFFERS> sizes; | 112 | boost::container::small_vector<u64, NUM_VERTEX_BUFFERS> sizes; |
| 112 | boost::container::small_vector<u64, NUM_VERTEX_BUFFERS> strides; | 113 | boost::container::small_vector<u64, NUM_VERTEX_BUFFERS> strides; |
diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.cpp b/src/video_core/renderer_opengl/gl_buffer_cache.cpp index 0cc546a3a..38d553d3c 100644 --- a/src/video_core/renderer_opengl/gl_buffer_cache.cpp +++ b/src/video_core/renderer_opengl/gl_buffer_cache.cpp | |||
| @@ -232,12 +232,12 @@ void BufferCacheRuntime::BindVertexBuffer(u32 index, Buffer& buffer, u32 offset, | |||
| 232 | } | 232 | } |
| 233 | } | 233 | } |
| 234 | 234 | ||
| 235 | void BufferCacheRuntime::BindVertexBuffers(VideoCommon::HostBindings& bindings) { | 235 | void BufferCacheRuntime::BindVertexBuffers(VideoCommon::HostBindings<Buffer>& bindings) { |
| 236 | for (u32 index = 0; index < bindings.buffers.size(); index++) { | 236 | for (u32 index = 0; index < bindings.buffers.size(); ++index) { |
| 237 | BindVertexBuffer( | 237 | BindVertexBuffer(bindings.min_index + index, *bindings.buffers[index], |
| 238 | bindings.min_index + index, *reinterpret_cast<Buffer*>(bindings.buffers[index]), | 238 | static_cast<u32>(bindings.offsets[index]), |
| 239 | static_cast<u32>(bindings.offsets[index]), static_cast<u32>(bindings.sizes[index]), | 239 | static_cast<u32>(bindings.sizes[index]), |
| 240 | static_cast<u32>(bindings.strides[index])); | 240 | static_cast<u32>(bindings.strides[index])); |
| 241 | } | 241 | } |
| 242 | } | 242 | } |
| 243 | 243 | ||
| @@ -329,10 +329,9 @@ void BufferCacheRuntime::BindTransformFeedbackBuffer(u32 index, Buffer& buffer, | |||
| 329 | static_cast<GLintptr>(offset), static_cast<GLsizeiptr>(size)); | 329 | static_cast<GLintptr>(offset), static_cast<GLsizeiptr>(size)); |
| 330 | } | 330 | } |
| 331 | 331 | ||
| 332 | void BufferCacheRuntime::BindTransformFeedbackBuffers(VideoCommon::HostBindings& bindings) { | 332 | void BufferCacheRuntime::BindTransformFeedbackBuffers(VideoCommon::HostBindings<Buffer>& bindings) { |
| 333 | for (u32 index = 0; index < bindings.buffers.size(); index++) { | 333 | for (u32 index = 0; index < bindings.buffers.size(); ++index) { |
| 334 | glBindBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, index, | 334 | glBindBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, index, bindings.buffers[index]->Handle(), |
| 335 | reinterpret_cast<Buffer*>(bindings.buffers[index])->Handle(), | ||
| 336 | static_cast<GLintptr>(bindings.offsets[index]), | 335 | static_cast<GLintptr>(bindings.offsets[index]), |
| 337 | static_cast<GLsizeiptr>(bindings.sizes[index])); | 336 | static_cast<GLsizeiptr>(bindings.sizes[index])); |
| 338 | } | 337 | } |
diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.h b/src/video_core/renderer_opengl/gl_buffer_cache.h index e4e000284..41b746f3b 100644 --- a/src/video_core/renderer_opengl/gl_buffer_cache.h +++ b/src/video_core/renderer_opengl/gl_buffer_cache.h | |||
| @@ -87,7 +87,8 @@ public: | |||
| 87 | void BindIndexBuffer(Buffer& buffer, u32 offset, u32 size); | 87 | void BindIndexBuffer(Buffer& buffer, u32 offset, u32 size); |
| 88 | 88 | ||
| 89 | void BindVertexBuffer(u32 index, Buffer& buffer, u32 offset, u32 size, u32 stride); | 89 | void BindVertexBuffer(u32 index, Buffer& buffer, u32 offset, u32 size, u32 stride); |
| 90 | void BindVertexBuffers(VideoCommon::HostBindings& bindings); | 90 | |
| 91 | void BindVertexBuffers(VideoCommon::HostBindings<Buffer>& bindings); | ||
| 91 | 92 | ||
| 92 | void BindUniformBuffer(size_t stage, u32 binding_index, Buffer& buffer, u32 offset, u32 size); | 93 | void BindUniformBuffer(size_t stage, u32 binding_index, Buffer& buffer, u32 offset, u32 size); |
| 93 | 94 | ||
| @@ -100,7 +101,8 @@ public: | |||
| 100 | bool is_written); | 101 | bool is_written); |
| 101 | 102 | ||
| 102 | void BindTransformFeedbackBuffer(u32 index, Buffer& buffer, u32 offset, u32 size); | 103 | void BindTransformFeedbackBuffer(u32 index, Buffer& buffer, u32 offset, u32 size); |
| 103 | void BindTransformFeedbackBuffers(VideoCommon::HostBindings& bindings); | 104 | |
| 105 | void BindTransformFeedbackBuffers(VideoCommon::HostBindings<Buffer>& bindings); | ||
| 104 | 106 | ||
| 105 | void BindTextureBuffer(Buffer& buffer, u32 offset, u32 size, | 107 | void BindTextureBuffer(Buffer& buffer, u32 offset, u32 size, |
| 106 | VideoCore::Surface::PixelFormat format); | 108 | VideoCore::Surface::PixelFormat format); |
diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp index d72d99899..8c33722d3 100644 --- a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp | |||
| @@ -501,11 +501,10 @@ void BufferCacheRuntime::BindVertexBuffer(u32 index, VkBuffer buffer, u32 offset | |||
| 501 | } | 501 | } |
| 502 | } | 502 | } |
| 503 | 503 | ||
| 504 | void BufferCacheRuntime::BindVertexBuffers(VideoCommon::HostBindings& bindings) { | 504 | void BufferCacheRuntime::BindVertexBuffers(VideoCommon::HostBindings<Buffer>& bindings) { |
| 505 | boost::container::small_vector<VkBuffer, 32> buffer_handles; | 505 | boost::container::small_vector<VkBuffer, 32> buffer_handles; |
| 506 | for (u32 index = 0; index < bindings.buffers.size(); index++) { | 506 | for (u32 index = 0; index < bindings.buffers.size(); ++index) { |
| 507 | auto& buffer = *reinterpret_cast<Buffer*>(bindings.buffers[index]); | 507 | auto handle = bindings.buffers[index]->Handle(); |
| 508 | auto handle = buffer.Handle(); | ||
| 509 | if (handle == VK_NULL_HANDLE) { | 508 | if (handle == VK_NULL_HANDLE) { |
| 510 | bindings.offsets[index] = 0; | 509 | bindings.offsets[index] = 0; |
| 511 | bindings.sizes[index] = VK_WHOLE_SIZE; | 510 | bindings.sizes[index] = VK_WHOLE_SIZE; |
| @@ -521,16 +520,13 @@ void BufferCacheRuntime::BindVertexBuffers(VideoCommon::HostBindings& bindings) | |||
| 521 | buffer_handles = buffer_handles](vk::CommandBuffer cmdbuf) { | 520 | buffer_handles = buffer_handles](vk::CommandBuffer cmdbuf) { |
| 522 | cmdbuf.BindVertexBuffers2EXT( | 521 | cmdbuf.BindVertexBuffers2EXT( |
| 523 | bindings.min_index, bindings.max_index - bindings.min_index, buffer_handles.data(), | 522 | bindings.min_index, bindings.max_index - bindings.min_index, buffer_handles.data(), |
| 524 | reinterpret_cast<const VkDeviceSize*>(bindings.offsets.data()), | 523 | bindings.offsets.data(), bindings.sizes.data(), bindings.strides.data()); |
| 525 | reinterpret_cast<const VkDeviceSize*>(bindings.sizes.data()), | ||
| 526 | reinterpret_cast<const VkDeviceSize*>(bindings.strides.data())); | ||
| 527 | }); | 524 | }); |
| 528 | } else { | 525 | } else { |
| 529 | scheduler.Record([bindings = bindings, | 526 | scheduler.Record([bindings = bindings, |
| 530 | buffer_handles = buffer_handles](vk::CommandBuffer cmdbuf) { | 527 | buffer_handles = buffer_handles](vk::CommandBuffer cmdbuf) { |
| 531 | cmdbuf.BindVertexBuffers( | 528 | cmdbuf.BindVertexBuffers(bindings.min_index, bindings.max_index - bindings.min_index, |
| 532 | bindings.min_index, bindings.max_index - bindings.min_index, buffer_handles.data(), | 529 | buffer_handles.data(), bindings.offsets.data()); |
| 533 | reinterpret_cast<const VkDeviceSize*>(bindings.offsets.data())); | ||
| 534 | }); | 530 | }); |
| 535 | } | 531 | } |
| 536 | } | 532 | } |
| @@ -556,22 +552,20 @@ void BufferCacheRuntime::BindTransformFeedbackBuffer(u32 index, VkBuffer buffer, | |||
| 556 | }); | 552 | }); |
| 557 | } | 553 | } |
| 558 | 554 | ||
| 559 | void BufferCacheRuntime::BindTransformFeedbackBuffers(VideoCommon::HostBindings& bindings) { | 555 | void BufferCacheRuntime::BindTransformFeedbackBuffers(VideoCommon::HostBindings<Buffer>& bindings) { |
| 560 | if (!device.IsExtTransformFeedbackSupported()) { | 556 | if (!device.IsExtTransformFeedbackSupported()) { |
| 561 | // Already logged in the rasterizer | 557 | // Already logged in the rasterizer |
| 562 | return; | 558 | return; |
| 563 | } | 559 | } |
| 564 | boost::container::small_vector<VkBuffer, 4> buffer_handles; | 560 | boost::container::small_vector<VkBuffer, 4> buffer_handles; |
| 565 | for (u32 index = 0; index < bindings.buffers.size(); index++) { | 561 | for (u32 index = 0; index < bindings.buffers.size(); ++index) { |
| 566 | auto& buffer = *reinterpret_cast<Buffer*>(bindings.buffers[index]); | 562 | buffer_handles.push_back(bindings.buffers[index]->Handle()); |
| 567 | buffer_handles.push_back(buffer.Handle()); | ||
| 568 | } | 563 | } |
| 569 | scheduler.Record( | 564 | scheduler.Record( |
| 570 | [bindings = bindings, buffer_handles = buffer_handles](vk::CommandBuffer cmdbuf) { | 565 | [bindings = bindings, buffer_handles = buffer_handles](vk::CommandBuffer cmdbuf) { |
| 571 | cmdbuf.BindTransformFeedbackBuffersEXT( | 566 | cmdbuf.BindTransformFeedbackBuffersEXT(0, static_cast<u32>(buffer_handles.size()), |
| 572 | 0, static_cast<u32>(buffer_handles.size()), buffer_handles.data(), | 567 | buffer_handles.data(), bindings.offsets.data(), |
| 573 | reinterpret_cast<const VkDeviceSize*>(bindings.offsets.data()), | 568 | bindings.sizes.data()); |
| 574 | reinterpret_cast<const VkDeviceSize*>(bindings.sizes.data())); | ||
| 575 | }); | 569 | }); |
| 576 | } | 570 | } |
| 577 | 571 | ||
diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.h b/src/video_core/renderer_vulkan/vk_buffer_cache.h index 92d3e9f32..cdeef8846 100644 --- a/src/video_core/renderer_vulkan/vk_buffer_cache.h +++ b/src/video_core/renderer_vulkan/vk_buffer_cache.h | |||
| @@ -97,10 +97,12 @@ public: | |||
| 97 | void BindQuadIndexBuffer(PrimitiveTopology topology, u32 first, u32 count); | 97 | void BindQuadIndexBuffer(PrimitiveTopology topology, u32 first, u32 count); |
| 98 | 98 | ||
| 99 | void BindVertexBuffer(u32 index, VkBuffer buffer, u32 offset, u32 size, u32 stride); | 99 | void BindVertexBuffer(u32 index, VkBuffer buffer, u32 offset, u32 size, u32 stride); |
| 100 | void BindVertexBuffers(VideoCommon::HostBindings& bindings); | 100 | |
| 101 | void BindVertexBuffers(VideoCommon::HostBindings<Buffer>& bindings); | ||
| 101 | 102 | ||
| 102 | void BindTransformFeedbackBuffer(u32 index, VkBuffer buffer, u32 offset, u32 size); | 103 | void BindTransformFeedbackBuffer(u32 index, VkBuffer buffer, u32 offset, u32 size); |
| 103 | void BindTransformFeedbackBuffers(VideoCommon::HostBindings& bindings); | 104 | |
| 105 | void BindTransformFeedbackBuffers(VideoCommon::HostBindings<Buffer>& bindings); | ||
| 104 | 106 | ||
| 105 | std::span<u8> BindMappedUniformBuffer([[maybe_unused]] size_t stage, | 107 | std::span<u8> BindMappedUniformBuffer([[maybe_unused]] size_t stage, |
| 106 | [[maybe_unused]] u32 binding_index, u32 size) { | 108 | [[maybe_unused]] u32 binding_index, u32 size) { |
diff --git a/vcpkg.json b/vcpkg.json index 26f545c6c..2fa2c80be 100644 --- a/vcpkg.json +++ b/vcpkg.json | |||
| @@ -1,7 +1,7 @@ | |||
| 1 | { | 1 | { |
| 2 | "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json", | 2 | "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json", |
| 3 | "name": "yuzu", | 3 | "name": "yuzu", |
| 4 | "builtin-baseline": "656fcc6ab2b05c6d999b7eaca717027ac3738f71", | 4 | "builtin-baseline": "a487471068f4cb1cbb4eeb340763cdcc0a75fd68", |
| 5 | "version": "1.0", | 5 | "version": "1.0", |
| 6 | "dependencies": [ | 6 | "dependencies": [ |
| 7 | "boost-algorithm", | 7 | "boost-algorithm", |