Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve single vs. multi-touch zoom & pan interaction (#7196) #8100

Merged
merged 16 commits into from
Sep 18, 2019
Merged

Conversation

vakila
Copy link

@vakila vakila commented Mar 30, 2019

Improves interaction when changing between single- and multi-finger touches, by checking for multi-touch events in dragPan handler and deactivating/reactivating that handler as appropriate if touchZoomRotate handler is active.

Changes

Launch Checklist

  • briefly describe the changes in this PR
  • write tests for all new functionality
  • document any changes to public APIs N/A
  • post benchmark scores N/A for these changes?
  • manually test the debug page - manually tested touch interactions on Android/Chrome

cc @asheemmamoowala

Anjana Vakil added 2 commits March 29, 2019 16:49
to reflect how we usually expect these events to look
- Stop an active single-touch drag if the user adds additional fingers
  and touchZoomRotate is enabled, so the latter can take over interaction
- Start a single-touch drag if the user removes all but one finger
- Add tests for expected single vs. multi-touch interactions
@vakila vakila changed the title Improve single vs. multi-touch zoom & pan interaction (#7169) Improve single vs. multi-touch zoom & pan interaction (#7196) Apr 22, 2019
@vakila
Copy link
Author

vakila commented Apr 24, 2019

To test the behavior changes implemented here (cc @asheemmamoowala )

  1. While single-finger panning, add a second finger(s) and continue panning/zooming

    • Before: the map still pans/zooms, but it's stuttery and not smooth (because both dragPan and touchZoomRotate handlers are simultaneously trying to move the map)
    • After (with PR): panning/zooming continues, more smoothly (because only touchZoomRotate is active, dragPan has been deactivated on the multi-finger touchstart)
  2. Remove the second finger leaving one finger on the screen, and try to keep panning.

    • Before: panning/interaction stops entirely until you end the single-finger touch. (because the multi-finger touchend deactivated both touchZoomRotate and dragPan, even though there's still one finger on the map)
    • After: single-finger panning continues as expected (because dragPan has been re-activated on the multi-finger touchend as if it were a single-finger touchstart).

@vakila
Copy link
Author

vakila commented Apr 24, 2019

Ah just discovered the multi-to-single-touch interaction problem was ticketed in #6900, which this also fixes; updated description above.

@1ec5
Copy link
Contributor

1ec5 commented Jun 7, 2019

Slightly off-topic: does GL JS currently address the case of a two-finger pinch-to-zoom gesture where the averaged point between the fingers moves (a “wandering zoom”)? On a touchscreen, it’s quite difficult to keep that centerpoint stationary as you pinch your fingers or spread them apart. To maintain a responsive feel, it’s necessary to pan at the same time as zooming (though perhaps some use cases may call for a more precise gesture handler that locks the center while zooming).

@mourner
Copy link
Member

mourner commented Jun 7, 2019

@1ec5 currently not, and it's been bothering me too — perhaps we should capture that in a ticket too. Leaflet does this.

Copy link
Contributor

@arindam1993 arindam1993 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice tests 💯

LGTM except one nit.

_onMultiTouchEnd(e: TouchEvent) {
// A multi-finger touch is ending; decide whether to activate dragging

if (e.touches && e.touches.length > 1) return; // there are still multiple fingers touching; wait
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe move this logic to something like isMultiTouch(e)?

@vakila
Copy link
Author

vakila commented Aug 20, 2019

Thanks for the review @arindam1993! Did you by any chance try this out on a touch device, and if so did you have any issues with the new behavior as describe above (#8100 (comment))? Wondering if you ran into the same issues @asheemmamoowala had a while back.

@arindam1993
Copy link
Contributor

arindam1993 commented Aug 26, 2019

I did some testing on Android Chrome,

To test the behavior changes implemented here (cc @asheemmamoowala )

  1. While single-finger panning, add a second finger(s) and continue panning/zooming

    • Before: the map still pans/zooms, but it's stuttery and not smooth (because both dragPan and touchZoomRotate handlers are simultaneously trying to move the map)
    • After (with PR): panning/zooming continues, more smoothly (because only touchZoomRotate is active, dragPan has been deactivated on the multi-finger touchstart)
  2. Remove the second finger leaving one finger on the screen, and try to keep panning.

    • Before: panning/interaction stops entirely until you end the single-finger touch. (because the multi-finger touchend deactivated both touchZoomRotate and dragPan, even though there's still one finger on the map)
    • After: single-finger panning continues as expected (because dragPan has been re-activated on the multi-finger touchend as if it were a single-finger touchstart).

2 works as expected and is a big improvement imo. With respect to 1 though it feels like we've traded some light consistent jitteriness with a large 1 frame hitch. Maybe we're skipping one pan handler fire?

@vakila
Copy link
Author

vakila commented Aug 26, 2019

Thanks @arindam1993 !

With respect to 1 though it feels like we've traded some light consistent jitteriness with a large 1 frame hitch. Maybe we're skipping one pan handler fire?

Hm maybe these were the issues @asheemmamoowala alluded to. Wonder if it's possible we're dropping one handler a little early, such that there's a gap before the other handler takes over. Will try to investigate further, lmk if you want to pair on that @arindam1993 !

Additional tests/cases to capture the fact that we
only expect touchZoomRotateHandler to take over once
we've actually begun zoom/rotate gesture; two-finger
straight pan should still be handled by dragPanHandler.

See comment on #8100 for more details.

Tests currently failing.
@vakila
Copy link
Author

vakila commented Aug 27, 2019

Capturing progress @arindam1993 & I made today:

Arindam found a bug in the transition from one to two fingers, during which we hand off control from the dragPanHandler to the touchZoomRotateHandler. The idea was for touchZoomRotateHandler to take over panning once the second finger is added, but it turns out that if you have a very steady hand and only drag, not zoom or rotate, with two fingers, this will not initially trigger any map updates from the touchZoomRotateHandler, because its _gestureintent will still be undefined. Once you have zoomed or rotated once, however, any subsequent pan-but-not-zoom/rotate gestures will be picked up by the touchZoomRotateHandler, because it never resets its _gestureintent to null until the end of the interaction.

The solution we came up with is to stop disabling dragPanHandler as soon as a second touch is added, and instead let dragPanHandler check the touchZoomRotateHandler._gestureIntent during the render frame call, and only then decide whether or not to update the map based on whether or not it expects touchZoomRotateHandler to do the same (as determined by its ._gestureIntent). This approach seemed to fix the interaction in some cursory testing we did, but we still need to make sure events are being fired as appropriate. We added some tests to reflect the expected behavior; they are not yet passing.

Arindam Bose and others added 5 commits August 27, 2019 11:57
Co-authored-by: Anjana Vakil <anjana.vakil@mapbox.com>
Co-authored-by: Arindam Bose <arindam.bose@mapbox.com>
Co-authored-by: Arindam Bose <arindam.bose@mapbox.com>
To account for the fact that move event handlers might be
inspecting the DragPanHandler's (in)active state, update
the state in the onMove handler rather than in onDragFrame.
However, don't fire the dragstart/movestart events
immediately on the move event as we had been doing before;
schedule these to be fired on the first drag frame.

Co-authored-by: Arindam Bose <arindam.bose@mapbox.com>
Co-authored-by: Arindam Bose <arindam.bose@mapbox.com>
@asheemmamoowala asheemmamoowala added this to the release-ristretto milestone Aug 29, 2019
@vakila vakila requested review from arindam1993 and asheemmamoowala and removed request for arindam1993 and asheemmamoowala September 13, 2019 21:57
@vakila
Copy link
Author

vakila commented Sep 13, 2019

This just needs a rebase/merge then will be is ready to go! I got the remaining tests passing and as far as @arindam1993 and I can tell through debug page testing on Android, iOS, & Surface Book, the behavior is as intended. @asheemmamoowala and/or @arindam1993 can you please 👀 so we can (finally!) get this merged in? 🙏 ❤️ 🤞

Copy link
Contributor

@arindam1993 arindam1993 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just 2 small things, looks good otherwise!

src/ui/handler/drag_pan.js Show resolved Hide resolved
src/ui/handler/drag_pan.js Outdated Show resolved Hide resolved
@vakila
Copy link
Author

vakila commented Sep 18, 2019

Thanks for the ✔️ @arindam1993! Made one update per your comment.

@asheemmamoowala did you want to 👀 this again or are we good to 🚢 ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
5 participants