android: Add Picture in Picture / Orientation
This commit is contained in:
parent
a10a091928
commit
de9100ea81
|
@ -54,6 +54,8 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||
android:name="org.yuzu.yuzu_emu.activities.EmulationActivity"
|
||||
android:theme="@style/Theme.Yuzu.Main"
|
||||
android:screenOrientation="userLandscape"
|
||||
android:supportsPictureInPicture="true"
|
||||
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|uiMode"
|
||||
android:exported="true">
|
||||
|
||||
<intent-filter>
|
||||
|
|
|
@ -4,14 +4,23 @@
|
|||
package org.yuzu.yuzu_emu.activities
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.PendingIntent
|
||||
import android.app.PictureInPictureParams
|
||||
import android.app.RemoteAction
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.Rect
|
||||
import android.graphics.drawable.Icon
|
||||
import android.hardware.Sensor
|
||||
import android.hardware.SensorEvent
|
||||
import android.hardware.SensorEventListener
|
||||
import android.hardware.SensorManager
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.util.Rational
|
||||
import android.view.InputDevice
|
||||
import android.view.KeyEvent
|
||||
import android.view.MotionEvent
|
||||
|
@ -27,6 +36,8 @@ import androidx.navigation.fragment.NavHostFragment
|
|||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.databinding.ActivityEmulationBinding
|
||||
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel
|
||||
import org.yuzu.yuzu_emu.model.Game
|
||||
import org.yuzu.yuzu_emu.utils.ControllerMappingHelper
|
||||
|
@ -50,6 +61,9 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
|||
private var motionTimestamp: Long = 0
|
||||
private var flipMotionOrientation: Boolean = false
|
||||
|
||||
private val actionPause = "ACTION_EMULATOR_PAUSE"
|
||||
private val actionPlay = "ACTION_EMULATOR_PLAY"
|
||||
|
||||
private val settingsViewModel: SettingsViewModel by viewModels()
|
||||
|
||||
override fun onDestroy() {
|
||||
|
@ -120,6 +134,8 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
|||
super.onResume()
|
||||
nfcReader.startScanning()
|
||||
startMotionSensorListener()
|
||||
|
||||
buildPictureInPictureParams()
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
|
@ -128,6 +144,16 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
|||
stopMotionSensorListener()
|
||||
}
|
||||
|
||||
override fun onUserLeaveHint() {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
|
||||
if (BooleanSetting.PICTURE_IN_PICTURE.boolean && !isInPictureInPictureMode) {
|
||||
val pictureInPictureParamsBuilder = PictureInPictureParams.Builder()
|
||||
.getPictureInPictureActionsBuilder().getPictureInPictureAspectBuilder()
|
||||
enterPictureInPictureMode(pictureInPictureParamsBuilder.build())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
super.onNewIntent(intent)
|
||||
setIntent(intent)
|
||||
|
@ -230,6 +256,79 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
|||
}
|
||||
}
|
||||
|
||||
private fun PictureInPictureParams.Builder.getPictureInPictureAspectBuilder() : PictureInPictureParams.Builder {
|
||||
val aspectRatio = when (IntSetting.RENDERER_ASPECT_RATIO.int) {
|
||||
0 -> Rational(16, 9)
|
||||
1 -> Rational(4, 3)
|
||||
2 -> Rational(21, 9)
|
||||
3 -> Rational(16, 10)
|
||||
else -> null
|
||||
}
|
||||
return this.apply { aspectRatio?.let { setAspectRatio(it) } }
|
||||
}
|
||||
|
||||
private fun PictureInPictureParams.Builder.getPictureInPictureActionsBuilder() : PictureInPictureParams.Builder {
|
||||
val pictureInPictureActions : MutableList<RemoteAction> = mutableListOf()
|
||||
val pendingFlags = PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||
|
||||
val isEmulationPaused = emulationFragment?.isEmulationStatePaused() ?: false
|
||||
if (isEmulationPaused) {
|
||||
val playIcon = Icon.createWithResource(this@EmulationActivity, R.drawable.ic_pip_play)
|
||||
val playPendingIntent = PendingIntent.getBroadcast(
|
||||
this@EmulationActivity, R.drawable.ic_pip_play, Intent(actionPlay), pendingFlags
|
||||
)
|
||||
val playRemoteAction = RemoteAction(playIcon, getString(R.string.play), getString(R.string.play), playPendingIntent)
|
||||
pictureInPictureActions.add(playRemoteAction)
|
||||
} else {
|
||||
val pauseIcon = Icon.createWithResource(this@EmulationActivity, R.drawable.ic_pip_pause)
|
||||
val pausePendingIntent = PendingIntent.getBroadcast(
|
||||
this@EmulationActivity, R.drawable.ic_pip_pause, Intent(actionPause), pendingFlags
|
||||
)
|
||||
val pauseRemoteAction = RemoteAction(pauseIcon, getString(R.string.pause), getString(R.string.pause), pausePendingIntent)
|
||||
pictureInPictureActions.add(pauseRemoteAction)
|
||||
}
|
||||
|
||||
return this.apply { setActions(pictureInPictureActions) }
|
||||
}
|
||||
|
||||
fun buildPictureInPictureParams() {
|
||||
val pictureInPictureParamsBuilder = PictureInPictureParams.Builder()
|
||||
.getPictureInPictureActionsBuilder().getPictureInPictureAspectBuilder()
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
pictureInPictureParamsBuilder.setAutoEnterEnabled(BooleanSetting.PICTURE_IN_PICTURE.boolean)
|
||||
}
|
||||
setPictureInPictureParams(pictureInPictureParamsBuilder.build())
|
||||
}
|
||||
|
||||
private var pictureInPictureReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context : Context?, intent : Intent) {
|
||||
if (intent.action == actionPlay) {
|
||||
emulationFragment?.onPictureInPicturePlay()
|
||||
} else if (intent.action == actionPause) {
|
||||
emulationFragment?.onPictureInPicturePause()
|
||||
}
|
||||
buildPictureInPictureParams()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean, newConfig: Configuration) {
|
||||
super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig)
|
||||
if (isInPictureInPictureMode) {
|
||||
IntentFilter().apply {
|
||||
addAction(actionPause)
|
||||
addAction(actionPlay)
|
||||
}.also {
|
||||
registerReceiver(pictureInPictureReceiver, it)
|
||||
}
|
||||
emulationFragment?.onPictureInPictureEnter()
|
||||
} else {
|
||||
try {
|
||||
unregisterReceiver(pictureInPictureReceiver)
|
||||
} catch (ignored : Exception) { }
|
||||
emulationFragment?.onPictureInPictureLeave()
|
||||
}
|
||||
}
|
||||
|
||||
private fun startMotionSensorListener() {
|
||||
val sensorManager = this.getSystemService(Context.SENSOR_SERVICE) as SensorManager
|
||||
val gyroSensor = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE)
|
||||
|
|
|
@ -8,6 +8,7 @@ enum class BooleanSetting(
|
|||
override val section: String,
|
||||
override val defaultValue: Boolean
|
||||
) : AbstractBooleanSetting {
|
||||
PICTURE_IN_PICTURE("picture_in_picture", Settings.SECTION_GENERAL, true),
|
||||
USE_CUSTOM_RTC("custom_rtc_enabled", Settings.SECTION_SYSTEM, false);
|
||||
|
||||
override var boolean: Boolean = defaultValue
|
||||
|
@ -27,6 +28,7 @@ enum class BooleanSetting(
|
|||
|
||||
companion object {
|
||||
private val NOT_RUNTIME_EDITABLE = listOf(
|
||||
PICTURE_IN_PICTURE,
|
||||
USE_CUSTOM_RTC
|
||||
)
|
||||
|
||||
|
|
|
@ -93,6 +93,11 @@ enum class IntSetting(
|
|||
Settings.SECTION_RENDERER,
|
||||
0
|
||||
),
|
||||
RENDERER_SCREEN_LAYOUT(
|
||||
"screen_layout",
|
||||
Settings.SECTION_RENDERER,
|
||||
Settings.LayoutOption_MobileLandscape
|
||||
),
|
||||
RENDERER_ASPECT_RATIO(
|
||||
"aspect_ratio",
|
||||
Settings.SECTION_RENDERER,
|
||||
|
|
|
@ -133,7 +133,6 @@ class Settings {
|
|||
const val PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER = "EmulationMenuSettings_JoystickRelCenter"
|
||||
const val PREF_MENU_SETTINGS_DPAD_SLIDE = "EmulationMenuSettings_DpadSlideEnable"
|
||||
const val PREF_MENU_SETTINGS_HAPTICS = "EmulationMenuSettings_Haptics"
|
||||
const val PREF_MENU_SETTINGS_LANDSCAPE = "EmulationMenuSettings_LandscapeScreenLayout"
|
||||
const val PREF_MENU_SETTINGS_SHOW_FPS = "EmulationMenuSettings_ShowFps"
|
||||
const val PREF_MENU_SETTINGS_SHOW_OVERLAY = "EmulationMenuSettings_ShowOverlay"
|
||||
|
||||
|
@ -144,6 +143,14 @@ class Settings {
|
|||
|
||||
private val configFileSectionsMap: MutableMap<String, List<String>> = HashMap()
|
||||
|
||||
// These must match what is defined in src/core/settings.h
|
||||
const val LayoutOption_Default = 0
|
||||
const val LayoutOption_SingleScreen = 1
|
||||
const val LayoutOption_LargeScreen = 2
|
||||
const val LayoutOption_SideScreen = 3
|
||||
const val LayoutOption_MobilePortrait = 4
|
||||
const val LayoutOption_MobileLandscape = 5
|
||||
|
||||
init {
|
||||
configFileSectionsMap[SettingsFile.FILE_NAME_CONFIG] =
|
||||
listOf(
|
||||
|
|
|
@ -16,6 +16,7 @@ import androidx.core.view.WindowCompat
|
|||
import androidx.core.view.WindowInsetsCompat
|
||||
import android.view.ViewGroup.MarginLayoutParams
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.core.view.updatePadding
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
|
@ -239,5 +240,12 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
|
|||
settings.putExtra(ARG_GAME_ID, gameId)
|
||||
context.startActivity(settings)
|
||||
}
|
||||
|
||||
fun launch(context: Context, launcher: ActivityResultLauncher<Intent>, menuTag: String?, gameId: String?) {
|
||||
val settings = Intent(context, SettingsActivity::class.java)
|
||||
settings.putExtra(ARG_MENU_TAG, menuTag)
|
||||
settings.putExtra(ARG_GAME_ID, gameId)
|
||||
launcher.launch(settings)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -166,6 +166,15 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
|||
IntSetting.CPU_ACCURACY.defaultValue
|
||||
)
|
||||
)
|
||||
add(
|
||||
SwitchSetting(
|
||||
BooleanSetting.PICTURE_IN_PICTURE,
|
||||
R.string.picture_in_picture,
|
||||
R.string.picture_in_picture_description,
|
||||
BooleanSetting.PICTURE_IN_PICTURE.key,
|
||||
BooleanSetting.PICTURE_IN_PICTURE.defaultValue
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -283,6 +292,17 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
|||
IntSetting.RENDERER_ANTI_ALIASING.defaultValue
|
||||
)
|
||||
)
|
||||
add(
|
||||
SingleChoiceSetting(
|
||||
IntSetting.RENDERER_SCREEN_LAYOUT,
|
||||
R.string.renderer_screen_layout,
|
||||
0,
|
||||
R.array.rendererScreenLayoutNames,
|
||||
R.array.rendererScreenLayoutValues,
|
||||
IntSetting.RENDERER_SCREEN_LAYOUT.key,
|
||||
IntSetting.RENDERER_SCREEN_LAYOUT.defaultValue
|
||||
)
|
||||
)
|
||||
add(
|
||||
SingleChoiceSetting(
|
||||
IntSetting.RENDERER_ASPECT_RATIO,
|
||||
|
|
|
@ -7,6 +7,7 @@ import android.annotation.SuppressLint
|
|||
import android.app.AlertDialog
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.content.pm.ActivityInfo
|
||||
import android.content.res.Resources
|
||||
|
@ -19,11 +20,14 @@ import android.util.TypedValue
|
|||
import android.view.*
|
||||
import android.widget.TextView
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.widget.PopupMenu
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.Lifecycle
|
||||
|
@ -61,11 +65,30 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||
|
||||
val args by navArgs<EmulationFragmentArgs>()
|
||||
|
||||
private lateinit var onReturnFromSettings: ActivityResultLauncher<Intent>
|
||||
|
||||
override fun onAttach(context: Context) {
|
||||
super.onAttach(context)
|
||||
if (context is EmulationActivity) {
|
||||
emulationActivity = context
|
||||
NativeLibrary.setEmulationActivity(context)
|
||||
|
||||
onReturnFromSettings = context.activityResultRegistry.register(
|
||||
"SettingsResult", ActivityResultContracts.StartActivityForResult()
|
||||
) {
|
||||
binding.surfaceEmulation.setAspectRatio(
|
||||
when (IntSetting.RENDERER_ASPECT_RATIO.int) {
|
||||
0 -> Rational(16, 9)
|
||||
1 -> Rational(4, 3)
|
||||
2 -> Rational(21, 9)
|
||||
3 -> Rational(16, 10)
|
||||
4 -> null // Stretch
|
||||
else -> Rational(16, 9)
|
||||
}
|
||||
)
|
||||
emulationActivity?.buildPictureInPictureParams()
|
||||
updateScreenLayout()
|
||||
}
|
||||
} else {
|
||||
throw IllegalStateException("EmulationFragment must have EmulationActivity parent")
|
||||
}
|
||||
|
@ -129,7 +152,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||
}
|
||||
|
||||
R.id.menu_settings -> {
|
||||
SettingsActivity.launch(requireContext(), SettingsFile.FILE_NAME_CONFIG, "")
|
||||
SettingsActivity.launch(
|
||||
requireContext(), onReturnFromSettings, SettingsFile.FILE_NAME_CONFIG, ""
|
||||
)
|
||||
true
|
||||
}
|
||||
|
||||
|
@ -162,7 +187,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
WindowInfoTracker.getOrCreate(requireContext())
|
||||
.windowLayoutInfo(requireActivity())
|
||||
.collect { updateCurrentLayout(requireActivity() as EmulationActivity, it) }
|
||||
.collect { updateFoldableLayout(requireActivity() as EmulationActivity, it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -204,6 +229,37 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||
super.onDetach()
|
||||
}
|
||||
|
||||
fun isEmulationStatePaused() : Boolean {
|
||||
return this::emulationState.isInitialized && emulationState.isPaused
|
||||
}
|
||||
|
||||
fun onPictureInPictureEnter() {
|
||||
if (binding.drawerLayout.isOpen) {
|
||||
binding.drawerLayout.close()
|
||||
}
|
||||
if (EmulationMenuSettings.showOverlay) {
|
||||
binding.surfaceInputOverlay.post { binding.surfaceInputOverlay.isVisible = false }
|
||||
}
|
||||
}
|
||||
|
||||
fun onPictureInPicturePause() {
|
||||
if (!emulationState.isPaused) {
|
||||
emulationState.pause()
|
||||
}
|
||||
}
|
||||
|
||||
fun onPictureInPicturePlay() {
|
||||
if (emulationState.isPaused) {
|
||||
emulationState.run(false)
|
||||
}
|
||||
}
|
||||
|
||||
fun onPictureInPictureLeave() {
|
||||
if (EmulationMenuSettings.showOverlay) {
|
||||
binding.surfaceInputOverlay.post { binding.surfaceInputOverlay.isVisible = true }
|
||||
}
|
||||
}
|
||||
|
||||
private fun refreshInputOverlay() {
|
||||
binding.surfaceInputOverlay.refreshControls()
|
||||
}
|
||||
|
@ -243,15 +299,33 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||
}
|
||||
}
|
||||
|
||||
@SuppressLint("SourceLockedOrientationActivity")
|
||||
private fun updateScreenLayout() {
|
||||
emulationActivity?.let {
|
||||
when (IntSetting.RENDERER_SCREEN_LAYOUT.int) {
|
||||
Settings.LayoutOption_MobileLandscape -> {
|
||||
it.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE
|
||||
}
|
||||
Settings.LayoutOption_MobilePortrait -> {
|
||||
it.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
|
||||
}
|
||||
Settings.LayoutOption_Default -> {
|
||||
it.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
|
||||
}
|
||||
else -> { it.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val Number.toPx get() = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, this.toFloat(), Resources.getSystem().displayMetrics).toInt()
|
||||
|
||||
fun updateCurrentLayout(emulationActivity: EmulationActivity, newLayoutInfo: WindowLayoutInfo) {
|
||||
fun updateFoldableLayout(emulationActivity: EmulationActivity, newLayoutInfo: WindowLayoutInfo) {
|
||||
val isFolding = (newLayoutInfo.displayFeatures.find { it is FoldingFeature } as? FoldingFeature)?.let {
|
||||
if (it.isSeparating) {
|
||||
emulationActivity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
|
||||
if (it.orientation == FoldingFeature.Orientation.HORIZONTAL) {
|
||||
binding.surfaceEmulation.layoutParams.height = it.bounds.top
|
||||
binding.inGameMenu.layoutParams.height = it.bounds.bottom
|
||||
binding.emulationContainer.layoutParams.height = it.bounds.top
|
||||
// Prevent touch regions from being displayed in the hinge
|
||||
binding.overlayContainer.layoutParams.height = it.bounds.bottom - 48.toPx
|
||||
binding.overlayContainer.updatePadding(0, 0, 0, 24.toPx)
|
||||
}
|
||||
|
@ -259,14 +333,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||
it.isSeparating
|
||||
} ?: false
|
||||
if (!isFolding) {
|
||||
binding.surfaceEmulation.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
|
||||
binding.inGameMenu.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
|
||||
binding.emulationContainer.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
|
||||
binding.overlayContainer.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
|
||||
binding.overlayContainer.updatePadding(0, 0, 0, 0)
|
||||
emulationActivity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
|
||||
updateScreenLayout()
|
||||
}
|
||||
binding.surfaceInputOverlay.requestLayout()
|
||||
binding.inGameMenu.requestLayout()
|
||||
binding.emulationContainer.requestLayout()
|
||||
binding.overlayContainer.requestLayout()
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
package org.yuzu.yuzu_emu.overlay
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
|
@ -15,12 +16,14 @@ import android.graphics.drawable.Drawable
|
|||
import android.graphics.drawable.VectorDrawable
|
||||
import android.os.Build
|
||||
import android.util.AttributeSet
|
||||
import android.util.Rational
|
||||
import android.view.HapticFeedbackConstants
|
||||
import android.view.MotionEvent
|
||||
import android.view.SurfaceView
|
||||
import android.view.View
|
||||
import android.view.View.OnTouchListener
|
||||
import android.view.WindowInsets
|
||||
import android.view.WindowManager
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.window.layout.WindowMetricsCalculator
|
||||
|
@ -33,6 +36,7 @@ import org.yuzu.yuzu_emu.features.settings.model.Settings
|
|||
import org.yuzu.yuzu_emu.utils.EmulationMenuSettings
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
/**
|
||||
* Draws the interactive input overlay on top of the
|
||||
|
@ -73,6 +77,25 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
|||
requestFocus()
|
||||
}
|
||||
|
||||
@SuppressLint("DrawAllocation")
|
||||
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
|
||||
val width = MeasureSpec.getSize(widthMeasureSpec)
|
||||
val height = MeasureSpec.getSize(heightMeasureSpec)
|
||||
if (height > width) {
|
||||
val aspectRatio = with (context.getSystemService(Context.WINDOW_SERVICE) as WindowManager) {
|
||||
val metrics = maximumWindowMetrics.bounds
|
||||
Rational(metrics.height(), metrics.width()).toFloat()
|
||||
}
|
||||
val newWidth: Int = width
|
||||
val newHeight: Int = (width / aspectRatio).roundToInt()
|
||||
setMeasuredDimension(newWidth, newHeight)
|
||||
invalidate()
|
||||
} else {
|
||||
setMeasuredDimension(width, height)
|
||||
}
|
||||
}
|
||||
|
||||
override fun draw(canvas: Canvas) {
|
||||
super.draw(canvas)
|
||||
for (button in overlayButtons) {
|
||||
|
@ -754,9 +777,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
|||
*/
|
||||
private fun getSafeScreenSize(context: Context): Pair<Point, Point> {
|
||||
// Get screen size
|
||||
val windowMetrics =
|
||||
WindowMetricsCalculator.getOrCreate()
|
||||
.computeCurrentWindowMetrics(context as Activity)
|
||||
val windowMetrics = WindowMetricsCalculator.getOrCreate()
|
||||
.computeCurrentWindowMetrics(context as Activity)
|
||||
var maxY = windowMetrics.bounds.height().toFloat()
|
||||
var maxX = windowMetrics.bounds.width().toFloat()
|
||||
var minY = 0
|
||||
|
|
|
@ -11,14 +11,6 @@ object EmulationMenuSettings {
|
|||
private val preferences =
|
||||
PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
|
||||
|
||||
// These must match what is defined in src/core/settings.h
|
||||
const val LayoutOption_Default = 0
|
||||
const val LayoutOption_SingleScreen = 1
|
||||
const val LayoutOption_LargeScreen = 2
|
||||
const val LayoutOption_SideScreen = 3
|
||||
const val LayoutOption_MobilePortrait = 4
|
||||
const val LayoutOption_MobileLandscape = 5
|
||||
|
||||
var joystickRelCenter: Boolean
|
||||
get() = preferences.getBoolean(Settings.PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER, true)
|
||||
set(value) {
|
||||
|
@ -41,16 +33,6 @@ object EmulationMenuSettings {
|
|||
.apply()
|
||||
}
|
||||
|
||||
var landscapeScreenLayout: Int
|
||||
get() = preferences.getInt(
|
||||
Settings.PREF_MENU_SETTINGS_LANDSCAPE,
|
||||
LayoutOption_MobileLandscape
|
||||
)
|
||||
set(value) {
|
||||
preferences.edit()
|
||||
.putInt(Settings.PREF_MENU_SETTINGS_LANDSCAPE, value)
|
||||
.apply()
|
||||
}
|
||||
var showFps: Boolean
|
||||
get() = preferences.getBoolean(Settings.PREF_MENU_SETTINGS_SHOW_FPS, false)
|
||||
set(value) {
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportHeight="24"
|
||||
android:viewportWidth="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M6,19h4L10,5L6,5v14zM14,5v14h4L18,5h-4z" />
|
||||
</vector>
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportHeight="24"
|
||||
android:viewportWidth="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M8,5v14l11,-7z" />
|
||||
</vector>
|
|
@ -12,14 +12,21 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<!-- This is what everything is rendered to during emulation -->
|
||||
<org.yuzu.yuzu_emu.views.FixedRatioSurfaceView
|
||||
android:id="@+id/surface_emulation"
|
||||
<FrameLayout
|
||||
android:id="@+id/emulation_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center"
|
||||
android:focusable="false"
|
||||
android:focusableInTouchMode="false" />
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<!-- This is what everything is rendered to during emulation -->
|
||||
<org.yuzu.yuzu_emu.views.FixedRatioSurfaceView
|
||||
android:id="@+id/surface_emulation"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center"
|
||||
android:focusable="false"
|
||||
android:focusableInTouchMode="false" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/overlay_container"
|
||||
|
@ -27,34 +34,36 @@
|
|||
android:layout_height="match_parent"
|
||||
android:layout_gravity="bottom">
|
||||
|
||||
<!-- This is the onscreen input overlay -->
|
||||
<org.yuzu.yuzu_emu.overlay.InputOverlay
|
||||
android:id="@+id/surface_input_overlay"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:focusable="true"
|
||||
android:focusableInTouchMode="true" />
|
||||
<!-- This is the onscreen input overlay -->
|
||||
<org.yuzu.yuzu_emu.overlay.InputOverlay
|
||||
android:id="@+id/surface_input_overlay"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="bottom"
|
||||
android:focusable="true"
|
||||
android:focusableInTouchMode="true" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/show_fps_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="left"
|
||||
android:clickable="false"
|
||||
android:focusable="false"
|
||||
android:shadowColor="@android:color/black"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="12sp"
|
||||
tools:ignore="RtlHardcoded" />
|
||||
<TextView
|
||||
android:id="@+id/show_fps_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="left"
|
||||
android:clickable="false"
|
||||
android:focusable="false"
|
||||
android:shadowColor="@android:color/black"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="12sp"
|
||||
tools:ignore="RtlHardcoded" />
|
||||
|
||||
<Button
|
||||
style="@style/Widget.Material3.Button.ElevatedButton"
|
||||
android:id="@+id/done_control_config"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:text="@string/emulation_done"
|
||||
android:visibility="gone" />
|
||||
|
||||
<Button
|
||||
style="@style/Widget.Material3.Button.ElevatedButton"
|
||||
android:id="@+id/done_control_config"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:text="@string/emulation_done"
|
||||
android:visibility="gone" />
|
||||
</FrameLayout>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
@ -63,7 +72,7 @@
|
|||
android:id="@+id/in_game_menu"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="start|bottom"
|
||||
android:layout_gravity="start"
|
||||
app:headerLayout="@layout/header_in_game"
|
||||
app:menu="@menu/menu_in_game" />
|
||||
|
||||
|
|
|
@ -119,6 +119,18 @@
|
|||
<item>3</item>
|
||||
</integer-array>
|
||||
|
||||
<string-array name="rendererScreenLayoutNames">
|
||||
<item>@string/screen_layout_landscape</item>
|
||||
<item>@string/screen_layout_portrait</item>
|
||||
<item>@string/screen_layout_auto</item>
|
||||
</string-array>
|
||||
|
||||
<integer-array name="rendererScreenLayoutValues">
|
||||
<item>5</item>
|
||||
<item>4</item>
|
||||
<item>0</item>
|
||||
</integer-array>
|
||||
|
||||
<string-array name="rendererAspectRatioNames">
|
||||
<item>@string/ratio_default</item>
|
||||
<item>@string/ratio_force_four_three</item>
|
||||
|
|
|
@ -162,6 +162,7 @@
|
|||
<string name="renderer_accuracy">Accuracy level</string>
|
||||
<string name="renderer_resolution">Resolution (Handheld/Docked)</string>
|
||||
<string name="renderer_vsync">VSync mode</string>
|
||||
<string name="renderer_screen_layout">Orientation</string>
|
||||
<string name="renderer_aspect_ratio">Aspect ratio</string>
|
||||
<string name="renderer_scaling_filter">Window adapting filter</string>
|
||||
<string name="renderer_anti_aliasing">Anti-aliasing method</string>
|
||||
|
@ -326,6 +327,11 @@
|
|||
<string name="anti_aliasing_fxaa">FXAA</string>
|
||||
<string name="anti_aliasing_smaa">SMAA</string>
|
||||
|
||||
<!-- Screen Layouts -->
|
||||
<string name="screen_layout_landscape">Landscape</string>
|
||||
<string name="screen_layout_portrait">Portrait</string>
|
||||
<string name="screen_layout_auto">Auto</string>
|
||||
|
||||
<!-- Aspect Ratios -->
|
||||
<string name="ratio_default">Default (16:9)</string>
|
||||
<string name="ratio_force_four_three">Force 4:3</string>
|
||||
|
@ -364,6 +370,12 @@
|
|||
<string name="use_black_backgrounds">Black backgrounds</string>
|
||||
<string name="use_black_backgrounds_description">When using the dark theme, apply black backgrounds.</string>
|
||||
|
||||
<!-- Picture-In-Picture -->
|
||||
<string name="picture_in_picture">Picture in Picture</string>
|
||||
<string name="picture_in_picture_description">Minimize window when placed in the background</string>
|
||||
<string name="pause">Pause</string>
|
||||
<string name="play">Play</string>
|
||||
|
||||
<!-- Licenses screen strings -->
|
||||
<string name="licenses">Licenses</string>
|
||||
<string name="license_fidelityfx_fsr" translatable="false">FidelityFX-FSR</string>
|
||||
|
|
Loading…
Reference in New Issue