Skip to content

Commit 0196da5

Browse files
committed
Dispatch press events directly from the native side
1 parent ea0f839 commit 0196da5

6 files changed

Lines changed: 193 additions & 150 deletions

File tree

packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerButtonViewManager.kt

Lines changed: 35 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import androidx.core.view.children
2727
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
2828
import com.facebook.react.R
2929
import com.facebook.react.bridge.Dynamic
30+
import com.facebook.react.bridge.ReactContext
3031
import com.facebook.react.bridge.ReadableArray
3132
import com.facebook.react.module.annotations.ReactModule
3233
import com.facebook.react.uimanager.BackgroundStyleApplicator
@@ -35,6 +36,7 @@ import com.facebook.react.uimanager.PixelUtil
3536
import com.facebook.react.uimanager.PointerEvents
3637
import com.facebook.react.uimanager.ReactPointerEventsView
3738
import com.facebook.react.uimanager.ThemedReactContext
39+
import com.facebook.react.uimanager.UIManagerHelper
3840
import com.facebook.react.uimanager.ViewGroupManager
3941
import com.facebook.react.uimanager.ViewManagerDelegate
4042
import com.facebook.react.uimanager.ViewProps
@@ -48,6 +50,7 @@ import com.swmansion.gesturehandler.core.GestureHandler
4850
import com.swmansion.gesturehandler.core.HoverGestureHandler
4951
import com.swmansion.gesturehandler.core.NativeViewGestureHandler
5052
import com.swmansion.gesturehandler.react.RNGestureHandlerButtonViewManager.ButtonViewGroup
53+
import com.swmansion.gesturehandler.react.events.RNGestureHandlerButtonEvent
5154

5255
@ReactModule(name = RNGestureHandlerButtonViewManager.REACT_CLASS)
5356
class RNGestureHandlerButtonViewManager :
@@ -63,6 +66,11 @@ class RNGestureHandlerButtonViewManager :
6366

6467
public override fun createViewInstance(context: ThemedReactContext) = ButtonViewGroup(context)
6568

69+
@ReactProp(name = "hasLongPressHandler")
70+
override fun setHasLongPressHandler(view: ButtonViewGroup, hasLongPressHandler: Boolean) {
71+
view.hasLongPressHandler = hasLongPressHandler
72+
}
73+
6674
@ReactProp(name = "foreground")
6775
override fun setForeground(view: ButtonViewGroup, useDrawableOnForeground: Boolean) {
6876
view.useDrawableOnForeground = useDrawableOnForeground
@@ -398,6 +406,7 @@ class RNGestureHandlerButtonViewManager :
398406
var useBorderlessDrawable = false
399407

400408
var exclusive = true
409+
var hasLongPressHandler = false
401410
var tapAnimationInDuration: Int = 50
402411
var tapAnimationOutDuration: Int = 100
403412
var longPressDuration: Int = -1
@@ -534,9 +543,9 @@ class RNGestureHandlerButtonViewManager :
534543
}
535544

