@@ -37,6 +37,7 @@ abstract class MapLibreMapState extends State<MapLibreMap>
3737 vsync: this ,
3838 duration: const Duration (milliseconds: 300 ),
3939 )..addListener (_onAnimation);
40+ final Map <int , Offset > _pointers = {};
4041 Animation <MapCamera >? _animation;
4142 ScaleStartDetails ? _onScaleStartEvent;
4243 TapDownDetails ? _doubleTapDownDetails;
@@ -54,8 +55,16 @@ abstract class MapLibreMapState extends State<MapLibreMap>
5455 child: Listener (
5556 onPointerDown: (event) {
5657 _pointerDownEvent = event;
58+ _pointers[event.pointer] = event.position;
5759 _stopAnimation ();
5860 },
61+ onPointerMove: (event) {
62+ _pointers[event.pointer] = event.position;
63+ },
64+ onPointerUp: (event) {
65+ _pointers.remove (event.pointer);
66+ if (_pointers.isEmpty) _pointerDownEvent = null ;
67+ },
5968 onPointerSignal: (event) {
6069 switch (event) {
6170 case final PointerScrollEvent event:
@@ -202,6 +211,7 @@ abstract class MapLibreMapState extends State<MapLibreMap>
202211 _lastScaleUpdateDetails = details;
203212 final ctrlPressed = HardwareKeyboard .instance.isControlPressed;
204213 final buttons = pointerDownEvent.buttons;
214+ final lastPointerOffset = lastEvent? .focalPoint ?? startEvent.focalPoint;
205215
206216 if (doubleTapDown != null && options.gestures.zoom) {
207217 // double tap drag: zoom
@@ -214,7 +224,6 @@ abstract class MapLibreMapState extends State<MapLibreMap>
214224 moveCamera (zoom: newZoom.clamp (options.minZoom, options.maxZoom));
215225 } else if ((buttons & kSecondaryMouseButton) != 0 || ctrlPressed) {
216226 // secondary button: pitch and bearing
217- final lastPointerOffset = lastEvent? .focalPoint ?? startEvent.focalPoint;
218227 final delta = details.focalPoint - lastPointerOffset;
219228 var newBearing = camera.bearing;
220229 if (options.gestures.rotate) {
@@ -231,21 +240,25 @@ abstract class MapLibreMapState extends State<MapLibreMap>
231240 moveCamera (bearing: newBearing, pitch: newPitch, zoom: newZoom);
232241 } else if ((buttons & kPrimaryMouseButton) != 0 ) {
233242 // primary button: pan, zoom, bearing, pinch
243+ var pitch = false ;
244+ if (options.gestures.pitch && _pointers.length == 2 ) {
245+ final pointers = _pointers.values.toList (growable: false );
246+ final delta = pointers.first - pointers.last;
247+ pitch = delta.dy.abs () < delta.dx.abs ();
248+ }
234249
235250 // zoom
236251 var newZoom = camera.zoom;
237252 final lastScale = lastEvent? .scale ?? 1.0 ;
238253 final scaleDelta = details.scale - lastScale;
239- if (scaleDelta != 0 && options.gestures.zoom) {
254+ if (scaleDelta != 0 && options.gestures.zoom && ! pitch ) {
240255 const scaleSensitivity = 1.0 ;
241256 newZoom = camera.zoom + scaleDelta * scaleSensitivity;
242257 }
243258
244259 // center
245260 var newCenter = camera.center;
246- if (options.gestures.pan) {
247- final lastPointerOffset =
248- lastEvent? .focalPoint ?? startEvent.focalPoint;
261+ if (options.gestures.pan && ! pitch) {
249262 final delta = details.focalPoint - lastPointerOffset;
250263 final centerOffset = toScreenLocation (camera.center);
251264 final newCenterOffset = centerOffset - delta;
@@ -254,18 +267,30 @@ abstract class MapLibreMapState extends State<MapLibreMap>
254267
255268 // bearing
256269 var newBearing = camera.bearing;
257- if (options.gestures.rotate && details.rotation != 0.0 ) {
270+ if (options.gestures.rotate && details.rotation != 0.0 && ! pitch ) {
258271 final lastRotation = lastEvent? .rotation ?? 0.0 ;
259272 final rotationDelta = details.rotation - lastRotation;
260273 newBearing = camera.bearing - rotationDelta * radian2Degree;
261274 }
262275
263- moveCamera (zoom: newZoom, center: newCenter, bearing: newBearing);
276+ // pitch
277+ var newPitch = camera.pitch;
278+ if (options.gestures.pitch && pitch) {
279+ final delta = details.focalPoint - lastPointerOffset;
280+ newPitch = camera.pitch - delta.dy * 0.5 ; // sensitivity;
281+ }
282+
283+ moveCamera (
284+ zoom: newZoom,
285+ center: newCenter,
286+ bearing: newBearing,
287+ pitch: newPitch,
288+ );
264289 }
265290 }
266291
267292 void _onScaleEnd (ScaleEndDetails details) {
268- debugPrint ('Scale end: $details ' );
293+ // debugPrint('Scale end: $details');
269294 final camera = this .camera! ;
270295 final firstEvent = _onScaleStartEvent;
271296 final secondToLastEvent = _secondToLastScaleUpdateDetails;
@@ -274,7 +299,6 @@ abstract class MapLibreMapState extends State<MapLibreMap>
274299
275300 // zoom out
276301 if (lastEvent == null && options.gestures.zoom) {
277- debugPrint ('### Zoom out on scale end' );
278302 var newCenter = camera.center;
279303 if (options.gestures.pan) {
280304 newCenter = toLngLat (
0 commit comments