Skip to content

Commit

Permalink
Track state per pointer
Browse files Browse the repository at this point in the history
Summary:
Changelog: [Internal] - Add more state to PointerEventState per pointer such that `offset` coordinates and `hitPath` accurately reflect each pointer.

This change eagerly calculates the hitPath of every pointer under a MotionEvent.

Reviewed By: mdvacca

Differential Revision: D39736109

fbshipit-source-id: ef76dc19a86eaf9b26f35c6a55781ece6e43eecc
  • Loading branch information
lunaleaps authored and mohitcharkha committed Oct 17, 2022
1 parent 4037205 commit e05fb66
Show file tree
Hide file tree
Showing 3 changed files with 171 additions and 130 deletions.
Expand Up @@ -16,6 +16,7 @@
import com.facebook.react.uimanager.TouchTargetHelper.ViewTarget;
import com.facebook.react.uimanager.events.EventDispatcher;
import com.facebook.react.uimanager.events.PointerEvent;
import com.facebook.react.uimanager.events.PointerEvent.PointerEventState;
import com.facebook.react.uimanager.events.PointerEventHelper;
import com.facebook.react.uimanager.events.PointerEventHelper.EVENT;
import java.util.ArrayList;
Expand All @@ -35,8 +36,8 @@ public class JSPointerDispatcher {
private static final float ONMOVE_EPSILON = 0.1f;
private static final String TAG = "POINTER EVENTS";

private final Map<Integer, List<ViewTarget>> mLastHitPathByPointerId = new HashMap<>();
private final Map<Integer, float[]> mLastEventCoodinatesByPointerId = new HashMap<>();
private Map<Integer, List<ViewTarget>> mLastHitPathByPointerId;
private Map<Integer, float[]> mLastEventCoordinatesByPointerId;

private int mChildHandlingNativeGesture = -1;
private int mPrimaryPointerId = UNSET_POINTER_ID;
Expand Down Expand Up @@ -72,11 +73,14 @@ private void onUp(
int activeTargetTag,
PointerEventState eventState,
MotionEvent motionEvent,
List<ViewTarget> hitPath,
EventDispatcher eventDispatcher) {

List<ViewTarget> activeHitPath =
eventState.getHitPathByPointerId().get(eventState.getActivePointerId());

boolean supportsHover = PointerEventHelper.supportsHover(motionEvent);
boolean listeningForUp = isAnyoneListeningForBubblingEvent(hitPath, EVENT.UP, EVENT.UP_CAPTURE);
boolean listeningForUp =
isAnyoneListeningForBubblingEvent(activeHitPath, EVENT.UP, EVENT.UP_CAPTURE);
if (listeningForUp) {
eventDispatcher.dispatchEvent(
PointerEvent.obtain(
Expand All @@ -85,15 +89,15 @@ private void onUp(

if (!supportsHover) {
boolean listeningForOut =
isAnyoneListeningForBubblingEvent(hitPath, EVENT.OUT, EVENT.OUT_CAPTURE);
isAnyoneListeningForBubblingEvent(activeHitPath, EVENT.OUT, EVENT.OUT_CAPTURE);
if (listeningForOut) {
eventDispatcher.dispatchEvent(
PointerEvent.obtain(
PointerEventHelper.POINTER_OUT, activeTargetTag, eventState, motionEvent));
}

List<ViewTarget> leaveViewTargets =
filterByShouldDispatch(hitPath, EVENT.LEAVE, EVENT.LEAVE_CAPTURE, false);
filterByShouldDispatch(activeHitPath, EVENT.LEAVE, EVENT.LEAVE_CAPTURE, false);

// target -> root
dispatchEventForViewTargets(
Expand All @@ -102,10 +106,6 @@ private void onUp(
motionEvent,
leaveViewTargets,
eventDispatcher);

int activePointerId = motionEvent.getPointerId(motionEvent.getActionIndex());
mLastHitPathByPointerId.remove(activePointerId);
mLastEventCoodinatesByPointerId.remove(activePointerId);
}

if (motionEvent.getActionMasked() == MotionEvent.ACTION_UP) {
Expand All @@ -125,23 +125,25 @@ private void onDown(
int activeTargetTag,
PointerEventState eventState,
MotionEvent motionEvent,
List<ViewTarget> hitPath,
EventDispatcher eventDispatcher) {

List<ViewTarget> activeHitPath =
eventState.getHitPathByPointerId().get(eventState.getActivePointerId());

incrementCoalescingKey();
boolean supportsHover = PointerEventHelper.supportsHover(motionEvent);
if (!supportsHover) {
// Indirect OVER event dispatches before ENTER
boolean listeningForOver =
isAnyoneListeningForBubblingEvent(hitPath, EVENT.OVER, EVENT.OVER_CAPTURE);
isAnyoneListeningForBubblingEvent(activeHitPath, EVENT.OVER, EVENT.OVER_CAPTURE);
if (listeningForOver) {
eventDispatcher.dispatchEvent(
PointerEvent.obtain(
PointerEventHelper.POINTER_OVER, activeTargetTag, eventState, motionEvent));
}

List<ViewTarget> enterViewTargets =
filterByShouldDispatch(hitPath, EVENT.ENTER, EVENT.ENTER_CAPTURE, false);
filterByShouldDispatch(activeHitPath, EVENT.ENTER, EVENT.ENTER_CAPTURE, false);

// Dispatch root -> target, we need to reverse order of enterViewTargets
Collections.reverse(enterViewTargets);
Expand All @@ -154,65 +156,82 @@ private void onDown(
}

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

private PointerEventState createEventState(MotionEvent motionEvent) {
int activeIndex = motionEvent.getActionIndex();

Map<Integer, float[]> offsetByPointerId = new HashMap<Integer, float[]>();
Map<Integer, List<ViewTarget>> hitPathByPointerId = new HashMap<Integer, List<ViewTarget>>();
Map<Integer, float[]> eventCoordinatesByPointerId = new HashMap<Integer, float[]>();
for (int index = 0; index < motionEvent.getPointerCount(); index++) {
float[] offsetCoordinates = new float[2];
float[] eventCoordinates = new float[] {motionEvent.getX(index), motionEvent.getY(index)};
List<ViewTarget> hitPath =
TouchTargetHelper.findTargetPathAndCoordinatesForTouch(
eventCoordinates[0], eventCoordinates[1], mRootViewGroup, offsetCoordinates);

int pointerId = motionEvent.getPointerId(index);
offsetByPointerId.put(pointerId, offsetCoordinates);
hitPathByPointerId.put(pointerId, hitPath);
eventCoordinatesByPointerId.put(pointerId, eventCoordinates);
}

int activePointerId = motionEvent.getPointerId(activeIndex);
int surfaceId = UIManagerHelper.getSurfaceId(mRootViewGroup);

return new PointerEventState(
mPrimaryPointerId,
activePointerId,
mLastButtonState,
surfaceId,
offsetByPointerId,
hitPathByPointerId,
eventCoordinatesByPointerId);
}

public void handleMotionEvent(MotionEvent motionEvent, EventDispatcher eventDispatcher) {
// Don't fire any pointer events if child view is handling native gesture
if (mChildHandlingNativeGesture != -1) {
return;
}

// Only relevant for POINTER_UP/POINTER_DOWN actions, otherwise 0
int actionIndex = motionEvent.getActionIndex();
float[] targetCoordinates = new float[2];
List<ViewTarget> hitPath =
TouchTargetHelper.findTargetPathAndCoordinatesForTouch(
motionEvent.getX(actionIndex),
motionEvent.getY(actionIndex),
mRootViewGroup,
targetCoordinates);

if (hitPath.isEmpty()) {
return;
}

int action = motionEvent.getActionMasked();

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

if (action == MotionEvent.ACTION_DOWN) {
mPrimaryPointerId = motionEvent.getPointerId(0);
}

PointerEventState eventState = new PointerEventState();
eventState.primaryPointerId = mPrimaryPointerId;
eventState.buttons = motionEvent.getButtonState();
eventState.button =
PointerEventHelper.getButtonChange(mLastButtonState, motionEvent.getButtonState());
eventState.offsetCoords = targetCoordinates;
eventState.surfaceId = UIManagerHelper.getSurfaceId(mRootViewGroup);
PointerEventState eventState = createEventState(motionEvent);
List<ViewTarget> activeHitPath =
eventState.getHitPathByPointerId().get(eventState.getActivePointerId());

if (activeHitPath == null || activeHitPath.isEmpty()) {
return;
}

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

switch (action) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN:
onDown(activeTargetTag, eventState, motionEvent, hitPath, eventDispatcher);
onDown(activeTargetTag, eventState, motionEvent, eventDispatcher);
break;
case MotionEvent.ACTION_HOVER_MOVE:
// TODO(luwe) - converge this with ACTION_MOVE
// HOVER_MOVE may occur before DOWN. Add its downTime as a coalescing key
onMove(activeTargetTag, eventState, motionEvent, hitPath, eventDispatcher);
onMove(activeTargetTag, eventState, motionEvent, eventDispatcher);
break;
case MotionEvent.ACTION_MOVE:
// TODO(luwe) - converge this with ACTION_HOVER_MOVE
boolean listeningForMove =
isAnyoneListeningForBubblingEvent(hitPath, EVENT.MOVE, EVENT.MOVE_CAPTURE);
isAnyoneListeningForBubblingEvent(activeHitPath, EVENT.MOVE, EVENT.MOVE_CAPTURE);
if (listeningForMove) {
eventDispatcher.dispatchEvent(
PointerEvent.obtain(
Expand All @@ -226,10 +245,10 @@ public void handleMotionEvent(MotionEvent motionEvent, EventDispatcher eventDisp
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
incrementCoalescingKey();
onUp(activeTargetTag, eventState, motionEvent, hitPath, eventDispatcher);
onUp(activeTargetTag, eventState, motionEvent, eventDispatcher);
break;
case MotionEvent.ACTION_CANCEL:
dispatchCancelEvent(eventState, hitPath, motionEvent, eventDispatcher);
dispatchCancelEvent(eventState, motionEvent, eventDispatcher);
break;
default:
FLog.w(
Expand All @@ -238,6 +257,8 @@ public void handleMotionEvent(MotionEvent motionEvent, EventDispatcher eventDisp
return;
}

mLastHitPathByPointerId = eventState.getHitPathByPointerId();
mLastEventCoordinatesByPointerId = eventState.getEventCoordinatesByPointerId();
mLastButtonState = motionEvent.getButtonState();
}

Expand Down Expand Up @@ -305,33 +326,28 @@ private void onMove(
int targetTag,
PointerEventState eventState,
MotionEvent motionEvent,
List<ViewTarget> hitPath,
EventDispatcher eventDispatcher) {

int action = motionEvent.getActionMasked();
if (action != MotionEvent.ACTION_HOVER_MOVE) {
return;
}
int activePointerId = eventState.getActivePointerId();
float[] eventCoordinates = eventState.getEventCoordinatesByPointerId().get(activePointerId);
List<ViewTarget> activeHitPath = eventState.getHitPathByPointerId().get(activePointerId);

int actionIndex = motionEvent.getActionIndex();
int activePointerId = motionEvent.getPointerId(actionIndex);
float x = motionEvent.getX();
float y = motionEvent.getY();
List<ViewTarget> lastHitPath =
mLastHitPathByPointerId.containsKey(activePointerId)
mLastHitPathByPointerId != null && mLastHitPathByPointerId.containsKey(activePointerId)
? mLastHitPathByPointerId.get(activePointerId)
: new ArrayList<ViewTarget>();

float[] lastEventCoordinates =
mLastEventCoodinatesByPointerId.containsKey(activePointerId)
? mLastEventCoodinatesByPointerId.get(activePointerId)
mLastEventCoordinatesByPointerId != null
&& mLastEventCoordinatesByPointerId.containsKey(activePointerId)
? mLastEventCoordinatesByPointerId.get(activePointerId)
: new float[] {0, 0};

boolean qualifiedMove =
(Math.abs(lastEventCoordinates[0] - x) > ONMOVE_EPSILON
|| Math.abs(lastEventCoordinates[1] - y) > ONMOVE_EPSILON);
(Math.abs(lastEventCoordinates[0] - eventCoordinates[0]) > ONMOVE_EPSILON
|| Math.abs(lastEventCoordinates[1] - eventCoordinates[1]) > ONMOVE_EPSILON);

// Early exit
// Early exit if active pointer has not moved enough
if (!qualifiedMove) {
return;
}
Expand All @@ -342,14 +358,14 @@ private void onMove(
boolean nonDivergentListeningToEnter = false;
boolean nonDivergentListeningToLeave = false;
int firstDivergentIndexFromBack = 0;
while (firstDivergentIndexFromBack < Math.min(hitPath.size(), lastHitPath.size())
&& hitPath
.get(hitPath.size() - 1 - firstDivergentIndexFromBack)
while (firstDivergentIndexFromBack < Math.min(activeHitPath.size(), lastHitPath.size())
&& activeHitPath
.get(activeHitPath.size() - 1 - firstDivergentIndexFromBack)
.equals(lastHitPath.get(lastHitPath.size() - 1 - firstDivergentIndexFromBack))) {

// Track if any non-diverging views are listening to enter/leave
View nonDivergentViewTargetView =
hitPath.get(hitPath.size() - 1 - firstDivergentIndexFromBack).getView();
activeHitPath.get(activeHitPath.size() - 1 - firstDivergentIndexFromBack).getView();
if (!nonDivergentListeningToEnter
&& PointerEventHelper.isListening(nonDivergentViewTargetView, EVENT.ENTER_CAPTURE)) {
nonDivergentListeningToEnter = true;
Expand All @@ -363,7 +379,7 @@ private void onMove(
}

boolean hasDiverged =
firstDivergentIndexFromBack < Math.max(hitPath.size(), lastHitPath.size());
firstDivergentIndexFromBack < Math.max(activeHitPath.size(), lastHitPath.size());

if (hasDiverged) {
// If something has changed in either enter/exit, let's start a new coalescing key
Expand Down Expand Up @@ -399,7 +415,7 @@ private void onMove(
}

boolean listeningForOver =
isAnyoneListeningForBubblingEvent(hitPath, EVENT.OVER, EVENT.OVER_CAPTURE);
isAnyoneListeningForBubblingEvent(activeHitPath, EVENT.OVER, EVENT.OVER_CAPTURE);
if (listeningForOver) {
eventDispatcher.dispatchEvent(
PointerEvent.obtain(
Expand All @@ -409,7 +425,7 @@ private void onMove(
// target -> root
List<ViewTarget> enterViewTargets =
filterByShouldDispatch(
hitPath.subList(0, hitPath.size() - firstDivergentIndexFromBack),
activeHitPath.subList(0, activeHitPath.size() - firstDivergentIndexFromBack),
EVENT.ENTER,
EVENT.ENTER_CAPTURE,
nonDivergentListeningToEnter);
Expand All @@ -427,7 +443,7 @@ private void onMove(
}

boolean listeningToMove =
isAnyoneListeningForBubblingEvent(hitPath, EVENT.MOVE, EVENT.MOVE_CAPTURE);
isAnyoneListeningForBubblingEvent(activeHitPath, EVENT.MOVE, EVENT.MOVE_CAPTURE);
if (listeningToMove) {
eventDispatcher.dispatchEvent(
PointerEvent.obtain(
Expand All @@ -437,50 +453,34 @@ private void onMove(
motionEvent,
getCoalescingKey()));
}

mLastHitPathByPointerId.put(activePointerId, hitPath);
mLastEventCoodinatesByPointerId.put(activePointerId, new float[] {x, y});
}

private void dispatchCancelEvent(MotionEvent motionEvent, EventDispatcher eventDispatcher) {
Assertions.assertCondition(
mChildHandlingNativeGesture == -1,
"Expected to not have already sent a cancel for this gesture");

float[] targetCoordinates = new float[2];
List<ViewTarget> hitPath =
TouchTargetHelper.findTargetPathAndCoordinatesForTouch(
motionEvent.getX(), motionEvent.getY(), mRootViewGroup, targetCoordinates);

PointerEventState eventState = new PointerEventState();

eventState.primaryPointerId = mPrimaryPointerId;
eventState.buttons = motionEvent.getButtonState();
eventState.button =
PointerEventHelper.getButtonChange(mLastButtonState, motionEvent.getButtonState());
eventState.offsetCoords = targetCoordinates;
eventState.surfaceId = UIManagerHelper.getSurfaceId(mRootViewGroup);

dispatchCancelEvent(eventState, hitPath, motionEvent, eventDispatcher);
PointerEventState eventState = createEventState(motionEvent);
dispatchCancelEvent(eventState, motionEvent, eventDispatcher);
}

private void dispatchCancelEvent(
PointerEventState eventState,
List<ViewTarget> hitPath,
MotionEvent motionEvent,
EventDispatcher eventDispatcher) {
PointerEventState eventState, MotionEvent motionEvent, EventDispatcher eventDispatcher) {
// This means the gesture has already ended, via some other CANCEL or UP event. This is not
// expected to happen very often as it would mean some child View has decided to intercept the
// touch stream and start a native gesture only upon receiving the UP/CANCEL event.
Assertions.assertCondition(
mChildHandlingNativeGesture == -1,
"Expected to not have already sent a cancel for this gesture");

if (!hitPath.isEmpty()) {
List<ViewTarget> activeHitPath =
eventState.getHitPathByPointerId().get(eventState.getActivePointerId());

if (!activeHitPath.isEmpty()) {
boolean listeningForCancel =
isAnyoneListeningForBubblingEvent(hitPath, EVENT.CANCEL, EVENT.CANCEL_CAPTURE);
isAnyoneListeningForBubblingEvent(activeHitPath, EVENT.CANCEL, EVENT.CANCEL_CAPTURE);
if (listeningForCancel) {
int targetTag = hitPath.get(0).getViewId();
int targetTag = activeHitPath.get(0).getViewId();
Assertions.assertNotNull(eventDispatcher)
.dispatchEvent(
PointerEvent.obtain(
Expand All @@ -490,7 +490,7 @@ private void dispatchCancelEvent(
// TODO(luwe) - Need to fire pointer out here as well:
// https://w3c.github.io/pointerevents/#dfn-suppress-a-pointer-event-stream
List<ViewTarget> leaveViewTargets =
filterByShouldDispatch(hitPath, EVENT.LEAVE, EVENT.LEAVE_CAPTURE, false);
filterByShouldDispatch(activeHitPath, EVENT.LEAVE, EVENT.LEAVE_CAPTURE, false);

// dispatch from target -> root
dispatchEventForViewTargets(
Expand Down

0 comments on commit e05fb66

Please sign in to comment.