android: Inset input overlay based on system cutouts

This commit is contained in:
Charles Lombardo 2023-03-21 00:34:39 -04:00 committed by bunnei
parent 3f35b34515
commit d3c3b69755
5 changed files with 93 additions and 58 deletions

View file

@ -138,6 +138,7 @@ dependencies {
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1" implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1"
implementation "io.coil-kt:coil:2.2.2" implementation "io.coil-kt:coil:2.2.2"
implementation 'androidx.core:core-splashscreen:1.0.0' implementation 'androidx.core:core-splashscreen:1.0.0'
implementation 'androidx.window:window:1.0.0'
// Allows FRP-style asynchronous operations in Android. // Allows FRP-style asynchronous operations in Android.
implementation 'io.reactivex:rxandroid:1.2.1' implementation 'io.reactivex:rxandroid:1.2.1'

View file

@ -54,7 +54,7 @@
android:resizeableActivity="false" android:resizeableActivity="false"
android:theme="@style/Theme.Yuzu.Main" android:theme="@style/Theme.Yuzu.Main"
android:launchMode="singleTop" android:launchMode="singleTop"
android:screenOrientation="landscape"/> android:screenOrientation="userLandscape" />
<service android:name="org.yuzu.yuzu_emu.utils.ForegroundService"/> <service android:name="org.yuzu.yuzu_emu.utils.ForegroundService"/>

View file

@ -153,7 +153,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
preferences.edit() preferences.edit()
.putInt(Settings.PREF_CONTROL_SCALE, 50) .putInt(Settings.PREF_CONTROL_SCALE, 50)
.apply() .apply()
binding.surfaceInputOverlay.resetButtonPlacement() binding.surfaceInputOverlay.post { binding.surfaceInputOverlay.resetButtonPlacement() }
} }
private fun updateShowFpsOverlay() { private fun updateShowFpsOverlay() {

View file

@ -18,14 +18,16 @@ import android.hardware.Sensor
import android.hardware.SensorEvent import android.hardware.SensorEvent
import android.hardware.SensorEventListener import android.hardware.SensorEventListener
import android.hardware.SensorManager import android.hardware.SensorManager
import android.os.Build
import android.util.AttributeSet import android.util.AttributeSet
import android.util.DisplayMetrics
import android.view.MotionEvent import android.view.MotionEvent
import android.view.SurfaceView import android.view.SurfaceView
import android.view.View import android.view.View
import android.view.View.OnTouchListener import android.view.View.OnTouchListener
import android.view.WindowInsets
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import androidx.window.layout.WindowMetricsCalculator
import org.yuzu.yuzu_emu.NativeLibrary import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.NativeLibrary.ButtonType import org.yuzu.yuzu_emu.NativeLibrary.ButtonType
import org.yuzu.yuzu_emu.NativeLibrary.StickType import org.yuzu.yuzu_emu.NativeLibrary.StickType
@ -34,7 +36,6 @@ import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.features.settings.model.Settings import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.utils.EmulationMenuSettings import org.yuzu.yuzu_emu.utils.EmulationMenuSettings
/** /**
* Draws the interactive input overlay on top of the * Draws the interactive input overlay on top of the
* [SurfaceView] that is rendering emulation. * [SurfaceView] that is rendering emulation.
@ -51,7 +52,22 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
private val accel = FloatArray(3) private val accel = FloatArray(3)
private var motionTimestamp: Long = 0 private var motionTimestamp: Long = 0
init { private lateinit var windowInsets: WindowInsets
private fun setMotionSensorListener(context: Context) {
val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
val gyroSensor = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE)
val accelSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
sensorManager.registerListener(this, gyroSensor, SensorManager.SENSOR_DELAY_GAME)
sensorManager.registerListener(this, accelSensor, SensorManager.SENSOR_DELAY_GAME)
}
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
windowInsets = rootWindowInsets
if (!preferences.getBoolean(Settings.PREF_OVERLAY_INIT, false)) { if (!preferences.getBoolean(Settings.PREF_OVERLAY_INIT, false)) {
defaultOverlay() defaultOverlay()
} }
@ -72,18 +88,6 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
requestFocus() requestFocus()
} }
private fun setMotionSensorListener(context: Context) {
val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
val gyroSensor = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE)
val accelSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
if (gyroSensor != null) {
sensorManager.registerListener(this, gyroSensor, SensorManager.SENSOR_DELAY_GAME)
}
if (accelSensor != null) {
sensorManager.registerListener(this, accelSensor, SensorManager.SENSOR_DELAY_GAME)
}
}
override fun draw(canvas: Canvas) { override fun draw(canvas: Canvas) {
super.draw(canvas) super.draw(canvas)
for (button in overlayButtons) { for (button in overlayButtons) {
@ -483,141 +487,169 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
private fun defaultOverlayLandscape() { private fun defaultOverlayLandscape() {
// Get screen size // Get screen size
val display = (context as Activity).windowManager.defaultDisplay val windowMetrics =
val outMetrics = DisplayMetrics() WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(context as Activity)
display.getRealMetrics(outMetrics) var maxY = windowMetrics.bounds.height().toFloat()
var maxX = outMetrics.heightPixels.toFloat() var maxX = windowMetrics.bounds.width().toFloat()
var maxY = outMetrics.widthPixels.toFloat() var minY = 0
// Height and width changes depending on orientation. Use the larger value for height. var minX = 0
if (maxY > maxX) {
val tmp = maxX // If we have API access, calculate the safe area to draw the overlay
maxX = maxY var cutoutLeft = 0
maxY = tmp var cutoutBottom = 0
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val insets = windowInsets.displayCutout!!
maxY =
if (insets.boundingRectTop.bottom != 0) insets.boundingRectTop.bottom.toFloat() else maxY
maxX =
if (insets.boundingRectRight.left != 0) insets.boundingRectRight.left.toFloat() else maxX
minX = insets.boundingRectLeft.right - insets.boundingRectLeft.left
minY = insets.boundingRectBottom.top - insets.boundingRectBottom.bottom
cutoutLeft = insets.boundingRectRight.right - insets.boundingRectRight.left
cutoutBottom = insets.boundingRectTop.top - insets.boundingRectTop.bottom
}
// This makes sure that if we have an inset on one side of the screen, we mirror it on
// the other side. Since removing space from one of the max values messes with the scale,
// we also have to account for it using our min values.
if (maxX.toInt() != windowMetrics.bounds.width()) minX += cutoutLeft
if (maxY.toInt() != windowMetrics.bounds.height()) minY += cutoutBottom
if (minX > 0 && maxX.toInt() == windowMetrics.bounds.width()) {
maxX -= (minX * 2)
} else if (minX > 0) {
maxX -= minX
}
if (minY > 0 && maxY.toInt() == windowMetrics.bounds.height()) {
maxY -= (minY * 2)
} else if (minY > 0) {
maxY -= minY
} }
val res = resources
// Each value is a percent from max X/Y stored as an int. Have to bring that value down // Each value is a percent from max X/Y stored as an int. Have to bring that value down
// to a decimal before multiplying by MAX X/Y. // to a decimal before multiplying by MAX X/Y.
preferences.edit() preferences.edit()
.putFloat( .putFloat(
ButtonType.BUTTON_A.toString() + "-X", ButtonType.BUTTON_A.toString() + "-X",
res.getInteger(R.integer.SWITCH_BUTTON_A_X).toFloat() / 1000 * maxX resources.getInteger(R.integer.SWITCH_BUTTON_A_X).toFloat() / 1000 * maxX + minX
) )
.putFloat( .putFloat(
ButtonType.BUTTON_A.toString() + "-Y", ButtonType.BUTTON_A.toString() + "-Y",
res.getInteger(R.integer.SWITCH_BUTTON_A_Y).toFloat() / 1000 * maxY resources.getInteger(R.integer.SWITCH_BUTTON_A_Y).toFloat() / 1000 * maxY + minY
) )
.putFloat( .putFloat(
ButtonType.BUTTON_B.toString() + "-X", ButtonType.BUTTON_B.toString() + "-X",
res.getInteger(R.integer.SWITCH_BUTTON_B_X).toFloat() / 1000 * maxX resources.getInteger(R.integer.SWITCH_BUTTON_B_X).toFloat() / 1000 * maxX + minX
) )
.putFloat( .putFloat(
ButtonType.BUTTON_B.toString() + "-Y", ButtonType.BUTTON_B.toString() + "-Y",
res.getInteger(R.integer.SWITCH_BUTTON_B_Y).toFloat() / 1000 * maxY resources.getInteger(R.integer.SWITCH_BUTTON_B_Y).toFloat() / 1000 * maxY + minY
) )
.putFloat( .putFloat(
ButtonType.BUTTON_X.toString() + "-X", ButtonType.BUTTON_X.toString() + "-X",
res.getInteger(R.integer.SWITCH_BUTTON_X_X).toFloat() / 1000 * maxX resources.getInteger(R.integer.SWITCH_BUTTON_X_X).toFloat() / 1000 * maxX + minX
) )
.putFloat( .putFloat(
ButtonType.BUTTON_X.toString() + "-Y", ButtonType.BUTTON_X.toString() + "-Y",
res.getInteger(R.integer.SWITCH_BUTTON_X_Y).toFloat() / 1000 * maxY resources.getInteger(R.integer.SWITCH_BUTTON_X_Y).toFloat() / 1000 * maxY + minY
) )
.putFloat( .putFloat(
ButtonType.BUTTON_Y.toString() + "-X", ButtonType.BUTTON_Y.toString() + "-X",
res.getInteger(R.integer.SWITCH_BUTTON_Y_X).toFloat() / 1000 * maxX resources.getInteger(R.integer.SWITCH_BUTTON_Y_X).toFloat() / 1000 * maxX + minX
) )
.putFloat( .putFloat(
ButtonType.BUTTON_Y.toString() + "-Y", ButtonType.BUTTON_Y.toString() + "-Y",
res.getInteger(R.integer.SWITCH_BUTTON_Y_Y).toFloat() / 1000 * maxY resources.getInteger(R.integer.SWITCH_BUTTON_Y_Y).toFloat() / 1000 * maxY + minY
) )
.putFloat( .putFloat(
ButtonType.TRIGGER_ZL.toString() + "-X", ButtonType.TRIGGER_ZL.toString() + "-X",
res.getInteger(R.integer.SWITCH_TRIGGER_ZL_X).toFloat() / 1000 * maxX resources.getInteger(R.integer.SWITCH_TRIGGER_ZL_X).toFloat() / 1000 * maxX + minX
) )
.putFloat( .putFloat(
ButtonType.TRIGGER_ZL.toString() + "-Y", ButtonType.TRIGGER_ZL.toString() + "-Y",
res.getInteger(R.integer.SWITCH_TRIGGER_ZL_Y).toFloat() / 1000 * maxY resources.getInteger(R.integer.SWITCH_TRIGGER_ZL_Y).toFloat() / 1000 * maxY + minY
) )
.putFloat( .putFloat(
ButtonType.TRIGGER_ZR.toString() + "-X", ButtonType.TRIGGER_ZR.toString() + "-X",
res.getInteger(R.integer.SWITCH_TRIGGER_ZR_X).toFloat() / 1000 * maxX resources.getInteger(R.integer.SWITCH_TRIGGER_ZR_X).toFloat() / 1000 * maxX + minX
) )
.putFloat( .putFloat(
ButtonType.TRIGGER_ZR.toString() + "-Y", ButtonType.TRIGGER_ZR.toString() + "-Y",
res.getInteger(R.integer.SWITCH_TRIGGER_ZR_Y).toFloat() / 1000 * maxY resources.getInteger(R.integer.SWITCH_TRIGGER_ZR_Y).toFloat() / 1000 * maxY + minY
) )
.putFloat( .putFloat(
ButtonType.DPAD_UP.toString() + "-X", ButtonType.DPAD_UP.toString() + "-X",
res.getInteger(R.integer.SWITCH_BUTTON_DPAD_X).toFloat() / 1000 * maxX resources.getInteger(R.integer.SWITCH_BUTTON_DPAD_X).toFloat() / 1000 * maxX + minX
) )
.putFloat( .putFloat(
ButtonType.DPAD_UP.toString() + "-Y", ButtonType.DPAD_UP.toString() + "-Y",
res.getInteger(R.integer.SWITCH_BUTTON_DPAD_Y).toFloat() / 1000 * maxY resources.getInteger(R.integer.SWITCH_BUTTON_DPAD_Y).toFloat() / 1000 * maxY + minY
) )
.putFloat( .putFloat(
ButtonType.TRIGGER_L.toString() + "-X", ButtonType.TRIGGER_L.toString() + "-X",
res.getInteger(R.integer.SWITCH_TRIGGER_L_X).toFloat() / 1000 * maxX resources.getInteger(R.integer.SWITCH_TRIGGER_L_X).toFloat() / 1000 * maxX + minX
) )
.putFloat( .putFloat(
ButtonType.TRIGGER_L.toString() + "-Y", ButtonType.TRIGGER_L.toString() + "-Y",
res.getInteger(R.integer.SWITCH_TRIGGER_L_Y).toFloat() / 1000 * maxY resources.getInteger(R.integer.SWITCH_TRIGGER_L_Y).toFloat() / 1000 * maxY + minY
) )
.putFloat( .putFloat(
ButtonType.TRIGGER_R.toString() + "-X", ButtonType.TRIGGER_R.toString() + "-X",
res.getInteger(R.integer.SWITCH_TRIGGER_R_X).toFloat() / 1000 * maxX resources.getInteger(R.integer.SWITCH_TRIGGER_R_X).toFloat() / 1000 * maxX + minX
) )
.putFloat( .putFloat(
ButtonType.TRIGGER_R.toString() + "-Y", ButtonType.TRIGGER_R.toString() + "-Y",
res.getInteger(R.integer.SWITCH_TRIGGER_R_Y).toFloat() / 1000 * maxY resources.getInteger(R.integer.SWITCH_TRIGGER_R_Y).toFloat() / 1000 * maxY + minY
) )
.putFloat( .putFloat(
ButtonType.BUTTON_PLUS.toString() + "-X", ButtonType.BUTTON_PLUS.toString() + "-X",
res.getInteger(R.integer.SWITCH_BUTTON_PLUS_X).toFloat() / 1000 * maxX resources.getInteger(R.integer.SWITCH_BUTTON_PLUS_X).toFloat() / 1000 * maxX + minX
) )
.putFloat( .putFloat(
ButtonType.BUTTON_PLUS.toString() + "-Y", ButtonType.BUTTON_PLUS.toString() + "-Y",
res.getInteger(R.integer.SWITCH_BUTTON_PLUS_Y).toFloat() / 1000 * maxY resources.getInteger(R.integer.SWITCH_BUTTON_PLUS_Y).toFloat() / 1000 * maxY + minY
) )
.putFloat( .putFloat(
ButtonType.BUTTON_MINUS.toString() + "-X", ButtonType.BUTTON_MINUS.toString() + "-X",
res.getInteger(R.integer.SWITCH_BUTTON_MINUS_X).toFloat() / 1000 * maxX resources.getInteger(R.integer.SWITCH_BUTTON_MINUS_X).toFloat() / 1000 * maxX + minX
) )
.putFloat( .putFloat(
ButtonType.BUTTON_MINUS.toString() + "-Y", ButtonType.BUTTON_MINUS.toString() + "-Y",
res.getInteger(R.integer.SWITCH_BUTTON_MINUS_Y).toFloat() / 1000 * maxY resources.getInteger(R.integer.SWITCH_BUTTON_MINUS_Y).toFloat() / 1000 * maxY + minY
) )
.putFloat( .putFloat(
ButtonType.BUTTON_HOME.toString() + "-X", ButtonType.BUTTON_HOME.toString() + "-X",
res.getInteger(R.integer.SWITCH_BUTTON_HOME_X).toFloat() / 1000 * maxX resources.getInteger(R.integer.SWITCH_BUTTON_HOME_X).toFloat() / 1000 * maxX + minX
) )
.putFloat( .putFloat(
ButtonType.BUTTON_HOME.toString() + "-Y", ButtonType.BUTTON_HOME.toString() + "-Y",
res.getInteger(R.integer.SWITCH_BUTTON_HOME_Y).toFloat() / 1000 * maxY resources.getInteger(R.integer.SWITCH_BUTTON_HOME_Y).toFloat() / 1000 * maxY + minY
) )
.putFloat( .putFloat(
ButtonType.BUTTON_CAPTURE.toString() + "-X", ButtonType.BUTTON_CAPTURE.toString() + "-X",
res.getInteger(R.integer.SWITCH_BUTTON_CAPTURE_X).toFloat() / 1000 * maxX resources.getInteger(R.integer.SWITCH_BUTTON_CAPTURE_X)
.toFloat() / 1000 * maxX + minX
) )
.putFloat( .putFloat(
ButtonType.BUTTON_CAPTURE.toString() + "-Y", ButtonType.BUTTON_CAPTURE.toString() + "-Y",
res.getInteger(R.integer.SWITCH_BUTTON_CAPTURE_Y).toFloat() / 1000 * maxY resources.getInteger(R.integer.SWITCH_BUTTON_CAPTURE_Y)
.toFloat() / 1000 * maxY + minY
) )
.putFloat( .putFloat(
ButtonType.STICK_R.toString() + "-X", ButtonType.STICK_R.toString() + "-X",
res.getInteger(R.integer.SWITCH_STICK_R_X).toFloat() / 1000 * maxX resources.getInteger(R.integer.SWITCH_STICK_R_X).toFloat() / 1000 * maxX + minX
) )
.putFloat( .putFloat(
ButtonType.STICK_R.toString() + "-Y", ButtonType.STICK_R.toString() + "-Y",
res.getInteger(R.integer.SWITCH_STICK_R_Y).toFloat() / 1000 * maxY resources.getInteger(R.integer.SWITCH_STICK_R_Y).toFloat() / 1000 * maxY + minY
) )
.putFloat( .putFloat(
ButtonType.STICK_L.toString() + "-X", ButtonType.STICK_L.toString() + "-X",
res.getInteger(R.integer.SWITCH_STICK_L_X).toFloat() / 1000 * maxX resources.getInteger(R.integer.SWITCH_STICK_L_X).toFloat() / 1000 * maxX + minX
) )
.putFloat( .putFloat(
ButtonType.STICK_L.toString() + "-Y", ButtonType.STICK_L.toString() + "-Y",
res.getInteger(R.integer.SWITCH_STICK_L_Y).toFloat() / 1000 * maxY resources.getInteger(R.integer.SWITCH_STICK_L_Y).toFloat() / 1000 * maxY + minY
) )
.commit() .commit()
// We want to commit right away, otherwise the overlay could load before this is saved. // We want to commit right away, otherwise the overlay could load before this is saved.

View file

@ -40,6 +40,8 @@
<item name="android:navigationBarColor">@android:color/transparent</item> <item name="android:navigationBarColor">@android:color/transparent</item>
<item name="sliderStyle">@style/YuzuSlider</item> <item name="sliderStyle">@style/YuzuSlider</item>
<item name="android:windowLayoutInDisplayCutoutMode">default</item>
</style> </style>
<!-- Trick for API >= 29 specific changes --> <!-- Trick for API >= 29 specific changes -->