Browse Source

android: Inset input overlay based on system cutouts

nce_cpp
Charles Lombardo 3 years ago
committed by bunnei
parent
commit
4330135912
  1. 1
      src/android/app/build.gradle
  2. 2
      src/android/app/src/main/AndroidManifest.xml
  3. 2
      src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
  4. 146
      src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt
  5. 2
      src/android/app/src/main/res/values/themes.xml

1
src/android/app/build.gradle

@ -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'

2
src/android/app/src/main/AndroidManifest.xml

@ -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"/>

2
src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt

@ -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() {

146
src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt

@ -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 outMetrics = DisplayMetrics()
display.getRealMetrics(outMetrics)
var maxX = outMetrics.heightPixels.toFloat()
var maxY = outMetrics.widthPixels.toFloat()
// Height and width changes depending on orientation. Use the larger value for height.
if (maxY > maxX) {
val tmp = maxX
maxX = maxY
maxY = tmp
}
val res = resources
val windowMetrics =
WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(context as Activity)
var maxY = windowMetrics.bounds.height().toFloat()
var maxX = windowMetrics.bounds.width().toFloat()
var minY = 0
var minX = 0
// If we have API access, calculate the safe area to draw the overlay
var cutoutLeft = 0
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
}
// 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.

2
src/android/app/src/main/res/values/themes.xml

@ -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 -->

Loading…
Cancel
Save