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

WIP: Gestures update - two finger rotation / interactions filter / map events #719

Merged
merged 44 commits into from Jan 29, 2021

Conversation

maRci002
Copy link
Contributor

@maRci002 maRci002 commented Aug 6, 2020

Hello, this PR adds two finger rotation support, the map will fire events, and gives opportunity to disable / mix interactions.
For instance you can move map only with fling events or enable only zoom / rotation interactions if GPS is on (I updated some examples, i.e. LiveLocationPage).

There are a lot of issues which ask for onMoveStart / onMoveEnd callbacks, just listen to mapEventStream and filter these events (MapEventMoveStart / MapEventMoveEnd), however take care because Map may use fling animation if the velocity is high enough so the final location will be different from MapEventMoveEnd's targetLocation, so there are two options:

  • disable fling animation
  • filter MapEventFlingEnd and MapEventFlingNotStarted

It is possible there is an ongoing fling animation (or double tap zoom animation) and flags updated during the animation so basicly the new flags don't contain any fling flag, then MapEventFlingEnd will be emitted, this behavior can be distinguished easily from normal fling end event by checking the event's source, in normal case source is MapEventSource.flingAnimationController.

Added an example page where these interaction filters can be tried and map events are listened.
closes #148
closes #701
closes #361
closes #498
closes #396
closes #723
closes #736

lib/src/map/map_state_widget.dart Show resolved Hide resolved
setIcon(Icons.gps_fixed);

