Skip to content

Commit

Permalink
Add mouseButton implementation on Android (#2680)
Browse files Browse the repository at this point in the history
## Description

This PR adds implementation of `mouseButton` prop on android.

Since `actionButton` field is available only in API >= 23, we provide full support only for those versions. The only thing that changes for API < 23 is ignoring `ACTION_BUTTON_*`, because we don't want handlers to react to both type of events.

Note that it requires [this PR](#2676) to work.

## Test plan

Tested on `MouseButtons` example.
  • Loading branch information
m-bert authored Feb 20, 2024
1 parent fa3af2e commit b4ebd18
Show file tree
Hide file tree
Showing 7 changed files with 73 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ class FlingGestureHandler : GestureHandler<FlingGestureHandler>() {
}

override fun onHandle(event: MotionEvent, sourceEvent: MotionEvent) {
if (!shouldActivateWithMouse(sourceEvent)) {
return
}

val state = state
if (state == STATE_UNDETERMINED) {
startFling(sourceEvent)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import android.content.Context
import android.content.ContextWrapper
import android.graphics.PointF
import android.graphics.Rect
import android.os.Build
import android.view.MotionEvent
import android.view.MotionEvent.PointerCoords
import android.view.MotionEvent.PointerProperties
Expand Down Expand Up @@ -68,6 +69,8 @@ open class GestureHandler<ConcreteGestureHandlerT : GestureHandler<ConcreteGestu
private var onTouchEventListener: OnTouchEventListener? = null
private var interactionController: GestureHandlerInteractionController? = null

protected var mouseButton = 0

@Suppress("UNCHECKED_CAST")
protected fun self(): ConcreteGestureHandlerT = this as ConcreteGestureHandlerT

Expand Down Expand Up @@ -159,6 +162,10 @@ open class GestureHandler<ConcreteGestureHandlerT : GestureHandler<ConcreteGestu
fun setInteractionController(controller: GestureHandlerInteractionController?): ConcreteGestureHandlerT =
applySelf { interactionController = controller }

fun setMouseButton(mouseButton: Int) = apply {
this.mouseButton = mouseButton
}

fun prepare(view: View?, orchestrator: GestureHandlerOrchestrator?) {
check(!(this.view != null || this.orchestrator != null)) { "Already prepared or hasn't been reset" }
Arrays.fill(trackedPointerIDs, -1)
Expand Down Expand Up @@ -688,6 +695,46 @@ open class GestureHandler<ConcreteGestureHandlerT : GestureHandler<ConcreteGestu
protected open fun onReset() {}
protected open fun onCancel() {}

private fun isButtonInConfig(clickedButton: Int): Boolean {
if (mouseButton == 0) {
return clickedButton == MotionEvent.BUTTON_PRIMARY
}

return clickedButton and mouseButton != 0
}

protected fun shouldActivateWithMouse(sourceEvent: MotionEvent): Boolean {
// While using mouse, we get both sets of events, for example ACTION_DOWN and ACTION_BUTTON_PRESS. That's why we want to take actions to only one of them.
// On API >= 23, we will use events with infix BUTTON, otherwise we use standard action events (like ACTION_DOWN).

with(sourceEvent) {
// To use actionButton, we need API >= 23.
if (getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// While using mouse, we want to ignore default events for touch.
if (action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_POINTER_UP || action == MotionEvent.ACTION_POINTER_DOWN) {
return@shouldActivateWithMouse false
}

// We don't want to do anything if wrong button was clicked. If we received event for BUTTON, we have to use actionButton to get which one was clicked.
if (action != MotionEvent.ACTION_MOVE && !isButtonInConfig(actionButton)) {
return@shouldActivateWithMouse false
}

// When we receive ACTION_MOVE, we have to check buttonState field.
if (action == MotionEvent.ACTION_MOVE && !isButtonInConfig(buttonState)) {
return@shouldActivateWithMouse false
}
} else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
// We do not fully support mouse below API 23, so we will ignore BUTTON events.
if (action == MotionEvent.ACTION_BUTTON_PRESS || action == MotionEvent.ACTION_BUTTON_RELEASE) {
return@shouldActivateWithMouse false
}
}
}

return true
}

/**
* Transforms a point in the coordinate space of the wrapperView (GestureHandlerRootView) to
* coordinate space of the view the gesture is attached to.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ class LongPressGestureHandler(context: Context) : GestureHandler<LongPressGestur
}

override fun onHandle(event: MotionEvent, sourceEvent: MotionEvent) {
if (!shouldActivateWithMouse(sourceEvent)) {
return
}

if (state == STATE_UNDETERMINED) {
previousTime = SystemClock.uptimeMillis()
startTime = previousTime
Expand All @@ -51,7 +55,7 @@ class LongPressGestureHandler(context: Context) : GestureHandler<LongPressGestur
activate()
}
}
if (sourceEvent.actionMasked == MotionEvent.ACTION_UP) {
if (sourceEvent.actionMasked == MotionEvent.ACTION_UP || sourceEvent.actionMasked == MotionEvent.ACTION_BUTTON_RELEASE) {
handler?.let {
it.removeCallbacksAndMessages(null)
handler = null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,10 @@ class PanGestureHandler(context: Context?) : GestureHandler<PanGestureHandler>()
}

override fun onHandle(event: MotionEvent, sourceEvent: MotionEvent) {
if (!shouldActivateWithMouse(sourceEvent)) {
return
}

val state = state
val action = sourceEvent.actionMasked
if (action == MotionEvent.ACTION_POINTER_UP || action == MotionEvent.ACTION_POINTER_DOWN) {
Expand Down Expand Up @@ -246,7 +250,7 @@ class PanGestureHandler(context: Context?) : GestureHandler<PanGestureHandler>()
velocityX = velocityTracker!!.xVelocity
velocityY = velocityTracker!!.yVelocity
}
if (action == MotionEvent.ACTION_UP) {
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_BUTTON_RELEASE) {
if (state == STATE_ACTIVE) {
end()
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@ class TapGestureHandler : GestureHandler<TapGestureHandler>() {
}

override fun onHandle(event: MotionEvent, sourceEvent: MotionEvent) {
if (!shouldActivateWithMouse(sourceEvent)) {
return
}

val state = state
val action = sourceEvent.actionMasked
if (state == STATE_UNDETERMINED) {
Expand All @@ -130,14 +134,14 @@ class TapGestureHandler : GestureHandler<TapGestureHandler>() {
if (shouldFail()) {
fail()
} else if (state == STATE_UNDETERMINED) {
if (action == MotionEvent.ACTION_DOWN) {
if (action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_BUTTON_PRESS) {
begin()
}
startTap()
} else if (state == STATE_BEGAN) {
if (action == MotionEvent.ACTION_UP) {
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_BUTTON_RELEASE) {
endTap()
} else if (action == MotionEvent.ACTION_DOWN) {
} else if (action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_BUTTON_PRESS) {
startTap()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ class RNGestureHandlerModule(reactContext: ReactApplicationContext?) :
if (config.hasKey(KEY_MANUAL_ACTIVATION)) {
handler.setManualActivation(config.getBoolean(KEY_MANUAL_ACTIVATION))
}
if (config.hasKey("mouseButton")) {
handler.setMouseButton(config.getInt("mouseButton"))
}
}

abstract fun createEventBuilder(handler: T): GestureHandlerEventDataBuilder<T>
Expand Down
4 changes: 2 additions & 2 deletions src/web/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,8 @@ export interface AdaptedEvent {

export enum MouseButton {
LEFT = 1,
MIDDLE = 2,
RIGHT = 4,
RIGHT = 2,
MIDDLE = 4,
BUTTON_4 = 8,
BUTTON_5 = 16,
ALL = 31,
Expand Down

0 comments on commit b4ebd18

Please sign in to comment.