android: Reimplement multitouch code correctly

This commit is contained in:
OpenSauce04 2025-08-11 01:14:32 +01:00 committed by OpenSauce
parent a964e63722
commit 2f6e8b5756
4 changed files with 189 additions and 160 deletions

View File

@ -97,15 +97,36 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
return onTouchWhileEditing(event) return onTouchWhileEditing(event)
} }
var hasActiveButtons = false val motionEvent = event.action and MotionEvent.ACTION_MASK
val pointerIndex = event.actionIndex val isActionDown =
motionEvent == MotionEvent.ACTION_DOWN || motionEvent == MotionEvent.ACTION_POINTER_DOWN
val isActionMove = motionEvent == MotionEvent.ACTION_MOVE
val isActionUp =
motionEvent == MotionEvent.ACTION_UP || motionEvent == MotionEvent.ACTION_POINTER_UP
val pointerList = (0 until event.pointerCount).toMutableList()
// Move the pointer that triggered the most recent event to the front
// of the list so that it is processed first
val currentActionPointer = event.actionIndex
pointerList.remove(pointerList.indexOf(currentActionPointer))
pointerList.add(0, currentActionPointer)
// Set up a loop for if we need to check touches other than the most recent one
// (Only happens if we're dragging the touch)
for (pointerIndex in pointerList) {
val pointerId = event.getPointerId(pointerIndex) val pointerId = event.getPointerId(pointerIndex)
val xPosition = event.getX(pointerIndex).toInt()
val yPosition = event.getY(pointerIndex).toInt()
var hasActiveButtons = false
for (button in overlayButtons) { for (button in overlayButtons) {
if (button.trackId == pointerId) { if (button.trackId == pointerId) {
hasActiveButtons = true hasActiveButtons = true
break break
} }
} }
var hasActiveDpad = false var hasActiveDpad = false
if (!hasActiveButtons) { if (!hasActiveButtons) {
for (dpad in overlayDpads) { for (dpad in overlayDpads) {
@ -126,13 +147,28 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
} }
} }
val hasActiveOverlay = hasActiveButtons || hasActiveDpad || hasActiveJoystick
if (preferences.getBoolean("isTouchEnabled", true) && !hasActiveOverlay) {
if (isActionMove) {
NativeLibrary.onTouchMoved(xPosition.toFloat(), yPosition.toFloat())
continue
}
else if (isActionUp) {
NativeLibrary.onTouchEvent(0f, 0f, false)
break // Up and down actions shouldn't loop
}
}
var anyOverlayStateChanged = false
var shouldUpdateView = false var shouldUpdateView = false
if(!hasActiveDpad && !hasActiveJoystick) { if(!hasActiveDpad && !hasActiveJoystick) {
for (button in overlayButtons) { for (button in overlayButtons) {
val stateChanged = button.updateStatus(event, hasActiveButtons, this) val stateChanged = button.updateStatus(event, pointerIndex, hasActiveButtons, this)
if (!stateChanged) { if (!stateChanged) {
continue continue
} }
anyOverlayStateChanged = true
if (button.id == NativeLibrary.ButtonType.BUTTON_SWAP && button.status == NativeLibrary.ButtonState.PRESSED) { if (button.id == NativeLibrary.ButtonType.BUTTON_SWAP && button.status == NativeLibrary.ButtonState.PRESSED) {
swapScreen() swapScreen()
@ -155,6 +191,7 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
for (dpad in overlayDpads) { for (dpad in overlayDpads) {
val stateChanged = dpad.updateStatus( val stateChanged = dpad.updateStatus(
event, event,
pointerIndex,
hasActiveDpad, hasActiveDpad,
EmulationMenuSettings.dpadSlide, EmulationMenuSettings.dpadSlide,
this this
@ -162,6 +199,7 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
if (!stateChanged) { if (!stateChanged) {
continue continue
} }
anyOverlayStateChanged = true
NativeLibrary.onGamePadEvent( NativeLibrary.onGamePadEvent(
NativeLibrary.TouchScreenDevice, NativeLibrary.TouchScreenDevice,
@ -190,10 +228,11 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
if(!hasActiveDpad && !hasActiveButtons) { if(!hasActiveDpad && !hasActiveButtons) {
for (joystick in overlayJoysticks) { for (joystick in overlayJoysticks) {
val stateChanged = joystick.updateStatus(event, hasActiveJoystick, this) val stateChanged = joystick.updateStatus(event, pointerIndex, hasActiveJoystick, this)
if (!stateChanged) { if (!stateChanged) {
continue continue
} }
anyOverlayStateChanged = true
val axisID = joystick.joystickId val axisID = joystick.joystickId
NativeLibrary.onGamePadMoveEvent( NativeLibrary.onGamePadMoveEvent(
@ -211,54 +250,42 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
invalidate() invalidate()
} }
if (!preferences.getBoolean("isTouchEnabled", true)) { if (preferences.getBoolean("isTouchEnabled", true) &&
return true isActionDown &&
!anyOverlayStateChanged
) {
// These need to be recalculated because touching the area
// right in the middle of the dpad (between the "buttons") or
// tapping a joystick in a certain way both don't cause
// `anyOverlayStateChanged` to be set to true
var isDpadPressed = false
for (dpad in overlayDpads) {
if (dpad.trackId == pointerId) {
isDpadPressed = true
break
}
}
var isJoystickPressed = false
for (joystick in overlayJoysticks) {
if (joystick.trackId == pointerId) {
isJoystickPressed = true
break
}
} }
val xPosition = event.getX(pointerIndex).toInt() if (!isDpadPressed && !isJoystickPressed) {
val yPosition = event.getY(pointerIndex).toInt()
val motionEvent = event.action and MotionEvent.ACTION_MASK
val isActionDown =
motionEvent == MotionEvent.ACTION_DOWN || motionEvent == MotionEvent.ACTION_POINTER_DOWN
val isActionMove = motionEvent == MotionEvent.ACTION_MOVE
val isActionUp =
motionEvent == MotionEvent.ACTION_UP || motionEvent == MotionEvent.ACTION_POINTER_UP
if (isActionDown && !isTouchInputConsumed(pointerId)) {
NativeLibrary.onTouchEvent(xPosition.toFloat(), yPosition.toFloat(), true) NativeLibrary.onTouchEvent(xPosition.toFloat(), yPosition.toFloat(), true)
} }
if (isActionMove) {
for (i in 0 until event.pointerCount) {
val fingerId = event.getPointerId(i)
if (isTouchInputConsumed(fingerId)) {
continue
}
NativeLibrary.onTouchMoved(xPosition.toFloat(), yPosition.toFloat())
}
}
if (isActionUp && !isTouchInputConsumed(pointerId)) {
NativeLibrary.onTouchEvent(0f, 0f, false)
}
return true
} }
private fun isTouchInputConsumed(trackId: Int): Boolean { // We should only loop here if touch is being dragged
overlayButtons.forEach { if (!isActionMove) {
if (it.trackId == trackId) { break
}
}
return true return true
} }
}
overlayDpads.forEach {
if (it.trackId == trackId) {
return true
}
}
overlayJoysticks.forEach {
if (it.trackId == trackId) {
return true
}
}
return false
}
fun onTouchWhileEditing(event: MotionEvent): Boolean { fun onTouchWhileEditing(event: MotionEvent): Boolean {
val pointerIndex = event.actionIndex val pointerIndex = event.actionIndex

View File

@ -70,9 +70,8 @@ class InputOverlayDrawableButton(
* *
* @return true if value was changed * @return true if value was changed
*/ */
fun updateStatus(event: MotionEvent, hasActiveButtons: Boolean, overlay: InputOverlay): Boolean { fun updateStatus(event: MotionEvent, pointerIndex: Int, hasActiveButtons: Boolean, overlay: InputOverlay): Boolean {
val buttonSliding = EmulationMenuSettings.buttonSlide val buttonSliding = EmulationMenuSettings.buttonSlide
val pointerIndex = event.actionIndex
val xPosition = event.getX(pointerIndex).toInt() val xPosition = event.getX(pointerIndex).toInt()
val yPosition = event.getY(pointerIndex).toInt() val yPosition = event.getY(pointerIndex).toInt()
val pointerId = event.getPointerId(pointerIndex) val pointerId = event.getPointerId(pointerIndex)
@ -92,7 +91,7 @@ class InputOverlayDrawableButton(
if (trackId != pointerId) { if (trackId != pointerId) {
return false return false
} }
buttonUp(overlay) buttonUp(overlay, false)
return true return true
} }
@ -105,11 +104,14 @@ class InputOverlayDrawableButton(
if (inside || trackId != pointerId) { if (inside || trackId != pointerId) {
return false return false
} }
// prevent the first (directly pressed) button to deactivate when sliding off // prevent the first (directly pressed) button to deactivate when sliding off
if (buttonSliding == ButtonSlidingMode.Alternative.int && isMotionFirstButton) { if (buttonSliding == ButtonSlidingMode.Alternative.int && isMotionFirstButton) {
return false return false
} }
buttonUp(overlay)
val preserveTrackId = (buttonSliding != ButtonSlidingMode.Disabled.int)
buttonUp(overlay, preserveTrackId)
return true return true
} else { } else {
// button was not yet pressed // button was not yet pressed
@ -132,10 +134,12 @@ class InputOverlayDrawableButton(
overlay.hapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY) overlay.hapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY)
} }
private fun buttonUp(overlay: InputOverlay) { private fun buttonUp(overlay: InputOverlay, preserveTrackId: Boolean) {
pressedState = false pressedState = false
isMotionFirstButton = false isMotionFirstButton = false
if (!preserveTrackId) {
trackId = -1 trackId = -1
}
overlay.hapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY_RELEASE) overlay.hapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY_RELEASE)
} }

View File

@ -63,9 +63,8 @@ class InputOverlayDrawableDpad(
trackId = -1 trackId = -1
} }
fun updateStatus(event: MotionEvent, hasActiveButtons: Boolean, dpadSlide: Boolean, overlay: InputOverlay): Boolean { fun updateStatus(event: MotionEvent, pointerIndex: Int, hasActiveButtons: Boolean, dpadSlide: Boolean, overlay: InputOverlay): Boolean {
var isDown = false var isDown = false
val pointerIndex = event.actionIndex
val xPosition = event.getX(pointerIndex).toInt() val xPosition = event.getX(pointerIndex).toInt()
val yPosition = event.getY(pointerIndex).toInt() val yPosition = event.getY(pointerIndex).toInt()
val pointerId = event.getPointerId(pointerIndex) val pointerId = event.getPointerId(pointerIndex)

View File

@ -93,8 +93,7 @@ class InputOverlayDrawableJoystick(
currentStateBitmapDrawable.draw(canvas) currentStateBitmapDrawable.draw(canvas)
} }
fun updateStatus(event: MotionEvent, hasActiveButtons: Boolean, overlay: InputOverlay): Boolean { fun updateStatus(event: MotionEvent, pointerIndex: Int, hasActiveButtons: Boolean, overlay: InputOverlay): Boolean {
val pointerIndex = event.actionIndex
val xPosition = event.getX(pointerIndex).toInt() val xPosition = event.getX(pointerIndex).toInt()
val yPosition = event.getY(pointerIndex).toInt() val yPosition = event.getY(pointerIndex).toInt()
val pointerId = event.getPointerId(pointerIndex) val pointerId = event.getPointerId(pointerIndex)