Skip to content

Commit

Permalink
PointerEvents: Mark when listened to for touch interactions
Browse files Browse the repository at this point in the history
Summary: Changelog: [Internal] - Bypass dispatching an event if no view along the hierarchy is listening to it. Only applied for touch-based interactions. Next change will add optimization for mouse interactions

Reviewed By: vincentriemer

Differential Revision: D35739417

fbshipit-source-id: 134ffefef3bb4f97bf3e63b6bccc0caca464dfbd
  • Loading branch information
Luna Wei authored and pull[bot] committed Jun 23, 2023
1 parent eb0542b commit 8823928
Show file tree
Hide file tree
Showing 8 changed files with 311 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ constexpr MapBuffer::Key VP_SHADOW_COLOR = 31;
constexpr MapBuffer::Key VP_TEST_ID = 32;
constexpr MapBuffer::Key VP_TRANSFORM = 33;
constexpr MapBuffer::Key VP_ZINDEX = 34;
constexpr MapBuffer::Key VP_POINTER_ENTER2 = 35;
constexpr MapBuffer::Key VP_POINTER_LEAVE2 = 36;
constexpr MapBuffer::Key VP_POINTER_MOVE2 = 37;
constexpr MapBuffer::Key VP_POINTER_ENTER2_CAPTURE = 38;
constexpr MapBuffer::Key VP_POINTER_LEAVE2_CAPTURE = 39;
constexpr MapBuffer::Key VP_POINTER_MOVE2_CAPTURE = 40;

// Yoga values
constexpr MapBuffer::Key YG_BORDER_WIDTH = 100;
Expand Down Expand Up @@ -462,6 +468,22 @@ static inline MapBuffer viewPropsDiff(
VP_POINTER_LEAVE, newProps.events[ViewEvents::Offset::PointerLeave]);
builder.putBool(
VP_POINTER_MOVE, newProps.events[ViewEvents::Offset::PointerMove]);

builder.putBool(
VP_POINTER_ENTER2, newProps.events[ViewEvents::Offset::PointerEnter2]);
builder.putBool(
VP_POINTER_ENTER2_CAPTURE,
newProps.events[ViewEvents::Offset::PointerEnter2Capture]);
builder.putBool(
VP_POINTER_LEAVE2, newProps.events[ViewEvents::Offset::PointerLeave2]);
builder.putBool(
VP_POINTER_LEAVE2_CAPTURE,
newProps.events[ViewEvents::Offset::PointerLeave2Capture]);
builder.putBool(
VP_POINTER_MOVE2, newProps.events[ViewEvents::Offset::PointerMove2]);
builder.putBool(
VP_POINTER_MOVE2_CAPTURE,
newProps.events[ViewEvents::Offset::PointerMove2Capture]);
}

if (oldProps.removeClippedSubviews != newProps.removeClippedSubviews) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,39 @@ public void setPointerMove(@NonNull T view, boolean value) {
view.setTag(R.id.pointer_move, value);
}

/* Experimental W3C Pointer events start */
@ReactProp(name = "onPointerEnter2")
public void setPointerEnter2(@NonNull T view, boolean value) {
view.setTag(R.id.pointer_enter2, value);
}

@ReactProp(name = "onPointerEnter2Capture")
public void setPointerEnter2Capture(@NonNull T view, boolean value) {
view.setTag(R.id.pointer_enter2_capture, value);
}

@ReactProp(name = "onPointerLeave2")
public void setPointerLeave2(@NonNull T view, boolean value) {
view.setTag(R.id.pointer_leave2, value);
}

@ReactProp(name = "onPointerLeave2Capture")
public void setPointerLeave2Capture(@NonNull T view, boolean value) {
view.setTag(R.id.pointer_leave2_capture, value);
}

@ReactProp(name = "onPointerMove2")
public void setPointerMove2(@NonNull T view, boolean value) {
view.setTag(R.id.pointer_move2, value);
}

@ReactProp(name = "onPointerMove2Capture")
public void setPointerMove2Capture(@NonNull T view, boolean value) {
view.setTag(R.id.pointer_move2_capture, value);
}

/* Experimental W3C Pointer events end */

