Skip to content

Commit

Permalink
Fix proper pointercancel/leave behavior when scrolling
Browse files Browse the repository at this point in the history
Summary: Changelog: [Internal] - Fix pointer cancel/leave behavior when a child view scrolls

Differential Revision: D48472486

fbshipit-source-id: fbd09a32b29b762bb0723befd3ecd899b0ec77b1
  • Loading branch information
vincentriemer committed Aug 18, 2023
1 parent 4deb29a commit fc2e79f
Showing 1 changed file with 34 additions and 68 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ public JSPointerDispatcher(ViewGroup viewGroup) {

public void onChildStartedNativeGesture(
View childView, MotionEvent motionEvent, EventDispatcher eventDispatcher) {

if (mChildHandlingNativeGesture != -1 || childView == null) {
// This means we previously had another child start handling this native gesture and now a
// different native parent of that child has decided to intercept the touch stream and handle
Expand All @@ -69,48 +70,24 @@ public void onChildStartedNativeGesture(
}

MotionEvent motionInRoot = convertMotionToRootFrame(childView, motionEvent);
motionInRoot.setAction(MotionEvent.ACTION_CANCEL);
handleMotionEvent(motionInRoot, eventDispatcher, false);

dispatchCancelEventForTarget(childView, motionInRoot, eventDispatcher);
mChildHandlingNativeGesture = childView.getId();

// clear "previous" state since interaction was canceled
resetPreviousStateForMotionEvent(motionEvent);
}

private MotionEvent convertMotionToRootFrame(View childView, MotionEvent childMotion) {
MotionEvent motionInRoot = MotionEvent.obtain(childMotion);
final int cX = (int) childMotion.getX();
final int cY = (int) childMotion.getY();
Rect childCoords = new Rect(cX, cY, cX + 1, cY + 1);
// note: should be safe since we're only looking at descendants of the root
mRootViewGroup.offsetDescendantRectToMyCoords(childView, childCoords);
motionInRoot.setLocation(childCoords.left, childCoords.top);

return motionInRoot;
}

private void updatePreviousStateFromEvent(MotionEvent event, PointerEventState eventState) {
// Caching the event state so we have a new "last"
// note: we need to make copies here as the eventState may be accessed later and we don't want
// mutations of these instance vars to affect it
mLastHitPathByPointerId = new HashMap<>(eventState.getHitPathByPointerId());
mLastEventCoordinatesByPointerId = new HashMap<>(eventState.getEventCoordinatesByPointerId());
mLastButtonState = event.getButtonState();
int[] location = new int[2];
mRootViewGroup.getLocationOnScreen(location);
float screenX = childMotion.getRawX();
float screenY = childMotion.getRawY();
float clientX = screenX - location[0];
float clientY = screenY - location[1];
motionInRoot.setLocation(clientX, clientY);

// Clean up any stale pointerIds
Set<Integer> allPointerIds = mLastEventCoordinatesByPointerId.keySet();
mHoveringPointerIds.retainAll(allPointerIds);
}

private void resetPreviousStateForMotionEvent(MotionEvent event) {
int activePointerId = event.getPointerId(event.getActionIndex());
if (mLastHitPathByPointerId != null) {
mLastHitPathByPointerId.remove(activePointerId);
}
if (mLastEventCoordinatesByPointerId != null) {
mLastEventCoordinatesByPointerId.remove(activePointerId);
}
mLastButtonState = 0;
return motionInRoot;
}

public void onChildEndedNativeGesture() {
Expand Down Expand Up @@ -391,6 +368,7 @@ public void handleMotionEvent(
break;
case MotionEvent.ACTION_CANCEL:
dispatchCancelEventForTarget(activeTargetView, eventState, motionEvent, eventDispatcher);
handleHitStateDivergence(-1, eventState, motionEvent, eventDispatcher);
break;
case MotionEvent.ACTION_HOVER_ENTER:
// Ignore these events as enters will be calculated from HOVER_MOVE
Expand All @@ -409,8 +387,6 @@ public void handleMotionEvent(
"Motion Event was ignored. Action=" + action + " Target=" + activeTargetTag);
return;
}

updatePreviousStateFromEvent(motionEvent, eventState);
}

private static boolean isAnyoneListeningForBubblingEvent(
Expand Down Expand Up @@ -485,7 +461,10 @@ private void handleHitStateDivergence(
MotionEvent motionEvent,
EventDispatcher eventDispatcher) {
int activePointerId = eventState.getActivePointerId();
List<ViewTarget> activeHitPath = eventState.getHitPathByPointerId().get(activePointerId);
List<ViewTarget> activeHitPath =
targetTag != -1
? eventState.getHitPathByPointerId().get(activePointerId)
: new ArrayList<ViewTarget>();
List<ViewTarget> lastHitPath =
mLastHitPathByPointerId != null && mLastHitPathByPointerId.containsKey(activePointerId)
? mLastHitPathByPointerId.get(activePointerId)
Expand Down Expand Up @@ -580,6 +559,24 @@ private void handleHitStateDivergence(
eventDispatcher);
}
}

Map<Integer, List<TouchTargetHelper.ViewTarget>> nextHitPathByPointerId =
new HashMap<>(eventState.getHitPathByPointerId());
Map<Integer, float[]> nextEventCoordinatesByPointerId =
new HashMap<>(eventState.getEventCoordinatesByPointerId());

if (targetTag == -1) {
nextHitPathByPointerId.remove(activePointerId);
nextEventCoordinatesByPointerId.remove(activePointerId);
}

mLastHitPathByPointerId = nextHitPathByPointerId;
mLastEventCoordinatesByPointerId = nextEventCoordinatesByPointerId;
mLastButtonState = motionEvent.getButtonState();

// Clean up any stale pointerIds
Set<Integer> allPointerIds = mLastEventCoordinatesByPointerId.keySet();
mHoveringPointerIds.retainAll(allPointerIds);
}

private void onMove(
Expand All @@ -603,18 +600,6 @@ private void onMove(
}
}

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

int activeIndex = motionEvent.getActionIndex();
int activePointerId = motionEvent.getPointerId(activeIndex);
PointerEventState eventState = createEventState(activePointerId, motionEvent);
dispatchCancelEventForTarget(targetView, eventState, motionEvent, eventDispatcher);
}

private void dispatchCancelEventForTarget(
View targetView,
PointerEventState eventState,
Expand Down Expand Up @@ -650,26 +635,7 @@ private void dispatchCancelEventForTarget(
motionEvent));
}

// Need to fire pointer out + pointer leave here as well:
// https://w3c.github.io/pointerevents/#dfn-suppress-a-pointer-event-stream
List<ViewTarget> outViewTargets =
filterByShouldDispatch(activeHitPath, EVENT.OUT, EVENT.OUT_CAPTURE, false);
List<ViewTarget> leaveViewTargets =
filterByShouldDispatch(activeHitPath, EVENT.LEAVE, EVENT.LEAVE_CAPTURE, false);

// dispatch from target -> root
dispatchEventForViewTargets(
PointerEventHelper.POINTER_OUT, eventState, motionEvent, outViewTargets, eventDispatcher);
dispatchEventForViewTargets(
PointerEventHelper.POINTER_LEAVE,
eventState,
motionEvent,
leaveViewTargets,
eventDispatcher);

incrementCoalescingKey();
mHoveringPointerIds.remove(mPrimaryPointerId);
mHoveringPointerIds.remove(activePointerId);
mPrimaryPointerId = UNSET_POINTER_ID;
}
}
Expand Down

0 comments on commit fc2e79f

Please sign in to comment.