From fad433e4d1ce570fc610270472fc989e9fad051f Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Tue, 23 Jan 2024 19:41:15 +0100 Subject: [PATCH 01/11] add KeyTriggerClickRotateGestureService --- .../map/gestures/services/base_services.dart | 2 + .../services/key_trigger_click_rotate.dart | 48 +++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 lib/src/map/gestures/services/key_trigger_click_rotate.dart diff --git a/lib/src/map/gestures/services/base_services.dart b/lib/src/map/gestures/services/base_services.dart index f67669e59..d8f26430f 100644 --- a/lib/src/map/gestures/services/base_services.dart +++ b/lib/src/map/gestures/services/base_services.dart @@ -5,10 +5,12 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/services.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:latlong2/latlong.dart'; +import 'package:vector_math/vector_math.dart'; part 'double_tap.dart'; part 'double_tap_drag_zoom.dart'; part 'drag.dart'; +part 'key_trigger_click_rotate.dart'; part 'key_trigger_drag_rotate.dart'; part 'long_press.dart'; part 'scroll_wheel_zoom.dart'; diff --git a/lib/src/map/gestures/services/key_trigger_click_rotate.dart b/lib/src/map/gestures/services/key_trigger_click_rotate.dart new file mode 100644 index 000000000..230ab587d --- /dev/null +++ b/lib/src/map/gestures/services/key_trigger_click_rotate.dart @@ -0,0 +1,48 @@ +part of 'base_services.dart'; + +/// Service to handle the key-trigger and click gesture to rotate the map. +/// By clicking at the top of the map the map gets set to 0°-ish, by clicking +/// on the left side of the map the rotation is set to 270°-ish. +/// +/// The key is by default the CTRL key on the keyboard. +class KeyTriggerClickRotateGestureService extends _BaseGestureService { + /// Getter for the keyboard keys that trigger the drag to rotate gesture. + List get keys => + _options.interactionOptions.keyTriggerDragRotateKeys; + + /// Create a new service that rotates the map if the map gets dragged while + /// a specified key is pressed. + KeyTriggerClickRotateGestureService({required super.controller}); + + /// Called when the gesture receives an update, updates the [MapCamera]. + void update(ScaleUpdateDetails details) { + controller.rotateRaw( + _camera.rotation - (details.focalPointDelta.dy * 0.5), + hasGesture: true, + source: MapEventSource.keyTriggerDragRotate, + ); + } + + /// Checks if one of the specified keys that enable this gesture is pressed. + bool get keyPressed => RawKeyboard.instance.keysPressed + .where((key) => keys.contains(key)) + .isNotEmpty; + + /// Get the Rotation in degrees in relation to the cursor position. + /// + /// - Cursor on the left side of the screen and downwards movement: + /// rotate counter clockwise + /// - Cursor on the right side of the screen and downwards movement: + /// rotate clockwise + /// and vice versa. + /// + /// Calculation thanks to https://stackoverflow.com/questions/48916517/javascript-click-and-drag-to-rotate + double getCursorRotationDegrees(Size screenSize, Offset cursorOffset) { + const correctionTerm = 180; // North = cursor + + return (-math.atan2(cursorOffset.dx - screenSize.width / 2, + cursorOffset.dy - screenSize.height / 2) * + radians2Degrees) + + correctionTerm; + } +} From 53735e8c5695f629dbaf63c3126d932a49f838ab Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Tue, 23 Jan 2024 19:56:41 +0100 Subject: [PATCH 02/11] add service to interactive viewer --- .../map/gestures/map_interactive_viewer.dart | 8 +++++ .../services/key_trigger_click_rotate.dart | 7 ++--- lib/src/map/options/map_gestures.dart | 29 +++++++++++++++---- 3 files changed, 34 insertions(+), 10 deletions(-) diff --git a/lib/src/map/gestures/map_interactive_viewer.dart b/lib/src/map/gestures/map_interactive_viewer.dart index 40a170e0e..24d72e830 100644 --- a/lib/src/map/gestures/map_interactive_viewer.dart +++ b/lib/src/map/gestures/map_interactive_viewer.dart @@ -42,6 +42,7 @@ class MapInteractiveViewerState extends State DragGestureService? _drag; DoubleTapDragZoomGestureService? _doubleTapDragZoom; KeyTriggerDragRotateGestureService? _keyTriggerDragRotate; + KeyTriggerClickRotateGestureService? _keyTriggerClickRotate; MapControllerImpl get _controller => widget.controller; @@ -297,6 +298,13 @@ class MapInteractiveViewerState extends State _keyTriggerDragRotate = null; } + if (newGestures.keyTriggerClickRotate) { + _keyTriggerClickRotate = + KeyTriggerClickRotateGestureService(controller: _controller); + } else { + _keyTriggerClickRotate = null; + } + if (newGestures.doubleTapDragZoom) { _doubleTapDragZoom = DoubleTapDragZoomGestureService(controller: _controller); diff --git a/lib/src/map/gestures/services/key_trigger_click_rotate.dart b/lib/src/map/gestures/services/key_trigger_click_rotate.dart index 230ab587d..a63d71631 100644 --- a/lib/src/map/gestures/services/key_trigger_click_rotate.dart +++ b/lib/src/map/gestures/services/key_trigger_click_rotate.dart @@ -30,11 +30,8 @@ class KeyTriggerClickRotateGestureService extends _BaseGestureService { /// Get the Rotation in degrees in relation to the cursor position. /// - /// - Cursor on the left side of the screen and downwards movement: - /// rotate counter clockwise - /// - Cursor on the right side of the screen and downwards movement: - /// rotate clockwise - /// and vice versa. + /// By clicking at the top of the map the map gets set to 0°-ish, by clicking + /// on the left side of the map the rotation is set to 270°-ish. /// /// Calculation thanks to https://stackoverflow.com/questions/48916517/javascript-click-and-drag-to-rotate double getCursorRotationDegrees(Size screenSize, Offset cursorOffset) { diff --git a/lib/src/map/options/map_gestures.dart b/lib/src/map/options/map_gestures.dart index d5eb94fb3..9ffda3fc2 100644 --- a/lib/src/map/options/map_gestures.dart +++ b/lib/src/map/options/map_gestures.dart @@ -21,6 +21,7 @@ class MapGestures { required this.scrollWheelZoom, required this.twoFingerRotate, required this.keyTriggerDragRotate, + required this.keyTriggerClickRotate, required this.trackpadZoom, }); @@ -39,6 +40,7 @@ class MapGestures { this.scrollWheelZoom = true, this.twoFingerRotate = true, this.keyTriggerDragRotate = true, + this.keyTriggerClickRotate = true, this.trackpadZoom = true, }); @@ -57,6 +59,7 @@ class MapGestures { this.scrollWheelZoom = false, this.twoFingerRotate = false, this.keyTriggerDragRotate = false, + this.keyTriggerClickRotate = false, this.trackpadZoom = false, }); @@ -81,6 +84,7 @@ class MapGestures { twoFingerZoom: zoom, twoFingerRotate: rotate, keyTriggerDragRotate: rotate, + keyTriggerClickRotate: rotate, trackpadZoom: zoom, ); @@ -133,6 +137,8 @@ class MapGestures { InteractiveFlag.hasFlag(flags, InteractiveFlag.twoFingerRotate), keyTriggerDragRotate: InteractiveFlag.hasFlag(flags, InteractiveFlag.keyTriggerDragRotate), + keyTriggerClickRotate: + InteractiveFlag.hasFlag(flags, InteractiveFlag.keyTriggerClickRotate), trackpadZoom: InteractiveFlag.hasFlag(flags, InteractiveFlag.trackpadZoom), ); @@ -172,6 +178,13 @@ class MapGestures { /// or finger. final bool keyTriggerDragRotate; + /// Enable rotation by pressing the defined keyboard key (by default CTRL key) + /// and clicking on the map. + /// + /// By clicking at the top of the map the map gets set to 0°-ish, by clicking + /// on the left side of the map the rotation is set to 270°-ish. + final bool keyTriggerClickRotate; + /// Wither to change the value of some gestures. Returns a new /// [MapGestures] object. MapGestures copyWith({ @@ -184,6 +197,7 @@ class MapGestures { bool? scrollWheelZoom, bool? twoFingerRotate, bool? keyTriggerDragRotate, + bool? keyTriggerClickRotate, bool? trackpadZoom, }) => MapGestures( @@ -196,6 +210,8 @@ class MapGestures { scrollWheelZoom: scrollWheelZoom ?? this.scrollWheelZoom, twoFingerRotate: twoFingerRotate ?? this.twoFingerRotate, keyTriggerDragRotate: keyTriggerDragRotate ?? this.keyTriggerDragRotate, + keyTriggerClickRotate: + keyTriggerClickRotate ?? this.keyTriggerClickRotate, trackpadZoom: trackpadZoom ?? this.trackpadZoom, ); @@ -256,7 +272,8 @@ abstract class InteractiveFlag { scrollWheelZoom | twoFingerRotate | trackpadZoom | - keyTriggerDragRotate; + keyTriggerDragRotate | + keyTriggerClickRotate; /// No enabled interactive flags, use as `flags: InteractiveFlag.none` to /// have a non interactive map. @@ -298,9 +315,6 @@ abstract class InteractiveFlag { static const int scrollWheelZoom = 1 << 6; /// Enable rotation with two-finger twist gesture - /// - /// For controlling cursor/keyboard rotation, see - /// [InteractionOptions.cursorKeyboardRotationOptions]. static const int twoFingerRotate = 1 << 7; /// Enable rotation with two-finger twist gesture. @@ -309,12 +323,17 @@ abstract class InteractiveFlag { /// Enable rotation by pressing the defined keyboard keys /// (by default CTRL Key) and drag the map with the cursor. - /// To change the key see [InteractionOptions.cursorKeyboardRotationOptions]. + /// To change the key see [InteractionOptions.keyTriggerDragRotateKeys]. static const int keyTriggerDragRotate = 1 << 8; /// Enable zooming by using the trackpad / touchpad of a device. static const int trackpadZoom = 1 << 9; + /// Enable rotation by pressing the defined keyboard keys + /// (by default CTRL Key) and click on the map. + /// To change the key see [InteractionOptions.keyTriggerDragRotateKeys]. + static const int keyTriggerClickRotate = 1 << 10; + /// Returns `true` if [leftFlags] has at least one member in [rightFlags] /// (intersection) for example /// [leftFlags] = [InteractiveFlag.drag] | [InteractiveFlag.twoFingerRotate] From 440b0ff2b24b27c4067c99dcc63bcf35b8204f86 Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Tue, 23 Jan 2024 20:33:06 +0100 Subject: [PATCH 03/11] integrate service callbacks --- .../map/gestures/map_interactive_viewer.dart | 38 +++++++++++++++++-- .../services/key_trigger_click_rotate.dart | 16 +++++++- 2 files changed, 49 insertions(+), 5 deletions(-) diff --git a/lib/src/map/gestures/map_interactive_viewer.dart b/lib/src/map/gestures/map_interactive_viewer.dart index 24d72e830..cf34f9f49 100644 --- a/lib/src/map/gestures/map_interactive_viewer.dart +++ b/lib/src/map/gestures/map_interactive_viewer.dart @@ -106,6 +106,7 @@ class MapInteractiveViewerState extends State _drag != null || _doubleTapDragZoom != null || _twoFingerInput != null; + final useTapCallback = _tap != null || _keyTriggerClickRotate != null; return Listener( onPointerDown: (event) { @@ -167,9 +168,40 @@ class MapInteractiveViewerState extends State onPointerPanZoomEnd: _trackpadZoom?.end, child: GestureDetector( - onTapDown: _tap?.setDetails, - onTapCancel: _tap?.reset, - onTap: _tap?.submit, + onTapDown: useTapCallback + ? (details) { + if (_keyTriggerClickRotate?.keyPressed ?? false) { + _keyTriggerClickRotate!.setDetails(details); + // Normally we would wait until the tap gesture is confirmed. + // For this gesture however we call it directly for faster + // response time. (Note that `onTap` still has a small delay) + // This however has the trade-off that the gesture could turn + // out to be a double click and both gesture would fire. + final screenSize = MediaQuery.sizeOf(context); + _keyTriggerClickRotate!.submit(screenSize); + return; + } + _tap?.setDetails(details); + } + : null, + onTapCancel: useTapCallback + ? () { + if (_keyTriggerClickRotate?.isActive ?? false) { + _keyTriggerClickRotate!.reset(); + return; + } + _tap?.reset(); + } + : null, + onTap: useTapCallback + ? () { + if (_keyTriggerClickRotate?.isActive ?? false) { + // gesture already submitted in `onTapDown`. + return; + } + _tap?.submit(); + } + : null, onLongPressStart: _longPress?.submit, diff --git a/lib/src/map/gestures/services/key_trigger_click_rotate.dart b/lib/src/map/gestures/services/key_trigger_click_rotate.dart index a63d71631..54b897b5f 100644 --- a/lib/src/map/gestures/services/key_trigger_click_rotate.dart +++ b/lib/src/map/gestures/services/key_trigger_click_rotate.dart @@ -6,18 +6,30 @@ part of 'base_services.dart'; /// /// The key is by default the CTRL key on the keyboard. class KeyTriggerClickRotateGestureService extends _BaseGestureService { + TapDownDetails? details; + /// Getter for the keyboard keys that trigger the drag to rotate gesture. List get keys => _options.interactionOptions.keyTriggerDragRotateKeys; + /// Returns true if the service has consumed a [TapDownDetails] for the + /// tap gesture. + bool get isActive => details != null; + /// Create a new service that rotates the map if the map gets dragged while /// a specified key is pressed. KeyTriggerClickRotateGestureService({required super.controller}); + void setDetails(TapDownDetails newDetails) => details = newDetails; + + void reset() => details = null; + /// Called when the gesture receives an update, updates the [MapCamera]. - void update(ScaleUpdateDetails details) { + void submit(Size screenSize) { + if (details == null) return; + controller.rotateRaw( - _camera.rotation - (details.focalPointDelta.dy * 0.5), + getCursorRotationDegrees(screenSize, details!.localPosition), hasGesture: true, source: MapEventSource.keyTriggerDragRotate, ); From 2d29f5dc19d21db144cad70b7d819cde9df7123b Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Tue, 23 Jan 2024 20:42:14 +0100 Subject: [PATCH 04/11] add to example app --- example/lib/pages/gestures_page.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/example/lib/pages/gestures_page.dart b/example/lib/pages/gestures_page.dart index 79f6e9b80..3076bd342 100644 --- a/example/lib/pages/gestures_page.dart +++ b/example/lib/pages/gestures_page.dart @@ -30,6 +30,7 @@ class _GesturesPageState extends State { 'Rotation': { InteractiveFlag.twoFingerRotate: 'Twist', InteractiveFlag.keyTriggerDragRotate: 'CTRL+Drag', + InteractiveFlag.keyTriggerClickRotate: 'CTRL+Click', }, }; @@ -48,7 +49,7 @@ class _GesturesPageState extends State { child: Column( children: [ Flex( - direction: screenWidth >= 750 ? Axis.horizontal : Axis.vertical, + direction: screenWidth >= 850 ? Axis.horizontal : Axis.vertical, mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: availableFlags.entries From 0ce24920ee79ed98ffabaab57adaf25f2555e7e7 Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Tue, 23 Jan 2024 21:11:01 +0100 Subject: [PATCH 05/11] make `getCursorRotationDegrees` static --- lib/src/map/gestures/services/key_trigger_click_rotate.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/map/gestures/services/key_trigger_click_rotate.dart b/lib/src/map/gestures/services/key_trigger_click_rotate.dart index 54b897b5f..c7b229486 100644 --- a/lib/src/map/gestures/services/key_trigger_click_rotate.dart +++ b/lib/src/map/gestures/services/key_trigger_click_rotate.dart @@ -46,7 +46,7 @@ class KeyTriggerClickRotateGestureService extends _BaseGestureService { /// on the left side of the map the rotation is set to 270°-ish. /// /// Calculation thanks to https://stackoverflow.com/questions/48916517/javascript-click-and-drag-to-rotate - double getCursorRotationDegrees(Size screenSize, Offset cursorOffset) { + static double getCursorRotationDegrees(Size screenSize, Offset cursorOffset) { const correctionTerm = 180; // North = cursor return (-math.atan2(cursorOffset.dx - screenSize.width / 2, From 80d9fe0ed91cb6c257228e5f3d96397b171347d7 Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Tue, 23 Jan 2024 21:24:17 +0100 Subject: [PATCH 06/11] make _getCursorRotationDegrees private --- lib/src/map/gestures/services/key_trigger_click_rotate.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/src/map/gestures/services/key_trigger_click_rotate.dart b/lib/src/map/gestures/services/key_trigger_click_rotate.dart index c7b229486..90053ae72 100644 --- a/lib/src/map/gestures/services/key_trigger_click_rotate.dart +++ b/lib/src/map/gestures/services/key_trigger_click_rotate.dart @@ -29,7 +29,7 @@ class KeyTriggerClickRotateGestureService extends _BaseGestureService { if (details == null) return; controller.rotateRaw( - getCursorRotationDegrees(screenSize, details!.localPosition), + _getCursorRotationDegrees(screenSize, details!.localPosition), hasGesture: true, source: MapEventSource.keyTriggerDragRotate, ); @@ -46,7 +46,8 @@ class KeyTriggerClickRotateGestureService extends _BaseGestureService { /// on the left side of the map the rotation is set to 270°-ish. /// /// Calculation thanks to https://stackoverflow.com/questions/48916517/javascript-click-and-drag-to-rotate - static double getCursorRotationDegrees(Size screenSize, Offset cursorOffset) { + static double _getCursorRotationDegrees( + Size screenSize, Offset cursorOffset) { const correctionTerm = 180; // North = cursor return (-math.atan2(cursorOffset.dx - screenSize.width / 2, From 41e1c13449c1910fdb11948d1f4bc0ae0c250b06 Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Thu, 1 Feb 2024 21:09:47 +0100 Subject: [PATCH 07/11] trigger gesture after tap up --- .../map/gestures/map_interactive_viewer.dart | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/lib/src/map/gestures/map_interactive_viewer.dart b/lib/src/map/gestures/map_interactive_viewer.dart index cf34f9f49..1346e09ba 100644 --- a/lib/src/map/gestures/map_interactive_viewer.dart +++ b/lib/src/map/gestures/map_interactive_viewer.dart @@ -172,13 +172,6 @@ class MapInteractiveViewerState extends State ? (details) { if (_keyTriggerClickRotate?.keyPressed ?? false) { _keyTriggerClickRotate!.setDetails(details); - // Normally we would wait until the tap gesture is confirmed. - // For this gesture however we call it directly for faster - // response time. (Note that `onTap` still has a small delay) - // This however has the trade-off that the gesture could turn - // out to be a double click and both gesture would fire. - final screenSize = MediaQuery.sizeOf(context); - _keyTriggerClickRotate!.submit(screenSize); return; } _tap?.setDetails(details); @@ -195,6 +188,16 @@ class MapInteractiveViewerState extends State : null, onTap: useTapCallback ? () { + if (_keyTriggerClickRotate?.keyPressed ?? false) { + // Normally we would wait until the tap gesture is confirmed. + // For this gesture however we call it directly for faster + // response time. (Note that `onTap` still has a small delay) + // This however has the trade-off that the gesture could turn + // out to be a double click and both gesture would fire. + final screenSize = MediaQuery.sizeOf(context); + _keyTriggerClickRotate!.submit(screenSize); + return; + } if (_keyTriggerClickRotate?.isActive ?? false) { // gesture already submitted in `onTapDown`. return; From 7d10f8e537dcc60d99b4776a7f9f28fad4fe1ca1 Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Thu, 1 Feb 2024 21:30:38 +0100 Subject: [PATCH 08/11] clean up --- lib/src/map/gestures/map_interactive_viewer.dart | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/src/map/gestures/map_interactive_viewer.dart b/lib/src/map/gestures/map_interactive_viewer.dart index 1346e09ba..de58ca4cb 100644 --- a/lib/src/map/gestures/map_interactive_viewer.dart +++ b/lib/src/map/gestures/map_interactive_viewer.dart @@ -198,10 +198,6 @@ class MapInteractiveViewerState extends State _keyTriggerClickRotate!.submit(screenSize); return; } - if (_keyTriggerClickRotate?.isActive ?? false) { - // gesture already submitted in `onTapDown`. - return; - } _tap?.submit(); } : null, From a77adcfad90af3d16d8dd5345e6670b6e6d0d50b Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Thu, 1 Feb 2024 21:30:41 +0100 Subject: [PATCH 09/11] fix update gestures --- lib/src/map/options/map_gestures.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/src/map/options/map_gestures.dart b/lib/src/map/options/map_gestures.dart index 9ffda3fc2..0aaffb0b0 100644 --- a/lib/src/map/options/map_gestures.dart +++ b/lib/src/map/options/map_gestures.dart @@ -228,6 +228,7 @@ class MapGestures { doubleTapDragZoom == other.doubleTapDragZoom && scrollWheelZoom == other.scrollWheelZoom && twoFingerRotate == other.twoFingerRotate && + keyTriggerClickRotate == other.keyTriggerClickRotate && keyTriggerDragRotate == other.keyTriggerDragRotate; @override @@ -240,6 +241,7 @@ class MapGestures { doubleTapDragZoom, scrollWheelZoom, twoFingerRotate, + keyTriggerClickRotate, keyTriggerDragRotate, ); } From a1ee82704acf46cb131c21a3b194fa9e11b4e71b Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Thu, 1 Feb 2024 23:54:50 +0100 Subject: [PATCH 10/11] format --- lib/src/map/gestures/services/key_trigger_click_rotate.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/src/map/gestures/services/key_trigger_click_rotate.dart b/lib/src/map/gestures/services/key_trigger_click_rotate.dart index 90053ae72..bb2af906a 100644 --- a/lib/src/map/gestures/services/key_trigger_click_rotate.dart +++ b/lib/src/map/gestures/services/key_trigger_click_rotate.dart @@ -45,7 +45,8 @@ class KeyTriggerClickRotateGestureService extends _BaseGestureService { /// By clicking at the top of the map the map gets set to 0°-ish, by clicking /// on the left side of the map the rotation is set to 270°-ish. /// - /// Calculation thanks to https://stackoverflow.com/questions/48916517/javascript-click-and-drag-to-rotate + /// Calculation thanks to + /// https://stackoverflow.com/questions/48916517/javascript-click-and-drag-to-rotate static double _getCursorRotationDegrees( Size screenSize, Offset cursorOffset) { const correctionTerm = 180; // North = cursor From f9fd14b3c9bc10c10ab9a1a4ca81cba78ad5721d Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Fri, 2 Feb 2024 01:12:51 +0100 Subject: [PATCH 11/11] fix keyTriggerDragRotate gesture --- .../map/gestures/map_interactive_viewer.dart | 3 ++- .../map/gestures/services/base_services.dart | 19 +++++++++++++ .../services/key_trigger_click_rotate.dart | 22 +++------------ .../services/key_trigger_drag_rotate.dart | 27 +++++++++++++++++-- 4 files changed, 50 insertions(+), 21 deletions(-) diff --git a/lib/src/map/gestures/map_interactive_viewer.dart b/lib/src/map/gestures/map_interactive_viewer.dart index de58ca4cb..b05da49ad 100644 --- a/lib/src/map/gestures/map_interactive_viewer.dart +++ b/lib/src/map/gestures/map_interactive_viewer.dart @@ -240,7 +240,8 @@ class MapInteractiveViewerState extends State onScaleStart: useScaleCallback ? (details) { if (_keyTriggerDragRotate?.keyPressed ?? false) { - _keyTriggerDragRotate!.start(); + final screenSize = MediaQuery.sizeOf(context); + _keyTriggerDragRotate!.start(screenSize); } else if (_doubleTapDragZoom?.isActive ?? false) { _doubleTapDragZoom!.start(details); } else if (details.pointerCount == 1) { diff --git a/lib/src/map/gestures/services/base_services.dart b/lib/src/map/gestures/services/base_services.dart index d8f26430f..0a63f76eb 100644 --- a/lib/src/map/gestures/services/base_services.dart +++ b/lib/src/map/gestures/services/base_services.dart @@ -78,3 +78,22 @@ Offset _rotateOffset(MapCamera camera, Offset offset) { return Offset(nx, ny); } + +/// Get the Rotation in degrees in relation to the cursor position. +/// +/// By clicking at the top of the map the map gets set to 0°-ish, by clicking +/// on the left side of the map the rotation is set to 270°-ish. +/// +/// Calculation thanks to +/// https://stackoverflow.com/questions/48916517/javascript-click-and-drag-to-rotate +double _getCursorRotationDegrees( + Size screenSize, + Offset cursorOffset, +) { + const degreesCorrection = 180; + final degrees = -math.atan2(cursorOffset.dx - screenSize.width / 2, + cursorOffset.dy - screenSize.height / 2) * + radians2Degrees; + + return degrees + degreesCorrection; +} diff --git a/lib/src/map/gestures/services/key_trigger_click_rotate.dart b/lib/src/map/gestures/services/key_trigger_click_rotate.dart index bb2af906a..8a2b59211 100644 --- a/lib/src/map/gestures/services/key_trigger_click_rotate.dart +++ b/lib/src/map/gestures/services/key_trigger_click_rotate.dart @@ -29,7 +29,10 @@ class KeyTriggerClickRotateGestureService extends _BaseGestureService { if (details == null) return; controller.rotateRaw( - _getCursorRotationDegrees(screenSize, details!.localPosition), + _getCursorRotationDegrees( + screenSize, + details!.localPosition, + ), hasGesture: true, source: MapEventSource.keyTriggerDragRotate, ); @@ -39,21 +42,4 @@ class KeyTriggerClickRotateGestureService extends _BaseGestureService { bool get keyPressed => RawKeyboard.instance.keysPressed .where((key) => keys.contains(key)) .isNotEmpty; - - /// Get the Rotation in degrees in relation to the cursor position. - /// - /// By clicking at the top of the map the map gets set to 0°-ish, by clicking - /// on the left side of the map the rotation is set to 270°-ish. - /// - /// Calculation thanks to - /// https://stackoverflow.com/questions/48916517/javascript-click-and-drag-to-rotate - static double _getCursorRotationDegrees( - Size screenSize, Offset cursorOffset) { - const correctionTerm = 180; // North = cursor - - return (-math.atan2(cursorOffset.dx - screenSize.width / 2, - cursorOffset.dy - screenSize.height / 2) * - radians2Degrees) + - correctionTerm; - } } diff --git a/lib/src/map/gestures/services/key_trigger_drag_rotate.dart b/lib/src/map/gestures/services/key_trigger_drag_rotate.dart index 821d239d7..aed7c8ca7 100644 --- a/lib/src/map/gestures/services/key_trigger_drag_rotate.dart +++ b/lib/src/map/gestures/services/key_trigger_drag_rotate.dart @@ -10,6 +10,17 @@ class KeyTriggerDragRotateGestureService extends _BaseGestureService { /// drag updates. bool isActive = false; + /// The size of the screen when the gesture starts. Because it is very + /// unlikely that the size of the screen changes during the gesture we use the + /// screen size of when the gesture starts. + Size? _screenSize; + + /// The rotation to the start on the gesture + double? _degreeCorrection; + + /// Start rotation + double? _startRotation; + /// Getter for the keyboard keys that trigger the drag to rotate gesture. List get keys => _options.interactionOptions.keyTriggerDragRotateKeys; @@ -19,7 +30,9 @@ class KeyTriggerDragRotateGestureService extends _BaseGestureService { KeyTriggerDragRotateGestureService({required super.controller}); /// Called when the gesture is started, stores important values. - void start() { + void start(Size screenSize) { + _screenSize = screenSize; + _startRotation = _camera.rotation; controller.emitMapEvent( MapEventRotateStart( camera: _camera, @@ -30,8 +43,16 @@ class KeyTriggerDragRotateGestureService extends _BaseGestureService { /// Called when the gesture receives an update, updates the [MapCamera]. void update(ScaleUpdateDetails details) { + if (_screenSize == null || _startRotation == null) return; + + final rotation = _getCursorRotationDegrees( + _screenSize!, + details.localFocalPoint, + ); + _degreeCorrection ??= rotation; + controller.rotateRaw( - _camera.rotation - (details.focalPointDelta.dy * 0.5), + rotation - _degreeCorrection! + _startRotation!, hasGesture: true, source: MapEventSource.keyTriggerDragRotate, ); @@ -39,6 +60,8 @@ class KeyTriggerDragRotateGestureService extends _BaseGestureService { /// Called when the gesture ends, cleans up the previously stored values. void end() { + _screenSize = null; + _degreeCorrection = null; controller.emitMapEvent( MapEventRotateEnd( camera: _camera,