mapEventSubscription = mapEventStream.listen((MapEvent event) {
if (event is MapEventMove && event.id == _eventKey) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

It is overkill to listen the stream but here is the example which checks event's id so it's safe to say it's coming from us rather than assume the first move was our one.

Copy link
Contributor

Choose a reason for hiding this comment

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

How can we differentiate between move start and end events? Also, is there a way to dfferantiate among rotate/pan/zoom/double tap zoom? These would be super helpful if you could add them in your PR:D

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You can check map_events it is still not documented but you can distinguish events forinstance MapEventRotateStart will be fired when map starts to rotate (when rotationThreshold is reached the default can be changed in MapOptions) and while rotating MapEventRotate will be fired continuously and when rotation ends then MapEventRotateEnd will be fired.

Example:

  @override
  void initState() {
    super.initState();
    mapController = MapController();

    mapController.onReady.then((_) {
      // at this point we can safely listen to [mapEventStream]
      // use stream transformer or anything you want
      subscription = mapController.mapEventStream.listen((MapEvent mapEvent) {
        if (mapEvent is MapEventRotateStart) {
          // do something
        } else if (mapEvent is MapEventFlingEnd ) {
          // do something
        }
      });
    });
  }

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for the clear example. I didn't see map_events before now it all makes sense. By the way I am a little confused with this scenario: User can easily both pan zoom at the same time. In this case what events will be emitted?
MapEventMoveStart
MapEventRotateStart
...
MapEventMove
MapEventRotate
MapEventMove
MapEventRotate
MapEventMove
MapEventRotate
...
MapEventMoveEnd
MapEventRotateEnd
Something like this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Zoom and move are same event. You can read zoom which was the zoom when event is emmited and there is a targetZoom property too. So if zoom != targetZoom then zoom has been changed you can compare center and targetCenter too. Maybe there should be a getter which tells the same so it can be used like this: if (event is MapEventMove && event.isZoomChanged)

Copy link
Contributor

Choose a reason for hiding this comment

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

Ok if zoom and move are considered same, let me ask the same question with zoom+rotate? Do we need that extra getter in that case?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If only pinchZoom and rotation are enabled then if only rotation happens then MapEventMove won't be fired (but it is almost impossible to do that with fingers).
I think I'll implement zoom threshold too, so rotation and zoom can race. I mean user won't able to rotate and zoom at same time at least Google maps work this way.

@maRci002
Copy link
Contributor Author

maRci002 commented Aug 7, 2020

There was some bug with rotation it has been fixed

@maRci002
Copy link
Contributor Author

maRci002 commented Aug 7, 2020

Arbitrary jumps fixed.

@maRci002 maRci002 changed the title Gestures update - two finger rotation / interactions filter / map events WIP: Gestures update - two finger rotation / interactions filter / map events Aug 7, 2020
@maRci002
Copy link
Contributor Author

maRci002 commented Aug 7, 2020

This PR still has problems, for example: onTap gives back wrong coords, map corners doesn't recognize user actions when map is rotated.
More events will come.

For a longer time (like 10 days) I'm going to take a nap so I cannot update this PR.

@christophemacabiau
Copy link

In leaflet, you can disable the map dragging, and in this case you still can move the map using a pinch (the pinch allows zoom + move). It's useful when you zoom/unzoom to recenter.
And also useful when you have complex gesture to implement on the map...

Maybe the "move" flag is too restrictive : it might be changed to "dragging" (like in leaflet), allowing the move during a pinch.
Or adding a pinchZoomMove event ?

if (hasMove || hasPinchZoom) {
final newZoom = hasPinchZoom
? _getZoomForScale(_mapZoomStart, details.scale)
: map.zoom;
LatLng newCenter;
if (hasMove) {
final focalStartPt = map.project(_focalStartGlobal, newZoom);
final newCenterPt = focalStartPt - focalOffset + map.size / 2.0;
newCenter = map.unproject(newCenterPt, newZoom);
} else {
newCenter = map.center;
}
mapMoved = map.move(
newCenter,
newZoom,
hasGesture: true,
source: MapEventSource.onDrag,
);

@maRci002
Copy link
Contributor Author

@christophemacabiau that's a good point. Next week I'll able to update PR.
So move will be renamed to drag, pinchZoom will be renamed to pinch. And zoom will be added so if pinch is disabled you will be able to zoom but cannot move the map even if drag is enabled.

And fling will be renamed to flingAnimation.

@maRci002 maRci002 mentioned this pull request Aug 27, 2020
@christophemacabiau
Copy link

Hi @maRci002 ,

Thanks for your work.
Sometimes you need the events, without the default handler : for instance, you want to prevent the user to drag the map, but you want to be able to listen to the drag event. In my case I need to draw something on the map, I don't want the map to move, but I need for this to get the "drag event".
In lib/src/gestures/gestures.dart, the event emission should be independant of InteractiveFlags. What do you think?

@maRci002
Copy link
Contributor Author

@christophemacabiau maybe it is a good idea, however stream is async (I think this isn't problem) and maybe someone wants more info than targetCenter and targetZoom forinstance if the finger is close to the edge of the map then he wants to move the map alongside with polygon or whatever, this is possible if we give Offset information (rotated and non rotated Offset) and we have information about how many fingers are on screen.

Maybe for this task adding funcation listeners to handleScaleStart / handleScaleUpdate / handleScaleEnd can work.

@christophemacabiau
Copy link

Thanks @maRci002 for your hint.

I am somewhat lost in the coordinates spaces used. In handleScaleUpdate focalOffset seems to be the offset relative to the top left corner of the map widget of the touch event. Am I right?

I need the touch event coordinates in the default CRS (3857).
For example line 256 , if mapState.project(_latitude:51.5,_longitude:-0.9) returns {x:8151.04, y:5448.63920935928} which is not the right projection.

There is something I missed, can you give me some hint please?

@maRci002
Copy link
Contributor Author

I am somewhat lost in the coordinates spaces used. In handleScaleUpdate focalOffset seems to be the offset relative to the top left corner of the map widget of the touch event. Am I right?

Yes it is relative to top left corner (note: in current implementation the GestureDetector itself isn't rotated and enlarged so now you can tap 0-0 position, but in handleScaleUpdate we have to play with rotation)

I need the touch event coordinates in the default CRS (3857).
For example line 256 , if mapState.project(_latitude:51.5,_longitude:-0.9) returns {x:8151.04, y:5448.63920935928} which is not the right projection.

mapState.project returns the pixel coordinate not the projected coordinate.

@catmorte
Copy link

Hi! Is there any news?

@JaffaKetchup
Copy link
Member

Hello, is there anymore news on this pull request, or is it dead?

@rpiosi
Copy link
Contributor

rpiosi commented Jan 22, 2021

@catmorte
@JaffaKetchup

Y'all can use it already by adding this PR to pubspec.yaml

dependencies:
  flutter:
    sdk: flutter

  ...

  flutter_map:
    git:
      url: git://github.com/maRci002/flutter_map.git
      ref: feature/gesture-update

Dragging when map is rotated works flawlessly!

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