@ReactProp(name = "onMoveShouldSetResponder")
public void setMoveShouldSetResponder(@NonNull T view, boolean value) {
// no-op, handled by JSResponder
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import com.facebook.react.uimanager.events.EventDispatcher;
import com.facebook.react.uimanager.events.PointerEvent;
import com.facebook.react.uimanager.events.PointerEventHelper;
import com.facebook.react.uimanager.events.PointerEventHelper.EVENT;
import com.facebook.react.uimanager.events.TouchEvent;
import com.facebook.react.uimanager.events.TouchEventCoalescingKeyHelper;
import java.util.Collections;
Expand All @@ -37,7 +38,7 @@ public class JSPointerDispatcher {
private final TouchEventCoalescingKeyHelper mTouchEventCoalescingKeyHelper =
new TouchEventCoalescingKeyHelper();

private static final float ONMOVE_EPSILON = 1f;
private static final float ONMOVE_EPSILON = 0.1f;

// Set globally for hover interactions, referenced for coalescing hover events
private long mHoverInteractionKey = TouchEvent.UNSET;
Expand Down Expand Up @@ -82,7 +83,9 @@ public void handleMotionEvent(MotionEvent motionEvent, EventDispatcher eventDisp
if (hitPath.isEmpty()) {
return;
}
int targetTag = hitPath.get(0).getViewId();

TouchTargetHelper.ViewTarget activeViewTarget = hitPath.get(0);
int activeTargetTag = activeViewTarget.getViewId();

if (supportsHover) {
if (action == MotionEvent.ACTION_HOVER_MOVE) {
Expand All @@ -107,15 +110,17 @@ public void handleMotionEvent(MotionEvent motionEvent, EventDispatcher eventDisp
mTouchEventCoalescingKeyHelper.addCoalescingKey(mDownStartTime);

if (!supportsHover) {
// Enter root -> child
for (int i = hitPath.size(); i-- > 0; ) {
int tag = hitPath.get(i).getViewId();
eventDispatcher.dispatchEvent(
PointerEvent.obtain(PointerEventHelper.POINTER_ENTER, surfaceId, tag, motionEvent));
}
dispatchNonBubblingEventForPathWhenListened(
EVENT.ENTER, EVENT.ENTER_CAPTURE, hitPath, eventDispatcher, surfaceId, motionEvent);
}

boolean listeningForDown =
isAnyoneListeningForBubblingEvent(hitPath, EVENT.DOWN, EVENT.DOWN_CAPTURE);
if (listeningForDown) {
eventDispatcher.dispatchEvent(
PointerEvent.obtain(
PointerEventHelper.POINTER_DOWN, surfaceId, activeTargetTag, motionEvent));
}
eventDispatcher.dispatchEvent(
PointerEvent.obtain(PointerEventHelper.POINTER_DOWN, surfaceId, targetTag, motionEvent));

return;
}
Expand All @@ -129,25 +134,47 @@ public void handleMotionEvent(MotionEvent motionEvent, EventDispatcher eventDisp
// New pointer goes down, this can only happen after ACTION_DOWN is sent for the first pointer
if (action == MotionEvent.ACTION_POINTER_DOWN) {
mTouchEventCoalescingKeyHelper.incrementCoalescingKey(mDownStartTime);
eventDispatcher.dispatchEvent(
PointerEvent.obtain(PointerEventHelper.POINTER_DOWN, surfaceId, targetTag, motionEvent));

boolean listeningForDown =
isAnyoneListeningForBubblingEvent(hitPath, EVENT.DOWN, EVENT.DOWN_CAPTURE);
if (listeningForDown) {
eventDispatcher.dispatchEvent(
PointerEvent.obtain(
PointerEventHelper.POINTER_DOWN, surfaceId, activeTargetTag, motionEvent));
}

return;
}

if (action == MotionEvent.ACTION_MOVE) {
int coalescingKey = mTouchEventCoalescingKeyHelper.getCoalescingKey(mDownStartTime);
eventDispatcher.dispatchEvent(
PointerEvent.obtain(
PointerEventHelper.POINTER_MOVE, surfaceId, targetTag, motionEvent, coalescingKey));

boolean listeningForMove =
isAnyoneListeningForBubblingEvent(hitPath, EVENT.MOVE, EVENT.MOVE_CAPTURE);
if (listeningForMove) {
eventDispatcher.dispatchEvent(
PointerEvent.obtain(
PointerEventHelper.POINTER_MOVE,
surfaceId,
activeTargetTag,
motionEvent,
coalescingKey));
}

return;
}

// Exactly one of the pointers goes up, not the last one
if (action == MotionEvent.ACTION_POINTER_UP) {
mTouchEventCoalescingKeyHelper.incrementCoalescingKey(mDownStartTime);
eventDispatcher.dispatchEvent(
PointerEvent.obtain(PointerEventHelper.POINTER_UP, surfaceId, targetTag, motionEvent));

boolean listeningForUp =
isAnyoneListeningForBubblingEvent(hitPath, EVENT.UP, EVENT.UP_CAPTURE);
if (listeningForUp) {
eventDispatcher.dispatchEvent(
PointerEvent.obtain(
PointerEventHelper.POINTER_UP, surfaceId, activeTargetTag, motionEvent));
}

return;
}
Expand All @@ -159,16 +186,17 @@ public void handleMotionEvent(MotionEvent motionEvent, EventDispatcher eventDisp
mTouchEventCoalescingKeyHelper.removeCoalescingKey(mDownStartTime);
mDownStartTime = TouchEvent.UNSET;

eventDispatcher.dispatchEvent(
PointerEvent.obtain(PointerEventHelper.POINTER_UP, surfaceId, targetTag, motionEvent));
boolean listeningForUp =
isAnyoneListeningForBubblingEvent(hitPath, EVENT.UP, EVENT.UP_CAPTURE);
if (listeningForUp) {
eventDispatcher.dispatchEvent(
PointerEvent.obtain(
PointerEventHelper.POINTER_UP, surfaceId, activeTargetTag, motionEvent));
}

if (!supportsHover) {
// Leave child -> root
for (int i = 0; i < hitPath.size(); i++) {
int tag = hitPath.get(i).getViewId();
eventDispatcher.dispatchEvent(
PointerEvent.obtain(PointerEventHelper.POINTER_LEAVE, surfaceId, tag, motionEvent));
}
dispatchNonBubblingEventForPathWhenListened(
EVENT.LEAVE, EVENT.LEAVE_CAPTURE, hitPath, eventDispatcher, surfaceId, motionEvent);
}
return;
}
Expand All @@ -183,15 +211,54 @@ public void handleMotionEvent(MotionEvent motionEvent, EventDispatcher eventDisp
"Warning : Motion Event was ignored. Action="
+ action
+ " Target="
+ targetTag
+ activeTargetTag
+ " Supports Hover="
+ supportsHover);
}

private int findTargetTagAndSetCoordinates(MotionEvent ev) {
// This method updates `mTargetCoordinates` with coordinates for the motion event.
return TouchTargetHelper.findTargetTagAndCoordinatesForTouch(
ev.getX(), ev.getY(), mRootViewGroup, mTargetCoordinates, null);
private static boolean isAnyoneListeningForBubblingEvent(
List<ViewTarget> hitPath, EVENT event, EVENT captureEvent) {
for (ViewTarget viewTarget : hitPath) {
if (PointerEventHelper.isListening(viewTarget.getView(), event)
|| PointerEventHelper.isListening(viewTarget.getView(), captureEvent)) {
return true;
}
}
return false;
}

/*
Dispatch event only if ancestor is listening to relevant event.
This should only be relevant for ENTER/LEAVE events.
@param hitPath - ordered from inner target to root
*/

/** Dispatch non-bubbling event along the hit path only when relevant listeners */
private static void dispatchNonBubblingEventForPathWhenListened(
EVENT event,
EVENT captureEvent,
List<ViewTarget> hitPath,
EventDispatcher dispatcher,
int surfaceId,
MotionEvent motionEvent) {

boolean ancestorListening = false;
String eventName = PointerEventHelper.getDispatchableEventName(event);
if (eventName == null) {
return;
}

// iterate through hitPath from ancestor -> target
for (int i = hitPath.size() - 1; i >= 0; i--) {
View view = hitPath.get(i).getView();
int viewId = hitPath.get(i).getViewId();
if (ancestorListening
|| (i == 0 && PointerEventHelper.isListening(view, event))
|| PointerEventHelper.isListening(view, captureEvent)) {
dispatcher.dispatchEvent(PointerEvent.obtain(eventName, surfaceId, viewId, motionEvent));
ancestorListening = true;
}
}
}

// called on hover_move motion events only
Expand Down Expand Up @@ -311,21 +378,21 @@ private void dispatchCancelEvent(
int surfaceId = UIManagerHelper.getSurfaceId(mRootViewGroup);

if (!hitPath.isEmpty()) {
int targetTag = hitPath.get(0).getViewId();
// Question: Does cancel fire on all in hit path?
Assertions.assertNotNull(eventDispatcher)
.dispatchEvent(
PointerEvent.obtain(
PointerEventHelper.POINTER_CANCEL, surfaceId, targetTag, motionEvent));

for (ViewTarget viewTarget : hitPath) {
eventDispatcher.dispatchEvent(
PointerEvent.obtain(
PointerEventHelper.POINTER_LEAVE, surfaceId, viewTarget.getViewId(), motionEvent));
boolean listeningForCancel =
isAnyoneListeningForBubblingEvent(hitPath, EVENT.CANCEL, EVENT.CANCEL_CAPTURE);
if (listeningForCancel) {
int targetTag = hitPath.get(0).getViewId();
Assertions.assertNotNull(eventDispatcher)
.dispatchEvent(
PointerEvent.obtain(
PointerEventHelper.POINTER_CANCEL, surfaceId, targetTag, motionEvent));
}
}

mTouchEventCoalescingKeyHelper.removeCoalescingKey(mDownStartTime);
mDownStartTime = TouchEvent.UNSET;
dispatchNonBubblingEventForPathWhenListened(
EVENT.LEAVE, EVENT.LEAVE_CAPTURE, hitPath, eventDispatcher, surfaceId, motionEvent);

mTouchEventCoalescingKeyHelper.removeCoalescingKey(mDownStartTime);
mDownStartTime = TouchEvent.UNSET;
}
}
}
Loading

0 comments on commit 8823928

Please sign in to comment.