536545
if (handler.isWithinBounds) {
537-
dispatchJSEvent(EventType.PressIn)
546+
dispatchJSEvent(EventType.PressIn, handler)
538547
} else {
539-
dispatchJSEvent(EventType.PressOut)
548+
dispatchJSEvent(EventType.PressOut, handler)
540549

541550
pendingLongPress?.let {
542551
this.handler?.removeCallbacks(it)
@@ -551,24 +560,26 @@ class RNGestureHandlerButtonViewManager :
551560
val localLastEventWasInside = lastEventWasInside
552561

553562
if (newState == GestureHandler.STATE_BEGAN) {
554-
dispatchJSEvent(EventType.PressIn)
563+
dispatchJSEvent(EventType.PressIn, handler)
564+
longPressDetected = false
555565

556-
val runnable = Runnable {
557-
pendingLongPress = null
558-
longPressDetected = true
559-
dispatchJSEvent(EventType.LongPress)
566+
if (hasLongPressHandler && longPressDuration >= 0) {
567+
val runnable = Runnable {
568+
pendingLongPress = null
569+
longPressDetected = true
570+
dispatchJSEvent(EventType.LongPress, handler)
571+
}
572+
pendingLongPress = runnable
573+
this.handler?.postDelayed(runnable, longPressDuration.toLong())
560574
}
561-
longPressDetected = false
562-
pendingLongPress = runnable
563-
this.handler?.postDelayed(runnable, longPressDuration.toLong())
564575
}
565576

566577
if (newState == GestureHandler.STATE_END ||
567578
newState == GestureHandler.STATE_FAILED ||
568579
newState == GestureHandler.STATE_CANCELLED
569580
) {
570581
if (localLastEventWasInside) {
571-
dispatchJSEvent(EventType.PressOut)
582+
dispatchJSEvent(EventType.PressOut, handler)
572583
}
573584

574585
pendingLongPress?.let {
@@ -578,33 +589,26 @@ class RNGestureHandlerButtonViewManager :
578589
}
579590

580591
if (newState == GestureHandler.STATE_END && !longPressDetected && localLastEventWasInside) {
581-
dispatchJSEvent(EventType.Press)
592+
dispatchJSEvent(EventType.Press, handler)
582593
}
583594

584595
if (newState == GestureHandler.STATE_END ||
585596
newState == GestureHandler.STATE_FAILED ||
586597
newState == GestureHandler.STATE_CANCELLED
587598
) {
588-
dispatchJSEvent(EventType.InteractionFinished)
599+
dispatchJSEvent(EventType.InteractionFinished, handler)
589600
}
590601
}
591602

592-
private fun dispatchJSEvent(type: EventType) {
593-
when (type) {
594-
EventType.Press -> {
595-
Log.w("RNGH", "onPress")
596-
}
597-
EventType.PressIn -> {
598-
lastEventWasInside = true
599-
Log.w("RNGH", "onPressIn")
600-
}
601-
EventType.PressOut -> {
602-
lastEventWasInside = false
603-
Log.w("RNGH", "onPressOut")
604-
}
605-
EventType.LongPress -> {
606-
Log.w("RNGH", "longPress")
607-
}
603+
private fun dispatchJSEvent(type: EventType, handler: NativeViewGestureHandler) {
604+
val reactContext = context as? ReactContext ?: return
605+
val eventDispatcher = UIManagerHelper.getEventDispatcher(reactContext) ?: return
606+
eventDispatcher.dispatchEvent(RNGestureHandlerButtonEvent.obtain(this, handler, type))
607+
608+
if (type == EventType.PressIn) {
609+
lastEventWasInside = true
610+
} else if (type == EventType.PressOut) {
611+
lastEventWasInside = false
608612
}
609613
}
610614

@@ -1199,7 +1203,8 @@ class RNGestureHandlerButtonViewManager :
11991203
Press,
12001204
PressIn,
12011205
PressOut,
1202-
LongPress
1206+
LongPress,
1207+
InteractionFinished,
12031208
}
12041209
}
12051210

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package com.swmansion.gesturehandler.react.events
2+
3+
import androidx.core.util.Pools
4+
import com.facebook.react.bridge.Arguments
5+
import com.facebook.react.bridge.WritableMap
6+
import com.facebook.react.uimanager.PixelUtil
7+
import com.facebook.react.uimanager.UIManagerHelper
8+
import com.facebook.react.uimanager.events.Event
9+
import com.swmansion.gesturehandler.core.GestureHandler
10+
import com.swmansion.gesturehandler.core.NativeViewGestureHandler
11+
import com.swmansion.gesturehandler.react.RNGestureHandlerButtonViewManager
12+
13+
class RNGestureHandlerButtonEvent private constructor() : Event<RNGestureHandlerButtonEvent>() {
14+
private lateinit var type: RNGestureHandlerButtonViewManager.ButtonViewGroup.EventType
15+
private lateinit var eventData: ButtonEventData
16+
17+
private fun init(
18+
button: RNGestureHandlerButtonViewManager.ButtonViewGroup,
19+
handler: NativeViewGestureHandler,
20+
type: RNGestureHandlerButtonViewManager.ButtonViewGroup.EventType
21+
) {
22+
super.init(UIManagerHelper.getSurfaceId(button), button.id)
23+
24+
this.type = type
25+
this.eventData = ButtonEventData(
26+
x = PixelUtil.toDIPFromPixel(handler.lastRelativePositionX).toDouble(),
27+
y = PixelUtil.toDIPFromPixel(handler.lastRelativePositionY).toDouble(),
28+
absoluteX = PixelUtil.toDIPFromPixel(handler.lastPositionInWindowX).toDouble(),
29+
absoluteY = PixelUtil.toDIPFromPixel(handler.lastPositionInWindowY).toDouble(),
30+
numberOfPointers = handler.numberOfPointers,
31+
pointerType = handler.pointerType,
32+
pointerInside = handler.isWithinBounds
33+
)
34+
}
35+
36+
override fun onDispose() {
37+
EVENTS_POOL.release(this)
38+
}
39+
40+
override fun getEventName() = when (type) {
41+
RNGestureHandlerButtonViewManager.ButtonViewGroup.EventType.Press -> ON_PRESS_EVENT_NAME
42+
RNGestureHandlerButtonViewManager.ButtonViewGroup.EventType.PressIn -> ON_PRESS_IN_EVENT_NAME
43+
RNGestureHandlerButtonViewManager.ButtonViewGroup.EventType.PressOut -> ON_PRESS_OUT_EVENT_NAME
44+
RNGestureHandlerButtonViewManager.ButtonViewGroup.EventType.LongPress -> ON_LONG_PRESS_EVENT_NAME
45+
RNGestureHandlerButtonViewManager.ButtonViewGroup.EventType.InteractionFinished -> ON_INTERACTION_FINISHED_EVENT_NAME
46+
}
47+
48+
// Unfortunately getCoalescingKey is not considered when sending event to C++, therefore we have to disable coalescing in v3
49+
override fun canCoalesce() = false
50+
51+
override fun getCoalescingKey(): Short = 0
52+
53+
override fun getEventData(): WritableMap = this.eventData.toWritableMap()
54+
55+
private data class ButtonEventData(
56+
val x: Double,
57+
val y: Double,
58+
val absoluteX: Double,
59+
val absoluteY: Double,
60+
val numberOfPointers: Int,
61+
val pointerType: Int,
62+
val pointerInside: Boolean,
63+
) {
64+
fun toWritableMap(): WritableMap = Arguments.createMap().apply {
65+
putDouble("x", x)
66+
putDouble("y", y)
67+
putDouble("absoluteX", absoluteX)
68+
putDouble("absoluteY", absoluteY)
69+
putInt("numberOfPointers", numberOfPointers)
70+
putInt("pointerType", pointerType)
71+
putBoolean("pointerInside", pointerInside)
72+
}
73+
}
74+
75+
companion object {
76+
private const val ON_PRESS_EVENT_NAME = "onPress"
77+
private const val ON_PRESS_IN_EVENT_NAME = "onPressIn"
78+
private const val ON_PRESS_OUT_EVENT_NAME = "onPressOut"
79+
private const val ON_LONG_PRESS_EVENT_NAME = "onLongPress"
80+
private const val ON_INTERACTION_FINISHED_EVENT_NAME = "onInteractionFinished"
81+
82+
private const val TOUCH_EVENTS_POOL_SIZE = 7 // magic
83+
private val EVENTS_POOL = Pools.SynchronizedPool<RNGestureHandlerButtonEvent>(TOUCH_EVENTS_POOL_SIZE)
84+
85+
fun obtain(
86+
button: RNGestureHandlerButtonViewManager.ButtonViewGroup,
87+
handler: NativeViewGestureHandler,
88+
type: RNGestureHandlerButtonViewManager.ButtonViewGroup.EventType
89+
): RNGestureHandlerButtonEvent = (EVENTS_POOL.acquire() ?: RNGestureHandlerButtonEvent()).apply {
90+
init(button, handler, type)
91+
}
92+
}
93+
}

packages/react-native-gesture-handler/src/components/GestureHandlerButton.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ import type {
88
ViewStyle,
99
} from 'react-native';
1010

11-
import RNGestureHandlerButtonNativeComponent from '../specs/RNGestureHandlerButtonNativeComponent';
11+
import RNGestureHandlerButtonNativeComponent, {
12+
type ButtonEvent,
13+
} from '../specs/RNGestureHandlerButtonNativeComponent';
1214

1315
export interface ButtonProps extends ViewProps, AccessibilityProps {
1416
children?: React.ReactNode;
@@ -18,6 +20,8 @@ export interface ButtonProps extends ViewProps, AccessibilityProps {
1820
*/
1921
enabled?: boolean | undefined;
2022

23+
hasLongPressHandler?: boolean | undefined;
24+
2125
/**
2226
* Defines if more than one button could be pressed simultaneously. By default
2327
* set true.
@@ -203,3 +207,4 @@ export const ButtonComponent =
203207
RNGestureHandlerButtonNativeComponent as HostComponent<ButtonProps>;
204208

205209
export default ButtonComponent;
210+
export type { ButtonEvent };

packages/react-native-gesture-handler/src/specs/RNGestureHandlerButtonNativeComponent.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,27 @@ import type {
66
} from 'react-native';
77
import { codegenNativeComponent } from 'react-native';
88

9+
export type ButtonEvent = Readonly<{
10+
pointerInside: boolean;
11+
x: CodegenTypes.Double;
12+
y: CodegenTypes.Double;
13+
absoluteX: CodegenTypes.Double;
14+
absoluteY: CodegenTypes.Double;
15+
numberOfPointers: CodegenTypes.Int32;
16+
pointerType: CodegenTypes.Int32;
17+
}>;
18+
919
// @ts-ignore - Redefining pointerEvents with WithDefault for codegen, conflicts with ViewProps type but codegen needs it
1020
interface NativeProps extends ViewProps {
21+
onPress?: CodegenTypes.DirectEventHandler<ButtonEvent> | undefined;
22+
onPressIn?: CodegenTypes.DirectEventHandler<ButtonEvent> | undefined;
23+
onPressOut?: CodegenTypes.DirectEventHandler<ButtonEvent> | undefined;
24+
onLongPress?: CodegenTypes.DirectEventHandler<ButtonEvent> | undefined;
25+
onInteractionFinished?:
26+
| CodegenTypes.DirectEventHandler<ButtonEvent>
27+
| undefined;
28+
29+
hasLongPressHandler?: CodegenTypes.WithDefault<boolean, false>;
1130
exclusive?: CodegenTypes.WithDefault<boolean, true>;
1231
foreground?: boolean;
1332
borderless?: boolean;

0 commit comments

Comments
 (0)