mirror of
https://github.com/izzy2lost/xemu.git
synced 2026-07-06 00:20:22 -07:00
added swipe up to open game menu
This commit is contained in:
@@ -11,11 +11,13 @@ import android.os.Process
|
||||
import android.util.Log
|
||||
import android.view.InputDevice
|
||||
import android.view.KeyEvent
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.WindowInsets
|
||||
import android.view.WindowInsetsController
|
||||
import android.view.WindowManager
|
||||
import android.view.ViewConfiguration
|
||||
import android.widget.BaseAdapter
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.ImageView
|
||||
@@ -30,6 +32,7 @@ import org.libsdl.app.SDLSurface
|
||||
import java.io.File
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
import kotlin.math.max
|
||||
|
||||
class MainActivity : SDLActivity(), InputManager.InputDeviceListener {
|
||||
companion object {
|
||||
@@ -58,6 +61,7 @@ class MainActivity : SDLActivity(), InputManager.InputDeviceListener {
|
||||
private var startupSnapshotSlot: Int? = null
|
||||
private var startupSnapshotLoadScheduled = false
|
||||
@Volatile private var processTerminationScheduled = false
|
||||
private lateinit var swipeUpGestureRecognizer: SwipeUpGestureRecognizer
|
||||
|
||||
override fun createSDLSurface(context: Context): SDLSurface {
|
||||
return super.createSDLSurface(context).apply {
|
||||
@@ -76,6 +80,7 @@ class MainActivity : SDLActivity(), InputManager.InputDeviceListener {
|
||||
if (requestedSlot in 1..TOTAL_SNAPSHOT_SLOTS) {
|
||||
startupSnapshotSlot = requestedSlot
|
||||
}
|
||||
initializeSwipeMenuGesture()
|
||||
setupOnScreenController()
|
||||
setupControllerDetection()
|
||||
hideSystemUI()
|
||||
@@ -121,6 +126,16 @@ class MainActivity : SDLActivity(), InputManager.InputDeviceListener {
|
||||
return super.dispatchKeyEvent(event)
|
||||
}
|
||||
|
||||
override fun dispatchTouchEvent(event: MotionEvent): Boolean {
|
||||
if (shouldHandleActivitySwipeMenu() && swipeUpGestureRecognizer.onTouchEvent(event)) {
|
||||
return true
|
||||
}
|
||||
if (!shouldHandleActivitySwipeMenu()) {
|
||||
swipeUpGestureRecognizer.reset()
|
||||
}
|
||||
return super.dispatchTouchEvent(event)
|
||||
}
|
||||
|
||||
private fun hideSystemUI() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
// Android 11 (API 30) and above
|
||||
@@ -144,6 +159,23 @@ class MainActivity : SDLActivity(), InputManager.InputDeviceListener {
|
||||
}
|
||||
}
|
||||
|
||||
private fun initializeSwipeMenuGesture() {
|
||||
val swipeTouchSlop = ViewConfiguration.get(this).scaledTouchSlop.toFloat()
|
||||
swipeUpGestureRecognizer = SwipeUpGestureRecognizer(
|
||||
minDistancePx = {
|
||||
max(currentGestureHostHeight() * 0.14f, swipeTouchSlop * 8f)
|
||||
},
|
||||
touchSlopPx = { swipeTouchSlop },
|
||||
canStartAt = { _, y ->
|
||||
y >= currentGestureHostHeight() * 0.35f
|
||||
},
|
||||
onTriggered = {
|
||||
onScreenController?.resetAllInputs()
|
||||
showInGameMenu()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun setupOnScreenController() {
|
||||
// Create on-screen controller
|
||||
onScreenController = OnScreenController(this).apply {
|
||||
@@ -180,6 +212,7 @@ class MainActivity : SDLActivity(), InputManager.InputDeviceListener {
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
swipeUpGestureRecognizer.reset()
|
||||
onScreenController?.resetAllInputs()
|
||||
super.onPause()
|
||||
}
|
||||
@@ -267,6 +300,7 @@ class MainActivity : SDLActivity(), InputManager.InputDeviceListener {
|
||||
|
||||
if (shouldShow != isControllerVisible) {
|
||||
isControllerVisible = shouldShow
|
||||
swipeUpGestureRecognizer.reset()
|
||||
onScreenController?.visibility = if (shouldShow) View.VISIBLE else View.GONE
|
||||
}
|
||||
}
|
||||
@@ -292,6 +326,7 @@ class MainActivity : SDLActivity(), InputManager.InputDeviceListener {
|
||||
|
||||
override fun onDestroy() {
|
||||
Log.i(TAG, "onDestroy()")
|
||||
swipeUpGestureRecognizer.reset()
|
||||
inGameMenuDialog?.dismiss()
|
||||
inGameMenuDialog = null
|
||||
val shouldTerminateProcess = isFinishing && !isChangingConfigurations
|
||||
@@ -665,6 +700,11 @@ class MainActivity : SDLActivity(), InputManager.InputDeviceListener {
|
||||
}
|
||||
|
||||
private fun showInGameMenu() {
|
||||
swipeUpGestureRecognizer.reset()
|
||||
if (inGameMenuDialog?.isShowing == true) {
|
||||
return
|
||||
}
|
||||
|
||||
val options = arrayOf(
|
||||
getString(R.string.in_game_menu_resume),
|
||||
if (isControllerVisible) {
|
||||
@@ -700,6 +740,15 @@ class MainActivity : SDLActivity(), InputManager.InputDeviceListener {
|
||||
dialog.show()
|
||||
}
|
||||
|
||||
private fun shouldHandleActivitySwipeMenu(): Boolean {
|
||||
return !isControllerVisible && inGameMenuDialog?.isShowing != true
|
||||
}
|
||||
|
||||
private fun currentGestureHostHeight(): Float {
|
||||
val hostHeight = mLayout?.height ?: window.decorView.height
|
||||
return max(hostHeight, 1).toFloat()
|
||||
}
|
||||
|
||||
private fun exitToGameLibrary() {
|
||||
val intent = Intent(this, GameLibraryActivity::class.java).apply {
|
||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||
|
||||
@@ -8,6 +8,8 @@ import android.graphics.PointF
|
||||
import android.util.AttributeSet
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.ViewConfiguration
|
||||
import kotlin.math.max
|
||||
import kotlin.math.pow
|
||||
import kotlin.math.sqrt
|
||||
|
||||
@@ -25,6 +27,21 @@ class OnScreenController @JvmOverloads constructor(
|
||||
private var menuButtonRadius = 0f
|
||||
private var menuButtonPressed = false
|
||||
private var menuButtonPointerId = -1
|
||||
private val swipeTouchSlop = ViewConfiguration.get(context).scaledTouchSlop.toFloat()
|
||||
private val swipeUpGestureRecognizer = SwipeUpGestureRecognizer(
|
||||
minDistancePx = {
|
||||
max(height * 0.14f, swipeTouchSlop * 8f)
|
||||
},
|
||||
touchSlopPx = { swipeTouchSlop },
|
||||
canStartAt = { x, y ->
|
||||
y >= height * 0.35f && !isPointInInteractiveElement(x, y)
|
||||
},
|
||||
onTriggered = {
|
||||
handleCancel()
|
||||
onMenuButtonTapped?.invoke()
|
||||
invalidate()
|
||||
},
|
||||
)
|
||||
|
||||
var onMenuButtonTapped: (() -> Unit)? = null
|
||||
|
||||
@@ -323,6 +340,11 @@ class OnScreenController @JvmOverloads constructor(
|
||||
}
|
||||
|
||||
override fun onTouchEvent(event: MotionEvent): Boolean {
|
||||
if (swipeUpGestureRecognizer.onTouchEvent(event)) {
|
||||
invalidate()
|
||||
return true
|
||||
}
|
||||
|
||||
val pointerIndex = event.actionIndex
|
||||
val pointerId = event.getPointerId(pointerIndex)
|
||||
val x = event.getX(pointerIndex)
|
||||
@@ -450,6 +472,8 @@ class OnScreenController @JvmOverloads constructor(
|
||||
}
|
||||
|
||||
private fun handleCancel() {
|
||||
swipeUpGestureRecognizer.reset()
|
||||
|
||||
if (menuButtonPointerId != -1) {
|
||||
menuButtonPressed = false
|
||||
menuButtonPointerId = -1
|
||||
@@ -534,6 +558,18 @@ class OnScreenController @JvmOverloads constructor(
|
||||
return sqrt(dx.pow(2) + dy.pow(2)) <= radius
|
||||
}
|
||||
|
||||
private fun isPointInInteractiveElement(x: Float, y: Float): Boolean {
|
||||
if (isPointInCircle(x, y, menuButtonCenter, menuButtonRadius)) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (sticks.values.any { isPointInCircle(x, y, it.center, it.radius) }) {
|
||||
return true
|
||||
}
|
||||
|
||||
return buttons.values.any { isPointInCircle(x, y, it.center, it.radius) }
|
||||
}
|
||||
|
||||
fun setVisibility(visible: Boolean) {
|
||||
visibility = if (visible) View.VISIBLE else View.GONE
|
||||
}
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
package com.izzy2lost.x1box
|
||||
|
||||
import android.view.MotionEvent
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.max
|
||||
|
||||
class SwipeUpGestureRecognizer(
|
||||
private val minDistancePx: () -> Float,
|
||||
private val touchSlopPx: () -> Float,
|
||||
private val canStartAt: (x: Float, y: Float) -> Boolean,
|
||||
private val onTriggered: () -> Unit,
|
||||
) {
|
||||
private var activePointerId = INVALID_POINTER_ID
|
||||
private var startX = 0f
|
||||
private var startY = 0f
|
||||
private var tracking = false
|
||||
private var triggered = false
|
||||
|
||||
fun onTouchEvent(event: MotionEvent): Boolean {
|
||||
if (triggered) {
|
||||
when (event.actionMasked) {
|
||||
MotionEvent.ACTION_UP,
|
||||
MotionEvent.ACTION_CANCEL -> reset()
|
||||
MotionEvent.ACTION_POINTER_UP -> {
|
||||
if (event.getPointerId(event.actionIndex) == activePointerId) {
|
||||
reset()
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
when (event.actionMasked) {
|
||||
MotionEvent.ACTION_DOWN -> {
|
||||
reset()
|
||||
val x = event.getX(0)
|
||||
val y = event.getY(0)
|
||||
if (canStartAt(x, y)) {
|
||||
activePointerId = event.getPointerId(0)
|
||||
startX = x
|
||||
startY = y
|
||||
tracking = true
|
||||
}
|
||||
}
|
||||
|
||||
MotionEvent.ACTION_POINTER_DOWN -> {
|
||||
reset()
|
||||
}
|
||||
|
||||
MotionEvent.ACTION_MOVE -> {
|
||||
if (!tracking) {
|
||||
return false
|
||||
}
|
||||
|
||||
val pointerIndex = event.findPointerIndex(activePointerId)
|
||||
if (pointerIndex < 0) {
|
||||
reset()
|
||||
return false
|
||||
}
|
||||
|
||||
val x = event.getX(pointerIndex)
|
||||
val y = event.getY(pointerIndex)
|
||||
val dx = x - startX
|
||||
val dy = y - startY
|
||||
val upwardDistance = -dy
|
||||
val slop = touchSlopPx()
|
||||
|
||||
if (dy > slop || abs(dx) > max(slop * 5f, upwardDistance * 0.9f + slop)) {
|
||||
reset()
|
||||
return false
|
||||
}
|
||||
|
||||
if (upwardDistance >= minDistancePx() &&
|
||||
upwardDistance >= abs(dx) * 1.35f) {
|
||||
triggered = true
|
||||
tracking = false
|
||||
onTriggered()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
MotionEvent.ACTION_POINTER_UP -> {
|
||||
if (event.getPointerId(event.actionIndex) == activePointerId) {
|
||||
reset()
|
||||
}
|
||||
}
|
||||
|
||||
MotionEvent.ACTION_UP,
|
||||
MotionEvent.ACTION_CANCEL -> reset()
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
fun reset() {
|
||||
activePointerId = INVALID_POINTER_ID
|
||||
startX = 0f
|
||||
startY = 0f
|
||||
tracking = false
|
||||
triggered = false
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val INVALID_POINTER_ID = -1
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user