From c8f3961cac01760552aaa45d226376a0b76b0eda Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Sun, 19 Nov 2023 20:16:10 +0100 Subject: [PATCH 001/102] tap, longPress, secondaryTap, drag, scroll gestures --- .../flutter_map_interactive_viewer.dart | 955 ------------------ lib/src/gestures/map_interactive_viewer.dart | 287 ++++++ lib/src/map/controller/internal.dart | 8 +- lib/src/map/widget.dart | 4 +- 4 files changed, 293 insertions(+), 961 deletions(-) delete mode 100644 lib/src/gestures/flutter_map_interactive_viewer.dart create mode 100644 lib/src/gestures/map_interactive_viewer.dart diff --git a/lib/src/gestures/flutter_map_interactive_viewer.dart b/lib/src/gestures/flutter_map_interactive_viewer.dart deleted file mode 100644 index 083deac33..000000000 --- a/lib/src/gestures/flutter_map_interactive_viewer.dart +++ /dev/null @@ -1,955 +0,0 @@ -import 'dart:async'; -import 'dart:math' as math; - -import 'package:flutter/gestures.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter/widgets.dart'; -import 'package:flutter_map/src/gestures/interactive_flag.dart'; -import 'package:flutter_map/src/gestures/latlng_tween.dart'; -import 'package:flutter_map/src/gestures/map_events.dart'; -import 'package:flutter_map/src/gestures/multi_finger_gesture.dart'; -import 'package:flutter_map/src/gestures/positioned_tap_detector_2.dart'; -import 'package:flutter_map/src/map/camera/camera.dart'; -import 'package:flutter_map/src/map/controller/internal.dart'; -import 'package:flutter_map/src/map/options/cursor_keyboard_rotation.dart'; -import 'package:flutter_map/src/map/options/interaction.dart'; -import 'package:flutter_map/src/map/options/options.dart'; -import 'package:flutter_map/src/misc/point_extensions.dart'; -import 'package:latlong2/latlong.dart'; - -typedef InteractiveViewerBuilder = Widget Function( - BuildContext context, - MapOptions options, - MapCamera camera, -); - -/// Applies interactions (gestures/scroll/taps etc) to the current [MapCamera] -/// via the internal [controller]. -class FlutterMapInteractiveViewer extends StatefulWidget { - final InteractiveViewerBuilder builder; - final FlutterMapInternalController controller; - - const FlutterMapInteractiveViewer({ - super.key, - required this.builder, - required this.controller, - }); - - @override - State createState() => - FlutterMapInteractiveViewerState(); -} - -class FlutterMapInteractiveViewerState - extends State with TickerProviderStateMixin { - static const int _kMinFlingVelocity = 800; - static const _kDoubleTapZoomDuration = 200; - static const doubleTapDelay = Duration(milliseconds: 250); - - final _positionedTapController = PositionedTapController(); - final _gestureArenaTeam = GestureArenaTeam(); - late Map _gestures; - - bool _dragMode = false; - int _gestureWinner = MultiFingerGesture.none; - int _pointerCounter = 0; - bool _isListeningForInterruptions = false; - - var _rotationStarted = false; - var _pinchZoomStarted = false; - var _pinchMoveStarted = false; - var _dragStarted = false; - var _flingAnimationStarted = false; - - /// Helps to reset ScaleUpdateDetails.scale back to 1.0 when a multi finger - /// gesture wins - late double _scaleCorrector; - late double _lastRotation; - late double _lastScale; - late Offset _lastFocalLocal; - late LatLng _mapCenterStart; - late double _mapZoomStart; - late Offset _focalStartLocal; - late LatLng _focalStartLatLng; - - late final AnimationController _flingController = - AnimationController(vsync: this); - late Animation _flingAnimation; - - late final AnimationController _doubleTapController = AnimationController( - vsync: this, - duration: const Duration( - milliseconds: _kDoubleTapZoomDuration, - ), - ); - late Animation _doubleTapZoomAnimation; - late Animation _doubleTapCenterAnimation; - - // 'ckr' = cursor/keyboard rotation - final _ckrTriggered = ValueNotifier(false); - double _ckrClickDegrees = 0; - double _ckrInitialDegrees = 0; - - int _tapUpCounter = 0; - Timer? _doubleTapHoldMaxDelay; - - MapCamera get _camera => widget.controller.camera; - - MapOptions get _options => widget.controller.options; - - InteractionOptions get _interactionOptions => _options.interactionOptions; - - @override - void initState() { - super.initState(); - widget.controller.interactiveViewerState = this; - widget.controller.addListener(onMapStateChange); - _flingController - ..addListener(_handleFlingAnimation) - ..addStatusListener(_flingAnimationStatusListener); - _doubleTapController - ..addListener(_handleDoubleTapZoomAnimation) - ..addStatusListener(_doubleTapZoomStatusListener); - - ServicesBinding.instance.keyboard - .addHandler(cursorKeyboardRotationTriggerHandler); - } - - @override - void didChangeDependencies() { - // _createGestures uses a MediaQuery to determine gesture settings. This - // will update those gesture settings if they change. - _gestures = _createGestures( - dragEnabled: InteractiveFlag.hasDrag(_interactionOptions.flags), - ); - super.didChangeDependencies(); - } - - @override - void dispose() { - widget.controller.removeListener(onMapStateChange); - _flingController.dispose(); - _doubleTapController.dispose(); - - _ckrTriggered.dispose(); - ServicesBinding.instance.keyboard - .removeHandler(cursorKeyboardRotationTriggerHandler); - - super.dispose(); - } - - void onMapStateChange() => setState(() {}); - - bool cursorKeyboardRotationTriggerHandler(KeyEvent event) { - _ckrTriggered.value = (event is KeyRepeatEvent || event is KeyDownEvent) && - (_interactionOptions.cursorKeyboardRotationOptions.isKeyTrigger ?? - (key) => CursorKeyboardRotationOptions.defaultTriggerKeys - .contains(key))(event.logicalKey); - return false; - } - - void updateGestures( - InteractionOptions oldOptions, - InteractionOptions newOptions, - ) { - final newHasDrag = InteractiveFlag.hasDrag(newOptions.flags); - if (newHasDrag != InteractiveFlag.hasDrag(oldOptions.flags)) { - _gestures = _createGestures(dragEnabled: newHasDrag); - } - - if (!InteractiveFlag.hasFlingAnimation(newOptions.flags)) { - _closeFlingAnimationController(MapEventSource.interactiveFlagsChanged); - } - if (InteractiveFlag.hasDoubleTapZoom(newOptions.flags)) { - _closeDoubleTapController(MapEventSource.interactiveFlagsChanged); - } - - final gestures = _getMultiFingerGestureFlags(newOptions); - - if (_rotationStarted && - !InteractiveFlag.hasRotate(newOptions.flags) && - !MultiFingerGesture.hasRotate(gestures)) { - _rotationStarted = false; - - if (_gestureWinner == MultiFingerGesture.rotate) { - _gestureWinner = MultiFingerGesture.none; - } - - widget.controller.rotateEnded(MapEventSource.interactiveFlagsChanged); - } - - var emitMapEventMoveEnd = false; - - if (_pinchZoomStarted && - !InteractiveFlag.hasPinchZoom(newOptions.flags) && - !MultiFingerGesture.hasPinchZoom(gestures)) { - _pinchZoomStarted = false; - emitMapEventMoveEnd = true; - - if (_gestureWinner == MultiFingerGesture.pinchZoom) { - _gestureWinner = MultiFingerGesture.none; - } - } - - if (_pinchMoveStarted && - !InteractiveFlag.hasPinchMove(newOptions.flags) && - !MultiFingerGesture.hasPinchMove(gestures)) { - _pinchMoveStarted = false; - emitMapEventMoveEnd = true; - - if (_gestureWinner == MultiFingerGesture.pinchMove) { - _gestureWinner = MultiFingerGesture.none; - } - } - - if (_dragStarted && !newHasDrag) { - _dragStarted = false; - emitMapEventMoveEnd = true; - } - - if (emitMapEventMoveEnd) { - widget.controller.moveEnded(MapEventSource.interactiveFlagsChanged); - } - - // No way to detect whether the [CursorKeyboardRotationOptions.isKeyTrigger]s - // are equal, so assume they aren't - ServicesBinding.instance.keyboard - .removeHandler(cursorKeyboardRotationTriggerHandler); - ServicesBinding.instance.keyboard - .addHandler(cursorKeyboardRotationTriggerHandler); - } - - Map _createGestures({ - required bool dragEnabled, - }) { - final gestureSettings = MediaQuery.gestureSettingsOf(context); - return { - TapGestureRecognizer: - GestureRecognizerFactoryWithHandlers( - () => TapGestureRecognizer(debugOwner: this), - (recognizer) { - recognizer - ..onTapDown = _positionedTapController.onTapDown - ..onTapUp = _handleOnTapUp - ..onTap = _positionedTapController.onTap - ..onSecondaryTap = _positionedTapController.onSecondaryTap - ..onSecondaryTapDown = _positionedTapController.onTapDown; - }, - ), - LongPressGestureRecognizer: - GestureRecognizerFactoryWithHandlers( - () => LongPressGestureRecognizer(debugOwner: this), - (recognizer) { - recognizer.onLongPress = _positionedTapController.onLongPress; - }, - ), - if (dragEnabled) - VerticalDragGestureRecognizer: - GestureRecognizerFactoryWithHandlers( - () => VerticalDragGestureRecognizer(debugOwner: this), - (recognizer) { - recognizer - ..gestureSettings = gestureSettings - ..team ??= _gestureArenaTeam - ..onUpdate = (details) { - // Absorbing vertical drags - }; - }, - ), - if (dragEnabled) - HorizontalDragGestureRecognizer: GestureRecognizerFactoryWithHandlers< - HorizontalDragGestureRecognizer>( - () => HorizontalDragGestureRecognizer(debugOwner: this), - (recognizer) { - recognizer - ..gestureSettings = gestureSettings - ..team ??= _gestureArenaTeam - ..onUpdate = (details) { - // Absorbing horizontal drags - }; - }), - ScaleGestureRecognizer: - GestureRecognizerFactoryWithHandlers( - () => ScaleGestureRecognizer(debugOwner: this), - (recognizer) { - recognizer - ..onStart = _handleScaleStart - ..onUpdate = _handleScaleUpdate - ..onEnd = _handleScaleEnd - ..team ??= _gestureArenaTeam; - _gestureArenaTeam.captain = recognizer; - }, - ), - }; - } - - @override - Widget build(BuildContext context) { - return Listener( - onPointerDown: _onPointerDown, - onPointerUp: _onPointerUp, - onPointerCancel: _onPointerCancel, - onPointerHover: _onPointerHover, - onPointerMove: _onPointerMove, - onPointerSignal: _onPointerSignal, - child: PositionedTapDetector2( - controller: _positionedTapController, - onTap: _handleTap, - onSecondaryTap: _handleSecondaryTap, - onLongPress: _handleLongPress, - onDoubleTap: _handleDoubleTap, - doubleTapDelay: - InteractiveFlag.hasDoubleTapZoom(_interactionOptions.flags) - ? null - : Duration.zero, - child: RawGestureDetector( - gestures: _gestures, - child: widget.builder( - context, - widget.controller.options, - widget.controller.camera, - ), - ), - ), - ); - } - - void _onPointerDown(PointerDownEvent event) { - ++_pointerCounter; - - if (_ckrTriggered.value) { - _ckrInitialDegrees = _camera.rotation; - _ckrClickDegrees = getCursorRotationDegrees(event.localPosition); - widget.controller.rotateStarted(MapEventSource.cursorKeyboardRotation); - } - - if (_options.onPointerDown != null) { - final latlng = _camera.offsetToCrs(event.localPosition); - _options.onPointerDown!(event, latlng); - } - } - - void _onPointerUp(PointerUpEvent event) { - --_pointerCounter; - - if (_interactionOptions.cursorKeyboardRotationOptions.setNorthOnClick && - _ckrTriggered.value && - _ckrInitialDegrees == _camera.rotation) { - widget.controller.rotate( - getCursorRotationDegrees(event.localPosition), - hasGesture: true, - source: MapEventSource.cursorKeyboardRotation, - id: null, - ); - } - - if (_options.onPointerUp != null) { - final latlng = _camera.offsetToCrs(event.localPosition); - _options.onPointerUp!(event, latlng); - } - } - - void _onPointerCancel(PointerCancelEvent event) { - --_pointerCounter; - - if (_options.onPointerCancel != null) { - final latlng = _camera.offsetToCrs(event.localPosition); - _options.onPointerCancel!(event, latlng); - } - } - - void _onPointerHover(PointerHoverEvent event) { - if (_options.onPointerHover != null) { - final latlng = _camera.offsetToCrs(event.localPosition); - _options.onPointerHover!(event, latlng); - } - } - - void _onPointerMove(PointerMoveEvent event) { - if (!_ckrTriggered.value) return; - - final baseSetNorth = - getCursorRotationDegrees(event.localPosition) - _ckrClickDegrees; - - widget.controller.rotate( - _interactionOptions.cursorKeyboardRotationOptions.behaviour == - CursorRotationBehaviour.setNorth - ? baseSetNorth - : (_ckrInitialDegrees + baseSetNorth) % 360, - hasGesture: true, - source: MapEventSource.cursorKeyboardRotation, - id: null, - ); - - if (_interactionOptions.cursorKeyboardRotationOptions.behaviour == - CursorRotationBehaviour.setNorth) _ckrClickDegrees = 0; - } - - void _onPointerSignal(PointerSignalEvent pointerSignal) { - // Handle mouse scroll events if the enableScrollWheel parameter is enabled - if (pointerSignal is PointerScrollEvent && - InteractiveFlag.hasScrollWheelZoom(_interactionOptions.flags) && - pointerSignal.scrollDelta.dy != 0) { - // Prevent scrolling of parent/child widgets simultaneously. See - // [PointerSignalResolver] documentation for more information. - GestureBinding.instance.pointerSignalResolver.register( - pointerSignal, - (pointerSignal) { - pointerSignal as PointerScrollEvent; - final minZoom = _options.minZoom ?? 0.0; - final maxZoom = _options.maxZoom ?? double.infinity; - final newZoom = (_camera.zoom - - pointerSignal.scrollDelta.dy * - _interactionOptions.scrollWheelVelocity) - .clamp(minZoom, maxZoom); - // Calculate offset of mouse cursor from viewport center - final newCenter = _camera.focusedZoomCenter( - pointerSignal.localPosition.toPoint(), - newZoom, - ); - widget.controller.move( - newCenter, - newZoom, - offset: Offset.zero, - hasGesture: true, - source: MapEventSource.scrollWheel, - id: null, - ); - }, - ); - } - } - - int _getMultiFingerGestureFlags(InteractionOptions interactionOptions) { - if (interactionOptions.enableMultiFingerGestureRace) { - if (_gestureWinner == MultiFingerGesture.pinchZoom) { - return interactionOptions.pinchZoomWinGestures; - } else if (_gestureWinner == MultiFingerGesture.rotate) { - return interactionOptions.rotationWinGestures; - } else if (_gestureWinner == MultiFingerGesture.pinchMove) { - return interactionOptions.pinchMoveWinGestures; - } - - return MultiFingerGesture.none; - } else { - return MultiFingerGesture.all; - } - } - - /// Thanks to https://stackoverflow.com/questions/48916517/javascript-click-and-drag-to-rotate - double getCursorRotationDegrees(Offset offset) { - const correctionTerm = 180; // North = cursor - - final size = MediaQuery.sizeOf(context); - return (-math.atan2( - offset.dx - size.width / 2, offset.dy - size.height / 2) * - (180 / math.pi)) + - correctionTerm; - } - - void _closeFlingAnimationController(MapEventSource source) { - _flingAnimationStarted = false; - if (_flingController.isAnimating) { - _flingController.stop(); - - _stopListeningForAnimationInterruptions(); - - widget.controller.flingEnded(source); - } - } - - void _closeDoubleTapController(MapEventSource source) { - if (_doubleTapController.isAnimating) { - _doubleTapController.stop(); - - _stopListeningForAnimationInterruptions(); - - widget.controller.doubleTapZoomEnded(source); - } - } - - void _handleScaleStart(ScaleStartDetails details) { - _dragMode = _pointerCounter == 1; - - final eventSource = _dragMode - ? MapEventSource.dragStart - : MapEventSource.multiFingerGestureStart; - _closeFlingAnimationController(eventSource); - _closeDoubleTapController(eventSource); - - _gestureWinner = MultiFingerGesture.none; - - _mapZoomStart = _camera.zoom; - _mapCenterStart = _camera.center; - _focalStartLocal = _lastFocalLocal = details.localFocalPoint; - _focalStartLatLng = _camera.offsetToCrs(_focalStartLocal); - - _dragStarted = false; - _pinchZoomStarted = false; - _pinchMoveStarted = false; - _rotationStarted = false; - - _lastRotation = 0.0; - _scaleCorrector = 0.0; - _lastScale = 1.0; - } - - void _handleScaleUpdate(ScaleUpdateDetails details) { - if (_tapUpCounter == 1) { - _handleDoubleTapHold(details); - return; - } - - final currentRotation = radianToDeg(details.rotation); - if (_dragMode) { - _handleScaleDragUpdate(details); - } else if (InteractiveFlag.hasMultiFinger(_interactionOptions.flags)) { - _handleScaleMultiFingerUpdate(details, currentRotation); - } - - _lastRotation = currentRotation; - _lastScale = details.scale; - _lastFocalLocal = details.localFocalPoint; - } - - void _handleScaleDragUpdate(ScaleUpdateDetails details) { - if (_ckrTriggered.value) return; - - const eventSource = MapEventSource.onDrag; - - if (InteractiveFlag.hasDrag(_interactionOptions.flags)) { - if (!_dragStarted) { - // We could emit start event at [handleScaleStart], however it is - // possible drag will be disabled during ongoing drag then - // [didUpdateWidget] will emit MapEventMoveEnd and if drag is enabled - // again then this will emit the start event again. - _dragStarted = true; - widget.controller.moveStarted(eventSource); - } - - final localDistanceOffset = _rotateOffset( - _lastFocalLocal - details.localFocalPoint, - ); - - widget.controller.dragUpdated(eventSource, localDistanceOffset); - } - } - - void _handleScaleMultiFingerUpdate( - ScaleUpdateDetails details, - double currentRotation, - ) { - final hasGestureRace = _interactionOptions.enableMultiFingerGestureRace; - - if (hasGestureRace && _gestureWinner == MultiFingerGesture.none) { - final gestureWinner = _determineMultiFingerGestureWinner( - _interactionOptions.rotationThreshold, - currentRotation, - details.scale, - details.localFocalPoint, - ); - if (gestureWinner != null) { - _gestureWinner = gestureWinner; - // note: here we could reset to current values instead of last values - _scaleCorrector = 1.0 - _lastScale; - } - } - - if (!hasGestureRace || _gestureWinner != MultiFingerGesture.none) { - final gestures = _getMultiFingerGestureFlags(_options.interactionOptions); - - final hasPinchZoom = - InteractiveFlag.hasPinchZoom(_interactionOptions.flags) && - MultiFingerGesture.hasPinchZoom(gestures); - final hasPinchMove = - InteractiveFlag.hasPinchMove(_interactionOptions.flags) && - MultiFingerGesture.hasPinchMove(gestures); - if (hasPinchZoom || hasPinchMove) { - _handleScalePinchZoomAndMove(details, hasPinchZoom, hasPinchMove); - } - - if (InteractiveFlag.hasRotate(_interactionOptions.flags) && - MultiFingerGesture.hasRotate(gestures)) { - _handleScalePinchRotate(details, currentRotation); - } - } - } - - void _handleScalePinchZoomAndMove( - ScaleUpdateDetails details, - bool hasPinchZoom, - bool hasPinchMove, - ) { - var newCenter = _camera.center; - var newZoom = _camera.zoom; - - // Handle pinch zoom. - if (hasPinchZoom && details.scale > 0.0) { - newZoom = _getZoomForScale( - _mapZoomStart, - details.scale + _scaleCorrector, - ); - - // Handle starting of pinch zoom. - if (!_pinchZoomStarted && newZoom != _mapZoomStart) { - _pinchZoomStarted = true; - - if (!_pinchMoveStarted) { - // We want to call moveStart only once for a movement so don't call - // it if a pinch move is already underway. - widget.controller.moveStarted(MapEventSource.onMultiFinger); - } - } - } - - // Handle pinch move. - if (hasPinchMove) { - newCenter = _calculatePinchZoomAndMove(details, newZoom); - - if (!_pinchMoveStarted && _lastFocalLocal != details.localFocalPoint) { - _pinchMoveStarted = true; - - if (!_pinchZoomStarted) { - // We want to call moveStart only once for a movement so don't call - // it if a pinch zoom is already underway. - widget.controller.moveStarted(MapEventSource.onMultiFinger); - } - } - } - - if (_pinchZoomStarted || _pinchMoveStarted) { - widget.controller.move( - newCenter, - newZoom, - offset: Offset.zero, - hasGesture: true, - source: MapEventSource.onMultiFinger, - id: null, - ); - } - } - - LatLng _calculatePinchZoomAndMove( - ScaleUpdateDetails details, - double zoomAfterPinchZoom, - ) { - final oldCenterPt = _camera.project(_camera.center, zoomAfterPinchZoom); - final newFocalLatLong = - _camera.offsetToCrs(_focalStartLocal, zoomAfterPinchZoom); - final newFocalPt = _camera.project(newFocalLatLong, zoomAfterPinchZoom); - final oldFocalPt = _camera.project(_focalStartLatLng, zoomAfterPinchZoom); - final zoomDifference = oldFocalPt - newFocalPt; - final moveDifference = _rotateOffset(_focalStartLocal - _lastFocalLocal); - - final newCenterPt = oldCenterPt + zoomDifference + moveDifference.toPoint(); - return _camera.unproject(newCenterPt, zoomAfterPinchZoom); - } - - void _handleScalePinchRotate( - ScaleUpdateDetails details, - double currentRotation, - ) { - if (!_rotationStarted && currentRotation != 0.0) { - _rotationStarted = true; - widget.controller.rotateStarted(MapEventSource.onMultiFinger); - } - - if (_rotationStarted) { - final rotationDiff = currentRotation - _lastRotation; - final oldCenterPt = _camera.project(_camera.center); - final rotationCenter = - _camera.project(_camera.offsetToCrs(_lastFocalLocal)); - final vector = oldCenterPt - rotationCenter; - final rotatedVector = vector.rotate(degToRadian(rotationDiff)); - final newCenter = rotationCenter + rotatedVector; - - widget.controller.moveAndRotate( - _camera.unproject(newCenter), - _camera.zoom, - _camera.rotation + rotationDiff, - offset: Offset.zero, - hasGesture: true, - source: MapEventSource.onMultiFinger, - id: null, - ); - } - } - - int? _determineMultiFingerGestureWinner(double rotationThreshold, - double currentRotation, double scale, Offset focalOffset) { - final int winner; - if (InteractiveFlag.hasPinchZoom(_interactionOptions.flags) && - (_getZoomForScale(_mapZoomStart, scale) - _mapZoomStart).abs() >= - _interactionOptions.pinchZoomThreshold) { - if (_interactionOptions.debugMultiFingerGestureWinner) { - debugPrint('Multi Finger Gesture winner: Pinch Zoom'); - } - winner = MultiFingerGesture.pinchZoom; - } else if (InteractiveFlag.hasRotate(_interactionOptions.flags) && - currentRotation.abs() >= rotationThreshold) { - if (_interactionOptions.debugMultiFingerGestureWinner) { - debugPrint('Multi Finger Gesture winner: Rotate'); - } - winner = MultiFingerGesture.rotate; - } else if (InteractiveFlag.hasPinchMove(_interactionOptions.flags) && - (_focalStartLocal - focalOffset).distance >= - _interactionOptions.pinchMoveThreshold) { - if (_interactionOptions.debugMultiFingerGestureWinner) { - debugPrint('Multi Finger Gesture winner: Pinch Move'); - } - winner = MultiFingerGesture.pinchMove; - } else { - return null; - } - - return winner; - } - - void _handleScaleEnd(ScaleEndDetails details) { - _resetDoubleTapHold(); - - final eventSource = - _dragMode ? MapEventSource.dragEnd : MapEventSource.multiFingerEnd; - - if (_rotationStarted) { - _rotationStarted = false; - widget.controller.rotateEnded(eventSource); - } - - if (_dragStarted || _pinchZoomStarted || _pinchMoveStarted) { - _dragStarted = _pinchZoomStarted = _pinchMoveStarted = false; - widget.controller.moveEnded(eventSource); - } - - // Prevent pan fling if rotation via keyboard/pointer is in progress - if (_ckrTriggered.value) return; - - final hasFling = - InteractiveFlag.hasFlingAnimation(_interactionOptions.flags); - - final magnitude = details.velocity.pixelsPerSecond.distance; - if (magnitude < _kMinFlingVelocity || !hasFling) { - if (hasFling) widget.controller.flingNotStarted(eventSource); - return; - } - - final direction = details.velocity.pixelsPerSecond / magnitude; - final distance = - (Offset.zero & Size(_camera.nonRotatedSize.x, _camera.nonRotatedSize.y)) - .shortestSide; - - final flingOffset = _focalStartLocal - _lastFocalLocal; - _flingAnimation = Tween( - begin: flingOffset, - end: flingOffset - direction * distance, - ).animate(_flingController); - - _flingController - ..value = 0.0 - ..fling( - velocity: magnitude / 1000.0, - springDescription: SpringDescription.withDampingRatio( - mass: 1, - stiffness: 1000, - ratio: 5, - )); - } - - void _handleTap(TapPosition position) { - if (_ckrTriggered.value) return; - - _closeFlingAnimationController(MapEventSource.tap); - _closeDoubleTapController(MapEventSource.tap); - - final relativePosition = position.relative; - if (relativePosition == null) return; - - widget.controller.tapped( - MapEventSource.tap, - position, - _camera.offsetToCrs(relativePosition), - ); - } - - void _handleSecondaryTap(TapPosition position) { - _closeFlingAnimationController(MapEventSource.secondaryTap); - _closeDoubleTapController(MapEventSource.secondaryTap); - - final relativePosition = position.relative; - if (relativePosition == null) return; - - widget.controller.secondaryTapped( - MapEventSource.secondaryTap, - position, - _camera.offsetToCrs(relativePosition), - ); - } - - void _handleLongPress(TapPosition position) { - if (_ckrTriggered.value) return; - - _resetDoubleTapHold(); - - _closeFlingAnimationController(MapEventSource.longPress); - _closeDoubleTapController(MapEventSource.longPress); - - widget.controller.longPressed( - MapEventSource.longPress, - position, - _camera.offsetToCrs(position.relative!), - ); - } - - void _handleDoubleTap(TapPosition tapPosition) { - _resetDoubleTapHold(); - - _closeFlingAnimationController(MapEventSource.doubleTap); - _closeDoubleTapController(MapEventSource.doubleTap); - - if (InteractiveFlag.hasDoubleTapZoom(_interactionOptions.flags)) { - final newZoom = _getZoomForScale(_camera.zoom, 2); - final newCenter = _camera.focusedZoomCenter( - tapPosition.relative!.toPoint(), - newZoom, - ); - _startDoubleTapAnimation(newZoom, newCenter); - } - } - - void _startDoubleTapAnimation(double newZoom, LatLng newCenter) { - _doubleTapZoomAnimation = Tween(begin: _camera.zoom, end: newZoom) - .chain(CurveTween(curve: Curves.linear)) - .animate(_doubleTapController); - _doubleTapCenterAnimation = - LatLngTween(begin: _camera.center, end: newCenter) - .chain(CurveTween(curve: Curves.linear)) - .animate(_doubleTapController); - _doubleTapController.forward(from: 0); - } - - void _doubleTapZoomStatusListener(AnimationStatus status) { - if (status == AnimationStatus.forward) { - widget.controller.doubleTapZoomStarted( - MapEventSource.doubleTapZoomAnimationController, - ); - _startListeningForAnimationInterruptions(); - } else if (status == AnimationStatus.completed) { - _stopListeningForAnimationInterruptions(); - - widget.controller.doubleTapZoomEnded( - MapEventSource.doubleTapZoomAnimationController, - ); - } - } - - void _handleDoubleTapZoomAnimation() { - widget.controller.move( - _doubleTapCenterAnimation.value, - _doubleTapZoomAnimation.value, - offset: Offset.zero, - hasGesture: true, - source: MapEventSource.doubleTapZoomAnimationController, - id: null, - ); - } - - void _handleOnTapUp(TapUpDetails details) { - _doubleTapHoldMaxDelay?.cancel(); - - if (++_tapUpCounter == 1) { - _doubleTapHoldMaxDelay = Timer(doubleTapDelay, _resetDoubleTapHold); - } - } - - void _handleDoubleTapHold(ScaleUpdateDetails details) { - _doubleTapHoldMaxDelay?.cancel(); - - final flags = _interactionOptions.flags; - if (InteractiveFlag.hasDoubleTapDragZoom(flags)) { - final verticalOffset = (_focalStartLocal - details.localFocalPoint).dy; - final newZoom = _mapZoomStart - verticalOffset / 360 * _camera.zoom; - - final min = _options.minZoom ?? 0.0; - final max = _options.maxZoom ?? double.infinity; - final actualZoom = math.max(min, math.min(max, newZoom)); - - widget.controller.move( - _camera.center, - actualZoom, - offset: Offset.zero, - hasGesture: true, - source: MapEventSource.doubleTapHold, - id: null, - ); - } - } - - void _handleFlingAnimation() { - if (!_flingAnimationStarted) { - _flingAnimationStarted = true; - widget.controller.flingStarted(MapEventSource.flingAnimationController); - _startListeningForAnimationInterruptions(); - } - - final newCenterPoint = _camera.project(_mapCenterStart) + - _flingAnimation.value.toPoint().rotate(_camera.rotationRad); - final newCenter = _camera.unproject(newCenterPoint); - - widget.controller.move( - newCenter, - _camera.zoom, - offset: Offset.zero, - hasGesture: true, - source: MapEventSource.flingAnimationController, - id: null, - ); - } - - void _resetDoubleTapHold() { - _doubleTapHoldMaxDelay?.cancel(); - _tapUpCounter = 0; - } - - void _flingAnimationStatusListener(AnimationStatus status) { - if (status == AnimationStatus.completed) { - _flingAnimationStarted = false; - _stopListeningForAnimationInterruptions(); - widget.controller.flingEnded(MapEventSource.flingAnimationController); - } - } - - void _startListeningForAnimationInterruptions() { - _isListeningForInterruptions = true; - } - - void _stopListeningForAnimationInterruptions() { - _isListeningForInterruptions = false; - } - - void interruptAnimatedMovement(MapEvent event) { - if (_isListeningForInterruptions) { - _closeDoubleTapController(event.source); - _closeFlingAnimationController(event.source); - } - } - - double _getZoomForScale(double startZoom, double scale) { - final resultZoom = - scale == 1.0 ? startZoom : startZoom + math.log(scale) / math.ln2; - return _camera.clampZoom(resultZoom); - } - - Offset _rotateOffset(Offset offset) { - final radians = _camera.rotationRad; - if (radians != 0.0) { - final cos = math.cos(radians); - final sin = math.sin(radians); - final nx = (cos * offset.dx) + (sin * offset.dy); - final ny = (cos * offset.dy) - (sin * offset.dx); - - return Offset(nx, ny); - } - - return offset; - } -} diff --git a/lib/src/gestures/map_interactive_viewer.dart b/lib/src/gestures/map_interactive_viewer.dart new file mode 100644 index 000000000..e1bac16f7 --- /dev/null +++ b/lib/src/gestures/map_interactive_viewer.dart @@ -0,0 +1,287 @@ +import 'dart:math' as math; + +import 'package:flutter/gestures.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_map/flutter_map.dart'; +import 'package:flutter_map/src/map/controller/internal.dart'; + +typedef ChildBuilder = Widget Function( + BuildContext context, + MapOptions options, + MapCamera camera, +); + +class MapInteractiveViewer extends StatefulWidget { + final ChildBuilder builder; + final FlutterMapInternalController controller; + + const MapInteractiveViewer({ + super.key, + required this.builder, + required this.controller, + }); + + @override + State createState() => MapInteractiveViewerState(); +} + +class MapInteractiveViewerState extends State + with TickerProviderStateMixin { + TapDownDetails? _tapDetails; + TapDownDetails? _secondaryTapDetails; + TapDownDetails? _doubleTapDetails; + TapDownDetails? _tertiaryTapDetails; + + Offset? _lastFocalLocal; + + MapCamera get _camera => widget.controller.camera; + + MapOptions get _options => widget.controller.options; + + InteractionOptions get _interactionOptions => _options.interactionOptions; + + bool _gestureEnabled(int flag) { + if (flag == InteractiveFlag.scrollWheelZoom && + // ignore: deprecated_member_use_from_same_package + _interactionOptions.enableScrollWheel) { + return true; + } + return InteractiveFlag.hasFlag( + _options.interactionOptions.flags, + flag, + ); + } + + @override + void initState() { + super.initState(); + widget.controller.addListener(reload); + } + + @override + void dispose() { + widget.controller.removeListener(reload); + super.dispose(); + } + + void reload() { + if (mounted) setState(() {}); + } + + @override + Widget build(BuildContext context) { + return Listener( + onPointerSignal: _onPointerSignal, + child: GestureDetector( + onTapDown: (details) => _tapDetails = details, + onTapCancel: () => _tapDetails = null, + onTap: _onTap, + + onLongPressStart: _onLongPressStart, + + onSecondaryTapDown: (details) => _secondaryTapDetails = details, + onSecondaryTapCancel: () => _secondaryTapDetails = null, + onSecondaryTap: _onSecondaryTap, + + onDoubleTapDown: (details) => _doubleTapDetails = details, + onDoubleTapCancel: () => _doubleTapDetails = null, + onDoubleTap: _onDoubleTap, + + onTertiaryTapDown: (details) => _tertiaryTapDetails = details, + onTertiaryTapCancel: () => _tertiaryTapDetails = null, + onTertiaryTapUp: _onTertiaryTapUp, + + onTertiaryLongPressStart: _onTertiaryLongPressStart, + + // pan and scale, scale is a superset of the pan gesture + onScaleStart: _onScalePanStart, + onScaleUpdate: _onScalePanUpdate, + onScaleEnd: _onScalePanEnd, + + child: widget.builder( + context, + widget.controller.options, + _camera, + ), + ), + ); + } + + /// A tap with a primary button has occurred. + /// This triggers when the tap gesture wins. + void _onTap() { + print('[MapInteractiveViewer] tap'); + if (_tapDetails == null) return; + final details = _tapDetails!; + _tapDetails = null; + widget.controller.tapped( + MapEventSource.tap, + TapPosition(details.globalPosition, details.localPosition), + _camera.offsetToCrs(details.localPosition), + ); + } + + /// Called when a long press gesture with a primary button has been + /// recognized. A pointer has remained in contact with the screen at the + /// same location for a long period of time. + void _onLongPressStart(LongPressStartDetails details) { + print('[MapInteractiveViewer] long press'); + widget.controller.longPressed( + MapEventSource.longPress, + TapPosition(details.globalPosition, details.localPosition), + _camera.offsetToCrs(details.localPosition), + ); + } + + /// A tap with a secondary button has occurred. + /// This triggers when the tap gesture wins. + void _onSecondaryTap() { + print('[MapInteractiveViewer] secondary tap'); + if (_secondaryTapDetails == null) return; + final details = _secondaryTapDetails!; + _secondaryTapDetails = null; + widget.controller.secondaryTapped( + MapEventSource.secondaryTap, + TapPosition(details.globalPosition, details.localPosition), + _camera.offsetToCrs(details.localPosition), + ); + } + + /// A double tap gesture tap has been registered + void _onDoubleTap() { + print('[MapInteractiveViewer] double tap'); + if (_doubleTapDetails == null) return; + final details = _doubleTapDetails!; + _doubleTapDetails = null; + if (!_gestureEnabled(InteractiveFlag.doubleTapZoom)) return; + + // start double tap animation + //widget.controller.doubleTapZoomStarted(MapEventSource.doubleTap); + // TODO + final newZoom = _getZoomForScale(_camera.zoom, 2); + final newCenter = _camera.focusedZoomCenter( + details.localPosition.toPoint(), + newZoom, + ); + widget.controller.move( + newCenter, + newZoom, + offset: details.localPosition, + hasGesture: true, + source: MapEventSource.doubleTap, + id: null, + ); + } + + void _onTertiaryTapUp(TapUpDetails _) { + print('[MapInteractiveViewer] tertiary tap'); + if (_tertiaryTapDetails == null) return; + final details = _tertiaryTapDetails!; + _tertiaryTapDetails = null; + // TODO + } + + void _onTertiaryLongPressStart(LongPressStartDetails details) { + print('[MapInteractiveViewer] tertiary long press'); + // TODO + } + + void _onScalePanStart(ScaleStartDetails details) { + print('[MapInteractiveViewer] scale pan start'); + if (!_gestureEnabled(InteractiveFlag.drag)) return; + // TODO + _lastFocalLocal = details.localFocalPoint; + widget.controller.moveStarted(MapEventSource.dragStart); + } + + void _onScalePanUpdate(ScaleUpdateDetails details) { + print( + '[MapInteractiveViewer] scale pan update, ${details.localFocalPoint}', + ); + if (!_gestureEnabled(InteractiveFlag.drag)) return; + // TODO + + widget.controller.dragUpdated( + MapEventSource.onDrag, + _rotateOffset( + _lastFocalLocal! - details.localFocalPoint, + ), + ); + _lastFocalLocal = details.localFocalPoint; + } + + void _onScalePanEnd(ScaleEndDetails details) { + print('[MapInteractiveViewer] scale pan end'); + if (!_gestureEnabled(InteractiveFlag.drag)) return; + // TODO + } + + /// Handles mouse scroll events if the enableScrollWheel parameter is enabled + void _onPointerSignal(PointerSignalEvent event) { + print('[MapInteractiveViewer] on pointer signal'); + if (event is! PointerScrollEvent || + !_gestureEnabled(InteractiveFlag.scrollWheelZoom) || + event.scrollDelta.dy == 0) return; + + // Prevent scrolling of parent/child widgets simultaneously. + // See [PointerSignalResolver] documentation for more information. + GestureBinding.instance.pointerSignalResolver.register(event, (event) { + event as PointerScrollEvent; + final minZoom = _options.minZoom ?? 0.0; + final maxZoom = _options.maxZoom ?? double.infinity; + final newZoom = (_camera.zoom - + event.scrollDelta.dy * _interactionOptions.scrollWheelVelocity) + .clamp(minZoom, maxZoom); + // Calculate offset of mouse cursor from viewport center + final newCenter = _camera.focusedZoomCenter( + event.localPosition.toPoint(), + newZoom, + ); + widget.controller.move( + newCenter, + newZoom, + offset: Offset.zero, + hasGesture: true, + source: MapEventSource.scrollWheel, + id: null, + ); + }); + } + + /// get the calculated zoom level for a given scaling, relative for the + /// startZoomLevel + double _getZoomForScale(double startZoom, double scale) { + if (scale == 1) { + return _camera.clampZoom(startZoom); + } + return _camera.clampZoom(startZoom + math.log(scale) / math.ln2); + } + + /// Used by the internal map controller to update interaction gestures + void updateGestures( + InteractionOptions oldOptions, + InteractionOptions newOptions, + ) { + print('[MapInteractiveViewer] updateGestures'); + // TODO + } + + /// Used by the internal map controller + void interruptAnimatedMovement(MapEventMove event) { + print('[MapInteractiveViewer] interruptAnimatedMovement'); + // TODO + } + + /// Return a rotated Offset + Offset _rotateOffset(Offset offset) { + final radians = _camera.rotationRad; + if (radians == 0) return offset; + + final cos = math.cos(radians); + final sin = math.sin(radians); + final nx = (cos * offset.dx) + (sin * offset.dy); + final ny = (cos * offset.dy) - (sin * offset.dx); + + return Offset(nx, ny); + } +} diff --git a/lib/src/map/controller/internal.dart b/lib/src/map/controller/internal.dart index 6746c5ab8..922b998ae 100644 --- a/lib/src/map/controller/internal.dart +++ b/lib/src/map/controller/internal.dart @@ -2,8 +2,8 @@ import 'dart:math'; import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; -import 'package:flutter_map/src/gestures/flutter_map_interactive_viewer.dart'; import 'package:flutter_map/src/gestures/map_events.dart'; +import 'package:flutter_map/src/gestures/map_interactive_viewer.dart'; import 'package:flutter_map/src/gestures/positioned_tap_detector_2.dart'; import 'package:flutter_map/src/map/camera/camera.dart'; import 'package:flutter_map/src/map/camera/camera_fit.dart'; @@ -17,7 +17,7 @@ import 'package:latlong2/latlong.dart'; /// This controller is for internal use. All updates to the state should be done /// by calling methods of this class to ensure consistency. class FlutterMapInternalController extends ValueNotifier<_InternalState> { - late final FlutterMapInteractiveViewerState _interactiveViewerState; + late final MapInteractiveViewerState _interactiveViewerState; late MapControllerImpl _mapControllerImpl; FlutterMapInternalController(MapOptions options) @@ -29,9 +29,9 @@ class FlutterMapInternalController extends ValueNotifier<_InternalState> { ); /// Link the viewer state with the controller. This should be done once when - /// the FlutterMapInteractiveViewerState is initialized. + /// the MapInteractiveViewerState is initialized. set interactiveViewerState( - FlutterMapInteractiveViewerState interactiveViewerState, + MapInteractiveViewerState interactiveViewerState, ) => _interactiveViewerState = interactiveViewerState; diff --git a/lib/src/map/widget.dart b/lib/src/map/widget.dart index 622a4e5e2..b9528c3cd 100644 --- a/lib/src/map/widget.dart +++ b/lib/src/map/widget.dart @@ -2,8 +2,8 @@ import 'dart:math'; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; -import 'package:flutter_map/src/gestures/flutter_map_interactive_viewer.dart'; import 'package:flutter_map/src/gestures/map_events.dart'; +import 'package:flutter_map/src/gestures/map_interactive_viewer.dart'; import 'package:flutter_map/src/layer/general/mobile_layer_transformer.dart'; import 'package:flutter_map/src/layer/general/translucent_pointer.dart'; import 'package:flutter_map/src/map/controller/impl.dart'; @@ -133,7 +133,7 @@ class _FlutterMapStateContainer extends State builder: (context, constraints) { _updateAndEmitSizeIfConstraintsChanged(constraints); - return FlutterMapInteractiveViewer( + return MapInteractiveViewer( controller: _flutterMapInternalController, builder: (context, options, camera) { return FlutterMapInheritedModel( From c409de6815c306833c6c56f0cd8c6d69c54e980d Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Mon, 20 Nov 2023 00:21:23 +0100 Subject: [PATCH 002/102] doubleClick, rename multiFingerGestureStart --- lib/src/gestures/map_events.dart | 2 +- lib/src/gestures/map_interactive_viewer.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/gestures/map_events.dart b/lib/src/gestures/map_events.dart index 03ce4eba2..ecf4fc9cb 100644 --- a/lib/src/gestures/map_events.dart +++ b/lib/src/gestures/map_events.dart @@ -14,7 +14,7 @@ enum MapEventSource { dragStart, onDrag, dragEnd, - multiFingerGestureStart, + multiFingerStart, onMultiFinger, multiFingerEnd, flingAnimationController, diff --git a/lib/src/gestures/map_interactive_viewer.dart b/lib/src/gestures/map_interactive_viewer.dart index e1bac16f7..07ce16be3 100644 --- a/lib/src/gestures/map_interactive_viewer.dart +++ b/lib/src/gestures/map_interactive_viewer.dart @@ -166,7 +166,7 @@ class MapInteractiveViewerState extends State widget.controller.move( newCenter, newZoom, - offset: details.localPosition, + offset: Offset.zero, hasGesture: true, source: MapEventSource.doubleTap, id: null, From 76b2e8f77c879bf1491728854b37785045aeb49f Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Tue, 21 Nov 2023 15:25:03 +0100 Subject: [PATCH 003/102] remove PositionedTapDetector2 --- lib/flutter_map.dart | 2 +- .../gestures/positioned_tap_detector_2.dart | 231 ------------------ lib/src/gestures/tap_position.dart | 21 ++ lib/src/map/controller/internal.dart | 10 +- lib/src/map/options/options.dart | 10 +- .../positioned_tap_detector_2_test.dart | 80 ------ 6 files changed, 24 insertions(+), 330 deletions(-) delete mode 100644 lib/src/gestures/positioned_tap_detector_2.dart create mode 100644 lib/src/gestures/tap_position.dart delete mode 100644 test/misc/private/positioned_tap_detector_2_test.dart diff --git a/lib/flutter_map.dart b/lib/flutter_map.dart index 907eee917..9531c39d8 100644 --- a/lib/flutter_map.dart +++ b/lib/flutter_map.dart @@ -22,7 +22,7 @@ export 'package:flutter_map/src/gestures/interactive_flag.dart'; export 'package:flutter_map/src/gestures/latlng_tween.dart'; export 'package:flutter_map/src/gestures/map_events.dart'; export 'package:flutter_map/src/gestures/multi_finger_gesture.dart'; -export 'package:flutter_map/src/gestures/positioned_tap_detector_2.dart'; +export 'package:flutter_map/src/gestures/tap_position.dart'; export 'package:flutter_map/src/layer/attribution_layer/rich/animation.dart'; export 'package:flutter_map/src/layer/attribution_layer/rich/source.dart'; export 'package:flutter_map/src/layer/attribution_layer/rich/widget.dart'; diff --git a/lib/src/gestures/positioned_tap_detector_2.dart b/lib/src/gestures/positioned_tap_detector_2.dart deleted file mode 100644 index ab1a85d84..000000000 --- a/lib/src/gestures/positioned_tap_detector_2.dart +++ /dev/null @@ -1,231 +0,0 @@ -// ////////////////////////////////////////////////////////////////// -// /// Based on the work by Ali Raghebi /// -// /// Now maintained here due to abandonment /// -// /// https://github.com/arsamme/flutter-positioned-tap-detector /// -// ////////////////////////////////////////////////////////////////// - -import 'dart:async'; -import 'dart:math'; - -import 'package:flutter/material.dart'; - -typedef TapPositionCallback = void Function(TapPosition position); - -@immutable -class PositionedTapDetector2 extends StatefulWidget { - const PositionedTapDetector2({ - super.key, - this.child, - this.onTap, - this.onDoubleTap, - this.onSecondaryTap, - this.onLongPress, - Duration? doubleTapDelay, - this.behavior, - this.controller, - }) : doubleTapDelay = doubleTapDelay ?? _defaultDelay; - - static const _defaultDelay = Duration(milliseconds: 250); - static const _doubleTapMaxOffset = 48.0; - - final Widget? child; - final HitTestBehavior? behavior; - final TapPositionCallback? onTap; - final TapPositionCallback? onSecondaryTap; - final TapPositionCallback? onDoubleTap; - final TapPositionCallback? onLongPress; - final Duration doubleTapDelay; - final PositionedTapController? controller; - - @override - State createState() => _TapPositionDetectorState(); -} - -class _TapPositionDetectorState extends State { - final _controller = StreamController(); - - late final _stream = _controller.stream.asBroadcastStream(); - - Sink get _sink => _controller.sink; - late StreamSubscription _streamSub; - - PositionedTapController? _tapController; - TapDownDetails? _pendingTap; - TapDownDetails? _firstTap; - - @override - void initState() { - _updateController(); - _startStream(); - super.initState(); - } - - @override - void didUpdateWidget(PositionedTapDetector2 oldWidget) { - super.didUpdateWidget(oldWidget); - if (widget.controller != oldWidget.controller) { - _updateController(); - } - if (widget.doubleTapDelay != oldWidget.doubleTapDelay) { - _streamSub.cancel().then(_startStream); - } - } - - void _startStream([dynamic d]) { - _streamSub = _stream - .timeout(widget.doubleTapDelay) - .handleError(_onTimeout, test: (e) => e is TimeoutException) - .listen(_onTapConfirmed); - } - - void _updateController() { - _tapController?._state = null; - if (widget.controller != null) { - widget.controller!._state = this; - _tapController = widget.controller; - } - } - - void _onTimeout(dynamic error) { - final firstTap = _firstTap; - if (firstTap != null && _pendingTap == null) { - _postCallback(firstTap, widget.onTap); - } - } - - void _onTapConfirmed(TapDownDetails details) { - if (_firstTap == null) { - _firstTap = details; - } else { - _handleSecondTap(details); - } - } - - void _handleSecondTap(TapDownDetails secondTap) { - final firstTap = _firstTap; - - if (firstTap == null) return; - - if (_isDoubleTap(firstTap, secondTap)) { - _postCallback(secondTap, widget.onDoubleTap); - } else { - _postCallback(firstTap, widget.onTap); - _postCallback(secondTap, widget.onTap); - } - } - - bool _isDoubleTap(TapDownDetails d1, TapDownDetails d2) { - final dx = d1.globalPosition.dx - d2.globalPosition.dx; - final dy = d1.globalPosition.dy - d2.globalPosition.dy; - return sqrt(dx * dx + dy * dy) <= - PositionedTapDetector2._doubleTapMaxOffset; - } - - void _onTapDownEvent(TapDownDetails details) { - _pendingTap = details; - } - - void _onTapEvent() { - final pending = _pendingTap; - if (pending == null) return; - - if (widget.onDoubleTap == null) { - _postCallback(pending, widget.onTap); - } else { - _sink.add(pending); - } - - _pendingTap = null; - } - - void _onSecondaryTapEvent() { - final pending = _pendingTap; - if (pending == null) return; - - _postCallback(pending, widget.onSecondaryTap); - _pendingTap = null; - } - - void _onLongPressEvent() { - final pending = _pendingTap; - if (pending != null) { - if (_firstTap == null) { - _postCallback(pending, widget.onLongPress); - } else { - _sink.add(pending); - _pendingTap = null; - } - } - } - - Future _postCallback( - TapDownDetails details, - TapPositionCallback? callback, - ) async { - _firstTap = null; - if (callback != null) { - callback(TapPosition(details.globalPosition, details.localPosition)); - } - } - - @override - void dispose() { - _controller.close(); - _streamSub.cancel(); - _tapController?._state = null; - super.dispose(); - } - - @override - Widget build(BuildContext context) { - if (widget.controller != null) { - if (widget.child != null) { - return widget.child!; - } else { - return Container(); - } - } - return GestureDetector( - behavior: widget.behavior ?? - (widget.child == null - ? HitTestBehavior.translucent - : HitTestBehavior.deferToChild), - onTap: _onTapEvent, - onLongPress: _onLongPressEvent, - onTapDown: _onTapDownEvent, - onSecondaryTapDown: _onTapDownEvent, - onSecondaryTap: _onSecondaryTapEvent, - child: widget.child, - ); - } -} - -class PositionedTapController { - _TapPositionDetectorState? _state; - - void onTap() => _state?._onTapEvent(); - - void onSecondaryTap() => _state?._onSecondaryTapEvent(); - - void onLongPress() => _state?._onLongPressEvent(); - - void onTapDown(TapDownDetails details) => _state?._onTapDownEvent(details); -} - -@immutable -class TapPosition { - const TapPosition(this.global, this.relative); - - final Offset global; - final Offset? relative; - - @override - bool operator ==(dynamic other) { - if (other is! TapPosition) return false; - final typedOther = other; - return global == typedOther.global && relative == other.relative; - } - - @override - int get hashCode => Object.hash(global, relative); -} diff --git a/lib/src/gestures/tap_position.dart b/lib/src/gestures/tap_position.dart new file mode 100644 index 000000000..08cd01723 --- /dev/null +++ b/lib/src/gestures/tap_position.dart @@ -0,0 +1,21 @@ +import 'dart:ui'; + +import 'package:meta/meta.dart'; + +@immutable +class TapPosition { + const TapPosition(this.global, this.relative); + + final Offset global; + final Offset? relative; + + @override + bool operator ==(dynamic other) { + if (other is! TapPosition) return false; + final typedOther = other; + return global == typedOther.global && relative == other.relative; + } + + @override + int get hashCode => Object.hash(global, relative); +} diff --git a/lib/src/map/controller/internal.dart b/lib/src/map/controller/internal.dart index 922b998ae..e1ddf5928 100644 --- a/lib/src/map/controller/internal.dart +++ b/lib/src/map/controller/internal.dart @@ -2,16 +2,8 @@ import 'dart:math'; import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; -import 'package:flutter_map/src/gestures/map_events.dart'; +import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_map/src/gestures/map_interactive_viewer.dart'; -import 'package:flutter_map/src/gestures/positioned_tap_detector_2.dart'; -import 'package:flutter_map/src/map/camera/camera.dart'; -import 'package:flutter_map/src/map/camera/camera_fit.dart'; -import 'package:flutter_map/src/map/controller/impl.dart'; -import 'package:flutter_map/src/map/options/options.dart'; -import 'package:flutter_map/src/misc/move_and_rotate_result.dart'; -import 'package:flutter_map/src/misc/point_extensions.dart'; -import 'package:flutter_map/src/misc/position.dart'; import 'package:latlong2/latlong.dart'; /// This controller is for internal use. All updates to the state should be done diff --git a/lib/src/map/options/options.dart b/lib/src/map/options/options.dart index 900e1b8f0..9cb028ee6 100644 --- a/lib/src/map/options/options.dart +++ b/lib/src/map/options/options.dart @@ -1,16 +1,8 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; -import 'package:flutter_map/src/geo/crs.dart'; -import 'package:flutter_map/src/gestures/map_events.dart'; -import 'package:flutter_map/src/gestures/positioned_tap_detector_2.dart'; -import 'package:flutter_map/src/layer/general/translucent_pointer.dart'; -import 'package:flutter_map/src/map/camera/camera_constraint.dart'; -import 'package:flutter_map/src/map/camera/camera_fit.dart'; +import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_map/src/map/inherited_model.dart'; -import 'package:flutter_map/src/map/options/interaction.dart'; -import 'package:flutter_map/src/map/widget.dart'; -import 'package:flutter_map/src/misc/position.dart'; import 'package:latlong2/latlong.dart'; typedef MapEventCallback = void Function(MapEvent); diff --git a/test/misc/private/positioned_tap_detector_2_test.dart b/test/misc/private/positioned_tap_detector_2_test.dart deleted file mode 100644 index e46b86892..000000000 --- a/test/misc/private/positioned_tap_detector_2_test.dart +++ /dev/null @@ -1,80 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_map/src/gestures/positioned_tap_detector_2.dart'; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - testWidgets('Test tap is detected where expected.', (tester) async { - const screenSize = Size(400, 400); - await tester.binding.setSurfaceSize(screenSize); - - // PositionedTapDetector2 fills the full screen. - Offset? lastTap; - final widget = PositionedTapDetector2( - onTap: (position) => lastTap = position.relative, - child: SizedBox( - width: screenSize.width, - height: screenSize.height, - child: Container( - color: Colors.red, - ), - ), - ); - - await tester.pumpWidget(widget); - - Future tap(Offset pos) async { - lastTap = null; - await tester.tapAt(pos); - expect(lastTap, pos); - } - - // Tap top left - await tap(Offset.zero); - // Tap middle - await tap(Offset(screenSize.width / 2, screenSize.height / 2)); - // Tap bottom right - await tap(Offset(screenSize.width - 1, screenSize.height - 1)); - }); - - testWidgets('Test tap is detected where expected with scale and offset.', - (tester) async { - const screenSize = Size(400, 400); - await tester.binding.setSurfaceSize(screenSize); - - Offset? lastTap; - // The Transform.scale fills the screen, but the PositionedTapDetector2 - // occupies the center, with height of 200 (0.5 * 400) and width 400. - final widget = Transform.scale( - scaleY: 0.5, - child: PositionedTapDetector2( - onTap: (position) => lastTap = position.relative, - child: SizedBox( - width: screenSize.width, - height: screenSize.height, - child: Container( - color: Colors.red, - ), - ), - ), - ); - - await tester.pumpWidget(widget); - - // On the screen the PositionedTapDetector2 is actually 400x200, but the - // widget thinks its 400x400. - expect(screenSize, tester.getSize(find.byType(SizedBox))); - - Future tap(Offset pos, Offset expected) async { - lastTap = null; - await tester.tapAt(pos); - expect(lastTap, expected); - } - - // Tap top left of PositionedTapDetector2 which should be 0,0. - await tap(const Offset(0, 100), Offset.zero); - - // Tap bottom right of PositionedTapDetector2 - await tap(const Offset(400 - 1, 300 - 0.5), - Offset(screenSize.width - 1, screenSize.height - 1)); - }); -} From 23588857cbbdbc974c3153cf675c2f1985840c62 Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Tue, 21 Nov 2023 18:12:25 +0100 Subject: [PATCH 004/102] create gesture classes --- lib/src/gestures/gestures.dart | 242 ++++++++++++++++++ lib/src/gestures/map_interactive_viewer.dart | 252 ++++--------------- lib/src/map/controller/internal.dart | 47 ++-- 3 files changed, 307 insertions(+), 234 deletions(-) create mode 100644 lib/src/gestures/gestures.dart diff --git a/lib/src/gestures/gestures.dart b/lib/src/gestures/gestures.dart new file mode 100644 index 000000000..63c990b68 --- /dev/null +++ b/lib/src/gestures/gestures.dart @@ -0,0 +1,242 @@ +import 'dart:math' as math; + +import 'package:flutter/gestures.dart'; +import 'package:flutter_map/flutter_map.dart'; +import 'package:flutter_map/src/map/controller/internal.dart'; + +abstract class Gesture { + final FlutterMapInternalController controller; + + Gesture({required this.controller}); + + MapCamera get _camera => controller.camera; + + MapOptions get _options => controller.options; +} + +abstract class DelayedGesture extends Gesture { + TapDownDetails? details; + + DelayedGesture({required super.controller}); + + void setDetails(TapDownDetails newDetails) => details = newDetails; + + void reset() => details = null; +} + +class TapGesture extends DelayedGesture { + TapGesture({required super.controller}); + + /// A tap with a primary button has occurred. + /// This triggers when the tap gesture wins. + void submit() { + if (details == null) return; + + final point = _camera.offsetToCrs(details!.localPosition); + final tapPosition = TapPosition( + details!.globalPosition, + details!.localPosition, + ); + _options.onTap?.call(tapPosition, point); + controller.emitMapEvent( + MapEventTap( + tapPosition: point, + camera: _camera, + source: MapEventSource.tap, + ), + ); + + reset(); + } +} + +class LongPressGesture extends Gesture { + LongPressGesture({required super.controller}); + + /// Called when a long press gesture with a primary button has been + /// recognized. A pointer has remained in contact with the screen at the + /// same location for a long period of time. + void submit(LongPressStartDetails details) { + controller.longPressed( + MapEventSource.longPress, + TapPosition(details.globalPosition, details.localPosition), + _camera.offsetToCrs(details.localPosition), + ); + } +} + +class SecondaryTapGesture extends DelayedGesture { + SecondaryTapGesture({required super.controller}); + + /// A tap with a secondary button has occurred. + /// This triggers when the tap gesture wins. + void submit() { + if (details == null) return; + + controller.secondaryTapped( + MapEventSource.secondaryTap, + TapPosition(details!.globalPosition, details!.localPosition), + _camera.offsetToCrs(details!.localPosition), + ); + + reset(); + } +} + +class DoubleTapGesture extends DelayedGesture { + DoubleTapGesture({required super.controller}); + + /// A double tap gesture tap has been registered + void submit() { + if (details == null) return; + + // start double tap animation + // TODO animate movement + //controller.doubleTapZoomStarted(MapEventSource.doubleTap); + final newZoom = _getZoomForScale(_camera.zoom, 2); + final newCenter = _camera.focusedZoomCenter( + details!.localPosition.toPoint(), + newZoom, + ); + controller.move( + newCenter, + newZoom, + offset: Offset.zero, + hasGesture: true, + source: MapEventSource.doubleTap, + id: null, + ); + + reset(); + } + + /// get the calculated zoom level for a given scaling, relative for the + /// startZoomLevel + double _getZoomForScale(double startZoom, double scale) { + if (scale == 1) { + return _camera.clampZoom(startZoom); + } + return _camera.clampZoom(startZoom + math.log(scale) / math.ln2); + } +} + +class TertiaryTapGesture extends DelayedGesture { + TertiaryTapGesture({required super.controller}); + + /// A tertiary tap gesture has happen (e.g. click on the mouse scroll wheel) + void submit(TapUpDetails _) { + if (details == null) return; + + // TODO implement + + reset(); + } +} + +class TertiaryLongPressGesture extends DelayedGesture { + TertiaryLongPressGesture({required super.controller}); + + /// A long press on the tertiary button has happen (e.g. click and hold on + /// the mouse scroll wheel) + void submit(LongPressStartDetails details) { + // TODO implement + + reset(); + } +} + +class ScrollWheelZoomGesture extends Gesture { + ScrollWheelZoomGesture({required super.controller}); + + /// Handles mouse scroll events + void submit(PointerScrollEvent details) { + if (details.scrollDelta.dy == 0) return; + + // Prevent scrolling of parent/child widgets simultaneously. + // See [PointerSignalResolver] documentation for more information. + GestureBinding.instance.pointerSignalResolver.register(details, (details) { + details as PointerScrollEvent; + final minZoom = _options.minZoom ?? 0.0; + final maxZoom = _options.maxZoom ?? double.infinity; + final velocity = _options.interactionOptions.scrollWheelVelocity; + final newZoom = (_camera.zoom - details.scrollDelta.dy * velocity) + .clamp(minZoom, maxZoom); + // Calculate offset of mouse cursor from viewport center + final newCenter = _camera.focusedZoomCenter( + details.localPosition.toPoint(), + newZoom, + ); + controller.move( + newCenter, + newZoom, + offset: Offset.zero, + hasGesture: true, + source: MapEventSource.scrollWheel, + id: null, + ); + }); + } +} + +/// A gesture with multiple inputs like zooming with two fingers +class MultiInputGesture extends Gesture { + Offset? _lastLocalFocal; + double? _lastScale; + + MultiInputGesture({required super.controller}); + + /// Initialize gesture, called when gesture has started + void start(ScaleStartDetails details) { + _lastLocalFocal = details.localFocalPoint; + controller.moveStarted(MapEventSource.multiFingerStart); + } + + /// Called multiple times to handle updates to the gesture + void update(ScaleUpdateDetails details) { + // TODO implement + final offset = _rotateOffset(_lastLocalFocal! - details.localFocalPoint); + final oldCenterPt = _camera.project(_camera.center); + final newCenterPt = oldCenterPt + offset.toPoint(); + final newCenter = _camera.unproject(newCenterPt); + + double newZoom = _camera.zoom; + if (_lastScale == null || (_lastScale! - details.scale).abs() > 0.05) { + newZoom *= details.scale; + } + + controller.move( + newCenter, + newZoom, + offset: Offset.zero, + hasGesture: true, + source: MapEventSource.onMultiFinger, + id: null, + ); + + _lastScale = details.scale; + _lastLocalFocal = details.localFocalPoint; + } + + /// gesture has ended, clean up + void end(ScaleEndDetails details) { + _lastScale = null; + _lastLocalFocal = null; + } + + /// Return a rotated Offset + Offset _rotateOffset(Offset offset) { + final radians = _camera.rotationRad; + if (radians == 0) return offset; + + final cos = math.cos(radians); + final sin = math.sin(radians); + final nx = (cos * offset.dx) + (sin * offset.dy); + final ny = (cos * offset.dy) - (sin * offset.dx); + + return Offset(nx, ny); + } +} + +class DragGesture extends Gesture { + DragGesture({required super.controller}); +} diff --git a/lib/src/gestures/map_interactive_viewer.dart b/lib/src/gestures/map_interactive_viewer.dart index 07ce16be3..0ec862dd9 100644 --- a/lib/src/gestures/map_interactive_viewer.dart +++ b/lib/src/gestures/map_interactive_viewer.dart @@ -1,8 +1,7 @@ -import 'dart:math' as math; - import 'package:flutter/gestures.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_map/flutter_map.dart'; +import 'package:flutter_map/src/gestures/gestures.dart'; import 'package:flutter_map/src/map/controller/internal.dart'; typedef ChildBuilder = Widget Function( @@ -27,12 +26,15 @@ class MapInteractiveViewer extends StatefulWidget { class MapInteractiveViewerState extends State with TickerProviderStateMixin { - TapDownDetails? _tapDetails; - TapDownDetails? _secondaryTapDetails; - TapDownDetails? _doubleTapDetails; - TapDownDetails? _tertiaryTapDetails; - - Offset? _lastFocalLocal; + TapGesture? _tap; + SecondaryTapGesture? _secondaryTap; + LongPressGesture? _longPress; + DoubleTapGesture? _doubleTap; + TertiaryTapGesture? _tertiaryTap; + TertiaryLongPressGesture? _tertiaryLongPress; + ScrollWheelZoomGesture? _scrollWheelZoom; + MultiInputGesture? _multiInput; + DragGesture? _drag; MapCamera get _camera => widget.controller.camera; @@ -46,16 +48,25 @@ class MapInteractiveViewerState extends State _interactionOptions.enableScrollWheel) { return true; } - return InteractiveFlag.hasFlag( - _options.interactionOptions.flags, - flag, - ); + return InteractiveFlag.hasFlag(_interactionOptions.flags, flag); } @override void initState() { super.initState(); widget.controller.addListener(reload); + + // TODO check if gestures are enabled + _tap = TapGesture(controller: widget.controller); + _secondaryTap = SecondaryTapGesture(controller: widget.controller); + _longPress = LongPressGesture(controller: widget.controller); + _doubleTap = DoubleTapGesture(controller: widget.controller); + _tertiaryTap = TertiaryTapGesture(controller: widget.controller); + _tertiaryLongPress = + TertiaryLongPressGesture(controller: widget.controller); + _scrollWheelZoom = ScrollWheelZoomGesture(controller: widget.controller); + _multiInput = MultiInputGesture(controller: widget.controller); + _drag = DragGesture(controller: widget.controller); } @override @@ -71,190 +82,42 @@ class MapInteractiveViewerState extends State @override Widget build(BuildContext context) { return Listener( - onPointerSignal: _onPointerSignal, + onPointerSignal: (event) { + // mouse scroll wheel + if (event is PointerScrollEvent && + _gestureEnabled(InteractiveFlag.scrollWheelZoom)) { + _scrollWheelZoom?.submit(event); + } + }, child: GestureDetector( - onTapDown: (details) => _tapDetails = details, - onTapCancel: () => _tapDetails = null, - onTap: _onTap, + onTapDown: _tap?.setDetails, + onTapCancel: _tap?.reset, + onTap: _tap?.submit, - onLongPressStart: _onLongPressStart, + onLongPressStart: _longPress?.submit, - onSecondaryTapDown: (details) => _secondaryTapDetails = details, - onSecondaryTapCancel: () => _secondaryTapDetails = null, - onSecondaryTap: _onSecondaryTap, + onSecondaryTapDown: (details) => _secondaryTap?.setDetails, + onSecondaryTapCancel: () => _secondaryTap?.reset(), + onSecondaryTap: _secondaryTap?.submit, - onDoubleTapDown: (details) => _doubleTapDetails = details, - onDoubleTapCancel: () => _doubleTapDetails = null, - onDoubleTap: _onDoubleTap, + onDoubleTapDown: (details) => _doubleTap?.setDetails, + onDoubleTapCancel: () => _doubleTap?.reset, + onDoubleTap: _doubleTap?.submit, - onTertiaryTapDown: (details) => _tertiaryTapDetails = details, - onTertiaryTapCancel: () => _tertiaryTapDetails = null, - onTertiaryTapUp: _onTertiaryTapUp, + onTertiaryTapDown: (details) => _tertiaryTap?.setDetails, + onTertiaryTapCancel: () => _tertiaryTap?.reset, + onTertiaryTapUp: _tertiaryTap?.submit, - onTertiaryLongPressStart: _onTertiaryLongPressStart, + onTertiaryLongPressStart: _tertiaryLongPress?.submit, // pan and scale, scale is a superset of the pan gesture - onScaleStart: _onScalePanStart, - onScaleUpdate: _onScalePanUpdate, - onScaleEnd: _onScalePanEnd, - - child: widget.builder( - context, - widget.controller.options, - _camera, - ), - ), - ); - } - - /// A tap with a primary button has occurred. - /// This triggers when the tap gesture wins. - void _onTap() { - print('[MapInteractiveViewer] tap'); - if (_tapDetails == null) return; - final details = _tapDetails!; - _tapDetails = null; - widget.controller.tapped( - MapEventSource.tap, - TapPosition(details.globalPosition, details.localPosition), - _camera.offsetToCrs(details.localPosition), - ); - } - - /// Called when a long press gesture with a primary button has been - /// recognized. A pointer has remained in contact with the screen at the - /// same location for a long period of time. - void _onLongPressStart(LongPressStartDetails details) { - print('[MapInteractiveViewer] long press'); - widget.controller.longPressed( - MapEventSource.longPress, - TapPosition(details.globalPosition, details.localPosition), - _camera.offsetToCrs(details.localPosition), - ); - } - - /// A tap with a secondary button has occurred. - /// This triggers when the tap gesture wins. - void _onSecondaryTap() { - print('[MapInteractiveViewer] secondary tap'); - if (_secondaryTapDetails == null) return; - final details = _secondaryTapDetails!; - _secondaryTapDetails = null; - widget.controller.secondaryTapped( - MapEventSource.secondaryTap, - TapPosition(details.globalPosition, details.localPosition), - _camera.offsetToCrs(details.localPosition), - ); - } - - /// A double tap gesture tap has been registered - void _onDoubleTap() { - print('[MapInteractiveViewer] double tap'); - if (_doubleTapDetails == null) return; - final details = _doubleTapDetails!; - _doubleTapDetails = null; - if (!_gestureEnabled(InteractiveFlag.doubleTapZoom)) return; - - // start double tap animation - //widget.controller.doubleTapZoomStarted(MapEventSource.doubleTap); - // TODO - final newZoom = _getZoomForScale(_camera.zoom, 2); - final newCenter = _camera.focusedZoomCenter( - details.localPosition.toPoint(), - newZoom, - ); - widget.controller.move( - newCenter, - newZoom, - offset: Offset.zero, - hasGesture: true, - source: MapEventSource.doubleTap, - id: null, - ); - } - - void _onTertiaryTapUp(TapUpDetails _) { - print('[MapInteractiveViewer] tertiary tap'); - if (_tertiaryTapDetails == null) return; - final details = _tertiaryTapDetails!; - _tertiaryTapDetails = null; - // TODO - } + onScaleStart: _multiInput?.start, + onScaleUpdate: _multiInput?.update, + onScaleEnd: _multiInput?.end, - void _onTertiaryLongPressStart(LongPressStartDetails details) { - print('[MapInteractiveViewer] tertiary long press'); - // TODO - } - - void _onScalePanStart(ScaleStartDetails details) { - print('[MapInteractiveViewer] scale pan start'); - if (!_gestureEnabled(InteractiveFlag.drag)) return; - // TODO - _lastFocalLocal = details.localFocalPoint; - widget.controller.moveStarted(MapEventSource.dragStart); - } - - void _onScalePanUpdate(ScaleUpdateDetails details) { - print( - '[MapInteractiveViewer] scale pan update, ${details.localFocalPoint}', - ); - if (!_gestureEnabled(InteractiveFlag.drag)) return; - // TODO - - widget.controller.dragUpdated( - MapEventSource.onDrag, - _rotateOffset( - _lastFocalLocal! - details.localFocalPoint, + child: widget.builder(context, _options, _camera), ), ); - _lastFocalLocal = details.localFocalPoint; - } - - void _onScalePanEnd(ScaleEndDetails details) { - print('[MapInteractiveViewer] scale pan end'); - if (!_gestureEnabled(InteractiveFlag.drag)) return; - // TODO - } - - /// Handles mouse scroll events if the enableScrollWheel parameter is enabled - void _onPointerSignal(PointerSignalEvent event) { - print('[MapInteractiveViewer] on pointer signal'); - if (event is! PointerScrollEvent || - !_gestureEnabled(InteractiveFlag.scrollWheelZoom) || - event.scrollDelta.dy == 0) return; - - // Prevent scrolling of parent/child widgets simultaneously. - // See [PointerSignalResolver] documentation for more information. - GestureBinding.instance.pointerSignalResolver.register(event, (event) { - event as PointerScrollEvent; - final minZoom = _options.minZoom ?? 0.0; - final maxZoom = _options.maxZoom ?? double.infinity; - final newZoom = (_camera.zoom - - event.scrollDelta.dy * _interactionOptions.scrollWheelVelocity) - .clamp(minZoom, maxZoom); - // Calculate offset of mouse cursor from viewport center - final newCenter = _camera.focusedZoomCenter( - event.localPosition.toPoint(), - newZoom, - ); - widget.controller.move( - newCenter, - newZoom, - offset: Offset.zero, - hasGesture: true, - source: MapEventSource.scrollWheel, - id: null, - ); - }); - } - - /// get the calculated zoom level for a given scaling, relative for the - /// startZoomLevel - double _getZoomForScale(double startZoom, double scale) { - if (scale == 1) { - return _camera.clampZoom(startZoom); - } - return _camera.clampZoom(startZoom + math.log(scale) / math.ln2); } /// Used by the internal map controller to update interaction gestures @@ -262,26 +125,11 @@ class MapInteractiveViewerState extends State InteractionOptions oldOptions, InteractionOptions newOptions, ) { - print('[MapInteractiveViewer] updateGestures'); - // TODO + // TODO implement } /// Used by the internal map controller void interruptAnimatedMovement(MapEventMove event) { - print('[MapInteractiveViewer] interruptAnimatedMovement'); - // TODO - } - - /// Return a rotated Offset - Offset _rotateOffset(Offset offset) { - final radians = _camera.rotationRad; - if (radians == 0) return offset; - - final cos = math.cos(radians); - final sin = math.sin(radians); - final nx = (cos * offset.dx) + (sin * offset.dy); - final ny = (cos * offset.dy) - (sin * offset.dx); - - return Offset(nx, ny); + // TODO implement } } diff --git a/lib/src/map/controller/internal.dart b/lib/src/map/controller/internal.dart index e1ddf5928..ff5d84a17 100644 --- a/lib/src/map/controller/internal.dart +++ b/lib/src/map/controller/internal.dart @@ -87,7 +87,7 @@ class FlutterMapInternalController extends ValueNotifier<_InternalState> { source: source, id: id, ); - if (movementEvent != null) _emitMapEvent(movementEvent); + if (movementEvent != null) emitMapEvent(movementEvent); options.onPositionChanged?.call( MapPosition( @@ -122,7 +122,7 @@ class FlutterMapInternalController extends ValueNotifier<_InternalState> { // Update camera then emit events and callbacks value = value.withMapCamera(newCamera); - _emitMapEvent( + emitMapEvent( MapEventRotate( id: id, source: source, @@ -283,7 +283,7 @@ class FlutterMapInternalController extends ValueNotifier<_InternalState> { /// To be called when a gesture that causes movement starts. void moveStarted(MapEventSource source) { - _emitMapEvent( + emitMapEvent( MapEventMoveStart( camera: camera, source: source, @@ -310,7 +310,7 @@ class FlutterMapInternalController extends ValueNotifier<_InternalState> { /// To be called when a drag gesture ends. void moveEnded(MapEventSource source) { - _emitMapEvent( + emitMapEvent( MapEventMoveEnd( camera: camera, source: source, @@ -320,7 +320,7 @@ class FlutterMapInternalController extends ValueNotifier<_InternalState> { /// To be called when a rotation gesture starts. void rotateStarted(MapEventSource source) { - _emitMapEvent( + emitMapEvent( MapEventRotateStart( camera: camera, source: source, @@ -330,7 +330,7 @@ class FlutterMapInternalController extends ValueNotifier<_InternalState> { /// To be called when a rotation gesture ends. void rotateEnded(MapEventSource source) { - _emitMapEvent( + emitMapEvent( MapEventRotateEnd( camera: camera, source: source, @@ -340,7 +340,7 @@ class FlutterMapInternalController extends ValueNotifier<_InternalState> { /// To be called when a fling gesture starts. void flingStarted(MapEventSource source) { - _emitMapEvent( + emitMapEvent( MapEventFlingAnimationStart( camera: camera, source: MapEventSource.flingAnimationController, @@ -350,7 +350,7 @@ class FlutterMapInternalController extends ValueNotifier<_InternalState> { /// To be called when a fling gesture ends. void flingEnded(MapEventSource source) { - _emitMapEvent( + emitMapEvent( MapEventFlingAnimationEnd( camera: camera, source: source, @@ -360,7 +360,7 @@ class FlutterMapInternalController extends ValueNotifier<_InternalState> { /// To be called when a fling gesture does not start. void flingNotStarted(MapEventSource source) { - _emitMapEvent( + emitMapEvent( MapEventFlingAnimationNotStarted( camera: camera, source: source, @@ -370,7 +370,7 @@ class FlutterMapInternalController extends ValueNotifier<_InternalState> { /// To be called when a double tap zoom starts. void doubleTapZoomStarted(MapEventSource source) { - _emitMapEvent( + emitMapEvent( MapEventDoubleTapZoomStart( camera: camera, source: source, @@ -380,7 +380,7 @@ class FlutterMapInternalController extends ValueNotifier<_InternalState> { /// To be called when a double tap zoom ends. void doubleTapZoomEnded(MapEventSource source) { - _emitMapEvent( + emitMapEvent( MapEventDoubleTapZoomEnd( camera: camera, source: source, @@ -388,28 +388,13 @@ class FlutterMapInternalController extends ValueNotifier<_InternalState> { ); } - void tapped( - MapEventSource source, - TapPosition tapPosition, - LatLng position, - ) { - options.onTap?.call(tapPosition, position); - _emitMapEvent( - MapEventTap( - tapPosition: position, - camera: camera, - source: source, - ), - ); - } - void secondaryTapped( MapEventSource source, TapPosition tapPosition, LatLng position, ) { options.onSecondaryTap?.call(tapPosition, position); - _emitMapEvent( + emitMapEvent( MapEventSecondaryTap( tapPosition: position, camera: camera, @@ -424,7 +409,7 @@ class FlutterMapInternalController extends ValueNotifier<_InternalState> { LatLng position, ) { options.onLongPress?.call(tapPosition, position); - _emitMapEvent( + emitMapEvent( MapEventLongPress( tapPosition: position, camera: camera, @@ -439,7 +424,7 @@ class FlutterMapInternalController extends ValueNotifier<_InternalState> { MapCamera oldCamera, MapCamera newCamera, ) { - _emitMapEvent( + emitMapEvent( MapEventNonRotatedSizeChange( source: MapEventSource.nonRotatedSizeChange, oldCamera: oldCamera, @@ -448,13 +433,11 @@ class FlutterMapInternalController extends ValueNotifier<_InternalState> { ); } - void _emitMapEvent(MapEvent event) { + void emitMapEvent(MapEvent event) { if (event.source == MapEventSource.mapController && event is MapEventMove) { _interactiveViewerState.interruptAnimatedMovement(event); } - options.onMapEvent?.call(event); - _mapControllerImpl.mapEventSink.add(event); } } From 184951bf481af2ec3b1cc735cc2f66557e588eb5 Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Tue, 21 Nov 2023 18:20:29 +0100 Subject: [PATCH 005/102] fix double tap and co --- lib/src/gestures/map_interactive_viewer.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/src/gestures/map_interactive_viewer.dart b/lib/src/gestures/map_interactive_viewer.dart index 0ec862dd9..f17302192 100644 --- a/lib/src/gestures/map_interactive_viewer.dart +++ b/lib/src/gestures/map_interactive_viewer.dart @@ -96,15 +96,15 @@ class MapInteractiveViewerState extends State onLongPressStart: _longPress?.submit, - onSecondaryTapDown: (details) => _secondaryTap?.setDetails, + onSecondaryTapDown: _secondaryTap?.setDetails, onSecondaryTapCancel: () => _secondaryTap?.reset(), onSecondaryTap: _secondaryTap?.submit, - onDoubleTapDown: (details) => _doubleTap?.setDetails, + onDoubleTapDown: _doubleTap?.setDetails, onDoubleTapCancel: () => _doubleTap?.reset, onDoubleTap: _doubleTap?.submit, - onTertiaryTapDown: (details) => _tertiaryTap?.setDetails, + onTertiaryTapDown: _tertiaryTap?.setDetails, onTertiaryTapCancel: () => _tertiaryTap?.reset, onTertiaryTapUp: _tertiaryTap?.submit, From ebef1238ca4b3d5dba59bad4c4a193294b8e59ed Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Tue, 21 Nov 2023 19:19:40 +0100 Subject: [PATCH 006/102] check if gesture is enabled, add tertiary and secondaryLongPress callbacks --- lib/src/gestures/gestures.dart | 12 ++++ lib/src/gestures/map_interactive_viewer.dart | 68 ++++++++++++-------- lib/src/map/controller/internal.dart | 3 +- lib/src/map/options/options.dart | 31 +++++---- 4 files changed, 71 insertions(+), 43 deletions(-) diff --git a/lib/src/gestures/gestures.dart b/lib/src/gestures/gestures.dart index 63c990b68..256345a09 100644 --- a/lib/src/gestures/gestures.dart +++ b/lib/src/gestures/gestures.dart @@ -239,4 +239,16 @@ class MultiInputGesture extends Gesture { class DragGesture extends Gesture { DragGesture({required super.controller}); + + void start() { + // TODO make use of the drag gesture + } + + void update() { + // TODO make use of the drag gesture + } + + void end() { + // TODO make use of the drag gesture + } } diff --git a/lib/src/gestures/map_interactive_viewer.dart b/lib/src/gestures/map_interactive_viewer.dart index f17302192..b01d69326 100644 --- a/lib/src/gestures/map_interactive_viewer.dart +++ b/lib/src/gestures/map_interactive_viewer.dart @@ -42,31 +42,30 @@ class MapInteractiveViewerState extends State InteractionOptions get _interactionOptions => _options.interactionOptions; - bool _gestureEnabled(int flag) { - if (flag == InteractiveFlag.scrollWheelZoom && - // ignore: deprecated_member_use_from_same_package - _interactionOptions.enableScrollWheel) { - return true; - } - return InteractiveFlag.hasFlag(_interactionOptions.flags, flag); - } - @override void initState() { super.initState(); widget.controller.addListener(reload); - // TODO check if gestures are enabled - _tap = TapGesture(controller: widget.controller); - _secondaryTap = SecondaryTapGesture(controller: widget.controller); - _longPress = LongPressGesture(controller: widget.controller); - _doubleTap = DoubleTapGesture(controller: widget.controller); - _tertiaryTap = TertiaryTapGesture(controller: widget.controller); - _tertiaryLongPress = - TertiaryLongPressGesture(controller: widget.controller); - _scrollWheelZoom = ScrollWheelZoomGesture(controller: widget.controller); - _multiInput = MultiInputGesture(controller: widget.controller); - _drag = DragGesture(controller: widget.controller); + // callback gestures for the application + if (_options.onTap != null) { + _tap = TapGesture(controller: widget.controller); + } + if (_options.onSecondaryTap != null) { + _secondaryTap = SecondaryTapGesture(controller: widget.controller); + } + if (_options.onLongPress != null) { + _longPress = LongPressGesture(controller: widget.controller); + } + if (_options.onTertiaryTap != null) { + _tertiaryTap = TertiaryTapGesture(controller: widget.controller); + } + if (_options.onTertiaryLongPress != null) { + _tertiaryLongPress = + TertiaryLongPressGesture(controller: widget.controller); + } + // gestures that change the map camera + updateGestures(_interactionOptions.flags); } @override @@ -84,8 +83,7 @@ class MapInteractiveViewerState extends State return Listener( onPointerSignal: (event) { // mouse scroll wheel - if (event is PointerScrollEvent && - _gestureEnabled(InteractiveFlag.scrollWheelZoom)) { + if (event is PointerScrollEvent) { _scrollWheelZoom?.submit(event); } }, @@ -121,11 +119,27 @@ class MapInteractiveViewerState extends State } /// Used by the internal map controller to update interaction gestures - void updateGestures( - InteractionOptions oldOptions, - InteractionOptions newOptions, - ) { - // TODO implement + void updateGestures(int flags) { + _multiInput = MultiInputGesture(controller: widget.controller); + + if (InteractiveFlag.hasDrag(flags)) { + _drag = DragGesture(controller: widget.controller); + } else { + _drag?.end(); + _drag = null; + } + + if (InteractiveFlag.hasDoubleTapZoom(flags)) { + _doubleTap = DoubleTapGesture(controller: widget.controller); + } else { + _doubleTap = null; + } + + if (MultiFingerGesture.hasRotate(flags)) { + _scrollWheelZoom = ScrollWheelZoomGesture(controller: widget.controller); + } else { + _scrollWheelZoom = null; + } } /// Used by the internal map controller diff --git a/lib/src/map/controller/internal.dart b/lib/src/map/controller/internal.dart index ff5d84a17..7622b1942 100644 --- a/lib/src/map/controller/internal.dart +++ b/lib/src/map/controller/internal.dart @@ -270,8 +270,7 @@ class FlutterMapInternalController extends ValueNotifier<_InternalState> { if (options.interactionOptions != newOptions.interactionOptions) { _interactiveViewerState.updateGestures( - options.interactionOptions, - newOptions.interactionOptions, + newOptions.interactionOptions.flags, ); } diff --git a/lib/src/map/options/options.dart b/lib/src/map/options/options.dart index 9cb028ee6..d1aa9f563 100644 --- a/lib/src/map/options/options.dart +++ b/lib/src/map/options/options.dart @@ -7,11 +7,7 @@ import 'package:latlong2/latlong.dart'; typedef MapEventCallback = void Function(MapEvent); -typedef TapCallback = void Function(TapPosition tapPosition, LatLng point); -typedef LongPressCallback = void Function( - TapPosition tapPosition, - LatLng point, -); +typedef GestureCallback = void Function(TapPosition tapPosition, LatLng point); typedef PointerDownCallback = void Function( PointerDownEvent event, LatLng point, @@ -51,13 +47,16 @@ class MapOptions { final Color backgroundColor; - final TapCallback? onTap; - final TapCallback? onSecondaryTap; - final LongPressCallback? onLongPress; - final PointerDownCallback? onPointerDown; - final PointerUpCallback? onPointerUp; - final PointerCancelCallback? onPointerCancel; - final PointerHoverCallback? onPointerHover; + final GestureCallback? onTap; + final GestureCallback? onSecondaryTap; + final GestureCallback? onLongPress; + final GestureCallback? onSecondaryLongPress; + final GestureCallback? onTertiaryTap; + final GestureCallback? onTertiaryLongPress; + final PointerDownCallback? onPointerDown; // TODO add support for callback + final PointerUpCallback? onPointerUp; // TODO add support for callback + final PointerCancelCallback? onPointerCancel; // TODO add support for callback + final PointerHoverCallback? onPointerHover; // TODO add support for callback final PositionCallback? onPositionChanged; final MapEventCallback? onMapEvent; @@ -98,6 +97,8 @@ class MapOptions { final InteractionOptions interactionOptions; + /// The options of the closest [FlutterMap] ancestor. If this is called from a + const MapOptions({ this.crs = const Epsg3857(), this.initialCenter = const LatLng(50.5, 30.51), @@ -110,8 +111,11 @@ class MapOptions { this.maxZoom, this.backgroundColor = const Color(0xFFE0E0E0), this.onTap, - this.onSecondaryTap, this.onLongPress, + this.onSecondaryTap, + this.onSecondaryLongPress, + this.onTertiaryTap, + this.onTertiaryLongPress, this.onPointerDown, this.onPointerUp, this.onPointerCancel, @@ -123,7 +127,6 @@ class MapOptions { this.applyPointerTranslucencyToLayers = true, }); - /// The options of the closest [FlutterMap] ancestor. If this is called from a /// context with no [FlutterMap] ancestor, null is returned. static MapOptions? maybeOf(BuildContext context) => FlutterMapInheritedModel.maybeOptionsOf(context); From ec57c70553778aaf6a50797f98cca04c1194d5de Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Tue, 21 Nov 2023 19:32:09 +0100 Subject: [PATCH 007/102] remove prefix from class, match file names --- lib/flutter_map.dart | 2 +- lib/src/gestures/gestures.dart | 4 +-- lib/src/gestures/map_interactive_viewer.dart | 4 +-- ...rnal.dart => internal_map_controller.dart} | 26 +++++++++++++++++-- lib/src/map/controller/map_controller.dart | 2 +- .../{impl.dart => map_controller_impl.dart} | 6 ++--- lib/src/map/widget.dart | 10 +++---- 7 files changed, 38 insertions(+), 16 deletions(-) rename lib/src/map/controller/{internal.dart => internal_map_controller.dart} (95%) rename lib/src/map/controller/{impl.dart => map_controller_impl.dart} (92%) diff --git a/lib/flutter_map.dart b/lib/flutter_map.dart index 9531c39d8..d04d42b99 100644 --- a/lib/flutter_map.dart +++ b/lib/flutter_map.dart @@ -49,8 +49,8 @@ export 'package:flutter_map/src/layer/tile_layer/tile_update_transformer.dart'; export 'package:flutter_map/src/map/camera/camera.dart'; export 'package:flutter_map/src/map/camera/camera_constraint.dart'; export 'package:flutter_map/src/map/camera/camera_fit.dart'; -export 'package:flutter_map/src/map/controller/impl.dart'; export 'package:flutter_map/src/map/controller/map_controller.dart'; +export 'package:flutter_map/src/map/controller/map_controller_impl.dart'; export 'package:flutter_map/src/map/options/cursor_keyboard_rotation.dart'; export 'package:flutter_map/src/map/options/interaction.dart'; export 'package:flutter_map/src/map/options/options.dart'; diff --git a/lib/src/gestures/gestures.dart b/lib/src/gestures/gestures.dart index 256345a09..83b4ba822 100644 --- a/lib/src/gestures/gestures.dart +++ b/lib/src/gestures/gestures.dart @@ -2,10 +2,10 @@ import 'dart:math' as math; import 'package:flutter/gestures.dart'; import 'package:flutter_map/flutter_map.dart'; -import 'package:flutter_map/src/map/controller/internal.dart'; +import 'package:flutter_map/src/map/controller/internal_map_controller.dart'; abstract class Gesture { - final FlutterMapInternalController controller; + final InternalMapController controller; Gesture({required this.controller}); diff --git a/lib/src/gestures/map_interactive_viewer.dart b/lib/src/gestures/map_interactive_viewer.dart index b01d69326..09b480178 100644 --- a/lib/src/gestures/map_interactive_viewer.dart +++ b/lib/src/gestures/map_interactive_viewer.dart @@ -2,7 +2,7 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_map/src/gestures/gestures.dart'; -import 'package:flutter_map/src/map/controller/internal.dart'; +import 'package:flutter_map/src/map/controller/internal_map_controller.dart'; typedef ChildBuilder = Widget Function( BuildContext context, @@ -12,7 +12,7 @@ typedef ChildBuilder = Widget Function( class MapInteractiveViewer extends StatefulWidget { final ChildBuilder builder; - final FlutterMapInternalController controller; + final InternalMapController controller; const MapInteractiveViewer({ super.key, diff --git a/lib/src/map/controller/internal.dart b/lib/src/map/controller/internal_map_controller.dart similarity index 95% rename from lib/src/map/controller/internal.dart rename to lib/src/map/controller/internal_map_controller.dart index 7622b1942..0409a8775 100644 --- a/lib/src/map/controller/internal.dart +++ b/lib/src/map/controller/internal_map_controller.dart @@ -8,11 +8,11 @@ import 'package:latlong2/latlong.dart'; /// This controller is for internal use. All updates to the state should be done /// by calling methods of this class to ensure consistency. -class FlutterMapInternalController extends ValueNotifier<_InternalState> { +class InternalMapController extends ValueNotifier<_InternalState> { late final MapInteractiveViewerState _interactiveViewerState; late MapControllerImpl _mapControllerImpl; - FlutterMapInternalController(MapOptions options) + InternalMapController(MapOptions options) : super( _InternalState( options: options, @@ -307,6 +307,28 @@ class FlutterMapInternalController extends ValueNotifier<_InternalState> { ); } + void moveRotateZoom({ + required Offset offset, + required double rotationRad, + required double zoom, + required MapEventSource source, + }) { + final oldCenterPt = camera.project(camera.center); + + final newCenterPt = oldCenterPt + offset.toPoint(); + final newCenter = camera.unproject(newCenterPt); + + moveAndRotate( + newCenter, + camera.zoom, + rotationRad, + offset: Offset.zero, + hasGesture: true, + source: source, + id: null, + ); + } + /// To be called when a drag gesture ends. void moveEnded(MapEventSource source) { emitMapEvent( diff --git a/lib/src/map/controller/map_controller.dart b/lib/src/map/controller/map_controller.dart index 9ffb23bae..069b06c41 100644 --- a/lib/src/map/controller/map_controller.dart +++ b/lib/src/map/controller/map_controller.dart @@ -5,7 +5,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_map/src/gestures/map_events.dart'; import 'package:flutter_map/src/map/camera/camera.dart'; import 'package:flutter_map/src/map/camera/camera_fit.dart'; -import 'package:flutter_map/src/map/controller/impl.dart'; +import 'package:flutter_map/src/map/controller/map_controller_impl.dart'; import 'package:flutter_map/src/map/inherited_model.dart'; import 'package:flutter_map/src/map/widget.dart'; import 'package:flutter_map/src/misc/move_and_rotate_result.dart'; diff --git a/lib/src/map/controller/impl.dart b/lib/src/map/controller/map_controller_impl.dart similarity index 92% rename from lib/src/map/controller/impl.dart rename to lib/src/map/controller/map_controller_impl.dart index 900cacff6..eae3c3df1 100644 --- a/lib/src/map/controller/impl.dart +++ b/lib/src/map/controller/map_controller_impl.dart @@ -5,7 +5,7 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_map/src/gestures/map_events.dart'; import 'package:flutter_map/src/map/camera/camera.dart'; import 'package:flutter_map/src/map/camera/camera_fit.dart'; -import 'package:flutter_map/src/map/controller/internal.dart'; +import 'package:flutter_map/src/map/controller/internal_map_controller.dart'; import 'package:flutter_map/src/map/controller/map_controller.dart'; import 'package:flutter_map/src/misc/move_and_rotate_result.dart'; import 'package:latlong2/latlong.dart'; @@ -14,12 +14,12 @@ import 'package:latlong2/latlong.dart'; /// should not be visible to the user (e.g. for setting the current camera or /// linking the internal controller). class MapControllerImpl implements MapController { - late FlutterMapInternalController _internalController; + late InternalMapController _internalController; final _mapEventStreamController = StreamController.broadcast(); MapControllerImpl(); - set internalController(FlutterMapInternalController internalController) { + set internalController(InternalMapController internalController) { _internalController = internalController; } diff --git a/lib/src/map/widget.dart b/lib/src/map/widget.dart index b9528c3cd..6d0e2009b 100644 --- a/lib/src/map/widget.dart +++ b/lib/src/map/widget.dart @@ -6,9 +6,10 @@ import 'package:flutter_map/src/gestures/map_events.dart'; import 'package:flutter_map/src/gestures/map_interactive_viewer.dart'; import 'package:flutter_map/src/layer/general/mobile_layer_transformer.dart'; import 'package:flutter_map/src/layer/general/translucent_pointer.dart'; -import 'package:flutter_map/src/map/controller/impl.dart'; -import 'package:flutter_map/src/map/controller/internal.dart'; +import 'package:flutter_map/src/map/camera/camera_fit.dart'; +import 'package:flutter_map/src/map/controller/internal_map_controller.dart'; import 'package:flutter_map/src/map/controller/map_controller.dart'; +import 'package:flutter_map/src/map/controller/map_controller_impl.dart'; import 'package:flutter_map/src/map/inherited_model.dart'; import 'package:flutter_map/src/map/options/options.dart'; import 'package:logger/logger.dart'; @@ -60,15 +61,14 @@ class _FlutterMapStateContainer extends State with AutomaticKeepAliveClientMixin { bool _initialCameraFitApplied = false; - late final FlutterMapInternalController _flutterMapInternalController; + late final InternalMapController _flutterMapInternalController; late MapControllerImpl _mapController; late bool _mapControllerCreatedInternally; @override void initState() { super.initState(); - _flutterMapInternalController = - FlutterMapInternalController(widget.options); + _flutterMapInternalController = InternalMapController(widget.options); _initializeAndLinkMapController(); WidgetsBinding.instance From c863202c01dbdaa7fe9e884af5f940d4479f2e63 Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Tue, 21 Nov 2023 20:32:08 +0100 Subject: [PATCH 008/102] fix new gestures --- lib/src/gestures/gestures.dart | 83 ++++++++++++++++--- lib/src/gestures/map_events.dart | 3 + lib/src/gestures/map_interactive_viewer.dart | 21 +++-- .../controller/internal_map_controller.dart | 15 ---- 4 files changed, 90 insertions(+), 32 deletions(-) diff --git a/lib/src/gestures/gestures.dart b/lib/src/gestures/gestures.dart index 83b4ba822..f7ba3811e 100644 --- a/lib/src/gestures/gestures.dart +++ b/lib/src/gestures/gestures.dart @@ -57,10 +57,41 @@ class LongPressGesture extends Gesture { /// recognized. A pointer has remained in contact with the screen at the /// same location for a long period of time. void submit(LongPressStartDetails details) { - controller.longPressed( - MapEventSource.longPress, - TapPosition(details.globalPosition, details.localPosition), - _camera.offsetToCrs(details.localPosition), + final tapPosition = TapPosition( + details.globalPosition, + details.localPosition, + ); + final position = _camera.offsetToCrs(details.localPosition); + _options.onLongPress?.call(tapPosition, position); + controller.emitMapEvent( + MapEventLongPress( + tapPosition: position, + camera: _camera, + source: MapEventSource.longPress, + ), + ); + } +} + +class SecondaryLongPressGesture extends Gesture { + SecondaryLongPressGesture({required super.controller}); + + /// Called when a long press gesture with a primary button has been + /// recognized. A pointer has remained in contact with the screen at the + /// same location for a long period of time. + void submit(LongPressStartDetails details) { + final tapPosition = TapPosition( + details.globalPosition, + details.localPosition, + ); + final position = _camera.offsetToCrs(details.localPosition); + _options.onSecondaryLongPress?.call(tapPosition, position); + controller.emitMapEvent( + MapEventLongPress( + tapPosition: position, + camera: _camera, + source: MapEventSource.secondaryLongPressed, + ), ); } } @@ -73,10 +104,18 @@ class SecondaryTapGesture extends DelayedGesture { void submit() { if (details == null) return; - controller.secondaryTapped( - MapEventSource.secondaryTap, - TapPosition(details!.globalPosition, details!.localPosition), - _camera.offsetToCrs(details!.localPosition), + final tapPosition = TapPosition( + details!.globalPosition, + details!.localPosition, + ); + final position = _camera.offsetToCrs(details!.localPosition); + _options.onSecondaryTap?.call(tapPosition, position); + controller.emitMapEvent( + MapEventLongPress( + tapPosition: position, + camera: _camera, + source: MapEventSource.secondaryTap, + ), ); reset(); @@ -127,7 +166,19 @@ class TertiaryTapGesture extends DelayedGesture { void submit(TapUpDetails _) { if (details == null) return; - // TODO implement + final point = _camera.offsetToCrs(details!.localPosition); + final tapPosition = TapPosition( + details!.globalPosition, + details!.localPosition, + ); + _options.onTertiaryTap?.call(tapPosition, point); + controller.emitMapEvent( + MapEventTap( + tapPosition: point, + camera: _camera, + source: MapEventSource.tertiaryTap, + ), + ); reset(); } @@ -139,7 +190,19 @@ class TertiaryLongPressGesture extends DelayedGesture { /// A long press on the tertiary button has happen (e.g. click and hold on /// the mouse scroll wheel) void submit(LongPressStartDetails details) { - // TODO implement + final point = _camera.offsetToCrs(details.localPosition); + final tapPosition = TapPosition( + details.globalPosition, + details.localPosition, + ); + _options.onTertiaryLongPress?.call(tapPosition, point); + controller.emitMapEvent( + MapEventLongPress( + tapPosition: point, + camera: _camera, + source: MapEventSource.tertiaryLongPress, + ), + ); reset(); } diff --git a/lib/src/gestures/map_events.dart b/lib/src/gestures/map_events.dart index ecf4fc9cb..3af7c7474 100644 --- a/lib/src/gestures/map_events.dart +++ b/lib/src/gestures/map_events.dart @@ -25,6 +25,9 @@ enum MapEventSource { scrollWheel, nonRotatedSizeChange, cursorKeyboardRotation, + tertiaryTap, + tertiaryLongPress, + secondaryLongPressed, } /// Base event class which is emitted by MapController instance, the event diff --git a/lib/src/gestures/map_interactive_viewer.dart b/lib/src/gestures/map_interactive_viewer.dart index 09b480178..76e06769c 100644 --- a/lib/src/gestures/map_interactive_viewer.dart +++ b/lib/src/gestures/map_interactive_viewer.dart @@ -27,11 +27,12 @@ class MapInteractiveViewer extends StatefulWidget { class MapInteractiveViewerState extends State with TickerProviderStateMixin { TapGesture? _tap; - SecondaryTapGesture? _secondaryTap; LongPressGesture? _longPress; - DoubleTapGesture? _doubleTap; + SecondaryTapGesture? _secondaryTap; + SecondaryLongPressGesture? _secondaryLongPress; TertiaryTapGesture? _tertiaryTap; TertiaryLongPressGesture? _tertiaryLongPress; + DoubleTapGesture? _doubleTap; ScrollWheelZoomGesture? _scrollWheelZoom; MultiInputGesture? _multiInput; DragGesture? _drag; @@ -51,11 +52,15 @@ class MapInteractiveViewerState extends State if (_options.onTap != null) { _tap = TapGesture(controller: widget.controller); } + if (_options.onLongPress != null) { + _longPress = LongPressGesture(controller: widget.controller); + } if (_options.onSecondaryTap != null) { _secondaryTap = SecondaryTapGesture(controller: widget.controller); } - if (_options.onLongPress != null) { - _longPress = LongPressGesture(controller: widget.controller); + if (_options.onSecondaryLongPress != null) { + _secondaryLongPress = + SecondaryLongPressGesture(controller: widget.controller); } if (_options.onTertiaryTap != null) { _tertiaryTap = TertiaryTapGesture(controller: widget.controller); @@ -95,15 +100,17 @@ class MapInteractiveViewerState extends State onLongPressStart: _longPress?.submit, onSecondaryTapDown: _secondaryTap?.setDetails, - onSecondaryTapCancel: () => _secondaryTap?.reset(), + onSecondaryTapCancel: _secondaryTap?.reset, onSecondaryTap: _secondaryTap?.submit, + onSecondaryLongPressStart: _secondaryLongPress?.submit, + onDoubleTapDown: _doubleTap?.setDetails, - onDoubleTapCancel: () => _doubleTap?.reset, + onDoubleTapCancel: _doubleTap?.reset, onDoubleTap: _doubleTap?.submit, onTertiaryTapDown: _tertiaryTap?.setDetails, - onTertiaryTapCancel: () => _tertiaryTap?.reset, + onTertiaryTapCancel: _tertiaryTap?.reset, onTertiaryTapUp: _tertiaryTap?.submit, onTertiaryLongPressStart: _tertiaryLongPress?.submit, diff --git a/lib/src/map/controller/internal_map_controller.dart b/lib/src/map/controller/internal_map_controller.dart index 0409a8775..ef32a86d1 100644 --- a/lib/src/map/controller/internal_map_controller.dart +++ b/lib/src/map/controller/internal_map_controller.dart @@ -424,21 +424,6 @@ class InternalMapController extends ValueNotifier<_InternalState> { ); } - void longPressed( - MapEventSource source, - TapPosition tapPosition, - LatLng position, - ) { - options.onLongPress?.call(tapPosition, position); - emitMapEvent( - MapEventLongPress( - tapPosition: position, - camera: camera, - source: MapEventSource.longPress, - ), - ); - } - /// To be called when the map's size constraints change. void nonRotatedSizeChange( MapEventSource source, From 499f77ade80b341a15bfa69eb4ed836692e02489 Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Tue, 21 Nov 2023 21:48:38 +0100 Subject: [PATCH 009/102] fix enable scroll wheel gesture check --- lib/src/gestures/map_interactive_viewer.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/gestures/map_interactive_viewer.dart b/lib/src/gestures/map_interactive_viewer.dart index 76e06769c..d683e6818 100644 --- a/lib/src/gestures/map_interactive_viewer.dart +++ b/lib/src/gestures/map_interactive_viewer.dart @@ -142,7 +142,7 @@ class MapInteractiveViewerState extends State _doubleTap = null; } - if (MultiFingerGesture.hasRotate(flags)) { + if (InteractiveFlag.hasScrollWheelZoom(flags)) { _scrollWheelZoom = ScrollWheelZoomGesture(controller: widget.controller); } else { _scrollWheelZoom = null; From 1f8764534f208e730589c9d8dd52112e56e88a1d Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Tue, 21 Nov 2023 22:07:57 +0100 Subject: [PATCH 010/102] use dedicated event classes for new map events --- example/lib/pages/interactive_test_page.dart | 6 +++ lib/src/gestures/gestures.dart | 37 ++++++++++++--- lib/src/gestures/map_events.dart | 39 ++++++++++++++++ .../controller/internal_map_controller.dart | 46 ------------------- lib/src/map/options/options.dart | 5 +- 5 files changed, 78 insertions(+), 55 deletions(-) diff --git a/example/lib/pages/interactive_test_page.dart b/example/lib/pages/interactive_test_page.dart index 69db1cb79..5c64a1df8 100644 --- a/example/lib/pages/interactive_test_page.dart +++ b/example/lib/pages/interactive_test_page.dart @@ -177,6 +177,12 @@ class _InteractiveFlagsPageState extends State { return 'MapEventRotateEnd'; case MapEventNonRotatedSizeChange(): return 'MapEventNonRotatedSizeChange'; + case MapEventSecondaryLongPress(): + return 'MapEventSecondaryLongPress'; + case MapEventTertiaryTap(): + return 'MapEventTertiaryTap'; + case MapEventTertiaryLongPress(): + return 'MapEventTertiaryLongPress'; case null: return 'null'; default: diff --git a/lib/src/gestures/gestures.dart b/lib/src/gestures/gestures.dart index f7ba3811e..2f47db064 100644 --- a/lib/src/gestures/gestures.dart +++ b/lib/src/gestures/gestures.dart @@ -87,7 +87,7 @@ class SecondaryLongPressGesture extends Gesture { final position = _camera.offsetToCrs(details.localPosition); _options.onSecondaryLongPress?.call(tapPosition, position); controller.emitMapEvent( - MapEventLongPress( + MapEventSecondaryLongPress( tapPosition: position, camera: _camera, source: MapEventSource.secondaryLongPressed, @@ -111,7 +111,7 @@ class SecondaryTapGesture extends DelayedGesture { final position = _camera.offsetToCrs(details!.localPosition); _options.onSecondaryTap?.call(tapPosition, position); controller.emitMapEvent( - MapEventLongPress( + MapEventSecondaryTap( tapPosition: position, camera: _camera, source: MapEventSource.secondaryTap, @@ -137,6 +137,14 @@ class DoubleTapGesture extends DelayedGesture { details!.localPosition.toPoint(), newZoom, ); + + controller.emitMapEvent( + MapEventDoubleTapZoomStart( + camera: _camera, + source: MapEventSource.doubleTap, + ), + ); + controller.move( newCenter, newZoom, @@ -146,6 +154,13 @@ class DoubleTapGesture extends DelayedGesture { id: null, ); + controller.emitMapEvent( + MapEventDoubleTapZoomEnd( + camera: _camera, + source: MapEventSource.doubleTap, + ), + ); + reset(); } @@ -173,7 +188,7 @@ class TertiaryTapGesture extends DelayedGesture { ); _options.onTertiaryTap?.call(tapPosition, point); controller.emitMapEvent( - MapEventTap( + MapEventTertiaryTap( tapPosition: point, camera: _camera, source: MapEventSource.tertiaryTap, @@ -197,7 +212,7 @@ class TertiaryLongPressGesture extends DelayedGesture { ); _options.onTertiaryLongPress?.call(tapPosition, point); controller.emitMapEvent( - MapEventLongPress( + MapEventTertiaryLongPress( tapPosition: point, camera: _camera, source: MapEventSource.tertiaryLongPress, @@ -304,7 +319,12 @@ class DragGesture extends Gesture { DragGesture({required super.controller}); void start() { - // TODO make use of the drag gesture + controller.emitMapEvent( + MapEventMoveStart( + camera: _camera, + source: MapEventSource.dragStart, + ), + ); } void update() { @@ -312,6 +332,11 @@ class DragGesture extends Gesture { } void end() { - // TODO make use of the drag gesture + controller.emitMapEvent( + MapEventMoveEnd( + camera: _camera, + source: MapEventSource.dragEnd, + ), + ); } } diff --git a/lib/src/gestures/map_events.dart b/lib/src/gestures/map_events.dart index 3af7c7474..5d8cfd29e 100644 --- a/lib/src/gestures/map_events.dart +++ b/lib/src/gestures/map_events.dart @@ -125,6 +125,18 @@ class MapEventSecondaryTap extends MapEvent { }); } +@immutable +class MapEventTertiaryTap extends MapEvent { + /// Point coordinates where user has tapped + final LatLng tapPosition; + + const MapEventTertiaryTap({ + required this.tapPosition, + required super.source, + required super.camera, + }); +} + /// Event which is fired when map is long-pressed @immutable class MapEventLongPress extends MapEvent { @@ -138,6 +150,33 @@ class MapEventLongPress extends MapEvent { }); } +/// Event which is fired when map is long-pressed with the secondary button. +@immutable +class MapEventSecondaryLongPress extends MapEvent { + /// Point coordinates where user has long-pressed + final LatLng tapPosition; + + const MapEventSecondaryLongPress({ + required this.tapPosition, + required super.source, + required super.camera, + }); +} + +/// Event which is fired when map is long-pressed with the tertiary button +/// (e.g. the mouse wheel is clicked). +@immutable +class MapEventTertiaryLongPress extends MapEvent { + /// Point coordinates where user has long-pressed + final LatLng tapPosition; + + const MapEventTertiaryLongPress({ + required this.tapPosition, + required super.source, + required super.camera, + }); +} + /// Event which is fired when map is being moved. @immutable class MapEventMove extends MapEventWithMove { diff --git a/lib/src/map/controller/internal_map_controller.dart b/lib/src/map/controller/internal_map_controller.dart index ef32a86d1..238a81cfe 100644 --- a/lib/src/map/controller/internal_map_controller.dart +++ b/lib/src/map/controller/internal_map_controller.dart @@ -4,7 +4,6 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_map/src/gestures/map_interactive_viewer.dart'; -import 'package:latlong2/latlong.dart'; /// This controller is for internal use. All updates to the state should be done /// by calling methods of this class to ensure consistency. @@ -329,16 +328,6 @@ class InternalMapController extends ValueNotifier<_InternalState> { ); } - /// To be called when a drag gesture ends. - void moveEnded(MapEventSource source) { - emitMapEvent( - MapEventMoveEnd( - camera: camera, - source: source, - ), - ); - } - /// To be called when a rotation gesture starts. void rotateStarted(MapEventSource source) { emitMapEvent( @@ -389,41 +378,6 @@ class InternalMapController extends ValueNotifier<_InternalState> { ); } - /// To be called when a double tap zoom starts. - void doubleTapZoomStarted(MapEventSource source) { - emitMapEvent( - MapEventDoubleTapZoomStart( - camera: camera, - source: source, - ), - ); - } - - /// To be called when a double tap zoom ends. - void doubleTapZoomEnded(MapEventSource source) { - emitMapEvent( - MapEventDoubleTapZoomEnd( - camera: camera, - source: source, - ), - ); - } - - void secondaryTapped( - MapEventSource source, - TapPosition tapPosition, - LatLng position, - ) { - options.onSecondaryTap?.call(tapPosition, position); - emitMapEvent( - MapEventSecondaryTap( - tapPosition: position, - camera: camera, - source: source, - ), - ); - } - /// To be called when the map's size constraints change. void nonRotatedSizeChange( MapEventSource source, diff --git a/lib/src/map/options/options.dart b/lib/src/map/options/options.dart index d1aa9f563..8b1d572d8 100644 --- a/lib/src/map/options/options.dart +++ b/lib/src/map/options/options.dart @@ -3,7 +3,6 @@ import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_map/src/map/inherited_model.dart'; -import 'package:latlong2/latlong.dart'; typedef MapEventCallback = void Function(MapEvent); @@ -68,13 +67,13 @@ class MapOptions { /// Only use this if your map isn't built immediately (like inside FutureBuilder) /// and you need to access the controller as soon as the map is built. /// Otherwise you can use WidgetsBinding.instance.addPostFrameCallback - /// In initState to controll the map before the next frame. + /// In initState to control the map before the next frame. final VoidCallback? onMapReady; /// Flag to enable the built in keep alive functionality /// /// If the map is within a complex layout, such as a [ListView] or [PageView], - /// the map will reset to it's inital position after it appears back into view. + /// the map will reset to it's initial position after it appears back into view. /// To ensure this doesn't happen, enable this flag to prevent the [FlutterMap] /// widget from rebuilding. final bool keepAlive; From 6c9ea3f0122d87a6c519eebd71771dd342b49b76 Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Tue, 21 Nov 2023 23:06:42 +0100 Subject: [PATCH 011/102] add DoubleTapDragZoomGesture --- lib/src/gestures/gestures.dart | 50 ++++++++++++++++++++ lib/src/gestures/map_events.dart | 1 + lib/src/gestures/map_interactive_viewer.dart | 50 +++++++++++++++++--- 3 files changed, 95 insertions(+), 6 deletions(-) diff --git a/lib/src/gestures/gestures.dart b/lib/src/gestures/gestures.dart index 2f47db064..b2f163f25 100644 --- a/lib/src/gestures/gestures.dart +++ b/lib/src/gestures/gestures.dart @@ -266,12 +266,14 @@ class MultiInputGesture extends Gesture { /// Initialize gesture, called when gesture has started void start(ScaleStartDetails details) { _lastLocalFocal = details.localFocalPoint; + _lastScale = 1; controller.moveStarted(MapEventSource.multiFingerStart); } /// Called multiple times to handle updates to the gesture void update(ScaleUpdateDetails details) { // TODO implement + if (_lastLocalFocal == null || _lastScale == null) return; final offset = _rotateOffset(_lastLocalFocal! - details.localFocalPoint); final oldCenterPt = _camera.project(_camera.center); final newCenterPt = oldCenterPt + offset.toPoint(); @@ -340,3 +342,51 @@ class DragGesture extends Gesture { ); } } + +class DoubleTapDragZoomGesture extends Gesture { + Offset? _focalLocalStart; + double? _mapZoomStart; + + DoubleTapDragZoomGesture({required super.controller}); + + void start(ScaleStartDetails details) { + _focalLocalStart = details.localFocalPoint; + _mapZoomStart = _camera.zoom; + controller.emitMapEvent( + MapEventDoubleTapZoomStart( + camera: _camera, + source: MapEventSource.doubleTapHold, + ), + ); + } + + void update(ScaleUpdateDetails details) { + // TODO make use of the double tap drag zoom gesture + if (_focalLocalStart == null || _mapZoomStart == null) return; + + final verticalOffset = (_focalLocalStart! - details.localFocalPoint).dy; + final newZoom = _mapZoomStart! - verticalOffset / 360 * _camera.zoom; + final min = _options.minZoom ?? 0.0; + final max = _options.maxZoom ?? double.infinity; + final actualZoom = math.max(min, math.min(max, newZoom)); + controller.move( + _camera.center, + actualZoom, + offset: Offset.zero, + hasGesture: true, + source: MapEventSource.doubleTapHold, + id: null, + ); + } + + void end(ScaleEndDetails details) { + _mapZoomStart = null; + _focalLocalStart = null; + controller.emitMapEvent( + MapEventDoubleTapZoomEnd( + camera: _camera, + source: MapEventSource.doubleTapHold, + ), + ); + } +} diff --git a/lib/src/gestures/map_events.dart b/lib/src/gestures/map_events.dart index 5d8cfd29e..6da7e9b85 100644 --- a/lib/src/gestures/map_events.dart +++ b/lib/src/gestures/map_events.dart @@ -88,6 +88,7 @@ abstract class MapEventWithMove extends MapEvent { ), MapEventSource.onDrag || MapEventSource.onMultiFinger || + MapEventSource.doubleTapHold || MapEventSource.mapController || MapEventSource.custom => MapEventMove( diff --git a/lib/src/gestures/map_interactive_viewer.dart b/lib/src/gestures/map_interactive_viewer.dart index d683e6818..ce5ae5d33 100644 --- a/lib/src/gestures/map_interactive_viewer.dart +++ b/lib/src/gestures/map_interactive_viewer.dart @@ -36,6 +36,9 @@ class MapInteractiveViewerState extends State ScrollWheelZoomGesture? _scrollWheelZoom; MultiInputGesture? _multiInput; DragGesture? _drag; + DoubleTapDragZoomGesture? _doubleTapDragZoom; + + bool _doubleTapHold = false; MapCamera get _camera => widget.controller.camera; @@ -105,9 +108,18 @@ class MapInteractiveViewerState extends State onSecondaryLongPressStart: _secondaryLongPress?.submit, - onDoubleTapDown: _doubleTap?.setDetails, - onDoubleTapCancel: _doubleTap?.reset, - onDoubleTap: _doubleTap?.submit, + onDoubleTapDown: (details) { + _doubleTapHold = true; + _doubleTap?.setDetails(details); + }, + onDoubleTapCancel: () { + _doubleTapHold = true; + _doubleTap?.reset(); + }, + onDoubleTap: () { + _doubleTapHold = false; + _doubleTap?.submit(); + }, onTertiaryTapDown: _tertiaryTap?.setDetails, onTertiaryTapCancel: _tertiaryTap?.reset, @@ -116,9 +128,28 @@ class MapInteractiveViewerState extends State onTertiaryLongPressStart: _tertiaryLongPress?.submit, // pan and scale, scale is a superset of the pan gesture - onScaleStart: _multiInput?.start, - onScaleUpdate: _multiInput?.update, - onScaleEnd: _multiInput?.end, + onScaleStart: (details) { + if (_doubleTapHold) { + _doubleTapDragZoom?.start(details); + } else { + _multiInput?.start(details); + } + }, + onScaleUpdate: (details) { + if (_doubleTapHold) { + _doubleTapDragZoom?.update(details); + } else { + _multiInput?.update(details); + } + }, + onScaleEnd: (details) { + if (_doubleTapHold) { + _doubleTapHold = false; + _doubleTapDragZoom?.end(details); + } else { + _multiInput?.end(details); + } + }, child: widget.builder(context, _options, _camera), ), @@ -147,6 +178,13 @@ class MapInteractiveViewerState extends State } else { _scrollWheelZoom = null; } + + if (InteractiveFlag.hasDoubleTapDragZoom(flags)) { + _doubleTapDragZoom = + DoubleTapDragZoomGesture(controller: widget.controller); + } else { + _doubleTapDragZoom = null; + } } /// Used by the internal map controller From d9088b3b1d0364da2e720722d6a241919d98e709 Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Wed, 22 Nov 2023 12:44:03 +0100 Subject: [PATCH 012/102] remove `MultiFingerGesture` non breaking commit --- lib/flutter_map.dart | 1 - lib/src/gestures/interactive_flag.dart | 10 +++++- lib/src/gestures/multi_finger_gesture.dart | 38 ---------------------- lib/src/map/options/interaction.dart | 5 ++- 4 files changed, 11 insertions(+), 43 deletions(-) delete mode 100644 lib/src/gestures/multi_finger_gesture.dart diff --git a/lib/flutter_map.dart b/lib/flutter_map.dart index d04d42b99..99a210132 100644 --- a/lib/flutter_map.dart +++ b/lib/flutter_map.dart @@ -21,7 +21,6 @@ export 'package:flutter_map/src/geo/latlng_bounds.dart'; export 'package:flutter_map/src/gestures/interactive_flag.dart'; export 'package:flutter_map/src/gestures/latlng_tween.dart'; export 'package:flutter_map/src/gestures/map_events.dart'; -export 'package:flutter_map/src/gestures/multi_finger_gesture.dart'; export 'package:flutter_map/src/gestures/tap_position.dart'; export 'package:flutter_map/src/layer/attribution_layer/rich/animation.dart'; export 'package:flutter_map/src/layer/attribution_layer/rich/source.dart'; diff --git a/lib/src/gestures/interactive_flag.dart b/lib/src/gestures/interactive_flag.dart index 1caabc385..627511251 100644 --- a/lib/src/gestures/interactive_flag.dart +++ b/lib/src/gestures/interactive_flag.dart @@ -21,7 +21,8 @@ abstract class InteractiveFlag { doubleTapZoom | doubleTapDragZoom | scrollWheelZoom | - rotate; + rotate | + ctrlDragRotate; static const int none = 0; @@ -54,6 +55,10 @@ abstract class InteractiveFlag { /// [InteractionOptions.cursorKeyboardRotationOptions]. static const int rotate = 1 << 7; + /// Enable rotation by pressing the CTRL Key and drag the map with the cursor. + /// To change the key see [InteractionOptions.cursorKeyboardRotationOptions]. + static const int ctrlDragRotate = 1 << 8; + /// Flags pertaining to gestures which require multiple fingers. static const _multiFingerFlags = pinchMove | pinchZoom | rotate; @@ -93,4 +98,7 @@ abstract class InteractiveFlag { /// True if the [scrollWheelZoom] interactive flag is enabled. static bool hasScrollWheelZoom(int flags) => hasFlag(flags, scrollWheelZoom); + + /// True if the [ctrlDragRotate] interactive flag is enabled. + static bool hasCtrlDragRotate(int flags) => hasFlag(flags, ctrlDragRotate); } diff --git a/lib/src/gestures/multi_finger_gesture.dart b/lib/src/gestures/multi_finger_gesture.dart deleted file mode 100644 index 8ae1bcd29..000000000 --- a/lib/src/gestures/multi_finger_gesture.dart +++ /dev/null @@ -1,38 +0,0 @@ -import 'package:meta/meta.dart'; - -/// Use [MultiFingerGesture] to disable / enable certain gestures Use -/// [MultiFingerGesture.all] to enable all gestures, use -/// [MultiFingerGesture.none] to disable all gestures -/// -/// If you want mix gestures for example rotate and pinchZoom gestures then you -/// have two options A.) add you own flags: [MultiFingerGesture.rotate] | -/// [MultiFingerGesture.pinchZoom] B.) remove unnecessary flags from all: -/// [MultiFingerGesture.all] & ~[MultiFingerGesture.pinchMove] -@immutable -class MultiFingerGesture { - static const int all = pinchMove | pinchZoom | rotate; - static const int none = 0; - - /// enable move with two or more fingers - static const int pinchMove = 1 << 0; - - /// enable pinch zoom - static const int pinchZoom = 1 << 1; - - /// enable map rotate - static const int rotate = 1 << 2; - - /// Returns `true` if [leftFlags] has at least one member in [rightFlags] - /// (intersection) for example [leftFlags]= [MultiFingerGesture.pinchMove] | - /// [MultiFingerGesture.rotate] and [rightFlags]= [MultiFingerGesture.rotate] - /// returns true because both have [MultiFingerGesture.rotate] flag - static bool hasFlag(int leftFlags, int rightFlags) { - return leftFlags & rightFlags != 0; - } - - static bool hasPinchMove(int gestures) => hasFlag(gestures, pinchMove); - - static bool hasPinchZoom(int gestures) => hasFlag(gestures, pinchZoom); - - static bool hasRotate(int gestures) => hasFlag(gestures, rotate); -} diff --git a/lib/src/map/options/interaction.dart b/lib/src/map/options/interaction.dart index 10fc0cfce..4211f21cb 100644 --- a/lib/src/map/options/interaction.dart +++ b/lib/src/map/options/interaction.dart @@ -1,5 +1,4 @@ import 'package:flutter_map/src/gestures/interactive_flag.dart'; -import 'package:flutter_map/src/gestures/multi_finger_gesture.dart'; import 'package:flutter_map/src/map/options/cursor_keyboard_rotation.dart'; import 'package:meta/meta.dart'; @@ -80,10 +79,10 @@ final class InteractionOptions { this.debugMultiFingerGestureWinner = false, this.enableMultiFingerGestureRace = false, this.rotationThreshold = 20.0, - this.rotationWinGestures = MultiFingerGesture.rotate, + this.rotationWinGestures = InteractiveFlag.rotate, this.pinchZoomThreshold = 0.5, this.pinchZoomWinGestures = - MultiFingerGesture.pinchZoom | MultiFingerGesture.pinchMove, + InteractiveFlag.pinchZoom | InteractiveFlag.pinchMove, this.pinchMoveThreshold = 40.0, this.pinchMoveWinGestures = MultiFingerGesture.pinchZoom | MultiFingerGesture.pinchMove, From 95a25b498e83994c257b0a8efde1c8cf1ad3289a Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Thu, 23 Nov 2023 13:40:12 +0100 Subject: [PATCH 013/102] fix rebase --- lib/src/map/options/interaction.dart | 2 +- lib/src/map/widget.dart | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/src/map/options/interaction.dart b/lib/src/map/options/interaction.dart index 4211f21cb..59213f0ad 100644 --- a/lib/src/map/options/interaction.dart +++ b/lib/src/map/options/interaction.dart @@ -85,7 +85,7 @@ final class InteractionOptions { InteractiveFlag.pinchZoom | InteractiveFlag.pinchMove, this.pinchMoveThreshold = 40.0, this.pinchMoveWinGestures = - MultiFingerGesture.pinchZoom | MultiFingerGesture.pinchMove, + InteractiveFlag.pinchZoom | InteractiveFlag.pinchMove, this.scrollWheelVelocity = 0.005, this.cursorKeyboardRotationOptions = const CursorKeyboardRotationOptions(), }) : assert( diff --git a/lib/src/map/widget.dart b/lib/src/map/widget.dart index 6d0e2009b..dfe1e1023 100644 --- a/lib/src/map/widget.dart +++ b/lib/src/map/widget.dart @@ -6,7 +6,6 @@ import 'package:flutter_map/src/gestures/map_events.dart'; import 'package:flutter_map/src/gestures/map_interactive_viewer.dart'; import 'package:flutter_map/src/layer/general/mobile_layer_transformer.dart'; import 'package:flutter_map/src/layer/general/translucent_pointer.dart'; -import 'package:flutter_map/src/map/camera/camera_fit.dart'; import 'package:flutter_map/src/map/controller/internal_map_controller.dart'; import 'package:flutter_map/src/map/controller/map_controller.dart'; import 'package:flutter_map/src/map/controller/map_controller_impl.dart'; From beaeb4449838dd99ccc7de695bbf42e6176eedc9 Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Sun, 26 Nov 2023 00:43:13 +0100 Subject: [PATCH 014/102] add `CtrlDragRotateGesture` --- lib/src/gestures/gestures.dart | 45 ++++++++++++++++++++ lib/src/gestures/map_events.dart | 3 ++ lib/src/gestures/map_interactive_viewer.dart | 35 +++++++++------ 3 files changed, 71 insertions(+), 12 deletions(-) diff --git a/lib/src/gestures/gestures.dart b/lib/src/gestures/gestures.dart index b2f163f25..09bbee7ca 100644 --- a/lib/src/gestures/gestures.dart +++ b/lib/src/gestures/gestures.dart @@ -1,6 +1,7 @@ import 'dart:math' as math; import 'package:flutter/gestures.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_map/src/map/controller/internal_map_controller.dart'; @@ -343,7 +344,51 @@ class DragGesture extends Gesture { } } +class CtrlDragRotateGesture extends Gesture { + bool isActive = false; + + CtrlDragRotateGesture({required super.controller}); + + void start() { + controller.emitMapEvent( + MapEventRotateStart( + camera: _camera, + source: MapEventSource.ctrlDragRotateStart, + ), + ); + } + + void update(ScaleUpdateDetails details) { + controller.rotate( + _camera.rotation - (details.focalPointDelta.dy * 0.5), + hasGesture: true, + source: MapEventSource.ctrlDragRotate, + id: null, + ); + } + + void end() { + controller.emitMapEvent( + MapEventRotateEnd( + camera: _camera, + source: MapEventSource.ctrlDragRotateEnd, + ), + ); + } + + bool get ctrlPressed { + const keys = [ + LogicalKeyboardKey.controlLeft, + LogicalKeyboardKey.controlRight, + ]; + return RawKeyboard.instance.keysPressed + .where((key) => keys.contains(key)) + .isNotEmpty; + } +} + class DoubleTapDragZoomGesture extends Gesture { + bool isActive = false; Offset? _focalLocalStart; double? _mapZoomStart; diff --git a/lib/src/gestures/map_events.dart b/lib/src/gestures/map_events.dart index 6da7e9b85..5ec20c9d4 100644 --- a/lib/src/gestures/map_events.dart +++ b/lib/src/gestures/map_events.dart @@ -28,6 +28,9 @@ enum MapEventSource { tertiaryTap, tertiaryLongPress, secondaryLongPressed, + ctrlDragRotateStart, + ctrlDragRotateEnd, + ctrlDragRotate, } /// Base event class which is emitted by MapController instance, the event diff --git a/lib/src/gestures/map_interactive_viewer.dart b/lib/src/gestures/map_interactive_viewer.dart index ce5ae5d33..c23629354 100644 --- a/lib/src/gestures/map_interactive_viewer.dart +++ b/lib/src/gestures/map_interactive_viewer.dart @@ -37,8 +37,7 @@ class MapInteractiveViewerState extends State MultiInputGesture? _multiInput; DragGesture? _drag; DoubleTapDragZoomGesture? _doubleTapDragZoom; - - bool _doubleTapHold = false; + CtrlDragRotateGesture? _ctrlDragRotate; MapCamera get _camera => widget.controller.camera; @@ -109,15 +108,15 @@ class MapInteractiveViewerState extends State onSecondaryLongPressStart: _secondaryLongPress?.submit, onDoubleTapDown: (details) { - _doubleTapHold = true; + _doubleTapDragZoom?.isActive = true; _doubleTap?.setDetails(details); }, onDoubleTapCancel: () { - _doubleTapHold = true; + _doubleTapDragZoom?.isActive = true; _doubleTap?.reset(); }, onDoubleTap: () { - _doubleTapHold = false; + _doubleTapDragZoom?.isActive = false; _doubleTap?.submit(); }, @@ -129,23 +128,29 @@ class MapInteractiveViewerState extends State // pan and scale, scale is a superset of the pan gesture onScaleStart: (details) { - if (_doubleTapHold) { - _doubleTapDragZoom?.start(details); + if (_ctrlDragRotate?.ctrlPressed ?? false) { + _ctrlDragRotate!.start(); + } else if (_doubleTapDragZoom?.isActive ?? false) { + _doubleTapDragZoom!.start(details); } else { _multiInput?.start(details); } }, onScaleUpdate: (details) { - if (_doubleTapHold) { - _doubleTapDragZoom?.update(details); + if (_ctrlDragRotate?.ctrlPressed ?? false) { + _ctrlDragRotate!.update(details); + } else if (_doubleTapDragZoom?.isActive ?? false) { + _doubleTapDragZoom!.update(details); } else { _multiInput?.update(details); } }, onScaleEnd: (details) { - if (_doubleTapHold) { - _doubleTapHold = false; - _doubleTapDragZoom?.end(details); + if (_ctrlDragRotate?.ctrlPressed ?? false) { + _ctrlDragRotate!.end(); + } else if (_doubleTapDragZoom?.isActive ?? false) { + _doubleTapDragZoom!.isActive = false; + _doubleTapDragZoom!.end(details); } else { _multiInput?.end(details); } @@ -179,6 +184,12 @@ class MapInteractiveViewerState extends State _scrollWheelZoom = null; } + if (InteractiveFlag.hasCtrlDragRotate(flags)) { + _ctrlDragRotate = CtrlDragRotateGesture(controller: widget.controller); + } else { + _ctrlDragRotate = null; + } + if (InteractiveFlag.hasDoubleTapDragZoom(flags)) { _doubleTapDragZoom = DoubleTapDragZoomGesture(controller: widget.controller); From 451ceacaf14ff5b43359c08fc2e038a55a960731 Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Sun, 26 Nov 2023 00:57:07 +0100 Subject: [PATCH 015/102] remove `TapPosition` --- example/lib/pages/custom_crs/custom_crs.dart | 2 +- example/lib/pages/latlng_to_screen_point.dart | 2 +- example/lib/pages/markers.dart | 4 +- example/lib/pages/secondary_tap.dart | 2 +- lib/flutter_map.dart | 1 - lib/src/gestures/gestures.dart | 41 ++++++------------- lib/src/gestures/tap_position.dart | 21 ---------- lib/src/map/options/options.dart | 6 ++- test/flutter_map_test.dart | 2 +- 9 files changed, 25 insertions(+), 56 deletions(-) delete mode 100644 lib/src/gestures/tap_position.dart diff --git a/example/lib/pages/custom_crs/custom_crs.dart b/example/lib/pages/custom_crs/custom_crs.dart index aba6f6bb1..fb1d269ba 100644 --- a/example/lib/pages/custom_crs/custom_crs.dart +++ b/example/lib/pages/custom_crs/custom_crs.dart @@ -134,7 +134,7 @@ class CustomCrsPageState extends State { // Set maxZoom usually scales.length - 1 OR resolutions.length - 1 // but not greater maxZoom: maxZoom, - onTap: (tapPosition, p) => setState(() { + onTap: (_, __, p) => setState(() { initText = 'You clicked at'; point = proj4.Point(x: p.latitude, y: p.longitude); }), diff --git a/example/lib/pages/latlng_to_screen_point.dart b/example/lib/pages/latlng_to_screen_point.dart index eacdd994c..586a2cfbf 100644 --- a/example/lib/pages/latlng_to_screen_point.dart +++ b/example/lib/pages/latlng_to_screen_point.dart @@ -49,7 +49,7 @@ class _LatLngToScreenPointPageState extends State { interactionOptions: const InteractionOptions( flags: ~InteractiveFlag.doubleTapZoom, ), - onTap: (_, latLng) { + onTap: (_, __, latLng) { final point = mapController.camera .latLngToScreenPoint(tappedCoords = latLng); setState(() => tappedPoint = Point(point.x, point.y)); diff --git a/example/lib/pages/markers.dart b/example/lib/pages/markers.dart index d62caa16d..f035c23aa 100644 --- a/example/lib/pages/markers.dart +++ b/example/lib/pages/markers.dart @@ -109,7 +109,9 @@ class _MarkerPageState extends State { options: MapOptions( initialCenter: const LatLng(51.5, -0.09), initialZoom: 5, - onTap: (_, p) => setState(() => customMarkers.add(buildPin(p))), + onTap: (_, __, p) { + setState(() => customMarkers.add(buildPin(p))); + }, interactionOptions: const InteractionOptions( flags: ~InteractiveFlag.doubleTapZoom, ), diff --git a/example/lib/pages/secondary_tap.dart b/example/lib/pages/secondary_tap.dart index 220b56f60..387f46d53 100644 --- a/example/lib/pages/secondary_tap.dart +++ b/example/lib/pages/secondary_tap.dart @@ -22,7 +22,7 @@ class SecondaryTapPage extends StatelessWidget { Flexible( child: FlutterMap( options: MapOptions( - onSecondaryTap: (tapPos, latLng) { + onSecondaryTap: (_, __, latLng) { ScaffoldMessenger.maybeOf(context)?.showSnackBar( SnackBar(content: Text('Secondary tap at $latLng')), ); diff --git a/lib/flutter_map.dart b/lib/flutter_map.dart index 99a210132..463e03144 100644 --- a/lib/flutter_map.dart +++ b/lib/flutter_map.dart @@ -21,7 +21,6 @@ export 'package:flutter_map/src/geo/latlng_bounds.dart'; export 'package:flutter_map/src/gestures/interactive_flag.dart'; export 'package:flutter_map/src/gestures/latlng_tween.dart'; export 'package:flutter_map/src/gestures/map_events.dart'; -export 'package:flutter_map/src/gestures/tap_position.dart'; export 'package:flutter_map/src/layer/attribution_layer/rich/animation.dart'; export 'package:flutter_map/src/layer/attribution_layer/rich/source.dart'; export 'package:flutter_map/src/layer/attribution_layer/rich/widget.dart'; diff --git a/lib/src/gestures/gestures.dart b/lib/src/gestures/gestures.dart index 09bbee7ca..7f6ebd259 100644 --- a/lib/src/gestures/gestures.dart +++ b/lib/src/gestures/gestures.dart @@ -34,11 +34,8 @@ class TapGesture extends DelayedGesture { if (details == null) return; final point = _camera.offsetToCrs(details!.localPosition); - final tapPosition = TapPosition( - details!.globalPosition, - details!.localPosition, - ); - _options.onTap?.call(tapPosition, point); + _options.onTap + ?.call(details!.globalPosition, details!.localPosition, point); controller.emitMapEvent( MapEventTap( tapPosition: point, @@ -58,12 +55,9 @@ class LongPressGesture extends Gesture { /// recognized. A pointer has remained in contact with the screen at the /// same location for a long period of time. void submit(LongPressStartDetails details) { - final tapPosition = TapPosition( - details.globalPosition, - details.localPosition, - ); final position = _camera.offsetToCrs(details.localPosition); - _options.onLongPress?.call(tapPosition, position); + _options.onLongPress + ?.call(details.globalPosition, details.localPosition, position); controller.emitMapEvent( MapEventLongPress( tapPosition: position, @@ -81,12 +75,12 @@ class SecondaryLongPressGesture extends Gesture { /// recognized. A pointer has remained in contact with the screen at the /// same location for a long period of time. void submit(LongPressStartDetails details) { - final tapPosition = TapPosition( + final position = _camera.offsetToCrs(details.localPosition); + _options.onSecondaryLongPress?.call( details.globalPosition, details.localPosition, + position, ); - final position = _camera.offsetToCrs(details.localPosition); - _options.onSecondaryLongPress?.call(tapPosition, position); controller.emitMapEvent( MapEventSecondaryLongPress( tapPosition: position, @@ -105,12 +99,9 @@ class SecondaryTapGesture extends DelayedGesture { void submit() { if (details == null) return; - final tapPosition = TapPosition( - details!.globalPosition, - details!.localPosition, - ); final position = _camera.offsetToCrs(details!.localPosition); - _options.onSecondaryTap?.call(tapPosition, position); + _options.onSecondaryTap + ?.call(details!.globalPosition, details!.localPosition, position); controller.emitMapEvent( MapEventSecondaryTap( tapPosition: position, @@ -183,11 +174,8 @@ class TertiaryTapGesture extends DelayedGesture { if (details == null) return; final point = _camera.offsetToCrs(details!.localPosition); - final tapPosition = TapPosition( - details!.globalPosition, - details!.localPosition, - ); - _options.onTertiaryTap?.call(tapPosition, point); + _options.onTertiaryTap + ?.call(details!.globalPosition, details!.localPosition, point); controller.emitMapEvent( MapEventTertiaryTap( tapPosition: point, @@ -207,11 +195,8 @@ class TertiaryLongPressGesture extends DelayedGesture { /// the mouse scroll wheel) void submit(LongPressStartDetails details) { final point = _camera.offsetToCrs(details.localPosition); - final tapPosition = TapPosition( - details.globalPosition, - details.localPosition, - ); - _options.onTertiaryLongPress?.call(tapPosition, point); + _options.onTertiaryLongPress + ?.call(details.globalPosition, details.localPosition, point); controller.emitMapEvent( MapEventTertiaryLongPress( tapPosition: point, diff --git a/lib/src/gestures/tap_position.dart b/lib/src/gestures/tap_position.dart deleted file mode 100644 index 08cd01723..000000000 --- a/lib/src/gestures/tap_position.dart +++ /dev/null @@ -1,21 +0,0 @@ -import 'dart:ui'; - -import 'package:meta/meta.dart'; - -@immutable -class TapPosition { - const TapPosition(this.global, this.relative); - - final Offset global; - final Offset? relative; - - @override - bool operator ==(dynamic other) { - if (other is! TapPosition) return false; - final typedOther = other; - return global == typedOther.global && relative == other.relative; - } - - @override - int get hashCode => Object.hash(global, relative); -} diff --git a/lib/src/map/options/options.dart b/lib/src/map/options/options.dart index 8b1d572d8..13845cf6e 100644 --- a/lib/src/map/options/options.dart +++ b/lib/src/map/options/options.dart @@ -6,7 +6,11 @@ import 'package:flutter_map/src/map/inherited_model.dart'; typedef MapEventCallback = void Function(MapEvent); -typedef GestureCallback = void Function(TapPosition tapPosition, LatLng point); +typedef GestureCallback = void Function( + Offset globalPosition, + Offset localPosition, + LatLng point, +); typedef PointerDownCallback = void Function( PointerDownEvent event, LatLng point, diff --git a/test/flutter_map_test.dart b/test/flutter_map_test.dart index 41da4b439..e0e275211 100644 --- a/test/flutter_map_test.dart +++ b/test/flutter_map_test.dart @@ -96,7 +96,7 @@ void main() { backgroundColor: Colors.transparent, maxZoom: 9, initialZoom: 10, // Higher than maxZoom. - onTap: (_, __) { + onTap: (_, __, ___) { taps++; }, ), From fdd0788725cb13ab8ce0d1eda038ef009189c162 Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Sun, 26 Nov 2023 01:21:27 +0100 Subject: [PATCH 016/102] pass full gesture event alongside the callback --- example/lib/pages/custom_crs/custom_crs.dart | 2 +- example/lib/pages/latlng_to_screen_point.dart | 2 +- example/lib/pages/markers.dart | 2 +- example/lib/pages/secondary_tap.dart | 2 +- lib/src/gestures/gestures.dart | 25 ++++++------------- lib/src/gestures/map_interactive_viewer.dart | 4 +-- lib/src/map/options/options.dart | 12 ++++----- test/flutter_map_test.dart | 2 +- 8 files changed, 21 insertions(+), 30 deletions(-) diff --git a/example/lib/pages/custom_crs/custom_crs.dart b/example/lib/pages/custom_crs/custom_crs.dart index fb1d269ba..33651e6aa 100644 --- a/example/lib/pages/custom_crs/custom_crs.dart +++ b/example/lib/pages/custom_crs/custom_crs.dart @@ -134,7 +134,7 @@ class CustomCrsPageState extends State { // Set maxZoom usually scales.length - 1 OR resolutions.length - 1 // but not greater maxZoom: maxZoom, - onTap: (_, __, p) => setState(() { + onTap: (_, p) => setState(() { initText = 'You clicked at'; point = proj4.Point(x: p.latitude, y: p.longitude); }), diff --git a/example/lib/pages/latlng_to_screen_point.dart b/example/lib/pages/latlng_to_screen_point.dart index 586a2cfbf..eacdd994c 100644 --- a/example/lib/pages/latlng_to_screen_point.dart +++ b/example/lib/pages/latlng_to_screen_point.dart @@ -49,7 +49,7 @@ class _LatLngToScreenPointPageState extends State { interactionOptions: const InteractionOptions( flags: ~InteractiveFlag.doubleTapZoom, ), - onTap: (_, __, latLng) { + onTap: (_, latLng) { final point = mapController.camera .latLngToScreenPoint(tappedCoords = latLng); setState(() => tappedPoint = Point(point.x, point.y)); diff --git a/example/lib/pages/markers.dart b/example/lib/pages/markers.dart index f035c23aa..14b3f40bd 100644 --- a/example/lib/pages/markers.dart +++ b/example/lib/pages/markers.dart @@ -109,7 +109,7 @@ class _MarkerPageState extends State { options: MapOptions( initialCenter: const LatLng(51.5, -0.09), initialZoom: 5, - onTap: (_, __, p) { + onTap: (_, p) { setState(() => customMarkers.add(buildPin(p))); }, interactionOptions: const InteractionOptions( diff --git a/example/lib/pages/secondary_tap.dart b/example/lib/pages/secondary_tap.dart index 387f46d53..e659cc3f5 100644 --- a/example/lib/pages/secondary_tap.dart +++ b/example/lib/pages/secondary_tap.dart @@ -22,7 +22,7 @@ class SecondaryTapPage extends StatelessWidget { Flexible( child: FlutterMap( options: MapOptions( - onSecondaryTap: (_, __, latLng) { + onSecondaryTap: (_, latLng) { ScaffoldMessenger.maybeOf(context)?.showSnackBar( SnackBar(content: Text('Secondary tap at $latLng')), ); diff --git a/lib/src/gestures/gestures.dart b/lib/src/gestures/gestures.dart index 7f6ebd259..87eb7754a 100644 --- a/lib/src/gestures/gestures.dart +++ b/lib/src/gestures/gestures.dart @@ -34,8 +34,7 @@ class TapGesture extends DelayedGesture { if (details == null) return; final point = _camera.offsetToCrs(details!.localPosition); - _options.onTap - ?.call(details!.globalPosition, details!.localPosition, point); + _options.onTap?.call(details!, point); controller.emitMapEvent( MapEventTap( tapPosition: point, @@ -56,8 +55,7 @@ class LongPressGesture extends Gesture { /// same location for a long period of time. void submit(LongPressStartDetails details) { final position = _camera.offsetToCrs(details.localPosition); - _options.onLongPress - ?.call(details.globalPosition, details.localPosition, position); + _options.onLongPress?.call(details, position); controller.emitMapEvent( MapEventLongPress( tapPosition: position, @@ -76,11 +74,7 @@ class SecondaryLongPressGesture extends Gesture { /// same location for a long period of time. void submit(LongPressStartDetails details) { final position = _camera.offsetToCrs(details.localPosition); - _options.onSecondaryLongPress?.call( - details.globalPosition, - details.localPosition, - position, - ); + _options.onSecondaryLongPress?.call(details, position); controller.emitMapEvent( MapEventSecondaryLongPress( tapPosition: position, @@ -100,8 +94,7 @@ class SecondaryTapGesture extends DelayedGesture { if (details == null) return; final position = _camera.offsetToCrs(details!.localPosition); - _options.onSecondaryTap - ?.call(details!.globalPosition, details!.localPosition, position); + _options.onSecondaryTap?.call(details!, position); controller.emitMapEvent( MapEventSecondaryTap( tapPosition: position, @@ -174,8 +167,7 @@ class TertiaryTapGesture extends DelayedGesture { if (details == null) return; final point = _camera.offsetToCrs(details!.localPosition); - _options.onTertiaryTap - ?.call(details!.globalPosition, details!.localPosition, point); + _options.onTertiaryTap?.call(details!, point); controller.emitMapEvent( MapEventTertiaryTap( tapPosition: point, @@ -195,8 +187,7 @@ class TertiaryLongPressGesture extends DelayedGesture { /// the mouse scroll wheel) void submit(LongPressStartDetails details) { final point = _camera.offsetToCrs(details.localPosition); - _options.onTertiaryLongPress - ?.call(details.globalPosition, details.localPosition, point); + _options.onTertiaryLongPress?.call(details, point); controller.emitMapEvent( MapEventTertiaryLongPress( tapPosition: point, @@ -243,11 +234,11 @@ class ScrollWheelZoomGesture extends Gesture { } /// A gesture with multiple inputs like zooming with two fingers -class MultiInputGesture extends Gesture { +class TwoFingerGesture extends Gesture { Offset? _lastLocalFocal; double? _lastScale; - MultiInputGesture({required super.controller}); + TwoFingerGesture({required super.controller}); /// Initialize gesture, called when gesture has started void start(ScaleStartDetails details) { diff --git a/lib/src/gestures/map_interactive_viewer.dart b/lib/src/gestures/map_interactive_viewer.dart index c23629354..9276c163a 100644 --- a/lib/src/gestures/map_interactive_viewer.dart +++ b/lib/src/gestures/map_interactive_viewer.dart @@ -34,7 +34,7 @@ class MapInteractiveViewerState extends State TertiaryLongPressGesture? _tertiaryLongPress; DoubleTapGesture? _doubleTap; ScrollWheelZoomGesture? _scrollWheelZoom; - MultiInputGesture? _multiInput; + TwoFingerGesture? _multiInput; DragGesture? _drag; DoubleTapDragZoomGesture? _doubleTapDragZoom; CtrlDragRotateGesture? _ctrlDragRotate; @@ -163,7 +163,7 @@ class MapInteractiveViewerState extends State /// Used by the internal map controller to update interaction gestures void updateGestures(int flags) { - _multiInput = MultiInputGesture(controller: widget.controller); + _multiInput = TwoFingerGesture(controller: widget.controller); if (InteractiveFlag.hasDrag(flags)) { _drag = DragGesture(controller: widget.controller); diff --git a/lib/src/map/options/options.dart b/lib/src/map/options/options.dart index 13845cf6e..baa81d5db 100644 --- a/lib/src/map/options/options.dart +++ b/lib/src/map/options/options.dart @@ -6,9 +6,9 @@ import 'package:flutter_map/src/map/inherited_model.dart'; typedef MapEventCallback = void Function(MapEvent); -typedef GestureCallback = void Function( - Offset globalPosition, - Offset localPosition, +typedef GestureCallback = void Function(TapDownDetails details, LatLng point); +typedef LongPressCallback = void Function( + LongPressStartDetails details, LatLng point, ); typedef PointerDownCallback = void Function( @@ -51,11 +51,11 @@ class MapOptions { final Color backgroundColor; final GestureCallback? onTap; + final LongPressCallback? onLongPress; final GestureCallback? onSecondaryTap; - final GestureCallback? onLongPress; - final GestureCallback? onSecondaryLongPress; + final LongPressCallback? onSecondaryLongPress; final GestureCallback? onTertiaryTap; - final GestureCallback? onTertiaryLongPress; + final LongPressCallback? onTertiaryLongPress; final PointerDownCallback? onPointerDown; // TODO add support for callback final PointerUpCallback? onPointerUp; // TODO add support for callback final PointerCancelCallback? onPointerCancel; // TODO add support for callback diff --git a/test/flutter_map_test.dart b/test/flutter_map_test.dart index e0e275211..7534c3046 100644 --- a/test/flutter_map_test.dart +++ b/test/flutter_map_test.dart @@ -96,7 +96,7 @@ void main() { backgroundColor: Colors.transparent, maxZoom: 9, initialZoom: 10, // Higher than maxZoom. - onTap: (_, __, ___) { + onTap: (_, ___) { taps++; }, ), From 41348c21d13289a48158238e2ba6d5419c2bb4ef Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Sun, 26 Nov 2023 02:02:45 +0100 Subject: [PATCH 017/102] add twoFingerRotate, twoFingerZoom, twoFingerMove --- lib/src/gestures/gestures.dart | 53 +++++++++++++++---- lib/src/gestures/map_interactive_viewer.dart | 19 +++++-- .../controller/internal_map_controller.dart | 32 ----------- 3 files changed, 57 insertions(+), 47 deletions(-) diff --git a/lib/src/gestures/gestures.dart b/lib/src/gestures/gestures.dart index 87eb7754a..8ab86e826 100644 --- a/lib/src/gestures/gestures.dart +++ b/lib/src/gestures/gestures.dart @@ -234,42 +234,69 @@ class ScrollWheelZoomGesture extends Gesture { } /// A gesture with multiple inputs like zooming with two fingers -class TwoFingerGesture extends Gesture { +class TwoFingerGestures extends Gesture { + final bool moveEnabled; + final bool rotateEnabled; + final bool zoomEnabled; Offset? _lastLocalFocal; double? _lastScale; + double? _lastRotation; - TwoFingerGesture({required super.controller}); + TwoFingerGestures({ + required this.moveEnabled, + required this.rotateEnabled, + required this.zoomEnabled, + required super.controller, + }); /// Initialize gesture, called when gesture has started void start(ScaleStartDetails details) { _lastLocalFocal = details.localFocalPoint; _lastScale = 1; - controller.moveStarted(MapEventSource.multiFingerStart); + _lastRotation = 0; + controller.emitMapEvent( + MapEventMoveStart( + camera: _camera, + source: MapEventSource.multiFingerStart, + ), + ); } /// Called multiple times to handle updates to the gesture void update(ScaleUpdateDetails details) { + if (_lastLocalFocal == null || + _lastScale == null || + _lastRotation == null) { + return; + } + // TODO implement - if (_lastLocalFocal == null || _lastScale == null) return; + double newRotation = _camera.rotation; + if (rotateEnabled) { + newRotation -= (_lastRotation! - details.rotation) * 80; + } + + double newZoom = _camera.zoom; + if (zoomEnabled) { + newZoom -= (_lastScale! - details.scale) * 2.2; + } + final offset = _rotateOffset(_lastLocalFocal! - details.localFocalPoint); final oldCenterPt = _camera.project(_camera.center); final newCenterPt = oldCenterPt + offset.toPoint(); final newCenter = _camera.unproject(newCenterPt); - double newZoom = _camera.zoom; - if (_lastScale == null || (_lastScale! - details.scale).abs() > 0.05) { - newZoom *= details.scale; - } - - controller.move( + controller.moveAndRotate( newCenter, newZoom, + newRotation, offset: Offset.zero, hasGesture: true, source: MapEventSource.onMultiFinger, id: null, ); + _lastRotation = details.rotation; _lastScale = details.scale; _lastLocalFocal = details.localFocalPoint; } @@ -278,6 +305,12 @@ class TwoFingerGesture extends Gesture { void end(ScaleEndDetails details) { _lastScale = null; _lastLocalFocal = null; + controller.emitMapEvent( + MapEventMoveEnd( + camera: _camera, + source: MapEventSource.multiFingerEnd, + ), + ); } /// Return a rotated Offset diff --git a/lib/src/gestures/map_interactive_viewer.dart b/lib/src/gestures/map_interactive_viewer.dart index 9276c163a..24b598ef9 100644 --- a/lib/src/gestures/map_interactive_viewer.dart +++ b/lib/src/gestures/map_interactive_viewer.dart @@ -34,7 +34,7 @@ class MapInteractiveViewerState extends State TertiaryLongPressGesture? _tertiaryLongPress; DoubleTapGesture? _doubleTap; ScrollWheelZoomGesture? _scrollWheelZoom; - TwoFingerGesture? _multiInput; + TwoFingerGestures? _twoFingerInput; DragGesture? _drag; DoubleTapDragZoomGesture? _doubleTapDragZoom; CtrlDragRotateGesture? _ctrlDragRotate; @@ -133,7 +133,7 @@ class MapInteractiveViewerState extends State } else if (_doubleTapDragZoom?.isActive ?? false) { _doubleTapDragZoom!.start(details); } else { - _multiInput?.start(details); + _twoFingerInput?.start(details); } }, onScaleUpdate: (details) { @@ -142,7 +142,7 @@ class MapInteractiveViewerState extends State } else if (_doubleTapDragZoom?.isActive ?? false) { _doubleTapDragZoom!.update(details); } else { - _multiInput?.update(details); + _twoFingerInput?.update(details); } }, onScaleEnd: (details) { @@ -152,7 +152,7 @@ class MapInteractiveViewerState extends State _doubleTapDragZoom!.isActive = false; _doubleTapDragZoom!.end(details); } else { - _multiInput?.end(details); + _twoFingerInput?.end(details); } }, @@ -163,7 +163,16 @@ class MapInteractiveViewerState extends State /// Used by the internal map controller to update interaction gestures void updateGestures(int flags) { - _multiInput = TwoFingerGesture(controller: widget.controller); + if (InteractiveFlag.hasMultiFinger(flags)) { + _twoFingerInput = TwoFingerGestures( + controller: widget.controller, + moveEnabled: InteractiveFlag.hasPinchMove(flags), + rotateEnabled: InteractiveFlag.hasRotate(flags), + zoomEnabled: InteractiveFlag.hasPinchZoom(flags), + ); + } else { + _twoFingerInput = null; + } if (InteractiveFlag.hasDrag(flags)) { _drag = DragGesture(controller: widget.controller); diff --git a/lib/src/map/controller/internal_map_controller.dart b/lib/src/map/controller/internal_map_controller.dart index 238a81cfe..07f290e28 100644 --- a/lib/src/map/controller/internal_map_controller.dart +++ b/lib/src/map/controller/internal_map_controller.dart @@ -279,16 +279,6 @@ class InternalMapController extends ValueNotifier<_InternalState> { ); } - /// To be called when a gesture that causes movement starts. - void moveStarted(MapEventSource source) { - emitMapEvent( - MapEventMoveStart( - camera: camera, - source: source, - ), - ); - } - /// To be called when an ongoing drag movement updates. void dragUpdated(MapEventSource source, Offset offset) { final oldCenterPt = camera.project(camera.center); @@ -306,28 +296,6 @@ class InternalMapController extends ValueNotifier<_InternalState> { ); } - void moveRotateZoom({ - required Offset offset, - required double rotationRad, - required double zoom, - required MapEventSource source, - }) { - final oldCenterPt = camera.project(camera.center); - - final newCenterPt = oldCenterPt + offset.toPoint(); - final newCenter = camera.unproject(newCenterPt); - - moveAndRotate( - newCenter, - camera.zoom, - rotationRad, - offset: Offset.zero, - hasGesture: true, - source: source, - id: null, - ); - } - /// To be called when a rotation gesture starts. void rotateStarted(MapEventSource source) { emitMapEvent( From 7c744decf27791554850c4bd4b7fcaf9cced7780 Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Sun, 26 Nov 2023 02:13:26 +0100 Subject: [PATCH 018/102] add pointer event callbacks --- lib/src/gestures/map_interactive_viewer.dart | 24 ++++++++++++++++++++ lib/src/map/options/options.dart | 8 +++---- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/lib/src/gestures/map_interactive_viewer.dart b/lib/src/gestures/map_interactive_viewer.dart index 24b598ef9..4ef78bb51 100644 --- a/lib/src/gestures/map_interactive_viewer.dart +++ b/lib/src/gestures/map_interactive_viewer.dart @@ -88,6 +88,30 @@ class MapInteractiveViewerState extends State @override Widget build(BuildContext context) { return Listener( + onPointerDown: _options.onPointerDown == null + ? null + : (event) => _options.onPointerDown?.call( + event, + _camera.offsetToCrs(event.localPosition), + ), + onPointerHover: _options.onPointerHover == null + ? null + : (event) => _options.onPointerHover?.call( + event, + _camera.offsetToCrs(event.localPosition), + ), + onPointerCancel: _options.onPointerCancel == null + ? null + : (event) => _options.onPointerCancel?.call( + event, + _camera.offsetToCrs(event.localPosition), + ), + onPointerUp: _options.onPointerUp == null + ? null + : (event) => _options.onPointerUp?.call( + event, + _camera.offsetToCrs(event.localPosition), + ), onPointerSignal: (event) { // mouse scroll wheel if (event is PointerScrollEvent) { diff --git a/lib/src/map/options/options.dart b/lib/src/map/options/options.dart index baa81d5db..8f1069fa8 100644 --- a/lib/src/map/options/options.dart +++ b/lib/src/map/options/options.dart @@ -56,10 +56,10 @@ class MapOptions { final LongPressCallback? onSecondaryLongPress; final GestureCallback? onTertiaryTap; final LongPressCallback? onTertiaryLongPress; - final PointerDownCallback? onPointerDown; // TODO add support for callback - final PointerUpCallback? onPointerUp; // TODO add support for callback - final PointerCancelCallback? onPointerCancel; // TODO add support for callback - final PointerHoverCallback? onPointerHover; // TODO add support for callback + final PointerDownCallback? onPointerDown; + final PointerUpCallback? onPointerUp; + final PointerCancelCallback? onPointerCancel; + final PointerHoverCallback? onPointerHover; final PositionCallback? onPositionChanged; final MapEventCallback? onMapEvent; From a2b2148d3d57d82b7df747b84b5f3abeb8a8888f Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Sun, 26 Nov 2023 02:30:32 +0100 Subject: [PATCH 019/102] add thresholds for two finger gestures --- lib/src/gestures/gestures.dart | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/lib/src/gestures/gestures.dart b/lib/src/gestures/gestures.dart index 8ab86e826..791e52aa2 100644 --- a/lib/src/gestures/gestures.dart +++ b/lib/src/gestures/gestures.dart @@ -238,6 +238,8 @@ class TwoFingerGestures extends Gesture { final bool moveEnabled; final bool rotateEnabled; final bool zoomEnabled; + bool _isZooming = false; + bool _isRotating = false; Offset? _lastLocalFocal; double? _lastScale; double? _lastRotation; @@ -254,6 +256,8 @@ class TwoFingerGestures extends Gesture { _lastLocalFocal = details.localFocalPoint; _lastScale = 1; _lastRotation = 0; + _isRotating = false; + _isZooming = false; controller.emitMapEvent( MapEventMoveStart( camera: _camera, @@ -270,15 +274,18 @@ class TwoFingerGestures extends Gesture { return; } - // TODO implement double newRotation = _camera.rotation; - if (rotateEnabled) { - newRotation -= (_lastRotation! - details.rotation) * 80; + final rotationDelta = _lastRotation! - details.rotation; + if (!_isRotating) _isRotating = rotationDelta.abs() > 0.01; + if (rotateEnabled && _isRotating) { + newRotation -= rotationDelta * 80; } double newZoom = _camera.zoom; - if (zoomEnabled) { - newZoom -= (_lastScale! - details.scale) * 2.2; + final scaleDelta = _lastScale! - details.scale; + if (!_isZooming) _isZooming = scaleDelta.abs() > 0.01; + if (zoomEnabled && _isZooming) { + newZoom -= scaleDelta * 2.2; } final offset = _rotateOffset(_lastLocalFocal! - details.localFocalPoint); @@ -305,6 +312,8 @@ class TwoFingerGestures extends Gesture { void end(ScaleEndDetails details) { _lastScale = null; _lastLocalFocal = null; + _isZooming = false; + _isRotating = false; controller.emitMapEvent( MapEventMoveEnd( camera: _camera, From 158dfb1fdc30de84d9cb8b843b5385d5858a51e7 Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Sun, 26 Nov 2023 02:49:04 +0100 Subject: [PATCH 020/102] add gesture, remove zoom and rotate threshold --- lib/src/gestures/gestures.dart | 83 ++++++++++++-------- lib/src/gestures/map_interactive_viewer.dart | 7 +- 2 files changed, 56 insertions(+), 34 deletions(-) diff --git a/lib/src/gestures/gestures.dart b/lib/src/gestures/gestures.dart index 791e52aa2..8cd54b0a3 100644 --- a/lib/src/gestures/gestures.dart +++ b/lib/src/gestures/gestures.dart @@ -238,8 +238,6 @@ class TwoFingerGestures extends Gesture { final bool moveEnabled; final bool rotateEnabled; final bool zoomEnabled; - bool _isZooming = false; - bool _isRotating = false; Offset? _lastLocalFocal; double? _lastScale; double? _lastRotation; @@ -253,11 +251,10 @@ class TwoFingerGestures extends Gesture { /// Initialize gesture, called when gesture has started void start(ScaleStartDetails details) { + if (details.pointerCount < 2) return; _lastLocalFocal = details.localFocalPoint; _lastScale = 1; _lastRotation = 0; - _isRotating = false; - _isZooming = false; controller.emitMapEvent( MapEventMoveStart( camera: _camera, @@ -268,6 +265,7 @@ class TwoFingerGestures extends Gesture { /// Called multiple times to handle updates to the gesture void update(ScaleUpdateDetails details) { + if (details.pointerCount < 2) return; if (_lastLocalFocal == null || _lastScale == null || _lastRotation == null) { @@ -275,20 +273,19 @@ class TwoFingerGestures extends Gesture { } double newRotation = _camera.rotation; - final rotationDelta = _lastRotation! - details.rotation; - if (!_isRotating) _isRotating = rotationDelta.abs() > 0.01; - if (rotateEnabled && _isRotating) { - newRotation -= rotationDelta * 80; + if (rotateEnabled) { + newRotation -= (_lastRotation! - details.rotation) * 80; } double newZoom = _camera.zoom; - final scaleDelta = _lastScale! - details.scale; - if (!_isZooming) _isZooming = scaleDelta.abs() > 0.01; - if (zoomEnabled && _isZooming) { - newZoom -= scaleDelta * 2.2; + if (zoomEnabled) { + newZoom -= (_lastScale! - details.scale) * 2.2; } - final offset = _rotateOffset(_lastLocalFocal! - details.localFocalPoint); + final offset = _rotateOffset( + _camera, + _lastLocalFocal! - details.localFocalPoint, + ); final oldCenterPt = _camera.project(_camera.center); final newCenterPt = oldCenterPt + offset.toPoint(); final newCenter = _camera.unproject(newCenterPt); @@ -310,10 +307,9 @@ class TwoFingerGestures extends Gesture { /// gesture has ended, clean up void end(ScaleEndDetails details) { + if (details.pointerCount < 2) return; _lastScale = null; _lastLocalFocal = null; - _isZooming = false; - _isRotating = false; controller.emitMapEvent( MapEventMoveEnd( camera: _camera, @@ -321,25 +317,15 @@ class TwoFingerGestures extends Gesture { ), ); } - - /// Return a rotated Offset - Offset _rotateOffset(Offset offset) { - final radians = _camera.rotationRad; - if (radians == 0) return offset; - - final cos = math.cos(radians); - final sin = math.sin(radians); - final nx = (cos * offset.dx) + (sin * offset.dy); - final ny = (cos * offset.dy) - (sin * offset.dx); - - return Offset(nx, ny); - } } class DragGesture extends Gesture { + Offset? _lastLocalFocal; + DragGesture({required super.controller}); - void start() { + void start(ScaleStartDetails details) { + _lastLocalFocal = details.localFocalPoint; controller.emitMapEvent( MapEventMoveStart( camera: _camera, @@ -348,11 +334,30 @@ class DragGesture extends Gesture { ); } - void update() { - // TODO make use of the drag gesture + void update(ScaleUpdateDetails details) { + if (_lastLocalFocal == null) return; + + final offset = _rotateOffset( + _camera, + _lastLocalFocal! - details.localFocalPoint, + ); + final oldCenterPt = _camera.project(_camera.center); + final newCenterPt = oldCenterPt + offset.toPoint(); + final newCenter = _camera.unproject(newCenterPt); + + controller.move( + newCenter, + _camera.zoom, + offset: Offset.zero, + hasGesture: true, + source: MapEventSource.onDrag, + id: null, + ); + + _lastLocalFocal = details.localFocalPoint; } - void end() { + void end(ScaleEndDetails details) { controller.emitMapEvent( MapEventMoveEnd( camera: _camera, @@ -424,7 +429,6 @@ class DoubleTapDragZoomGesture extends Gesture { } void update(ScaleUpdateDetails details) { - // TODO make use of the double tap drag zoom gesture if (_focalLocalStart == null || _mapZoomStart == null) return; final verticalOffset = (_focalLocalStart! - details.localFocalPoint).dy; @@ -453,3 +457,16 @@ class DoubleTapDragZoomGesture extends Gesture { ); } } + +/// Return a rotated Offset +Offset _rotateOffset(MapCamera camera, Offset offset) { + final radians = camera.rotationRad; + if (radians == 0) return offset; + + final cos = math.cos(radians); + final sin = math.sin(radians); + final nx = (cos * offset.dx) + (sin * offset.dy); + final ny = (cos * offset.dy) - (sin * offset.dx); + + return Offset(nx, ny); +} diff --git a/lib/src/gestures/map_interactive_viewer.dart b/lib/src/gestures/map_interactive_viewer.dart index 4ef78bb51..98366ada3 100644 --- a/lib/src/gestures/map_interactive_viewer.dart +++ b/lib/src/gestures/map_interactive_viewer.dart @@ -156,6 +156,8 @@ class MapInteractiveViewerState extends State _ctrlDragRotate!.start(); } else if (_doubleTapDragZoom?.isActive ?? false) { _doubleTapDragZoom!.start(details); + } else if (details.pointerCount == 1) { + _drag?.start(details); } else { _twoFingerInput?.start(details); } @@ -165,6 +167,8 @@ class MapInteractiveViewerState extends State _ctrlDragRotate!.update(details); } else if (_doubleTapDragZoom?.isActive ?? false) { _doubleTapDragZoom!.update(details); + } else if (details.pointerCount == 1) { + _drag?.update(details); } else { _twoFingerInput?.update(details); } @@ -175,6 +179,8 @@ class MapInteractiveViewerState extends State } else if (_doubleTapDragZoom?.isActive ?? false) { _doubleTapDragZoom!.isActive = false; _doubleTapDragZoom!.end(details); + } else if (details.pointerCount == 1) { + _drag?.end(details); } else { _twoFingerInput?.end(details); } @@ -201,7 +207,6 @@ class MapInteractiveViewerState extends State if (InteractiveFlag.hasDrag(flags)) { _drag = DragGesture(controller: widget.controller); } else { - _drag?.end(); _drag = null; } From 09be7e7b676667c6410b7113306b04fa0a0da582 Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Sun, 26 Nov 2023 03:02:36 +0100 Subject: [PATCH 021/102] change default scroll wheel velocity --- lib/src/gestures/gestures.dart | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/src/gestures/gestures.dart b/lib/src/gestures/gestures.dart index 8cd54b0a3..8678510a7 100644 --- a/lib/src/gestures/gestures.dart +++ b/lib/src/gestures/gestures.dart @@ -1,4 +1,5 @@ import 'dart:math' as math; +import 'dart:ui'; import 'package:flutter/gestures.dart'; import 'package:flutter/services.dart'; @@ -214,8 +215,11 @@ class ScrollWheelZoomGesture extends Gesture { final minZoom = _options.minZoom ?? 0.0; final maxZoom = _options.maxZoom ?? double.infinity; final velocity = _options.interactionOptions.scrollWheelVelocity; - final newZoom = (_camera.zoom - details.scrollDelta.dy * velocity) - .clamp(minZoom, maxZoom); + final newZoom = clampDouble( + _camera.zoom - details.scrollDelta.dy * velocity * 2, + minZoom, + maxZoom, + ); // Calculate offset of mouse cursor from viewport center final newCenter = _camera.focusedZoomCenter( details.localPosition.toPoint(), From 30fded1c363d03de52a6bc13cad0f93abf47f4b0 Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Fri, 8 Dec 2023 01:11:35 +0100 Subject: [PATCH 022/102] Delete internal_map_controller.dart --- .../controller/internal_map_controller.dart | 387 ------------------ 1 file changed, 387 deletions(-) delete mode 100644 lib/src/map/controller/internal_map_controller.dart diff --git a/lib/src/map/controller/internal_map_controller.dart b/lib/src/map/controller/internal_map_controller.dart deleted file mode 100644 index 07f290e28..000000000 --- a/lib/src/map/controller/internal_map_controller.dart +++ /dev/null @@ -1,387 +0,0 @@ -import 'dart:math'; - -import 'package:flutter/foundation.dart'; -import 'package:flutter/rendering.dart'; -import 'package:flutter_map/flutter_map.dart'; -import 'package:flutter_map/src/gestures/map_interactive_viewer.dart'; - -/// This controller is for internal use. All updates to the state should be done -/// by calling methods of this class to ensure consistency. -class InternalMapController extends ValueNotifier<_InternalState> { - late final MapInteractiveViewerState _interactiveViewerState; - late MapControllerImpl _mapControllerImpl; - - InternalMapController(MapOptions options) - : super( - _InternalState( - options: options, - camera: MapCamera.initialCamera(options), - ), - ); - - /// Link the viewer state with the controller. This should be done once when - /// the MapInteractiveViewerState is initialized. - set interactiveViewerState( - MapInteractiveViewerState interactiveViewerState, - ) => - _interactiveViewerState = interactiveViewerState; - - MapOptions get options => value.options; - - MapCamera get camera => value.camera; - - void linkMapController(MapControllerImpl mapControllerImpl) { - _mapControllerImpl = mapControllerImpl; - _mapControllerImpl.internalController = this; - } - - /// This setter should only be called in this class or within tests. Changes - /// to the [FlutterMapInternalState] should be done via methods in this class. - @visibleForTesting - @override - // ignore: library_private_types_in_public_api - set value(_InternalState value) => super.value = value; - - /// Note: All named parameters are required to prevent inconsistent default - /// values since this method can be called by MapController which declares - /// defaults. - bool move( - LatLng newCenter, - double newZoom, { - required Offset offset, - required bool hasGesture, - required MapEventSource source, - required String? id, - }) { - // Algorithm thanks to https://github.com/tlserver/flutter_map_location_marker - if (offset != Offset.zero) { - final newPoint = camera.project(newCenter, newZoom); - newCenter = camera.unproject( - camera.rotatePoint( - newPoint, - newPoint - Point(offset.dx, offset.dy), - ), - newZoom, - ); - } - - MapCamera? newCamera = camera.withPosition( - center: newCenter, - zoom: camera.clampZoom(newZoom), - ); - - newCamera = options.cameraConstraint.constrain(newCamera); - if (newCamera == null || - (newCamera.center == camera.center && newCamera.zoom == camera.zoom)) { - return false; - } - - final oldCamera = camera; - value = value.withMapCamera(newCamera); - - final movementEvent = MapEventWithMove.fromSource( - oldCamera: oldCamera, - camera: camera, - hasGesture: hasGesture, - source: source, - id: id, - ); - if (movementEvent != null) emitMapEvent(movementEvent); - - options.onPositionChanged?.call( - MapPosition( - center: newCenter, - bounds: camera.visibleBounds, - zoom: newZoom, - hasGesture: hasGesture, - ), - hasGesture, - ); - - return true; - } - - /// Note: All named parameters are required to prevent inconsistent default - /// values since this method can be called by MapController which declares - /// defaults. - bool rotate( - double newRotation, { - required bool hasGesture, - required MapEventSource source, - required String? id, - }) { - if (newRotation != camera.rotation) { - final newCamera = options.cameraConstraint.constrain( - camera.withRotation(newRotation), - ); - if (newCamera == null) return false; - - final oldCamera = camera; - - // Update camera then emit events and callbacks - value = value.withMapCamera(newCamera); - - emitMapEvent( - MapEventRotate( - id: id, - source: source, - oldCamera: oldCamera, - camera: camera, - ), - ); - return true; - } - - return false; - } - - /// Note: All named parameters are required to prevent inconsistent default - /// values since this method can be called by MapController which declares - /// defaults. - MoveAndRotateResult rotateAroundPoint( - double degree, { - required Point? point, - required Offset? offset, - required bool hasGesture, - required MapEventSource source, - required String? id, - }) { - if (point != null && offset != null) { - throw ArgumentError('Only one of `point` or `offset` may be non-null'); - } - if (point == null && offset == null) { - throw ArgumentError('One of `point` or `offset` must be non-null'); - } - - if (degree == camera.rotation) { - return const (moveSuccess: false, rotateSuccess: false); - } - - if (offset == Offset.zero) { - return ( - moveSuccess: true, - rotateSuccess: rotate( - degree, - hasGesture: hasGesture, - source: source, - id: id, - ), - ); - } - - final rotationDiff = degree - camera.rotation; - final rotationCenter = camera.project(camera.center) + - (point != null - ? (point - (camera.nonRotatedSize / 2.0)) - : Point(offset!.dx, offset.dy)) - .rotate(camera.rotationRad); - - return ( - moveSuccess: move( - camera.unproject( - rotationCenter + - (camera.project(camera.center) - rotationCenter) - .rotate(degToRadian(rotationDiff)), - ), - camera.zoom, - offset: Offset.zero, - hasGesture: hasGesture, - source: source, - id: id, - ), - rotateSuccess: rotate( - camera.rotation + rotationDiff, - hasGesture: hasGesture, - source: source, - id: id, - ), - ); - } - - /// Note: All named parameters are required to prevent inconsistent default - /// values since this method can be called by MapController which declares - /// defaults. - MoveAndRotateResult moveAndRotate( - LatLng newCenter, - double newZoom, - double newRotation, { - required Offset offset, - required bool hasGesture, - required MapEventSource source, - required String? id, - }) => - ( - moveSuccess: move( - newCenter, - newZoom, - offset: offset, - hasGesture: hasGesture, - source: source, - id: id, - ), - rotateSuccess: - rotate(newRotation, id: id, source: source, hasGesture: hasGesture), - ); - - /// Note: All named parameters are required to prevent inconsistent default - /// values since this method can be called by MapController which declares - /// defaults. - bool fitCamera( - CameraFit cameraFit, { - required Offset offset, - }) { - final fitted = cameraFit.fit(camera); - - return move( - fitted.center, - fitted.zoom, - offset: offset, - hasGesture: false, - source: MapEventSource.mapController, - id: null, - ); - } - - bool setNonRotatedSizeWithoutEmittingEvent( - Point nonRotatedSize, - ) { - if (nonRotatedSize != MapCamera.kImpossibleSize && - nonRotatedSize != camera.nonRotatedSize) { - value = value.withMapCamera(camera.withNonRotatedSize(nonRotatedSize)); - return true; - } - - return false; - } - - void setOptions(MapOptions newOptions) { - assert( - newOptions != value.options, - 'Should not update options unless they change', - ); - - final newCamera = camera.withOptions(newOptions); - - assert( - newOptions.cameraConstraint.constrain(newCamera) == newCamera, - 'MapCamera is no longer within the cameraConstraint after an option change.', - ); - - if (options.interactionOptions != newOptions.interactionOptions) { - _interactiveViewerState.updateGestures( - newOptions.interactionOptions.flags, - ); - } - - value = _InternalState( - options: newOptions, - camera: newCamera, - ); - } - - /// To be called when an ongoing drag movement updates. - void dragUpdated(MapEventSource source, Offset offset) { - final oldCenterPt = camera.project(camera.center); - - final newCenterPt = oldCenterPt + offset.toPoint(); - final newCenter = camera.unproject(newCenterPt); - - move( - newCenter, - camera.zoom, - offset: Offset.zero, - hasGesture: true, - source: source, - id: null, - ); - } - - /// To be called when a rotation gesture starts. - void rotateStarted(MapEventSource source) { - emitMapEvent( - MapEventRotateStart( - camera: camera, - source: source, - ), - ); - } - - /// To be called when a rotation gesture ends. - void rotateEnded(MapEventSource source) { - emitMapEvent( - MapEventRotateEnd( - camera: camera, - source: source, - ), - ); - } - - /// To be called when a fling gesture starts. - void flingStarted(MapEventSource source) { - emitMapEvent( - MapEventFlingAnimationStart( - camera: camera, - source: MapEventSource.flingAnimationController, - ), - ); - } - - /// To be called when a fling gesture ends. - void flingEnded(MapEventSource source) { - emitMapEvent( - MapEventFlingAnimationEnd( - camera: camera, - source: source, - ), - ); - } - - /// To be called when a fling gesture does not start. - void flingNotStarted(MapEventSource source) { - emitMapEvent( - MapEventFlingAnimationNotStarted( - camera: camera, - source: source, - ), - ); - } - - /// To be called when the map's size constraints change. - void nonRotatedSizeChange( - MapEventSource source, - MapCamera oldCamera, - MapCamera newCamera, - ) { - emitMapEvent( - MapEventNonRotatedSizeChange( - source: MapEventSource.nonRotatedSizeChange, - oldCamera: oldCamera, - camera: newCamera, - ), - ); - } - - void emitMapEvent(MapEvent event) { - if (event.source == MapEventSource.mapController && event is MapEventMove) { - _interactiveViewerState.interruptAnimatedMovement(event); - } - options.onMapEvent?.call(event); - _mapControllerImpl.mapEventSink.add(event); - } -} - -@immutable -class _InternalState { - final MapCamera camera; - final MapOptions options; - - const _InternalState({ - required this.options, - required this.camera, - }); - - _InternalState withMapCamera(MapCamera camera) => _InternalState( - options: options, - camera: camera, - ); -} From f25abccf257c77ca401fa5be1360df2412b93acc Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Wed, 13 Dec 2023 00:13:16 +0100 Subject: [PATCH 023/102] fix imports --- test/full_coverage_test.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/full_coverage_test.dart b/test/full_coverage_test.dart index 0f567f3ff..cfa987629 100644 --- a/test/full_coverage_test.dart +++ b/test/full_coverage_test.dart @@ -6,8 +6,6 @@ import 'package:flutter_map/src/gestures/interactive_flag.dart'; import 'package:flutter_map/src/gestures/latlng_tween.dart'; import 'package:flutter_map/src/gestures/map_events.dart'; import 'package:flutter_map/src/gestures/map_interactive_viewer.dart'; -import 'package:flutter_map/src/gestures/multi_finger_gesture.dart'; -import 'package:flutter_map/src/gestures/positioned_tap_detector_2.dart'; import 'package:flutter_map/src/layer/attribution_layer/rich/animation.dart'; import 'package:flutter_map/src/layer/attribution_layer/rich/source.dart'; import 'package:flutter_map/src/layer/attribution_layer/rich/widget.dart'; From 26f24e6226040da1c1592815f90536f1ed161f27 Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Wed, 13 Dec 2023 00:16:34 +0100 Subject: [PATCH 024/102] fix merge --- lib/src/gestures/map_interactive_viewer.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/src/gestures/map_interactive_viewer.dart b/lib/src/gestures/map_interactive_viewer.dart index baec6d82b..da1dc089d 100644 --- a/lib/src/gestures/map_interactive_viewer.dart +++ b/lib/src/gestures/map_interactive_viewer.dart @@ -47,6 +47,7 @@ class MapInteractiveViewerState extends State @override void initState() { super.initState(); + widget.controller.interactiveViewerState = this; widget.controller.addListener(reload); // callback gestures for the application From 7166dcd5f4f6a41d68a5ea2bae9955dfc759871d Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Wed, 13 Dec 2023 00:23:37 +0100 Subject: [PATCH 025/102] add doubleTapZoomIn --- lib/src/gestures/gestures.dart | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/src/gestures/gestures.dart b/lib/src/gestures/gestures.dart index a6e346cea..4d887f079 100644 --- a/lib/src/gestures/gestures.dart +++ b/lib/src/gestures/gestures.dart @@ -1,6 +1,7 @@ import 'dart:math' as math; import 'dart:ui'; +import 'package:flutter/animation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/services.dart'; import 'package:flutter_map/flutter_map.dart'; @@ -115,8 +116,6 @@ class DoubleTapGesture extends DelayedGesture { if (details == null) return; // start double tap animation - // TODO animate moveRawment - //controller.doubleTapZoomStarted(MapEventSource.doubleTap); final newZoom = _getZoomForScale(_camera.zoom, 2); final newCenter = _camera.focusedZoomCenter( details!.localPosition.toPoint(), @@ -130,11 +129,13 @@ class DoubleTapGesture extends DelayedGesture { ), ); - controller.moveRaw( + controller.moveAnimatedRaw( newCenter, newZoom, hasGesture: true, source: MapEventSource.doubleTap, + curve: Curves.fastOutSlowIn, + duration: const Duration(milliseconds: 200), ); controller.emitMapEvent( From 7db3361dc85fffa7e68138d893fa2053fb93f222 Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Wed, 13 Dec 2023 00:45:18 +0100 Subject: [PATCH 026/102] fix `DragGesture` end callback --- lib/src/gestures/map_interactive_viewer.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/gestures/map_interactive_viewer.dart b/lib/src/gestures/map_interactive_viewer.dart index da1dc089d..3c96cb057 100644 --- a/lib/src/gestures/map_interactive_viewer.dart +++ b/lib/src/gestures/map_interactive_viewer.dart @@ -179,7 +179,7 @@ class MapInteractiveViewerState extends State } else if (_doubleTapDragZoom?.isActive ?? false) { _doubleTapDragZoom!.isActive = false; _doubleTapDragZoom!.end(details); - } else if (details.pointerCount == 1) { + } else if (_drag?.isActive ?? false) { _drag?.end(details); } else { _twoFingerInput?.end(details); From 0c6e859b5b3dd88c7dfc8e6a9e96954a343bc898 Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Wed, 13 Dec 2023 00:45:37 +0100 Subject: [PATCH 027/102] stop animation on gesture input --- lib/src/gestures/gestures.dart | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lib/src/gestures/gestures.dart b/lib/src/gestures/gestures.dart index 4d887f079..238f62545 100644 --- a/lib/src/gestures/gestures.dart +++ b/lib/src/gestures/gestures.dart @@ -32,6 +32,7 @@ class TapGesture extends DelayedGesture { /// A tap with a primary button has occurred. /// This triggers when the tap gesture wins. void submit() { + controller.stopAnimationRaw(); if (details == null) return; final point = _camera.offsetToCrs(details!.localPosition); @@ -55,6 +56,7 @@ class LongPressGesture extends Gesture { /// recognized. A pointer has remained in contact with the screen at the /// same location for a long period of time. void submit(LongPressStartDetails details) { + controller.stopAnimationRaw(); final position = _camera.offsetToCrs(details.localPosition); _options.onLongPress?.call(details, position); controller.emitMapEvent( @@ -74,6 +76,7 @@ class SecondaryLongPressGesture extends Gesture { /// recognized. A pointer has remained in contact with the screen at the /// same location for a long period of time. void submit(LongPressStartDetails details) { + controller.stopAnimationRaw(); final position = _camera.offsetToCrs(details.localPosition); _options.onSecondaryLongPress?.call(details, position); controller.emitMapEvent( @@ -92,6 +95,7 @@ class SecondaryTapGesture extends DelayedGesture { /// A tap with a secondary button has occurred. /// This triggers when the tap gesture wins. void submit() { + controller.stopAnimationRaw(); if (details == null) return; final position = _camera.offsetToCrs(details!.localPosition); @@ -113,6 +117,7 @@ class DoubleTapGesture extends DelayedGesture { /// A double tap gesture tap has been registered void submit() { + controller.stopAnimationRaw(); if (details == null) return; // start double tap animation @@ -163,6 +168,7 @@ class TertiaryTapGesture extends DelayedGesture { /// A tertiary tap gesture has happen (e.g. click on the mouse scroll wheel) void submit(TapUpDetails _) { + controller.stopAnimationRaw(); if (details == null) return; final point = _camera.offsetToCrs(details!.localPosition); @@ -185,6 +191,7 @@ class TertiaryLongPressGesture extends DelayedGesture { /// A long press on the tertiary button has happen (e.g. click and hold on /// the mouse scroll wheel) void submit(LongPressStartDetails details) { + controller.stopAnimationRaw(); final point = _camera.offsetToCrs(details.localPosition); _options.onTertiaryLongPress?.call(details, point); controller.emitMapEvent( @@ -204,6 +211,7 @@ class ScrollWheelZoomGesture extends Gesture { /// Handles mouse scroll events void submit(PointerScrollEvent details) { + controller.stopAnimationRaw(); if (details.scrollDelta.dy == 0) return; // Prevent scrolling of parent/child widgets simultaneously. @@ -251,6 +259,7 @@ class TwoFingerGestures extends Gesture { /// Initialize gesture, called when gesture has started void start(ScaleStartDetails details) { + controller.stopAnimationRaw(); if (details.pointerCount < 2) return; _lastLocalFocal = details.localFocalPoint; _lastScale = 1; @@ -323,7 +332,10 @@ class DragGesture extends Gesture { DragGesture({required super.controller}); + bool get isActive => _lastLocalFocal != null; + void start(ScaleStartDetails details) { + controller.stopAnimationRaw(); _lastLocalFocal = details.localFocalPoint; controller.emitMapEvent( MapEventMoveStart( @@ -370,6 +382,7 @@ class CtrlDragRotateGesture extends Gesture { CtrlDragRotateGesture({required super.controller}); void start() { + controller.stopAnimationRaw(); controller.emitMapEvent( MapEventRotateStart( camera: _camera, @@ -414,6 +427,7 @@ class DoubleTapDragZoomGesture extends Gesture { DoubleTapDragZoomGesture({required super.controller}); void start(ScaleStartDetails details) { + controller.stopAnimationRaw(); _focalLocalStart = details.localFocalPoint; _mapZoomStart = _camera.zoom; controller.emitMapEvent( From 2ea24e0389c5fb47afb0f9d66ee2eda5dcdef87b Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Wed, 13 Dec 2023 01:15:46 +0100 Subject: [PATCH 028/102] add flingAnimation --- lib/src/gestures/gestures.dart | 40 +++++++++++++++++++- lib/src/gestures/map_interactive_viewer.dart | 5 ++- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/lib/src/gestures/gestures.dart b/lib/src/gestures/gestures.dart index 238f62545..5e29933cc 100644 --- a/lib/src/gestures/gestures.dart +++ b/lib/src/gestures/gestures.dart @@ -328,15 +328,19 @@ class TwoFingerGestures extends Gesture { } class DragGesture extends Gesture { + final bool flingEnabled; + Offset? _lastLocalFocal; + Offset? _focalStartLocal; - DragGesture({required super.controller}); + DragGesture({required super.controller, required this.flingEnabled}); bool get isActive => _lastLocalFocal != null; void start(ScaleStartDetails details) { controller.stopAnimationRaw(); _lastLocalFocal = details.localFocalPoint; + _focalStartLocal = details.localFocalPoint; controller.emitMapEvent( MapEventMoveStart( camera: _camera, @@ -373,6 +377,40 @@ class DragGesture extends Gesture { source: MapEventSource.dragEnd, ), ); + final lastLocalFocal = _lastLocalFocal!; + final focalStartLocal = _focalStartLocal!; + _lastLocalFocal = null; + _focalStartLocal = null; + + if (!flingEnabled) return; + + final magnitude = details.velocity.pixelsPerSecond.distance; + + // don't start fling if the magnitude is not high enough + if (magnitude < 800) { + controller.emitMapEvent( + MapEventFlingAnimationNotStarted( + source: MapEventSource.flingAnimationController, + camera: _camera, + ), + ); + return; + } + + final direction = details.velocity.pixelsPerSecond / magnitude; + + controller.flingAnimatedRaw( + velocity: magnitude / 1000.0, + direction: direction, + begin: focalStartLocal - lastLocalFocal, + hasGesture: true, + ); + controller.emitMapEvent( + MapEventFlingAnimationStart( + source: MapEventSource.flingAnimationController, + camera: _camera, + ), + ); } } diff --git a/lib/src/gestures/map_interactive_viewer.dart b/lib/src/gestures/map_interactive_viewer.dart index 3c96cb057..6c8574838 100644 --- a/lib/src/gestures/map_interactive_viewer.dart +++ b/lib/src/gestures/map_interactive_viewer.dart @@ -205,7 +205,10 @@ class MapInteractiveViewerState extends State } if (InteractiveFlag.hasDrag(flags)) { - _drag = DragGesture(controller: widget.controller); + _drag = DragGesture( + controller: widget.controller, + flingEnabled: InteractiveFlag.hasFlingAnimation(flags), + ); } else { _drag = null; } From ec0cef791fae9eae3552016c10d7422b5c84a21c Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Wed, 13 Dec 2023 02:07:32 +0100 Subject: [PATCH 029/102] Update flutter_map_test.dart --- test/flutter_map_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/flutter_map_test.dart b/test/flutter_map_test.dart index 7534c3046..41da4b439 100644 --- a/test/flutter_map_test.dart +++ b/test/flutter_map_test.dart @@ -96,7 +96,7 @@ void main() { backgroundColor: Colors.transparent, maxZoom: 9, initialZoom: 10, // Higher than maxZoom. - onTap: (_, ___) { + onTap: (_, __) { taps++; }, ), From e3738a16e7ddb2df0a235a4972cacf19a104859a Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Wed, 13 Dec 2023 13:13:28 +0100 Subject: [PATCH 030/102] Update full_coverage_test.dart --- test/full_coverage_test.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/test/full_coverage_test.dart b/test/full_coverage_test.dart index cfa987629..a9760cd1b 100644 --- a/test/full_coverage_test.dart +++ b/test/full_coverage_test.dart @@ -2,6 +2,7 @@ import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_map/src/geo/crs.dart'; import 'package:flutter_map/src/geo/latlng_bounds.dart'; +import 'package:flutter_map/src/gestures/gestures.dart'; import 'package:flutter_map/src/gestures/interactive_flag.dart'; import 'package:flutter_map/src/gestures/latlng_tween.dart'; import 'package:flutter_map/src/gestures/map_events.dart'; From 92e360b8f53c9ce89c92fcf25a21f519a570bc6b Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Wed, 13 Dec 2023 13:20:46 +0100 Subject: [PATCH 031/102] use bool fields for interactiveFlags, keep support for bitfield --- example/lib/pages/interactive_test_page.dart | 2 +- example/lib/pages/latlng_to_screen_point.dart | 6 +- example/lib/pages/many_circles.dart | 2 +- example/lib/pages/many_markers.dart | 2 +- example/lib/pages/markers.dart | 5 +- lib/flutter_map.dart | 1 + lib/src/gestures/gestures.dart | 13 +- lib/src/gestures/interactive_flag.dart | 34 ----- lib/src/gestures/interactive_flags.dart | 119 ++++++++++++++++++ lib/src/gestures/map_interactive_viewer.dart | 25 ++-- .../map/controller/map_controller_impl.dart | 5 - lib/src/map/options/interaction.dart | 7 +- test/flutter_map_test.dart | 19 +-- test/full_coverage_test.dart | 1 + 14 files changed, 157 insertions(+), 84 deletions(-) create mode 100644 lib/src/gestures/interactive_flags.dart diff --git a/example/lib/pages/interactive_test_page.dart b/example/lib/pages/interactive_test_page.dart index a7ee41a80..7843c2f5a 100644 --- a/example/lib/pages/interactive_test_page.dart +++ b/example/lib/pages/interactive_test_page.dart @@ -121,7 +121,7 @@ class _InteractiveFlagsPageState extends State { initialCenter: const LatLng(51.5, -0.09), initialZoom: 11, interactionOptions: InteractionOptions( - flags: flags, + flags: InteractiveFlags.bitfield(flags), cursorKeyboardRotationOptions: CursorKeyboardRotationOptions( isKeyTrigger: (key) => diff --git a/example/lib/pages/latlng_to_screen_point.dart b/example/lib/pages/latlng_to_screen_point.dart index 4b35f2920..9391048e6 100644 --- a/example/lib/pages/latlng_to_screen_point.dart +++ b/example/lib/pages/latlng_to_screen_point.dart @@ -47,8 +47,10 @@ class _LatLngToScreenPointPageState extends State { options: MapOptions( initialCenter: const LatLng(51.5, -0.09), initialZoom: 11, - interactionOptions: const InteractionOptions( - flags: ~InteractiveFlag.doubleTapZoom, + interactionOptions: InteractionOptions( + flags: InteractiveFlags.bitfield( + ~InteractiveFlag.doubleTapZoom, + ), ), onTap: (_, latLng) { final point = mapController.camera diff --git a/example/lib/pages/many_circles.dart b/example/lib/pages/many_circles.dart index 68ea62d8a..5d0208824 100644 --- a/example/lib/pages/many_circles.dart +++ b/example/lib/pages/many_circles.dart @@ -74,7 +74,7 @@ class ManyCirclesPageState extends State { initialCenter: LatLng(50, 20), initialZoom: 5, interactionOptions: InteractionOptions( - flags: InteractiveFlag.all - InteractiveFlag.rotate, + flags: InteractiveFlags.all(rotate: false), ), ), children: [ diff --git a/example/lib/pages/many_markers.dart b/example/lib/pages/many_markers.dart index 5b19aa8fb..7d616c274 100644 --- a/example/lib/pages/many_markers.dart +++ b/example/lib/pages/many_markers.dart @@ -72,7 +72,7 @@ class ManyMarkersPageState extends State { initialCenter: LatLng(50, 20), initialZoom: 5, interactionOptions: InteractionOptions( - flags: InteractiveFlag.all - InteractiveFlag.rotate, + flags: InteractiveFlags.all(rotate: false), ), ), children: [ diff --git a/example/lib/pages/markers.dart b/example/lib/pages/markers.dart index 3e44f35d1..264596cc5 100644 --- a/example/lib/pages/markers.dart +++ b/example/lib/pages/markers.dart @@ -113,8 +113,9 @@ class _MarkerPageState extends State { onTap: (_, p) { setState(() => customMarkers.add(buildPin(p))); }, - interactionOptions: const InteractionOptions( - flags: ~InteractiveFlag.doubleTapZoom, + interactionOptions: InteractionOptions( + flags: + InteractiveFlags.bitfield(~InteractiveFlag.doubleTapZoom), ), ), children: [ diff --git a/lib/flutter_map.dart b/lib/flutter_map.dart index 822f010c3..dba33d199 100644 --- a/lib/flutter_map.dart +++ b/lib/flutter_map.dart @@ -19,6 +19,7 @@ library flutter_map; export 'package:flutter_map/src/geo/crs.dart'; export 'package:flutter_map/src/geo/latlng_bounds.dart'; export 'package:flutter_map/src/gestures/interactive_flag.dart'; +export 'package:flutter_map/src/gestures/interactive_flags.dart'; export 'package:flutter_map/src/gestures/latlng_tween.dart'; export 'package:flutter_map/src/gestures/map_events.dart'; export 'package:flutter_map/src/layer/attribution_layer/rich/animation.dart'; diff --git a/lib/src/gestures/gestures.dart b/lib/src/gestures/gestures.dart index 5e29933cc..23955755b 100644 --- a/lib/src/gestures/gestures.dart +++ b/lib/src/gestures/gestures.dart @@ -251,11 +251,11 @@ class TwoFingerGestures extends Gesture { double? _lastRotation; TwoFingerGestures({ - required this.moveEnabled, - required this.rotateEnabled, - required this.zoomEnabled, + required InteractiveFlags interactiveFlags, required super.controller, - }); + }) : moveEnabled = interactiveFlags.pinchMove, + zoomEnabled = interactiveFlags.pinchZoom, + rotateEnabled = interactiveFlags.rotate; /// Initialize gesture, called when gesture has started void start(ScaleStartDetails details) { @@ -333,7 +333,10 @@ class DragGesture extends Gesture { Offset? _lastLocalFocal; Offset? _focalStartLocal; - DragGesture({required super.controller, required this.flingEnabled}); + DragGesture({ + required super.controller, + required InteractiveFlags interactiveFlags, + }) : flingEnabled = interactiveFlags.flingAnimation; bool get isActive => _lastLocalFocal != null; diff --git a/lib/src/gestures/interactive_flag.dart b/lib/src/gestures/interactive_flag.dart index 627511251..ee3e5b31c 100644 --- a/lib/src/gestures/interactive_flag.dart +++ b/lib/src/gestures/interactive_flag.dart @@ -59,9 +59,6 @@ abstract class InteractiveFlag { /// To change the key see [InteractionOptions.cursorKeyboardRotationOptions]. static const int ctrlDragRotate = 1 << 8; - /// Flags pertaining to gestures which require multiple fingers. - static const _multiFingerFlags = pinchMove | pinchZoom | rotate; - /// Returns `true` if [leftFlags] has at least one member in [rightFlags] /// (intersection) for example [leftFlags]= [InteractiveFlag.drag] | /// [InteractiveFlag.rotate] and [rightFlags]= [InteractiveFlag.rotate] | @@ -70,35 +67,4 @@ abstract class InteractiveFlag { static bool hasFlag(int leftFlags, int rightFlags) { return leftFlags & rightFlags != 0; } - - /// True if any multi-finger gesture flags are enabled. - static bool hasMultiFinger(int flags) => hasFlag(flags, _multiFingerFlags); - - /// True if the [drag] interactive flag is enabled. - static bool hasDrag(int flags) => hasFlag(flags, drag); - - /// True if the [flingAnimation] interactive flag is enabled. - static bool hasFlingAnimation(int flags) => hasFlag(flags, flingAnimation); - - /// True if the [pinchMove] interactive flag is enabled. - static bool hasPinchMove(int flags) => hasFlag(flags, pinchMove); - - /// True if the [pinchZoom] interactive flag is enabled. - static bool hasPinchZoom(int flags) => hasFlag(flags, pinchZoom); - - /// True if the [doubleTapDragZoom] interactive flag is enabled. - static bool hasDoubleTapDragZoom(int flags) => - hasFlag(flags, doubleTapDragZoom); - - /// True if the [doubleTapZoom] interactive flag is enabled. - static bool hasDoubleTapZoom(int flags) => hasFlag(flags, doubleTapZoom); - - /// True if the [rotate] interactive flag is enabled. - static bool hasRotate(int flags) => hasFlag(flags, rotate); - - /// True if the [scrollWheelZoom] interactive flag is enabled. - static bool hasScrollWheelZoom(int flags) => hasFlag(flags, scrollWheelZoom); - - /// True if the [ctrlDragRotate] interactive flag is enabled. - static bool hasCtrlDragRotate(int flags) => hasFlag(flags, ctrlDragRotate); } diff --git a/lib/src/gestures/interactive_flags.dart b/lib/src/gestures/interactive_flags.dart new file mode 100644 index 000000000..32fea86dc --- /dev/null +++ b/lib/src/gestures/interactive_flags.dart @@ -0,0 +1,119 @@ +import 'package:flutter_map/flutter_map.dart'; +import 'package:meta/meta.dart'; + +@immutable +class InteractiveFlags { + const InteractiveFlags._({ + required this.drag, + required this.flingAnimation, + required this.pinchMove, + required this.pinchZoom, + required this.doubleTapZoom, + required this.doubleTapDragZoom, + required this.scrollWheelZoom, + required this.rotate, + required this.ctrlDragRotate, + }); + + const InteractiveFlags.all({ + this.drag = true, + this.flingAnimation = true, + this.pinchMove = true, + this.pinchZoom = true, + this.doubleTapZoom = true, + this.doubleTapDragZoom = true, + this.scrollWheelZoom = true, + this.rotate = true, + this.ctrlDragRotate = true, + }); + + const InteractiveFlags.none({ + this.drag = false, + this.flingAnimation = false, + this.pinchMove = false, + this.pinchZoom = false, + this.doubleTapZoom = false, + this.doubleTapDragZoom = false, + this.scrollWheelZoom = false, + this.rotate = false, + this.ctrlDragRotate = false, + }); + + factory InteractiveFlags.bitfield(int flags) { + return InteractiveFlags._( + drag: InteractiveFlag.hasFlag(flags, InteractiveFlag.drag), + flingAnimation: + InteractiveFlag.hasFlag(flags, InteractiveFlag.flingAnimation), + pinchMove: InteractiveFlag.hasFlag(flags, InteractiveFlag.pinchMove), + pinchZoom: InteractiveFlag.hasFlag(flags, InteractiveFlag.pinchZoom), + doubleTapZoom: + InteractiveFlag.hasFlag(flags, InteractiveFlag.doubleTapZoom), + doubleTapDragZoom: + InteractiveFlag.hasFlag(flags, InteractiveFlag.doubleTapDragZoom), + scrollWheelZoom: + InteractiveFlag.hasFlag(flags, InteractiveFlag.scrollWheelZoom), + rotate: InteractiveFlag.hasFlag(flags, InteractiveFlag.rotate), + ctrlDragRotate: + InteractiveFlag.hasFlag(flags, InteractiveFlag.ctrlDragRotate), + ); + } + + /// Enable panning with a single finger or cursor + final bool drag; + + /// Enable fling animation after panning if velocity is great enough. + final bool flingAnimation; + + /// Enable panning with multiple fingers + final bool pinchMove; + + /// Enable zooming with a multi-finger pinch gesture + final bool pinchZoom; + + /// Enable zooming with a single-finger double tap gesture + final bool doubleTapZoom; + + /// Enable zooming with a single-finger double-tap-drag gesture + /// + /// The associated [MapEventSource] is [MapEventSource.doubleTapHold]. + final bool doubleTapDragZoom; + + /// Enable zooming with a mouse scroll wheel + final bool scrollWheelZoom; + + /// Enable rotation with two-finger twist gesture + /// + /// For controlling cursor/keyboard rotation, see + /// [InteractionOptions.cursorKeyboardRotationOptions]. + final bool rotate; + + /// Enable rotation by pressing the CTRL key and dragging with the cursor + /// or finger. + final bool ctrlDragRotate; + + bool hasMultiFinger() => pinchMove || pinchZoom || rotate; + + /// This constructor gives wither functionality to the model + InteractiveFlags withFlag({ + bool? pinchZoom, + bool? drag, + bool? flingAnimation, + bool? pinchMove, + bool? doubleTapZoom, + bool? doubleTapDragZoom, + bool? scrollWheelZoom, + bool? rotate, + bool? ctrlDragRotate, + }) => + InteractiveFlags._( + pinchZoom: pinchZoom ?? this.pinchZoom, + drag: drag ?? this.drag, + flingAnimation: flingAnimation ?? this.flingAnimation, + pinchMove: pinchMove ?? this.pinchMove, + doubleTapZoom: doubleTapZoom ?? this.doubleTapZoom, + doubleTapDragZoom: doubleTapDragZoom ?? this.doubleTapDragZoom, + scrollWheelZoom: scrollWheelZoom ?? this.scrollWheelZoom, + rotate: rotate ?? this.rotate, + ctrlDragRotate: rotate ?? this.ctrlDragRotate, + ); +} diff --git a/lib/src/gestures/map_interactive_viewer.dart b/lib/src/gestures/map_interactive_viewer.dart index 6c8574838..d891ec2f0 100644 --- a/lib/src/gestures/map_interactive_viewer.dart +++ b/lib/src/gestures/map_interactive_viewer.dart @@ -192,55 +192,48 @@ class MapInteractiveViewerState extends State } /// Used by the internal map controller to update interaction gestures - void updateGestures(int flags) { - if (InteractiveFlag.hasMultiFinger(flags)) { + void updateGestures(InteractiveFlags flags) { + if (flags.hasMultiFinger()) { _twoFingerInput = TwoFingerGestures( controller: widget.controller, - moveEnabled: InteractiveFlag.hasPinchMove(flags), - rotateEnabled: InteractiveFlag.hasRotate(flags), - zoomEnabled: InteractiveFlag.hasPinchZoom(flags), + interactiveFlags: flags, ); } else { _twoFingerInput = null; } - if (InteractiveFlag.hasDrag(flags)) { + if (flags.drag) { _drag = DragGesture( controller: widget.controller, - flingEnabled: InteractiveFlag.hasFlingAnimation(flags), + interactiveFlags: flags, ); } else { _drag = null; } - if (InteractiveFlag.hasDoubleTapZoom(flags)) { + if (flags.doubleTapZoom) { _doubleTap = DoubleTapGesture(controller: widget.controller); } else { _doubleTap = null; } - if (InteractiveFlag.hasScrollWheelZoom(flags)) { + if (flags.scrollWheelZoom) { _scrollWheelZoom = ScrollWheelZoomGesture(controller: widget.controller); } else { _scrollWheelZoom = null; } - if (InteractiveFlag.hasCtrlDragRotate(flags)) { + if (flags.ctrlDragRotate) { _ctrlDragRotate = CtrlDragRotateGesture(controller: widget.controller); } else { _ctrlDragRotate = null; } - if (InteractiveFlag.hasDoubleTapDragZoom(flags)) { + if (flags.doubleTapDragZoom) { _doubleTapDragZoom = DoubleTapDragZoomGesture(controller: widget.controller); } else { _doubleTapDragZoom = null; } } - - /// Used by the internal map controller - void interruptAnimatedMovement(MapEventMove event) { - // TODO implement - } } diff --git a/lib/src/map/controller/map_controller_impl.dart b/lib/src/map/controller/map_controller_impl.dart index eb2f94919..9fb5164c7 100644 --- a/lib/src/map/controller/map_controller_impl.dart +++ b/lib/src/map/controller/map_controller_impl.dart @@ -543,12 +543,7 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState> } void emitMapEvent(MapEvent event) { - if (event.source == MapEventSource.mapController && event is MapEventMove) { - _interactiveViewerState.interruptAnimatedMovement(event); - } - options.onMapEvent?.call(event); - _mapEventSink.add(event); } diff --git a/lib/src/map/options/interaction.dart b/lib/src/map/options/interaction.dart index 59213f0ad..64749acf7 100644 --- a/lib/src/map/options/interaction.dart +++ b/lib/src/map/options/interaction.dart @@ -1,11 +1,12 @@ import 'package:flutter_map/src/gestures/interactive_flag.dart'; +import 'package:flutter_map/src/gestures/interactive_flags.dart'; import 'package:flutter_map/src/map/options/cursor_keyboard_rotation.dart'; import 'package:meta/meta.dart'; @immutable final class InteractionOptions { - /// See [InteractiveFlag] for custom settings - final int flags; + /// See [InteractiveFlags] for custom settings + final InteractiveFlags flags; /// Prints multi finger gesture winner Helps to fine adjust /// [rotationThreshold] and [pinchZoomThreshold] and [pinchMoveThreshold] @@ -75,7 +76,7 @@ final class InteractionOptions { final CursorKeyboardRotationOptions cursorKeyboardRotationOptions; const InteractionOptions({ - this.flags = InteractiveFlag.all, + this.flags = const InteractiveFlags.all(), this.debugMultiFingerGestureWinner = false, this.enableMultiFingerGestureRace = false, this.rotationThreshold = 20.0, diff --git a/test/flutter_map_test.dart b/test/flutter_map_test.dart index 41da4b439..7e0cb1fa9 100644 --- a/test/flutter_map_test.dart +++ b/test/flutter_map_test.dart @@ -1,13 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:flutter_map/src/geo/crs.dart'; -import 'package:flutter_map/src/gestures/interactive_flag.dart'; -import 'package:flutter_map/src/layer/marker_layer.dart'; -import 'package:flutter_map/src/layer/tile_layer/tile_layer.dart'; -import 'package:flutter_map/src/map/camera/camera.dart'; -import 'package:flutter_map/src/map/controller/map_controller.dart'; -import 'package:flutter_map/src/map/options/interaction.dart'; -import 'package:flutter_map/src/map/options/options.dart'; -import 'package:flutter_map/src/map/widget.dart'; +import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:latlong2/latlong.dart'; @@ -223,7 +215,7 @@ class TestRebuildsApp extends StatefulWidget { class _TestRebuildsAppState extends State { MapController _mapController = MapController(); Crs _crs = const Epsg3857(); - int _interactiveFlags = InteractiveFlag.all; + InteractiveFlags _interactiveFlags = const InteractiveFlags.all(); @override void dispose() { @@ -250,10 +242,9 @@ class _TestRebuildsAppState extends State { TextButton( onPressed: () { setState(() { - _interactiveFlags = - InteractiveFlag.hasDrag(_interactiveFlags) - ? _interactiveFlags & ~InteractiveFlag.drag - : InteractiveFlag.all; + _interactiveFlags = _interactiveFlags.withFlag( + drag: !_interactiveFlags.drag, + ); }); }, child: const Text('Change flags'), diff --git a/test/full_coverage_test.dart b/test/full_coverage_test.dart index a9760cd1b..8fbe057e7 100644 --- a/test/full_coverage_test.dart +++ b/test/full_coverage_test.dart @@ -4,6 +4,7 @@ import 'package:flutter_map/src/geo/crs.dart'; import 'package:flutter_map/src/geo/latlng_bounds.dart'; import 'package:flutter_map/src/gestures/gestures.dart'; import 'package:flutter_map/src/gestures/interactive_flag.dart'; +import 'package:flutter_map/src/gestures/interactive_flags.dart'; import 'package:flutter_map/src/gestures/latlng_tween.dart'; import 'package:flutter_map/src/gestures/map_events.dart'; import 'package:flutter_map/src/gestures/map_interactive_viewer.dart'; From f3c5e21e95e4a10a93628bb27105b77771e15dae Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Wed, 13 Dec 2023 20:45:31 +0100 Subject: [PATCH 032/102] small improvements --- lib/src/gestures/interactive_flags.dart | 28 +++++++++++++++++++ lib/src/gestures/map_interactive_viewer.dart | 21 +++++++------- .../map/controller/map_controller_impl.dart | 1 + 3 files changed, 40 insertions(+), 10 deletions(-) diff --git a/lib/src/gestures/interactive_flags.dart b/lib/src/gestures/interactive_flags.dart index 32fea86dc..143ccf6a3 100644 --- a/lib/src/gestures/interactive_flags.dart +++ b/lib/src/gestures/interactive_flags.dart @@ -116,4 +116,32 @@ class InteractiveFlags { rotate: rotate ?? this.rotate, ctrlDragRotate: rotate ?? this.ctrlDragRotate, ); + + @override + bool operator ==(Object other) => + identical(this, other) || + other is InteractiveFlags && + runtimeType == other.runtimeType && + drag == other.drag && + flingAnimation == other.flingAnimation && + pinchMove == other.pinchMove && + pinchZoom == other.pinchZoom && + doubleTapZoom == other.doubleTapZoom && + doubleTapDragZoom == other.doubleTapDragZoom && + scrollWheelZoom == other.scrollWheelZoom && + rotate == other.rotate && + ctrlDragRotate == other.ctrlDragRotate; + + @override + int get hashCode => Object.hash( + drag, + flingAnimation, + pinchMove, + pinchZoom, + doubleTapZoom, + doubleTapDragZoom, + scrollWheelZoom, + rotate, + ctrlDragRotate, + ); } diff --git a/lib/src/gestures/map_interactive_viewer.dart b/lib/src/gestures/map_interactive_viewer.dart index d891ec2f0..048b70aec 100644 --- a/lib/src/gestures/map_interactive_viewer.dart +++ b/lib/src/gestures/map_interactive_viewer.dart @@ -72,7 +72,7 @@ class MapInteractiveViewerState extends State TertiaryLongPressGesture(controller: widget.controller); } // gestures that change the map camera - updateGestures(_interactionOptions.flags); + updateGestures(null, _interactionOptions.flags); } @override @@ -192,44 +192,45 @@ class MapInteractiveViewerState extends State } /// Used by the internal map controller to update interaction gestures - void updateGestures(InteractiveFlags flags) { - if (flags.hasMultiFinger()) { + void updateGestures(InteractiveFlags? oldFlags, InteractiveFlags newFlags) { + if (oldFlags == newFlags) return; + if (newFlags.hasMultiFinger()) { _twoFingerInput = TwoFingerGestures( controller: widget.controller, - interactiveFlags: flags, + interactiveFlags: newFlags, ); } else { _twoFingerInput = null; } - if (flags.drag) { + if (newFlags.drag) { _drag = DragGesture( controller: widget.controller, - interactiveFlags: flags, + interactiveFlags: newFlags, ); } else { _drag = null; } - if (flags.doubleTapZoom) { + if (newFlags.doubleTapZoom) { _doubleTap = DoubleTapGesture(controller: widget.controller); } else { _doubleTap = null; } - if (flags.scrollWheelZoom) { + if (newFlags.scrollWheelZoom) { _scrollWheelZoom = ScrollWheelZoomGesture(controller: widget.controller); } else { _scrollWheelZoom = null; } - if (flags.ctrlDragRotate) { + if (newFlags.ctrlDragRotate) { _ctrlDragRotate = CtrlDragRotateGesture(controller: widget.controller); } else { _ctrlDragRotate = null; } - if (flags.doubleTapDragZoom) { + if (newFlags.doubleTapDragZoom) { _doubleTapDragZoom = DoubleTapDragZoomGesture(controller: widget.controller); } else { diff --git a/lib/src/map/controller/map_controller_impl.dart b/lib/src/map/controller/map_controller_impl.dart index 9fb5164c7..22ca9f1be 100644 --- a/lib/src/map/controller/map_controller_impl.dart +++ b/lib/src/map/controller/map_controller_impl.dart @@ -348,6 +348,7 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState> if (value.options != null && value.options!.interactionOptions != newOptions.interactionOptions) { _interactiveViewerState.updateGestures( + value.options!.interactionOptions.flags, newOptions.interactionOptions.flags, ); } From 9202645415effdddb9f3a2ec601e615f074cea24 Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Thu, 14 Dec 2023 00:34:44 +0100 Subject: [PATCH 033/102] disable double clicks --- lib/src/gestures/map_interactive_viewer.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/gestures/map_interactive_viewer.dart b/lib/src/gestures/map_interactive_viewer.dart index 048b70aec..1533164dc 100644 --- a/lib/src/gestures/map_interactive_viewer.dart +++ b/lib/src/gestures/map_interactive_viewer.dart @@ -131,7 +131,7 @@ class MapInteractiveViewerState extends State onSecondaryLongPressStart: _secondaryLongPress?.submit, - onDoubleTapDown: (details) { + /* onDoubleTapDown: (details) { _doubleTapDragZoom?.isActive = true; _doubleTap?.setDetails(details); }, @@ -142,7 +142,7 @@ class MapInteractiveViewerState extends State onDoubleTap: () { _doubleTapDragZoom?.isActive = false; _doubleTap?.submit(); - }, + },*/ onTertiaryTapDown: _tertiaryTap?.setDetails, onTertiaryTapCancel: _tertiaryTap?.reset, From e59f4da677fe01880d1e88a257209ffbf424fe46 Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Thu, 14 Dec 2023 00:46:21 +0100 Subject: [PATCH 034/102] optional double click and scale callbacks --- lib/src/gestures/map_interactive_viewer.dart | 111 +++++++++++-------- test/flutter_map_test.dart | 7 +- 2 files changed, 71 insertions(+), 47 deletions(-) diff --git a/lib/src/gestures/map_interactive_viewer.dart b/lib/src/gestures/map_interactive_viewer.dart index 1533164dc..b61460f3c 100644 --- a/lib/src/gestures/map_interactive_viewer.dart +++ b/lib/src/gestures/map_interactive_viewer.dart @@ -87,6 +87,13 @@ class MapInteractiveViewerState extends State @override Widget build(BuildContext context) { + final useDoubleTapCallback = + _doubleTap != null || _doubleTapDragZoom != null; + final useScaleCallback = _ctrlDragRotate != null || + _drag != null || + _doubleTapDragZoom != null || + _twoFingerInput != null; + return Listener( onPointerDown: _options.onPointerDown == null ? null @@ -131,18 +138,24 @@ class MapInteractiveViewerState extends State onSecondaryLongPressStart: _secondaryLongPress?.submit, - /* onDoubleTapDown: (details) { - _doubleTapDragZoom?.isActive = true; - _doubleTap?.setDetails(details); - }, - onDoubleTapCancel: () { - _doubleTapDragZoom?.isActive = true; - _doubleTap?.reset(); - }, - onDoubleTap: () { - _doubleTapDragZoom?.isActive = false; - _doubleTap?.submit(); - },*/ + onDoubleTapDown: useDoubleTapCallback + ? (details) { + _doubleTapDragZoom?.isActive = true; + _doubleTap?.setDetails(details); + } + : null, + onDoubleTapCancel: useDoubleTapCallback + ? () { + _doubleTapDragZoom?.isActive = true; + _doubleTap?.reset(); + } + : null, + onDoubleTap: useDoubleTapCallback + ? () { + _doubleTapDragZoom?.isActive = false; + _doubleTap?.submit(); + } + : null, onTertiaryTapDown: _tertiaryTap?.setDetails, onTertiaryTapCancel: _tertiaryTap?.reset, @@ -151,40 +164,46 @@ class MapInteractiveViewerState extends State onTertiaryLongPressStart: _tertiaryLongPress?.submit, // pan and scale, scale is a superset of the pan gesture - onScaleStart: (details) { - if (_ctrlDragRotate?.ctrlPressed ?? false) { - _ctrlDragRotate!.start(); - } else if (_doubleTapDragZoom?.isActive ?? false) { - _doubleTapDragZoom!.start(details); - } else if (details.pointerCount == 1) { - _drag?.start(details); - } else { - _twoFingerInput?.start(details); - } - }, - onScaleUpdate: (details) { - if (_ctrlDragRotate?.ctrlPressed ?? false) { - _ctrlDragRotate!.update(details); - } else if (_doubleTapDragZoom?.isActive ?? false) { - _doubleTapDragZoom!.update(details); - } else if (details.pointerCount == 1) { - _drag?.update(details); - } else { - _twoFingerInput?.update(details); - } - }, - onScaleEnd: (details) { - if (_ctrlDragRotate?.ctrlPressed ?? false) { - _ctrlDragRotate!.end(); - } else if (_doubleTapDragZoom?.isActive ?? false) { - _doubleTapDragZoom!.isActive = false; - _doubleTapDragZoom!.end(details); - } else if (_drag?.isActive ?? false) { - _drag?.end(details); - } else { - _twoFingerInput?.end(details); - } - }, + onScaleStart: useScaleCallback + ? (details) { + if (_ctrlDragRotate?.ctrlPressed ?? false) { + _ctrlDragRotate!.start(); + } else if (_doubleTapDragZoom?.isActive ?? false) { + _doubleTapDragZoom!.start(details); + } else if (details.pointerCount == 1) { + _drag?.start(details); + } else { + _twoFingerInput?.start(details); + } + } + : null, + onScaleUpdate: useScaleCallback + ? (details) { + if (_ctrlDragRotate?.ctrlPressed ?? false) { + _ctrlDragRotate!.update(details); + } else if (_doubleTapDragZoom?.isActive ?? false) { + _doubleTapDragZoom!.update(details); + } else if (details.pointerCount == 1) { + _drag?.update(details); + } else { + _twoFingerInput?.update(details); + } + } + : null, + onScaleEnd: useScaleCallback + ? (details) { + if (_ctrlDragRotate?.ctrlPressed ?? false) { + _ctrlDragRotate!.end(); + } else if (_doubleTapDragZoom?.isActive ?? false) { + _doubleTapDragZoom!.isActive = false; + _doubleTapDragZoom!.end(details); + } else if (_drag?.isActive ?? false) { + _drag?.end(details); + } else { + _twoFingerInput?.end(details); + } + } + : null, child: widget.builder(context, _options, _camera), ), diff --git a/test/flutter_map_test.dart b/test/flutter_map_test.dart index 7e0cb1fa9..70102b2eb 100644 --- a/test/flutter_map_test.dart +++ b/test/flutter_map_test.dart @@ -215,7 +215,12 @@ class TestRebuildsApp extends StatefulWidget { class _TestRebuildsAppState extends State { MapController _mapController = MapController(); Crs _crs = const Epsg3857(); - InteractiveFlags _interactiveFlags = const InteractiveFlags.all(); + + /// double tap gestures delay the tap gestures, disable them here + InteractiveFlags _interactiveFlags = const InteractiveFlags.all( + doubleTapZoom: false, + doubleTapDragZoom: false, + ); @override void dispose() { From cefacc3b81d8683fe1ce9052791e7ae100045bf1 Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Thu, 14 Dec 2023 00:51:10 +0100 Subject: [PATCH 035/102] remove null checks --- lib/src/gestures/map_interactive_viewer.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/src/gestures/map_interactive_viewer.dart b/lib/src/gestures/map_interactive_viewer.dart index b61460f3c..e5b8914a9 100644 --- a/lib/src/gestures/map_interactive_viewer.dart +++ b/lib/src/gestures/map_interactive_viewer.dart @@ -97,25 +97,25 @@ class MapInteractiveViewerState extends State return Listener( onPointerDown: _options.onPointerDown == null ? null - : (event) => _options.onPointerDown?.call( + : (event) => _options.onPointerDown!.call( event, _camera.offsetToCrs(event.localPosition), ), onPointerHover: _options.onPointerHover == null ? null - : (event) => _options.onPointerHover?.call( + : (event) => _options.onPointerHover!.call( event, _camera.offsetToCrs(event.localPosition), ), onPointerCancel: _options.onPointerCancel == null ? null - : (event) => _options.onPointerCancel?.call( + : (event) => _options.onPointerCancel!.call( event, _camera.offsetToCrs(event.localPosition), ), onPointerUp: _options.onPointerUp == null ? null - : (event) => _options.onPointerUp?.call( + : (event) => _options.onPointerUp!.call( event, _camera.offsetToCrs(event.localPosition), ), From f32cca272a511fd21042714ee95a4c1497b022ed Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Thu, 14 Dec 2023 12:24:56 +0100 Subject: [PATCH 036/102] add docs --- lib/flutter_map.dart | 1 - lib/src/gestures/interactive_flag.dart | 70 ------------------ lib/src/gestures/interactive_flags.dart | 98 ++++++++++++++++++++++++- lib/src/map/options/interaction.dart | 1 - test/full_coverage_test.dart | 1 - 5 files changed, 97 insertions(+), 74 deletions(-) delete mode 100644 lib/src/gestures/interactive_flag.dart diff --git a/lib/flutter_map.dart b/lib/flutter_map.dart index dba33d199..0683a5c1f 100644 --- a/lib/flutter_map.dart +++ b/lib/flutter_map.dart @@ -18,7 +18,6 @@ library flutter_map; export 'package:flutter_map/src/geo/crs.dart'; export 'package:flutter_map/src/geo/latlng_bounds.dart'; -export 'package:flutter_map/src/gestures/interactive_flag.dart'; export 'package:flutter_map/src/gestures/interactive_flags.dart'; export 'package:flutter_map/src/gestures/latlng_tween.dart'; export 'package:flutter_map/src/gestures/map_events.dart'; diff --git a/lib/src/gestures/interactive_flag.dart b/lib/src/gestures/interactive_flag.dart deleted file mode 100644 index ee3e5b31c..000000000 --- a/lib/src/gestures/interactive_flag.dart +++ /dev/null @@ -1,70 +0,0 @@ -/// Use [InteractiveFlag] to disable / enable certain events Use -/// [InteractiveFlag.all] to enable all events, use [InteractiveFlag.none] to -/// disable all events -/// -/// If you want mix interactions for example drag and rotate interactions then -/// you have two options: -/// a. Add your own flags: [InteractiveFlag.drag] | [InteractiveFlag.rotate] -/// b. Remove unnecessary flags from all: -/// [InteractiveFlag.all] & -/// ~[InteractiveFlag.flingAnimation] & -/// ~[InteractiveFlag.pinchMove] & -/// ~[InteractiveFlag.pinchZoom] & -/// ~[InteractiveFlag.doubleTapZoom] -abstract class InteractiveFlag { - const InteractiveFlag._(); - - static const int all = drag | - flingAnimation | - pinchMove | - pinchZoom | - doubleTapZoom | - doubleTapDragZoom | - scrollWheelZoom | - rotate | - ctrlDragRotate; - - static const int none = 0; - - /// Enable panning with a single finger or cursor - static const int drag = 1 << 0; - - /// Enable fling animation after panning if velocity is great enough. - static const int flingAnimation = 1 << 1; - - /// Enable panning with multiple fingers - static const int pinchMove = 1 << 2; - - /// Enable zooming with a multi-finger pinch gesture - static const int pinchZoom = 1 << 3; - - /// Enable zooming with a single-finger double tap gesture - static const int doubleTapZoom = 1 << 4; - - /// Enable zooming with a single-finger double-tap-drag gesture - /// - /// The associated [MapEventSource] is [MapEventSource.doubleTapHold]. - static const int doubleTapDragZoom = 1 << 5; - - /// Enable zooming with a mouse scroll wheel - static const int scrollWheelZoom = 1 << 6; - - /// Enable rotation with two-finger twist gesture - /// - /// For controlling cursor/keyboard rotation, see - /// [InteractionOptions.cursorKeyboardRotationOptions]. - static const int rotate = 1 << 7; - - /// Enable rotation by pressing the CTRL Key and drag the map with the cursor. - /// To change the key see [InteractionOptions.cursorKeyboardRotationOptions]. - static const int ctrlDragRotate = 1 << 8; - - /// Returns `true` if [leftFlags] has at least one member in [rightFlags] - /// (intersection) for example [leftFlags]= [InteractiveFlag.drag] | - /// [InteractiveFlag.rotate] and [rightFlags]= [InteractiveFlag.rotate] | - /// [InteractiveFlag.flingAnimation] returns true because both have - /// [InteractiveFlag.rotate] flag - static bool hasFlag(int leftFlags, int rightFlags) { - return leftFlags & rightFlags != 0; - } -} diff --git a/lib/src/gestures/interactive_flags.dart b/lib/src/gestures/interactive_flags.dart index 143ccf6a3..a0dddb0b4 100644 --- a/lib/src/gestures/interactive_flags.dart +++ b/lib/src/gestures/interactive_flags.dart @@ -3,6 +3,10 @@ import 'package:meta/meta.dart'; @immutable class InteractiveFlags { + /// Private constructor, use the constructors [InteractiveFlags.all] or + /// [InteractiveFlags.none] instead to enable or disable all gestures by default. + /// If you want to define your enabled gestures using bitfield operations, + /// use [InteractiveFlags.bitfield] instead. const InteractiveFlags._({ required this.drag, required this.flingAnimation, @@ -15,6 +19,18 @@ class InteractiveFlags { required this.ctrlDragRotate, }); + /// Shortcut constructor to allow all gestures that don't rotate the map. + const InteractiveFlags.noRotation() + : this.all( + rotate: false, + ctrlDragRotate: false, + ); + + /// This constructor enables all gestures by default. Use this constructor if + /// you want have all gestures enabled or disable some gestures only. + /// + /// In case you want have no or only few gestures enabled use the + /// [InteractiveFlags.none] constructor instead. const InteractiveFlags.all({ this.drag = true, this.flingAnimation = true, @@ -27,6 +43,11 @@ class InteractiveFlags { this.ctrlDragRotate = true, }); + /// This constructor has no enabled gestures by default. Use this constructor + /// if you want have no gestures enabled or only some specific gestures. + /// + /// In case you want have most or all of the gestures enabled use the + /// [InteractiveFlags.all] constructor instead. const InteractiveFlags.none({ this.drag = false, this.flingAnimation = false, @@ -39,6 +60,8 @@ class InteractiveFlags { this.ctrlDragRotate = false, }); + /// This constructor supports bitfield operations on the static fields + /// from [InteractiveFlag]. factory InteractiveFlags.bitfield(int flags) { return InteractiveFlags._( drag: InteractiveFlag.hasFlag(flags, InteractiveFlag.drag), @@ -91,9 +114,11 @@ class InteractiveFlags { /// or finger. final bool ctrlDragRotate; + /// Returns true of any gesture with more than one finger is enabled. bool hasMultiFinger() => pinchMove || pinchZoom || rotate; - /// This constructor gives wither functionality to the model + /// Wither to change the value of some gestures. Returns a new + /// [InteractiveFlags] object. InteractiveFlags withFlag({ bool? pinchZoom, bool? drag, @@ -145,3 +170,74 @@ class InteractiveFlags { ctrlDragRotate, ); } + +/// Use [InteractiveFlag] to disable / enable certain events Use +/// [InteractiveFlag.all] to enable all events, use [InteractiveFlag.none] to +/// disable all events +/// +/// If you want mix interactions for example drag and rotate interactions then +/// you have two options: +/// a. Add your own flags: [InteractiveFlag.drag] | [InteractiveFlag.rotate] +/// b. Remove unnecessary flags from all: +/// [InteractiveFlag.all] & +/// ~[InteractiveFlag.flingAnimation] & +/// ~[InteractiveFlag.pinchMove] & +/// ~[InteractiveFlag.pinchZoom] & +/// ~[InteractiveFlag.doubleTapZoom] +abstract class InteractiveFlag { + const InteractiveFlag._(); + + static const int all = drag | + flingAnimation | + pinchMove | + pinchZoom | + doubleTapZoom | + doubleTapDragZoom | + scrollWheelZoom | + rotate | + ctrlDragRotate; + + static const int none = 0; + + /// Enable panning with a single finger or cursor + static const int drag = 1 << 0; + + /// Enable fling animation after panning if velocity is great enough. + static const int flingAnimation = 1 << 1; + + /// Enable panning with multiple fingers + static const int pinchMove = 1 << 2; + + /// Enable zooming with a multi-finger pinch gesture + static const int pinchZoom = 1 << 3; + + /// Enable zooming with a single-finger double tap gesture + static const int doubleTapZoom = 1 << 4; + + /// Enable zooming with a single-finger double-tap-drag gesture + /// + /// The associated [MapEventSource] is [MapEventSource.doubleTapHold]. + static const int doubleTapDragZoom = 1 << 5; + + /// Enable zooming with a mouse scroll wheel + static const int scrollWheelZoom = 1 << 6; + + /// Enable rotation with two-finger twist gesture + /// + /// For controlling cursor/keyboard rotation, see + /// [InteractionOptions.cursorKeyboardRotationOptions]. + static const int rotate = 1 << 7; + + /// Enable rotation by pressing the CTRL Key and drag the map with the cursor. + /// To change the key see [InteractionOptions.cursorKeyboardRotationOptions]. + static const int ctrlDragRotate = 1 << 8; + + /// Returns `true` if [leftFlags] has at least one member in [rightFlags] + /// (intersection) for example [leftFlags]= [InteractiveFlag.drag] | + /// [InteractiveFlag.rotate] and [rightFlags]= [InteractiveFlag.rotate] | + /// [InteractiveFlag.flingAnimation] returns true because both have + /// [InteractiveFlag.rotate] flag + static bool hasFlag(int leftFlags, int rightFlags) { + return leftFlags & rightFlags != 0; + } +} diff --git a/lib/src/map/options/interaction.dart b/lib/src/map/options/interaction.dart index 64749acf7..5b9c9d53d 100644 --- a/lib/src/map/options/interaction.dart +++ b/lib/src/map/options/interaction.dart @@ -1,4 +1,3 @@ -import 'package:flutter_map/src/gestures/interactive_flag.dart'; import 'package:flutter_map/src/gestures/interactive_flags.dart'; import 'package:flutter_map/src/map/options/cursor_keyboard_rotation.dart'; import 'package:meta/meta.dart'; diff --git a/test/full_coverage_test.dart b/test/full_coverage_test.dart index 8fbe057e7..5a966bdbf 100644 --- a/test/full_coverage_test.dart +++ b/test/full_coverage_test.dart @@ -3,7 +3,6 @@ import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_map/src/geo/crs.dart'; import 'package:flutter_map/src/geo/latlng_bounds.dart'; import 'package:flutter_map/src/gestures/gestures.dart'; -import 'package:flutter_map/src/gestures/interactive_flag.dart'; import 'package:flutter_map/src/gestures/interactive_flags.dart'; import 'package:flutter_map/src/gestures/latlng_tween.dart'; import 'package:flutter_map/src/gestures/map_events.dart'; From 794e9ba61f5c7f3b0e9740e20a5597d1611761e1 Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Thu, 14 Dec 2023 13:00:27 +0100 Subject: [PATCH 037/102] refactor, turn MoveAndRotateResult to a class --- lib/flutter_map.dart | 11 ++--- lib/src/gestures/events/map_event_source.dart | 30 +++++++++++++ lib/src/gestures/{ => events}/map_events.dart | 33 +-------------- lib/src/gestures/map_interactive_viewer.dart | 23 +++++++--- .../layer/attribution_layer/rich/widget.dart | 2 +- .../layer/tile_layer/tile_update_event.dart | 2 +- .../tile_layer/tile_update_transformer.dart | 2 +- lib/src/map/camera/camera.dart | 2 +- lib/src/map/controller/map_controller.dart | 1 + .../map/controller/map_controller_impl.dart | 11 +++-- lib/src/map/inherited_model.dart | 2 +- .../map/options/cursor_keyboard_rotation.dart | 38 ++++++++--------- ...eraction.dart => interaction_options.dart} | 2 +- .../options}/interactive_flags.dart | 0 .../{options.dart => map_options.dart} | 42 +++++++++---------- lib/src/misc/move_and_rotate_result.dart | 1 - test/full_coverage_test.dart | 10 ++--- test/test_utils/test_app.dart | 2 +- 18 files changed, 114 insertions(+), 100 deletions(-) create mode 100644 lib/src/gestures/events/map_event_source.dart rename lib/src/gestures/{ => events}/map_events.dart (92%) rename lib/src/map/options/{interaction.dart => interaction_options.dart} (98%) rename lib/src/{gestures => map/options}/interactive_flags.dart (100%) rename lib/src/map/options/{options.dart => map_options.dart} (100%) delete mode 100644 lib/src/misc/move_and_rotate_result.dart diff --git a/lib/flutter_map.dart b/lib/flutter_map.dart index 0683a5c1f..bdfa794a6 100644 --- a/lib/flutter_map.dart +++ b/lib/flutter_map.dart @@ -18,9 +18,10 @@ library flutter_map; export 'package:flutter_map/src/geo/crs.dart'; export 'package:flutter_map/src/geo/latlng_bounds.dart'; -export 'package:flutter_map/src/gestures/interactive_flags.dart'; +export 'package:flutter_map/src/gestures/events/map_event_source.dart'; +export 'package:flutter_map/src/gestures/events/map_events.dart'; export 'package:flutter_map/src/gestures/latlng_tween.dart'; -export 'package:flutter_map/src/gestures/map_events.dart'; +export 'package:flutter_map/src/gestures/move_and_rotate_result.dart'; export 'package:flutter_map/src/layer/attribution_layer/rich/animation.dart'; export 'package:flutter_map/src/layer/attribution_layer/rich/source.dart'; export 'package:flutter_map/src/layer/attribution_layer/rich/widget.dart'; @@ -50,11 +51,11 @@ export 'package:flutter_map/src/map/camera/camera_fit.dart'; export 'package:flutter_map/src/map/controller/map_controller.dart'; export 'package:flutter_map/src/map/controller/map_controller_impl.dart'; export 'package:flutter_map/src/map/options/cursor_keyboard_rotation.dart'; -export 'package:flutter_map/src/map/options/interaction.dart'; -export 'package:flutter_map/src/map/options/options.dart'; +export 'package:flutter_map/src/map/options/interaction_options.dart'; +export 'package:flutter_map/src/map/options/interactive_flags.dart'; +export 'package:flutter_map/src/map/options/map_options.dart'; export 'package:flutter_map/src/map/widget.dart'; export 'package:flutter_map/src/misc/bounds.dart'; export 'package:flutter_map/src/misc/center_zoom.dart'; -export 'package:flutter_map/src/misc/move_and_rotate_result.dart'; export 'package:flutter_map/src/misc/point_extensions.dart'; export 'package:flutter_map/src/misc/position.dart'; diff --git a/lib/src/gestures/events/map_event_source.dart b/lib/src/gestures/events/map_event_source.dart new file mode 100644 index 000000000..669ac10a9 --- /dev/null +++ b/lib/src/gestures/events/map_event_source.dart @@ -0,0 +1,30 @@ +/// Event sources which are used to identify different types of +/// [MapEvent] events +enum MapEventSource { + mapController, + tap, + secondaryTap, + longPress, + doubleTap, + doubleTapHold, + dragStart, + onDrag, + dragEnd, + multiFingerStart, + onMultiFinger, + multiFingerEnd, + flingAnimationController, + doubleTapZoomAnimationController, + interactiveFlagsChanged, + fitCamera, + custom, + scrollWheel, + nonRotatedSizeChange, + cursorKeyboardRotation, + tertiaryTap, + tertiaryLongPress, + secondaryLongPressed, + ctrlDragRotateStart, + ctrlDragRotateEnd, + ctrlDragRotate, +} diff --git a/lib/src/gestures/map_events.dart b/lib/src/gestures/events/map_events.dart similarity index 92% rename from lib/src/gestures/map_events.dart rename to lib/src/gestures/events/map_events.dart index 5ec20c9d4..3fdff624a 100644 --- a/lib/src/gestures/map_events.dart +++ b/lib/src/gestures/events/map_events.dart @@ -1,38 +1,7 @@ -import 'package:flutter_map/src/map/camera/camera.dart'; +import 'package:flutter_map/flutter_map.dart'; import 'package:latlong2/latlong.dart'; import 'package:meta/meta.dart'; -/// Event sources which are used to identify different types of -/// [MapEvent] events -enum MapEventSource { - mapController, - tap, - secondaryTap, - longPress, - doubleTap, - doubleTapHold, - dragStart, - onDrag, - dragEnd, - multiFingerStart, - onMultiFinger, - multiFingerEnd, - flingAnimationController, - doubleTapZoomAnimationController, - interactiveFlagsChanged, - fitCamera, - custom, - scrollWheel, - nonRotatedSizeChange, - cursorKeyboardRotation, - tertiaryTap, - tertiaryLongPress, - secondaryLongPressed, - ctrlDragRotateStart, - ctrlDragRotateEnd, - ctrlDragRotate, -} - /// Base event class which is emitted by MapController instance, the event /// is usually related to performed gesture on the map itself or it can /// be an event related to map configuration diff --git a/lib/src/gestures/map_interactive_viewer.dart b/lib/src/gestures/map_interactive_viewer.dart index e5b8914a9..372ce308c 100644 --- a/lib/src/gestures/map_interactive_viewer.dart +++ b/lib/src/gestures/map_interactive_viewer.dart @@ -3,12 +3,6 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_map/src/gestures/gestures.dart'; -typedef ChildBuilder = Widget Function( - BuildContext context, - MapOptions options, - MapCamera camera, -); - class MapInteractiveViewer extends StatefulWidget { final ChildBuilder builder; final MapControllerImpl controller; @@ -257,3 +251,20 @@ class MapInteractiveViewerState extends State } } } + +typedef ChildBuilder = Widget Function( + BuildContext context, + MapOptions options, + MapCamera camera, +); + +@immutable +class MoveAndRotateResult { + final bool moveSuccess; + final bool rotateSuccess; + + const MoveAndRotateResult({ + required this.moveSuccess, + required this.rotateSuccess, + }); +} diff --git a/lib/src/layer/attribution_layer/rich/widget.dart b/lib/src/layer/attribution_layer/rich/widget.dart index fb2f76d17..e23d0e899 100644 --- a/lib/src/layer/attribution_layer/rich/widget.dart +++ b/lib/src/layer/attribution_layer/rich/widget.dart @@ -1,7 +1,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; -import 'package:flutter_map/src/gestures/map_events.dart'; +import 'package:flutter_map/src/gestures/events/map_events.dart'; import 'package:flutter_map/src/layer/attribution_layer/rich/animation.dart'; import 'package:flutter_map/src/layer/attribution_layer/rich/source.dart'; import 'package:flutter_map/src/map/controller/map_controller.dart'; diff --git a/lib/src/layer/tile_layer/tile_update_event.dart b/lib/src/layer/tile_layer/tile_update_event.dart index 64029eee7..1a51ad0cf 100644 --- a/lib/src/layer/tile_layer/tile_update_event.dart +++ b/lib/src/layer/tile_layer/tile_update_event.dart @@ -1,4 +1,4 @@ -import 'package:flutter_map/src/gestures/map_events.dart'; +import 'package:flutter_map/src/gestures/events/map_events.dart'; import 'package:flutter_map/src/map/camera/camera.dart'; import 'package:latlong2/latlong.dart'; import 'package:meta/meta.dart'; diff --git a/lib/src/layer/tile_layer/tile_update_transformer.dart b/lib/src/layer/tile_layer/tile_update_transformer.dart index d71f4f8f7..8489fc5c1 100644 --- a/lib/src/layer/tile_layer/tile_update_transformer.dart +++ b/lib/src/layer/tile_layer/tile_update_transformer.dart @@ -1,6 +1,6 @@ import 'dart:async'; -import 'package:flutter_map/src/gestures/map_events.dart'; +import 'package:flutter_map/src/gestures/events/map_events.dart'; import 'package:flutter_map/src/layer/tile_layer/tile_layer.dart'; import 'package:flutter_map/src/layer/tile_layer/tile_update_event.dart'; import 'package:meta/meta.dart'; diff --git a/lib/src/map/camera/camera.dart b/lib/src/map/camera/camera.dart index 64374bda7..80f79a603 100644 --- a/lib/src/map/camera/camera.dart +++ b/lib/src/map/camera/camera.dart @@ -5,7 +5,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_map/src/geo/crs.dart'; import 'package:flutter_map/src/geo/latlng_bounds.dart'; import 'package:flutter_map/src/map/inherited_model.dart'; -import 'package:flutter_map/src/map/options/options.dart'; +import 'package:flutter_map/src/map/options/map_options.dart'; import 'package:flutter_map/src/misc/bounds.dart'; import 'package:flutter_map/src/misc/point_extensions.dart'; import 'package:latlong2/latlong.dart'; diff --git a/lib/src/map/controller/map_controller.dart b/lib/src/map/controller/map_controller.dart index 4a6113fee..877d2e0f2 100644 --- a/lib/src/map/controller/map_controller.dart +++ b/lib/src/map/controller/map_controller.dart @@ -3,6 +3,7 @@ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter_map/flutter_map.dart'; +import 'package:flutter_map/src/gestures/map_interactive_viewer.dart'; import 'package:flutter_map/src/map/inherited_model.dart'; import 'package:latlong2/latlong.dart'; diff --git a/lib/src/map/controller/map_controller_impl.dart b/lib/src/map/controller/map_controller_impl.dart index 22ca9f1be..ecd103b75 100644 --- a/lib/src/map/controller/map_controller_impl.dart +++ b/lib/src/map/controller/map_controller_impl.dart @@ -236,11 +236,14 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState> } if (degree == camera.rotation) { - return const (moveSuccess: false, rotateSuccess: false); + return const MoveAndRotateResult( + moveSuccess: false, + rotateSuccess: false, + ); } if (offset == Offset.zero) { - return ( + return MoveAndRotateResult( moveSuccess: true, rotateSuccess: rotateRaw( degree, @@ -258,7 +261,7 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState> : Point(offset!.dx, offset.dy)) .rotate(camera.rotationRad); - return ( + return MoveAndRotateResult( moveSuccess: moveRaw( camera.unproject( rotationCenter + @@ -288,7 +291,7 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState> required MapEventSource source, String? id, }) => - ( + MoveAndRotateResult( moveSuccess: moveRaw( newCenter, newZoom, diff --git a/lib/src/map/inherited_model.dart b/lib/src/map/inherited_model.dart index a076d09cf..7ee5d265b 100644 --- a/lib/src/map/inherited_model.dart +++ b/lib/src/map/inherited_model.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_map/src/map/camera/camera.dart'; import 'package:flutter_map/src/map/controller/map_controller.dart'; -import 'package:flutter_map/src/map/options/options.dart'; +import 'package:flutter_map/src/map/options/map_options.dart'; /// Allows descendents of [FlutterMap] to access the [MapCamera], [MapOptions] /// and [MapController]. Those classes provide of/maybeOf methods for users to diff --git a/lib/src/map/options/cursor_keyboard_rotation.dart b/lib/src/map/options/cursor_keyboard_rotation.dart index 2fd9db7a4..e20bcc01c 100644 --- a/lib/src/map/options/cursor_keyboard_rotation.dart +++ b/lib/src/map/options/cursor_keyboard_rotation.dart @@ -1,25 +1,6 @@ import 'package:flutter/services.dart'; import 'package:meta/meta.dart'; -/// See [CursorKeyboardRotationOptions.isKeyTrigger] -typedef IsKeyCursorRotationTrigger = bool Function(LogicalKeyboardKey key); - -/// The behaviour of the cursor/keyboard rotation function in terms of the angle -/// that the map is rotated to -/// -/// Does not disable cursor/keyboard rotation, or adjust its triggers: see -/// [CursorKeyboardRotationOptions.isKeyTrigger]. -/// -/// Also see [CursorKeyboardRotationOptions.setNorthOnClick]. -enum CursorRotationBehaviour { - /// Set the North of the map to the angle at which the user drags their cursor - setNorth, - - /// Offset the current rotation of the map to the angle at which the user drags - /// their cursor - offset, -} - /// Options to configure cursor/keyboard rotation /// /// {@template cursorkeyboard_explanation} @@ -81,3 +62,22 @@ class CursorKeyboardRotationOptions { /// {@macro cursorkeyboard_explanation} CursorKeyboardRotationOptions.disabled() : this(isKeyTrigger: (_) => false); } + +/// See [CursorKeyboardRotationOptions.isKeyTrigger] +typedef IsKeyCursorRotationTrigger = bool Function(LogicalKeyboardKey key); + +/// The behaviour of the cursor/keyboard rotation function in terms of the angle +/// that the map is rotated to +/// +/// Does not disable cursor/keyboard rotation, or adjust its triggers: see +/// [CursorKeyboardRotationOptions.isKeyTrigger]. +/// +/// Also see [CursorKeyboardRotationOptions.setNorthOnClick]. +enum CursorRotationBehaviour { + /// Set the North of the map to the angle at which the user drags their cursor + setNorth, + + /// Offset the current rotation of the map to the angle at which the user + /// drags their cursor + offset, +} diff --git a/lib/src/map/options/interaction.dart b/lib/src/map/options/interaction_options.dart similarity index 98% rename from lib/src/map/options/interaction.dart rename to lib/src/map/options/interaction_options.dart index 5b9c9d53d..f03bd8976 100644 --- a/lib/src/map/options/interaction.dart +++ b/lib/src/map/options/interaction_options.dart @@ -1,5 +1,5 @@ -import 'package:flutter_map/src/gestures/interactive_flags.dart'; import 'package:flutter_map/src/map/options/cursor_keyboard_rotation.dart'; +import 'package:flutter_map/src/map/options/interactive_flags.dart'; import 'package:meta/meta.dart'; @immutable diff --git a/lib/src/gestures/interactive_flags.dart b/lib/src/map/options/interactive_flags.dart similarity index 100% rename from lib/src/gestures/interactive_flags.dart rename to lib/src/map/options/interactive_flags.dart diff --git a/lib/src/map/options/options.dart b/lib/src/map/options/map_options.dart similarity index 100% rename from lib/src/map/options/options.dart rename to lib/src/map/options/map_options.dart index 2146dc8be..79540c76b 100644 --- a/lib/src/map/options/options.dart +++ b/lib/src/map/options/map_options.dart @@ -5,27 +5,6 @@ import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_map/src/map/inherited_model.dart'; import 'package:latlong2/latlong.dart'; -typedef MapEventCallback = void Function(MapEvent); - -typedef GestureCallback = void Function(TapDownDetails details, LatLng point); -typedef LongPressCallback = void Function( - LongPressStartDetails details, - LatLng point, -); -typedef PointerDownCallback = void Function( - PointerDownEvent event, - LatLng point, -); -typedef PointerUpCallback = void Function(PointerUpEvent event, LatLng point); -typedef PointerCancelCallback = void Function( - PointerCancelEvent event, - LatLng point, -); -typedef PointerHoverCallback = void Function( - PointerHoverEvent event, - LatLng point, -); - @immutable class MapOptions { /// The Coordinate Reference System, defaults to [Epsg3857]. @@ -197,3 +176,24 @@ class MapOptions { applyPointerTranslucencyToLayers, ]); } + +typedef MapEventCallback = void Function(MapEvent); + +typedef GestureCallback = void Function(TapDownDetails details, LatLng point); +typedef LongPressCallback = void Function( + LongPressStartDetails details, + LatLng point, +); +typedef PointerDownCallback = void Function( + PointerDownEvent event, + LatLng point, +); +typedef PointerUpCallback = void Function(PointerUpEvent event, LatLng point); +typedef PointerCancelCallback = void Function( + PointerCancelEvent event, + LatLng point, +); +typedef PointerHoverCallback = void Function( + PointerHoverEvent event, + LatLng point, +); diff --git a/lib/src/misc/move_and_rotate_result.dart b/lib/src/misc/move_and_rotate_result.dart deleted file mode 100644 index b6887349f..000000000 --- a/lib/src/misc/move_and_rotate_result.dart +++ /dev/null @@ -1 +0,0 @@ -typedef MoveAndRotateResult = ({bool moveSuccess, bool rotateSuccess}); diff --git a/test/full_coverage_test.dart b/test/full_coverage_test.dart index 5a966bdbf..322d8ea01 100644 --- a/test/full_coverage_test.dart +++ b/test/full_coverage_test.dart @@ -2,11 +2,11 @@ import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_map/src/geo/crs.dart'; import 'package:flutter_map/src/geo/latlng_bounds.dart'; +import 'package:flutter_map/src/gestures/events/map_events.dart'; import 'package:flutter_map/src/gestures/gestures.dart'; -import 'package:flutter_map/src/gestures/interactive_flags.dart'; import 'package:flutter_map/src/gestures/latlng_tween.dart'; -import 'package:flutter_map/src/gestures/map_events.dart'; import 'package:flutter_map/src/gestures/map_interactive_viewer.dart'; +import 'package:flutter_map/src/gestures/move_and_rotate_result.dart'; import 'package:flutter_map/src/layer/attribution_layer/rich/animation.dart'; import 'package:flutter_map/src/layer/attribution_layer/rich/source.dart'; import 'package:flutter_map/src/layer/attribution_layer/rich/widget.dart'; @@ -47,12 +47,12 @@ import 'package:flutter_map/src/map/controller/map_controller.dart'; import 'package:flutter_map/src/map/controller/map_controller_impl.dart'; import 'package:flutter_map/src/map/inherited_model.dart'; import 'package:flutter_map/src/map/options/cursor_keyboard_rotation.dart'; -import 'package:flutter_map/src/map/options/interaction.dart'; -import 'package:flutter_map/src/map/options/options.dart'; +import 'package:flutter_map/src/map/options/interaction_options.dart'; +import 'package:flutter_map/src/map/options/interactive_flags.dart'; +import 'package:flutter_map/src/map/options/map_options.dart'; import 'package:flutter_map/src/map/widget.dart'; import 'package:flutter_map/src/misc/bounds.dart'; import 'package:flutter_map/src/misc/center_zoom.dart'; -import 'package:flutter_map/src/misc/move_and_rotate_result.dart'; import 'package:flutter_map/src/misc/offsets.dart'; import 'package:flutter_map/src/misc/point_extensions.dart'; import 'package:flutter_map/src/misc/position.dart'; diff --git a/test/test_utils/test_app.dart b/test/test_utils/test_app.dart index e0ea2cc21..a836bd654 100644 --- a/test/test_utils/test_app.dart +++ b/test/test_utils/test_app.dart @@ -5,7 +5,7 @@ import 'package:flutter_map/src/layer/polygon_layer/polygon_layer.dart'; import 'package:flutter_map/src/layer/polyline_layer.dart'; import 'package:flutter_map/src/layer/tile_layer/tile_layer.dart'; import 'package:flutter_map/src/map/controller/map_controller.dart'; -import 'package:flutter_map/src/map/options/options.dart'; +import 'package:flutter_map/src/map/options/map_options.dart'; import 'package:flutter_map/src/map/widget.dart'; import 'package:latlong2/latlong.dart'; From 91f77dcee3b413408b6124e66646f32e6f52796f Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Thu, 14 Dec 2023 13:00:42 +0100 Subject: [PATCH 038/102] fix imports --- lib/flutter_map.dart | 1 - test/full_coverage_test.dart | 1 - 2 files changed, 2 deletions(-) diff --git a/lib/flutter_map.dart b/lib/flutter_map.dart index bdfa794a6..beb6b4bfc 100644 --- a/lib/flutter_map.dart +++ b/lib/flutter_map.dart @@ -21,7 +21,6 @@ export 'package:flutter_map/src/geo/latlng_bounds.dart'; export 'package:flutter_map/src/gestures/events/map_event_source.dart'; export 'package:flutter_map/src/gestures/events/map_events.dart'; export 'package:flutter_map/src/gestures/latlng_tween.dart'; -export 'package:flutter_map/src/gestures/move_and_rotate_result.dart'; export 'package:flutter_map/src/layer/attribution_layer/rich/animation.dart'; export 'package:flutter_map/src/layer/attribution_layer/rich/source.dart'; export 'package:flutter_map/src/layer/attribution_layer/rich/widget.dart'; diff --git a/test/full_coverage_test.dart b/test/full_coverage_test.dart index 322d8ea01..a4f2bc726 100644 --- a/test/full_coverage_test.dart +++ b/test/full_coverage_test.dart @@ -6,7 +6,6 @@ import 'package:flutter_map/src/gestures/events/map_events.dart'; import 'package:flutter_map/src/gestures/gestures.dart'; import 'package:flutter_map/src/gestures/latlng_tween.dart'; import 'package:flutter_map/src/gestures/map_interactive_viewer.dart'; -import 'package:flutter_map/src/gestures/move_and_rotate_result.dart'; import 'package:flutter_map/src/layer/attribution_layer/rich/animation.dart'; import 'package:flutter_map/src/layer/attribution_layer/rich/source.dart'; import 'package:flutter_map/src/layer/attribution_layer/rich/widget.dart'; From 1ec5152a30f1e1fd366fc23a009fc09558c58d64 Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Thu, 14 Dec 2023 13:02:56 +0100 Subject: [PATCH 039/102] move map events to the controller --- lib/flutter_map.dart | 4 ++-- lib/src/layer/attribution_layer/rich/widget.dart | 2 +- lib/src/layer/tile_layer/tile_update_event.dart | 2 +- lib/src/layer/tile_layer/tile_update_transformer.dart | 2 +- .../{gestures => map/controller}/events/map_event_source.dart | 0 lib/src/{gestures => map/controller}/events/map_events.dart | 0 test/full_coverage_test.dart | 2 +- 7 files changed, 6 insertions(+), 6 deletions(-) rename lib/src/{gestures => map/controller}/events/map_event_source.dart (100%) rename lib/src/{gestures => map/controller}/events/map_events.dart (100%) diff --git a/lib/flutter_map.dart b/lib/flutter_map.dart index beb6b4bfc..fda2bfce0 100644 --- a/lib/flutter_map.dart +++ b/lib/flutter_map.dart @@ -18,8 +18,6 @@ library flutter_map; export 'package:flutter_map/src/geo/crs.dart'; export 'package:flutter_map/src/geo/latlng_bounds.dart'; -export 'package:flutter_map/src/gestures/events/map_event_source.dart'; -export 'package:flutter_map/src/gestures/events/map_events.dart'; export 'package:flutter_map/src/gestures/latlng_tween.dart'; export 'package:flutter_map/src/layer/attribution_layer/rich/animation.dart'; export 'package:flutter_map/src/layer/attribution_layer/rich/source.dart'; @@ -47,6 +45,8 @@ export 'package:flutter_map/src/layer/tile_layer/tile_update_transformer.dart'; export 'package:flutter_map/src/map/camera/camera.dart'; export 'package:flutter_map/src/map/camera/camera_constraint.dart'; export 'package:flutter_map/src/map/camera/camera_fit.dart'; +export 'package:flutter_map/src/map/controller/events/map_event_source.dart'; +export 'package:flutter_map/src/map/controller/events/map_events.dart'; export 'package:flutter_map/src/map/controller/map_controller.dart'; export 'package:flutter_map/src/map/controller/map_controller_impl.dart'; export 'package:flutter_map/src/map/options/cursor_keyboard_rotation.dart'; diff --git a/lib/src/layer/attribution_layer/rich/widget.dart b/lib/src/layer/attribution_layer/rich/widget.dart index e23d0e899..d108b182e 100644 --- a/lib/src/layer/attribution_layer/rich/widget.dart +++ b/lib/src/layer/attribution_layer/rich/widget.dart @@ -1,9 +1,9 @@ import 'dart:async'; import 'package:flutter/material.dart'; -import 'package:flutter_map/src/gestures/events/map_events.dart'; import 'package:flutter_map/src/layer/attribution_layer/rich/animation.dart'; import 'package:flutter_map/src/layer/attribution_layer/rich/source.dart'; +import 'package:flutter_map/src/map/controller/events/map_events.dart'; import 'package:flutter_map/src/map/controller/map_controller.dart'; /// Position to anchor [RichAttributionWidget] to relative to the [FlutterMap] diff --git a/lib/src/layer/tile_layer/tile_update_event.dart b/lib/src/layer/tile_layer/tile_update_event.dart index 1a51ad0cf..59c27af6b 100644 --- a/lib/src/layer/tile_layer/tile_update_event.dart +++ b/lib/src/layer/tile_layer/tile_update_event.dart @@ -1,5 +1,5 @@ -import 'package:flutter_map/src/gestures/events/map_events.dart'; import 'package:flutter_map/src/map/camera/camera.dart'; +import 'package:flutter_map/src/map/controller/events/map_events.dart'; import 'package:latlong2/latlong.dart'; import 'package:meta/meta.dart'; diff --git a/lib/src/layer/tile_layer/tile_update_transformer.dart b/lib/src/layer/tile_layer/tile_update_transformer.dart index 8489fc5c1..396212393 100644 --- a/lib/src/layer/tile_layer/tile_update_transformer.dart +++ b/lib/src/layer/tile_layer/tile_update_transformer.dart @@ -1,8 +1,8 @@ import 'dart:async'; -import 'package:flutter_map/src/gestures/events/map_events.dart'; import 'package:flutter_map/src/layer/tile_layer/tile_layer.dart'; import 'package:flutter_map/src/layer/tile_layer/tile_update_event.dart'; +import 'package:flutter_map/src/map/controller/events/map_events.dart'; import 'package:meta/meta.dart'; /// Defines which [TileUpdateEvent]s should cause which [TileUpdateEvent]s and diff --git a/lib/src/gestures/events/map_event_source.dart b/lib/src/map/controller/events/map_event_source.dart similarity index 100% rename from lib/src/gestures/events/map_event_source.dart rename to lib/src/map/controller/events/map_event_source.dart diff --git a/lib/src/gestures/events/map_events.dart b/lib/src/map/controller/events/map_events.dart similarity index 100% rename from lib/src/gestures/events/map_events.dart rename to lib/src/map/controller/events/map_events.dart diff --git a/test/full_coverage_test.dart b/test/full_coverage_test.dart index a4f2bc726..262d8fead 100644 --- a/test/full_coverage_test.dart +++ b/test/full_coverage_test.dart @@ -2,7 +2,6 @@ import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_map/src/geo/crs.dart'; import 'package:flutter_map/src/geo/latlng_bounds.dart'; -import 'package:flutter_map/src/gestures/events/map_events.dart'; import 'package:flutter_map/src/gestures/gestures.dart'; import 'package:flutter_map/src/gestures/latlng_tween.dart'; import 'package:flutter_map/src/gestures/map_interactive_viewer.dart'; @@ -42,6 +41,7 @@ import 'package:flutter_map/src/layer/tile_layer/tile_update_transformer.dart'; import 'package:flutter_map/src/map/camera/camera.dart'; import 'package:flutter_map/src/map/camera/camera_constraint.dart'; import 'package:flutter_map/src/map/camera/camera_fit.dart'; +import 'package:flutter_map/src/map/controller/events/map_events.dart'; import 'package:flutter_map/src/map/controller/map_controller.dart'; import 'package:flutter_map/src/map/controller/map_controller_impl.dart'; import 'package:flutter_map/src/map/inherited_model.dart'; From 94b687d3e5a6d98c1a62f06de8e09e8bfac10a78 Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Thu, 14 Dec 2023 13:05:16 +0100 Subject: [PATCH 040/102] move gestures to map directory --- lib/flutter_map.dart | 2 +- lib/src/map/controller/map_controller.dart | 2 +- lib/src/map/controller/map_controller_impl.dart | 2 +- lib/src/{ => map}/gestures/gestures.dart | 0 lib/src/{ => map}/gestures/latlng_tween.dart | 0 lib/src/{ => map}/gestures/map_interactive_viewer.dart | 2 +- lib/src/map/widget.dart | 2 +- test/full_coverage_test.dart | 6 +++--- 8 files changed, 8 insertions(+), 8 deletions(-) rename lib/src/{ => map}/gestures/gestures.dart (100%) rename lib/src/{ => map}/gestures/latlng_tween.dart (100%) rename lib/src/{ => map}/gestures/map_interactive_viewer.dart (99%) diff --git a/lib/flutter_map.dart b/lib/flutter_map.dart index fda2bfce0..9cbd6e3b2 100644 --- a/lib/flutter_map.dart +++ b/lib/flutter_map.dart @@ -18,7 +18,6 @@ library flutter_map; export 'package:flutter_map/src/geo/crs.dart'; export 'package:flutter_map/src/geo/latlng_bounds.dart'; -export 'package:flutter_map/src/gestures/latlng_tween.dart'; export 'package:flutter_map/src/layer/attribution_layer/rich/animation.dart'; export 'package:flutter_map/src/layer/attribution_layer/rich/source.dart'; export 'package:flutter_map/src/layer/attribution_layer/rich/widget.dart'; @@ -49,6 +48,7 @@ export 'package:flutter_map/src/map/controller/events/map_event_source.dart'; export 'package:flutter_map/src/map/controller/events/map_events.dart'; export 'package:flutter_map/src/map/controller/map_controller.dart'; export 'package:flutter_map/src/map/controller/map_controller_impl.dart'; +export 'package:flutter_map/src/map/gestures/latlng_tween.dart'; export 'package:flutter_map/src/map/options/cursor_keyboard_rotation.dart'; export 'package:flutter_map/src/map/options/interaction_options.dart'; export 'package:flutter_map/src/map/options/interactive_flags.dart'; diff --git a/lib/src/map/controller/map_controller.dart b/lib/src/map/controller/map_controller.dart index 877d2e0f2..ffa9a2931 100644 --- a/lib/src/map/controller/map_controller.dart +++ b/lib/src/map/controller/map_controller.dart @@ -3,7 +3,7 @@ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter_map/flutter_map.dart'; -import 'package:flutter_map/src/gestures/map_interactive_viewer.dart'; +import 'package:flutter_map/src/map/gestures/map_interactive_viewer.dart'; import 'package:flutter_map/src/map/inherited_model.dart'; import 'package:latlong2/latlong.dart'; diff --git a/lib/src/map/controller/map_controller_impl.dart b/lib/src/map/controller/map_controller_impl.dart index ecd103b75..5c11c67de 100644 --- a/lib/src/map/controller/map_controller_impl.dart +++ b/lib/src/map/controller/map_controller_impl.dart @@ -3,7 +3,7 @@ import 'dart:math'; import 'package:flutter/widgets.dart'; import 'package:flutter_map/flutter_map.dart'; -import 'package:flutter_map/src/gestures/map_interactive_viewer.dart'; +import 'package:flutter_map/src/map/gestures/map_interactive_viewer.dart'; import 'package:latlong2/latlong.dart'; import 'package:vector_math/vector_math_64.dart'; diff --git a/lib/src/gestures/gestures.dart b/lib/src/map/gestures/gestures.dart similarity index 100% rename from lib/src/gestures/gestures.dart rename to lib/src/map/gestures/gestures.dart diff --git a/lib/src/gestures/latlng_tween.dart b/lib/src/map/gestures/latlng_tween.dart similarity index 100% rename from lib/src/gestures/latlng_tween.dart rename to lib/src/map/gestures/latlng_tween.dart diff --git a/lib/src/gestures/map_interactive_viewer.dart b/lib/src/map/gestures/map_interactive_viewer.dart similarity index 99% rename from lib/src/gestures/map_interactive_viewer.dart rename to lib/src/map/gestures/map_interactive_viewer.dart index 372ce308c..84bf0e10f 100644 --- a/lib/src/gestures/map_interactive_viewer.dart +++ b/lib/src/map/gestures/map_interactive_viewer.dart @@ -1,7 +1,7 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_map/flutter_map.dart'; -import 'package:flutter_map/src/gestures/gestures.dart'; +import 'package:flutter_map/src/map/gestures/gestures.dart'; class MapInteractiveViewer extends StatefulWidget { final ChildBuilder builder; diff --git a/lib/src/map/widget.dart b/lib/src/map/widget.dart index 958f0cdb9..a93668d4a 100644 --- a/lib/src/map/widget.dart +++ b/lib/src/map/widget.dart @@ -3,7 +3,7 @@ import 'dart:math'; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_map/flutter_map.dart'; -import 'package:flutter_map/src/gestures/map_interactive_viewer.dart'; +import 'package:flutter_map/src/map/gestures/map_interactive_viewer.dart'; import 'package:flutter_map/src/map/inherited_model.dart'; import 'package:logger/logger.dart'; diff --git a/test/full_coverage_test.dart b/test/full_coverage_test.dart index 262d8fead..1d08a8e8c 100644 --- a/test/full_coverage_test.dart +++ b/test/full_coverage_test.dart @@ -2,9 +2,6 @@ import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_map/src/geo/crs.dart'; import 'package:flutter_map/src/geo/latlng_bounds.dart'; -import 'package:flutter_map/src/gestures/gestures.dart'; -import 'package:flutter_map/src/gestures/latlng_tween.dart'; -import 'package:flutter_map/src/gestures/map_interactive_viewer.dart'; import 'package:flutter_map/src/layer/attribution_layer/rich/animation.dart'; import 'package:flutter_map/src/layer/attribution_layer/rich/source.dart'; import 'package:flutter_map/src/layer/attribution_layer/rich/widget.dart'; @@ -44,6 +41,9 @@ import 'package:flutter_map/src/map/camera/camera_fit.dart'; import 'package:flutter_map/src/map/controller/events/map_events.dart'; import 'package:flutter_map/src/map/controller/map_controller.dart'; import 'package:flutter_map/src/map/controller/map_controller_impl.dart'; +import 'package:flutter_map/src/map/gestures/gestures.dart'; +import 'package:flutter_map/src/map/gestures/latlng_tween.dart'; +import 'package:flutter_map/src/map/gestures/map_interactive_viewer.dart'; import 'package:flutter_map/src/map/inherited_model.dart'; import 'package:flutter_map/src/map/options/cursor_keyboard_rotation.dart'; import 'package:flutter_map/src/map/options/interaction_options.dart'; From 7d615158734d818ac69b9e5fa41577cdaef836fe Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Thu, 14 Dec 2023 13:21:39 +0100 Subject: [PATCH 041/102] move things around --- example/lib/pages/interactive_test_page.dart | 4 +- example/lib/pages/latlng_to_screen_point.dart | 2 +- example/lib/pages/many_circles.dart | 2 +- example/lib/pages/many_markers.dart | 2 +- example/lib/pages/markers.dart | 4 +- lib/flutter_map.dart | 2 +- .../map/controller/map_controller_impl.dart | 4 +- lib/src/map/gestures/gestures.dart | 4 +- .../map/gestures/map_interactive_viewer.dart | 4 +- ...ctive_flags.dart => enabled_gestures.dart} | 22 +++---- lib/src/map/options/interaction_options.dart | 61 ++++--------------- test/flutter_map_test.dart | 4 +- test/full_coverage_test.dart | 3 +- 13 files changed, 42 insertions(+), 76 deletions(-) rename lib/src/map/options/{interactive_flags.dart => enabled_gestures.dart} (95%) diff --git a/example/lib/pages/interactive_test_page.dart b/example/lib/pages/interactive_test_page.dart index 7843c2f5a..c87183af1 100644 --- a/example/lib/pages/interactive_test_page.dart +++ b/example/lib/pages/interactive_test_page.dart @@ -5,7 +5,7 @@ import 'package:flutter_map_example/widgets/drawer/menu_drawer.dart'; import 'package:latlong2/latlong.dart'; class InteractiveFlagsPage extends StatefulWidget { - static const String route = '/interactive_flags_page'; + static const String route = '/enabled_gestures_page'; const InteractiveFlagsPage({super.key}); @@ -121,7 +121,7 @@ class _InteractiveFlagsPageState extends State { initialCenter: const LatLng(51.5, -0.09), initialZoom: 11, interactionOptions: InteractionOptions( - flags: InteractiveFlags.bitfield(flags), + enabledGestures: EnabledGestures.bitfield(flags), cursorKeyboardRotationOptions: CursorKeyboardRotationOptions( isKeyTrigger: (key) => diff --git a/example/lib/pages/latlng_to_screen_point.dart b/example/lib/pages/latlng_to_screen_point.dart index 9391048e6..dffc6a55f 100644 --- a/example/lib/pages/latlng_to_screen_point.dart +++ b/example/lib/pages/latlng_to_screen_point.dart @@ -48,7 +48,7 @@ class _LatLngToScreenPointPageState extends State { initialCenter: const LatLng(51.5, -0.09), initialZoom: 11, interactionOptions: InteractionOptions( - flags: InteractiveFlags.bitfield( + enabledGestures: EnabledGestures.bitfield( ~InteractiveFlag.doubleTapZoom, ), ), diff --git a/example/lib/pages/many_circles.dart b/example/lib/pages/many_circles.dart index 5d0208824..70d329c1c 100644 --- a/example/lib/pages/many_circles.dart +++ b/example/lib/pages/many_circles.dart @@ -74,7 +74,7 @@ class ManyCirclesPageState extends State { initialCenter: LatLng(50, 20), initialZoom: 5, interactionOptions: InteractionOptions( - flags: InteractiveFlags.all(rotate: false), + enabledGestures: EnabledGestures.all(rotate: false), ), ), children: [ diff --git a/example/lib/pages/many_markers.dart b/example/lib/pages/many_markers.dart index 7d616c274..c8ab91046 100644 --- a/example/lib/pages/many_markers.dart +++ b/example/lib/pages/many_markers.dart @@ -72,7 +72,7 @@ class ManyMarkersPageState extends State { initialCenter: LatLng(50, 20), initialZoom: 5, interactionOptions: InteractionOptions( - flags: InteractiveFlags.all(rotate: false), + enabledGestures: EnabledGestures.all(rotate: false), ), ), children: [ diff --git a/example/lib/pages/markers.dart b/example/lib/pages/markers.dart index 264596cc5..a4c46ddfe 100644 --- a/example/lib/pages/markers.dart +++ b/example/lib/pages/markers.dart @@ -114,8 +114,8 @@ class _MarkerPageState extends State { setState(() => customMarkers.add(buildPin(p))); }, interactionOptions: InteractionOptions( - flags: - InteractiveFlags.bitfield(~InteractiveFlag.doubleTapZoom), + enabledGestures: + EnabledGestures.bitfield(~InteractiveFlag.doubleTapZoom), ), ), children: [ diff --git a/lib/flutter_map.dart b/lib/flutter_map.dart index 9cbd6e3b2..c023de2f8 100644 --- a/lib/flutter_map.dart +++ b/lib/flutter_map.dart @@ -50,8 +50,8 @@ export 'package:flutter_map/src/map/controller/map_controller.dart'; export 'package:flutter_map/src/map/controller/map_controller_impl.dart'; export 'package:flutter_map/src/map/gestures/latlng_tween.dart'; export 'package:flutter_map/src/map/options/cursor_keyboard_rotation.dart'; +export 'package:flutter_map/src/map/options/enabled_gestures.dart'; export 'package:flutter_map/src/map/options/interaction_options.dart'; -export 'package:flutter_map/src/map/options/interactive_flags.dart'; export 'package:flutter_map/src/map/options/map_options.dart'; export 'package:flutter_map/src/map/widget.dart'; export 'package:flutter_map/src/misc/bounds.dart'; diff --git a/lib/src/map/controller/map_controller_impl.dart b/lib/src/map/controller/map_controller_impl.dart index 5c11c67de..ea18970c7 100644 --- a/lib/src/map/controller/map_controller_impl.dart +++ b/lib/src/map/controller/map_controller_impl.dart @@ -351,8 +351,8 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState> if (value.options != null && value.options!.interactionOptions != newOptions.interactionOptions) { _interactiveViewerState.updateGestures( - value.options!.interactionOptions.flags, - newOptions.interactionOptions.flags, + value.options!.interactionOptions.enabledGestures, + newOptions.interactionOptions.enabledGestures, ); } diff --git a/lib/src/map/gestures/gestures.dart b/lib/src/map/gestures/gestures.dart index 23955755b..d1df346cb 100644 --- a/lib/src/map/gestures/gestures.dart +++ b/lib/src/map/gestures/gestures.dart @@ -251,7 +251,7 @@ class TwoFingerGestures extends Gesture { double? _lastRotation; TwoFingerGestures({ - required InteractiveFlags interactiveFlags, + required EnabledGestures interactiveFlags, required super.controller, }) : moveEnabled = interactiveFlags.pinchMove, zoomEnabled = interactiveFlags.pinchZoom, @@ -335,7 +335,7 @@ class DragGesture extends Gesture { DragGesture({ required super.controller, - required InteractiveFlags interactiveFlags, + required EnabledGestures interactiveFlags, }) : flingEnabled = interactiveFlags.flingAnimation; bool get isActive => _lastLocalFocal != null; diff --git a/lib/src/map/gestures/map_interactive_viewer.dart b/lib/src/map/gestures/map_interactive_viewer.dart index 84bf0e10f..49fe48a85 100644 --- a/lib/src/map/gestures/map_interactive_viewer.dart +++ b/lib/src/map/gestures/map_interactive_viewer.dart @@ -66,7 +66,7 @@ class MapInteractiveViewerState extends State TertiaryLongPressGesture(controller: widget.controller); } // gestures that change the map camera - updateGestures(null, _interactionOptions.flags); + updateGestures(null, _interactionOptions.enabledGestures); } @override @@ -205,7 +205,7 @@ class MapInteractiveViewerState extends State } /// Used by the internal map controller to update interaction gestures - void updateGestures(InteractiveFlags? oldFlags, InteractiveFlags newFlags) { + void updateGestures(EnabledGestures? oldFlags, EnabledGestures newFlags) { if (oldFlags == newFlags) return; if (newFlags.hasMultiFinger()) { _twoFingerInput = TwoFingerGestures( diff --git a/lib/src/map/options/interactive_flags.dart b/lib/src/map/options/enabled_gestures.dart similarity index 95% rename from lib/src/map/options/interactive_flags.dart rename to lib/src/map/options/enabled_gestures.dart index a0dddb0b4..57f2e597e 100644 --- a/lib/src/map/options/interactive_flags.dart +++ b/lib/src/map/options/enabled_gestures.dart @@ -2,12 +2,12 @@ import 'package:flutter_map/flutter_map.dart'; import 'package:meta/meta.dart'; @immutable -class InteractiveFlags { +class EnabledGestures { /// Private constructor, use the constructors [InteractiveFlags.all] or /// [InteractiveFlags.none] instead to enable or disable all gestures by default. /// If you want to define your enabled gestures using bitfield operations, /// use [InteractiveFlags.bitfield] instead. - const InteractiveFlags._({ + const EnabledGestures._({ required this.drag, required this.flingAnimation, required this.pinchMove, @@ -20,7 +20,7 @@ class InteractiveFlags { }); /// Shortcut constructor to allow all gestures that don't rotate the map. - const InteractiveFlags.noRotation() + const EnabledGestures.noRotation() : this.all( rotate: false, ctrlDragRotate: false, @@ -31,7 +31,7 @@ class InteractiveFlags { /// /// In case you want have no or only few gestures enabled use the /// [InteractiveFlags.none] constructor instead. - const InteractiveFlags.all({ + const EnabledGestures.all({ this.drag = true, this.flingAnimation = true, this.pinchMove = true, @@ -48,7 +48,7 @@ class InteractiveFlags { /// /// In case you want have most or all of the gestures enabled use the /// [InteractiveFlags.all] constructor instead. - const InteractiveFlags.none({ + const EnabledGestures.none({ this.drag = false, this.flingAnimation = false, this.pinchMove = false, @@ -62,8 +62,8 @@ class InteractiveFlags { /// This constructor supports bitfield operations on the static fields /// from [InteractiveFlag]. - factory InteractiveFlags.bitfield(int flags) { - return InteractiveFlags._( + factory EnabledGestures.bitfield(int flags) { + return EnabledGestures._( drag: InteractiveFlag.hasFlag(flags, InteractiveFlag.drag), flingAnimation: InteractiveFlag.hasFlag(flags, InteractiveFlag.flingAnimation), @@ -118,8 +118,8 @@ class InteractiveFlags { bool hasMultiFinger() => pinchMove || pinchZoom || rotate; /// Wither to change the value of some gestures. Returns a new - /// [InteractiveFlags] object. - InteractiveFlags withFlag({ + /// [EnabledGestures] object. + EnabledGestures withFlag({ bool? pinchZoom, bool? drag, bool? flingAnimation, @@ -130,7 +130,7 @@ class InteractiveFlags { bool? rotate, bool? ctrlDragRotate, }) => - InteractiveFlags._( + EnabledGestures._( pinchZoom: pinchZoom ?? this.pinchZoom, drag: drag ?? this.drag, flingAnimation: flingAnimation ?? this.flingAnimation, @@ -145,7 +145,7 @@ class InteractiveFlags { @override bool operator ==(Object other) => identical(this, other) || - other is InteractiveFlags && + other is EnabledGestures && runtimeType == other.runtimeType && drag == other.drag && flingAnimation == other.flingAnimation && diff --git a/lib/src/map/options/interaction_options.dart b/lib/src/map/options/interaction_options.dart index f03bd8976..6a343c2ab 100644 --- a/lib/src/map/options/interaction_options.dart +++ b/lib/src/map/options/interaction_options.dart @@ -1,22 +1,11 @@ import 'package:flutter_map/src/map/options/cursor_keyboard_rotation.dart'; -import 'package:flutter_map/src/map/options/interactive_flags.dart'; +import 'package:flutter_map/src/map/options/enabled_gestures.dart'; import 'package:meta/meta.dart'; @immutable final class InteractionOptions { - /// See [InteractiveFlags] for custom settings - final InteractiveFlags flags; - - /// Prints multi finger gesture winner Helps to fine adjust - /// [rotationThreshold] and [pinchZoomThreshold] and [pinchMoveThreshold] - /// Note: only takes effect if [enableMultiFingerGestureRace] is true - final bool debugMultiFingerGestureWinner; - - /// If true then [rotationThreshold] and [pinchZoomThreshold] and - /// [pinchMoveThreshold] will race If multiple gestures win at the same time - /// then precedence: [pinchZoomWinGestures] > [rotationWinGestures] > - /// [pinchMoveWinGestures] - final bool enableMultiFingerGestureRace; + /// See [EnabledGestures] for custom settings + final EnabledGestures enabledGestures; /// Rotation threshold in degree default is 20.0 Map starts to rotate when /// [rotationThreshold] has been achieved or another multi finger gesture wins @@ -25,12 +14,6 @@ final class InteractionOptions { /// is false then rotate cannot win final double rotationThreshold; - /// When [rotationThreshold] wins over [pinchZoomThreshold] and - /// [pinchMoveThreshold] then [rotationWinGestures] gestures will be used. By - /// default only [MultiFingerGesture.rotate] gesture will take effect see - /// [MultiFingerGesture] for custom settings - final int rotationWinGestures; - /// Pinch Zoom threshold default is 0.5 Map starts to zoom when /// [pinchZoomThreshold] has been achieved or another multi finger gesture /// wins which allows [MultiFingerGesture.pinchZoom] Note: if @@ -52,12 +35,6 @@ final class InteractionOptions { /// then pinch move cannot win final double pinchMoveThreshold; - /// When [pinchMoveThreshold] wins over [rotationThreshold] and - /// [pinchZoomThreshold] then [pinchMoveWinGestures] gestures will be used. By - /// default [MultiFingerGesture.pinchMove] and [MultiFingerGesture.pinchZoom] - /// gestures will take effect see [MultiFingerGesture] for custom settings - final int pinchMoveWinGestures; - final double scrollWheelVelocity; /// Options to configure cursor/keyboard rotation @@ -75,17 +52,12 @@ final class InteractionOptions { final CursorKeyboardRotationOptions cursorKeyboardRotationOptions; const InteractionOptions({ - this.flags = const InteractiveFlags.all(), - this.debugMultiFingerGestureWinner = false, - this.enableMultiFingerGestureRace = false, + this.enabledGestures = const EnabledGestures.all(), this.rotationThreshold = 20.0, - this.rotationWinGestures = InteractiveFlag.rotate, this.pinchZoomThreshold = 0.5, this.pinchZoomWinGestures = InteractiveFlag.pinchZoom | InteractiveFlag.pinchMove, this.pinchMoveThreshold = 40.0, - this.pinchMoveWinGestures = - InteractiveFlag.pinchZoom | InteractiveFlag.pinchMove, this.scrollWheelVelocity = 0.005, this.cursorKeyboardRotationOptions = const CursorKeyboardRotationOptions(), }) : assert( @@ -103,29 +75,22 @@ final class InteractionOptions { @override bool operator ==(Object other) => - other is InteractionOptions && - flags == other.flags && - debugMultiFingerGestureWinner == other.debugMultiFingerGestureWinner && - enableMultiFingerGestureRace == other.enableMultiFingerGestureRace && - rotationThreshold == other.rotationThreshold && - rotationWinGestures == other.rotationWinGestures && - pinchZoomThreshold == other.pinchZoomThreshold && - pinchZoomWinGestures == other.pinchZoomWinGestures && - pinchMoveThreshold == other.pinchMoveThreshold && - pinchMoveWinGestures == other.pinchMoveWinGestures && - scrollWheelVelocity == other.scrollWheelVelocity; + identical(this, other) || + (other is InteractionOptions && + enabledGestures == other.enabledGestures && + rotationThreshold == other.rotationThreshold && + pinchZoomThreshold == other.pinchZoomThreshold && + pinchZoomWinGestures == other.pinchZoomWinGestures && + pinchMoveThreshold == other.pinchMoveThreshold && + scrollWheelVelocity == other.scrollWheelVelocity); @override int get hashCode => Object.hash( - flags, - debugMultiFingerGestureWinner, - enableMultiFingerGestureRace, + enabledGestures, rotationThreshold, - rotationWinGestures, pinchZoomThreshold, pinchZoomWinGestures, pinchMoveThreshold, - pinchMoveWinGestures, scrollWheelVelocity, ); } diff --git a/test/flutter_map_test.dart b/test/flutter_map_test.dart index 70102b2eb..c7ce320dd 100644 --- a/test/flutter_map_test.dart +++ b/test/flutter_map_test.dart @@ -217,7 +217,7 @@ class _TestRebuildsAppState extends State { Crs _crs = const Epsg3857(); /// double tap gestures delay the tap gestures, disable them here - InteractiveFlags _interactiveFlags = const InteractiveFlags.all( + EnabledGestures _interactiveFlags = const EnabledGestures.all( doubleTapZoom: false, doubleTapDragZoom: false, ); @@ -237,7 +237,7 @@ class _TestRebuildsAppState extends State { options: MapOptions( crs: _crs, interactionOptions: InteractionOptions( - flags: _interactiveFlags, + enabledGestures: _interactiveFlags, ), ), children: [ diff --git a/test/full_coverage_test.dart b/test/full_coverage_test.dart index 1d08a8e8c..991fbcf34 100644 --- a/test/full_coverage_test.dart +++ b/test/full_coverage_test.dart @@ -38,6 +38,7 @@ import 'package:flutter_map/src/layer/tile_layer/tile_update_transformer.dart'; import 'package:flutter_map/src/map/camera/camera.dart'; import 'package:flutter_map/src/map/camera/camera_constraint.dart'; import 'package:flutter_map/src/map/camera/camera_fit.dart'; +import 'package:flutter_map/src/map/controller/events/map_event_source.dart'; import 'package:flutter_map/src/map/controller/events/map_events.dart'; import 'package:flutter_map/src/map/controller/map_controller.dart'; import 'package:flutter_map/src/map/controller/map_controller_impl.dart'; @@ -46,8 +47,8 @@ import 'package:flutter_map/src/map/gestures/latlng_tween.dart'; import 'package:flutter_map/src/map/gestures/map_interactive_viewer.dart'; import 'package:flutter_map/src/map/inherited_model.dart'; import 'package:flutter_map/src/map/options/cursor_keyboard_rotation.dart'; +import 'package:flutter_map/src/map/options/enabled_gestures.dart'; import 'package:flutter_map/src/map/options/interaction_options.dart'; -import 'package:flutter_map/src/map/options/interactive_flags.dart'; import 'package:flutter_map/src/map/options/map_options.dart'; import 'package:flutter_map/src/map/widget.dart'; import 'package:flutter_map/src/misc/bounds.dart'; From 3b7b08d383b96d5e742ee6aa7da369aa04ce338d Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Thu, 14 Dec 2023 14:00:00 +0100 Subject: [PATCH 042/102] clean up typedefs --- lib/src/map/options/map_options.dart | 28 ++++++++-------------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/lib/src/map/options/map_options.dart b/lib/src/map/options/map_options.dart index 79540c76b..4a95db888 100644 --- a/lib/src/map/options/map_options.dart +++ b/lib/src/map/options/map_options.dart @@ -36,12 +36,15 @@ class MapOptions { final LongPressCallback? onSecondaryLongPress; final GestureCallback? onTertiaryTap; final LongPressCallback? onTertiaryLongPress; - final PointerDownCallback? onPointerDown; - final PointerUpCallback? onPointerUp; - final PointerCancelCallback? onPointerCancel; - final PointerHoverCallback? onPointerHover; + + final void Function(PointerDownEvent event, LatLng point)? onPointerDown; + final void Function(PointerUpEvent event, LatLng point)? onPointerUp; + final void Function(PointerCancelEvent event, LatLng point)? onPointerCancel; + final void Function(PointerHoverEvent event, LatLng point)? onPointerHover; + final PositionCallback? onPositionChanged; - final MapEventCallback? onMapEvent; + + final void Function(MapEvent event)? onMapEvent; /// Define limits for viewing the map. final CameraConstraint cameraConstraint; @@ -177,23 +180,8 @@ class MapOptions { ]); } -typedef MapEventCallback = void Function(MapEvent); - typedef GestureCallback = void Function(TapDownDetails details, LatLng point); typedef LongPressCallback = void Function( LongPressStartDetails details, LatLng point, ); -typedef PointerDownCallback = void Function( - PointerDownEvent event, - LatLng point, -); -typedef PointerUpCallback = void Function(PointerUpEvent event, LatLng point); -typedef PointerCancelCallback = void Function( - PointerCancelEvent event, - LatLng point, -); -typedef PointerHoverCallback = void Function( - PointerHoverEvent event, - LatLng point, -); From 1fce148f9dfb0f4587d7ed887a754c25cb920e03 Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Thu, 14 Dec 2023 14:15:36 +0100 Subject: [PATCH 043/102] clean up --- example/lib/main.dart | 4 ++-- ...eractive_test_page.dart => gestures_page.dart} | 15 ++++++++------- example/lib/pages/latlng_to_screen_point.dart | 6 ++---- example/lib/pages/markers.dart | 5 ++--- example/lib/widgets/drawer/menu_drawer.dart | 4 ++-- 5 files changed, 16 insertions(+), 18 deletions(-) rename example/lib/pages/{interactive_test_page.dart => gestures_page.dart} (94%) diff --git a/example/lib/main.dart b/example/lib/main.dart index dfa3974aa..6b2e105b5 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -7,8 +7,8 @@ import 'package:flutter_map_example/pages/custom_crs/custom_crs.dart'; import 'package:flutter_map_example/pages/epsg3413_crs.dart'; import 'package:flutter_map_example/pages/epsg4326_crs.dart'; import 'package:flutter_map_example/pages/fallback_url_page.dart'; +import 'package:flutter_map_example/pages/gestures_page.dart'; import 'package:flutter_map_example/pages/home.dart'; -import 'package:flutter_map_example/pages/interactive_test_page.dart'; import 'package:flutter_map_example/pages/latlng_to_screen_point.dart'; import 'package:flutter_map_example/pages/many_circles.dart'; import 'package:flutter_map_example/pages/many_markers.dart'; @@ -71,7 +71,7 @@ class MyApp extends StatelessWidget { TileLoadingErrorHandle.route: (context) => const TileLoadingErrorHandle(), TileBuilderPage.route: (context) => const TileBuilderPage(), - InteractiveFlagsPage.route: (context) => const InteractiveFlagsPage(), + GesturesPage.route: (context) => const GesturesPage(), ManyMarkersPage.route: (context) => const ManyMarkersPage(), StatefulMarkersPage.route: (context) => const StatefulMarkersPage(), MapInsideListViewPage.route: (context) => const MapInsideListViewPage(), diff --git a/example/lib/pages/interactive_test_page.dart b/example/lib/pages/gestures_page.dart similarity index 94% rename from example/lib/pages/interactive_test_page.dart rename to example/lib/pages/gestures_page.dart index c87183af1..24bb3d677 100644 --- a/example/lib/pages/interactive_test_page.dart +++ b/example/lib/pages/gestures_page.dart @@ -4,16 +4,16 @@ import 'package:flutter_map_example/misc/tile_providers.dart'; import 'package:flutter_map_example/widgets/drawer/menu_drawer.dart'; import 'package:latlong2/latlong.dart'; -class InteractiveFlagsPage extends StatefulWidget { +class GesturesPage extends StatefulWidget { static const String route = '/enabled_gestures_page'; - const InteractiveFlagsPage({super.key}); + const GesturesPage({super.key}); @override - State createState() => _InteractiveFlagsPageState(); + State createState() => _GesturesPageState(); } -class _InteractiveFlagsPageState extends State { +class _GesturesPageState extends State { static const availableFlags = { 'Movement': { InteractiveFlag.drag: 'Drag', @@ -24,10 +24,11 @@ class _InteractiveFlagsPageState extends State { InteractiveFlag.pinchZoom: 'Pinch', InteractiveFlag.scrollWheelZoom: 'Scroll', InteractiveFlag.doubleTapZoom: 'Double tap', - InteractiveFlag.doubleTapDragZoom: '+ drag', + InteractiveFlag.doubleTapDragZoom: 'Double tap + drag', }, 'Rotation': { InteractiveFlag.rotate: 'Twist', + InteractiveFlag.ctrlDragRotate: 'CTRL + Drag', }, }; @@ -40,8 +41,8 @@ class _InteractiveFlagsPageState extends State { Widget build(BuildContext context) { final screenWidth = MediaQuery.sizeOf(context).width; return Scaffold( - appBar: AppBar(title: const Text('Interactive Flags')), - drawer: const MenuDrawer(InteractiveFlagsPage.route), + appBar: AppBar(title: const Text('Input gestures')), + drawer: const MenuDrawer(GesturesPage.route), body: Padding( padding: const EdgeInsets.all(8), child: Column( diff --git a/example/lib/pages/latlng_to_screen_point.dart b/example/lib/pages/latlng_to_screen_point.dart index dffc6a55f..2922083bf 100644 --- a/example/lib/pages/latlng_to_screen_point.dart +++ b/example/lib/pages/latlng_to_screen_point.dart @@ -47,10 +47,8 @@ class _LatLngToScreenPointPageState extends State { options: MapOptions( initialCenter: const LatLng(51.5, -0.09), initialZoom: 11, - interactionOptions: InteractionOptions( - enabledGestures: EnabledGestures.bitfield( - ~InteractiveFlag.doubleTapZoom, - ), + interactionOptions: const InteractionOptions( + enabledGestures: EnabledGestures.all(doubleTapZoom: false), ), onTap: (_, latLng) { final point = mapController.camera diff --git a/example/lib/pages/markers.dart b/example/lib/pages/markers.dart index a4c46ddfe..4471d95b8 100644 --- a/example/lib/pages/markers.dart +++ b/example/lib/pages/markers.dart @@ -113,9 +113,8 @@ class _MarkerPageState extends State { onTap: (_, p) { setState(() => customMarkers.add(buildPin(p))); }, - interactionOptions: InteractionOptions( - enabledGestures: - EnabledGestures.bitfield(~InteractiveFlag.doubleTapZoom), + interactionOptions: const InteractionOptions( + enabledGestures: EnabledGestures.noRotation(), ), ), children: [ diff --git a/example/lib/widgets/drawer/menu_drawer.dart b/example/lib/widgets/drawer/menu_drawer.dart index d3bf254fc..404faee71 100644 --- a/example/lib/widgets/drawer/menu_drawer.dart +++ b/example/lib/widgets/drawer/menu_drawer.dart @@ -7,8 +7,8 @@ import 'package:flutter_map_example/pages/custom_crs/custom_crs.dart'; import 'package:flutter_map_example/pages/epsg3413_crs.dart'; import 'package:flutter_map_example/pages/epsg4326_crs.dart'; import 'package:flutter_map_example/pages/fallback_url_page.dart'; +import 'package:flutter_map_example/pages/gestures_page.dart'; import 'package:flutter_map_example/pages/home.dart'; -import 'package:flutter_map_example/pages/interactive_test_page.dart'; import 'package:flutter_map_example/pages/latlng_to_screen_point.dart'; import 'package:flutter_map_example/pages/many_circles.dart'; import 'package:flutter_map_example/pages/many_markers.dart'; @@ -108,7 +108,7 @@ class MenuDrawer extends StatelessWidget { ), MenuItemWidget( caption: 'Interactive Flags', - routeName: InteractiveFlagsPage.route, + routeName: GesturesPage.route, currentRoute: currentRoute, ), const Divider(), From a1084fcbd20f71a2f5cf13e24ce28e797de38a0f Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Fri, 15 Dec 2023 19:52:13 +0100 Subject: [PATCH 044/102] revert rename of `TapCallback` --- lib/src/map/options/map_options.dart | 69 ++++++++++++++-------------- 1 file changed, 35 insertions(+), 34 deletions(-) diff --git a/lib/src/map/options/map_options.dart b/lib/src/map/options/map_options.dart index 4a95db888..cd0c5713c 100644 --- a/lib/src/map/options/map_options.dart +++ b/lib/src/map/options/map_options.dart @@ -30,11 +30,11 @@ class MapOptions { final Color backgroundColor; - final GestureCallback? onTap; + final TapCallback? onTap; final LongPressCallback? onLongPress; - final GestureCallback? onSecondaryTap; + final TapCallback? onSecondaryTap; final LongPressCallback? onSecondaryLongPress; - final GestureCallback? onTertiaryTap; + final TapCallback? onTertiaryTap; final LongPressCallback? onTertiaryLongPress; final void Function(PointerDownEvent event, LatLng point)? onPointerDown; @@ -121,39 +121,40 @@ class MapOptions { /// context with no [FlutterMap] ancestor a [StateError] will be thrown. static MapOptions of(BuildContext context) => maybeOf(context) ?? - (throw StateError( - '`MapOptions.of()` should not be called outside a `FlutterMap` and its descendants')); + (throw StateError( + '`MapOptions.of()` should not be called outside a `FlutterMap` and its descendants')); @override bool operator ==(Object other) => other is MapOptions && - crs == other.crs && - initialCenter == other.initialCenter && - initialZoom == other.initialZoom && - initialRotation == other.initialRotation && - initialCameraFit == other.initialCameraFit && - minZoom == other.minZoom && - maxZoom == other.maxZoom && - backgroundColor == other.backgroundColor && - onTap == other.onTap && - onSecondaryTap == other.onSecondaryTap && - onLongPress == other.onLongPress && - onPointerDown == other.onPointerDown && - onPointerUp == other.onPointerUp && - onPointerCancel == other.onPointerCancel && - onPointerHover == other.onPointerHover && - onPositionChanged == other.onPositionChanged && - onMapEvent == other.onMapEvent && - cameraConstraint == other.cameraConstraint && - onMapReady == other.onMapReady && - keepAlive == other.keepAlive && - interactionOptions == other.interactionOptions && - backgroundColor == other.backgroundColor && - applyPointerTranslucencyToLayers == - other.applyPointerTranslucencyToLayers; + crs == other.crs && + initialCenter == other.initialCenter && + initialZoom == other.initialZoom && + initialRotation == other.initialRotation && + initialCameraFit == other.initialCameraFit && + minZoom == other.minZoom && + maxZoom == other.maxZoom && + backgroundColor == other.backgroundColor && + onTap == other.onTap && + onSecondaryTap == other.onSecondaryTap && + onLongPress == other.onLongPress && + onPointerDown == other.onPointerDown && + onPointerUp == other.onPointerUp && + onPointerCancel == other.onPointerCancel && + onPointerHover == other.onPointerHover && + onPositionChanged == other.onPositionChanged && + onMapEvent == other.onMapEvent && + cameraConstraint == other.cameraConstraint && + onMapReady == other.onMapReady && + keepAlive == other.keepAlive && + interactionOptions == other.interactionOptions && + backgroundColor == other.backgroundColor && + applyPointerTranslucencyToLayers == + other.applyPointerTranslucencyToLayers; @override - int get hashCode => Object.hashAll([ + int get hashCode => + Object.hashAll([ crs, initialCenter, initialZoom, @@ -180,8 +181,8 @@ class MapOptions { ]); } -typedef GestureCallback = void Function(TapDownDetails details, LatLng point); +typedef TapCallback = void Function(TapDownDetails details, LatLng point); typedef LongPressCallback = void Function( - LongPressStartDetails details, - LatLng point, -); + LongPressStartDetails details, + LatLng point, + ); From 121da5e3bed41d15fec35bc58a009639fc456eac Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Fri, 15 Dec 2023 19:59:00 +0100 Subject: [PATCH 045/102] remove `pinchZoomWinGestures` option --- lib/src/map/options/interaction_options.dart | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/lib/src/map/options/interaction_options.dart b/lib/src/map/options/interaction_options.dart index 6a343c2ab..25b24223e 100644 --- a/lib/src/map/options/interaction_options.dart +++ b/lib/src/map/options/interaction_options.dart @@ -21,12 +21,6 @@ final class InteractionOptions { /// [enableMultiFingerGestureRace] is false then zoom cannot win final double pinchZoomThreshold; - /// When [pinchZoomThreshold] wins over [rotationThreshold] and - /// [pinchMoveThreshold] then [pinchZoomWinGestures] gestures will be used. By - /// default [MultiFingerGesture.pinchZoom] and [MultiFingerGesture.pinchMove] - /// gestures will take effect see [MultiFingerGesture] for custom settings - final int pinchZoomWinGestures; - /// Pinch Move threshold default is 40.0 (note: this doesn't take any effect /// on drag) Map starts to move when [pinchMoveThreshold] has been achieved or /// another multi finger gesture wins which allows @@ -55,8 +49,6 @@ final class InteractionOptions { this.enabledGestures = const EnabledGestures.all(), this.rotationThreshold = 20.0, this.pinchZoomThreshold = 0.5, - this.pinchZoomWinGestures = - InteractiveFlag.pinchZoom | InteractiveFlag.pinchMove, this.pinchMoveThreshold = 40.0, this.scrollWheelVelocity = 0.005, this.cursorKeyboardRotationOptions = const CursorKeyboardRotationOptions(), @@ -80,7 +72,6 @@ final class InteractionOptions { enabledGestures == other.enabledGestures && rotationThreshold == other.rotationThreshold && pinchZoomThreshold == other.pinchZoomThreshold && - pinchZoomWinGestures == other.pinchZoomWinGestures && pinchMoveThreshold == other.pinchMoveThreshold && scrollWheelVelocity == other.scrollWheelVelocity); @@ -89,7 +80,6 @@ final class InteractionOptions { enabledGestures, rotationThreshold, pinchZoomThreshold, - pinchZoomWinGestures, pinchMoveThreshold, scrollWheelVelocity, ); From 0f10eb9c7614dd78f95062f8ac99f87106603259 Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Fri, 15 Dec 2023 20:03:28 +0100 Subject: [PATCH 046/102] remove `IsKeyCursorRotationTrigger` typdef --- lib/src/map/options/cursor_keyboard_rotation.dart | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/src/map/options/cursor_keyboard_rotation.dart b/lib/src/map/options/cursor_keyboard_rotation.dart index e20bcc01c..f3a621fd6 100644 --- a/lib/src/map/options/cursor_keyboard_rotation.dart +++ b/lib/src/map/options/cursor_keyboard_rotation.dart @@ -19,7 +19,7 @@ class CursorKeyboardRotationOptions { /// Fix to returning `false`, or use the /// [CursorKeyboardRotationOptions.disabled] constructor to disable /// cursor/keyboard rotation. - final IsKeyCursorRotationTrigger? isKeyTrigger; + final bool Function(LogicalKeyboardKey key)? isKeyTrigger; /// The behaviour of the cursor/keyboard rotation function in terms of the /// angle that the map is rotated to @@ -63,9 +63,6 @@ class CursorKeyboardRotationOptions { CursorKeyboardRotationOptions.disabled() : this(isKeyTrigger: (_) => false); } -/// See [CursorKeyboardRotationOptions.isKeyTrigger] -typedef IsKeyCursorRotationTrigger = bool Function(LogicalKeyboardKey key); - /// The behaviour of the cursor/keyboard rotation function in terms of the angle /// that the map is rotated to /// From cb45f99e5d1f4a1f06bc92552516b1fcfa53dd90 Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Fri, 15 Dec 2023 20:14:34 +0100 Subject: [PATCH 047/102] dart format --- lib/src/map/options/map_options.dart | 61 ++++++++++++++-------------- 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/lib/src/map/options/map_options.dart b/lib/src/map/options/map_options.dart index cd0c5713c..d9d938483 100644 --- a/lib/src/map/options/map_options.dart +++ b/lib/src/map/options/map_options.dart @@ -121,40 +121,39 @@ class MapOptions { /// context with no [FlutterMap] ancestor a [StateError] will be thrown. static MapOptions of(BuildContext context) => maybeOf(context) ?? - (throw StateError( - '`MapOptions.of()` should not be called outside a `FlutterMap` and its descendants')); + (throw StateError( + '`MapOptions.of()` should not be called outside a `FlutterMap` and its descendants')); @override bool operator ==(Object other) => other is MapOptions && - crs == other.crs && - initialCenter == other.initialCenter && - initialZoom == other.initialZoom && - initialRotation == other.initialRotation && - initialCameraFit == other.initialCameraFit && - minZoom == other.minZoom && - maxZoom == other.maxZoom && - backgroundColor == other.backgroundColor && - onTap == other.onTap && - onSecondaryTap == other.onSecondaryTap && - onLongPress == other.onLongPress && - onPointerDown == other.onPointerDown && - onPointerUp == other.onPointerUp && - onPointerCancel == other.onPointerCancel && - onPointerHover == other.onPointerHover && - onPositionChanged == other.onPositionChanged && - onMapEvent == other.onMapEvent && - cameraConstraint == other.cameraConstraint && - onMapReady == other.onMapReady && - keepAlive == other.keepAlive && - interactionOptions == other.interactionOptions && - backgroundColor == other.backgroundColor && - applyPointerTranslucencyToLayers == - other.applyPointerTranslucencyToLayers; + crs == other.crs && + initialCenter == other.initialCenter && + initialZoom == other.initialZoom && + initialRotation == other.initialRotation && + initialCameraFit == other.initialCameraFit && + minZoom == other.minZoom && + maxZoom == other.maxZoom && + backgroundColor == other.backgroundColor && + onTap == other.onTap && + onSecondaryTap == other.onSecondaryTap && + onLongPress == other.onLongPress && + onPointerDown == other.onPointerDown && + onPointerUp == other.onPointerUp && + onPointerCancel == other.onPointerCancel && + onPointerHover == other.onPointerHover && + onPositionChanged == other.onPositionChanged && + onMapEvent == other.onMapEvent && + cameraConstraint == other.cameraConstraint && + onMapReady == other.onMapReady && + keepAlive == other.keepAlive && + interactionOptions == other.interactionOptions && + backgroundColor == other.backgroundColor && + applyPointerTranslucencyToLayers == + other.applyPointerTranslucencyToLayers; @override - int get hashCode => - Object.hashAll([ + int get hashCode => Object.hashAll([ crs, initialCenter, initialZoom, @@ -183,6 +182,6 @@ class MapOptions { typedef TapCallback = void Function(TapDownDetails details, LatLng point); typedef LongPressCallback = void Function( - LongPressStartDetails details, - LatLng point, - ); + LongPressStartDetails details, + LatLng point, +); From e2a2e4024c85870771c33e227d4139834a2c6c57 Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Fri, 15 Dec 2023 21:25:02 +0100 Subject: [PATCH 048/102] remove `CursorKeyboardRotationOptions` --- example/lib/pages/gestures_page.dart | 28 +------ example/lib/widgets/drawer/menu_drawer.dart | 2 +- lib/flutter_map.dart | 1 - lib/src/map/gestures/gestures.dart | 18 ++--- .../map/gestures/map_interactive_viewer.dart | 11 ++- .../map/options/cursor_keyboard_rotation.dart | 80 ------------------- lib/src/map/options/interaction_options.dart | 22 ++--- test/full_coverage_test.dart | 1 - 8 files changed, 27 insertions(+), 136 deletions(-) delete mode 100644 lib/src/map/options/cursor_keyboard_rotation.dart diff --git a/example/lib/pages/gestures_page.dart b/example/lib/pages/gestures_page.dart index 24bb3d677..5557753f4 100644 --- a/example/lib/pages/gestures_page.dart +++ b/example/lib/pages/gestures_page.dart @@ -24,16 +24,15 @@ class _GesturesPageState extends State { InteractiveFlag.pinchZoom: 'Pinch', InteractiveFlag.scrollWheelZoom: 'Scroll', InteractiveFlag.doubleTapZoom: 'Double tap', - InteractiveFlag.doubleTapDragZoom: 'Double tap + drag', + InteractiveFlag.doubleTapDragZoom: 'Double tap+drag', }, 'Rotation': { InteractiveFlag.rotate: 'Twist', - InteractiveFlag.ctrlDragRotate: 'CTRL + Drag', + InteractiveFlag.ctrlDragRotate: 'CTRL+Drag', }, }; int flags = InteractiveFlag.drag | InteractiveFlag.pinchZoom; - bool keyboardCursorRotate = false; MapEvent? _latestEvent; @@ -48,7 +47,7 @@ class _GesturesPageState extends State { child: Column( children: [ Flex( - direction: screenWidth >= 600 ? Axis.horizontal : Axis.vertical, + direction: screenWidth >= 650 ? Axis.horizontal : Axis.vertical, mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: availableFlags.entries @@ -76,22 +75,10 @@ class _GesturesPageState extends State { setState(() => flags |= e.key); }, ), - Text(e.value), + Text(e.value, textAlign: TextAlign.center), ], ), ), - if (category.key == 'Rotation') ...[ - Column( - children: [ - Checkbox.adaptive( - value: keyboardCursorRotate, - onChanged: (enabled) => setState( - () => keyboardCursorRotate = enabled!), - ), - const Text('Cursor & CTRL'), - ], - ), - ] ].interleave(const SizedBox(width: 12)).toList() ..removeLast(), ) @@ -123,13 +110,6 @@ class _GesturesPageState extends State { initialZoom: 11, interactionOptions: InteractionOptions( enabledGestures: EnabledGestures.bitfield(flags), - cursorKeyboardRotationOptions: - CursorKeyboardRotationOptions( - isKeyTrigger: (key) => - keyboardCursorRotate && - CursorKeyboardRotationOptions.defaultTriggerKeys - .contains(key), - ), ), ), children: [openStreetMapTileLayer], diff --git a/example/lib/widgets/drawer/menu_drawer.dart b/example/lib/widgets/drawer/menu_drawer.dart index 404faee71..88b401340 100644 --- a/example/lib/widgets/drawer/menu_drawer.dart +++ b/example/lib/widgets/drawer/menu_drawer.dart @@ -107,7 +107,7 @@ class MenuDrawer extends StatelessWidget { currentRoute: currentRoute, ), MenuItemWidget( - caption: 'Interactive Flags', + caption: 'Map Gestures', routeName: GesturesPage.route, currentRoute: currentRoute, ), diff --git a/lib/flutter_map.dart b/lib/flutter_map.dart index c023de2f8..4a4c5bb5e 100644 --- a/lib/flutter_map.dart +++ b/lib/flutter_map.dart @@ -49,7 +49,6 @@ export 'package:flutter_map/src/map/controller/events/map_events.dart'; export 'package:flutter_map/src/map/controller/map_controller.dart'; export 'package:flutter_map/src/map/controller/map_controller_impl.dart'; export 'package:flutter_map/src/map/gestures/latlng_tween.dart'; -export 'package:flutter_map/src/map/options/cursor_keyboard_rotation.dart'; export 'package:flutter_map/src/map/options/enabled_gestures.dart'; export 'package:flutter_map/src/map/options/interaction_options.dart'; export 'package:flutter_map/src/map/options/map_options.dart'; diff --git a/lib/src/map/gestures/gestures.dart b/lib/src/map/gestures/gestures.dart index d1df346cb..94b062002 100644 --- a/lib/src/map/gestures/gestures.dart +++ b/lib/src/map/gestures/gestures.dart @@ -419,8 +419,12 @@ class DragGesture extends Gesture { class CtrlDragRotateGesture extends Gesture { bool isActive = false; + final List keys; - CtrlDragRotateGesture({required super.controller}); + CtrlDragRotateGesture({ + required super.controller, + required this.keys, + }); void start() { controller.stopAnimationRaw(); @@ -449,15 +453,9 @@ class CtrlDragRotateGesture extends Gesture { ); } - bool get ctrlPressed { - const keys = [ - LogicalKeyboardKey.controlLeft, - LogicalKeyboardKey.controlRight, - ]; - return RawKeyboard.instance.keysPressed - .where((key) => keys.contains(key)) - .isNotEmpty; - } + bool get keyPressed => RawKeyboard.instance.keysPressed + .where((key) => keys.contains(key)) + .isNotEmpty; } class DoubleTapDragZoomGesture extends Gesture { diff --git a/lib/src/map/gestures/map_interactive_viewer.dart b/lib/src/map/gestures/map_interactive_viewer.dart index 49fe48a85..fc1991a54 100644 --- a/lib/src/map/gestures/map_interactive_viewer.dart +++ b/lib/src/map/gestures/map_interactive_viewer.dart @@ -160,7 +160,7 @@ class MapInteractiveViewerState extends State // pan and scale, scale is a superset of the pan gesture onScaleStart: useScaleCallback ? (details) { - if (_ctrlDragRotate?.ctrlPressed ?? false) { + if (_ctrlDragRotate?.keyPressed ?? false) { _ctrlDragRotate!.start(); } else if (_doubleTapDragZoom?.isActive ?? false) { _doubleTapDragZoom!.start(details); @@ -173,7 +173,7 @@ class MapInteractiveViewerState extends State : null, onScaleUpdate: useScaleCallback ? (details) { - if (_ctrlDragRotate?.ctrlPressed ?? false) { + if (_ctrlDragRotate?.keyPressed ?? false) { _ctrlDragRotate!.update(details); } else if (_doubleTapDragZoom?.isActive ?? false) { _doubleTapDragZoom!.update(details); @@ -186,7 +186,7 @@ class MapInteractiveViewerState extends State : null, onScaleEnd: useScaleCallback ? (details) { - if (_ctrlDragRotate?.ctrlPressed ?? false) { + if (_ctrlDragRotate?.keyPressed ?? false) { _ctrlDragRotate!.end(); } else if (_doubleTapDragZoom?.isActive ?? false) { _doubleTapDragZoom!.isActive = false; @@ -238,7 +238,10 @@ class MapInteractiveViewerState extends State } if (newFlags.ctrlDragRotate) { - _ctrlDragRotate = CtrlDragRotateGesture(controller: widget.controller); + _ctrlDragRotate = CtrlDragRotateGesture( + controller: widget.controller, + keys: _options.interactionOptions.ctrlRotateKeys, + ); } else { _ctrlDragRotate = null; } diff --git a/lib/src/map/options/cursor_keyboard_rotation.dart b/lib/src/map/options/cursor_keyboard_rotation.dart deleted file mode 100644 index f3a621fd6..000000000 --- a/lib/src/map/options/cursor_keyboard_rotation.dart +++ /dev/null @@ -1,80 +0,0 @@ -import 'package:flutter/services.dart'; -import 'package:meta/meta.dart'; - -/// Options to configure cursor/keyboard rotation -/// -/// {@template cursorkeyboard_explanation} -/// Cursor/keyboard rotation is designed for desktop platforms, and allows the -/// cursor to be used to set the rotation of the map whilst a keyboard key is -/// held down (as triggered by [isKeyTrigger]). -/// {@endtemplate} -@immutable -class CursorKeyboardRotationOptions { - /// Whether to trigger cursor/keyboard rotation dependent on the currently - /// pressed [LogicalKeyboardKey] - /// - /// By default, rotation is triggered if any key in [defaultTriggerKeys] is - /// held (any of the "Control" keys). - /// - /// Fix to returning `false`, or use the - /// [CursorKeyboardRotationOptions.disabled] constructor to disable - /// cursor/keyboard rotation. - final bool Function(LogicalKeyboardKey key)? isKeyTrigger; - - /// The behaviour of the cursor/keyboard rotation function in terms of the - /// angle that the map is rotated to - /// - /// Does not disable cursor/keyboard rotation, or adjust its triggers: see - /// [isKeyTrigger]. - /// - /// Defaults to [CursorRotationBehaviour.offset]. - final CursorRotationBehaviour behaviour; - - /// Whether to set the North of the map to the clicked angle, when the user - /// clicks their mouse without dragging (a `onPointerDown` event - /// followed by `onPointerUp` without a change in rotation) - final bool setNorthOnClick; - - /// Default trigger keys used in the default [isKeyTrigger] - static final defaultTriggerKeys = { - LogicalKeyboardKey.control, - LogicalKeyboardKey.controlLeft, - LogicalKeyboardKey.controlRight, - }; - - /// Create options to configure cursor/keyboard rotation - /// - /// {@macro cursorkeyboard_explanation} - /// - /// To disable cursor/keyboard rotation, fix [isKeyTrigger] to return `false`, - /// or use the [CursorKeyboardRotationOptions.disabled] constructor instead. - /// - /// This constructor defaults to setting [isKeyTrigger] to triggering if any - /// key in [defaultTriggerKeys] is held (any of the "Control" keys). - const CursorKeyboardRotationOptions({ - this.isKeyTrigger, - this.behaviour = CursorRotationBehaviour.offset, - this.setNorthOnClick = true, - }); - - /// Create options to disable cursor/keyboard rotation - /// - /// {@macro cursorkeyboard_explanation} - CursorKeyboardRotationOptions.disabled() : this(isKeyTrigger: (_) => false); -} - -/// The behaviour of the cursor/keyboard rotation function in terms of the angle -/// that the map is rotated to -/// -/// Does not disable cursor/keyboard rotation, or adjust its triggers: see -/// [CursorKeyboardRotationOptions.isKeyTrigger]. -/// -/// Also see [CursorKeyboardRotationOptions.setNorthOnClick]. -enum CursorRotationBehaviour { - /// Set the North of the map to the angle at which the user drags their cursor - setNorth, - - /// Offset the current rotation of the map to the angle at which the user - /// drags their cursor - offset, -} diff --git a/lib/src/map/options/interaction_options.dart b/lib/src/map/options/interaction_options.dart index 25b24223e..0b354ea01 100644 --- a/lib/src/map/options/interaction_options.dart +++ b/lib/src/map/options/interaction_options.dart @@ -1,4 +1,4 @@ -import 'package:flutter_map/src/map/options/cursor_keyboard_rotation.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_map/src/map/options/enabled_gestures.dart'; import 'package:meta/meta.dart'; @@ -31,19 +31,7 @@ final class InteractionOptions { final double scrollWheelVelocity; - /// Options to configure cursor/keyboard rotation - /// - /// Cursor/keyboard rotation is designed for desktop platforms, and allows the - /// cursor to be used to set the rotation of the map whilst a keyboard key is - /// held down (as triggered by [CursorKeyboardRotationOptions.isKeyTrigger]). - /// - /// By default, rotation is triggered if any key in - /// [CursorKeyboardRotationOptions.defaultTriggerKeys] is held (any of the - /// "Control" keys). - /// - /// To disable cursor/keyboard rotation, use the - /// [CursorKeyboardRotationOptions.disabled] constructor. - final CursorKeyboardRotationOptions cursorKeyboardRotationOptions; + final List ctrlRotateKeys; const InteractionOptions({ this.enabledGestures = const EnabledGestures.all(), @@ -51,7 +39,11 @@ final class InteractionOptions { this.pinchZoomThreshold = 0.5, this.pinchMoveThreshold = 40.0, this.scrollWheelVelocity = 0.005, - this.cursorKeyboardRotationOptions = const CursorKeyboardRotationOptions(), + this.ctrlRotateKeys = const [ + LogicalKeyboardKey.control, + LogicalKeyboardKey.controlLeft, + LogicalKeyboardKey.controlRight, + ], }) : assert( rotationThreshold >= 0.0, 'rotationThreshold needs to be a positive value', diff --git a/test/full_coverage_test.dart b/test/full_coverage_test.dart index 991fbcf34..3109fad83 100644 --- a/test/full_coverage_test.dart +++ b/test/full_coverage_test.dart @@ -46,7 +46,6 @@ import 'package:flutter_map/src/map/gestures/gestures.dart'; import 'package:flutter_map/src/map/gestures/latlng_tween.dart'; import 'package:flutter_map/src/map/gestures/map_interactive_viewer.dart'; import 'package:flutter_map/src/map/inherited_model.dart'; -import 'package:flutter_map/src/map/options/cursor_keyboard_rotation.dart'; import 'package:flutter_map/src/map/options/enabled_gestures.dart'; import 'package:flutter_map/src/map/options/interaction_options.dart'; import 'package:flutter_map/src/map/options/map_options.dart'; From 9f1d4a13e89c4daaae81e4762635fe3f48d7da8c Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Fri, 15 Dec 2023 21:40:12 +0100 Subject: [PATCH 049/102] add docs, add ctrlRotateKeys to the hashKey and == function --- lib/src/map/options/interaction_options.dart | 24 ++++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/lib/src/map/options/interaction_options.dart b/lib/src/map/options/interaction_options.dart index 0b354ea01..8cc4b302c 100644 --- a/lib/src/map/options/interaction_options.dart +++ b/lib/src/map/options/interaction_options.dart @@ -4,7 +4,17 @@ import 'package:meta/meta.dart'; @immutable final class InteractionOptions { - /// See [EnabledGestures] for custom settings + /// Enable or disable specific gestures. By default all gestures are enabled. + /// If you want to disable all gestures or almost all gestures, use the + /// [EnabledGestures.none] constructor. + /// In case you want to disable only few gestures, use [EnabledGestures.all] + /// and you can use the [EnabledGestures.noRotation] for an easy way to + /// disable all rotation gestures. + /// + /// In addition you can specify your gestures via bitfield operations using + /// the [EnabledGestures.bitfield] constructor together with the static + /// fields in [InteractiveFlag]. + /// For more information see the documentation on [InteractiveFlag]. final EnabledGestures enabledGestures; /// Rotation threshold in degree default is 20.0 Map starts to rotate when @@ -12,14 +22,14 @@ final class InteractionOptions { /// which allows [MultiFingerGesture.rotate] Note: if [interactiveFlags] /// doesn't contain [InteractiveFlag.rotate] or [enableMultiFingerGestureRace] /// is false then rotate cannot win - final double rotationThreshold; + final double rotationThreshold; // TODO /// Pinch Zoom threshold default is 0.5 Map starts to zoom when /// [pinchZoomThreshold] has been achieved or another multi finger gesture /// wins which allows [MultiFingerGesture.pinchZoom] Note: if /// [interactiveFlags] doesn't contain [InteractiveFlag.pinchZoom] or /// [enableMultiFingerGestureRace] is false then zoom cannot win - final double pinchZoomThreshold; + final double pinchZoomThreshold; // TODO /// Pinch Move threshold default is 40.0 (note: this doesn't take any effect /// on drag) Map starts to move when [pinchMoveThreshold] has been achieved or @@ -27,10 +37,12 @@ final class InteractionOptions { /// [MultiFingerGesture.pinchMove] Note: if [interactiveFlags] doesn't contain /// [InteractiveFlag.pinchMove] or [enableMultiFingerGestureRace] is false /// then pinch move cannot win - final double pinchMoveThreshold; + final double pinchMoveThreshold; // TODO - final double scrollWheelVelocity; + final double scrollWheelVelocity; // TODO + /// Override this option if you want to use custom keys for the CTRL+drag + /// rotate gesture. By default the left and right control key are used. final List ctrlRotateKeys; const InteractionOptions({ @@ -65,6 +77,7 @@ final class InteractionOptions { rotationThreshold == other.rotationThreshold && pinchZoomThreshold == other.pinchZoomThreshold && pinchMoveThreshold == other.pinchMoveThreshold && + ctrlRotateKeys == other.ctrlRotateKeys && scrollWheelVelocity == other.scrollWheelVelocity); @override @@ -73,6 +86,7 @@ final class InteractionOptions { rotationThreshold, pinchZoomThreshold, pinchMoveThreshold, + ctrlRotateKeys, scrollWheelVelocity, ); } From b0c0b5fbe19a0896f47a71a818ca3c874b6e96ce Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Fri, 15 Dec 2023 21:56:41 +0100 Subject: [PATCH 050/102] update docs --- lib/src/map/gestures/gestures.dart | 6 ++-- lib/src/map/options/interaction_options.dart | 31 ++++++++++---------- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/lib/src/map/gestures/gestures.dart b/lib/src/map/gestures/gestures.dart index 94b062002..81821e508 100644 --- a/lib/src/map/gestures/gestures.dart +++ b/lib/src/map/gestures/gestures.dart @@ -220,9 +220,8 @@ class ScrollWheelZoomGesture extends Gesture { details as PointerScrollEvent; final minZoom = _options.minZoom ?? 0.0; final maxZoom = _options.maxZoom ?? double.infinity; - final velocity = _options.interactionOptions.scrollWheelVelocity; final newZoom = clampDouble( - _camera.zoom - details.scrollDelta.dy * velocity * 2, + _camera.zoom - details.scrollDelta.dy * _scrollWheelVelocity * 2, minZoom, maxZoom, ); @@ -239,6 +238,9 @@ class ScrollWheelZoomGesture extends Gesture { ); }); } + + double get _scrollWheelVelocity => + _options.interactionOptions.scrollWheelVelocity; } /// A gesture with multiple inputs like zooming with two fingers diff --git a/lib/src/map/options/interaction_options.dart b/lib/src/map/options/interaction_options.dart index 8cc4b302c..9e410c520 100644 --- a/lib/src/map/options/interaction_options.dart +++ b/lib/src/map/options/interaction_options.dart @@ -2,6 +2,8 @@ import 'package:flutter/services.dart'; import 'package:flutter_map/src/map/options/enabled_gestures.dart'; import 'package:meta/meta.dart'; +/// Set interation options for input gestures. +/// Most commonly used is [InteractionOptions.enabledGestures]. @immutable final class InteractionOptions { /// Enable or disable specific gestures. By default all gestures are enabled. @@ -17,29 +19,28 @@ final class InteractionOptions { /// For more information see the documentation on [InteractiveFlag]. final EnabledGestures enabledGestures; - /// Rotation threshold in degree default is 20.0 Map starts to rotate when - /// [rotationThreshold] has been achieved or another multi finger gesture wins - /// which allows [MultiFingerGesture.rotate] Note: if [interactiveFlags] - /// doesn't contain [InteractiveFlag.rotate] or [enableMultiFingerGestureRace] - /// is false then rotate cannot win + /// Rotation threshold in degree. Map starts to rotate when + /// [rotationThreshold] has been achieved or another multi finger + /// gesture wins. + /// Default is 20.0 final double rotationThreshold; // TODO - /// Pinch Zoom threshold default is 0.5 Map starts to zoom when + /// Pinch Zoom threshold. Map starts to zoom when /// [pinchZoomThreshold] has been achieved or another multi finger gesture - /// wins which allows [MultiFingerGesture.pinchZoom] Note: if - /// [interactiveFlags] doesn't contain [InteractiveFlag.pinchZoom] or - /// [enableMultiFingerGestureRace] is false then zoom cannot win + /// wins. + /// Default is 0.5 final double pinchZoomThreshold; // TODO - /// Pinch Move threshold default is 40.0 (note: this doesn't take any effect + /// Pinch Move threshold (note: this doesn't take any effect /// on drag) Map starts to move when [pinchMoveThreshold] has been achieved or - /// another multi finger gesture wins which allows - /// [MultiFingerGesture.pinchMove] Note: if [interactiveFlags] doesn't contain - /// [InteractiveFlag.pinchMove] or [enableMultiFingerGestureRace] is false - /// then pinch move cannot win + /// another multi finger gesture wins. + /// Default is 40.0 final double pinchMoveThreshold; // TODO - final double scrollWheelVelocity; // TODO + /// The velocity how fast the map should zoom when using the scroll wheel + /// of the mouse. + /// Defaults to 0.005. + final double scrollWheelVelocity; /// Override this option if you want to use custom keys for the CTRL+drag /// rotate gesture. By default the left and right control key are used. From dcc07b8d8ccbe35d1a4253aa6ca8a255c35c8d0a Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Fri, 15 Dec 2023 22:08:36 +0100 Subject: [PATCH 051/102] rename `InteractiveFlag` fields --- example/lib/pages/gestures_page.dart | 10 ++--- lib/src/map/options/enabled_gestures.dart | 51 ++++++++++++++--------- 2 files changed, 36 insertions(+), 25 deletions(-) diff --git a/example/lib/pages/gestures_page.dart b/example/lib/pages/gestures_page.dart index 5557753f4..7fc34bd1c 100644 --- a/example/lib/pages/gestures_page.dart +++ b/example/lib/pages/gestures_page.dart @@ -18,21 +18,21 @@ class _GesturesPageState extends State { 'Movement': { InteractiveFlag.drag: 'Drag', InteractiveFlag.flingAnimation: 'Fling', - InteractiveFlag.pinchMove: 'Pinch', + InteractiveFlag.twoFingerMove: 'Pinch', }, 'Zooming': { - InteractiveFlag.pinchZoom: 'Pinch', + InteractiveFlag.twoFingerZoom: 'Pinch', InteractiveFlag.scrollWheelZoom: 'Scroll', - InteractiveFlag.doubleTapZoom: 'Double tap', + InteractiveFlag.doubleTapZoomIn: 'Double tap', InteractiveFlag.doubleTapDragZoom: 'Double tap+drag', }, 'Rotation': { - InteractiveFlag.rotate: 'Twist', + InteractiveFlag.twoFingerRotate: 'Twist', InteractiveFlag.ctrlDragRotate: 'CTRL+Drag', }, }; - int flags = InteractiveFlag.drag | InteractiveFlag.pinchZoom; + int flags = InteractiveFlag.drag | InteractiveFlag.twoFingerZoom; MapEvent? _latestEvent; diff --git a/lib/src/map/options/enabled_gestures.dart b/lib/src/map/options/enabled_gestures.dart index 57f2e597e..cf3e189c6 100644 --- a/lib/src/map/options/enabled_gestures.dart +++ b/lib/src/map/options/enabled_gestures.dart @@ -67,15 +67,15 @@ class EnabledGestures { drag: InteractiveFlag.hasFlag(flags, InteractiveFlag.drag), flingAnimation: InteractiveFlag.hasFlag(flags, InteractiveFlag.flingAnimation), - pinchMove: InteractiveFlag.hasFlag(flags, InteractiveFlag.pinchMove), - pinchZoom: InteractiveFlag.hasFlag(flags, InteractiveFlag.pinchZoom), + pinchMove: InteractiveFlag.hasFlag(flags, InteractiveFlag.twoFingerMove), + pinchZoom: InteractiveFlag.hasFlag(flags, InteractiveFlag.twoFingerZoom), doubleTapZoom: - InteractiveFlag.hasFlag(flags, InteractiveFlag.doubleTapZoom), + InteractiveFlag.hasFlag(flags, InteractiveFlag.doubleTapZoomIn), doubleTapDragZoom: InteractiveFlag.hasFlag(flags, InteractiveFlag.doubleTapDragZoom), scrollWheelZoom: InteractiveFlag.hasFlag(flags, InteractiveFlag.scrollWheelZoom), - rotate: InteractiveFlag.hasFlag(flags, InteractiveFlag.rotate), + rotate: InteractiveFlag.hasFlag(flags, InteractiveFlag.twoFingerRotate), ctrlDragRotate: InteractiveFlag.hasFlag(flags, InteractiveFlag.ctrlDragRotate), ); @@ -177,24 +177,25 @@ class EnabledGestures { /// /// If you want mix interactions for example drag and rotate interactions then /// you have two options: -/// a. Add your own flags: [InteractiveFlag.drag] | [InteractiveFlag.rotate] +/// a. Add your own flags: +/// [InteractiveFlag.drag] | [InteractiveFlag.twoFingerRotate] /// b. Remove unnecessary flags from all: /// [InteractiveFlag.all] & /// ~[InteractiveFlag.flingAnimation] & -/// ~[InteractiveFlag.pinchMove] & -/// ~[InteractiveFlag.pinchZoom] & -/// ~[InteractiveFlag.doubleTapZoom] +/// ~[InteractiveFlag.twoFingerMove] & +/// ~[InteractiveFlag.twoFingerZoom] & +/// ~[InteractiveFlag.doubleTapZoomIn] abstract class InteractiveFlag { const InteractiveFlag._(); static const int all = drag | flingAnimation | - pinchMove | - pinchZoom | - doubleTapZoom | + twoFingerMove | + twoFingerZoom | + doubleTapZoomIn | doubleTapDragZoom | scrollWheelZoom | - rotate | + twoFingerRotate | ctrlDragRotate; static const int none = 0; @@ -206,13 +207,19 @@ abstract class InteractiveFlag { static const int flingAnimation = 1 << 1; /// Enable panning with multiple fingers - static const int pinchMove = 1 << 2; + static const int twoFingerMove = 1 << 2; + @Deprecated('Renamed to twoFingerMove') + static const int pinchMove = twoFingerMove; /// Enable zooming with a multi-finger pinch gesture - static const int pinchZoom = 1 << 3; + static const int twoFingerZoom = 1 << 3; + @Deprecated('Renamed to twoFingerZoom') + static const int pinchZoom = twoFingerZoom; /// Enable zooming with a single-finger double tap gesture - static const int doubleTapZoom = 1 << 4; + static const int doubleTapZoomIn = 1 << 4; + @Deprecated('Renamed to doubleTapZoomIn') + static const int doubleTapZoom = doubleTapZoomIn; /// Enable zooming with a single-finger double-tap-drag gesture /// @@ -226,17 +233,21 @@ abstract class InteractiveFlag { /// /// For controlling cursor/keyboard rotation, see /// [InteractionOptions.cursorKeyboardRotationOptions]. - static const int rotate = 1 << 7; + static const int twoFingerRotate = 1 << 7; + @Deprecated('Renamed to twoFingerRotate') + static const int rotate = twoFingerRotate; /// Enable rotation by pressing the CTRL Key and drag the map with the cursor. /// To change the key see [InteractionOptions.cursorKeyboardRotationOptions]. static const int ctrlDragRotate = 1 << 8; /// Returns `true` if [leftFlags] has at least one member in [rightFlags] - /// (intersection) for example [leftFlags]= [InteractiveFlag.drag] | - /// [InteractiveFlag.rotate] and [rightFlags]= [InteractiveFlag.rotate] | - /// [InteractiveFlag.flingAnimation] returns true because both have - /// [InteractiveFlag.rotate] flag + /// (intersection) for example + /// [leftFlags] = [InteractiveFlag.drag] | [InteractiveFlag.twoFingerRotate] + /// and + /// [rightFlags] = [InteractiveFlag.twoFingerRotate] + /// | [InteractiveFlag.flingAnimation] + /// returns true because both have the [InteractiveFlag.twoFingerRotate] flag. static bool hasFlag(int leftFlags, int rightFlags) { return leftFlags & rightFlags != 0; } From b93e02ad14f3a10b572826a059a53aafb67547f5 Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Fri, 15 Dec 2023 22:42:00 +0100 Subject: [PATCH 052/102] clean up --- example/lib/pages/latlng_to_screen_point.dart | 2 +- example/lib/pages/many_circles.dart | 2 +- example/lib/pages/many_markers.dart | 2 +- lib/src/map/gestures/gestures.dart | 51 ++++++----- .../map/gestures/map_interactive_viewer.dart | 12 +-- lib/src/map/options/enabled_gestures.dart | 88 +++++++++---------- lib/src/map/options/interaction_options.dart | 47 +++++----- test/flutter_map_test.dart | 2 +- 8 files changed, 101 insertions(+), 105 deletions(-) diff --git a/example/lib/pages/latlng_to_screen_point.dart b/example/lib/pages/latlng_to_screen_point.dart index 2922083bf..a6514d38a 100644 --- a/example/lib/pages/latlng_to_screen_point.dart +++ b/example/lib/pages/latlng_to_screen_point.dart @@ -48,7 +48,7 @@ class _LatLngToScreenPointPageState extends State { initialCenter: const LatLng(51.5, -0.09), initialZoom: 11, interactionOptions: const InteractionOptions( - enabledGestures: EnabledGestures.all(doubleTapZoom: false), + enabledGestures: EnabledGestures.all(doubleTapZoomIn: false), ), onTap: (_, latLng) { final point = mapController.camera diff --git a/example/lib/pages/many_circles.dart b/example/lib/pages/many_circles.dart index 70d329c1c..6b382a158 100644 --- a/example/lib/pages/many_circles.dart +++ b/example/lib/pages/many_circles.dart @@ -74,7 +74,7 @@ class ManyCirclesPageState extends State { initialCenter: LatLng(50, 20), initialZoom: 5, interactionOptions: InteractionOptions( - enabledGestures: EnabledGestures.all(rotate: false), + enabledGestures: EnabledGestures.all(twoFingerRotate: false), ), ), children: [ diff --git a/example/lib/pages/many_markers.dart b/example/lib/pages/many_markers.dart index c8ab91046..b2996aa76 100644 --- a/example/lib/pages/many_markers.dart +++ b/example/lib/pages/many_markers.dart @@ -72,7 +72,7 @@ class ManyMarkersPageState extends State { initialCenter: LatLng(50, 20), initialZoom: 5, interactionOptions: InteractionOptions( - enabledGestures: EnabledGestures.all(rotate: false), + enabledGestures: EnabledGestures.all(twoFingerRotate: false), ), ), children: [ diff --git a/lib/src/map/gestures/gestures.dart b/lib/src/map/gestures/gestures.dart index 81821e508..87a90bc62 100644 --- a/lib/src/map/gestures/gestures.dart +++ b/lib/src/map/gestures/gestures.dart @@ -5,6 +5,7 @@ import 'package:flutter/animation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/services.dart'; import 'package:flutter_map/flutter_map.dart'; +import 'package:latlong2/latlong.dart'; abstract class Gesture { final MapControllerImpl controller; @@ -245,19 +246,20 @@ class ScrollWheelZoomGesture extends Gesture { /// A gesture with multiple inputs like zooming with two fingers class TwoFingerGestures extends Gesture { - final bool moveEnabled; - final bool rotateEnabled; - final bool zoomEnabled; Offset? _lastLocalFocal; double? _lastScale; double? _lastRotation; - TwoFingerGestures({ - required EnabledGestures interactiveFlags, - required super.controller, - }) : moveEnabled = interactiveFlags.pinchMove, - zoomEnabled = interactiveFlags.pinchZoom, - rotateEnabled = interactiveFlags.rotate; + bool get _moveEnabled => + _options.interactionOptions.enabledGestures.twoFingerMove; + + bool get _rotateEnabled => + _options.interactionOptions.enabledGestures.twoFingerRotate; + + bool get _zoomEnabled => + _options.interactionOptions.enabledGestures.twoFingerZoom; + + TwoFingerGestures({required super.controller}); /// Initialize gesture, called when gesture has started void start(ScaleStartDetails details) { @@ -284,22 +286,25 @@ class TwoFingerGestures extends Gesture { } double newRotation = _camera.rotation; - if (rotateEnabled) { + if (_rotateEnabled) { newRotation -= (_lastRotation! - details.rotation) * 80; } double newZoom = _camera.zoom; - if (zoomEnabled) { + if (_zoomEnabled) { newZoom -= (_lastScale! - details.scale) * 2.2; } - final offset = _rotateOffset( - _camera, - _lastLocalFocal! - details.localFocalPoint, - ); - final oldCenterPt = _camera.project(_camera.center); - final newCenterPt = oldCenterPt + offset.toPoint(); - final newCenter = _camera.unproject(newCenterPt); + LatLng newCenter = _camera.center; + if (_moveEnabled) { + final offset = _rotateOffset( + _camera, + _lastLocalFocal! - details.localFocalPoint, + ); + final oldCenterPt = _camera.project(_camera.center); + final newCenterPt = oldCenterPt + offset.toPoint(); + newCenter = _camera.unproject(newCenterPt); + } controller.moveAndRotateRaw( newCenter, @@ -330,15 +335,13 @@ class TwoFingerGestures extends Gesture { } class DragGesture extends Gesture { - final bool flingEnabled; + bool get _flingEnabled => + _options.interactionOptions.enabledGestures.flingAnimation; Offset? _lastLocalFocal; Offset? _focalStartLocal; - DragGesture({ - required super.controller, - required EnabledGestures interactiveFlags, - }) : flingEnabled = interactiveFlags.flingAnimation; + DragGesture({required super.controller}); bool get isActive => _lastLocalFocal != null; @@ -387,7 +390,7 @@ class DragGesture extends Gesture { _lastLocalFocal = null; _focalStartLocal = null; - if (!flingEnabled) return; + if (!_flingEnabled) return; final magnitude = details.velocity.pixelsPerSecond.distance; diff --git a/lib/src/map/gestures/map_interactive_viewer.dart b/lib/src/map/gestures/map_interactive_viewer.dart index fc1991a54..f1fdfd2ca 100644 --- a/lib/src/map/gestures/map_interactive_viewer.dart +++ b/lib/src/map/gestures/map_interactive_viewer.dart @@ -208,24 +208,18 @@ class MapInteractiveViewerState extends State void updateGestures(EnabledGestures? oldFlags, EnabledGestures newFlags) { if (oldFlags == newFlags) return; if (newFlags.hasMultiFinger()) { - _twoFingerInput = TwoFingerGestures( - controller: widget.controller, - interactiveFlags: newFlags, - ); + _twoFingerInput = TwoFingerGestures(controller: widget.controller); } else { _twoFingerInput = null; } if (newFlags.drag) { - _drag = DragGesture( - controller: widget.controller, - interactiveFlags: newFlags, - ); + _drag = DragGesture(controller: widget.controller); } else { _drag = null; } - if (newFlags.doubleTapZoom) { + if (newFlags.doubleTapZoomIn) { _doubleTap = DoubleTapGesture(controller: widget.controller); } else { _doubleTap = null; diff --git a/lib/src/map/options/enabled_gestures.dart b/lib/src/map/options/enabled_gestures.dart index cf3e189c6..c3af8bc00 100644 --- a/lib/src/map/options/enabled_gestures.dart +++ b/lib/src/map/options/enabled_gestures.dart @@ -10,19 +10,19 @@ class EnabledGestures { const EnabledGestures._({ required this.drag, required this.flingAnimation, - required this.pinchMove, - required this.pinchZoom, - required this.doubleTapZoom, + required this.twoFingerMove, + required this.twoFingerZoom, + required this.doubleTapZoomIn, required this.doubleTapDragZoom, required this.scrollWheelZoom, - required this.rotate, + required this.twoFingerRotate, required this.ctrlDragRotate, }); /// Shortcut constructor to allow all gestures that don't rotate the map. const EnabledGestures.noRotation() : this.all( - rotate: false, + twoFingerRotate: false, ctrlDragRotate: false, ); @@ -34,12 +34,12 @@ class EnabledGestures { const EnabledGestures.all({ this.drag = true, this.flingAnimation = true, - this.pinchMove = true, - this.pinchZoom = true, - this.doubleTapZoom = true, + this.twoFingerMove = true, + this.twoFingerZoom = true, + this.doubleTapZoomIn = true, this.doubleTapDragZoom = true, this.scrollWheelZoom = true, - this.rotate = true, + this.twoFingerRotate = true, this.ctrlDragRotate = true, }); @@ -51,12 +51,12 @@ class EnabledGestures { const EnabledGestures.none({ this.drag = false, this.flingAnimation = false, - this.pinchMove = false, - this.pinchZoom = false, - this.doubleTapZoom = false, + this.twoFingerMove = false, + this.twoFingerZoom = false, + this.doubleTapZoomIn = false, this.doubleTapDragZoom = false, this.scrollWheelZoom = false, - this.rotate = false, + this.twoFingerRotate = false, this.ctrlDragRotate = false, }); @@ -67,15 +67,18 @@ class EnabledGestures { drag: InteractiveFlag.hasFlag(flags, InteractiveFlag.drag), flingAnimation: InteractiveFlag.hasFlag(flags, InteractiveFlag.flingAnimation), - pinchMove: InteractiveFlag.hasFlag(flags, InteractiveFlag.twoFingerMove), - pinchZoom: InteractiveFlag.hasFlag(flags, InteractiveFlag.twoFingerZoom), - doubleTapZoom: + twoFingerMove: + InteractiveFlag.hasFlag(flags, InteractiveFlag.twoFingerMove), + twoFingerZoom: + InteractiveFlag.hasFlag(flags, InteractiveFlag.twoFingerZoom), + doubleTapZoomIn: InteractiveFlag.hasFlag(flags, InteractiveFlag.doubleTapZoomIn), doubleTapDragZoom: InteractiveFlag.hasFlag(flags, InteractiveFlag.doubleTapDragZoom), scrollWheelZoom: InteractiveFlag.hasFlag(flags, InteractiveFlag.scrollWheelZoom), - rotate: InteractiveFlag.hasFlag(flags, InteractiveFlag.twoFingerRotate), + twoFingerRotate: + InteractiveFlag.hasFlag(flags, InteractiveFlag.twoFingerRotate), ctrlDragRotate: InteractiveFlag.hasFlag(flags, InteractiveFlag.ctrlDragRotate), ); @@ -88,13 +91,16 @@ class EnabledGestures { final bool flingAnimation; /// Enable panning with multiple fingers - final bool pinchMove; + final bool twoFingerMove; /// Enable zooming with a multi-finger pinch gesture - final bool pinchZoom; + final bool twoFingerZoom; + + /// Enable rotation with two-finger twist gesture + final bool twoFingerRotate; /// Enable zooming with a single-finger double tap gesture - final bool doubleTapZoom; + final bool doubleTapZoomIn; /// Enable zooming with a single-finger double-tap-drag gesture /// @@ -104,42 +110,36 @@ class EnabledGestures { /// Enable zooming with a mouse scroll wheel final bool scrollWheelZoom; - /// Enable rotation with two-finger twist gesture - /// - /// For controlling cursor/keyboard rotation, see - /// [InteractionOptions.cursorKeyboardRotationOptions]. - final bool rotate; - /// Enable rotation by pressing the CTRL key and dragging with the cursor /// or finger. final bool ctrlDragRotate; /// Returns true of any gesture with more than one finger is enabled. - bool hasMultiFinger() => pinchMove || pinchZoom || rotate; + bool hasMultiFinger() => twoFingerMove || twoFingerZoom || twoFingerRotate; /// Wither to change the value of some gestures. Returns a new /// [EnabledGestures] object. EnabledGestures withFlag({ - bool? pinchZoom, bool? drag, bool? flingAnimation, - bool? pinchMove, - bool? doubleTapZoom, + bool? twoFingerZoom, + bool? twoFingerMove, + bool? doubleTapZoomIn, bool? doubleTapDragZoom, bool? scrollWheelZoom, - bool? rotate, + bool? twoFingerRotate, bool? ctrlDragRotate, }) => EnabledGestures._( - pinchZoom: pinchZoom ?? this.pinchZoom, drag: drag ?? this.drag, flingAnimation: flingAnimation ?? this.flingAnimation, - pinchMove: pinchMove ?? this.pinchMove, - doubleTapZoom: doubleTapZoom ?? this.doubleTapZoom, + twoFingerZoom: twoFingerZoom ?? this.twoFingerZoom, + twoFingerMove: twoFingerMove ?? this.twoFingerMove, + doubleTapZoomIn: doubleTapZoomIn ?? this.doubleTapZoomIn, doubleTapDragZoom: doubleTapDragZoom ?? this.doubleTapDragZoom, scrollWheelZoom: scrollWheelZoom ?? this.scrollWheelZoom, - rotate: rotate ?? this.rotate, - ctrlDragRotate: rotate ?? this.ctrlDragRotate, + twoFingerRotate: twoFingerRotate ?? this.twoFingerRotate, + ctrlDragRotate: ctrlDragRotate ?? this.ctrlDragRotate, ); @override @@ -149,24 +149,24 @@ class EnabledGestures { runtimeType == other.runtimeType && drag == other.drag && flingAnimation == other.flingAnimation && - pinchMove == other.pinchMove && - pinchZoom == other.pinchZoom && - doubleTapZoom == other.doubleTapZoom && + twoFingerMove == other.twoFingerMove && + twoFingerZoom == other.twoFingerZoom && + doubleTapZoomIn == other.doubleTapZoomIn && doubleTapDragZoom == other.doubleTapDragZoom && scrollWheelZoom == other.scrollWheelZoom && - rotate == other.rotate && + twoFingerRotate == other.twoFingerRotate && ctrlDragRotate == other.ctrlDragRotate; @override int get hashCode => Object.hash( drag, flingAnimation, - pinchMove, - pinchZoom, - doubleTapZoom, + twoFingerMove, + twoFingerZoom, + doubleTapZoomIn, doubleTapDragZoom, scrollWheelZoom, - rotate, + twoFingerRotate, ctrlDragRotate, ); } diff --git a/lib/src/map/options/interaction_options.dart b/lib/src/map/options/interaction_options.dart index 9e410c520..dc2f72e0d 100644 --- a/lib/src/map/options/interaction_options.dart +++ b/lib/src/map/options/interaction_options.dart @@ -19,23 +19,21 @@ final class InteractionOptions { /// For more information see the documentation on [InteractiveFlag]. final EnabledGestures enabledGestures; - /// Rotation threshold in degree. Map starts to rotate when - /// [rotationThreshold] has been achieved or another multi finger - /// gesture wins. + /// Map starts to rotate when [twoFingerRotationThreshold] has been achieved + /// or another multi finger gesture wins. /// Default is 20.0 - final double rotationThreshold; // TODO + final double twoFingerRotationThreshold; // TODO - /// Pinch Zoom threshold. Map starts to zoom when - /// [pinchZoomThreshold] has been achieved or another multi finger gesture - /// wins. + /// Map starts to zoom when [twoFingerZoomThreshold] has been achieved or + /// another multi finger gesture wins. /// Default is 0.5 - final double pinchZoomThreshold; // TODO + final double twoFingerZoomThreshold; // TODO - /// Pinch Move threshold (note: this doesn't take any effect - /// on drag) Map starts to move when [pinchMoveThreshold] has been achieved or + /// Map starts to move when [twoFingerMoveThreshold] has been achieved or /// another multi finger gesture wins. + /// Note: this doesn't take any effect on drag. /// Default is 40.0 - final double pinchMoveThreshold; // TODO + final double twoFingerMoveThreshold; // TODO /// The velocity how fast the map should zoom when using the scroll wheel /// of the mouse. @@ -43,14 +41,15 @@ final class InteractionOptions { final double scrollWheelVelocity; /// Override this option if you want to use custom keys for the CTRL+drag - /// rotate gesture. By default the left and right control key are used. + /// rotate gesture. + /// By default the left and right control key are both used. final List ctrlRotateKeys; const InteractionOptions({ this.enabledGestures = const EnabledGestures.all(), - this.rotationThreshold = 20.0, - this.pinchZoomThreshold = 0.5, - this.pinchMoveThreshold = 40.0, + this.twoFingerRotationThreshold = 20.0, + this.twoFingerZoomThreshold = 0.5, + this.twoFingerMoveThreshold = 40.0, this.scrollWheelVelocity = 0.005, this.ctrlRotateKeys = const [ LogicalKeyboardKey.control, @@ -58,15 +57,15 @@ final class InteractionOptions { LogicalKeyboardKey.controlRight, ], }) : assert( - rotationThreshold >= 0.0, + twoFingerRotationThreshold >= 0.0, 'rotationThreshold needs to be a positive value', ), assert( - pinchZoomThreshold >= 0.0, + twoFingerZoomThreshold >= 0.0, 'pinchZoomThreshold needs to be a positive value', ), assert( - pinchMoveThreshold >= 0.0, + twoFingerMoveThreshold >= 0.0, 'pinchMoveThreshold needs to be a positive value', ); @@ -75,18 +74,18 @@ final class InteractionOptions { identical(this, other) || (other is InteractionOptions && enabledGestures == other.enabledGestures && - rotationThreshold == other.rotationThreshold && - pinchZoomThreshold == other.pinchZoomThreshold && - pinchMoveThreshold == other.pinchMoveThreshold && + twoFingerRotationThreshold == other.twoFingerRotationThreshold && + twoFingerZoomThreshold == other.twoFingerZoomThreshold && + twoFingerMoveThreshold == other.twoFingerMoveThreshold && ctrlRotateKeys == other.ctrlRotateKeys && scrollWheelVelocity == other.scrollWheelVelocity); @override int get hashCode => Object.hash( enabledGestures, - rotationThreshold, - pinchZoomThreshold, - pinchMoveThreshold, + twoFingerRotationThreshold, + twoFingerZoomThreshold, + twoFingerMoveThreshold, ctrlRotateKeys, scrollWheelVelocity, ); diff --git a/test/flutter_map_test.dart b/test/flutter_map_test.dart index c7ce320dd..9d4ebbca6 100644 --- a/test/flutter_map_test.dart +++ b/test/flutter_map_test.dart @@ -218,7 +218,7 @@ class _TestRebuildsAppState extends State { /// double tap gestures delay the tap gestures, disable them here EnabledGestures _interactiveFlags = const EnabledGestures.all( - doubleTapZoom: false, + doubleTapZoomIn: false, doubleTapDragZoom: false, ); From 134efa131ad231bb5412e70f4c0a5a07f288eeff Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Fri, 15 Dec 2023 22:43:27 +0100 Subject: [PATCH 053/102] consistent naming --- lib/src/map/options/interaction_options.dart | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/src/map/options/interaction_options.dart b/lib/src/map/options/interaction_options.dart index dc2f72e0d..a8c85e1d7 100644 --- a/lib/src/map/options/interaction_options.dart +++ b/lib/src/map/options/interaction_options.dart @@ -19,10 +19,10 @@ final class InteractionOptions { /// For more information see the documentation on [InteractiveFlag]. final EnabledGestures enabledGestures; - /// Map starts to rotate when [twoFingerRotationThreshold] has been achieved + /// Map starts to rotate when [twoFingerRotateThreshold] has been achieved /// or another multi finger gesture wins. /// Default is 20.0 - final double twoFingerRotationThreshold; // TODO + final double twoFingerRotateThreshold; // TODO /// Map starts to zoom when [twoFingerZoomThreshold] has been achieved or /// another multi finger gesture wins. @@ -47,7 +47,7 @@ final class InteractionOptions { const InteractionOptions({ this.enabledGestures = const EnabledGestures.all(), - this.twoFingerRotationThreshold = 20.0, + this.twoFingerRotateThreshold = 20.0, this.twoFingerZoomThreshold = 0.5, this.twoFingerMoveThreshold = 40.0, this.scrollWheelVelocity = 0.005, @@ -57,7 +57,7 @@ final class InteractionOptions { LogicalKeyboardKey.controlRight, ], }) : assert( - twoFingerRotationThreshold >= 0.0, + twoFingerRotateThreshold >= 0.0, 'rotationThreshold needs to be a positive value', ), assert( @@ -74,7 +74,7 @@ final class InteractionOptions { identical(this, other) || (other is InteractionOptions && enabledGestures == other.enabledGestures && - twoFingerRotationThreshold == other.twoFingerRotationThreshold && + twoFingerRotateThreshold == other.twoFingerRotateThreshold && twoFingerZoomThreshold == other.twoFingerZoomThreshold && twoFingerMoveThreshold == other.twoFingerMoveThreshold && ctrlRotateKeys == other.ctrlRotateKeys && @@ -83,7 +83,7 @@ final class InteractionOptions { @override int get hashCode => Object.hash( enabledGestures, - twoFingerRotationThreshold, + twoFingerRotateThreshold, twoFingerZoomThreshold, twoFingerMoveThreshold, ctrlRotateKeys, From ee7bf4a20f7cd2cafc8746e0555dbd5451171c6e Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Fri, 15 Dec 2023 22:44:33 +0100 Subject: [PATCH 054/102] refactor --- lib/src/map/gestures/gestures.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/src/map/gestures/gestures.dart b/lib/src/map/gestures/gestures.dart index 87a90bc62..686ac8947 100644 --- a/lib/src/map/gestures/gestures.dart +++ b/lib/src/map/gestures/gestures.dart @@ -10,7 +10,7 @@ import 'package:latlong2/latlong.dart'; abstract class Gesture { final MapControllerImpl controller; - Gesture({required this.controller}); + const Gesture({required this.controller}); MapCamera get _camera => controller.camera; @@ -335,12 +335,12 @@ class TwoFingerGestures extends Gesture { } class DragGesture extends Gesture { - bool get _flingEnabled => - _options.interactionOptions.enabledGestures.flingAnimation; - Offset? _lastLocalFocal; Offset? _focalStartLocal; + bool get _flingEnabled => + _options.interactionOptions.enabledGestures.flingAnimation; + DragGesture({required super.controller}); bool get isActive => _lastLocalFocal != null; From 950fdd1a2ef455d9fadb198a69855fc2a0a16710 Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Fri, 15 Dec 2023 23:05:53 +0100 Subject: [PATCH 055/102] use threshhold options for two finger gestures --- lib/src/map/gestures/gestures.dart | 48 ++++++++++++++++++-- lib/src/map/options/interaction_options.dart | 6 +-- 2 files changed, 46 insertions(+), 8 deletions(-) diff --git a/lib/src/map/gestures/gestures.dart b/lib/src/map/gestures/gestures.dart index 686ac8947..5d8410fcd 100644 --- a/lib/src/map/gestures/gestures.dart +++ b/lib/src/map/gestures/gestures.dart @@ -249,6 +249,9 @@ class TwoFingerGestures extends Gesture { Offset? _lastLocalFocal; double? _lastScale; double? _lastRotation; + bool _zooming = false; + bool _moving = false; + bool _rotating = false; bool get _moveEnabled => _options.interactionOptions.enabledGestures.twoFingerMove; @@ -259,15 +262,29 @@ class TwoFingerGestures extends Gesture { bool get _zoomEnabled => _options.interactionOptions.enabledGestures.twoFingerZoom; + double get _rotateThreshold => + _options.interactionOptions.twoFingerRotateThreshold; + + double get _moveThreshold => + _options.interactionOptions.twoFingerMoveThreshold; + + double get _zoomThreshold => + _options.interactionOptions.twoFingerZoomThreshold; + TwoFingerGestures({required super.controller}); /// Initialize gesture, called when gesture has started void start(ScaleStartDetails details) { controller.stopAnimationRaw(); if (details.pointerCount < 2) return; + _lastLocalFocal = details.localFocalPoint; _lastScale = 1; _lastRotation = 0; + _rotating = false; + _moving = false; + _zooming = false; + controller.emitMapEvent( MapEventMoveStart( camera: _camera, @@ -287,12 +304,24 @@ class TwoFingerGestures extends Gesture { double newRotation = _camera.rotation; if (_rotateEnabled) { - newRotation -= (_lastRotation! - details.rotation) * 80; + // enable rotation if threshold is reached + if (!_rotating && details.rotation.abs() > _rotateThreshold) { + _rotating = true; + } + if (_rotating) { + newRotation -= (_lastRotation! - details.rotation) * 80; + } } double newZoom = _camera.zoom; if (_zoomEnabled) { - newZoom -= (_lastScale! - details.scale) * 2.2; + // enable zooming if threshold is reached + if (!_zooming && details.scale.abs() > _zoomThreshold) { + _zooming = true; + } + if (_zooming) { + newZoom -= (_lastScale! - details.scale) * 2.2; + } } LatLng newCenter = _camera.center; @@ -301,9 +330,15 @@ class TwoFingerGestures extends Gesture { _camera, _lastLocalFocal! - details.localFocalPoint, ); - final oldCenterPt = _camera.project(_camera.center); - final newCenterPt = oldCenterPt + offset.toPoint(); - newCenter = _camera.unproject(newCenterPt); + // enable moving if threshold is reached + if (!_moving && offset.distanceSquared > _moveThreshold) { + _moving = true; + } + if (_moving) { + final oldCenterPt = _camera.project(_camera.center); + final newCenterPt = oldCenterPt + offset.toPoint(); + newCenter = _camera.unproject(newCenterPt); + } } controller.moveAndRotateRaw( @@ -325,6 +360,9 @@ class TwoFingerGestures extends Gesture { if (details.pointerCount < 2) return; _lastScale = null; _lastLocalFocal = null; + _rotating = false; + _zooming = false; + _moving = false; controller.emitMapEvent( MapEventMoveEnd( camera: _camera, diff --git a/lib/src/map/options/interaction_options.dart b/lib/src/map/options/interaction_options.dart index a8c85e1d7..01a09f74c 100644 --- a/lib/src/map/options/interaction_options.dart +++ b/lib/src/map/options/interaction_options.dart @@ -47,9 +47,9 @@ final class InteractionOptions { const InteractionOptions({ this.enabledGestures = const EnabledGestures.all(), - this.twoFingerRotateThreshold = 20.0, - this.twoFingerZoomThreshold = 0.5, - this.twoFingerMoveThreshold = 40.0, + this.twoFingerRotateThreshold = 0.2, + this.twoFingerZoomThreshold = 0.8, + this.twoFingerMoveThreshold = 5.0, this.scrollWheelVelocity = 0.005, this.ctrlRotateKeys = const [ LogicalKeyboardKey.control, From 4ac22ebe258d8c7f8b8f1a1004d16a82a4ed06cb Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Fri, 15 Dec 2023 23:39:53 +0100 Subject: [PATCH 056/102] adjust threshholds --- lib/src/map/gestures/gestures.dart | 6 ++++-- lib/src/map/options/interaction_options.dart | 16 ++++++++-------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/lib/src/map/gestures/gestures.dart b/lib/src/map/gestures/gestures.dart index 5d8410fcd..c8e24254b 100644 --- a/lib/src/map/gestures/gestures.dart +++ b/lib/src/map/gestures/gestures.dart @@ -316,11 +316,13 @@ class TwoFingerGestures extends Gesture { double newZoom = _camera.zoom; if (_zoomEnabled) { // enable zooming if threshold is reached - if (!_zooming && details.scale.abs() > _zoomThreshold) { + final scaleDiff = (_lastScale! - details.scale) * 1.5; + if (!_zooming && scaleDiff.abs() > _zoomThreshold) { _zooming = true; + _moving = true; } if (_zooming) { - newZoom -= (_lastScale! - details.scale) * 2.2; + newZoom -= scaleDiff * 1.5; } } diff --git a/lib/src/map/options/interaction_options.dart b/lib/src/map/options/interaction_options.dart index 01a09f74c..d6e5dba74 100644 --- a/lib/src/map/options/interaction_options.dart +++ b/lib/src/map/options/interaction_options.dart @@ -21,19 +21,19 @@ final class InteractionOptions { /// Map starts to rotate when [twoFingerRotateThreshold] has been achieved /// or another multi finger gesture wins. - /// Default is 20.0 - final double twoFingerRotateThreshold; // TODO + /// Default is 0.2 + final double twoFingerRotateThreshold; /// Map starts to zoom when [twoFingerZoomThreshold] has been achieved or /// another multi finger gesture wins. - /// Default is 0.5 - final double twoFingerZoomThreshold; // TODO + /// Default is 0.01 + final double twoFingerZoomThreshold; /// Map starts to move when [twoFingerMoveThreshold] has been achieved or /// another multi finger gesture wins. /// Note: this doesn't take any effect on drag. - /// Default is 40.0 - final double twoFingerMoveThreshold; // TODO + /// Default is 3.0 + final double twoFingerMoveThreshold; /// The velocity how fast the map should zoom when using the scroll wheel /// of the mouse. @@ -48,8 +48,8 @@ final class InteractionOptions { const InteractionOptions({ this.enabledGestures = const EnabledGestures.all(), this.twoFingerRotateThreshold = 0.2, - this.twoFingerZoomThreshold = 0.8, - this.twoFingerMoveThreshold = 5.0, + this.twoFingerZoomThreshold = 0.01, + this.twoFingerMoveThreshold = 3.0, this.scrollWheelVelocity = 0.005, this.ctrlRotateKeys = const [ LogicalKeyboardKey.control, From c22106093d37cc3080f227c73dde561a1f46fdeb Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Sat, 16 Dec 2023 00:32:34 +0100 Subject: [PATCH 057/102] clean up --- lib/src/map/gestures/gestures.dart | 17 +++++------------ lib/src/map/options/interaction_options.dart | 4 ++-- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/lib/src/map/gestures/gestures.dart b/lib/src/map/gestures/gestures.dart index c8e24254b..104b0ce4e 100644 --- a/lib/src/map/gestures/gestures.dart +++ b/lib/src/map/gestures/gestures.dart @@ -1,5 +1,4 @@ import 'dart:math' as math; -import 'dart:ui'; import 'package:flutter/animation.dart'; import 'package:flutter/gestures.dart'; @@ -210,6 +209,9 @@ class TertiaryLongPressGesture extends DelayedGesture { class ScrollWheelZoomGesture extends Gesture { ScrollWheelZoomGesture({required super.controller}); + double get _scrollWheelVelocity => + _options.interactionOptions.scrollWheelVelocity; + /// Handles mouse scroll events void submit(PointerScrollEvent details) { controller.stopAnimationRaw(); @@ -219,13 +221,8 @@ class ScrollWheelZoomGesture extends Gesture { // See [PointerSignalResolver] documentation for more information. GestureBinding.instance.pointerSignalResolver.register(details, (details) { details as PointerScrollEvent; - final minZoom = _options.minZoom ?? 0.0; - final maxZoom = _options.maxZoom ?? double.infinity; - final newZoom = clampDouble( - _camera.zoom - details.scrollDelta.dy * _scrollWheelVelocity * 2, - minZoom, - maxZoom, - ); + final newZoom = + _camera.zoom - details.scrollDelta.dy * _scrollWheelVelocity; // Calculate offset of mouse cursor from viewport center final newCenter = _camera.focusedZoomCenter( details.localPosition.toPoint(), @@ -239,9 +236,6 @@ class ScrollWheelZoomGesture extends Gesture { ); }); } - - double get _scrollWheelVelocity => - _options.interactionOptions.scrollWheelVelocity; } /// A gesture with multiple inputs like zooming with two fingers @@ -319,7 +313,6 @@ class TwoFingerGestures extends Gesture { final scaleDiff = (_lastScale! - details.scale) * 1.5; if (!_zooming && scaleDiff.abs() > _zoomThreshold) { _zooming = true; - _moving = true; } if (_zooming) { newZoom -= scaleDiff * 1.5; diff --git a/lib/src/map/options/interaction_options.dart b/lib/src/map/options/interaction_options.dart index d6e5dba74..df92679c2 100644 --- a/lib/src/map/options/interaction_options.dart +++ b/lib/src/map/options/interaction_options.dart @@ -37,7 +37,7 @@ final class InteractionOptions { /// The velocity how fast the map should zoom when using the scroll wheel /// of the mouse. - /// Defaults to 0.005. + /// Defaults to 0.01. final double scrollWheelVelocity; /// Override this option if you want to use custom keys for the CTRL+drag @@ -50,7 +50,7 @@ final class InteractionOptions { this.twoFingerRotateThreshold = 0.2, this.twoFingerZoomThreshold = 0.01, this.twoFingerMoveThreshold = 3.0, - this.scrollWheelVelocity = 0.005, + this.scrollWheelVelocity = 0.01, this.ctrlRotateKeys = const [ LogicalKeyboardKey.control, LogicalKeyboardKey.controlLeft, From 5dfe78413a9c4bab7364e630ac5db3843aaa9706 Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Sat, 16 Dec 2023 11:49:58 +0100 Subject: [PATCH 058/102] refactor --- lib/src/map/gestures/gestures.dart | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/lib/src/map/gestures/gestures.dart b/lib/src/map/gestures/gestures.dart index 104b0ce4e..35ebffe7a 100644 --- a/lib/src/map/gestures/gestures.dart +++ b/lib/src/map/gestures/gestures.dart @@ -307,18 +307,6 @@ class TwoFingerGestures extends Gesture { } } - double newZoom = _camera.zoom; - if (_zoomEnabled) { - // enable zooming if threshold is reached - final scaleDiff = (_lastScale! - details.scale) * 1.5; - if (!_zooming && scaleDiff.abs() > _zoomThreshold) { - _zooming = true; - } - if (_zooming) { - newZoom -= scaleDiff * 1.5; - } - } - LatLng newCenter = _camera.center; if (_moveEnabled) { final offset = _rotateOffset( @@ -336,6 +324,20 @@ class TwoFingerGestures extends Gesture { } } + double newZoom = _camera.zoom; + if (_zoomEnabled) { + // enable zooming if threshold is reached + // TODO: fix bug where zooming is faster if you zoom in at the start of the gesture + final scaleDiff = (_lastScale! - details.scale) * 1.5; + if (!_zooming && scaleDiff.abs() > _zoomThreshold) { + // TODO add support to zoom to the gesture, not the map center + _zooming = true; + } + if (_zooming) { + newZoom -= scaleDiff; + } + } + controller.moveAndRotateRaw( newCenter, newZoom, From 7bef3eb3bd6b01e5171f6abbfbe2181e81c57c1f Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Sat, 16 Dec 2023 13:05:02 +0100 Subject: [PATCH 059/102] split gesture service into more files --- lib/src/map/gestures/gestures.dart | 559 ------------------ .../map/gestures/map_interactive_viewer.dart | 51 +- .../map/gestures/services/base_services.dart | 49 ++ .../gestures/services/ctrl_drag_rotate.dart | 42 ++ lib/src/map/gestures/services/double_tap.dart | 52 ++ .../services/double_tap_drag_zoom.dart | 48 ++ lib/src/map/gestures/services/drag.dart | 89 +++ lib/src/map/gestures/services/long_press.dart | 62 ++ .../gestures/services/scroll_wheel_zoom.dart | 33 ++ lib/src/map/gestures/services/tap.dart | 69 +++ lib/src/map/gestures/services/two_finger.dart | 132 +++++ test/full_coverage_test.dart | 2 +- 12 files changed, 603 insertions(+), 585 deletions(-) delete mode 100644 lib/src/map/gestures/gestures.dart create mode 100644 lib/src/map/gestures/services/base_services.dart create mode 100644 lib/src/map/gestures/services/ctrl_drag_rotate.dart create mode 100644 lib/src/map/gestures/services/double_tap.dart create mode 100644 lib/src/map/gestures/services/double_tap_drag_zoom.dart create mode 100644 lib/src/map/gestures/services/drag.dart create mode 100644 lib/src/map/gestures/services/long_press.dart create mode 100644 lib/src/map/gestures/services/scroll_wheel_zoom.dart create mode 100644 lib/src/map/gestures/services/tap.dart create mode 100644 lib/src/map/gestures/services/two_finger.dart diff --git a/lib/src/map/gestures/gestures.dart b/lib/src/map/gestures/gestures.dart deleted file mode 100644 index 35ebffe7a..000000000 --- a/lib/src/map/gestures/gestures.dart +++ /dev/null @@ -1,559 +0,0 @@ -import 'dart:math' as math; - -import 'package:flutter/animation.dart'; -import 'package:flutter/gestures.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_map/flutter_map.dart'; -import 'package:latlong2/latlong.dart'; - -abstract class Gesture { - final MapControllerImpl controller; - - const Gesture({required this.controller}); - - MapCamera get _camera => controller.camera; - - MapOptions get _options => controller.options; -} - -abstract class DelayedGesture extends Gesture { - TapDownDetails? details; - - DelayedGesture({required super.controller}); - - void setDetails(TapDownDetails newDetails) => details = newDetails; - - void reset() => details = null; -} - -class TapGesture extends DelayedGesture { - TapGesture({required super.controller}); - - /// A tap with a primary button has occurred. - /// This triggers when the tap gesture wins. - void submit() { - controller.stopAnimationRaw(); - if (details == null) return; - - final point = _camera.offsetToCrs(details!.localPosition); - _options.onTap?.call(details!, point); - controller.emitMapEvent( - MapEventTap( - tapPosition: point, - camera: _camera, - source: MapEventSource.tap, - ), - ); - - reset(); - } -} - -class LongPressGesture extends Gesture { - LongPressGesture({required super.controller}); - - /// Called when a long press gesture with a primary button has been - /// recognized. A pointer has remained in contact with the screen at the - /// same location for a long period of time. - void submit(LongPressStartDetails details) { - controller.stopAnimationRaw(); - final position = _camera.offsetToCrs(details.localPosition); - _options.onLongPress?.call(details, position); - controller.emitMapEvent( - MapEventLongPress( - tapPosition: position, - camera: _camera, - source: MapEventSource.longPress, - ), - ); - } -} - -class SecondaryLongPressGesture extends Gesture { - SecondaryLongPressGesture({required super.controller}); - - /// Called when a long press gesture with a primary button has been - /// recognized. A pointer has remained in contact with the screen at the - /// same location for a long period of time. - void submit(LongPressStartDetails details) { - controller.stopAnimationRaw(); - final position = _camera.offsetToCrs(details.localPosition); - _options.onSecondaryLongPress?.call(details, position); - controller.emitMapEvent( - MapEventSecondaryLongPress( - tapPosition: position, - camera: _camera, - source: MapEventSource.secondaryLongPressed, - ), - ); - } -} - -class SecondaryTapGesture extends DelayedGesture { - SecondaryTapGesture({required super.controller}); - - /// A tap with a secondary button has occurred. - /// This triggers when the tap gesture wins. - void submit() { - controller.stopAnimationRaw(); - if (details == null) return; - - final position = _camera.offsetToCrs(details!.localPosition); - _options.onSecondaryTap?.call(details!, position); - controller.emitMapEvent( - MapEventSecondaryTap( - tapPosition: position, - camera: _camera, - source: MapEventSource.secondaryTap, - ), - ); - - reset(); - } -} - -class DoubleTapGesture extends DelayedGesture { - DoubleTapGesture({required super.controller}); - - /// A double tap gesture tap has been registered - void submit() { - controller.stopAnimationRaw(); - if (details == null) return; - - // start double tap animation - final newZoom = _getZoomForScale(_camera.zoom, 2); - final newCenter = _camera.focusedZoomCenter( - details!.localPosition.toPoint(), - newZoom, - ); - - controller.emitMapEvent( - MapEventDoubleTapZoomStart( - camera: _camera, - source: MapEventSource.doubleTap, - ), - ); - - controller.moveAnimatedRaw( - newCenter, - newZoom, - hasGesture: true, - source: MapEventSource.doubleTap, - curve: Curves.fastOutSlowIn, - duration: const Duration(milliseconds: 200), - ); - - controller.emitMapEvent( - MapEventDoubleTapZoomEnd( - camera: _camera, - source: MapEventSource.doubleTap, - ), - ); - - reset(); - } - - /// get the calculated zoom level for a given scaling, relative for the - /// startZoomLevel - double _getZoomForScale(double startZoom, double scale) { - if (scale == 1) { - return _camera.clampZoom(startZoom); - } - return _camera.clampZoom(startZoom + math.log(scale) / math.ln2); - } -} - -class TertiaryTapGesture extends DelayedGesture { - TertiaryTapGesture({required super.controller}); - - /// A tertiary tap gesture has happen (e.g. click on the mouse scroll wheel) - void submit(TapUpDetails _) { - controller.stopAnimationRaw(); - if (details == null) return; - - final point = _camera.offsetToCrs(details!.localPosition); - _options.onTertiaryTap?.call(details!, point); - controller.emitMapEvent( - MapEventTertiaryTap( - tapPosition: point, - camera: _camera, - source: MapEventSource.tertiaryTap, - ), - ); - - reset(); - } -} - -class TertiaryLongPressGesture extends DelayedGesture { - TertiaryLongPressGesture({required super.controller}); - - /// A long press on the tertiary button has happen (e.g. click and hold on - /// the mouse scroll wheel) - void submit(LongPressStartDetails details) { - controller.stopAnimationRaw(); - final point = _camera.offsetToCrs(details.localPosition); - _options.onTertiaryLongPress?.call(details, point); - controller.emitMapEvent( - MapEventTertiaryLongPress( - tapPosition: point, - camera: _camera, - source: MapEventSource.tertiaryLongPress, - ), - ); - - reset(); - } -} - -class ScrollWheelZoomGesture extends Gesture { - ScrollWheelZoomGesture({required super.controller}); - - double get _scrollWheelVelocity => - _options.interactionOptions.scrollWheelVelocity; - - /// Handles mouse scroll events - void submit(PointerScrollEvent details) { - controller.stopAnimationRaw(); - if (details.scrollDelta.dy == 0) return; - - // Prevent scrolling of parent/child widgets simultaneously. - // See [PointerSignalResolver] documentation for more information. - GestureBinding.instance.pointerSignalResolver.register(details, (details) { - details as PointerScrollEvent; - final newZoom = - _camera.zoom - details.scrollDelta.dy * _scrollWheelVelocity; - // Calculate offset of mouse cursor from viewport center - final newCenter = _camera.focusedZoomCenter( - details.localPosition.toPoint(), - newZoom, - ); - controller.moveRaw( - newCenter, - newZoom, - hasGesture: true, - source: MapEventSource.scrollWheel, - ); - }); - } -} - -/// A gesture with multiple inputs like zooming with two fingers -class TwoFingerGestures extends Gesture { - Offset? _lastLocalFocal; - double? _lastScale; - double? _lastRotation; - bool _zooming = false; - bool _moving = false; - bool _rotating = false; - - bool get _moveEnabled => - _options.interactionOptions.enabledGestures.twoFingerMove; - - bool get _rotateEnabled => - _options.interactionOptions.enabledGestures.twoFingerRotate; - - bool get _zoomEnabled => - _options.interactionOptions.enabledGestures.twoFingerZoom; - - double get _rotateThreshold => - _options.interactionOptions.twoFingerRotateThreshold; - - double get _moveThreshold => - _options.interactionOptions.twoFingerMoveThreshold; - - double get _zoomThreshold => - _options.interactionOptions.twoFingerZoomThreshold; - - TwoFingerGestures({required super.controller}); - - /// Initialize gesture, called when gesture has started - void start(ScaleStartDetails details) { - controller.stopAnimationRaw(); - if (details.pointerCount < 2) return; - - _lastLocalFocal = details.localFocalPoint; - _lastScale = 1; - _lastRotation = 0; - _rotating = false; - _moving = false; - _zooming = false; - - controller.emitMapEvent( - MapEventMoveStart( - camera: _camera, - source: MapEventSource.multiFingerStart, - ), - ); - } - - /// Called multiple times to handle updates to the gesture - void update(ScaleUpdateDetails details) { - if (details.pointerCount < 2) return; - if (_lastLocalFocal == null || - _lastScale == null || - _lastRotation == null) { - return; - } - - double newRotation = _camera.rotation; - if (_rotateEnabled) { - // enable rotation if threshold is reached - if (!_rotating && details.rotation.abs() > _rotateThreshold) { - _rotating = true; - } - if (_rotating) { - newRotation -= (_lastRotation! - details.rotation) * 80; - } - } - - LatLng newCenter = _camera.center; - if (_moveEnabled) { - final offset = _rotateOffset( - _camera, - _lastLocalFocal! - details.localFocalPoint, - ); - // enable moving if threshold is reached - if (!_moving && offset.distanceSquared > _moveThreshold) { - _moving = true; - } - if (_moving) { - final oldCenterPt = _camera.project(_camera.center); - final newCenterPt = oldCenterPt + offset.toPoint(); - newCenter = _camera.unproject(newCenterPt); - } - } - - double newZoom = _camera.zoom; - if (_zoomEnabled) { - // enable zooming if threshold is reached - // TODO: fix bug where zooming is faster if you zoom in at the start of the gesture - final scaleDiff = (_lastScale! - details.scale) * 1.5; - if (!_zooming && scaleDiff.abs() > _zoomThreshold) { - // TODO add support to zoom to the gesture, not the map center - _zooming = true; - } - if (_zooming) { - newZoom -= scaleDiff; - } - } - - controller.moveAndRotateRaw( - newCenter, - newZoom, - newRotation, - offset: Offset.zero, - hasGesture: true, - source: MapEventSource.onMultiFinger, - ); - - _lastRotation = details.rotation; - _lastScale = details.scale; - _lastLocalFocal = details.localFocalPoint; - } - - /// gesture has ended, clean up - void end(ScaleEndDetails details) { - if (details.pointerCount < 2) return; - _lastScale = null; - _lastLocalFocal = null; - _rotating = false; - _zooming = false; - _moving = false; - controller.emitMapEvent( - MapEventMoveEnd( - camera: _camera, - source: MapEventSource.multiFingerEnd, - ), - ); - } -} - -class DragGesture extends Gesture { - Offset? _lastLocalFocal; - Offset? _focalStartLocal; - - bool get _flingEnabled => - _options.interactionOptions.enabledGestures.flingAnimation; - - DragGesture({required super.controller}); - - bool get isActive => _lastLocalFocal != null; - - void start(ScaleStartDetails details) { - controller.stopAnimationRaw(); - _lastLocalFocal = details.localFocalPoint; - _focalStartLocal = details.localFocalPoint; - controller.emitMapEvent( - MapEventMoveStart( - camera: _camera, - source: MapEventSource.dragStart, - ), - ); - } - - void update(ScaleUpdateDetails details) { - if (_lastLocalFocal == null) return; - - final offset = _rotateOffset( - _camera, - _lastLocalFocal! - details.localFocalPoint, - ); - final oldCenterPt = _camera.project(_camera.center); - final newCenterPt = oldCenterPt + offset.toPoint(); - final newCenter = _camera.unproject(newCenterPt); - - controller.moveRaw( - newCenter, - _camera.zoom, - hasGesture: true, - source: MapEventSource.onDrag, - ); - - _lastLocalFocal = details.localFocalPoint; - } - - void end(ScaleEndDetails details) { - controller.emitMapEvent( - MapEventMoveEnd( - camera: _camera, - source: MapEventSource.dragEnd, - ), - ); - final lastLocalFocal = _lastLocalFocal!; - final focalStartLocal = _focalStartLocal!; - _lastLocalFocal = null; - _focalStartLocal = null; - - if (!_flingEnabled) return; - - final magnitude = details.velocity.pixelsPerSecond.distance; - - // don't start fling if the magnitude is not high enough - if (magnitude < 800) { - controller.emitMapEvent( - MapEventFlingAnimationNotStarted( - source: MapEventSource.flingAnimationController, - camera: _camera, - ), - ); - return; - } - - final direction = details.velocity.pixelsPerSecond / magnitude; - - controller.flingAnimatedRaw( - velocity: magnitude / 1000.0, - direction: direction, - begin: focalStartLocal - lastLocalFocal, - hasGesture: true, - ); - controller.emitMapEvent( - MapEventFlingAnimationStart( - source: MapEventSource.flingAnimationController, - camera: _camera, - ), - ); - } -} - -class CtrlDragRotateGesture extends Gesture { - bool isActive = false; - final List keys; - - CtrlDragRotateGesture({ - required super.controller, - required this.keys, - }); - - void start() { - controller.stopAnimationRaw(); - controller.emitMapEvent( - MapEventRotateStart( - camera: _camera, - source: MapEventSource.ctrlDragRotateStart, - ), - ); - } - - void update(ScaleUpdateDetails details) { - controller.rotateRaw( - _camera.rotation - (details.focalPointDelta.dy * 0.5), - hasGesture: true, - source: MapEventSource.ctrlDragRotate, - ); - } - - void end() { - controller.emitMapEvent( - MapEventRotateEnd( - camera: _camera, - source: MapEventSource.ctrlDragRotateEnd, - ), - ); - } - - bool get keyPressed => RawKeyboard.instance.keysPressed - .where((key) => keys.contains(key)) - .isNotEmpty; -} - -class DoubleTapDragZoomGesture extends Gesture { - bool isActive = false; - Offset? _focalLocalStart; - double? _mapZoomStart; - - DoubleTapDragZoomGesture({required super.controller}); - - void start(ScaleStartDetails details) { - controller.stopAnimationRaw(); - _focalLocalStart = details.localFocalPoint; - _mapZoomStart = _camera.zoom; - controller.emitMapEvent( - MapEventDoubleTapZoomStart( - camera: _camera, - source: MapEventSource.doubleTapHold, - ), - ); - } - - void update(ScaleUpdateDetails details) { - if (_focalLocalStart == null || _mapZoomStart == null) return; - - final verticalOffset = (_focalLocalStart! - details.localFocalPoint).dy; - final newZoom = _mapZoomStart! - verticalOffset / 360 * _camera.zoom; - final min = _options.minZoom ?? 0.0; - final max = _options.maxZoom ?? double.infinity; - final actualZoom = math.max(min, math.min(max, newZoom)); - controller.moveRaw( - _camera.center, - actualZoom, - hasGesture: true, - source: MapEventSource.doubleTapHold, - ); - } - - void end(ScaleEndDetails details) { - _mapZoomStart = null; - _focalLocalStart = null; - controller.emitMapEvent( - MapEventDoubleTapZoomEnd( - camera: _camera, - source: MapEventSource.doubleTapHold, - ), - ); - } -} - -/// Return a rotated Offset -Offset _rotateOffset(MapCamera camera, Offset offset) { - final radians = camera.rotationRad; - if (radians == 0) return offset; - - final cos = math.cos(radians); - final sin = math.sin(radians); - final nx = (cos * offset.dx) + (sin * offset.dy); - final ny = (cos * offset.dy) - (sin * offset.dx); - - return Offset(nx, ny); -} diff --git a/lib/src/map/gestures/map_interactive_viewer.dart b/lib/src/map/gestures/map_interactive_viewer.dart index f1fdfd2ca..c67046ec4 100644 --- a/lib/src/map/gestures/map_interactive_viewer.dart +++ b/lib/src/map/gestures/map_interactive_viewer.dart @@ -1,7 +1,7 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_map/flutter_map.dart'; -import 'package:flutter_map/src/map/gestures/gestures.dart'; +import 'package:flutter_map/src/map/gestures/services/base_services.dart'; class MapInteractiveViewer extends StatefulWidget { final ChildBuilder builder; @@ -19,18 +19,18 @@ class MapInteractiveViewer extends StatefulWidget { class MapInteractiveViewerState extends State with TickerProviderStateMixin { - TapGesture? _tap; - LongPressGesture? _longPress; - SecondaryTapGesture? _secondaryTap; - SecondaryLongPressGesture? _secondaryLongPress; - TertiaryTapGesture? _tertiaryTap; - TertiaryLongPressGesture? _tertiaryLongPress; - DoubleTapGesture? _doubleTap; - ScrollWheelZoomGesture? _scrollWheelZoom; - TwoFingerGestures? _twoFingerInput; - DragGesture? _drag; - DoubleTapDragZoomGesture? _doubleTapDragZoom; - CtrlDragRotateGesture? _ctrlDragRotate; + TapGestureService? _tap; + LongPressGestureService? _longPress; + SecondaryTapGestureService? _secondaryTap; + SecondaryLongPressGestureService? _secondaryLongPress; + TertiaryTapGestureService? _tertiaryTap; + TertiaryLongPressGestureService? _tertiaryLongPress; + DoubleTapGestureService? _doubleTap; + ScrollWheelZoomGestureService? _scrollWheelZoom; + TwoFingerGesturesService? _twoFingerInput; + DragGestureService? _drag; + DoubleTapDragZoomGestureService? _doubleTapDragZoom; + CtrlDragRotateGestureService? _ctrlDragRotate; MapCamera get _camera => widget.controller.camera; @@ -46,24 +46,24 @@ class MapInteractiveViewerState extends State // callback gestures for the application if (_options.onTap != null) { - _tap = TapGesture(controller: widget.controller); + _tap = TapGestureService(controller: widget.controller); } if (_options.onLongPress != null) { - _longPress = LongPressGesture(controller: widget.controller); + _longPress = LongPressGestureService(controller: widget.controller); } if (_options.onSecondaryTap != null) { - _secondaryTap = SecondaryTapGesture(controller: widget.controller); + _secondaryTap = SecondaryTapGestureService(controller: widget.controller); } if (_options.onSecondaryLongPress != null) { _secondaryLongPress = - SecondaryLongPressGesture(controller: widget.controller); + SecondaryLongPressGestureService(controller: widget.controller); } if (_options.onTertiaryTap != null) { - _tertiaryTap = TertiaryTapGesture(controller: widget.controller); + _tertiaryTap = TertiaryTapGestureService(controller: widget.controller); } if (_options.onTertiaryLongPress != null) { _tertiaryLongPress = - TertiaryLongPressGesture(controller: widget.controller); + TertiaryLongPressGestureService(controller: widget.controller); } // gestures that change the map camera updateGestures(null, _interactionOptions.enabledGestures); @@ -208,31 +208,32 @@ class MapInteractiveViewerState extends State void updateGestures(EnabledGestures? oldFlags, EnabledGestures newFlags) { if (oldFlags == newFlags) return; if (newFlags.hasMultiFinger()) { - _twoFingerInput = TwoFingerGestures(controller: widget.controller); + _twoFingerInput = TwoFingerGesturesService(controller: widget.controller); } else { _twoFingerInput = null; } if (newFlags.drag) { - _drag = DragGesture(controller: widget.controller); + _drag = DragGestureService(controller: widget.controller); } else { _drag = null; } if (newFlags.doubleTapZoomIn) { - _doubleTap = DoubleTapGesture(controller: widget.controller); + _doubleTap = DoubleTapGestureService(controller: widget.controller); } else { _doubleTap = null; } if (newFlags.scrollWheelZoom) { - _scrollWheelZoom = ScrollWheelZoomGesture(controller: widget.controller); + _scrollWheelZoom = + ScrollWheelZoomGestureService(controller: widget.controller); } else { _scrollWheelZoom = null; } if (newFlags.ctrlDragRotate) { - _ctrlDragRotate = CtrlDragRotateGesture( + _ctrlDragRotate = CtrlDragRotateGestureService( controller: widget.controller, keys: _options.interactionOptions.ctrlRotateKeys, ); @@ -242,7 +243,7 @@ class MapInteractiveViewerState extends State if (newFlags.doubleTapDragZoom) { _doubleTapDragZoom = - DoubleTapDragZoomGesture(controller: widget.controller); + DoubleTapDragZoomGestureService(controller: widget.controller); } else { _doubleTapDragZoom = null; } diff --git a/lib/src/map/gestures/services/base_services.dart b/lib/src/map/gestures/services/base_services.dart new file mode 100644 index 000000000..88a54a50a --- /dev/null +++ b/lib/src/map/gestures/services/base_services.dart @@ -0,0 +1,49 @@ +import 'dart:math' as math; + +import 'package:flutter/animation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_map/flutter_map.dart'; +import 'package:latlong2/latlong.dart'; + +part 'ctrl_drag_rotate.dart'; +part 'double_tap.dart'; +part 'double_tap_drag_zoom.dart'; +part 'drag.dart'; +part 'long_press.dart'; +part 'scroll_wheel_zoom.dart'; +part 'tap.dart'; +part 'two_finger.dart'; + +abstract class BaseGestureService { + final MapControllerImpl controller; + + const BaseGestureService({required this.controller}); + + MapCamera get _camera => controller.camera; + + MapOptions get _options => controller.options; +} + +abstract class DelayedGestureService extends BaseGestureService { + TapDownDetails? details; + + DelayedGestureService({required super.controller}); + + void setDetails(TapDownDetails newDetails) => details = newDetails; + + void reset() => details = null; +} + +/// Return a rotated Offset +Offset _rotateOffset(MapCamera camera, Offset offset) { + final radians = camera.rotationRad; + if (radians == 0) return offset; + + final cos = math.cos(radians); + final sin = math.sin(radians); + final nx = (cos * offset.dx) + (sin * offset.dy); + final ny = (cos * offset.dy) - (sin * offset.dx); + + return Offset(nx, ny); +} diff --git a/lib/src/map/gestures/services/ctrl_drag_rotate.dart b/lib/src/map/gestures/services/ctrl_drag_rotate.dart new file mode 100644 index 000000000..1580ff500 --- /dev/null +++ b/lib/src/map/gestures/services/ctrl_drag_rotate.dart @@ -0,0 +1,42 @@ +part of 'base_services.dart'; + +class CtrlDragRotateGestureService extends BaseGestureService { + bool isActive = false; + final List keys; + + CtrlDragRotateGestureService({ + required super.controller, + required this.keys, + }); + + void start() { + controller.stopAnimationRaw(); + controller.emitMapEvent( + MapEventRotateStart( + camera: _camera, + source: MapEventSource.ctrlDragRotateStart, + ), + ); + } + + void update(ScaleUpdateDetails details) { + controller.rotateRaw( + _camera.rotation - (details.focalPointDelta.dy * 0.5), + hasGesture: true, + source: MapEventSource.ctrlDragRotate, + ); + } + + void end() { + controller.emitMapEvent( + MapEventRotateEnd( + camera: _camera, + source: MapEventSource.ctrlDragRotateEnd, + ), + ); + } + + bool get keyPressed => RawKeyboard.instance.keysPressed + .where((key) => keys.contains(key)) + .isNotEmpty; +} diff --git a/lib/src/map/gestures/services/double_tap.dart b/lib/src/map/gestures/services/double_tap.dart new file mode 100644 index 000000000..3b20e4dd5 --- /dev/null +++ b/lib/src/map/gestures/services/double_tap.dart @@ -0,0 +1,52 @@ +part of 'base_services.dart'; + +class DoubleTapGestureService extends DelayedGestureService { + DoubleTapGestureService({required super.controller}); + + /// A double tap gesture tap has been registered + void submit() { + controller.stopAnimationRaw(); + if (details == null) return; + + // start double tap animation + final newZoom = _getZoomForScale(_camera.zoom, 2); + final newCenter = _camera.focusedZoomCenter( + details!.localPosition.toPoint(), + newZoom, + ); + + controller.emitMapEvent( + MapEventDoubleTapZoomStart( + camera: _camera, + source: MapEventSource.doubleTap, + ), + ); + + controller.moveAnimatedRaw( + newCenter, + newZoom, + hasGesture: true, + source: MapEventSource.doubleTap, + curve: Curves.fastOutSlowIn, + duration: const Duration(milliseconds: 200), + ); + + controller.emitMapEvent( + MapEventDoubleTapZoomEnd( + camera: _camera, + source: MapEventSource.doubleTap, + ), + ); + + reset(); + } + + /// get the calculated zoom level for a given scaling, relative for the + /// startZoomLevel + double _getZoomForScale(double startZoom, double scale) { + if (scale == 1) { + return _camera.clampZoom(startZoom); + } + return _camera.clampZoom(startZoom + math.log(scale) / math.ln2); + } +} diff --git a/lib/src/map/gestures/services/double_tap_drag_zoom.dart b/lib/src/map/gestures/services/double_tap_drag_zoom.dart new file mode 100644 index 000000000..346918985 --- /dev/null +++ b/lib/src/map/gestures/services/double_tap_drag_zoom.dart @@ -0,0 +1,48 @@ +part of 'base_services.dart'; + +class DoubleTapDragZoomGestureService extends BaseGestureService { + bool isActive = false; + Offset? _focalLocalStart; + double? _mapZoomStart; + + DoubleTapDragZoomGestureService({required super.controller}); + + void start(ScaleStartDetails details) { + controller.stopAnimationRaw(); + _focalLocalStart = details.localFocalPoint; + _mapZoomStart = _camera.zoom; + controller.emitMapEvent( + MapEventDoubleTapZoomStart( + camera: _camera, + source: MapEventSource.doubleTapHold, + ), + ); + } + + void update(ScaleUpdateDetails details) { + if (_focalLocalStart == null || _mapZoomStart == null) return; + + final verticalOffset = (_focalLocalStart! - details.localFocalPoint).dy; + final newZoom = _mapZoomStart! - verticalOffset / 360 * _camera.zoom; + final min = _options.minZoom ?? 0.0; + final max = _options.maxZoom ?? double.infinity; + final actualZoom = math.max(min, math.min(max, newZoom)); + controller.moveRaw( + _camera.center, + actualZoom, + hasGesture: true, + source: MapEventSource.doubleTapHold, + ); + } + + void end(ScaleEndDetails details) { + _mapZoomStart = null; + _focalLocalStart = null; + controller.emitMapEvent( + MapEventDoubleTapZoomEnd( + camera: _camera, + source: MapEventSource.doubleTapHold, + ), + ); + } +} diff --git a/lib/src/map/gestures/services/drag.dart b/lib/src/map/gestures/services/drag.dart new file mode 100644 index 000000000..c8ac7fead --- /dev/null +++ b/lib/src/map/gestures/services/drag.dart @@ -0,0 +1,89 @@ +part of 'base_services.dart'; + +class DragGestureService extends BaseGestureService { + Offset? _lastLocalFocal; + Offset? _focalStartLocal; + + bool get _flingEnabled => + _options.interactionOptions.enabledGestures.flingAnimation; + + DragGestureService({required super.controller}); + + bool get isActive => _lastLocalFocal != null; + + void start(ScaleStartDetails details) { + controller.stopAnimationRaw(); + _lastLocalFocal = details.localFocalPoint; + _focalStartLocal = details.localFocalPoint; + controller.emitMapEvent( + MapEventMoveStart( + camera: _camera, + source: MapEventSource.dragStart, + ), + ); + } + + void update(ScaleUpdateDetails details) { + if (_lastLocalFocal == null) return; + + final offset = _rotateOffset( + _camera, + _lastLocalFocal! - details.localFocalPoint, + ); + final oldCenterPt = _camera.project(_camera.center); + final newCenterPt = oldCenterPt + offset.toPoint(); + final newCenter = _camera.unproject(newCenterPt); + + controller.moveRaw( + newCenter, + _camera.zoom, + hasGesture: true, + source: MapEventSource.onDrag, + ); + + _lastLocalFocal = details.localFocalPoint; + } + + void end(ScaleEndDetails details) { + controller.emitMapEvent( + MapEventMoveEnd( + camera: _camera, + source: MapEventSource.dragEnd, + ), + ); + final lastLocalFocal = _lastLocalFocal!; + final focalStartLocal = _focalStartLocal!; + _lastLocalFocal = null; + _focalStartLocal = null; + + if (!_flingEnabled) return; + + final magnitude = details.velocity.pixelsPerSecond.distance; + + // don't start fling if the magnitude is not high enough + if (magnitude < 800) { + controller.emitMapEvent( + MapEventFlingAnimationNotStarted( + source: MapEventSource.flingAnimationController, + camera: _camera, + ), + ); + return; + } + + final direction = details.velocity.pixelsPerSecond / magnitude; + + controller.flingAnimatedRaw( + velocity: magnitude / 1000.0, + direction: direction, + begin: focalStartLocal - lastLocalFocal, + hasGesture: true, + ); + controller.emitMapEvent( + MapEventFlingAnimationStart( + source: MapEventSource.flingAnimationController, + camera: _camera, + ), + ); + } +} diff --git a/lib/src/map/gestures/services/long_press.dart b/lib/src/map/gestures/services/long_press.dart new file mode 100644 index 000000000..47ab3a0d3 --- /dev/null +++ b/lib/src/map/gestures/services/long_press.dart @@ -0,0 +1,62 @@ +part of 'base_services.dart'; + +class LongPressGestureService extends BaseGestureService { + LongPressGestureService({required super.controller}); + + /// Called when a long press gesture with a primary button has been + /// recognized. A pointer has remained in contact with the screen at the + /// same location for a long period of time. + void submit(LongPressStartDetails details) { + controller.stopAnimationRaw(); + final position = _camera.offsetToCrs(details.localPosition); + _options.onLongPress?.call(details, position); + controller.emitMapEvent( + MapEventLongPress( + tapPosition: position, + camera: _camera, + source: MapEventSource.longPress, + ), + ); + } +} + +class SecondaryLongPressGestureService extends BaseGestureService { + SecondaryLongPressGestureService({required super.controller}); + + /// Called when a long press gesture with a primary button has been + /// recognized. A pointer has remained in contact with the screen at the + /// same location for a long period of time. + void submit(LongPressStartDetails details) { + controller.stopAnimationRaw(); + final position = _camera.offsetToCrs(details.localPosition); + _options.onSecondaryLongPress?.call(details, position); + controller.emitMapEvent( + MapEventSecondaryLongPress( + tapPosition: position, + camera: _camera, + source: MapEventSource.secondaryLongPressed, + ), + ); + } +} + +class TertiaryLongPressGestureService extends DelayedGestureService { + TertiaryLongPressGestureService({required super.controller}); + + /// A long press on the tertiary button has happen (e.g. click and hold on + /// the mouse scroll wheel) + void submit(LongPressStartDetails details) { + controller.stopAnimationRaw(); + final point = _camera.offsetToCrs(details.localPosition); + _options.onTertiaryLongPress?.call(details, point); + controller.emitMapEvent( + MapEventTertiaryLongPress( + tapPosition: point, + camera: _camera, + source: MapEventSource.tertiaryLongPress, + ), + ); + + reset(); + } +} diff --git a/lib/src/map/gestures/services/scroll_wheel_zoom.dart b/lib/src/map/gestures/services/scroll_wheel_zoom.dart new file mode 100644 index 000000000..996e2a367 --- /dev/null +++ b/lib/src/map/gestures/services/scroll_wheel_zoom.dart @@ -0,0 +1,33 @@ +part of 'base_services.dart'; + +class ScrollWheelZoomGestureService extends BaseGestureService { + ScrollWheelZoomGestureService({required super.controller}); + + double get _scrollWheelVelocity => + _options.interactionOptions.scrollWheelVelocity; + + /// Handles mouse scroll events + void submit(PointerScrollEvent details) { + controller.stopAnimationRaw(); + if (details.scrollDelta.dy == 0) return; + + // Prevent scrolling of parent/child widgets simultaneously. + // See [PointerSignalResolver] documentation for more information. + GestureBinding.instance.pointerSignalResolver.register(details, (details) { + details as PointerScrollEvent; + final newZoom = + _camera.zoom - details.scrollDelta.dy * _scrollWheelVelocity; + // Calculate offset of mouse cursor from viewport center + final newCenter = _camera.focusedZoomCenter( + details.localPosition.toPoint(), + newZoom, + ); + controller.moveRaw( + newCenter, + newZoom, + hasGesture: true, + source: MapEventSource.scrollWheel, + ); + }); + } +} diff --git a/lib/src/map/gestures/services/tap.dart b/lib/src/map/gestures/services/tap.dart new file mode 100644 index 000000000..4cfdd816e --- /dev/null +++ b/lib/src/map/gestures/services/tap.dart @@ -0,0 +1,69 @@ +part of 'base_services.dart'; + +class TapGestureService extends DelayedGestureService { + TapGestureService({required super.controller}); + + /// A tap with a primary button has occurred. + /// This triggers when the tap gesture wins. + void submit() { + controller.stopAnimationRaw(); + if (details == null) return; + + final point = _camera.offsetToCrs(details!.localPosition); + _options.onTap?.call(details!, point); + controller.emitMapEvent( + MapEventTap( + tapPosition: point, + camera: _camera, + source: MapEventSource.tap, + ), + ); + + reset(); + } +} + +class SecondaryTapGestureService extends DelayedGestureService { + SecondaryTapGestureService({required super.controller}); + + /// A tap with a secondary button has occurred. + /// This triggers when the tap gesture wins. + void submit() { + controller.stopAnimationRaw(); + if (details == null) return; + + final position = _camera.offsetToCrs(details!.localPosition); + _options.onSecondaryTap?.call(details!, position); + controller.emitMapEvent( + MapEventSecondaryTap( + tapPosition: position, + camera: _camera, + source: MapEventSource.secondaryTap, + ), + ); + + reset(); + } +} + +class TertiaryTapGestureService extends DelayedGestureService { + TertiaryTapGestureService({required super.controller}); + + /// A tertiary tap gesture has happen (e.g. click on the mouse scroll wheel) + void submit(TapUpDetails _) { + controller.stopAnimationRaw(); + if (details == null) return; + + final point = _camera.offsetToCrs(details!.localPosition); + _options.onTertiaryTap?.call(details!, point); + controller.emitMapEvent( + MapEventTertiaryTap( + tapPosition: point, + camera: _camera, + source: MapEventSource.tertiaryTap, + ), + ); + + reset(); + } +} diff --git a/lib/src/map/gestures/services/two_finger.dart b/lib/src/map/gestures/services/two_finger.dart new file mode 100644 index 000000000..a3df67ad4 --- /dev/null +++ b/lib/src/map/gestures/services/two_finger.dart @@ -0,0 +1,132 @@ +part of 'base_services.dart'; + +/// A gesture with multiple inputs like zooming with two fingers +class TwoFingerGesturesService extends BaseGestureService { + Offset? _lastLocalFocal; + double? _lastScale; + double? _lastRotation; + bool _zooming = false; + bool _moving = false; + bool _rotating = false; + + bool get _moveEnabled => + _options.interactionOptions.enabledGestures.twoFingerMove; + + bool get _rotateEnabled => + _options.interactionOptions.enabledGestures.twoFingerRotate; + + bool get _zoomEnabled => + _options.interactionOptions.enabledGestures.twoFingerZoom; + + double get _rotateThreshold => + _options.interactionOptions.twoFingerRotateThreshold; + + double get _moveThreshold => + _options.interactionOptions.twoFingerMoveThreshold; + + double get _zoomThreshold => + _options.interactionOptions.twoFingerZoomThreshold; + + TwoFingerGesturesService({required super.controller}); + + /// Initialize gesture, called when gesture has started + void start(ScaleStartDetails details) { + controller.stopAnimationRaw(); + if (details.pointerCount < 2) return; + + _lastLocalFocal = details.localFocalPoint; + _lastScale = 1; + _lastRotation = 0; + _rotating = false; + _moving = false; + _zooming = false; + + controller.emitMapEvent( + MapEventMoveStart( + camera: _camera, + source: MapEventSource.multiFingerStart, + ), + ); + } + + /// Called multiple times to handle updates to the gesture + void update(ScaleUpdateDetails details) { + if (details.pointerCount < 2) return; + if (_lastLocalFocal == null || + _lastScale == null || + _lastRotation == null) { + return; + } + + double newRotation = _camera.rotation; + if (_rotateEnabled) { + // enable rotation if threshold is reached + if (!_rotating && details.rotation.abs() > _rotateThreshold) { + _rotating = true; + } + if (_rotating) { + newRotation -= (_lastRotation! - details.rotation) * 80; + } + } + + LatLng newCenter = _camera.center; + if (_moveEnabled) { + final offset = _rotateOffset( + _camera, + _lastLocalFocal! - details.localFocalPoint, + ); + // enable moving if threshold is reached + if (!_moving && offset.distanceSquared > _moveThreshold) { + _moving = true; + } + if (_moving) { + final oldCenterPt = _camera.project(_camera.center); + final newCenterPt = oldCenterPt + offset.toPoint(); + newCenter = _camera.unproject(newCenterPt); + } + } + + double newZoom = _camera.zoom; + if (_zoomEnabled) { + // enable zooming if threshold is reached + // TODO: fix bug where zooming is faster if you zoom in at the start of the gesture + final scaleDiff = (_lastScale! - details.scale) * 1.5; + if (!_zooming && scaleDiff.abs() > _zoomThreshold) { + // TODO add support to zoom to the gesture, not the map center + _zooming = true; + } + if (_zooming) { + newZoom -= scaleDiff; + } + } + + controller.moveAndRotateRaw( + newCenter, + newZoom, + newRotation, + offset: Offset.zero, + hasGesture: true, + source: MapEventSource.onMultiFinger, + ); + + _lastRotation = details.rotation; + _lastScale = details.scale; + _lastLocalFocal = details.localFocalPoint; + } + + /// gesture has ended, clean up + void end(ScaleEndDetails details) { + if (details.pointerCount < 2) return; + _lastScale = null; + _lastLocalFocal = null; + _rotating = false; + _zooming = false; + _moving = false; + controller.emitMapEvent( + MapEventMoveEnd( + camera: _camera, + source: MapEventSource.multiFingerEnd, + ), + ); + } +} diff --git a/test/full_coverage_test.dart b/test/full_coverage_test.dart index 3109fad83..ea2dacc3f 100644 --- a/test/full_coverage_test.dart +++ b/test/full_coverage_test.dart @@ -42,9 +42,9 @@ import 'package:flutter_map/src/map/controller/events/map_event_source.dart'; import 'package:flutter_map/src/map/controller/events/map_events.dart'; import 'package:flutter_map/src/map/controller/map_controller.dart'; import 'package:flutter_map/src/map/controller/map_controller_impl.dart'; -import 'package:flutter_map/src/map/gestures/gestures.dart'; import 'package:flutter_map/src/map/gestures/latlng_tween.dart'; import 'package:flutter_map/src/map/gestures/map_interactive_viewer.dart'; +import 'package:flutter_map/src/map/gestures/services/base_services.dart'; import 'package:flutter_map/src/map/inherited_model.dart'; import 'package:flutter_map/src/map/options/enabled_gestures.dart'; import 'package:flutter_map/src/map/options/interaction_options.dart'; From 15d865893d97a660f31db8fe7639008da1f44a35 Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Sat, 16 Dec 2023 13:27:24 +0100 Subject: [PATCH 060/102] use delta to start value for threshold --- lib/src/map/gestures/services/two_finger.dart | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/lib/src/map/gestures/services/two_finger.dart b/lib/src/map/gestures/services/two_finger.dart index a3df67ad4..14d129ed7 100644 --- a/lib/src/map/gestures/services/two_finger.dart +++ b/lib/src/map/gestures/services/two_finger.dart @@ -2,6 +2,9 @@ part of 'base_services.dart'; /// A gesture with multiple inputs like zooming with two fingers class TwoFingerGesturesService extends BaseGestureService { + Offset? _startLocalFocal; + double? _startScale; + double? _startRotation; Offset? _lastLocalFocal; double? _lastScale; double? _lastRotation; @@ -37,6 +40,9 @@ class TwoFingerGesturesService extends BaseGestureService { _lastLocalFocal = details.localFocalPoint; _lastScale = 1; _lastRotation = 0; + _startLocalFocal = _lastLocalFocal; + _startScale = _lastScale; + _startRotation = _lastRotation; _rotating = false; _moving = false; _zooming = false; @@ -54,14 +60,19 @@ class TwoFingerGesturesService extends BaseGestureService { if (details.pointerCount < 2) return; if (_lastLocalFocal == null || _lastScale == null || - _lastRotation == null) { + _lastRotation == null || + _startScale == null || + _startRotation == null || + _startLocalFocal == null) { return; } + // TODO: adjust every threshold value double newRotation = _camera.rotation; if (_rotateEnabled) { // enable rotation if threshold is reached - if (!_rotating && details.rotation.abs() > _rotateThreshold) { + if (!_rotating && + (details.rotation - _startRotation!).abs() > _rotateThreshold) { _rotating = true; } if (_rotating) { @@ -76,7 +87,8 @@ class TwoFingerGesturesService extends BaseGestureService { _lastLocalFocal! - details.localFocalPoint, ); // enable moving if threshold is reached - if (!_moving && offset.distanceSquared > _moveThreshold) { + if (!_moving && + ((offset - _startLocalFocal!).distanceSquared) > _moveThreshold) { _moving = true; } if (_moving) { @@ -91,11 +103,11 @@ class TwoFingerGesturesService extends BaseGestureService { // enable zooming if threshold is reached // TODO: fix bug where zooming is faster if you zoom in at the start of the gesture final scaleDiff = (_lastScale! - details.scale) * 1.5; - if (!_zooming && scaleDiff.abs() > _zoomThreshold) { - // TODO add support to zoom to the gesture, not the map center + if (!_zooming && (scaleDiff - _startScale!).abs() > _zoomThreshold) { _zooming = true; } if (_zooming) { + // TODO: add support to zoom to the gesture, not the map center newZoom -= scaleDiff; } } @@ -119,6 +131,10 @@ class TwoFingerGesturesService extends BaseGestureService { if (details.pointerCount < 2) return; _lastScale = null; _lastLocalFocal = null; + _lastRotation = null; + _startRotation = null; + _startLocalFocal = null; + _startScale = null; _rotating = false; _zooming = false; _moving = false; From 876c098b31dcab89c9e2d2009677a4f2c59db8cc Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Sat, 16 Dec 2023 22:39:45 +0100 Subject: [PATCH 061/102] fix: two finger gestures don't comply with options --- lib/src/map/gestures/services/two_finger.dart | 94 ++++++++++++------- 1 file changed, 62 insertions(+), 32 deletions(-) diff --git a/lib/src/map/gestures/services/two_finger.dart b/lib/src/map/gestures/services/two_finger.dart index 14d129ed7..f2ed6d3a9 100644 --- a/lib/src/map/gestures/services/two_finger.dart +++ b/lib/src/map/gestures/services/two_finger.dart @@ -2,12 +2,15 @@ part of 'base_services.dart'; /// A gesture with multiple inputs like zooming with two fingers class TwoFingerGesturesService extends BaseGestureService { + MapCamera? _startCamera; + LatLng? _startFocalLatLng; Offset? _startLocalFocal; - double? _startScale; - double? _startRotation; + + MapCamera? _lastCamera; Offset? _lastLocalFocal; double? _lastScale; double? _lastRotation; + bool _zooming = false; bool _moving = false; bool _rotating = false; @@ -37,12 +40,15 @@ class TwoFingerGesturesService extends BaseGestureService { controller.stopAnimationRaw(); if (details.pointerCount < 2) return; - _lastLocalFocal = details.localFocalPoint; + _startCamera = _camera; + _startLocalFocal = _lastLocalFocal = details.localFocalPoint; + _startFocalLatLng = _camera.offsetToCrs(_startLocalFocal!); + _lastScale = 1; _lastRotation = 0; - _startLocalFocal = _lastLocalFocal; - _startScale = _lastScale; - _startRotation = _lastRotation; + _lastLocalFocal = _startLocalFocal; + _lastCamera = _startCamera; + _rotating = false; _moving = false; _zooming = false; @@ -61,9 +67,10 @@ class TwoFingerGesturesService extends BaseGestureService { if (_lastLocalFocal == null || _lastScale == null || _lastRotation == null || - _startScale == null || - _startRotation == null || - _startLocalFocal == null) { + _startCamera == null || + _startLocalFocal == null || + _startFocalLatLng == null || + _lastCamera == null) { return; } @@ -72,7 +79,8 @@ class TwoFingerGesturesService extends BaseGestureService { if (_rotateEnabled) { // enable rotation if threshold is reached if (!_rotating && - (details.rotation - _startRotation!).abs() > _rotateThreshold) { + (details.rotation - _startCamera!.rotation).abs() > + _rotateThreshold) { _rotating = true; } if (_rotating) { @@ -80,35 +88,53 @@ class TwoFingerGesturesService extends BaseGestureService { } } + double newZoom = _camera.zoom; + if (_zoomEnabled) { + // enable zooming if threshold is reached + final scaleDiff = (_lastScale! - details.scale) * 1.5; + const startScale = 1; + if (!_zooming && (scaleDiff - startScale).abs() > _zoomThreshold) { + _zooming = true; + } + if (_zooming) { + final tmpZoom = details.scale == 1 + ? _startCamera!.zoom + : _startCamera!.zoom + math.log(details.scale) / math.ln2; + newZoom = _camera.clampZoom(tmpZoom); + } + } + LatLng newCenter = _camera.center; if (_moveEnabled) { - final offset = _rotateOffset( + final currentOffset = _rotateOffset( _camera, _lastLocalFocal! - details.localFocalPoint, ); // enable moving if threshold is reached if (!_moving && - ((offset - _startLocalFocal!).distanceSquared) > _moveThreshold) { + ((currentOffset - _startLocalFocal!).distanceSquared) > + _moveThreshold) { _moving = true; } if (_moving) { - final oldCenterPt = _camera.project(_camera.center); - final newCenterPt = oldCenterPt + offset.toPoint(); - newCenter = _camera.unproject(newCenterPt); - } - } - - double newZoom = _camera.zoom; - if (_zoomEnabled) { - // enable zooming if threshold is reached - // TODO: fix bug where zooming is faster if you zoom in at the start of the gesture - final scaleDiff = (_lastScale! - details.scale) * 1.5; - if (!_zooming && (scaleDiff - _startScale!).abs() > _zoomThreshold) { - _zooming = true; - } - if (_zooming) { - // TODO: add support to zoom to the gesture, not the map center - newZoom -= scaleDiff; + math.Point newCenterPt; + if (_zooming) { + final oldCenterPt = _camera.project(_camera.center, newZoom); + final newFocalLatLong = + _camera.offsetToCrs(_startLocalFocal!, newZoom); + final newFocalPt = _camera.project(newFocalLatLong, newZoom); + final oldFocalPt = _camera.project(_startFocalLatLng!, newZoom); + final zoomDifference = oldFocalPt - newFocalPt; + final moveDifference = + _rotateOffset(_camera, _startLocalFocal! - _lastLocalFocal!); + + newCenterPt = oldCenterPt + zoomDifference + moveDifference.toPoint(); + } else { + // simplification for no zooming + newCenterPt = _camera.project(_camera.center, newZoom) + + currentOffset.toPoint(); + } + newCenter = _camera.unproject(newCenterPt, newZoom); } } @@ -122,6 +148,7 @@ class TwoFingerGesturesService extends BaseGestureService { ); _lastRotation = details.rotation; + _lastCamera = _camera; _lastScale = details.scale; _lastLocalFocal = details.localFocalPoint; } @@ -129,12 +156,15 @@ class TwoFingerGesturesService extends BaseGestureService { /// gesture has ended, clean up void end(ScaleEndDetails details) { if (details.pointerCount < 2) return; + _startCamera = null; + _startLocalFocal = null; + _startFocalLatLng = null; + + _lastCamera = null; _lastScale = null; _lastLocalFocal = null; _lastRotation = null; - _startRotation = null; - _startLocalFocal = null; - _startScale = null; + _rotating = false; _zooming = false; _moving = false; From 2c22a95382aec07069a8f522edf7b448ec733272 Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Sat, 16 Dec 2023 23:02:16 +0100 Subject: [PATCH 062/102] adjust thresholds one last time --- lib/src/map/gestures/services/two_finger.dart | 22 +++++++++---------- lib/src/map/options/interaction_options.dart | 17 +++++++++----- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/lib/src/map/gestures/services/two_finger.dart b/lib/src/map/gestures/services/two_finger.dart index f2ed6d3a9..a62626311 100644 --- a/lib/src/map/gestures/services/two_finger.dart +++ b/lib/src/map/gestures/services/two_finger.dart @@ -74,13 +74,10 @@ class TwoFingerGesturesService extends BaseGestureService { return; } - // TODO: adjust every threshold value double newRotation = _camera.rotation; if (_rotateEnabled) { // enable rotation if threshold is reached - if (!_rotating && - (details.rotation - _startCamera!.rotation).abs() > - _rotateThreshold) { + if (!_rotating && details.rotation.abs() > _rotateThreshold) { _rotating = true; } if (_rotating) { @@ -106,14 +103,11 @@ class TwoFingerGesturesService extends BaseGestureService { LatLng newCenter = _camera.center; if (_moveEnabled) { - final currentOffset = _rotateOffset( - _camera, - _lastLocalFocal! - details.localFocalPoint, - ); - // enable moving if threshold is reached - if (!_moving && - ((currentOffset - _startLocalFocal!).distanceSquared) > - _moveThreshold) { + final distanceSqToStart = + (details.localFocalPoint - _startLocalFocal!).distanceSquared; + // Ignore twoFingerMoveThreshold if twoFingerZoomThreshold is reached. + if (!_moving && distanceSqToStart > _moveThreshold || _zooming) { + // Move threshold reached or zooming activated. _moving = true; } if (_moving) { @@ -131,6 +125,10 @@ class TwoFingerGesturesService extends BaseGestureService { newCenterPt = oldCenterPt + zoomDifference + moveDifference.toPoint(); } else { // simplification for no zooming + final currentOffset = _rotateOffset( + _camera, + _lastLocalFocal! - details.localFocalPoint, + ); newCenterPt = _camera.project(_camera.center, newZoom) + currentOffset.toPoint(); } diff --git a/lib/src/map/options/interaction_options.dart b/lib/src/map/options/interaction_options.dart index df92679c2..de1d86a66 100644 --- a/lib/src/map/options/interaction_options.dart +++ b/lib/src/map/options/interaction_options.dart @@ -21,18 +21,23 @@ final class InteractionOptions { /// Map starts to rotate when [twoFingerRotateThreshold] has been achieved /// or another multi finger gesture wins. - /// Default is 0.2 + /// Default is 0.1 final double twoFingerRotateThreshold; /// Map starts to zoom when [twoFingerZoomThreshold] has been achieved or /// another multi finger gesture wins. - /// Default is 0.01 + /// Default is 0.1 final double twoFingerZoomThreshold; /// Map starts to move when [twoFingerMoveThreshold] has been achieved or - /// another multi finger gesture wins. - /// Note: this doesn't take any effect on drag. - /// Default is 3.0 + /// another multi finger gesture wins. This doesn't take any effect on drag + /// gestures by a single pointer like a single finger. + /// + /// This option gets superseded by [twoFingerZoomThreshold] if + /// [EnabledGestures.twoFingerMove] and [EnabledGestures.twoFingerZoom] are + /// both active and the [twoFingerZoomThreshold] is reached. + /// + /// Default is 3.0 (disabled) final double twoFingerMoveThreshold; /// The velocity how fast the map should zoom when using the scroll wheel @@ -47,7 +52,7 @@ final class InteractionOptions { const InteractionOptions({ this.enabledGestures = const EnabledGestures.all(), - this.twoFingerRotateThreshold = 0.2, + this.twoFingerRotateThreshold = 0.1, this.twoFingerZoomThreshold = 0.01, this.twoFingerMoveThreshold = 3.0, this.scrollWheelVelocity = 0.01, From f9bcdc7f536951aa1f753f1c85f592baf6e58bd3 Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Sat, 16 Dec 2023 23:21:15 +0100 Subject: [PATCH 063/102] fix map events --- .../controller/events/map_event_source.dart | 1 - lib/src/map/controller/events/map_events.dart | 18 ++++++++++++++++++ .../map/controller/map_controller_impl.dart | 11 ++++++++--- .../services/double_tap_drag_zoom.dart | 4 ++-- lib/src/map/widget.dart | 5 ++++- 5 files changed, 32 insertions(+), 7 deletions(-) diff --git a/lib/src/map/controller/events/map_event_source.dart b/lib/src/map/controller/events/map_event_source.dart index 669ac10a9..b73491039 100644 --- a/lib/src/map/controller/events/map_event_source.dart +++ b/lib/src/map/controller/events/map_event_source.dart @@ -20,7 +20,6 @@ enum MapEventSource { custom, scrollWheel, nonRotatedSizeChange, - cursorKeyboardRotation, tertiaryTap, tertiaryLongPress, secondaryLongPressed, diff --git a/lib/src/map/controller/events/map_events.dart b/lib/src/map/controller/events/map_events.dart index 3fdff624a..ce4c6a750 100644 --- a/lib/src/map/controller/events/map_events.dart +++ b/lib/src/map/controller/events/map_events.dart @@ -258,6 +258,24 @@ class MapEventDoubleTapZoomEnd extends MapEvent { }); } +/// Event which is fired when animation for double tap gesture is started +@immutable +class MapEventDoubleTapDragZoomStart extends MapEvent { + const MapEventDoubleTapDragZoomStart({ + required super.source, + required super.camera, + }); +} + +/// Event which is fired when animation for double tap gesture ends +@immutable +class MapEventDoubleTapDragZoomEnd extends MapEvent { + const MapEventDoubleTapDragZoomEnd({ + required super.source, + required super.camera, + }); +} + /// Event which is fired when map is being rotated @immutable class MapEventRotate extends MapEventWithMove { diff --git a/lib/src/map/controller/map_controller_impl.dart b/lib/src/map/controller/map_controller_impl.dart index ea18970c7..a0896a01f 100644 --- a/lib/src/map/controller/map_controller_impl.dart +++ b/lib/src/map/controller/map_controller_impl.dart @@ -133,7 +133,10 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState> ); @override - bool fitCamera(CameraFit cameraFit) => fitCameraRaw(cameraFit); + bool fitCamera(CameraFit cameraFit) => fitCameraRaw( + cameraFit, + source: MapEventSource.mapController, + ); bool moveRaw( LatLng newCenter, @@ -311,14 +314,16 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState> bool fitCameraRaw( CameraFit cameraFit, { Offset offset = Offset.zero, + bool hasGesture = false, + required MapEventSource source, }) { final fitted = cameraFit.fit(camera); return moveRaw( fitted.center, fitted.zoom, offset: offset, - hasGesture: false, - source: MapEventSource.mapController, + hasGesture: hasGesture, + source: source, ); } diff --git a/lib/src/map/gestures/services/double_tap_drag_zoom.dart b/lib/src/map/gestures/services/double_tap_drag_zoom.dart index 346918985..47ec81d38 100644 --- a/lib/src/map/gestures/services/double_tap_drag_zoom.dart +++ b/lib/src/map/gestures/services/double_tap_drag_zoom.dart @@ -12,7 +12,7 @@ class DoubleTapDragZoomGestureService extends BaseGestureService { _focalLocalStart = details.localFocalPoint; _mapZoomStart = _camera.zoom; controller.emitMapEvent( - MapEventDoubleTapZoomStart( + MapEventDoubleTapDragZoomStart( camera: _camera, source: MapEventSource.doubleTapHold, ), @@ -39,7 +39,7 @@ class DoubleTapDragZoomGestureService extends BaseGestureService { _mapZoomStart = null; _focalLocalStart = null; controller.emitMapEvent( - MapEventDoubleTapZoomEnd( + MapEventDoubleTapDragZoomEnd( camera: _camera, source: MapEventSource.doubleTapHold, ), diff --git a/lib/src/map/widget.dart b/lib/src/map/widget.dart index a93668d4a..59d7c0e9b 100644 --- a/lib/src/map/widget.dart +++ b/lib/src/map/widget.dart @@ -141,7 +141,10 @@ class _FlutterMapStateContainer extends State _parentConstraintsAreSet(context, constraints)) { _initialCameraFitApplied = true; - _mapController.fitCamera(widget.options.initialCameraFit!); + _mapController.fitCameraRaw( + widget.options.initialCameraFit!, + source: MapEventSource.fitCamera, + ); } } From 230902656e9c5e4a8258b2a4806c8043dc4283bd Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Sun, 17 Dec 2023 13:51:00 +0100 Subject: [PATCH 064/102] `TertiaryLongPressGestureService` is no delayed gesture --- lib/src/map/gestures/services/long_press.dart | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/src/map/gestures/services/long_press.dart b/lib/src/map/gestures/services/long_press.dart index 47ab3a0d3..05ae7a81d 100644 --- a/lib/src/map/gestures/services/long_press.dart +++ b/lib/src/map/gestures/services/long_press.dart @@ -40,7 +40,7 @@ class SecondaryLongPressGestureService extends BaseGestureService { } } -class TertiaryLongPressGestureService extends DelayedGestureService { +class TertiaryLongPressGestureService extends BaseGestureService { TertiaryLongPressGestureService({required super.controller}); /// A long press on the tertiary button has happen (e.g. click and hold on @@ -56,7 +56,5 @@ class TertiaryLongPressGestureService extends DelayedGestureService { source: MapEventSource.tertiaryLongPress, ), ); - - reset(); } } From f0904eaa0d88d68d65b4e1f0da6910d4aa5bfb98 Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Sun, 17 Dec 2023 13:51:33 +0100 Subject: [PATCH 065/102] fix refactoring https://github.com/fleaflet/flutter_map/pull/1733#discussion_r1428965313 --- lib/src/map/options/map_options.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/src/map/options/map_options.dart b/lib/src/map/options/map_options.dart index d9d938483..61ef78513 100644 --- a/lib/src/map/options/map_options.dart +++ b/lib/src/map/options/map_options.dart @@ -83,8 +83,6 @@ class MapOptions { final InteractionOptions interactionOptions; - /// The options of the closest [FlutterMap] ancestor. If this is called from a - const MapOptions({ this.crs = const Epsg3857(), this.initialCenter = const LatLng(50.5, 30.51), @@ -113,6 +111,7 @@ class MapOptions { this.applyPointerTranslucencyToLayers = true, }); + /// The options of the closest [FlutterMap] ancestor. If this is called from a /// context with no [FlutterMap] ancestor, null is returned. static MapOptions? maybeOf(BuildContext context) => MapInheritedModel.maybeOptionsOf(context); From 48e1a3b0d427595b19889b0ae6e5e61caa8f97d2 Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Sun, 17 Dec 2023 14:12:14 +0100 Subject: [PATCH 066/102] rename wither https://github.com/fleaflet/flutter_map/pull/1733#discussion_r1428964175 --- lib/src/map/options/enabled_gestures.dart | 2 +- test/flutter_map_test.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/map/options/enabled_gestures.dart b/lib/src/map/options/enabled_gestures.dart index c3af8bc00..bfd48557e 100644 --- a/lib/src/map/options/enabled_gestures.dart +++ b/lib/src/map/options/enabled_gestures.dart @@ -119,7 +119,7 @@ class EnabledGestures { /// Wither to change the value of some gestures. Returns a new /// [EnabledGestures] object. - EnabledGestures withFlag({ + EnabledGestures copyWith({ bool? drag, bool? flingAnimation, bool? twoFingerZoom, diff --git a/test/flutter_map_test.dart b/test/flutter_map_test.dart index 9d4ebbca6..1266224e8 100644 --- a/test/flutter_map_test.dart +++ b/test/flutter_map_test.dart @@ -247,7 +247,7 @@ class _TestRebuildsAppState extends State { TextButton( onPressed: () { setState(() { - _interactiveFlags = _interactiveFlags.withFlag( + _interactiveFlags = _interactiveFlags.copyWith( drag: !_interactiveFlags.drag, ); }); From be56976418bdfc99bbe67655b800871ca714f2e0 Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Sun, 17 Dec 2023 20:17:14 +0100 Subject: [PATCH 067/102] small fix in docs --- lib/src/map/options/interaction_options.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/src/map/options/interaction_options.dart b/lib/src/map/options/interaction_options.dart index de1d86a66..bddc273ff 100644 --- a/lib/src/map/options/interaction_options.dart +++ b/lib/src/map/options/interaction_options.dart @@ -37,7 +37,7 @@ final class InteractionOptions { /// [EnabledGestures.twoFingerMove] and [EnabledGestures.twoFingerZoom] are /// both active and the [twoFingerZoomThreshold] is reached. /// - /// Default is 3.0 (disabled) + /// Default is 3.0. final double twoFingerMoveThreshold; /// The velocity how fast the map should zoom when using the scroll wheel @@ -60,6 +60,9 @@ final class InteractionOptions { LogicalKeyboardKey.control, LogicalKeyboardKey.controlLeft, LogicalKeyboardKey.controlRight, + LogicalKeyboardKey.shift, + LogicalKeyboardKey.shiftLeft, + LogicalKeyboardKey.shiftRight, ], }) : assert( twoFingerRotateThreshold >= 0.0, From 5aa3aff0d28d38efeb446ca88b0efcb3953773d1 Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Mon, 18 Dec 2023 08:31:11 +0100 Subject: [PATCH 068/102] fix disable double tap in example pages with clicks https://github.com/fleaflet/flutter_map/pull/1733#discussion_r1429194762 --- example/lib/pages/latlng_to_screen_point.dart | 5 ++++- example/lib/pages/markers.dart | 6 ++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/example/lib/pages/latlng_to_screen_point.dart b/example/lib/pages/latlng_to_screen_point.dart index a6514d38a..d8d0ae52f 100644 --- a/example/lib/pages/latlng_to_screen_point.dart +++ b/example/lib/pages/latlng_to_screen_point.dart @@ -48,7 +48,10 @@ class _LatLngToScreenPointPageState extends State { initialCenter: const LatLng(51.5, -0.09), initialZoom: 11, interactionOptions: const InteractionOptions( - enabledGestures: EnabledGestures.all(doubleTapZoomIn: false), + enabledGestures: EnabledGestures.all( + doubleTapZoomIn: false, + doubleTapDragZoom: false, + ), ), onTap: (_, latLng) { final point = mapController.camera diff --git a/example/lib/pages/markers.dart b/example/lib/pages/markers.dart index 4471d95b8..fd83363ea 100644 --- a/example/lib/pages/markers.dart +++ b/example/lib/pages/markers.dart @@ -114,8 +114,10 @@ class _MarkerPageState extends State { setState(() => customMarkers.add(buildPin(p))); }, interactionOptions: const InteractionOptions( - enabledGestures: EnabledGestures.noRotation(), - ), + enabledGestures: EnabledGestures.all( + doubleTapDragZoom: false, + doubleTapZoomIn: false, + )), ), children: [ openStreetMapTileLayer, From e2c7827c4a229ff381135565088ab9d9ad478301 Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Mon, 18 Dec 2023 08:34:28 +0100 Subject: [PATCH 069/102] rename `groups` constructor to `byGroup` https://github.com/fleaflet/flutter_map/pull/1733#discussion_r1428964013 --- lib/src/map/options/enabled_gestures.dart | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/lib/src/map/options/enabled_gestures.dart b/lib/src/map/options/enabled_gestures.dart index bfd48557e..ddd57d526 100644 --- a/lib/src/map/options/enabled_gestures.dart +++ b/lib/src/map/options/enabled_gestures.dart @@ -142,6 +142,22 @@ class EnabledGestures { ctrlDragRotate: ctrlDragRotate ?? this.ctrlDragRotate, ); + const EnabledGestures.byGroup({ + required bool move, + required bool zoom, + required bool rotate, + }) : this._( + drag: move, + twoFingerMove: move, + flingAnimation: move, + doubleTapDragZoom: zoom, + doubleTapZoomIn: zoom, + scrollWheelZoom: zoom, + twoFingerZoom: zoom, + twoFingerRotate: rotate, + ctrlDragRotate: rotate, + ); + @override bool operator ==(Object other) => identical(this, other) || From 9c117cb88b6320454536b6c86ae773eaff576942 Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Mon, 18 Dec 2023 08:37:21 +0100 Subject: [PATCH 070/102] update label "Two finger drag" https://github.com/fleaflet/flutter_map/pull/1733#discussion_r1428955888 --- example/lib/pages/gestures_page.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/lib/pages/gestures_page.dart b/example/lib/pages/gestures_page.dart index 7fc34bd1c..0af8910c3 100644 --- a/example/lib/pages/gestures_page.dart +++ b/example/lib/pages/gestures_page.dart @@ -18,7 +18,7 @@ class _GesturesPageState extends State { 'Movement': { InteractiveFlag.drag: 'Drag', InteractiveFlag.flingAnimation: 'Fling', - InteractiveFlag.twoFingerMove: 'Pinch', + InteractiveFlag.twoFingerMove: 'Two finger drag', }, 'Zooming': { InteractiveFlag.twoFingerZoom: 'Pinch', From 15697d7b390955c9aaa67a0cb3f12dbb0edc27ae Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Mon, 18 Dec 2023 08:53:26 +0100 Subject: [PATCH 071/102] remove `MoveAndRotateResult` https://github.com/fleaflet/flutter_map/pull/1733#discussion_r1428958757 --- lib/src/map/controller/map_controller.dart | 5 +- .../map/controller/map_controller_impl.dart | 95 +++++++++---------- .../map/gestures/map_interactive_viewer.dart | 11 --- 3 files changed, 45 insertions(+), 66 deletions(-) diff --git a/lib/src/map/controller/map_controller.dart b/lib/src/map/controller/map_controller.dart index ffa9a2931..442d21f50 100644 --- a/lib/src/map/controller/map_controller.dart +++ b/lib/src/map/controller/map_controller.dart @@ -3,7 +3,6 @@ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter_map/flutter_map.dart'; -import 'package:flutter_map/src/map/gestures/map_interactive_viewer.dart'; import 'package:flutter_map/src/map/inherited_model.dart'; import 'package:latlong2/latlong.dart'; @@ -105,7 +104,7 @@ abstract class MapController { /// /// The operation was successful if both fields of the resulting record are /// `true`. - MoveAndRotateResult rotateAroundPoint( + bool rotateAroundPoint( double degree, { Point? point, Offset? offset, @@ -121,7 +120,7 @@ abstract class MapController { /// /// The operation was successful if both fields of the resulting record are /// `true`. - MoveAndRotateResult moveAndRotate( + bool moveAndRotate( LatLng center, double zoom, double degree, { diff --git a/lib/src/map/controller/map_controller_impl.dart b/lib/src/map/controller/map_controller_impl.dart index a0896a01f..b890343bc 100644 --- a/lib/src/map/controller/map_controller_impl.dart +++ b/lib/src/map/controller/map_controller_impl.dart @@ -100,7 +100,7 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState> ); @override - MoveAndRotateResult rotateAroundPoint( + bool rotateAroundPoint( double degree, { Point? point, Offset? offset, @@ -116,7 +116,7 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState> ); @override - MoveAndRotateResult moveAndRotate( + bool moveAndRotate( LatLng center, double zoom, double degree, { @@ -223,7 +223,7 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState> return true; } - MoveAndRotateResult rotateAroundPointRaw( + bool rotateAroundPointRaw( double degree, { required Point? point, required Offset? offset, @@ -238,22 +238,14 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState> throw ArgumentError('One of `point` or `offset` must be non-null'); } - if (degree == camera.rotation) { - return const MoveAndRotateResult( - moveSuccess: false, - rotateSuccess: false, - ); - } + if (degree == camera.rotation) return false; if (offset == Offset.zero) { - return MoveAndRotateResult( - moveSuccess: true, - rotateSuccess: rotateRaw( - degree, - hasGesture: hasGesture, - source: source, - id: id, - ), + return rotateRaw( + degree, + hasGesture: hasGesture, + source: source, + id: id, ); } @@ -264,28 +256,27 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState> : Point(offset!.dx, offset.dy)) .rotate(camera.rotationRad); - return MoveAndRotateResult( - moveSuccess: moveRaw( - camera.unproject( - rotationCenter + - (camera.project(camera.center) - rotationCenter) - .rotate(degrees2Radians * rotationDiff), - ), - camera.zoom, - hasGesture: hasGesture, - source: source, - id: id, - ), - rotateSuccess: rotateRaw( - camera.rotation + rotationDiff, - hasGesture: hasGesture, - source: source, - id: id, + final moved = moveRaw( + camera.unproject( + rotationCenter + + (camera.project(camera.center) - rotationCenter) + .rotate(degrees2Radians * rotationDiff), ), + camera.zoom, + hasGesture: hasGesture, + source: source, + id: id, ); + final rotated = rotateRaw( + camera.rotation + rotationDiff, + hasGesture: hasGesture, + source: source, + id: id, + ); + return moved || rotated; } - MoveAndRotateResult moveAndRotateRaw( + bool moveAndRotateRaw( LatLng newCenter, double newZoom, double newRotation, { @@ -293,23 +284,23 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState> required bool hasGesture, required MapEventSource source, String? id, - }) => - MoveAndRotateResult( - moveSuccess: moveRaw( - newCenter, - newZoom, - offset: offset, - hasGesture: hasGesture, - source: source, - id: id, - ), - rotateSuccess: rotateRaw( - newRotation, - id: id, - source: source, - hasGesture: hasGesture, - ), - ); + }) { + final moved = moveRaw( + newCenter, + newZoom, + offset: offset, + hasGesture: hasGesture, + source: source, + id: id, + ); + final rotated = rotateRaw( + newRotation, + id: id, + source: source, + hasGesture: hasGesture, + ); + return moved || rotated; + } bool fitCameraRaw( CameraFit cameraFit, { diff --git a/lib/src/map/gestures/map_interactive_viewer.dart b/lib/src/map/gestures/map_interactive_viewer.dart index c67046ec4..479968a54 100644 --- a/lib/src/map/gestures/map_interactive_viewer.dart +++ b/lib/src/map/gestures/map_interactive_viewer.dart @@ -255,14 +255,3 @@ typedef ChildBuilder = Widget Function( MapOptions options, MapCamera camera, ); - -@immutable -class MoveAndRotateResult { - final bool moveSuccess; - final bool rotateSuccess; - - const MoveAndRotateResult({ - required this.moveSuccess, - required this.rotateSuccess, - }); -} From 5df6045961db6db698fd076b0e59cdb0fa9c8fee Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Mon, 18 Dec 2023 09:03:13 +0100 Subject: [PATCH 072/102] public default constructor for `EnabledGestures` https://github.com/fleaflet/flutter_map/pull/1733#discussion_r1429195441 --- lib/src/map/options/enabled_gestures.dart | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/src/map/options/enabled_gestures.dart b/lib/src/map/options/enabled_gestures.dart index ddd57d526..8126c638c 100644 --- a/lib/src/map/options/enabled_gestures.dart +++ b/lib/src/map/options/enabled_gestures.dart @@ -3,11 +3,14 @@ import 'package:meta/meta.dart'; @immutable class EnabledGestures { - /// Private constructor, use the constructors [InteractiveFlags.all] or - /// [InteractiveFlags.none] instead to enable or disable all gestures by default. + /// Use this constructor if you want to set all gestures manually. + /// + /// Prefer to use the constructors [InteractiveFlags.all] or + /// [InteractiveFlags.none] to enable or disable all gestures by default. + /// /// If you want to define your enabled gestures using bitfield operations, /// use [InteractiveFlags.bitfield] instead. - const EnabledGestures._({ + const EnabledGestures({ required this.drag, required this.flingAnimation, required this.twoFingerMove, @@ -63,7 +66,7 @@ class EnabledGestures { /// This constructor supports bitfield operations on the static fields /// from [InteractiveFlag]. factory EnabledGestures.bitfield(int flags) { - return EnabledGestures._( + return EnabledGestures( drag: InteractiveFlag.hasFlag(flags, InteractiveFlag.drag), flingAnimation: InteractiveFlag.hasFlag(flags, InteractiveFlag.flingAnimation), @@ -130,7 +133,7 @@ class EnabledGestures { bool? twoFingerRotate, bool? ctrlDragRotate, }) => - EnabledGestures._( + EnabledGestures( drag: drag ?? this.drag, flingAnimation: flingAnimation ?? this.flingAnimation, twoFingerZoom: twoFingerZoom ?? this.twoFingerZoom, @@ -146,7 +149,7 @@ class EnabledGestures { required bool move, required bool zoom, required bool rotate, - }) : this._( + }) : this( drag: move, twoFingerMove: move, flingAnimation: move, From 39faadc517b460635b8b45126f2950adeaa729cb Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Tue, 19 Dec 2023 13:20:32 +0100 Subject: [PATCH 073/102] use `byGroup` constructor for `noRotation` constructor --- lib/src/map/options/enabled_gestures.dart | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/src/map/options/enabled_gestures.dart b/lib/src/map/options/enabled_gestures.dart index 8126c638c..657b6e865 100644 --- a/lib/src/map/options/enabled_gestures.dart +++ b/lib/src/map/options/enabled_gestures.dart @@ -24,10 +24,7 @@ class EnabledGestures { /// Shortcut constructor to allow all gestures that don't rotate the map. const EnabledGestures.noRotation() - : this.all( - twoFingerRotate: false, - ctrlDragRotate: false, - ); + : this.byGroup(move: true, zoom: true, rotate: false); /// This constructor enables all gestures by default. Use this constructor if /// you want have all gestures enabled or disable some gestures only. From 00a0dc43078033bc83141002bc22289136a92aac Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Tue, 19 Dec 2023 13:25:23 +0100 Subject: [PATCH 074/102] use static field for default keyboard keys --- lib/src/map/options/interaction_options.dart | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/lib/src/map/options/interaction_options.dart b/lib/src/map/options/interaction_options.dart index bddc273ff..b223d7e1c 100644 --- a/lib/src/map/options/interaction_options.dart +++ b/lib/src/map/options/interaction_options.dart @@ -50,20 +50,23 @@ final class InteractionOptions { /// By default the left and right control key are both used. final List ctrlRotateKeys; + /// Default keys for the key press and drag to rotate gesture. + static const defaultCtrlRotateKey = [ + LogicalKeyboardKey.control, + LogicalKeyboardKey.controlLeft, + LogicalKeyboardKey.controlRight, + LogicalKeyboardKey.shift, + LogicalKeyboardKey.shiftLeft, + LogicalKeyboardKey.shiftRight, + ]; + const InteractionOptions({ this.enabledGestures = const EnabledGestures.all(), this.twoFingerRotateThreshold = 0.1, this.twoFingerZoomThreshold = 0.01, this.twoFingerMoveThreshold = 3.0, this.scrollWheelVelocity = 0.01, - this.ctrlRotateKeys = const [ - LogicalKeyboardKey.control, - LogicalKeyboardKey.controlLeft, - LogicalKeyboardKey.controlRight, - LogicalKeyboardKey.shift, - LogicalKeyboardKey.shiftLeft, - LogicalKeyboardKey.shiftRight, - ], + this.ctrlRotateKeys = defaultCtrlRotateKey, }) : assert( twoFingerRotateThreshold >= 0.0, 'rotationThreshold needs to be a positive value', From 905abbd402a6e3b94488df75c5e32d425936a84a Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Mon, 15 Jan 2024 23:15:53 +0100 Subject: [PATCH 075/102] rename CtrlDragRotate to KeyTriggerDrag gesture https://github.com/fleaflet/flutter_map/pull/1733#discussion_r1429264628 --- example/lib/pages/gestures_page.dart | 2 +- .../controller/events/map_event_source.dart | 6 ++-- .../map/gestures/map_interactive_viewer.dart | 24 +++++++------- .../map/gestures/services/base_services.dart | 2 +- ...tate.dart => key_trigger_drag_rotate.dart} | 10 +++--- lib/src/map/options/enabled_gestures.dart | 32 ++++++++++--------- lib/src/map/options/interaction_options.dart | 14 ++++---- 7 files changed, 46 insertions(+), 44 deletions(-) rename lib/src/map/gestures/services/{ctrl_drag_rotate.dart => key_trigger_drag_rotate.dart} (72%) diff --git a/example/lib/pages/gestures_page.dart b/example/lib/pages/gestures_page.dart index 0af8910c3..e9031094c 100644 --- a/example/lib/pages/gestures_page.dart +++ b/example/lib/pages/gestures_page.dart @@ -28,7 +28,7 @@ class _GesturesPageState extends State { }, 'Rotation': { InteractiveFlag.twoFingerRotate: 'Twist', - InteractiveFlag.ctrlDragRotate: 'CTRL+Drag', + InteractiveFlag.keyTriggerDragRotate: 'CTRL+Drag', }, }; diff --git a/lib/src/map/controller/events/map_event_source.dart b/lib/src/map/controller/events/map_event_source.dart index b73491039..4506de24c 100644 --- a/lib/src/map/controller/events/map_event_source.dart +++ b/lib/src/map/controller/events/map_event_source.dart @@ -23,7 +23,7 @@ enum MapEventSource { tertiaryTap, tertiaryLongPress, secondaryLongPressed, - ctrlDragRotateStart, - ctrlDragRotateEnd, - ctrlDragRotate, + keyTriggerDragRotateStart, + keyTriggerDragRotateEnd, + keyTriggerDragRotate, } diff --git a/lib/src/map/gestures/map_interactive_viewer.dart b/lib/src/map/gestures/map_interactive_viewer.dart index 479968a54..2137ff889 100644 --- a/lib/src/map/gestures/map_interactive_viewer.dart +++ b/lib/src/map/gestures/map_interactive_viewer.dart @@ -30,7 +30,7 @@ class MapInteractiveViewerState extends State TwoFingerGesturesService? _twoFingerInput; DragGestureService? _drag; DoubleTapDragZoomGestureService? _doubleTapDragZoom; - CtrlDragRotateGestureService? _ctrlDragRotate; + KeyTriggerDragRotateGestureService? _keyTriggerDragRotate; MapCamera get _camera => widget.controller.camera; @@ -83,7 +83,7 @@ class MapInteractiveViewerState extends State Widget build(BuildContext context) { final useDoubleTapCallback = _doubleTap != null || _doubleTapDragZoom != null; - final useScaleCallback = _ctrlDragRotate != null || + final useScaleCallback = _keyTriggerDragRotate != null || _drag != null || _doubleTapDragZoom != null || _twoFingerInput != null; @@ -160,8 +160,8 @@ class MapInteractiveViewerState extends State // pan and scale, scale is a superset of the pan gesture onScaleStart: useScaleCallback ? (details) { - if (_ctrlDragRotate?.keyPressed ?? false) { - _ctrlDragRotate!.start(); + if (_keyTriggerDragRotate?.keyPressed ?? false) { + _keyTriggerDragRotate!.start(); } else if (_doubleTapDragZoom?.isActive ?? false) { _doubleTapDragZoom!.start(details); } else if (details.pointerCount == 1) { @@ -173,8 +173,8 @@ class MapInteractiveViewerState extends State : null, onScaleUpdate: useScaleCallback ? (details) { - if (_ctrlDragRotate?.keyPressed ?? false) { - _ctrlDragRotate!.update(details); + if (_keyTriggerDragRotate?.keyPressed ?? false) { + _keyTriggerDragRotate!.update(details); } else if (_doubleTapDragZoom?.isActive ?? false) { _doubleTapDragZoom!.update(details); } else if (details.pointerCount == 1) { @@ -186,8 +186,8 @@ class MapInteractiveViewerState extends State : null, onScaleEnd: useScaleCallback ? (details) { - if (_ctrlDragRotate?.keyPressed ?? false) { - _ctrlDragRotate!.end(); + if (_keyTriggerDragRotate?.keyPressed ?? false) { + _keyTriggerDragRotate!.end(); } else if (_doubleTapDragZoom?.isActive ?? false) { _doubleTapDragZoom!.isActive = false; _doubleTapDragZoom!.end(details); @@ -232,13 +232,13 @@ class MapInteractiveViewerState extends State _scrollWheelZoom = null; } - if (newFlags.ctrlDragRotate) { - _ctrlDragRotate = CtrlDragRotateGestureService( + if (newFlags.keyTriggerDragRotate) { + _keyTriggerDragRotate = KeyTriggerDragRotateGestureService( controller: widget.controller, - keys: _options.interactionOptions.ctrlRotateKeys, + keys: _options.interactionOptions.keyTriggerDragRotateKeys, ); } else { - _ctrlDragRotate = null; + _keyTriggerDragRotate = null; } if (newFlags.doubleTapDragZoom) { diff --git a/lib/src/map/gestures/services/base_services.dart b/lib/src/map/gestures/services/base_services.dart index 88a54a50a..bc1034868 100644 --- a/lib/src/map/gestures/services/base_services.dart +++ b/lib/src/map/gestures/services/base_services.dart @@ -6,10 +6,10 @@ import 'package:flutter/services.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:latlong2/latlong.dart'; -part 'ctrl_drag_rotate.dart'; part 'double_tap.dart'; part 'double_tap_drag_zoom.dart'; part 'drag.dart'; +part 'key_trigger_drag_rotate.dart'; part 'long_press.dart'; part 'scroll_wheel_zoom.dart'; part 'tap.dart'; diff --git a/lib/src/map/gestures/services/ctrl_drag_rotate.dart b/lib/src/map/gestures/services/key_trigger_drag_rotate.dart similarity index 72% rename from lib/src/map/gestures/services/ctrl_drag_rotate.dart rename to lib/src/map/gestures/services/key_trigger_drag_rotate.dart index 1580ff500..b931aec49 100644 --- a/lib/src/map/gestures/services/ctrl_drag_rotate.dart +++ b/lib/src/map/gestures/services/key_trigger_drag_rotate.dart @@ -1,10 +1,10 @@ part of 'base_services.dart'; -class CtrlDragRotateGestureService extends BaseGestureService { +class KeyTriggerDragRotateGestureService extends BaseGestureService { bool isActive = false; final List keys; - CtrlDragRotateGestureService({ + KeyTriggerDragRotateGestureService({ required super.controller, required this.keys, }); @@ -14,7 +14,7 @@ class CtrlDragRotateGestureService extends BaseGestureService { controller.emitMapEvent( MapEventRotateStart( camera: _camera, - source: MapEventSource.ctrlDragRotateStart, + source: MapEventSource.keyTriggerDragRotateStart, ), ); } @@ -23,7 +23,7 @@ class CtrlDragRotateGestureService extends BaseGestureService { controller.rotateRaw( _camera.rotation - (details.focalPointDelta.dy * 0.5), hasGesture: true, - source: MapEventSource.ctrlDragRotate, + source: MapEventSource.keyTriggerDragRotate, ); } @@ -31,7 +31,7 @@ class CtrlDragRotateGestureService extends BaseGestureService { controller.emitMapEvent( MapEventRotateEnd( camera: _camera, - source: MapEventSource.ctrlDragRotateEnd, + source: MapEventSource.keyTriggerDragRotateEnd, ), ); } diff --git a/lib/src/map/options/enabled_gestures.dart b/lib/src/map/options/enabled_gestures.dart index 657b6e865..54e8b89c4 100644 --- a/lib/src/map/options/enabled_gestures.dart +++ b/lib/src/map/options/enabled_gestures.dart @@ -19,7 +19,7 @@ class EnabledGestures { required this.doubleTapDragZoom, required this.scrollWheelZoom, required this.twoFingerRotate, - required this.ctrlDragRotate, + required this.keyTriggerDragRotate, }); /// Shortcut constructor to allow all gestures that don't rotate the map. @@ -40,7 +40,7 @@ class EnabledGestures { this.doubleTapDragZoom = true, this.scrollWheelZoom = true, this.twoFingerRotate = true, - this.ctrlDragRotate = true, + this.keyTriggerDragRotate = true, }); /// This constructor has no enabled gestures by default. Use this constructor @@ -57,7 +57,7 @@ class EnabledGestures { this.doubleTapDragZoom = false, this.scrollWheelZoom = false, this.twoFingerRotate = false, - this.ctrlDragRotate = false, + this.keyTriggerDragRotate = false, }); /// This constructor supports bitfield operations on the static fields @@ -79,8 +79,8 @@ class EnabledGestures { InteractiveFlag.hasFlag(flags, InteractiveFlag.scrollWheelZoom), twoFingerRotate: InteractiveFlag.hasFlag(flags, InteractiveFlag.twoFingerRotate), - ctrlDragRotate: - InteractiveFlag.hasFlag(flags, InteractiveFlag.ctrlDragRotate), + keyTriggerDragRotate: + InteractiveFlag.hasFlag(flags, InteractiveFlag.keyTriggerDragRotate), ); } @@ -110,9 +110,10 @@ class EnabledGestures { /// Enable zooming with a mouse scroll wheel final bool scrollWheelZoom; - /// Enable rotation by pressing the CTRL key and dragging with the cursor + /// Enable rotation by pressing the defined keyboard key (by default CTRL key) + /// and dragging with the cursor /// or finger. - final bool ctrlDragRotate; + final bool keyTriggerDragRotate; /// Returns true of any gesture with more than one finger is enabled. bool hasMultiFinger() => twoFingerMove || twoFingerZoom || twoFingerRotate; @@ -128,7 +129,7 @@ class EnabledGestures { bool? doubleTapDragZoom, bool? scrollWheelZoom, bool? twoFingerRotate, - bool? ctrlDragRotate, + bool? keyTriggerDragRotate, }) => EnabledGestures( drag: drag ?? this.drag, @@ -139,7 +140,7 @@ class EnabledGestures { doubleTapDragZoom: doubleTapDragZoom ?? this.doubleTapDragZoom, scrollWheelZoom: scrollWheelZoom ?? this.scrollWheelZoom, twoFingerRotate: twoFingerRotate ?? this.twoFingerRotate, - ctrlDragRotate: ctrlDragRotate ?? this.ctrlDragRotate, + keyTriggerDragRotate: keyTriggerDragRotate ?? this.keyTriggerDragRotate, ); const EnabledGestures.byGroup({ @@ -155,7 +156,7 @@ class EnabledGestures { scrollWheelZoom: zoom, twoFingerZoom: zoom, twoFingerRotate: rotate, - ctrlDragRotate: rotate, + keyTriggerDragRotate: rotate, ); @override @@ -171,7 +172,7 @@ class EnabledGestures { doubleTapDragZoom == other.doubleTapDragZoom && scrollWheelZoom == other.scrollWheelZoom && twoFingerRotate == other.twoFingerRotate && - ctrlDragRotate == other.ctrlDragRotate; + keyTriggerDragRotate == other.keyTriggerDragRotate; @override int get hashCode => Object.hash( @@ -183,7 +184,7 @@ class EnabledGestures { doubleTapDragZoom, scrollWheelZoom, twoFingerRotate, - ctrlDragRotate, + keyTriggerDragRotate, ); } @@ -212,7 +213,7 @@ abstract class InteractiveFlag { doubleTapDragZoom | scrollWheelZoom | twoFingerRotate | - ctrlDragRotate; + keyTriggerDragRotate; static const int none = 0; @@ -253,9 +254,10 @@ abstract class InteractiveFlag { @Deprecated('Renamed to twoFingerRotate') static const int rotate = twoFingerRotate; - /// Enable rotation by pressing the CTRL Key and drag the map with the cursor. + /// 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]. - static const int ctrlDragRotate = 1 << 8; + static const int keyTriggerDragRotate = 1 << 8; /// Returns `true` if [leftFlags] has at least one member in [rightFlags] /// (intersection) for example diff --git a/lib/src/map/options/interaction_options.dart b/lib/src/map/options/interaction_options.dart index b223d7e1c..ad460af39 100644 --- a/lib/src/map/options/interaction_options.dart +++ b/lib/src/map/options/interaction_options.dart @@ -45,13 +45,13 @@ final class InteractionOptions { /// Defaults to 0.01. final double scrollWheelVelocity; - /// Override this option if you want to use custom keys for the CTRL+drag - /// rotate gesture. + /// Override this option if you want to use custom keys for the key trigger + /// drag rotate gesture (aka CTRL+drag rotate gesture). /// By default the left and right control key are both used. - final List ctrlRotateKeys; + final List keyTriggerDragRotateKeys; /// Default keys for the key press and drag to rotate gesture. - static const defaultCtrlRotateKey = [ + static const defaultKeyTriggerDragRotateKeys = [ LogicalKeyboardKey.control, LogicalKeyboardKey.controlLeft, LogicalKeyboardKey.controlRight, @@ -66,7 +66,7 @@ final class InteractionOptions { this.twoFingerZoomThreshold = 0.01, this.twoFingerMoveThreshold = 3.0, this.scrollWheelVelocity = 0.01, - this.ctrlRotateKeys = defaultCtrlRotateKey, + this.keyTriggerDragRotateKeys = defaultKeyTriggerDragRotateKeys, }) : assert( twoFingerRotateThreshold >= 0.0, 'rotationThreshold needs to be a positive value', @@ -88,7 +88,7 @@ final class InteractionOptions { twoFingerRotateThreshold == other.twoFingerRotateThreshold && twoFingerZoomThreshold == other.twoFingerZoomThreshold && twoFingerMoveThreshold == other.twoFingerMoveThreshold && - ctrlRotateKeys == other.ctrlRotateKeys && + keyTriggerDragRotateKeys == other.keyTriggerDragRotateKeys && scrollWheelVelocity == other.scrollWheelVelocity); @override @@ -97,7 +97,7 @@ final class InteractionOptions { twoFingerRotateThreshold, twoFingerZoomThreshold, twoFingerMoveThreshold, - ctrlRotateKeys, + keyTriggerDragRotateKeys, scrollWheelVelocity, ); } From 6809dad296a42364428ee7670442da1de58a63cc Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Mon, 15 Jan 2024 23:34:17 +0100 Subject: [PATCH 076/102] rename `DelayedGestureService` to `BaseDetailsGestureService` https://github.com/fleaflet/flutter_map/pull/1733#discussion_r1452779863 --- lib/src/map/gestures/services/base_services.dart | 4 ++-- lib/src/map/gestures/services/double_tap.dart | 2 +- lib/src/map/gestures/services/tap.dart | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/src/map/gestures/services/base_services.dart b/lib/src/map/gestures/services/base_services.dart index bc1034868..10d12b30c 100644 --- a/lib/src/map/gestures/services/base_services.dart +++ b/lib/src/map/gestures/services/base_services.dart @@ -25,10 +25,10 @@ abstract class BaseGestureService { MapOptions get _options => controller.options; } -abstract class DelayedGestureService extends BaseGestureService { +abstract class BaseDetailsGestureService extends BaseGestureService { TapDownDetails? details; - DelayedGestureService({required super.controller}); + BaseDetailsGestureService({required super.controller}); void setDetails(TapDownDetails newDetails) => details = newDetails; diff --git a/lib/src/map/gestures/services/double_tap.dart b/lib/src/map/gestures/services/double_tap.dart index 3b20e4dd5..0c915d177 100644 --- a/lib/src/map/gestures/services/double_tap.dart +++ b/lib/src/map/gestures/services/double_tap.dart @@ -1,6 +1,6 @@ part of 'base_services.dart'; -class DoubleTapGestureService extends DelayedGestureService { +class DoubleTapGestureService extends BaseDetailsGestureService { DoubleTapGestureService({required super.controller}); /// A double tap gesture tap has been registered diff --git a/lib/src/map/gestures/services/tap.dart b/lib/src/map/gestures/services/tap.dart index 4cfdd816e..041aa3597 100644 --- a/lib/src/map/gestures/services/tap.dart +++ b/lib/src/map/gestures/services/tap.dart @@ -1,6 +1,6 @@ part of 'base_services.dart'; -class TapGestureService extends DelayedGestureService { +class TapGestureService extends BaseDetailsGestureService { TapGestureService({required super.controller}); /// A tap with a primary button has occurred. @@ -23,7 +23,7 @@ class TapGestureService extends DelayedGestureService { } } -class SecondaryTapGestureService extends DelayedGestureService { +class SecondaryTapGestureService extends BaseDetailsGestureService { SecondaryTapGestureService({required super.controller}); /// A tap with a secondary button has occurred. @@ -46,7 +46,7 @@ class SecondaryTapGestureService extends DelayedGestureService { } } -class TertiaryTapGestureService extends DelayedGestureService { +class TertiaryTapGestureService extends BaseDetailsGestureService { TertiaryTapGestureService({required super.controller}); /// A tertiary tap gesture has happen (e.g. click on the mouse scroll wheel) From 6d90dd3d7a6f516a3552ec9036392875484731e6 Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Tue, 16 Jan 2024 01:39:33 +0100 Subject: [PATCH 077/102] add docs --- .../map/controller/map_controller_impl.dart | 35 ++++++++++++++ .../map/gestures/map_interactive_viewer.dart | 10 ++++ .../map/gestures/services/base_services.dart | 5 ++ lib/src/map/gestures/services/double_tap.dart | 2 + .../services/double_tap_drag_zoom.dart | 5 ++ lib/src/map/gestures/services/drag.dart | 5 ++ .../services/key_trigger_drag_rotate.dart | 6 +++ lib/src/map/gestures/services/long_press.dart | 6 +++ .../gestures/services/scroll_wheel_zoom.dart | 5 +- lib/src/map/gestures/services/tap.dart | 5 ++ lib/src/map/gestures/services/two_finger.dart | 19 ++++++-- lib/src/map/options/map_options.dart | 48 +++++++++++++++++++ 12 files changed, 146 insertions(+), 5 deletions(-) diff --git a/lib/src/map/controller/map_controller_impl.dart b/lib/src/map/controller/map_controller_impl.dart index b890343bc..28d9f8522 100644 --- a/lib/src/map/controller/map_controller_impl.dart +++ b/lib/src/map/controller/map_controller_impl.dart @@ -25,6 +25,8 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState> late Offset _animationOffset; late Point _flingMapCenterStartPoint; + /// Create a new [MapController] instance. This constructor is only used + /// internally. MapControllerImpl({MapOptions? options, TickerProvider? vsync}) : super( _MapControllerState( @@ -49,12 +51,14 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState> @override Stream get mapEventStream => _mapEventStreamController.stream; + /// Get the [MapOptions] from the controller state MapOptions get options { return value.options ?? (throw Exception('You need to have the FlutterMap widget rendered at ' 'least once before using the MapController.')); } + /// Get the [MapCamera] from the controller state @override MapCamera get camera { return value.camera ?? @@ -62,6 +66,7 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState> 'least once before using the MapController.')); } + /// Get the [AnimationController] from the controller state AnimationController get _animationController { return value.animationController ?? (throw Exception('You need to have the FlutterMap widget rendered at ' @@ -75,6 +80,8 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState> // ignore: library_private_types_in_public_api set value(_MapControllerState value) => super.value = value; + /// Implemented method from the public [MapController.move] API. + /// Calls [moveRaw] with [MapEventSource.mapController] as event source. @override bool move( LatLng center, @@ -91,6 +98,8 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState> id: id, ); + /// Implemented method from the public [MapController.rotate] API. + /// Calls [rotateRaw] with [MapEventSource.mapController] as event source. @override bool rotate(double degree, {String? id}) => rotateRaw( degree, @@ -99,6 +108,9 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState> id: id, ); + /// Implemented method from the public [MapController.rotateAroundPoint] API. + /// Calls [rotateAroundPointRaw] with [MapEventSource.mapController] as + /// event source. @override bool rotateAroundPoint( double degree, { @@ -115,6 +127,9 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState> id: id, ); + /// Implemented method from the public [MapController.moveAndRotate] API. + /// Calls [moveAndRotateRaw] with [MapEventSource.mapController] as + /// event source. @override bool moveAndRotate( LatLng center, @@ -132,12 +147,15 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState> id: id, ); + /// Implemented method from the public [MapController.fitCamera] API. + /// Calls [fitCameraRaw] with [MapEventSource.mapController] as event source. @override bool fitCamera(CameraFit cameraFit) => fitCameraRaw( cameraFit, source: MapEventSource.mapController, ); + /// Internal method, allows access to every parameter. bool moveRaw( LatLng newCenter, double newZoom, { @@ -194,6 +212,7 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState> return true; } + /// Internal method, allows access to every parameter. bool rotateRaw( double newRotation, { required bool hasGesture, @@ -223,6 +242,7 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState> return true; } + /// Internal method, allows access to every parameter. bool rotateAroundPointRaw( double degree, { required Point? point, @@ -276,6 +296,8 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState> return moved || rotated; } + /// Internal method, allows access to every parameter. + /// Calls [moveRaw] and [rotateRaw]. bool moveAndRotateRaw( LatLng newCenter, double newZoom, @@ -302,6 +324,7 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState> return moved || rotated; } + /// Internal method, allows access to every parameter. bool fitCameraRaw( CameraFit cameraFit, { Offset offset = Offset.zero, @@ -330,6 +353,7 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState> return false; } + /// Update the [MapOptions] in the controller state. set options(MapOptions newOptions) { assert( newOptions != value.options, @@ -359,6 +383,7 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState> ); } + /// Update the [TickerProvider] for the animations in the controller state. set vsync(TickerProvider tickerProvider) { if (value.animationController == null) { value = _MapControllerState( @@ -387,6 +412,7 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState> ); } + /// Internal method, allows access to every parameter. void moveAndRotateAnimatedRaw( LatLng newCenter, double newZoom, @@ -433,6 +459,7 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState> _animationController.forward(from: 0); } + /// Internal method, allows access to every parameter. void rotateAnimatedRaw( double newRotation, { required Offset offset, @@ -460,6 +487,7 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState> _animationController.forward(from: 0); } + /// Internal method, stops all ongoing animations of the [MapControllerImpl]. void stopAnimationRaw({bool canceled = true}) { if (isAnimating) _animationController.stop(canceled: canceled); } @@ -473,6 +501,7 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState> _flingAnimation = null; } + /// Internal method, allows access to every parameter. void flingAnimatedRaw({ required double velocity, required Offset direction, @@ -511,6 +540,7 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState> ); } + /// Internal method, allows access to every parameter. void moveAnimatedRaw( LatLng newCenter, double newZoom, { @@ -542,11 +572,14 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState> _animationController.forward(from: 0); } + /// Emit an [MapEvent] to the event system. void emitMapEvent(MapEvent event) { options.onMapEvent?.call(event); _mapEventSink.add(event); } + /// Callback that gets called by the [AnimationController] and updates + /// the [MapCamera]. void _handleAnimation() { // fling animation if (_flingAnimation != null) { @@ -603,6 +636,7 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState> } } +/// The state for the [MapControllerImpl] [ValueNotifier]. @immutable class _MapControllerState { final MapCamera? camera; @@ -615,6 +649,7 @@ class _MapControllerState { required this.animationController, }); + /// Copy the [_MapControllerState] and set [MapCamera] to some new value. _MapControllerState withMapCamera(MapCamera camera) => _MapControllerState( options: options, camera: camera, diff --git a/lib/src/map/gestures/map_interactive_viewer.dart b/lib/src/map/gestures/map_interactive_viewer.dart index 2137ff889..65db95207 100644 --- a/lib/src/map/gestures/map_interactive_viewer.dart +++ b/lib/src/map/gestures/map_interactive_viewer.dart @@ -3,6 +3,8 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_map/src/map/gestures/services/base_services.dart'; +/// The [MapInteractiveViewer] widget contains the [GestureDetector] and +/// [Listener] to handle gesture inputs. class MapInteractiveViewer extends StatefulWidget { final ChildBuilder builder; final MapControllerImpl controller; @@ -17,6 +19,7 @@ class MapInteractiveViewer extends StatefulWidget { State createState() => MapInteractiveViewerState(); } +/// The state for the [MapInteractiveViewer] class MapInteractiveViewerState extends State with TickerProviderStateMixin { TapGestureService? _tap; @@ -38,6 +41,7 @@ class MapInteractiveViewerState extends State InteractionOptions get _interactionOptions => _options.interactionOptions; + /// Initialize all services for the enabled gestures and input callbacks. @override void initState() { super.initState(); @@ -69,16 +73,20 @@ class MapInteractiveViewerState extends State updateGestures(null, _interactionOptions.enabledGestures); } + /// Called when the widgets gets disposed, used to clean up Stream listeners @override void dispose() { widget.controller.removeListener(reload); super.dispose(); } + /// Calls [setState] on the [MapInteractiveViewer] widget to refresh the + /// widget. void reload() { if (mounted) setState(() {}); } + /// Widget build method @override Widget build(BuildContext context) { final useDoubleTapCallback = @@ -250,6 +258,8 @@ class MapInteractiveViewerState extends State } } +/// Build method for the child widget. Provides [MapOptions] and [MapCamera] +/// as parameters. typedef ChildBuilder = Widget Function( BuildContext context, MapOptions options, diff --git a/lib/src/map/gestures/services/base_services.dart b/lib/src/map/gestures/services/base_services.dart index 10d12b30c..259ab7580 100644 --- a/lib/src/map/gestures/services/base_services.dart +++ b/lib/src/map/gestures/services/base_services.dart @@ -15,16 +15,21 @@ part 'scroll_wheel_zoom.dart'; part 'tap.dart'; part 'two_finger.dart'; +/// Abstract base service class for every gesture service. abstract class BaseGestureService { final MapControllerImpl controller; const BaseGestureService({required this.controller}); + /// Getter to provide a short way to access the [MapCamera]. MapCamera get _camera => controller.camera; + /// Getter to provide a short way to access the [MapOptions]. MapOptions get _options => controller.options; } +/// Abstract base service that additionally stores [TapDownDetails] as it is +/// commonly used by the different kind of tap gestures. abstract class BaseDetailsGestureService extends BaseGestureService { TapDownDetails? details; diff --git a/lib/src/map/gestures/services/double_tap.dart b/lib/src/map/gestures/services/double_tap.dart index 0c915d177..2e3833bd8 100644 --- a/lib/src/map/gestures/services/double_tap.dart +++ b/lib/src/map/gestures/services/double_tap.dart @@ -1,5 +1,7 @@ part of 'base_services.dart'; +/// Service to handle double tap gestures to perform the +/// double-tap-zoom-in gesture. class DoubleTapGestureService extends BaseDetailsGestureService { DoubleTapGestureService({required super.controller}); diff --git a/lib/src/map/gestures/services/double_tap_drag_zoom.dart b/lib/src/map/gestures/services/double_tap_drag_zoom.dart index 47ec81d38..5f61e3cda 100644 --- a/lib/src/map/gestures/services/double_tap_drag_zoom.dart +++ b/lib/src/map/gestures/services/double_tap_drag_zoom.dart @@ -1,5 +1,7 @@ part of 'base_services.dart'; +/// Service to handle the double-tap and drag gesture to let the user zoom in +/// and out with a single finger / one hand. class DoubleTapDragZoomGestureService extends BaseGestureService { bool isActive = false; Offset? _focalLocalStart; @@ -7,6 +9,7 @@ class DoubleTapDragZoomGestureService extends BaseGestureService { DoubleTapDragZoomGestureService({required super.controller}); + /// Called when the gesture is started, stores important values. void start(ScaleStartDetails details) { controller.stopAnimationRaw(); _focalLocalStart = details.localFocalPoint; @@ -19,6 +22,7 @@ class DoubleTapDragZoomGestureService extends BaseGestureService { ); } + /// Called when the gesture receives an update, updates the [MapCamera]. void update(ScaleUpdateDetails details) { if (_focalLocalStart == null || _mapZoomStart == null) return; @@ -35,6 +39,7 @@ class DoubleTapDragZoomGestureService extends BaseGestureService { ); } + /// Called when the gesture ends, cleans up the previously stored values. void end(ScaleEndDetails details) { _mapZoomStart = null; _focalLocalStart = null; diff --git a/lib/src/map/gestures/services/drag.dart b/lib/src/map/gestures/services/drag.dart index c8ac7fead..f62c65684 100644 --- a/lib/src/map/gestures/services/drag.dart +++ b/lib/src/map/gestures/services/drag.dart @@ -1,5 +1,7 @@ part of 'base_services.dart'; +/// Service that handles drag gestures performed with one pointer +/// (like a finger or cursor). class DragGestureService extends BaseGestureService { Offset? _lastLocalFocal; Offset? _focalStartLocal; @@ -11,6 +13,7 @@ class DragGestureService extends BaseGestureService { bool get isActive => _lastLocalFocal != null; + /// Called when the gesture is started, stores important values. void start(ScaleStartDetails details) { controller.stopAnimationRaw(); _lastLocalFocal = details.localFocalPoint; @@ -23,6 +26,7 @@ class DragGestureService extends BaseGestureService { ); } + /// Called when the gesture receives an update, updates the [MapCamera]. void update(ScaleUpdateDetails details) { if (_lastLocalFocal == null) return; @@ -44,6 +48,7 @@ class DragGestureService extends BaseGestureService { _lastLocalFocal = details.localFocalPoint; } + /// Called when the gesture ends, cleans up the previously stored values. void end(ScaleEndDetails details) { controller.emitMapEvent( MapEventMoveEnd( 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 b931aec49..21801bab2 100644 --- a/lib/src/map/gestures/services/key_trigger_drag_rotate.dart +++ b/lib/src/map/gestures/services/key_trigger_drag_rotate.dart @@ -1,5 +1,7 @@ part of 'base_services.dart'; +/// Service to handle the key-trigger and drag gesture to rotate the map. This +/// is by default a CTRL + class KeyTriggerDragRotateGestureService extends BaseGestureService { bool isActive = false; final List keys; @@ -9,6 +11,7 @@ class KeyTriggerDragRotateGestureService extends BaseGestureService { required this.keys, }); + /// Called when the gesture is started, stores important values. void start() { controller.stopAnimationRaw(); controller.emitMapEvent( @@ -19,6 +22,7 @@ class KeyTriggerDragRotateGestureService extends BaseGestureService { ); } + /// Called when the gesture receives an update, updates the [MapCamera]. void update(ScaleUpdateDetails details) { controller.rotateRaw( _camera.rotation - (details.focalPointDelta.dy * 0.5), @@ -27,6 +31,7 @@ class KeyTriggerDragRotateGestureService extends BaseGestureService { ); } + /// Called when the gesture ends, cleans up the previously stored values. void end() { controller.emitMapEvent( MapEventRotateEnd( @@ -36,6 +41,7 @@ class KeyTriggerDragRotateGestureService extends BaseGestureService { ); } + /// 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; diff --git a/lib/src/map/gestures/services/long_press.dart b/lib/src/map/gestures/services/long_press.dart index 05ae7a81d..9b6ca8dda 100644 --- a/lib/src/map/gestures/services/long_press.dart +++ b/lib/src/map/gestures/services/long_press.dart @@ -1,5 +1,7 @@ part of 'base_services.dart'; +/// Service to handle long press gestures for the +/// [MapOptions.onLongPress] callback. class LongPressGestureService extends BaseGestureService { LongPressGestureService({required super.controller}); @@ -20,6 +22,8 @@ class LongPressGestureService extends BaseGestureService { } } +/// Service to handle secondary long press gestures for the +/// [MapOptions.onSecondaryLongPress] callback. class SecondaryLongPressGestureService extends BaseGestureService { SecondaryLongPressGestureService({required super.controller}); @@ -40,6 +44,8 @@ class SecondaryLongPressGestureService extends BaseGestureService { } } +/// Service to handle tertiary long press gestures for the +/// [MapOptions.onTertiaryLongPress] callback. class TertiaryLongPressGestureService extends BaseGestureService { TertiaryLongPressGestureService({required super.controller}); diff --git a/lib/src/map/gestures/services/scroll_wheel_zoom.dart b/lib/src/map/gestures/services/scroll_wheel_zoom.dart index 996e2a367..0326ea94e 100644 --- a/lib/src/map/gestures/services/scroll_wheel_zoom.dart +++ b/lib/src/map/gestures/services/scroll_wheel_zoom.dart @@ -1,12 +1,15 @@ part of 'base_services.dart'; +/// Service to the handle scroll wheel gesture to zoom the map in or out. class ScrollWheelZoomGestureService extends BaseGestureService { ScrollWheelZoomGestureService({required super.controller}); + /// Shortcut for the zoom velocity of the scroll wheel double get _scrollWheelVelocity => _options.interactionOptions.scrollWheelVelocity; - /// Handles mouse scroll events + /// Handles mouse scroll events, called by the [Listener] of + /// the [MapInteractiveViewer]. void submit(PointerScrollEvent details) { controller.stopAnimationRaw(); if (details.scrollDelta.dy == 0) return; diff --git a/lib/src/map/gestures/services/tap.dart b/lib/src/map/gestures/services/tap.dart index 041aa3597..7747b88aa 100644 --- a/lib/src/map/gestures/services/tap.dart +++ b/lib/src/map/gestures/services/tap.dart @@ -1,5 +1,6 @@ part of 'base_services.dart'; +/// Service to handle tap gestures for the [MapOptions.onTap] callback. class TapGestureService extends BaseDetailsGestureService { TapGestureService({required super.controller}); @@ -23,6 +24,8 @@ class TapGestureService extends BaseDetailsGestureService { } } +/// Service to handle secondary tap gestures for the +/// [MapOptions.onSecondaryTap] callback. class SecondaryTapGestureService extends BaseDetailsGestureService { SecondaryTapGestureService({required super.controller}); @@ -46,6 +49,8 @@ class SecondaryTapGestureService extends BaseDetailsGestureService { } } +/// Service to handle tertiary tap gestures for the +/// [MapOptions.onTertiaryTap] callback. class TertiaryTapGestureService extends BaseDetailsGestureService { TertiaryTapGestureService({required super.controller}); diff --git a/lib/src/map/gestures/services/two_finger.dart b/lib/src/map/gestures/services/two_finger.dart index a62626311..bf167c0bd 100644 --- a/lib/src/map/gestures/services/two_finger.dart +++ b/lib/src/map/gestures/services/two_finger.dart @@ -1,6 +1,9 @@ part of 'base_services.dart'; -/// A gesture with multiple inputs like zooming with two fingers +/// A gesture with multiple inputs. This service handles the following gestures: +/// - [EnabledGestures.twoFingerMove] +/// - [EnabledGestures.twoFingerZoom] +/// - [EnabledGestures.twoFingerRotate] class TwoFingerGesturesService extends BaseGestureService { MapCamera? _startCamera; LatLng? _startFocalLatLng; @@ -15,12 +18,18 @@ class TwoFingerGesturesService extends BaseGestureService { bool _moving = false; bool _rotating = false; + /// Getter as shortcut to check if [EnabledGestures.twoFingerMove] + /// is enabled. bool get _moveEnabled => _options.interactionOptions.enabledGestures.twoFingerMove; + /// Getter as shortcut to check if [EnabledGestures.twoFingerRotate] + /// is enabled. bool get _rotateEnabled => _options.interactionOptions.enabledGestures.twoFingerRotate; + /// Getter as shortcut to check if [EnabledGestures.twoFingerZoom] + /// is enabled. bool get _zoomEnabled => _options.interactionOptions.enabledGestures.twoFingerZoom; @@ -35,7 +44,8 @@ class TwoFingerGesturesService extends BaseGestureService { TwoFingerGesturesService({required super.controller}); - /// Initialize gesture, called when gesture has started + /// Initialize gesture, called when gesture has started. + /// Stores all values, that are required later on. void start(ScaleStartDetails details) { controller.stopAnimationRaw(); if (details.pointerCount < 2) return; @@ -61,7 +71,8 @@ class TwoFingerGesturesService extends BaseGestureService { ); } - /// Called multiple times to handle updates to the gesture + /// Called multiple times to handle updates to the gesture. + /// Updates the [MapCamera]. void update(ScaleUpdateDetails details) { if (details.pointerCount < 2) return; if (_lastLocalFocal == null || @@ -151,7 +162,7 @@ class TwoFingerGesturesService extends BaseGestureService { _lastLocalFocal = details.localFocalPoint; } - /// gesture has ended, clean up + /// gesture has ended, clean up the previously stored values. void end(ScaleEndDetails details) { if (details.pointerCount < 2) return; _startCamera = null; diff --git a/lib/src/map/options/map_options.dart b/lib/src/map/options/map_options.dart index 61ef78513..5ba291fff 100644 --- a/lib/src/map/options/map_options.dart +++ b/lib/src/map/options/map_options.dart @@ -30,20 +30,63 @@ class MapOptions { final Color backgroundColor; + /// Callback that gets called when the user has performed a confirmed single + /// tap or click on the map. If double tap gestures are enabled in + /// [InteractionOptions.enabledGestures], the callback waits until the + /// double-tap delay has passed by and the tap gesture is confirmed. final TapCallback? onTap; + + /// Callback that gets called when the user has performed a confirmed + /// long press on the map. final LongPressCallback? onLongPress; + + /// Callback that gets called when the user has performed a confirmed + /// single secondary tap or click on the map. This is for example when the + /// user clicks with the right mouse button. final TapCallback? onSecondaryTap; + + /// Callback that gets called when the user has performed a confirmed + /// long press on the map with the secondary pointer. final LongPressCallback? onSecondaryLongPress; + + /// Callback that gets called when the user has performed a confirmed + /// tap or click with the tertiary pointer. This is for example by clicking + /// on the scroll wheel of the mouse. final TapCallback? onTertiaryTap; + + /// Callback that gets called when the user has performed a confirmed + /// long press using the tertiary pointer. This is for example by + /// long pressing the scroll wheel of the mouse. final LongPressCallback? onTertiaryLongPress; + /// Callback that gets called when internal + /// [Listener.onPointerDown] callback fires. Useful for custom or advanced + /// gesture handling. final void Function(PointerDownEvent event, LatLng point)? onPointerDown; + + /// Callback that gets called when internal + /// [Listener.onPointerUp] callback fires. Useful for custom or advanced + /// gesture handling. final void Function(PointerUpEvent event, LatLng point)? onPointerUp; + + /// Callback that gets called when internal + /// [Listener.onPointerCancel] callback fires. Useful for custom or advanced + /// gesture handling. final void Function(PointerCancelEvent event, LatLng point)? onPointerCancel; + + /// Callback that gets called when internal + /// [Listener.onPointerHover] callback fires. Useful for custom or advanced + /// gesture handling. final void Function(PointerHoverEvent event, LatLng point)? onPointerHover; + /// Callback that gets called when the [MapCamera] changes position. final PositionCallback? onPositionChanged; + /// Callback to listen for events emitted by the FlutterMap event system. + /// Every event is a subclass of [MapEvent]. Check its type to filter + /// for a specific event. + /// + /// Events for gestures are only emitted if the respective gesture is enabled. final void Function(MapEvent event)? onMapEvent; /// Define limits for viewing the map. @@ -83,6 +126,8 @@ class MapOptions { final InteractionOptions interactionOptions; + /// Constructor for the [MapOptions]. Set custom options or override + /// default values. const MapOptions({ this.crs = const Epsg3857(), this.initialCenter = const LatLng(50.5, 30.51), @@ -179,7 +224,10 @@ class MapOptions { ]); } +/// Callback function signature used by short taps typedef TapCallback = void Function(TapDownDetails details, LatLng point); + +/// Callback function signature used by long presses typedef LongPressCallback = void Function( LongPressStartDetails details, LatLng point, From 8a4995b6f2efe66c78529fff8230b75ebb3e4968 Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Wed, 17 Jan 2024 22:46:13 +0100 Subject: [PATCH 078/102] rename BaseDetailsGestureService to SingleShotGestureService --- lib/src/map/gestures/services/base_services.dart | 6 +++--- lib/src/map/gestures/services/double_tap.dart | 2 +- lib/src/map/gestures/services/tap.dart | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/src/map/gestures/services/base_services.dart b/lib/src/map/gestures/services/base_services.dart index 259ab7580..918bc58dd 100644 --- a/lib/src/map/gestures/services/base_services.dart +++ b/lib/src/map/gestures/services/base_services.dart @@ -1,6 +1,6 @@ import 'dart:math' as math; -import 'package:flutter/animation.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/services.dart'; import 'package:flutter_map/flutter_map.dart'; @@ -30,10 +30,10 @@ abstract class BaseGestureService { /// Abstract base service that additionally stores [TapDownDetails] as it is /// commonly used by the different kind of tap gestures. -abstract class BaseDetailsGestureService extends BaseGestureService { +abstract class SingleShotGestureService extends BaseGestureService { TapDownDetails? details; - BaseDetailsGestureService({required super.controller}); + SingleShotGestureService({required super.controller}); void setDetails(TapDownDetails newDetails) => details = newDetails; diff --git a/lib/src/map/gestures/services/double_tap.dart b/lib/src/map/gestures/services/double_tap.dart index 2e3833bd8..fdfcab96d 100644 --- a/lib/src/map/gestures/services/double_tap.dart +++ b/lib/src/map/gestures/services/double_tap.dart @@ -2,7 +2,7 @@ part of 'base_services.dart'; /// Service to handle double tap gestures to perform the /// double-tap-zoom-in gesture. -class DoubleTapGestureService extends BaseDetailsGestureService { +class DoubleTapGestureService extends SingleShotGestureService { DoubleTapGestureService({required super.controller}); /// A double tap gesture tap has been registered diff --git a/lib/src/map/gestures/services/tap.dart b/lib/src/map/gestures/services/tap.dart index 7747b88aa..bc251dde7 100644 --- a/lib/src/map/gestures/services/tap.dart +++ b/lib/src/map/gestures/services/tap.dart @@ -1,7 +1,7 @@ part of 'base_services.dart'; /// Service to handle tap gestures for the [MapOptions.onTap] callback. -class TapGestureService extends BaseDetailsGestureService { +class TapGestureService extends SingleShotGestureService { TapGestureService({required super.controller}); /// A tap with a primary button has occurred. @@ -26,7 +26,7 @@ class TapGestureService extends BaseDetailsGestureService { /// Service to handle secondary tap gestures for the /// [MapOptions.onSecondaryTap] callback. -class SecondaryTapGestureService extends BaseDetailsGestureService { +class SecondaryTapGestureService extends SingleShotGestureService { SecondaryTapGestureService({required super.controller}); /// A tap with a secondary button has occurred. @@ -51,7 +51,7 @@ class SecondaryTapGestureService extends BaseDetailsGestureService { /// Service to handle tertiary tap gestures for the /// [MapOptions.onTertiaryTap] callback. -class TertiaryTapGestureService extends BaseDetailsGestureService { +class TertiaryTapGestureService extends SingleShotGestureService { TertiaryTapGestureService({required super.controller}); /// A tertiary tap gesture has happen (e.g. click on the mouse scroll wheel) From ca813579345f71ff42f5eab9420ef02ee858d27e Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Wed, 17 Jan 2024 22:53:06 +0100 Subject: [PATCH 079/102] introduce SetTapDownDetailsMixin --- .../map/gestures/map_interactive_viewer.dart | 3 ++- .../map/gestures/services/base_services.dart | 16 +++++++++++++-- lib/src/map/gestures/services/double_tap.dart | 6 ++++-- lib/src/map/gestures/services/tap.dart | 20 ++++++++++++------- 4 files changed, 33 insertions(+), 12 deletions(-) diff --git a/lib/src/map/gestures/map_interactive_viewer.dart b/lib/src/map/gestures/map_interactive_viewer.dart index 65db95207..f36085f68 100644 --- a/lib/src/map/gestures/map_interactive_viewer.dart +++ b/lib/src/map/gestures/map_interactive_viewer.dart @@ -161,7 +161,8 @@ class MapInteractiveViewerState extends State onTertiaryTapDown: _tertiaryTap?.setDetails, onTertiaryTapCancel: _tertiaryTap?.reset, - onTertiaryTapUp: _tertiaryTap?.submit, + onTertiaryTapUp: + _tertiaryTap == null ? null : (_) => _tertiaryTap?.submit(), onTertiaryLongPressStart: _tertiaryLongPress?.submit, diff --git a/lib/src/map/gestures/services/base_services.dart b/lib/src/map/gestures/services/base_services.dart index 918bc58dd..688a1fdfd 100644 --- a/lib/src/map/gestures/services/base_services.dart +++ b/lib/src/map/gestures/services/base_services.dart @@ -5,6 +5,7 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/services.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:latlong2/latlong.dart'; +import 'package:meta/meta.dart'; part 'double_tap.dart'; part 'double_tap_drag_zoom.dart'; @@ -31,10 +32,21 @@ abstract class BaseGestureService { /// Abstract base service that additionally stores [TapDownDetails] as it is /// commonly used by the different kind of tap gestures. abstract class SingleShotGestureService extends BaseGestureService { - TapDownDetails? details; - SingleShotGestureService({required super.controller}); + @mustCallSuper + @mustBeOverridden + void submit() { + controller.stopAnimationRaw(); + } +} + +/// mixin that adds the [setDetails] method to a GestureService. Is used if the +/// [TapDownDetails] are set at a different time of when the gesture gets +/// confirmed and submitted. +mixin SetTapDownDetailsMixin on BaseGestureService { + TapDownDetails? details; + void setDetails(TapDownDetails newDetails) => details = newDetails; void reset() => details = null; diff --git a/lib/src/map/gestures/services/double_tap.dart b/lib/src/map/gestures/services/double_tap.dart index fdfcab96d..2cc85d2d0 100644 --- a/lib/src/map/gestures/services/double_tap.dart +++ b/lib/src/map/gestures/services/double_tap.dart @@ -2,12 +2,14 @@ part of 'base_services.dart'; /// Service to handle double tap gestures to perform the /// double-tap-zoom-in gesture. -class DoubleTapGestureService extends SingleShotGestureService { +class DoubleTapGestureService extends SingleShotGestureService + with SetTapDownDetailsMixin { DoubleTapGestureService({required super.controller}); /// A double tap gesture tap has been registered + @override void submit() { - controller.stopAnimationRaw(); + super.submit(); if (details == null) return; // start double tap animation diff --git a/lib/src/map/gestures/services/tap.dart b/lib/src/map/gestures/services/tap.dart index bc251dde7..20ec6ea67 100644 --- a/lib/src/map/gestures/services/tap.dart +++ b/lib/src/map/gestures/services/tap.dart @@ -1,13 +1,15 @@ part of 'base_services.dart'; /// Service to handle tap gestures for the [MapOptions.onTap] callback. -class TapGestureService extends SingleShotGestureService { +class TapGestureService extends SingleShotGestureService + with SetTapDownDetailsMixin { TapGestureService({required super.controller}); /// A tap with a primary button has occurred. /// This triggers when the tap gesture wins. + @override void submit() { - controller.stopAnimationRaw(); + super.submit(); if (details == null) return; final point = _camera.offsetToCrs(details!.localPosition); @@ -26,13 +28,15 @@ class TapGestureService extends SingleShotGestureService { /// Service to handle secondary tap gestures for the /// [MapOptions.onSecondaryTap] callback. -class SecondaryTapGestureService extends SingleShotGestureService { +class SecondaryTapGestureService extends SingleShotGestureService + with SetTapDownDetailsMixin { SecondaryTapGestureService({required super.controller}); /// A tap with a secondary button has occurred. /// This triggers when the tap gesture wins. + @override void submit() { - controller.stopAnimationRaw(); + super.submit(); if (details == null) return; final position = _camera.offsetToCrs(details!.localPosition); @@ -51,12 +55,14 @@ class SecondaryTapGestureService extends SingleShotGestureService { /// Service to handle tertiary tap gestures for the /// [MapOptions.onTertiaryTap] callback. -class TertiaryTapGestureService extends SingleShotGestureService { +class TertiaryTapGestureService extends SingleShotGestureService + with SetTapDownDetailsMixin { TertiaryTapGestureService({required super.controller}); /// A tertiary tap gesture has happen (e.g. click on the mouse scroll wheel) - void submit(TapUpDetails _) { - controller.stopAnimationRaw(); + @override + void submit() { + super.submit(); if (details == null) return; final point = _camera.offsetToCrs(details!.localPosition); From 64d68921198d016ad5ac37f1fdb33a70a9d43eac Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Wed, 17 Jan 2024 23:27:38 +0100 Subject: [PATCH 080/102] add ProgressableGestureService , BaseLongPressGestureService, remove SetTapDownDetailsMixin --- .../map/gestures/services/base_services.dart | 53 ++++++++++++++----- lib/src/map/gestures/services/double_tap.dart | 3 +- .../services/double_tap_drag_zoom.dart | 7 ++- lib/src/map/gestures/services/drag.dart | 7 ++- .../services/key_trigger_drag_rotate.dart | 7 ++- lib/src/map/gestures/services/long_press.dart | 14 +++-- .../gestures/services/scroll_wheel_zoom.dart | 2 +- lib/src/map/gestures/services/tap.dart | 9 ++-- lib/src/map/gestures/services/two_finger.dart | 7 ++- 9 files changed, 74 insertions(+), 35 deletions(-) diff --git a/lib/src/map/gestures/services/base_services.dart b/lib/src/map/gestures/services/base_services.dart index 688a1fdfd..f5ffc17a2 100644 --- a/lib/src/map/gestures/services/base_services.dart +++ b/lib/src/map/gestures/services/base_services.dart @@ -17,10 +17,10 @@ part 'tap.dart'; part 'two_finger.dart'; /// Abstract base service class for every gesture service. -abstract class BaseGestureService { +abstract class _BaseGestureService { final MapControllerImpl controller; - const BaseGestureService({required this.controller}); + const _BaseGestureService({required this.controller}); /// Getter to provide a short way to access the [MapCamera]. MapCamera get _camera => controller.camera; @@ -29,27 +29,54 @@ abstract class BaseGestureService { MapOptions get _options => controller.options; } -/// Abstract base service that additionally stores [TapDownDetails] as it is -/// commonly used by the different kind of tap gestures. -abstract class SingleShotGestureService extends BaseGestureService { - SingleShotGestureService({required super.controller}); +/// Abstract base service for a gesture that fires only one time. +/// Commonly used by the different kind of tap gestures. +abstract class _SingleShotGestureService extends _BaseGestureService { + _SingleShotGestureService({required super.controller}); + TapDownDetails? details; + + void setDetails(TapDownDetails newDetails) => details = newDetails; + + /// Called when the gesture fires and is confirmed. @mustCallSuper @mustBeOverridden void submit() { controller.stopAnimationRaw(); } + + void reset() => details = null; } -/// mixin that adds the [setDetails] method to a GestureService. Is used if the -/// [TapDownDetails] are set at a different time of when the gesture gets -/// confirmed and submitted. -mixin SetTapDownDetailsMixin on BaseGestureService { - TapDownDetails? details; +/// Abstract base service for a long-press gesture that receives a +/// [LongPressStartDetails] when called. +abstract class _BaseLongPressGestureService extends _BaseGestureService { + _BaseLongPressGestureService({required super.controller}); - void setDetails(TapDownDetails newDetails) => details = newDetails; + /// Called when the gesture fires and is confirmed. + @mustCallSuper + @mustBeOverridden + void submit(LongPressStartDetails details) { + controller.stopAnimationRaw(); + } +} - void reset() => details = null; +/// Abstract base service for a gesture that fires multiple times time. +abstract class _ProgressableGestureService extends _BaseGestureService { + _ProgressableGestureService({required super.controller}); + + /// Called when the gesture is started, stores important values. + @mustCallSuper + @mustBeOverridden + void start(ScaleStartDetails details) { + controller.stopAnimationRaw(); + } + + /// Called when the gesture receives an update, updates the [MapCamera]. + void update(ScaleUpdateDetails details); + + /// Called when the gesture ends, cleans up the previously stored values. + void end(ScaleEndDetails details); } /// Return a rotated Offset diff --git a/lib/src/map/gestures/services/double_tap.dart b/lib/src/map/gestures/services/double_tap.dart index 2cc85d2d0..fe04341fd 100644 --- a/lib/src/map/gestures/services/double_tap.dart +++ b/lib/src/map/gestures/services/double_tap.dart @@ -2,8 +2,7 @@ part of 'base_services.dart'; /// Service to handle double tap gestures to perform the /// double-tap-zoom-in gesture. -class DoubleTapGestureService extends SingleShotGestureService - with SetTapDownDetailsMixin { +class DoubleTapGestureService extends _SingleShotGestureService { DoubleTapGestureService({required super.controller}); /// A double tap gesture tap has been registered diff --git a/lib/src/map/gestures/services/double_tap_drag_zoom.dart b/lib/src/map/gestures/services/double_tap_drag_zoom.dart index 5f61e3cda..09772d935 100644 --- a/lib/src/map/gestures/services/double_tap_drag_zoom.dart +++ b/lib/src/map/gestures/services/double_tap_drag_zoom.dart @@ -2,7 +2,7 @@ part of 'base_services.dart'; /// Service to handle the double-tap and drag gesture to let the user zoom in /// and out with a single finger / one hand. -class DoubleTapDragZoomGestureService extends BaseGestureService { +class DoubleTapDragZoomGestureService extends _ProgressableGestureService { bool isActive = false; Offset? _focalLocalStart; double? _mapZoomStart; @@ -10,8 +10,9 @@ class DoubleTapDragZoomGestureService extends BaseGestureService { DoubleTapDragZoomGestureService({required super.controller}); /// Called when the gesture is started, stores important values. + @override void start(ScaleStartDetails details) { - controller.stopAnimationRaw(); + super.start(details); _focalLocalStart = details.localFocalPoint; _mapZoomStart = _camera.zoom; controller.emitMapEvent( @@ -23,6 +24,7 @@ class DoubleTapDragZoomGestureService extends BaseGestureService { } /// Called when the gesture receives an update, updates the [MapCamera]. + @override void update(ScaleUpdateDetails details) { if (_focalLocalStart == null || _mapZoomStart == null) return; @@ -40,6 +42,7 @@ class DoubleTapDragZoomGestureService extends BaseGestureService { } /// Called when the gesture ends, cleans up the previously stored values. + @override void end(ScaleEndDetails details) { _mapZoomStart = null; _focalLocalStart = null; diff --git a/lib/src/map/gestures/services/drag.dart b/lib/src/map/gestures/services/drag.dart index f62c65684..4fbb118ec 100644 --- a/lib/src/map/gestures/services/drag.dart +++ b/lib/src/map/gestures/services/drag.dart @@ -2,7 +2,7 @@ part of 'base_services.dart'; /// Service that handles drag gestures performed with one pointer /// (like a finger or cursor). -class DragGestureService extends BaseGestureService { +class DragGestureService extends _ProgressableGestureService { Offset? _lastLocalFocal; Offset? _focalStartLocal; @@ -14,8 +14,9 @@ class DragGestureService extends BaseGestureService { bool get isActive => _lastLocalFocal != null; /// Called when the gesture is started, stores important values. + @override void start(ScaleStartDetails details) { - controller.stopAnimationRaw(); + super.start(details); _lastLocalFocal = details.localFocalPoint; _focalStartLocal = details.localFocalPoint; controller.emitMapEvent( @@ -27,6 +28,7 @@ class DragGestureService extends BaseGestureService { } /// Called when the gesture receives an update, updates the [MapCamera]. + @override void update(ScaleUpdateDetails details) { if (_lastLocalFocal == null) return; @@ -49,6 +51,7 @@ class DragGestureService extends BaseGestureService { } /// Called when the gesture ends, cleans up the previously stored values. + @override void end(ScaleEndDetails details) { controller.emitMapEvent( MapEventMoveEnd( 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 21801bab2..88e67439f 100644 --- a/lib/src/map/gestures/services/key_trigger_drag_rotate.dart +++ b/lib/src/map/gestures/services/key_trigger_drag_rotate.dart @@ -1,8 +1,11 @@ part of 'base_services.dart'; /// Service to handle the key-trigger and drag gesture to rotate the map. This -/// is by default a CTRL + -class KeyTriggerDragRotateGestureService extends BaseGestureService { +/// is by default a CTRL +. +/// +/// Can't extend from [_ProgressableGestureService] because of different +/// method signatures. +class KeyTriggerDragRotateGestureService extends _BaseGestureService { bool isActive = false; final List keys; diff --git a/lib/src/map/gestures/services/long_press.dart b/lib/src/map/gestures/services/long_press.dart index 9b6ca8dda..e93176939 100644 --- a/lib/src/map/gestures/services/long_press.dart +++ b/lib/src/map/gestures/services/long_press.dart @@ -2,14 +2,15 @@ part of 'base_services.dart'; /// Service to handle long press gestures for the /// [MapOptions.onLongPress] callback. -class LongPressGestureService extends BaseGestureService { +class LongPressGestureService extends _BaseLongPressGestureService { LongPressGestureService({required super.controller}); /// Called when a long press gesture with a primary button has been /// recognized. A pointer has remained in contact with the screen at the /// same location for a long period of time. + @override void submit(LongPressStartDetails details) { - controller.stopAnimationRaw(); + super.submit(details); final position = _camera.offsetToCrs(details.localPosition); _options.onLongPress?.call(details, position); controller.emitMapEvent( @@ -24,14 +25,15 @@ class LongPressGestureService extends BaseGestureService { /// Service to handle secondary long press gestures for the /// [MapOptions.onSecondaryLongPress] callback. -class SecondaryLongPressGestureService extends BaseGestureService { +class SecondaryLongPressGestureService extends _BaseLongPressGestureService { SecondaryLongPressGestureService({required super.controller}); /// Called when a long press gesture with a primary button has been /// recognized. A pointer has remained in contact with the screen at the /// same location for a long period of time. + @override void submit(LongPressStartDetails details) { - controller.stopAnimationRaw(); + super.submit(details); final position = _camera.offsetToCrs(details.localPosition); _options.onSecondaryLongPress?.call(details, position); controller.emitMapEvent( @@ -46,12 +48,14 @@ class SecondaryLongPressGestureService extends BaseGestureService { /// Service to handle tertiary long press gestures for the /// [MapOptions.onTertiaryLongPress] callback. -class TertiaryLongPressGestureService extends BaseGestureService { +class TertiaryLongPressGestureService extends _BaseLongPressGestureService { TertiaryLongPressGestureService({required super.controller}); /// A long press on the tertiary button has happen (e.g. click and hold on /// the mouse scroll wheel) + @override void submit(LongPressStartDetails details) { + super.submit(details); controller.stopAnimationRaw(); final point = _camera.offsetToCrs(details.localPosition); _options.onTertiaryLongPress?.call(details, point); diff --git a/lib/src/map/gestures/services/scroll_wheel_zoom.dart b/lib/src/map/gestures/services/scroll_wheel_zoom.dart index 0326ea94e..66b760903 100644 --- a/lib/src/map/gestures/services/scroll_wheel_zoom.dart +++ b/lib/src/map/gestures/services/scroll_wheel_zoom.dart @@ -1,7 +1,7 @@ part of 'base_services.dart'; /// Service to the handle scroll wheel gesture to zoom the map in or out. -class ScrollWheelZoomGestureService extends BaseGestureService { +class ScrollWheelZoomGestureService extends _BaseGestureService { ScrollWheelZoomGestureService({required super.controller}); /// Shortcut for the zoom velocity of the scroll wheel diff --git a/lib/src/map/gestures/services/tap.dart b/lib/src/map/gestures/services/tap.dart index 20ec6ea67..2d1060333 100644 --- a/lib/src/map/gestures/services/tap.dart +++ b/lib/src/map/gestures/services/tap.dart @@ -1,8 +1,7 @@ part of 'base_services.dart'; /// Service to handle tap gestures for the [MapOptions.onTap] callback. -class TapGestureService extends SingleShotGestureService - with SetTapDownDetailsMixin { +class TapGestureService extends _SingleShotGestureService { TapGestureService({required super.controller}); /// A tap with a primary button has occurred. @@ -28,8 +27,7 @@ class TapGestureService extends SingleShotGestureService /// Service to handle secondary tap gestures for the /// [MapOptions.onSecondaryTap] callback. -class SecondaryTapGestureService extends SingleShotGestureService - with SetTapDownDetailsMixin { +class SecondaryTapGestureService extends _SingleShotGestureService { SecondaryTapGestureService({required super.controller}); /// A tap with a secondary button has occurred. @@ -55,8 +53,7 @@ class SecondaryTapGestureService extends SingleShotGestureService /// Service to handle tertiary tap gestures for the /// [MapOptions.onTertiaryTap] callback. -class TertiaryTapGestureService extends SingleShotGestureService - with SetTapDownDetailsMixin { +class TertiaryTapGestureService extends _SingleShotGestureService { TertiaryTapGestureService({required super.controller}); /// A tertiary tap gesture has happen (e.g. click on the mouse scroll wheel) diff --git a/lib/src/map/gestures/services/two_finger.dart b/lib/src/map/gestures/services/two_finger.dart index bf167c0bd..5ccd7bf18 100644 --- a/lib/src/map/gestures/services/two_finger.dart +++ b/lib/src/map/gestures/services/two_finger.dart @@ -4,7 +4,7 @@ part of 'base_services.dart'; /// - [EnabledGestures.twoFingerMove] /// - [EnabledGestures.twoFingerZoom] /// - [EnabledGestures.twoFingerRotate] -class TwoFingerGesturesService extends BaseGestureService { +class TwoFingerGesturesService extends _ProgressableGestureService { MapCamera? _startCamera; LatLng? _startFocalLatLng; Offset? _startLocalFocal; @@ -46,8 +46,9 @@ class TwoFingerGesturesService extends BaseGestureService { /// Initialize gesture, called when gesture has started. /// Stores all values, that are required later on. + @override void start(ScaleStartDetails details) { - controller.stopAnimationRaw(); + super.start(details); if (details.pointerCount < 2) return; _startCamera = _camera; @@ -73,6 +74,7 @@ class TwoFingerGesturesService extends BaseGestureService { /// Called multiple times to handle updates to the gesture. /// Updates the [MapCamera]. + @override void update(ScaleUpdateDetails details) { if (details.pointerCount < 2) return; if (_lastLocalFocal == null || @@ -163,6 +165,7 @@ class TwoFingerGesturesService extends BaseGestureService { } /// gesture has ended, clean up the previously stored values. + @override void end(ScaleEndDetails details) { if (details.pointerCount < 2) return; _startCamera = null; From c102197df14677f2f15313283a333022fce533b5 Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Thu, 18 Jan 2024 00:20:32 +0100 Subject: [PATCH 081/102] stop gesture animation by any onPointerDown event https://github.com/fleaflet/flutter_map/issues/1429#issuecomment-1896992462 --- lib/src/map/gestures/map_interactive_viewer.dart | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/src/map/gestures/map_interactive_viewer.dart b/lib/src/map/gestures/map_interactive_viewer.dart index 65db95207..1ba1e4f6a 100644 --- a/lib/src/map/gestures/map_interactive_viewer.dart +++ b/lib/src/map/gestures/map_interactive_viewer.dart @@ -97,12 +97,13 @@ class MapInteractiveViewerState extends State _twoFingerInput != null; return Listener( - onPointerDown: _options.onPointerDown == null - ? null - : (event) => _options.onPointerDown!.call( - event, - _camera.offsetToCrs(event.localPosition), - ), + onPointerDown: (event) { + widget.controller.stopAnimationRaw(); + _options.onPointerDown?.call( + event, + _camera.offsetToCrs(event.localPosition), + ); + }, onPointerHover: _options.onPointerHover == null ? null : (event) => _options.onPointerHover!.call( From 9a39009c770fa5f0a78f7e14b745c7452061e046 Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Fri, 19 Jan 2024 10:50:19 +0100 Subject: [PATCH 082/102] create getter for MapControllerImpl --- .../map/gestures/map_interactive_viewer.dart | 39 ++++++++++--------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/lib/src/map/gestures/map_interactive_viewer.dart b/lib/src/map/gestures/map_interactive_viewer.dart index 1ba1e4f6a..3b771f846 100644 --- a/lib/src/map/gestures/map_interactive_viewer.dart +++ b/lib/src/map/gestures/map_interactive_viewer.dart @@ -35,9 +35,11 @@ class MapInteractiveViewerState extends State DoubleTapDragZoomGestureService? _doubleTapDragZoom; KeyTriggerDragRotateGestureService? _keyTriggerDragRotate; - MapCamera get _camera => widget.controller.camera; + MapControllerImpl get _controller => widget.controller; - MapOptions get _options => widget.controller.options; + MapCamera get _camera => _controller.camera; + + MapOptions get _options => _controller.options; InteractionOptions get _interactionOptions => _options.interactionOptions; @@ -45,29 +47,29 @@ class MapInteractiveViewerState extends State @override void initState() { super.initState(); - widget.controller.interactiveViewerState = this; - widget.controller.addListener(reload); + _controller.interactiveViewerState = this; + _controller.addListener(reload); // callback gestures for the application if (_options.onTap != null) { - _tap = TapGestureService(controller: widget.controller); + _tap = TapGestureService(controller: _controller); } if (_options.onLongPress != null) { - _longPress = LongPressGestureService(controller: widget.controller); + _longPress = LongPressGestureService(controller: _controller); } if (_options.onSecondaryTap != null) { - _secondaryTap = SecondaryTapGestureService(controller: widget.controller); + _secondaryTap = SecondaryTapGestureService(controller: _controller); } if (_options.onSecondaryLongPress != null) { _secondaryLongPress = - SecondaryLongPressGestureService(controller: widget.controller); + SecondaryLongPressGestureService(controller: _controller); } if (_options.onTertiaryTap != null) { - _tertiaryTap = TertiaryTapGestureService(controller: widget.controller); + _tertiaryTap = TertiaryTapGestureService(controller: _controller); } if (_options.onTertiaryLongPress != null) { _tertiaryLongPress = - TertiaryLongPressGestureService(controller: widget.controller); + TertiaryLongPressGestureService(controller: _controller); } // gestures that change the map camera updateGestures(null, _interactionOptions.enabledGestures); @@ -76,7 +78,7 @@ class MapInteractiveViewerState extends State /// Called when the widgets gets disposed, used to clean up Stream listeners @override void dispose() { - widget.controller.removeListener(reload); + _controller.removeListener(reload); super.dispose(); } @@ -98,7 +100,7 @@ class MapInteractiveViewerState extends State return Listener( onPointerDown: (event) { - widget.controller.stopAnimationRaw(); + _controller.stopAnimationRaw(); _options.onPointerDown?.call( event, _camera.offsetToCrs(event.localPosition), @@ -217,33 +219,32 @@ class MapInteractiveViewerState extends State void updateGestures(EnabledGestures? oldFlags, EnabledGestures newFlags) { if (oldFlags == newFlags) return; if (newFlags.hasMultiFinger()) { - _twoFingerInput = TwoFingerGesturesService(controller: widget.controller); + _twoFingerInput = TwoFingerGesturesService(controller: _controller); } else { _twoFingerInput = null; } if (newFlags.drag) { - _drag = DragGestureService(controller: widget.controller); + _drag = DragGestureService(controller: _controller); } else { _drag = null; } if (newFlags.doubleTapZoomIn) { - _doubleTap = DoubleTapGestureService(controller: widget.controller); + _doubleTap = DoubleTapGestureService(controller: _controller); } else { _doubleTap = null; } if (newFlags.scrollWheelZoom) { - _scrollWheelZoom = - ScrollWheelZoomGestureService(controller: widget.controller); + _scrollWheelZoom = ScrollWheelZoomGestureService(controller: _controller); } else { _scrollWheelZoom = null; } if (newFlags.keyTriggerDragRotate) { _keyTriggerDragRotate = KeyTriggerDragRotateGestureService( - controller: widget.controller, + controller: _controller, keys: _options.interactionOptions.keyTriggerDragRotateKeys, ); } else { @@ -252,7 +253,7 @@ class MapInteractiveViewerState extends State if (newFlags.doubleTapDragZoom) { _doubleTapDragZoom = - DoubleTapDragZoomGestureService(controller: widget.controller); + DoubleTapDragZoomGestureService(controller: _controller); } else { _doubleTapDragZoom = null; } From 9956ada4ef1d3b44fc945fc51f6da1d7ebd2e166 Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Fri, 19 Jan 2024 10:53:47 +0100 Subject: [PATCH 083/102] remove obsolete `controller.stopAnimationRaw()` from services --- lib/src/map/gestures/map_interactive_viewer.dart | 4 ++++ lib/src/map/gestures/services/double_tap.dart | 1 - lib/src/map/gestures/services/double_tap_drag_zoom.dart | 1 - lib/src/map/gestures/services/drag.dart | 1 - lib/src/map/gestures/services/key_trigger_drag_rotate.dart | 1 - lib/src/map/gestures/services/long_press.dart | 3 --- lib/src/map/gestures/services/scroll_wheel_zoom.dart | 1 - lib/src/map/gestures/services/tap.dart | 3 --- lib/src/map/gestures/services/two_finger.dart | 1 - 9 files changed, 4 insertions(+), 12 deletions(-) diff --git a/lib/src/map/gestures/map_interactive_viewer.dart b/lib/src/map/gestures/map_interactive_viewer.dart index 3b771f846..4c037969b 100644 --- a/lib/src/map/gestures/map_interactive_viewer.dart +++ b/lib/src/map/gestures/map_interactive_viewer.dart @@ -127,6 +127,10 @@ class MapInteractiveViewerState extends State onPointerSignal: (event) { // mouse scroll wheel if (event is PointerScrollEvent) { + // `stopAnimationRaw()` will probably get moved to the service + // to handle animated zooming with the scroll wheel but + // we keep it here for now. + _controller.stopAnimationRaw(); _scrollWheelZoom?.submit(event); } }, diff --git a/lib/src/map/gestures/services/double_tap.dart b/lib/src/map/gestures/services/double_tap.dart index fdfcab96d..82da058d6 100644 --- a/lib/src/map/gestures/services/double_tap.dart +++ b/lib/src/map/gestures/services/double_tap.dart @@ -7,7 +7,6 @@ class DoubleTapGestureService extends SingleShotGestureService { /// A double tap gesture tap has been registered void submit() { - controller.stopAnimationRaw(); if (details == null) return; // start double tap animation diff --git a/lib/src/map/gestures/services/double_tap_drag_zoom.dart b/lib/src/map/gestures/services/double_tap_drag_zoom.dart index 5f61e3cda..7a7b6b71d 100644 --- a/lib/src/map/gestures/services/double_tap_drag_zoom.dart +++ b/lib/src/map/gestures/services/double_tap_drag_zoom.dart @@ -11,7 +11,6 @@ class DoubleTapDragZoomGestureService extends BaseGestureService { /// Called when the gesture is started, stores important values. void start(ScaleStartDetails details) { - controller.stopAnimationRaw(); _focalLocalStart = details.localFocalPoint; _mapZoomStart = _camera.zoom; controller.emitMapEvent( diff --git a/lib/src/map/gestures/services/drag.dart b/lib/src/map/gestures/services/drag.dart index f62c65684..b219f1258 100644 --- a/lib/src/map/gestures/services/drag.dart +++ b/lib/src/map/gestures/services/drag.dart @@ -15,7 +15,6 @@ class DragGestureService extends BaseGestureService { /// Called when the gesture is started, stores important values. void start(ScaleStartDetails details) { - controller.stopAnimationRaw(); _lastLocalFocal = details.localFocalPoint; _focalStartLocal = details.localFocalPoint; controller.emitMapEvent( 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 21801bab2..e11875410 100644 --- a/lib/src/map/gestures/services/key_trigger_drag_rotate.dart +++ b/lib/src/map/gestures/services/key_trigger_drag_rotate.dart @@ -13,7 +13,6 @@ class KeyTriggerDragRotateGestureService extends BaseGestureService { /// Called when the gesture is started, stores important values. void start() { - controller.stopAnimationRaw(); controller.emitMapEvent( MapEventRotateStart( camera: _camera, diff --git a/lib/src/map/gestures/services/long_press.dart b/lib/src/map/gestures/services/long_press.dart index 9b6ca8dda..cf9190661 100644 --- a/lib/src/map/gestures/services/long_press.dart +++ b/lib/src/map/gestures/services/long_press.dart @@ -9,7 +9,6 @@ class LongPressGestureService extends BaseGestureService { /// recognized. A pointer has remained in contact with the screen at the /// same location for a long period of time. void submit(LongPressStartDetails details) { - controller.stopAnimationRaw(); final position = _camera.offsetToCrs(details.localPosition); _options.onLongPress?.call(details, position); controller.emitMapEvent( @@ -31,7 +30,6 @@ class SecondaryLongPressGestureService extends BaseGestureService { /// recognized. A pointer has remained in contact with the screen at the /// same location for a long period of time. void submit(LongPressStartDetails details) { - controller.stopAnimationRaw(); final position = _camera.offsetToCrs(details.localPosition); _options.onSecondaryLongPress?.call(details, position); controller.emitMapEvent( @@ -52,7 +50,6 @@ class TertiaryLongPressGestureService extends BaseGestureService { /// A long press on the tertiary button has happen (e.g. click and hold on /// the mouse scroll wheel) void submit(LongPressStartDetails details) { - controller.stopAnimationRaw(); final point = _camera.offsetToCrs(details.localPosition); _options.onTertiaryLongPress?.call(details, point); controller.emitMapEvent( diff --git a/lib/src/map/gestures/services/scroll_wheel_zoom.dart b/lib/src/map/gestures/services/scroll_wheel_zoom.dart index 0326ea94e..9367fbcd8 100644 --- a/lib/src/map/gestures/services/scroll_wheel_zoom.dart +++ b/lib/src/map/gestures/services/scroll_wheel_zoom.dart @@ -11,7 +11,6 @@ class ScrollWheelZoomGestureService extends BaseGestureService { /// Handles mouse scroll events, called by the [Listener] of /// the [MapInteractiveViewer]. void submit(PointerScrollEvent details) { - controller.stopAnimationRaw(); if (details.scrollDelta.dy == 0) return; // Prevent scrolling of parent/child widgets simultaneously. diff --git a/lib/src/map/gestures/services/tap.dart b/lib/src/map/gestures/services/tap.dart index bc251dde7..e24134710 100644 --- a/lib/src/map/gestures/services/tap.dart +++ b/lib/src/map/gestures/services/tap.dart @@ -7,7 +7,6 @@ class TapGestureService extends SingleShotGestureService { /// A tap with a primary button has occurred. /// This triggers when the tap gesture wins. void submit() { - controller.stopAnimationRaw(); if (details == null) return; final point = _camera.offsetToCrs(details!.localPosition); @@ -32,7 +31,6 @@ class SecondaryTapGestureService extends SingleShotGestureService { /// A tap with a secondary button has occurred. /// This triggers when the tap gesture wins. void submit() { - controller.stopAnimationRaw(); if (details == null) return; final position = _camera.offsetToCrs(details!.localPosition); @@ -56,7 +54,6 @@ class TertiaryTapGestureService extends SingleShotGestureService { /// A tertiary tap gesture has happen (e.g. click on the mouse scroll wheel) void submit(TapUpDetails _) { - controller.stopAnimationRaw(); if (details == null) return; final point = _camera.offsetToCrs(details!.localPosition); diff --git a/lib/src/map/gestures/services/two_finger.dart b/lib/src/map/gestures/services/two_finger.dart index bf167c0bd..9456d4e3c 100644 --- a/lib/src/map/gestures/services/two_finger.dart +++ b/lib/src/map/gestures/services/two_finger.dart @@ -47,7 +47,6 @@ class TwoFingerGesturesService extends BaseGestureService { /// Initialize gesture, called when gesture has started. /// Stores all values, that are required later on. void start(ScaleStartDetails details) { - controller.stopAnimationRaw(); if (details.pointerCount < 2) return; _startCamera = _camera; From 3fae4e204ef8d8b0043de0ebcda1b2f9665e2421 Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Fri, 19 Jan 2024 11:16:21 +0100 Subject: [PATCH 084/102] clean up methods no longer necessary to cancel animations within the services, any pointer-down event stop ongoing gesture animation now. --- .../map/gestures/services/base_services.dart | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/lib/src/map/gestures/services/base_services.dart b/lib/src/map/gestures/services/base_services.dart index f5ffc17a2..13d4a1f84 100644 --- a/lib/src/map/gestures/services/base_services.dart +++ b/lib/src/map/gestures/services/base_services.dart @@ -5,7 +5,6 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/services.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:latlong2/latlong.dart'; -import 'package:meta/meta.dart'; part 'double_tap.dart'; part 'double_tap_drag_zoom.dart'; @@ -39,11 +38,7 @@ abstract class _SingleShotGestureService extends _BaseGestureService { void setDetails(TapDownDetails newDetails) => details = newDetails; /// Called when the gesture fires and is confirmed. - @mustCallSuper - @mustBeOverridden - void submit() { - controller.stopAnimationRaw(); - } + void submit(); void reset() => details = null; } @@ -54,11 +49,7 @@ abstract class _BaseLongPressGestureService extends _BaseGestureService { _BaseLongPressGestureService({required super.controller}); /// Called when the gesture fires and is confirmed. - @mustCallSuper - @mustBeOverridden - void submit(LongPressStartDetails details) { - controller.stopAnimationRaw(); - } + void submit(LongPressStartDetails details); } /// Abstract base service for a gesture that fires multiple times time. @@ -66,11 +57,7 @@ abstract class _ProgressableGestureService extends _BaseGestureService { _ProgressableGestureService({required super.controller}); /// Called when the gesture is started, stores important values. - @mustCallSuper - @mustBeOverridden - void start(ScaleStartDetails details) { - controller.stopAnimationRaw(); - } + void start(ScaleStartDetails details); /// Called when the gesture receives an update, updates the [MapCamera]. void update(ScaleUpdateDetails details); From 0b17c54886e706daef58de4939cce5fd7010bfa9 Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Fri, 19 Jan 2024 11:34:10 +0100 Subject: [PATCH 085/102] change abtract classes that have no method bodies or fields to interfaces --- lib/src/map/gestures/services/base_services.dart | 8 ++------ lib/src/map/gestures/services/double_tap_drag_zoom.dart | 3 ++- lib/src/map/gestures/services/drag.dart | 3 ++- lib/src/map/gestures/services/long_press.dart | 9 ++++++--- lib/src/map/gestures/services/two_finger.dart | 3 ++- 5 files changed, 14 insertions(+), 12 deletions(-) diff --git a/lib/src/map/gestures/services/base_services.dart b/lib/src/map/gestures/services/base_services.dart index 13d4a1f84..253197466 100644 --- a/lib/src/map/gestures/services/base_services.dart +++ b/lib/src/map/gestures/services/base_services.dart @@ -45,17 +45,13 @@ abstract class _SingleShotGestureService extends _BaseGestureService { /// Abstract base service for a long-press gesture that receives a /// [LongPressStartDetails] when called. -abstract class _BaseLongPressGestureService extends _BaseGestureService { - _BaseLongPressGestureService({required super.controller}); - +abstract interface class _BaseLongPressGestureService { /// Called when the gesture fires and is confirmed. void submit(LongPressStartDetails details); } /// Abstract base service for a gesture that fires multiple times time. -abstract class _ProgressableGestureService extends _BaseGestureService { - _ProgressableGestureService({required super.controller}); - +abstract interface class _ProgressableGestureService { /// Called when the gesture is started, stores important values. void start(ScaleStartDetails details); diff --git a/lib/src/map/gestures/services/double_tap_drag_zoom.dart b/lib/src/map/gestures/services/double_tap_drag_zoom.dart index aa9c7772f..f24ac3f26 100644 --- a/lib/src/map/gestures/services/double_tap_drag_zoom.dart +++ b/lib/src/map/gestures/services/double_tap_drag_zoom.dart @@ -2,7 +2,8 @@ part of 'base_services.dart'; /// Service to handle the double-tap and drag gesture to let the user zoom in /// and out with a single finger / one hand. -class DoubleTapDragZoomGestureService extends _ProgressableGestureService { +class DoubleTapDragZoomGestureService extends _BaseGestureService + implements _ProgressableGestureService { bool isActive = false; Offset? _focalLocalStart; double? _mapZoomStart; diff --git a/lib/src/map/gestures/services/drag.dart b/lib/src/map/gestures/services/drag.dart index 72bedfaac..6cbc59c3b 100644 --- a/lib/src/map/gestures/services/drag.dart +++ b/lib/src/map/gestures/services/drag.dart @@ -2,7 +2,8 @@ part of 'base_services.dart'; /// Service that handles drag gestures performed with one pointer /// (like a finger or cursor). -class DragGestureService extends _ProgressableGestureService { +class DragGestureService extends _BaseGestureService + implements _ProgressableGestureService { Offset? _lastLocalFocal; Offset? _focalStartLocal; diff --git a/lib/src/map/gestures/services/long_press.dart b/lib/src/map/gestures/services/long_press.dart index 219a0218f..efc14e28d 100644 --- a/lib/src/map/gestures/services/long_press.dart +++ b/lib/src/map/gestures/services/long_press.dart @@ -2,7 +2,8 @@ part of 'base_services.dart'; /// Service to handle long press gestures for the /// [MapOptions.onLongPress] callback. -class LongPressGestureService extends _BaseLongPressGestureService { +class LongPressGestureService extends _BaseGestureService + implements _BaseLongPressGestureService { LongPressGestureService({required super.controller}); /// Called when a long press gesture with a primary button has been @@ -24,7 +25,8 @@ class LongPressGestureService extends _BaseLongPressGestureService { /// Service to handle secondary long press gestures for the /// [MapOptions.onSecondaryLongPress] callback. -class SecondaryLongPressGestureService extends _BaseLongPressGestureService { +class SecondaryLongPressGestureService extends _BaseGestureService + implements _BaseLongPressGestureService { SecondaryLongPressGestureService({required super.controller}); /// Called when a long press gesture with a primary button has been @@ -46,7 +48,8 @@ class SecondaryLongPressGestureService extends _BaseLongPressGestureService { /// Service to handle tertiary long press gestures for the /// [MapOptions.onTertiaryLongPress] callback. -class TertiaryLongPressGestureService extends _BaseLongPressGestureService { +class TertiaryLongPressGestureService extends _BaseGestureService + implements _BaseLongPressGestureService { TertiaryLongPressGestureService({required super.controller}); /// A long press on the tertiary button has happen (e.g. click and hold on diff --git a/lib/src/map/gestures/services/two_finger.dart b/lib/src/map/gestures/services/two_finger.dart index 79a88b817..dd6a6ea7c 100644 --- a/lib/src/map/gestures/services/two_finger.dart +++ b/lib/src/map/gestures/services/two_finger.dart @@ -4,7 +4,8 @@ part of 'base_services.dart'; /// - [EnabledGestures.twoFingerMove] /// - [EnabledGestures.twoFingerZoom] /// - [EnabledGestures.twoFingerRotate] -class TwoFingerGesturesService extends _ProgressableGestureService { +class TwoFingerGesturesService extends _BaseGestureService + implements _ProgressableGestureService { MapCamera? _startCamera; LatLng? _startFocalLatLng; Offset? _startLocalFocal; From 57d94dbe0334ac7385e6feb766fd36f723671ef9 Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Sat, 20 Jan 2024 20:12:51 +0100 Subject: [PATCH 086/102] fix spelling --- lib/src/map/gestures/services/scroll_wheel_zoom.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/map/gestures/services/scroll_wheel_zoom.dart b/lib/src/map/gestures/services/scroll_wheel_zoom.dart index ec188a36a..5f97e173c 100644 --- a/lib/src/map/gestures/services/scroll_wheel_zoom.dart +++ b/lib/src/map/gestures/services/scroll_wheel_zoom.dart @@ -1,6 +1,6 @@ part of 'base_services.dart'; -/// Service to the handle scroll wheel gesture to zoom the map in or out. +/// Service to handle the scroll wheel gesture to zoom the map in or out. class ScrollWheelZoomGestureService extends _BaseGestureService { ScrollWheelZoomGestureService({required super.controller}); From be6cefb19c081c825151333eb79b872a0c368743 Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Sat, 20 Jan 2024 22:07:23 +0100 Subject: [PATCH 087/102] feat: add support for trackpad zoom gesture (#3) * add trackpad zoom gesture to EnabledGestures, as event and as service * split both trackpad gestures into two services * trackpad zoom center on cursor * rename variable --- .../controller/events/map_event_source.dart | 1 + lib/src/map/controller/events/map_events.dart | 15 ++++ .../map/gestures/map_interactive_viewer.dart | 50 +++++++++++--- .../map/gestures/services/base_services.dart | 2 + .../services/trackpad_legacy_zoom.dart | 38 ++++++++++ .../map/gestures/services/trackpad_zoom.dart | 69 +++++++++++++++++++ lib/src/map/options/enabled_gestures.dart | 15 ++++ lib/src/map/options/interaction_options.dart | 6 ++ 8 files changed, 188 insertions(+), 8 deletions(-) create mode 100644 lib/src/map/gestures/services/trackpad_legacy_zoom.dart create mode 100644 lib/src/map/gestures/services/trackpad_zoom.dart diff --git a/lib/src/map/controller/events/map_event_source.dart b/lib/src/map/controller/events/map_event_source.dart index 4506de24c..936fe872f 100644 --- a/lib/src/map/controller/events/map_event_source.dart +++ b/lib/src/map/controller/events/map_event_source.dart @@ -26,4 +26,5 @@ enum MapEventSource { keyTriggerDragRotateStart, keyTriggerDragRotateEnd, keyTriggerDragRotate, + trackpad, } diff --git a/lib/src/map/controller/events/map_events.dart b/lib/src/map/controller/events/map_events.dart index ce4c6a750..8329579a3 100644 --- a/lib/src/map/controller/events/map_events.dart +++ b/lib/src/map/controller/events/map_events.dart @@ -58,6 +58,11 @@ abstract class MapEventWithMove extends MapEvent { camera: camera, source: source, ), + MapEventSource.trackpad => MapEventTrackpadZoom( + oldCamera: oldCamera, + camera: camera, + source: source, + ), MapEventSource.onDrag || MapEventSource.onMultiFinger || MapEventSource.doubleTapHold || @@ -240,6 +245,16 @@ class MapEventScrollWheelZoom extends MapEventWithMove { }); } +/// Event which is fired when the trackpad of a device is used to zoom +@immutable +class MapEventTrackpadZoom extends MapEventWithMove { + const MapEventTrackpadZoom({ + required super.source, + required super.oldCamera, + required super.camera, + }); +} + /// Event which is fired when animation for double tap gesture is started @immutable class MapEventDoubleTapZoomStart extends MapEvent { diff --git a/lib/src/map/gestures/map_interactive_viewer.dart b/lib/src/map/gestures/map_interactive_viewer.dart index 5017c2896..833adc869 100644 --- a/lib/src/map/gestures/map_interactive_viewer.dart +++ b/lib/src/map/gestures/map_interactive_viewer.dart @@ -31,6 +31,8 @@ class MapInteractiveViewerState extends State DoubleTapGestureService? _doubleTap; ScrollWheelZoomGestureService? _scrollWheelZoom; TwoFingerGesturesService? _twoFingerInput; + TrackpadZoomGestureService? _trackpadZoom; + TrackpadLegacyZoomGestureService? _trackpadLegacyZoom; DragGestureService? _drag; DoubleTapDragZoomGestureService? _doubleTapDragZoom; KeyTriggerDragRotateGestureService? _keyTriggerDragRotate; @@ -125,15 +127,38 @@ class MapInteractiveViewerState extends State _camera.offsetToCrs(event.localPosition), ), onPointerSignal: (event) { - // mouse scroll wheel - if (event is PointerScrollEvent) { - // `stopAnimationRaw()` will probably get moved to the service - // to handle animated zooming with the scroll wheel but - // we keep it here for now. - _controller.stopAnimationRaw(); - _scrollWheelZoom?.submit(event); + switch (event) { + case final PointerScrollEvent event: + // mouse scroll wheel + // `stopAnimationRaw()` will probably get moved to the service + // to handle animated zooming with the scroll wheel but + // we keep it here for now. + _controller.stopAnimationRaw(); + _scrollWheelZoom?.submit(event); + break; + case final PointerScaleEvent event: + // Trackpad pinch gesture, in case the pointerPanZoom event + // callbacks can't be used and trackpad scrolling must still use + // this old PointerScrollSignal system. + // + // This is the case if not enough data is + // provided to the Flutter engine by platform APIs: + // - On **Windows**, where trackpad gesture support is dependent on + // the trackpad’s driver, + // - On **Web**, where not enough data is provided by browser APIs. + // + // https://docs.flutter.dev/release/breaking-changes/trackpad-gestures#description-of-change + _trackpadLegacyZoom?.submit(event); + break; } }, + // Trackpad gestures on most platforms since flutter 3.3 use + // these onPointerPanZoom* callbacks. + // See https://docs.flutter.dev/release/breaking-changes/trackpad-gestures + onPointerPanZoomStart: _trackpadZoom?.start, + onPointerPanZoomUpdate: _trackpadZoom?.update, + onPointerPanZoomEnd: _trackpadZoom?.end, + child: GestureDetector( onTapDown: _tap?.setDetails, onTapCancel: _tap?.reset, @@ -193,7 +218,7 @@ class MapInteractiveViewerState extends State _keyTriggerDragRotate!.update(details); } else if (_doubleTapDragZoom?.isActive ?? false) { _doubleTapDragZoom!.update(details); - } else if (details.pointerCount == 1) { + } else if (details.pointerCount == 1 && details.scale == 1) { _drag?.update(details); } else { _twoFingerInput?.update(details); @@ -247,6 +272,15 @@ class MapInteractiveViewerState extends State _scrollWheelZoom = null; } + if (newFlags.trackpadZoom) { + _trackpadZoom = TrackpadZoomGestureService(controller: _controller); + _trackpadLegacyZoom = + TrackpadLegacyZoomGestureService(controller: _controller); + } else { + _trackpadZoom = null; + _trackpadLegacyZoom = null; + } + if (newFlags.keyTriggerDragRotate) { _keyTriggerDragRotate = KeyTriggerDragRotateGestureService( controller: _controller, diff --git a/lib/src/map/gestures/services/base_services.dart b/lib/src/map/gestures/services/base_services.dart index 253197466..f67669e59 100644 --- a/lib/src/map/gestures/services/base_services.dart +++ b/lib/src/map/gestures/services/base_services.dart @@ -13,6 +13,8 @@ part 'key_trigger_drag_rotate.dart'; part 'long_press.dart'; part 'scroll_wheel_zoom.dart'; part 'tap.dart'; +part 'trackpad_legacy_zoom.dart'; +part 'trackpad_zoom.dart'; part 'two_finger.dart'; /// Abstract base service class for every gesture service. diff --git a/lib/src/map/gestures/services/trackpad_legacy_zoom.dart b/lib/src/map/gestures/services/trackpad_legacy_zoom.dart new file mode 100644 index 000000000..f67bdd0cd --- /dev/null +++ b/lib/src/map/gestures/services/trackpad_legacy_zoom.dart @@ -0,0 +1,38 @@ +part of 'base_services.dart'; + +/// Service to handle the trackpad (aka. touchpad) zoom gesture to zoom +/// the map in or out. +/// +/// Trackpad pinch gesture, in case the pointerPanZoom event +/// callbacks can't be used and trackpad scrolling must still use +/// this old PointerScrollSignal system. +/// +/// This is the case if not enough data is +/// provided to the Flutter engine by platform APIs: +/// - On **Windows**, where trackpad gesture support is dependent on +/// the trackpad’s driver, +/// - On **Web**, where not enough data is provided by browser APIs. +/// +/// https://docs.flutter.dev/release/breaking-changes/trackpad-gestures#description-of-change +class TrackpadLegacyZoomGestureService extends _BaseGestureService { + TrackpadLegacyZoomGestureService({required super.controller}); + + double get _velocity => _options.interactionOptions.trackpadZoomVelocity; + + void submit(PointerScaleEvent details) { + if (details.scale == 1) return; + + final tmpZoom = + _camera.zoom + (math.log(details.scale) / math.ln2) * _velocity; + final newZoom = _camera.clampZoom(tmpZoom); + + // TODO: calculate new center + + controller.moveRaw( + _camera.center, + newZoom, + hasGesture: true, + source: MapEventSource.trackpad, + ); + } +} diff --git a/lib/src/map/gestures/services/trackpad_zoom.dart b/lib/src/map/gestures/services/trackpad_zoom.dart new file mode 100644 index 000000000..b095ec4d9 --- /dev/null +++ b/lib/src/map/gestures/services/trackpad_zoom.dart @@ -0,0 +1,69 @@ +part of 'base_services.dart'; + +/// Service to handle the trackpad (aka. touchpad) zoom gesture to zoom +/// the map in or out. +/// +/// Trackpad gestures on most platforms since flutter 3.3 use +/// these onPointerPanZoom* callbacks. +/// See https://docs.flutter.dev/release/breaking-changes/trackpad-gestures +class TrackpadZoomGestureService extends _BaseGestureService { + double _lastScale = 1; + Offset? _startLocalFocal; + LatLng? _startFocalLatLng; + Offset? _lastLocalFocal; + + TrackpadZoomGestureService({required super.controller}); + + double get _velocity => _options.interactionOptions.trackpadZoomVelocity; + + bool get _moveEnabled => _options.interactionOptions.enabledGestures.drag; + + void start(PointerPanZoomStartEvent details) { + _lastScale = 1; + _startLocalFocal = details.localPosition; + _startFocalLatLng = _camera.offsetToCrs(_startLocalFocal!); + _lastLocalFocal = _startLocalFocal; + } + + void update(PointerPanZoomUpdateEvent details) { + if (_startFocalLatLng == null || + _startLocalFocal == null || + _lastLocalFocal == null) return; + if (details.scale == _lastScale) return; + final scaleFactor = (details.scale - _lastScale) * _velocity + 1; + + final tmpZoom = _camera.zoom * scaleFactor; + final newZoom = _camera.clampZoom(tmpZoom); + + LatLng newCenter = _camera.center; + if (_moveEnabled) { + math.Point newCenterPt; + + final oldCenterPt = _camera.project(_camera.center, newZoom); + final newFocalLatLng = _camera.offsetToCrs(_startLocalFocal!, newZoom); + final newFocalPt = _camera.project(newFocalLatLng, newZoom); + final oldFocalPt = _camera.project(_startFocalLatLng!, newZoom); + final zoomDifference = oldFocalPt - newFocalPt; + final moveDifference = + _rotateOffset(_camera, _startLocalFocal! - _lastLocalFocal!); + + newCenterPt = oldCenterPt + zoomDifference + moveDifference.toPoint(); + newCenter = _camera.unproject(newCenterPt, newZoom); + } + + _lastScale = details.scale; + _lastLocalFocal = details.localPosition; + controller.moveRaw( + newCenter, + newZoom, + hasGesture: true, + source: MapEventSource.trackpad, + ); + } + + void end(PointerPanZoomEndEvent details) { + _startLocalFocal = null; + _startFocalLatLng = null; + _lastLocalFocal = null; + } +} diff --git a/lib/src/map/options/enabled_gestures.dart b/lib/src/map/options/enabled_gestures.dart index 54e8b89c4..ebedb70c6 100644 --- a/lib/src/map/options/enabled_gestures.dart +++ b/lib/src/map/options/enabled_gestures.dart @@ -20,6 +20,7 @@ class EnabledGestures { required this.scrollWheelZoom, required this.twoFingerRotate, required this.keyTriggerDragRotate, + required this.trackpadZoom, }); /// Shortcut constructor to allow all gestures that don't rotate the map. @@ -41,6 +42,7 @@ class EnabledGestures { this.scrollWheelZoom = true, this.twoFingerRotate = true, this.keyTriggerDragRotate = true, + this.trackpadZoom = true, }); /// This constructor has no enabled gestures by default. Use this constructor @@ -58,6 +60,7 @@ class EnabledGestures { this.scrollWheelZoom = false, this.twoFingerRotate = false, this.keyTriggerDragRotate = false, + this.trackpadZoom = false, }); /// This constructor supports bitfield operations on the static fields @@ -81,6 +84,8 @@ class EnabledGestures { InteractiveFlag.hasFlag(flags, InteractiveFlag.twoFingerRotate), keyTriggerDragRotate: InteractiveFlag.hasFlag(flags, InteractiveFlag.keyTriggerDragRotate), + trackpadZoom: + InteractiveFlag.hasFlag(flags, InteractiveFlag.trackpadZoom), ); } @@ -110,6 +115,9 @@ class EnabledGestures { /// Enable zooming with a mouse scroll wheel final bool scrollWheelZoom; + /// Enable zooming with the device trackpad / touchpad + final bool trackpadZoom; + /// Enable rotation by pressing the defined keyboard key (by default CTRL key) /// and dragging with the cursor /// or finger. @@ -130,6 +138,7 @@ class EnabledGestures { bool? scrollWheelZoom, bool? twoFingerRotate, bool? keyTriggerDragRotate, + bool? trackpadZoom, }) => EnabledGestures( drag: drag ?? this.drag, @@ -141,6 +150,7 @@ class EnabledGestures { scrollWheelZoom: scrollWheelZoom ?? this.scrollWheelZoom, twoFingerRotate: twoFingerRotate ?? this.twoFingerRotate, keyTriggerDragRotate: keyTriggerDragRotate ?? this.keyTriggerDragRotate, + trackpadZoom: trackpadZoom ?? this.trackpadZoom, ); const EnabledGestures.byGroup({ @@ -157,6 +167,7 @@ class EnabledGestures { twoFingerZoom: zoom, twoFingerRotate: rotate, keyTriggerDragRotate: rotate, + trackpadZoom: zoom, ); @override @@ -213,6 +224,7 @@ abstract class InteractiveFlag { doubleTapDragZoom | scrollWheelZoom | twoFingerRotate | + trackpadZoom | keyTriggerDragRotate; static const int none = 0; @@ -259,6 +271,9 @@ abstract class InteractiveFlag { /// To change the key see [InteractionOptions.cursorKeyboardRotationOptions]. static const int keyTriggerDragRotate = 1 << 8; + /// Enable zooming by using the trackpad / touchpad of a device. + static const int trackpadZoom = 1 << 9; + /// Returns `true` if [leftFlags] has at least one member in [rightFlags] /// (intersection) for example /// [leftFlags] = [InteractiveFlag.drag] | [InteractiveFlag.twoFingerRotate] diff --git a/lib/src/map/options/interaction_options.dart b/lib/src/map/options/interaction_options.dart index ad460af39..682eeaa1f 100644 --- a/lib/src/map/options/interaction_options.dart +++ b/lib/src/map/options/interaction_options.dart @@ -45,6 +45,11 @@ final class InteractionOptions { /// Defaults to 0.01. final double scrollWheelVelocity; + /// The velocity how fast the map should zoom when using the + /// trackpad / touchpad. + /// Defaults to 0.5. + final double trackpadZoomVelocity; + /// Override this option if you want to use custom keys for the key trigger /// drag rotate gesture (aka CTRL+drag rotate gesture). /// By default the left and right control key are both used. @@ -66,6 +71,7 @@ final class InteractionOptions { this.twoFingerZoomThreshold = 0.01, this.twoFingerMoveThreshold = 3.0, this.scrollWheelVelocity = 0.01, + this.trackpadZoomVelocity = 0.5, this.keyTriggerDragRotateKeys = defaultKeyTriggerDragRotateKeys, }) : assert( twoFingerRotateThreshold >= 0.0, From 874b3a9be72f2d0cda814f6d0e2c691d4359805a Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Sun, 21 Jan 2024 00:02:31 +0100 Subject: [PATCH 088/102] match trackpad velocity on new and legacy service --- lib/src/map/gestures/services/trackpad_legacy_zoom.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/src/map/gestures/services/trackpad_legacy_zoom.dart b/lib/src/map/gestures/services/trackpad_legacy_zoom.dart index f67bdd0cd..ddcabad67 100644 --- a/lib/src/map/gestures/services/trackpad_legacy_zoom.dart +++ b/lib/src/map/gestures/services/trackpad_legacy_zoom.dart @@ -15,6 +15,8 @@ part of 'base_services.dart'; /// /// https://docs.flutter.dev/release/breaking-changes/trackpad-gestures#description-of-change class TrackpadLegacyZoomGestureService extends _BaseGestureService { + static const _velocityAdjustment = 4.5; + TrackpadLegacyZoomGestureService({required super.controller}); double get _velocity => _options.interactionOptions.trackpadZoomVelocity; @@ -22,8 +24,8 @@ class TrackpadLegacyZoomGestureService extends _BaseGestureService { void submit(PointerScaleEvent details) { if (details.scale == 1) return; - final tmpZoom = - _camera.zoom + (math.log(details.scale) / math.ln2) * _velocity; + final tmpZoom = _camera.zoom + + (math.log(details.scale) / math.ln2) * _velocity * _velocityAdjustment; final newZoom = _camera.clampZoom(tmpZoom); // TODO: calculate new center From e49bc02d8b11d90d0bbffb2f3e9d75152c4a9bbc Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Sun, 21 Jan 2024 11:21:51 +0100 Subject: [PATCH 089/102] rename `EnabledGestures` to `MapGestures` --- example/lib/pages/gestures_page.dart | 2 +- example/lib/pages/latlng_to_screen_point.dart | 2 +- example/lib/pages/markers.dart | 2 +- lib/flutter_map.dart | 2 +- .../map/controller/map_controller_impl.dart | 4 ++-- .../map/gestures/map_interactive_viewer.dart | 20 ++++++++-------- lib/src/map/gestures/services/drag.dart | 3 +-- .../map/gestures/services/trackpad_zoom.dart | 2 +- lib/src/map/gestures/services/two_finger.dart | 20 +++++++--------- lib/src/map/options/interaction_options.dart | 22 ++++++++--------- ...nabled_gestures.dart => map_gestures.dart} | 24 +++++++++---------- lib/src/map/options/map_options.dart | 2 +- test/flutter_map_test.dart | 4 ++-- test/full_coverage_test.dart | 2 +- 14 files changed, 54 insertions(+), 57 deletions(-) rename lib/src/map/options/{enabled_gestures.dart => map_gestures.dart} (96%) diff --git a/example/lib/pages/gestures_page.dart b/example/lib/pages/gestures_page.dart index e9031094c..7803c6a0a 100644 --- a/example/lib/pages/gestures_page.dart +++ b/example/lib/pages/gestures_page.dart @@ -109,7 +109,7 @@ class _GesturesPageState extends State { initialCenter: const LatLng(51.5, -0.09), initialZoom: 11, interactionOptions: InteractionOptions( - enabledGestures: EnabledGestures.bitfield(flags), + gestures: MapGestures.bitfield(flags), ), ), children: [openStreetMapTileLayer], diff --git a/example/lib/pages/latlng_to_screen_point.dart b/example/lib/pages/latlng_to_screen_point.dart index d8d0ae52f..1676cf187 100644 --- a/example/lib/pages/latlng_to_screen_point.dart +++ b/example/lib/pages/latlng_to_screen_point.dart @@ -48,7 +48,7 @@ class _LatLngToScreenPointPageState extends State { initialCenter: const LatLng(51.5, -0.09), initialZoom: 11, interactionOptions: const InteractionOptions( - enabledGestures: EnabledGestures.all( + gestures: MapGestures.all( doubleTapZoomIn: false, doubleTapDragZoom: false, ), diff --git a/example/lib/pages/markers.dart b/example/lib/pages/markers.dart index fd83363ea..8bafd3740 100644 --- a/example/lib/pages/markers.dart +++ b/example/lib/pages/markers.dart @@ -114,7 +114,7 @@ class _MarkerPageState extends State { setState(() => customMarkers.add(buildPin(p))); }, interactionOptions: const InteractionOptions( - enabledGestures: EnabledGestures.all( + gestures: MapGestures.all( doubleTapDragZoom: false, doubleTapZoomIn: false, )), diff --git a/lib/flutter_map.dart b/lib/flutter_map.dart index 3cb98aa4f..49f86f310 100644 --- a/lib/flutter_map.dart +++ b/lib/flutter_map.dart @@ -50,8 +50,8 @@ export 'package:flutter_map/src/map/controller/events/map_events.dart'; export 'package:flutter_map/src/map/controller/map_controller.dart'; export 'package:flutter_map/src/map/controller/map_controller_impl.dart'; export 'package:flutter_map/src/map/gestures/latlng_tween.dart'; -export 'package:flutter_map/src/map/options/enabled_gestures.dart'; export 'package:flutter_map/src/map/options/interaction_options.dart'; +export 'package:flutter_map/src/map/options/map_gestures.dart'; export 'package:flutter_map/src/map/options/map_options.dart'; export 'package:flutter_map/src/map/widget.dart'; export 'package:flutter_map/src/misc/bounds.dart'; diff --git a/lib/src/map/controller/map_controller_impl.dart b/lib/src/map/controller/map_controller_impl.dart index 3bd91d796..7dbca4cf4 100644 --- a/lib/src/map/controller/map_controller_impl.dart +++ b/lib/src/map/controller/map_controller_impl.dart @@ -363,8 +363,8 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState> if (value.options != null && value.options!.interactionOptions != newOptions.interactionOptions) { _interactiveViewerState.updateGestures( - value.options!.interactionOptions.enabledGestures, - newOptions.interactionOptions.enabledGestures, + value.options!.interactionOptions.gestures, + newOptions.interactionOptions.gestures, ); } diff --git a/lib/src/map/gestures/map_interactive_viewer.dart b/lib/src/map/gestures/map_interactive_viewer.dart index 833adc869..30c550d82 100644 --- a/lib/src/map/gestures/map_interactive_viewer.dart +++ b/lib/src/map/gestures/map_interactive_viewer.dart @@ -74,7 +74,7 @@ class MapInteractiveViewerState extends State TertiaryLongPressGestureService(controller: _controller); } // gestures that change the map camera - updateGestures(null, _interactionOptions.enabledGestures); + updateGestures(null, _interactionOptions.gestures); } /// Called when the widgets gets disposed, used to clean up Stream listeners @@ -246,33 +246,33 @@ class MapInteractiveViewerState extends State } /// Used by the internal map controller to update interaction gestures - void updateGestures(EnabledGestures? oldFlags, EnabledGestures newFlags) { - if (oldFlags == newFlags) return; - if (newFlags.hasMultiFinger()) { + void updateGestures(MapGestures? oldGestures, MapGestures newGestures) { + if (oldGestures == newGestures) return; + if (newGestures.hasMultiFinger()) { _twoFingerInput = TwoFingerGesturesService(controller: _controller); } else { _twoFingerInput = null; } - if (newFlags.drag) { + if (newGestures.drag) { _drag = DragGestureService(controller: _controller); } else { _drag = null; } - if (newFlags.doubleTapZoomIn) { + if (newGestures.doubleTapZoomIn) { _doubleTap = DoubleTapGestureService(controller: _controller); } else { _doubleTap = null; } - if (newFlags.scrollWheelZoom) { + if (newGestures.scrollWheelZoom) { _scrollWheelZoom = ScrollWheelZoomGestureService(controller: _controller); } else { _scrollWheelZoom = null; } - if (newFlags.trackpadZoom) { + if (newGestures.trackpadZoom) { _trackpadZoom = TrackpadZoomGestureService(controller: _controller); _trackpadLegacyZoom = TrackpadLegacyZoomGestureService(controller: _controller); @@ -281,7 +281,7 @@ class MapInteractiveViewerState extends State _trackpadLegacyZoom = null; } - if (newFlags.keyTriggerDragRotate) { + if (newGestures.keyTriggerDragRotate) { _keyTriggerDragRotate = KeyTriggerDragRotateGestureService( controller: _controller, keys: _options.interactionOptions.keyTriggerDragRotateKeys, @@ -290,7 +290,7 @@ class MapInteractiveViewerState extends State _keyTriggerDragRotate = null; } - if (newFlags.doubleTapDragZoom) { + if (newGestures.doubleTapDragZoom) { _doubleTapDragZoom = DoubleTapDragZoomGestureService(controller: _controller); } else { diff --git a/lib/src/map/gestures/services/drag.dart b/lib/src/map/gestures/services/drag.dart index 6cbc59c3b..f5d9e7906 100644 --- a/lib/src/map/gestures/services/drag.dart +++ b/lib/src/map/gestures/services/drag.dart @@ -7,8 +7,7 @@ class DragGestureService extends _BaseGestureService Offset? _lastLocalFocal; Offset? _focalStartLocal; - bool get _flingEnabled => - _options.interactionOptions.enabledGestures.flingAnimation; + bool get _flingEnabled => _options.interactionOptions.gestures.flingAnimation; DragGestureService({required super.controller}); diff --git a/lib/src/map/gestures/services/trackpad_zoom.dart b/lib/src/map/gestures/services/trackpad_zoom.dart index b095ec4d9..361cdf5e6 100644 --- a/lib/src/map/gestures/services/trackpad_zoom.dart +++ b/lib/src/map/gestures/services/trackpad_zoom.dart @@ -16,7 +16,7 @@ class TrackpadZoomGestureService extends _BaseGestureService { double get _velocity => _options.interactionOptions.trackpadZoomVelocity; - bool get _moveEnabled => _options.interactionOptions.enabledGestures.drag; + bool get _moveEnabled => _options.interactionOptions.gestures.drag; void start(PointerPanZoomStartEvent details) { _lastScale = 1; diff --git a/lib/src/map/gestures/services/two_finger.dart b/lib/src/map/gestures/services/two_finger.dart index dd6a6ea7c..a774cee73 100644 --- a/lib/src/map/gestures/services/two_finger.dart +++ b/lib/src/map/gestures/services/two_finger.dart @@ -1,9 +1,9 @@ part of 'base_services.dart'; /// A gesture with multiple inputs. This service handles the following gestures: -/// - [EnabledGestures.twoFingerMove] -/// - [EnabledGestures.twoFingerZoom] -/// - [EnabledGestures.twoFingerRotate] +/// - [MapGestures.twoFingerMove] +/// - [MapGestures.twoFingerZoom] +/// - [MapGestures.twoFingerRotate] class TwoFingerGesturesService extends _BaseGestureService implements _ProgressableGestureService { MapCamera? _startCamera; @@ -19,20 +19,18 @@ class TwoFingerGesturesService extends _BaseGestureService bool _moving = false; bool _rotating = false; - /// Getter as shortcut to check if [EnabledGestures.twoFingerMove] + /// Getter as shortcut to check if [MapGestures.twoFingerMove] /// is enabled. - bool get _moveEnabled => - _options.interactionOptions.enabledGestures.twoFingerMove; + bool get _moveEnabled => _options.interactionOptions.gestures.twoFingerMove; - /// Getter as shortcut to check if [EnabledGestures.twoFingerRotate] + /// Getter as shortcut to check if [MapGestures.twoFingerRotate] /// is enabled. bool get _rotateEnabled => - _options.interactionOptions.enabledGestures.twoFingerRotate; + _options.interactionOptions.gestures.twoFingerRotate; - /// Getter as shortcut to check if [EnabledGestures.twoFingerZoom] + /// Getter as shortcut to check if [MapGestures.twoFingerZoom] /// is enabled. - bool get _zoomEnabled => - _options.interactionOptions.enabledGestures.twoFingerZoom; + bool get _zoomEnabled => _options.interactionOptions.gestures.twoFingerZoom; double get _rotateThreshold => _options.interactionOptions.twoFingerRotateThreshold; diff --git a/lib/src/map/options/interaction_options.dart b/lib/src/map/options/interaction_options.dart index 682eeaa1f..777d851d4 100644 --- a/lib/src/map/options/interaction_options.dart +++ b/lib/src/map/options/interaction_options.dart @@ -1,23 +1,23 @@ import 'package:flutter/services.dart'; -import 'package:flutter_map/src/map/options/enabled_gestures.dart'; +import 'package:flutter_map/src/map/options/map_gestures.dart'; import 'package:meta/meta.dart'; /// Set interation options for input gestures. -/// Most commonly used is [InteractionOptions.enabledGestures]. +/// Most commonly used is [InteractionOptions.gestures]. @immutable final class InteractionOptions { /// Enable or disable specific gestures. By default all gestures are enabled. /// If you want to disable all gestures or almost all gestures, use the - /// [EnabledGestures.none] constructor. - /// In case you want to disable only few gestures, use [EnabledGestures.all] - /// and you can use the [EnabledGestures.noRotation] for an easy way to + /// [MapGestures.none] constructor. + /// In case you want to disable only few gestures, use [MapGestures.all] + /// and you can use the [MapGestures.noRotation] for an easy way to /// disable all rotation gestures. /// /// In addition you can specify your gestures via bitfield operations using - /// the [EnabledGestures.bitfield] constructor together with the static + /// the [MapGestures.bitfield] constructor together with the static /// fields in [InteractiveFlag]. /// For more information see the documentation on [InteractiveFlag]. - final EnabledGestures enabledGestures; + final MapGestures gestures; /// Map starts to rotate when [twoFingerRotateThreshold] has been achieved /// or another multi finger gesture wins. @@ -34,7 +34,7 @@ final class InteractionOptions { /// gestures by a single pointer like a single finger. /// /// This option gets superseded by [twoFingerZoomThreshold] if - /// [EnabledGestures.twoFingerMove] and [EnabledGestures.twoFingerZoom] are + /// [MapGestures.twoFingerMove] and [MapGestures.twoFingerZoom] are /// both active and the [twoFingerZoomThreshold] is reached. /// /// Default is 3.0. @@ -66,7 +66,7 @@ final class InteractionOptions { ]; const InteractionOptions({ - this.enabledGestures = const EnabledGestures.all(), + this.gestures = const MapGestures.all(), this.twoFingerRotateThreshold = 0.1, this.twoFingerZoomThreshold = 0.01, this.twoFingerMoveThreshold = 3.0, @@ -90,7 +90,7 @@ final class InteractionOptions { bool operator ==(Object other) => identical(this, other) || (other is InteractionOptions && - enabledGestures == other.enabledGestures && + gestures == other.gestures && twoFingerRotateThreshold == other.twoFingerRotateThreshold && twoFingerZoomThreshold == other.twoFingerZoomThreshold && twoFingerMoveThreshold == other.twoFingerMoveThreshold && @@ -99,7 +99,7 @@ final class InteractionOptions { @override int get hashCode => Object.hash( - enabledGestures, + gestures, twoFingerRotateThreshold, twoFingerZoomThreshold, twoFingerMoveThreshold, diff --git a/lib/src/map/options/enabled_gestures.dart b/lib/src/map/options/map_gestures.dart similarity index 96% rename from lib/src/map/options/enabled_gestures.dart rename to lib/src/map/options/map_gestures.dart index ebedb70c6..10335290e 100644 --- a/lib/src/map/options/enabled_gestures.dart +++ b/lib/src/map/options/map_gestures.dart @@ -2,7 +2,7 @@ import 'package:flutter_map/flutter_map.dart'; import 'package:meta/meta.dart'; @immutable -class EnabledGestures { +class MapGestures { /// Use this constructor if you want to set all gestures manually. /// /// Prefer to use the constructors [InteractiveFlags.all] or @@ -10,7 +10,7 @@ class EnabledGestures { /// /// If you want to define your enabled gestures using bitfield operations, /// use [InteractiveFlags.bitfield] instead. - const EnabledGestures({ + const MapGestures({ required this.drag, required this.flingAnimation, required this.twoFingerMove, @@ -24,7 +24,7 @@ class EnabledGestures { }); /// Shortcut constructor to allow all gestures that don't rotate the map. - const EnabledGestures.noRotation() + const MapGestures.noRotation() : this.byGroup(move: true, zoom: true, rotate: false); /// This constructor enables all gestures by default. Use this constructor if @@ -32,7 +32,7 @@ class EnabledGestures { /// /// In case you want have no or only few gestures enabled use the /// [InteractiveFlags.none] constructor instead. - const EnabledGestures.all({ + const MapGestures.all({ this.drag = true, this.flingAnimation = true, this.twoFingerMove = true, @@ -50,7 +50,7 @@ class EnabledGestures { /// /// In case you want have most or all of the gestures enabled use the /// [InteractiveFlags.all] constructor instead. - const EnabledGestures.none({ + const MapGestures.none({ this.drag = false, this.flingAnimation = false, this.twoFingerMove = false, @@ -65,8 +65,8 @@ class EnabledGestures { /// This constructor supports bitfield operations on the static fields /// from [InteractiveFlag]. - factory EnabledGestures.bitfield(int flags) { - return EnabledGestures( + factory MapGestures.bitfield(int flags) { + return MapGestures( drag: InteractiveFlag.hasFlag(flags, InteractiveFlag.drag), flingAnimation: InteractiveFlag.hasFlag(flags, InteractiveFlag.flingAnimation), @@ -127,8 +127,8 @@ class EnabledGestures { bool hasMultiFinger() => twoFingerMove || twoFingerZoom || twoFingerRotate; /// Wither to change the value of some gestures. Returns a new - /// [EnabledGestures] object. - EnabledGestures copyWith({ + /// [MapGestures] object. + MapGestures copyWith({ bool? drag, bool? flingAnimation, bool? twoFingerZoom, @@ -140,7 +140,7 @@ class EnabledGestures { bool? keyTriggerDragRotate, bool? trackpadZoom, }) => - EnabledGestures( + MapGestures( drag: drag ?? this.drag, flingAnimation: flingAnimation ?? this.flingAnimation, twoFingerZoom: twoFingerZoom ?? this.twoFingerZoom, @@ -153,7 +153,7 @@ class EnabledGestures { trackpadZoom: trackpadZoom ?? this.trackpadZoom, ); - const EnabledGestures.byGroup({ + const MapGestures.byGroup({ required bool move, required bool zoom, required bool rotate, @@ -173,7 +173,7 @@ class EnabledGestures { @override bool operator ==(Object other) => identical(this, other) || - other is EnabledGestures && + other is MapGestures && runtimeType == other.runtimeType && drag == other.drag && flingAnimation == other.flingAnimation && diff --git a/lib/src/map/options/map_options.dart b/lib/src/map/options/map_options.dart index 5e666e153..5f6e73e80 100644 --- a/lib/src/map/options/map_options.dart +++ b/lib/src/map/options/map_options.dart @@ -32,7 +32,7 @@ class MapOptions { /// Callback that gets called when the user has performed a confirmed single /// tap or click on the map. If double tap gestures are enabled in - /// [InteractionOptions.enabledGestures], the callback waits until the + /// [InteractionOptions.gestures], the callback waits until the /// double-tap delay has passed by and the tap gesture is confirmed. final TapCallback? onTap; diff --git a/test/flutter_map_test.dart b/test/flutter_map_test.dart index 1266224e8..cb20bdf6f 100644 --- a/test/flutter_map_test.dart +++ b/test/flutter_map_test.dart @@ -217,7 +217,7 @@ class _TestRebuildsAppState extends State { Crs _crs = const Epsg3857(); /// double tap gestures delay the tap gestures, disable them here - EnabledGestures _interactiveFlags = const EnabledGestures.all( + MapGestures _interactiveFlags = const MapGestures.all( doubleTapZoomIn: false, doubleTapDragZoom: false, ); @@ -237,7 +237,7 @@ class _TestRebuildsAppState extends State { options: MapOptions( crs: _crs, interactionOptions: InteractionOptions( - enabledGestures: _interactiveFlags, + gestures: _interactiveFlags, ), ), children: [ diff --git a/test/full_coverage_test.dart b/test/full_coverage_test.dart index f5018b9bc..d623ad229 100644 --- a/test/full_coverage_test.dart +++ b/test/full_coverage_test.dart @@ -46,8 +46,8 @@ import 'package:flutter_map/src/map/gestures/latlng_tween.dart'; import 'package:flutter_map/src/map/gestures/map_interactive_viewer.dart'; import 'package:flutter_map/src/map/gestures/services/base_services.dart'; import 'package:flutter_map/src/map/inherited_model.dart'; -import 'package:flutter_map/src/map/options/enabled_gestures.dart'; import 'package:flutter_map/src/map/options/interaction_options.dart'; +import 'package:flutter_map/src/map/options/map_gestures.dart'; import 'package:flutter_map/src/map/options/map_options.dart'; import 'package:flutter_map/src/map/widget.dart'; import 'package:flutter_map/src/misc/bounds.dart'; From e175fcf7d397ca57849ff720c50426ba2487f727 Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Sun, 21 Jan 2024 11:28:00 +0100 Subject: [PATCH 090/102] remove hasMultiFinger() method Removed this method because it was only used in MapInteractiveViewer.updateGestures and could cause confusion between multi point touchscreen and mutli point trackpad gestures. --- lib/src/map/gestures/map_interactive_viewer.dart | 4 +++- lib/src/map/options/map_gestures.dart | 3 --- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/src/map/gestures/map_interactive_viewer.dart b/lib/src/map/gestures/map_interactive_viewer.dart index 30c550d82..4b76ee5a3 100644 --- a/lib/src/map/gestures/map_interactive_viewer.dart +++ b/lib/src/map/gestures/map_interactive_viewer.dart @@ -248,7 +248,9 @@ class MapInteractiveViewerState extends State /// Used by the internal map controller to update interaction gestures void updateGestures(MapGestures? oldGestures, MapGestures newGestures) { if (oldGestures == newGestures) return; - if (newGestures.hasMultiFinger()) { + if (newGestures.twoFingerMove || + newGestures.twoFingerZoom || + newGestures.twoFingerRotate) { _twoFingerInput = TwoFingerGesturesService(controller: _controller); } else { _twoFingerInput = null; diff --git a/lib/src/map/options/map_gestures.dart b/lib/src/map/options/map_gestures.dart index 10335290e..dfee71aaf 100644 --- a/lib/src/map/options/map_gestures.dart +++ b/lib/src/map/options/map_gestures.dart @@ -123,9 +123,6 @@ class MapGestures { /// or finger. final bool keyTriggerDragRotate; - /// Returns true of any gesture with more than one finger is enabled. - bool hasMultiFinger() => twoFingerMove || twoFingerZoom || twoFingerRotate; - /// Wither to change the value of some gestures. Returns a new /// [MapGestures] object. MapGestures copyWith({ From 7d7b5394a9bf80fa389690a48483764261c8842c Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Sun, 21 Jan 2024 11:39:25 +0100 Subject: [PATCH 091/102] update docs --- lib/src/map/controller/map_controller.dart | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/src/map/controller/map_controller.dart b/lib/src/map/controller/map_controller.dart index 442d21f50..486caf9ad 100644 --- a/lib/src/map/controller/map_controller.dart +++ b/lib/src/map/controller/map_controller.dart @@ -102,8 +102,8 @@ abstract class MapController { /// The emitted [MapEventRotate.source]/[MapEventMove.source] properties will /// be [MapEventSource.mapController]. /// - /// The operation was successful if both fields of the resulting record are - /// `true`. + /// Returns `true` unless the [degree] parameter patches the + /// current [MapCamera.rotation] value. bool rotateAroundPoint( double degree, { Point? point, @@ -116,10 +116,11 @@ abstract class MapController { /// /// Does not support offsets or rotations around custom points. /// - /// See documentation on those methods for more details. + /// This method calls the internal [MapControllerImpl.moveAndRotateRaw]. The + /// emitted events will have [MapEventSource.mapController] as event source. /// - /// The operation was successful if both fields of the resulting record are - /// `true`. + /// Returns `true` unless [center], [zoom] and [degree] matched the current + /// value in [MapCamera]. bool moveAndRotate( LatLng center, double zoom, @@ -129,6 +130,9 @@ abstract class MapController { /// Move and zoom the map to fit [cameraFit]. /// + /// This method calls the internal [MapControllerImpl.fitCameraRaw]. The + /// emitted events will have [MapEventSource.mapController] as event source. + /// /// For information about the return value and emitted events, see [move]'s /// documentation. bool fitCamera(CameraFit cameraFit); From 53af338ce71594123fb179d2c7911d562c5410c8 Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Sun, 21 Jan 2024 11:43:56 +0100 Subject: [PATCH 092/102] rename `multiFinger` to `twoFinger` to match the gesture services --- lib/src/map/controller/events/map_event_source.dart | 6 +++--- lib/src/map/controller/events/map_events.dart | 2 +- lib/src/map/gestures/services/two_finger.dart | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/src/map/controller/events/map_event_source.dart b/lib/src/map/controller/events/map_event_source.dart index 936fe872f..4d7f80209 100644 --- a/lib/src/map/controller/events/map_event_source.dart +++ b/lib/src/map/controller/events/map_event_source.dart @@ -10,9 +10,9 @@ enum MapEventSource { dragStart, onDrag, dragEnd, - multiFingerStart, - onMultiFinger, - multiFingerEnd, + twoFingerStart, + onTwoFinger, + twoFingerEnd, flingAnimationController, doubleTapZoomAnimationController, interactiveFlagsChanged, diff --git a/lib/src/map/controller/events/map_events.dart b/lib/src/map/controller/events/map_events.dart index 8329579a3..b5267cc56 100644 --- a/lib/src/map/controller/events/map_events.dart +++ b/lib/src/map/controller/events/map_events.dart @@ -64,7 +64,7 @@ abstract class MapEventWithMove extends MapEvent { source: source, ), MapEventSource.onDrag || - MapEventSource.onMultiFinger || + MapEventSource.onTwoFinger || MapEventSource.doubleTapHold || MapEventSource.mapController || MapEventSource.custom => diff --git a/lib/src/map/gestures/services/two_finger.dart b/lib/src/map/gestures/services/two_finger.dart index a774cee73..7ec54c6b0 100644 --- a/lib/src/map/gestures/services/two_finger.dart +++ b/lib/src/map/gestures/services/two_finger.dart @@ -65,7 +65,7 @@ class TwoFingerGesturesService extends _BaseGestureService controller.emitMapEvent( MapEventMoveStart( camera: _camera, - source: MapEventSource.multiFingerStart, + source: MapEventSource.twoFingerStart, ), ); } @@ -153,7 +153,7 @@ class TwoFingerGesturesService extends _BaseGestureService newRotation, offset: Offset.zero, hasGesture: true, - source: MapEventSource.onMultiFinger, + source: MapEventSource.onTwoFinger, ); _lastRotation = details.rotation; @@ -181,7 +181,7 @@ class TwoFingerGesturesService extends _BaseGestureService controller.emitMapEvent( MapEventMoveEnd( camera: _camera, - source: MapEventSource.multiFingerEnd, + source: MapEventSource.twoFingerEnd, ), ); } From 82659533cff7dcb81326ee42bebe252a9b9eed47 Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Sun, 21 Jan 2024 14:20:02 +0100 Subject: [PATCH 093/102] MapGestures: remove `noRotation()`, add `.noGroups` and `.allGroups()` --- lib/src/map/options/map_gestures.dart | 73 +++++++++++++++++++-------- 1 file changed, 52 insertions(+), 21 deletions(-) diff --git a/lib/src/map/options/map_gestures.dart b/lib/src/map/options/map_gestures.dart index dfee71aaf..a6ea0f64f 100644 --- a/lib/src/map/options/map_gestures.dart +++ b/lib/src/map/options/map_gestures.dart @@ -23,10 +23,6 @@ class MapGestures { required this.trackpadZoom, }); - /// Shortcut constructor to allow all gestures that don't rotate the map. - const MapGestures.noRotation() - : this.byGroup(move: true, zoom: true, rotate: false); - /// This constructor enables all gestures by default. Use this constructor if /// you want have all gestures enabled or disable some gestures only. /// @@ -63,6 +59,58 @@ class MapGestures { this.trackpadZoom = false, }); + /// Enable or disable gestures by groups. + /// - [move] includes all gestures that alter [MapCamera.center]. + /// - [zoom] includes all gestures that alter [MapCamera.zoom]. + /// - [rotate] includes all gestures that alter [MapCamera.rotation]. + /// + /// - Use [MapGestures.allGroups] to follow an blacklist approach. + /// - Use [MapGestures.noGroups] to follow an whitelist approach. + const MapGestures.byGroup({ + required bool move, + required bool zoom, + required bool rotate, + }) : this( + drag: move, + twoFingerMove: move, + flingAnimation: move, + doubleTapDragZoom: zoom, + doubleTapZoomIn: zoom, + scrollWheelZoom: zoom, + twoFingerZoom: zoom, + twoFingerRotate: rotate, + keyTriggerDragRotate: rotate, + trackpadZoom: zoom, + ); + + /// Enable gestures by groups. + /// - [move] includes all gestures that alter [MapCamera.center]. + /// - [zoom] includes all gestures that alter [MapCamera.zoom]. + /// - [rotate] includes all gestures that alter [MapCamera.rotation]. + /// + /// Every group is enabled by defaults when using this + /// constructor (blacklist approach). If you want to allow only certain + /// groups, use [MapGestures.noGroups] instead. + const MapGestures.allGroups({ + bool move = true, + bool zoom = true, + bool rotate = true, + }) : this.byGroup(move: move, zoom: zoom, rotate: rotate); + + /// Disable gestures by groups. + /// - [move] includes all gestures that alter [MapCamera.center]. + /// - [zoom] includes all gestures that alter [MapCamera.zoom]. + /// - [rotate] includes all gestures that alter [MapCamera.rotation]. + /// + /// Every group is disabled by default when using this + /// constructor (whitelist approach). If you want to allow only certain + /// groups, use [MapGestures.allGroups] instead. + const MapGestures.noneGroups({ + bool move = false, + bool zoom = false, + bool rotate = false, + }) : this.byGroup(move: move, zoom: zoom, rotate: rotate); + /// This constructor supports bitfield operations on the static fields /// from [InteractiveFlag]. factory MapGestures.bitfield(int flags) { @@ -150,23 +198,6 @@ class MapGestures { trackpadZoom: trackpadZoom ?? this.trackpadZoom, ); - const MapGestures.byGroup({ - required bool move, - required bool zoom, - required bool rotate, - }) : this( - drag: move, - twoFingerMove: move, - flingAnimation: move, - doubleTapDragZoom: zoom, - doubleTapZoomIn: zoom, - scrollWheelZoom: zoom, - twoFingerZoom: zoom, - twoFingerRotate: rotate, - keyTriggerDragRotate: rotate, - trackpadZoom: zoom, - ); - @override bool operator ==(Object other) => identical(this, other) || From 51fa6160b5f8a8ed3e59edb80b55e54241c97c2b Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Sun, 21 Jan 2024 14:25:02 +0100 Subject: [PATCH 094/102] change to noneByGroup, allByGroup --- lib/src/map/options/map_gestures.dart | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/src/map/options/map_gestures.dart b/lib/src/map/options/map_gestures.dart index a6ea0f64f..e76c47f55 100644 --- a/lib/src/map/options/map_gestures.dart +++ b/lib/src/map/options/map_gestures.dart @@ -64,8 +64,8 @@ class MapGestures { /// - [zoom] includes all gestures that alter [MapCamera.zoom]. /// - [rotate] includes all gestures that alter [MapCamera.rotation]. /// - /// - Use [MapGestures.allGroups] to follow an blacklist approach. - /// - Use [MapGestures.noGroups] to follow an whitelist approach. + /// - Use [MapGestures.allByGroup] to follow an blacklist approach. + /// - Use [MapGestures.noneByGroup] to follow an whitelist approach. const MapGestures.byGroup({ required bool move, required bool zoom, @@ -90,8 +90,8 @@ class MapGestures { /// /// Every group is enabled by defaults when using this /// constructor (blacklist approach). If you want to allow only certain - /// groups, use [MapGestures.noGroups] instead. - const MapGestures.allGroups({ + /// groups, use [MapGestures.noneByGroup] instead. + const MapGestures.allByGroup({ bool move = true, bool zoom = true, bool rotate = true, @@ -104,8 +104,8 @@ class MapGestures { /// /// Every group is disabled by default when using this /// constructor (whitelist approach). If you want to allow only certain - /// groups, use [MapGestures.allGroups] instead. - const MapGestures.noneGroups({ + /// groups, use [MapGestures.allByGroup] instead. + const MapGestures.noneByGroup({ bool move = false, bool zoom = false, bool rotate = false, From bacd10d7c6d0fd08d8680243cc3f9b19c652621c Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Sun, 21 Jan 2024 19:03:53 +0100 Subject: [PATCH 095/102] additional changes to merge upstream --- .../layer/tile_layer/tile_update_event.dart | 5 +- .../tile_layer/wms_tile_layer_options.dart | 2 +- .../controller/events/map_event_source.dart | 69 +++++++++++++++++-- .../map/gestures/map_interactive_viewer.dart | 9 ++- lib/src/map/options/interaction_options.dart | 4 +- lib/src/map/options/map_gestures.dart | 2 + lib/src/misc/center_zoom.dart | 32 --------- lib/src/misc/position.dart | 32 --------- 8 files changed, 83 insertions(+), 72 deletions(-) delete mode 100644 lib/src/misc/center_zoom.dart delete mode 100644 lib/src/misc/position.dart diff --git a/lib/src/layer/tile_layer/tile_update_event.dart b/lib/src/layer/tile_layer/tile_update_event.dart index e21f8c913..2380494c7 100644 --- a/lib/src/layer/tile_layer/tile_update_event.dart +++ b/lib/src/layer/tile_layer/tile_update_event.dart @@ -78,8 +78,11 @@ class TileUpdateEvent { /// Checks if the [MapEvent] has been caused by a tap. bool wasTriggeredByTap() => mapEvent is MapEventTap || + mapEvent is MapEventLongPress || mapEvent is MapEventSecondaryTap || - mapEvent is MapEventLongPress; + mapEvent is MapEventSecondaryLongPress || + mapEvent is MapEventTertiaryTap || + mapEvent is MapEventTertiaryLongPress; @override String toString() => diff --git a/lib/src/layer/tile_layer/wms_tile_layer_options.dart b/lib/src/layer/tile_layer/wms_tile_layer_options.dart index b2bf075ad..65413bbe2 100644 --- a/lib/src/layer/tile_layer/wms_tile_layer_options.dart +++ b/lib/src/layer/tile_layer/wms_tile_layer_options.dart @@ -1,6 +1,6 @@ part of 'tile_layer.dart'; -/// Options for the [] +/// Options for the WMS [TileLayer]. @immutable class WMSTileLayerOptions { static const service = 'WMS'; diff --git a/lib/src/map/controller/events/map_event_source.dart b/lib/src/map/controller/events/map_event_source.dart index 4d7f80209..ca6c209c2 100644 --- a/lib/src/map/controller/events/map_event_source.dart +++ b/lib/src/map/controller/events/map_event_source.dart @@ -1,30 +1,91 @@ /// Event sources which are used to identify different types of /// [MapEvent] events enum MapEventSource { + /// The [MapEvent] is caused programmatically by the [MapController]. mapController, + + /// The [MapEvent] is caused by a tap gesture. + /// (e.g. a click on the left mouse button or a tap on the touchscreen) tap, + + /// The [MapEvent] is caused by a secondary tap gesture. + /// (e.g. a click on the right mouse button) secondaryTap, + + /// The [MapEvent] is caused by a tertiary tap gesture + /// (e.g. click on the mouse scroll wheel). + tertiaryTap, + + /// The [MapEvent] is caused by a long press gesture. longPress, + + /// The [MapEvent] is caused by a long press gesture on the secondary button + /// (e.g. the right mouse button). + secondaryLongPressed, + + /// The [MapEvent] is caused by a long press gesture on the tertiary button + /// (e.g. the mouse scroll wheel). + tertiaryLongPress, + + /// The [MapEvent] is caused by a double tap gesture. doubleTap, + + /// The [MapEvent] is caused by a double tap and hold gesture. doubleTapHold, + + /// The [MapEvent] is caused by the start of a drag gesture. dragStart, + + /// The [MapEvent] is caused by a drag update gesture. onDrag, + + /// The [MapEvent] is caused by the end of a drag gesture. dragEnd, + + /// The [MapEvent] is caused by the start of a two finger gesture. twoFingerStart, + + /// The [MapEvent] is caused by a two finger gesture update. onTwoFinger, + + /// The [MapEvent] is caused by a the end of a two finger gesture. twoFingerEnd, + + /// The [MapEvent] is caused by the [AnimationController] while performing + /// the fling gesture. flingAnimationController, + + /// The [MapEvent] is caused by the [AnimationController] while performing + /// the double tap zoom in animation. doubleTapZoomAnimationController, + + /// The [MapEvent] is caused by a change of the interactive flags. interactiveFlagsChanged, + + /// The [MapEvent] is caused by calling fitCamera. fitCamera, + + /// The [MapEvent] is caused by a custom source. custom, + + /// The [MapEvent] is caused by a scroll wheel zoom gesture. scrollWheel, + + /// The [MapEvent] is caused by a size change of the [FlutterMap] constraints. nonRotatedSizeChange, - tertiaryTap, - tertiaryLongPress, - secondaryLongPressed, + + /// The [MapEvent] is caused by the start of a key-press and drag gesture + /// (e.g. CTRL + drag to rotate the map). keyTriggerDragRotateStart, - keyTriggerDragRotateEnd, + + /// The [MapEvent] is caused by a key-press and drag gesture + /// (e.g. CTRL + drag to rotate the map). keyTriggerDragRotate, + + /// The [MapEvent] is caused by the end of a key-press and drag gesture + /// (e.g. CTRL + drag to rotate the map). + keyTriggerDragRotateEnd, + + /// The [MapEvent] is caused by the trackpad / touchpad of the device. trackpad, } diff --git a/lib/src/map/gestures/map_interactive_viewer.dart b/lib/src/map/gestures/map_interactive_viewer.dart index 4b76ee5a3..c38dbf964 100644 --- a/lib/src/map/gestures/map_interactive_viewer.dart +++ b/lib/src/map/gestures/map_interactive_viewer.dart @@ -5,10 +5,16 @@ import 'package:flutter_map/src/map/gestures/services/base_services.dart'; /// The [MapInteractiveViewer] widget contains the [GestureDetector] and /// [Listener] to handle gesture inputs. +/// It applies interactions (gestures/scroll/taps etc) to the +/// current [MapCamera] via the internal [controller]. class MapInteractiveViewer extends StatefulWidget { + /// The builder callback for the child widget. final ChildBuilder builder; + + /// Reference to the [MapControllerImpl]. final MapControllerImpl controller; + /// Create a new [MapInteractiveViewer] instance. const MapInteractiveViewer({ super.key, required this.builder, @@ -245,7 +251,8 @@ class MapInteractiveViewerState extends State ); } - /// Used by the internal map controller to update interaction gestures + /// Perform all required actions when the [InteractionOptions] have changed. + /// Used by the internal map controller to update interaction gestures. void updateGestures(MapGestures? oldGestures, MapGestures newGestures) { if (oldGestures == newGestures) return; if (newGestures.twoFingerMove || diff --git a/lib/src/map/options/interaction_options.dart b/lib/src/map/options/interaction_options.dart index 777d851d4..d90f613e9 100644 --- a/lib/src/map/options/interaction_options.dart +++ b/lib/src/map/options/interaction_options.dart @@ -2,7 +2,7 @@ import 'package:flutter/services.dart'; import 'package:flutter_map/src/map/options/map_gestures.dart'; import 'package:meta/meta.dart'; -/// Set interation options for input gestures. +/// Set interaction options for input gestures. /// Most commonly used is [InteractionOptions.gestures]. @immutable final class InteractionOptions { @@ -65,6 +65,8 @@ final class InteractionOptions { LogicalKeyboardKey.shiftRight, ]; + /// Create a new [InteractionOptions] instance to be used + /// in [MapOptions.interactionOptions]. const InteractionOptions({ this.gestures = const MapGestures.all(), this.twoFingerRotateThreshold = 0.1, diff --git a/lib/src/map/options/map_gestures.dart b/lib/src/map/options/map_gestures.dart index e76c47f55..2bfe89ca6 100644 --- a/lib/src/map/options/map_gestures.dart +++ b/lib/src/map/options/map_gestures.dart @@ -244,6 +244,8 @@ class MapGestures { abstract class InteractiveFlag { const InteractiveFlag._(); + /// All available interactive flags, use as `flags: InteractiveFlag.all` to + /// enable all gestures. static const int all = drag | flingAnimation | twoFingerMove | diff --git a/lib/src/misc/center_zoom.dart b/lib/src/misc/center_zoom.dart deleted file mode 100644 index cc563e558..000000000 --- a/lib/src/misc/center_zoom.dart +++ /dev/null @@ -1,32 +0,0 @@ -import 'package:latlong2/latlong.dart'; -import 'package:meta/meta.dart'; - -/// Geographical point with applied zoom level -@immutable -class CenterZoom { - /// Coordinates for zoomed point - final LatLng center; - - /// Zoom value - final double zoom; - - /// Create a new [CenterZoom] object by setting all its values. - const CenterZoom({required this.center, required this.zoom}); - - /// Wither that returns a new [CenterZoom] object with an updated map center. - CenterZoom withCenter(LatLng center) => - CenterZoom(center: center, zoom: zoom); - - /// Wither that returns a new [CenterZoom] object with an updated zoom value. - CenterZoom withZoom(double zoom) => CenterZoom(center: center, zoom: zoom); - - @override - int get hashCode => Object.hash(center, zoom); - - @override - bool operator ==(Object other) => - other is CenterZoom && other.center == center && other.zoom == zoom; - - @override - String toString() => 'CenterZoom(center: $center, zoom: $zoom)'; -} diff --git a/lib/src/misc/position.dart b/lib/src/misc/position.dart deleted file mode 100644 index 116b57b97..000000000 --- a/lib/src/misc/position.dart +++ /dev/null @@ -1,32 +0,0 @@ -import 'package:flutter_map/src/geo/latlng_bounds.dart'; -import 'package:latlong2/latlong.dart'; -import 'package:meta/meta.dart'; - -// ignore_for_file: public_member_api_docs - -@immutable -class MapPosition { - final LatLng? center; - final LatLngBounds? bounds; - final double? zoom; - final bool hasGesture; - - const MapPosition({ - this.center, - this.bounds, - this.zoom, - this.hasGesture = false, - }); - - @override - int get hashCode => Object.hash(center, bounds, zoom); - - @override - bool operator ==(Object other) => - other is MapPosition && - other.center == center && - other.bounds == bounds && - other.zoom == zoom; -} - -typedef PositionCallback = void Function(MapPosition position, bool hasGesture); From bcf30c52d05077cd8a815f0ade95d636fbc823a6 Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Sun, 21 Jan 2024 19:34:02 +0100 Subject: [PATCH 096/102] add documentation --- lib/src/map/gestures/map_interactive_viewer.dart | 6 ++---- lib/src/map/gestures/services/double_tap.dart | 1 + .../gestures/services/double_tap_drag_zoom.dart | 3 +++ lib/src/map/gestures/services/drag.dart | 2 ++ .../gestures/services/key_trigger_drag_rotate.dart | 14 +++++++++----- lib/src/map/gestures/services/long_press.dart | 4 ++++ .../map/gestures/services/scroll_wheel_zoom.dart | 1 + lib/src/map/gestures/services/tap.dart | 3 +++ .../gestures/services/trackpad_legacy_zoom.dart | 2 ++ lib/src/map/gestures/services/trackpad_zoom.dart | 4 ++++ lib/src/map/gestures/services/two_finger.dart | 2 ++ lib/src/map/options/map_gestures.dart | 11 +++++++++++ lib/src/map/options/map_options.dart | 2 ++ 13 files changed, 46 insertions(+), 9 deletions(-) diff --git a/lib/src/map/gestures/map_interactive_viewer.dart b/lib/src/map/gestures/map_interactive_viewer.dart index c38dbf964..40a170e0e 100644 --- a/lib/src/map/gestures/map_interactive_viewer.dart +++ b/lib/src/map/gestures/map_interactive_viewer.dart @@ -291,10 +291,8 @@ class MapInteractiveViewerState extends State } if (newGestures.keyTriggerDragRotate) { - _keyTriggerDragRotate = KeyTriggerDragRotateGestureService( - controller: _controller, - keys: _options.interactionOptions.keyTriggerDragRotateKeys, - ); + _keyTriggerDragRotate = + KeyTriggerDragRotateGestureService(controller: _controller); } else { _keyTriggerDragRotate = null; } diff --git a/lib/src/map/gestures/services/double_tap.dart b/lib/src/map/gestures/services/double_tap.dart index c603eb358..bf46882c1 100644 --- a/lib/src/map/gestures/services/double_tap.dart +++ b/lib/src/map/gestures/services/double_tap.dart @@ -3,6 +3,7 @@ part of 'base_services.dart'; /// Service to handle double tap gestures to perform the /// double-tap-zoom-in gesture. class DoubleTapGestureService extends _SingleShotGestureService { + /// Create a new service for the double-tap gesture. DoubleTapGestureService({required super.controller}); /// A double tap gesture tap has been registered diff --git a/lib/src/map/gestures/services/double_tap_drag_zoom.dart b/lib/src/map/gestures/services/double_tap_drag_zoom.dart index f24ac3f26..23d3e7a0a 100644 --- a/lib/src/map/gestures/services/double_tap_drag_zoom.dart +++ b/lib/src/map/gestures/services/double_tap_drag_zoom.dart @@ -4,10 +4,13 @@ part of 'base_services.dart'; /// and out with a single finger / one hand. class DoubleTapDragZoomGestureService extends _BaseGestureService implements _ProgressableGestureService { + /// Set to true if the [DoubleTapDragZoomGestureService] consumes the gesture + /// and prevents the normal double-tap logic from being executed. bool isActive = false; Offset? _focalLocalStart; double? _mapZoomStart; + /// Create a new service for the double-tap-drag-zoom gesture. DoubleTapDragZoomGestureService({required super.controller}); /// Called when the gesture is started, stores important values. diff --git a/lib/src/map/gestures/services/drag.dart b/lib/src/map/gestures/services/drag.dart index f5d9e7906..c273c0f75 100644 --- a/lib/src/map/gestures/services/drag.dart +++ b/lib/src/map/gestures/services/drag.dart @@ -9,8 +9,10 @@ class DragGestureService extends _BaseGestureService bool get _flingEnabled => _options.interactionOptions.gestures.flingAnimation; + /// Create a new service to handle drag gestures. DragGestureService({required super.controller}); + /// Returns true if the screen currently gets dragged. bool get isActive => _lastLocalFocal != null; /// Called when the gesture is started, stores important values. 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 746037e57..3a262f49a 100644 --- a/lib/src/map/gestures/services/key_trigger_drag_rotate.dart +++ b/lib/src/map/gestures/services/key_trigger_drag_rotate.dart @@ -6,13 +6,17 @@ part of 'base_services.dart'; /// Can't extend from [_ProgressableGestureService] because of different /// method signatures. class KeyTriggerDragRotateGestureService extends _BaseGestureService { + /// Set to true if the gesture service is marked as active and consumes the + /// drag updates. bool isActive = false; - final List keys; - KeyTriggerDragRotateGestureService({ - required super.controller, - required this.keys, - }); + /// 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. + KeyTriggerDragRotateGestureService({required super.controller}); /// Called when the gesture is started, stores important values. void start() { diff --git a/lib/src/map/gestures/services/long_press.dart b/lib/src/map/gestures/services/long_press.dart index efc14e28d..05b94865d 100644 --- a/lib/src/map/gestures/services/long_press.dart +++ b/lib/src/map/gestures/services/long_press.dart @@ -4,6 +4,7 @@ part of 'base_services.dart'; /// [MapOptions.onLongPress] callback. class LongPressGestureService extends _BaseGestureService implements _BaseLongPressGestureService { + /// Create a new service that handles long press gestures. LongPressGestureService({required super.controller}); /// Called when a long press gesture with a primary button has been @@ -27,6 +28,8 @@ class LongPressGestureService extends _BaseGestureService /// [MapOptions.onSecondaryLongPress] callback. class SecondaryLongPressGestureService extends _BaseGestureService implements _BaseLongPressGestureService { + /// Create a new service that handles long press gestures with the + /// secondary button. SecondaryLongPressGestureService({required super.controller}); /// Called when a long press gesture with a primary button has been @@ -50,6 +53,7 @@ class SecondaryLongPressGestureService extends _BaseGestureService /// [MapOptions.onTertiaryLongPress] callback. class TertiaryLongPressGestureService extends _BaseGestureService implements _BaseLongPressGestureService { + /// Creates a service that handles long press gestures by the tertiary button. TertiaryLongPressGestureService({required super.controller}); /// A long press on the tertiary button has happen (e.g. click and hold on diff --git a/lib/src/map/gestures/services/scroll_wheel_zoom.dart b/lib/src/map/gestures/services/scroll_wheel_zoom.dart index 5f97e173c..365dce98f 100644 --- a/lib/src/map/gestures/services/scroll_wheel_zoom.dart +++ b/lib/src/map/gestures/services/scroll_wheel_zoom.dart @@ -2,6 +2,7 @@ part of 'base_services.dart'; /// Service to handle the scroll wheel gesture to zoom the map in or out. class ScrollWheelZoomGestureService extends _BaseGestureService { + /// Creates a service that handles scroll wheel zooming. ScrollWheelZoomGestureService({required super.controller}); /// Shortcut for the zoom velocity of the scroll wheel diff --git a/lib/src/map/gestures/services/tap.dart b/lib/src/map/gestures/services/tap.dart index 11a0cb8f3..8a0d616d0 100644 --- a/lib/src/map/gestures/services/tap.dart +++ b/lib/src/map/gestures/services/tap.dart @@ -2,6 +2,7 @@ part of 'base_services.dart'; /// Service to handle tap gestures for the [MapOptions.onTap] callback. class TapGestureService extends _SingleShotGestureService { + /// Creates a service that handles short tap gestures with the primary button. TapGestureService({required super.controller}); /// A tap with a primary button has occurred. @@ -27,6 +28,7 @@ class TapGestureService extends _SingleShotGestureService { /// Service to handle secondary tap gestures for the /// [MapOptions.onSecondaryTap] callback. class SecondaryTapGestureService extends _SingleShotGestureService { + /// Creates a service that handles short tap gestures by the secondary button. SecondaryTapGestureService({required super.controller}); /// A tap with a secondary button has occurred. @@ -52,6 +54,7 @@ class SecondaryTapGestureService extends _SingleShotGestureService { /// Service to handle tertiary tap gestures for the /// [MapOptions.onTertiaryTap] callback. class TertiaryTapGestureService extends _SingleShotGestureService { + /// Creates a service that handles short tap gestures by the tertiary button. TertiaryTapGestureService({required super.controller}); /// A tertiary tap gesture has happen (e.g. click on the mouse scroll wheel) diff --git a/lib/src/map/gestures/services/trackpad_legacy_zoom.dart b/lib/src/map/gestures/services/trackpad_legacy_zoom.dart index ddcabad67..662c62cca 100644 --- a/lib/src/map/gestures/services/trackpad_legacy_zoom.dart +++ b/lib/src/map/gestures/services/trackpad_legacy_zoom.dart @@ -17,10 +17,12 @@ part of 'base_services.dart'; class TrackpadLegacyZoomGestureService extends _BaseGestureService { static const _velocityAdjustment = 4.5; + /// Creates a service that handles the legacy trackpad zoom. TrackpadLegacyZoomGestureService({required super.controller}); double get _velocity => _options.interactionOptions.trackpadZoomVelocity; + /// Called if a [PointerScaleEvent] gets submitted by the [Listener] widget. void submit(PointerScaleEvent details) { if (details.scale == 1) return; diff --git a/lib/src/map/gestures/services/trackpad_zoom.dart b/lib/src/map/gestures/services/trackpad_zoom.dart index 361cdf5e6..74a9aee0f 100644 --- a/lib/src/map/gestures/services/trackpad_zoom.dart +++ b/lib/src/map/gestures/services/trackpad_zoom.dart @@ -12,12 +12,14 @@ class TrackpadZoomGestureService extends _BaseGestureService { LatLng? _startFocalLatLng; Offset? _lastLocalFocal; + /// Create a new service that handles trackpad zoom gestures. TrackpadZoomGestureService({required super.controller}); double get _velocity => _options.interactionOptions.trackpadZoomVelocity; bool get _moveEnabled => _options.interactionOptions.gestures.drag; + /// Callback method for [Listener.onPointerPanZoomStart]. void start(PointerPanZoomStartEvent details) { _lastScale = 1; _startLocalFocal = details.localPosition; @@ -25,6 +27,7 @@ class TrackpadZoomGestureService extends _BaseGestureService { _lastLocalFocal = _startLocalFocal; } + /// Callback method for [Listener.onPointerPanZoomUpdate]. void update(PointerPanZoomUpdateEvent details) { if (_startFocalLatLng == null || _startLocalFocal == null || @@ -61,6 +64,7 @@ class TrackpadZoomGestureService extends _BaseGestureService { ); } + /// Callback method for [Listener.onPointerPanZoomEnd]. void end(PointerPanZoomEndEvent details) { _startLocalFocal = null; _startFocalLatLng = null; diff --git a/lib/src/map/gestures/services/two_finger.dart b/lib/src/map/gestures/services/two_finger.dart index 7ec54c6b0..305c0a2e4 100644 --- a/lib/src/map/gestures/services/two_finger.dart +++ b/lib/src/map/gestures/services/two_finger.dart @@ -41,6 +41,8 @@ class TwoFingerGesturesService extends _BaseGestureService double get _zoomThreshold => _options.interactionOptions.twoFingerZoomThreshold; + /// Create a new two-finger gesture service that handels all multi-point + /// touchscreen gestures. TwoFingerGesturesService({required super.controller}); /// Initialize gesture, called when gesture has started. diff --git a/lib/src/map/options/map_gestures.dart b/lib/src/map/options/map_gestures.dart index 2bfe89ca6..d5eb94fb3 100644 --- a/lib/src/map/options/map_gestures.dart +++ b/lib/src/map/options/map_gestures.dart @@ -1,6 +1,7 @@ import 'package:flutter_map/flutter_map.dart'; import 'package:meta/meta.dart'; +/// The available map gestures to move, zoom or rotate the map. @immutable class MapGestures { /// Use this constructor if you want to set all gestures manually. @@ -257,6 +258,8 @@ abstract class InteractiveFlag { trackpadZoom | keyTriggerDragRotate; + /// No enabled interactive flags, use as `flags: InteractiveFlag.none` to + /// have a non interactive map. static const int none = 0; /// Enable panning with a single finger or cursor @@ -267,16 +270,22 @@ abstract class InteractiveFlag { /// Enable panning with multiple fingers static const int twoFingerMove = 1 << 2; + + /// Enable panning with multiple fingers @Deprecated('Renamed to twoFingerMove') static const int pinchMove = twoFingerMove; /// Enable zooming with a multi-finger pinch gesture static const int twoFingerZoom = 1 << 3; + + /// Enable zooming with a multi-finger pinch gesture @Deprecated('Renamed to twoFingerZoom') static const int pinchZoom = twoFingerZoom; /// Enable zooming with a single-finger double tap gesture static const int doubleTapZoomIn = 1 << 4; + + /// Enable zooming with a single-finger double tap gesture @Deprecated('Renamed to doubleTapZoomIn') static const int doubleTapZoom = doubleTapZoomIn; @@ -293,6 +302,8 @@ abstract class InteractiveFlag { /// For controlling cursor/keyboard rotation, see /// [InteractionOptions.cursorKeyboardRotationOptions]. static const int twoFingerRotate = 1 << 7; + + /// Enable rotation with two-finger twist gesture. @Deprecated('Renamed to twoFingerRotate') static const int rotate = twoFingerRotate; diff --git a/lib/src/map/options/map_options.dart b/lib/src/map/options/map_options.dart index 735ee89ca..6999a0c71 100644 --- a/lib/src/map/options/map_options.dart +++ b/lib/src/map/options/map_options.dart @@ -5,6 +5,8 @@ import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_map/src/map/inherited_model.dart'; import 'package:latlong2/latlong.dart'; +/// The map options are used to configure the map settings. +/// It gets provided to the map widget as the [FlutterMap.options] parameter. @immutable class MapOptions { /// The Coordinate Reference System, defaults to [Epsg3857]. From 6155f3afcd4434031c4ac970b6e1fcc61826d82d Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Sun, 21 Jan 2024 19:41:36 +0100 Subject: [PATCH 097/102] add `trackpadZoom` to example app --- example/lib/pages/gestures_page.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/example/lib/pages/gestures_page.dart b/example/lib/pages/gestures_page.dart index 7803c6a0a..c8b7c3ee3 100644 --- a/example/lib/pages/gestures_page.dart +++ b/example/lib/pages/gestures_page.dart @@ -25,6 +25,7 @@ class _GesturesPageState extends State { InteractiveFlag.scrollWheelZoom: 'Scroll', InteractiveFlag.doubleTapZoomIn: 'Double tap', InteractiveFlag.doubleTapDragZoom: 'Double tap+drag', + InteractiveFlag.trackpadZoom: 'Touchpad zoom', }, 'Rotation': { InteractiveFlag.twoFingerRotate: 'Twist', From e8d45777a82b288cd217228e18b4811beab7c3f0 Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Sun, 21 Jan 2024 20:23:52 +0100 Subject: [PATCH 098/102] fix overflow in example app --- example/lib/pages/gestures_page.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/lib/pages/gestures_page.dart b/example/lib/pages/gestures_page.dart index c8b7c3ee3..79f6e9b80 100644 --- a/example/lib/pages/gestures_page.dart +++ b/example/lib/pages/gestures_page.dart @@ -48,7 +48,7 @@ class _GesturesPageState extends State { child: Column( children: [ Flex( - direction: screenWidth >= 650 ? Axis.horizontal : Axis.vertical, + direction: screenWidth >= 750 ? Axis.horizontal : Axis.vertical, mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: availableFlags.entries From 7279c45d88e1b3d1ce917d5291354cb46b365b46 Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Tue, 23 Jan 2024 10:17:02 +0100 Subject: [PATCH 099/102] Update full_coverage_test.dart --- test/full_coverage_test.dart | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/full_coverage_test.dart b/test/full_coverage_test.dart index 09771a7bc..7883dfea0 100644 --- a/test/full_coverage_test.dart +++ b/test/full_coverage_test.dart @@ -51,11 +51,8 @@ import 'package:flutter_map/src/map/options/map_gestures.dart'; import 'package:flutter_map/src/map/options/map_options.dart'; import 'package:flutter_map/src/map/widget.dart'; import 'package:flutter_map/src/misc/bounds.dart'; -import 'package:flutter_map/src/misc/center_zoom.dart'; import 'package:flutter_map/src/misc/extensions.dart'; -import 'package:flutter_map/src/misc/move_and_rotate_result.dart'; import 'package:flutter_map/src/misc/offsets.dart'; -import 'package:flutter_map/src/misc/position.dart'; import 'package:flutter_map/src/misc/simplify.dart'; void main() {} From 09f9e13e85132e9df5316ac94c72ec6d237468c4 Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Tue, 23 Jan 2024 19:34:35 +0100 Subject: [PATCH 100/102] fix doc string --- lib/src/map/gestures/services/key_trigger_drag_rotate.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 3a262f49a..821d239d7 100644 --- a/lib/src/map/gestures/services/key_trigger_drag_rotate.dart +++ b/lib/src/map/gestures/services/key_trigger_drag_rotate.dart @@ -1,7 +1,7 @@ part of 'base_services.dart'; /// Service to handle the key-trigger and drag gesture to rotate the map. This -/// is by default a CTRL +. +/// is by default a CTRL + drag. /// /// Can't extend from [_ProgressableGestureService] because of different /// method signatures. From 6213a4fe7cd30088b141f29792755ca6c27e0031 Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Sat, 3 Feb 2024 12:49:15 +0100 Subject: [PATCH 101/102] feat(gestures): fling animation as drag gesture option (#1814) --- example/lib/pages/gestures_page.dart | 1 - lib/src/map/gestures/services/drag.dart | 10 +++++++++- lib/src/map/options/interaction_options.dart | 6 ++++++ lib/src/map/options/map_gestures.dart | 16 ---------------- 4 files changed, 15 insertions(+), 18 deletions(-) diff --git a/example/lib/pages/gestures_page.dart b/example/lib/pages/gestures_page.dart index 79f6e9b80..cc59d7537 100644 --- a/example/lib/pages/gestures_page.dart +++ b/example/lib/pages/gestures_page.dart @@ -17,7 +17,6 @@ class _GesturesPageState extends State { static const availableFlags = { 'Movement': { InteractiveFlag.drag: 'Drag', - InteractiveFlag.flingAnimation: 'Fling', InteractiveFlag.twoFingerMove: 'Two finger drag', }, 'Zooming': { diff --git a/lib/src/map/gestures/services/drag.dart b/lib/src/map/gestures/services/drag.dart index c273c0f75..2c0981085 100644 --- a/lib/src/map/gestures/services/drag.dart +++ b/lib/src/map/gestures/services/drag.dart @@ -7,7 +7,7 @@ class DragGestureService extends _BaseGestureService Offset? _lastLocalFocal; Offset? _focalStartLocal; - bool get _flingEnabled => _options.interactionOptions.gestures.flingAnimation; + bool get _flingEnabled => _options.interactionOptions.dragFlingAnimation; /// Create a new service to handle drag gestures. DragGestureService({required super.controller}); @@ -65,6 +65,14 @@ class DragGestureService extends _BaseGestureService _lastLocalFocal = null; _focalStartLocal = null; + flingAnimation(details, focalStartLocal, lastLocalFocal); + } + + void flingAnimation( + ScaleEndDetails details, + Offset focalStartLocal, + Offset lastLocalFocal, + ) { if (!_flingEnabled) return; final magnitude = details.velocity.pixelsPerSecond.distance; diff --git a/lib/src/map/options/interaction_options.dart b/lib/src/map/options/interaction_options.dart index d90f613e9..babef7c39 100644 --- a/lib/src/map/options/interaction_options.dart +++ b/lib/src/map/options/interaction_options.dart @@ -19,6 +19,11 @@ final class InteractionOptions { /// For more information see the documentation on [InteractiveFlag]. final MapGestures gestures; + /// Enable fling animation after panning if velocity is great enough. + /// + /// Defaults to true, this requires the `drag` gesture to be enabled. + final bool dragFlingAnimation; + /// Map starts to rotate when [twoFingerRotateThreshold] has been achieved /// or another multi finger gesture wins. /// Default is 0.1 @@ -74,6 +79,7 @@ final class InteractionOptions { this.twoFingerMoveThreshold = 3.0, this.scrollWheelVelocity = 0.01, this.trackpadZoomVelocity = 0.5, + this.dragFlingAnimation = true, this.keyTriggerDragRotateKeys = defaultKeyTriggerDragRotateKeys, }) : assert( twoFingerRotateThreshold >= 0.0, diff --git a/lib/src/map/options/map_gestures.dart b/lib/src/map/options/map_gestures.dart index d5eb94fb3..1205b16a9 100644 --- a/lib/src/map/options/map_gestures.dart +++ b/lib/src/map/options/map_gestures.dart @@ -13,7 +13,6 @@ class MapGestures { /// use [InteractiveFlags.bitfield] instead. const MapGestures({ required this.drag, - required this.flingAnimation, required this.twoFingerMove, required this.twoFingerZoom, required this.doubleTapZoomIn, @@ -31,7 +30,6 @@ class MapGestures { /// [InteractiveFlags.none] constructor instead. const MapGestures.all({ this.drag = true, - this.flingAnimation = true, this.twoFingerMove = true, this.twoFingerZoom = true, this.doubleTapZoomIn = true, @@ -49,7 +47,6 @@ class MapGestures { /// [InteractiveFlags.all] constructor instead. const MapGestures.none({ this.drag = false, - this.flingAnimation = false, this.twoFingerMove = false, this.twoFingerZoom = false, this.doubleTapZoomIn = false, @@ -74,7 +71,6 @@ class MapGestures { }) : this( drag: move, twoFingerMove: move, - flingAnimation: move, doubleTapDragZoom: zoom, doubleTapZoomIn: zoom, scrollWheelZoom: zoom, @@ -117,8 +113,6 @@ class MapGestures { factory MapGestures.bitfield(int flags) { return MapGestures( drag: InteractiveFlag.hasFlag(flags, InteractiveFlag.drag), - flingAnimation: - InteractiveFlag.hasFlag(flags, InteractiveFlag.flingAnimation), twoFingerMove: InteractiveFlag.hasFlag(flags, InteractiveFlag.twoFingerMove), twoFingerZoom: @@ -141,9 +135,6 @@ class MapGestures { /// Enable panning with a single finger or cursor final bool drag; - /// Enable fling animation after panning if velocity is great enough. - final bool flingAnimation; - /// Enable panning with multiple fingers final bool twoFingerMove; @@ -188,7 +179,6 @@ class MapGestures { }) => MapGestures( drag: drag ?? this.drag, - flingAnimation: flingAnimation ?? this.flingAnimation, twoFingerZoom: twoFingerZoom ?? this.twoFingerZoom, twoFingerMove: twoFingerMove ?? this.twoFingerMove, doubleTapZoomIn: doubleTapZoomIn ?? this.doubleTapZoomIn, @@ -205,7 +195,6 @@ class MapGestures { other is MapGestures && runtimeType == other.runtimeType && drag == other.drag && - flingAnimation == other.flingAnimation && twoFingerMove == other.twoFingerMove && twoFingerZoom == other.twoFingerZoom && doubleTapZoomIn == other.doubleTapZoomIn && @@ -217,7 +206,6 @@ class MapGestures { @override int get hashCode => Object.hash( drag, - flingAnimation, twoFingerMove, twoFingerZoom, doubleTapZoomIn, @@ -248,7 +236,6 @@ abstract class InteractiveFlag { /// All available interactive flags, use as `flags: InteractiveFlag.all` to /// enable all gestures. static const int all = drag | - flingAnimation | twoFingerMove | twoFingerZoom | doubleTapZoomIn | @@ -265,9 +252,6 @@ abstract class InteractiveFlag { /// Enable panning with a single finger or cursor static const int drag = 1 << 0; - /// Enable fling animation after panning if velocity is great enough. - static const int flingAnimation = 1 << 1; - /// Enable panning with multiple fingers static const int twoFingerMove = 1 << 2; From 72fa64e2d699160de3e7b595cf0e5d21a7da62e4 Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Sat, 3 Feb 2024 12:53:24 +0100 Subject: [PATCH 102/102] fix(gestures): double tap zoom events, fling events (#1815) --- example/lib/pages/gestures_page.dart | 2 +- .../map/controller/map_controller_impl.dart | 104 +++++++++++++----- lib/src/map/gestures/services/double_tap.dart | 6 +- 3 files changed, 80 insertions(+), 32 deletions(-) diff --git a/example/lib/pages/gestures_page.dart b/example/lib/pages/gestures_page.dart index cc59d7537..6754c92b7 100644 --- a/example/lib/pages/gestures_page.dart +++ b/example/lib/pages/gestures_page.dart @@ -105,7 +105,7 @@ class _GesturesPageState extends State { Expanded( child: FlutterMap( options: MapOptions( - onMapEvent: (evt) => setState(() => _latestEvent = evt), + onMapEvent: (event) => setState(() => _latestEvent = event), initialCenter: const LatLng(51.5, -0.09), initialZoom: 11, interactionOptions: InteractionOptions( diff --git a/lib/src/map/controller/map_controller_impl.dart b/lib/src/map/controller/map_controller_impl.dart index 9bd8666dd..6f1bc874e 100644 --- a/lib/src/map/controller/map_controller_impl.dart +++ b/lib/src/map/controller/map_controller_impl.dart @@ -22,6 +22,9 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState> Animation? _rotationAnimation; Animation? _flingAnimation; late bool _animationHasGesture; + late MapEventSource _animationSource; + AnimationEndedCallback? _animatedEndedCallback; + AnimationCancelledCallback? _animatedCancelledCallback; late Offset _animationOffset; late Point _flingMapCenterStartPoint; @@ -36,7 +39,9 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState> vsync == null ? null : AnimationController(vsync: vsync), ), ) { - value.animationController?.addListener(_handleAnimation); + value.animationController + ?..addListener(_handleAnimation) + ..addStatusListener(_handleAnimationStatus); } /// Link the viewer state with the controller. This should be done once when @@ -385,7 +390,8 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState> options: value.options, camera: value.camera, animationController: AnimationController(vsync: tickerProvider) - ..addListener(_handleAnimation), + ..addListener(_handleAnimation) + ..addStatusListener(_handleAnimationStatus), ); } else { _animationController.resync(tickerProvider); @@ -418,8 +424,11 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState> required Curve curve, required bool hasGesture, required MapEventSource source, + AnimationEndedCallback? onAnimatedEnded, + AnimationCancelledCallback? onAnimationCancelled, }) { if (newRotation == camera.rotation) { + // if the rotation is the same we just need to move the MapCamera moveAnimatedRaw( newCenter, newZoom, @@ -427,12 +436,12 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState> curve: curve, hasGesture: hasGesture, source: source, + onAnimatedEnded: onAnimatedEnded, + onAnimationCancelled: onAnimationCancelled, ); return; } - // cancel all ongoing animation - _animationController.stop(); - _resetAnimations(); + stopAnimationRaw(); if (newCenter == camera.center && newZoom == camera.zoom) return; @@ -450,6 +459,9 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState> _animationController.duration = duration; _animationHasGesture = hasGesture; _animationOffset = offset; + _animationSource = source; + _animatedCancelledCallback = onAnimationCancelled; + _animatedEndedCallback = onAnimatedEnded; // start the animation from its start _animationController.forward(from: 0); @@ -464,11 +476,10 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState> required Curve curve, required bool hasGesture, required MapEventSource source, + AnimationEndedCallback? onAnimatedEnded, + AnimationCancelledCallback? onAnimationCancelled, }) { - // cancel all ongoing animation - _animationController.stop(); - _resetAnimations(); - + stopAnimationRaw(); if (newRotation == camera.rotation) return; // create the new animation @@ -479,6 +490,9 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState> _animationController.duration = duration; _animationHasGesture = hasGesture; _animationOffset = offset; + _animationSource = source; + _animatedCancelledCallback = onAnimationCancelled; + _animatedEndedCallback = onAnimatedEnded; // start the animation from its start _animationController.forward(from: 0); @@ -488,20 +502,22 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState> /// This is commonly used by other gestures that should stop all /// ongoing movement. void stopAnimationRaw({bool canceled = true}) { - if (isAnimating) _animationController.stop(canceled: canceled); - } - - /// Getter that returns true if the [MapControllerImpl] performs a zoom, - /// drag or rotate animation. - bool get isAnimating => _animationController.isAnimating; - - void _resetAnimations() { + if (isAnimating) { + _animatedCancelledCallback?.call(camera, _animationSource); + _animationController.stop(canceled: canceled); + } _moveAnimation = null; _rotationAnimation = null; _zoomAnimation = null; _flingAnimation = null; + _animatedEndedCallback = null; + _animatedCancelledCallback = null; } + /// Getter that returns true if the [MapControllerImpl] performs a zoom, + /// drag or rotate animation. + bool get isAnimating => _animationController.isAnimating; + /// Fling animation for the map. /// The raw method allows to set all parameters. void flingAnimatedRaw({ @@ -514,13 +530,12 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState> double ratio = 5, required bool hasGesture, }) { - // cancel all ongoing animation - _animationController.stop(); - _resetAnimations(); + stopAnimationRaw(); _animationHasGesture = hasGesture; _animationOffset = offset; _flingMapCenterStartPoint = camera.project(camera.center); + _animationSource = MapEventSource.flingAnimationController; final distance = (Offset.zero & Size(camera.nonRotatedSize.x, camera.nonRotatedSize.y)) @@ -552,11 +567,10 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState> required Curve curve, required bool hasGesture, required MapEventSource source, + AnimationEndedCallback? onAnimatedEnded, + AnimationCancelledCallback? onAnimationCancelled, }) { - // cancel all ongoing animation - _animationController.stop(); - _resetAnimations(); - + stopAnimationRaw(); if (newCenter == camera.center && newZoom == camera.zoom) return; // create the new animation @@ -570,6 +584,9 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState> _animationController.duration = duration; _animationHasGesture = hasGesture; _animationOffset = offset; + _animationSource = source; + _animatedCancelledCallback = onAnimationCancelled; + _animatedEndedCallback = onAnimatedEnded; // start the animation from its start _animationController.forward(from: 0); @@ -592,7 +609,7 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState> camera.unproject(newCenterPoint), camera.zoom, hasGesture: _animationHasGesture, - source: MapEventSource.flingAnimationController, + source: _animationSource, offset: _animationOffset, ); return; @@ -606,7 +623,7 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState> _zoomAnimation?.value ?? camera.zoom, _rotationAnimation!.value, hasGesture: _animationHasGesture, - source: MapEventSource.mapController, + source: _animationSource, offset: _animationOffset, ); } else { @@ -614,7 +631,7 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState> _moveAnimation!.value, _zoomAnimation?.value ?? camera.zoom, hasGesture: _animationHasGesture, - source: MapEventSource.mapController, + source: _animationSource, offset: _animationOffset, ); } @@ -626,7 +643,7 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState> rotateRaw( _rotationAnimation!.value, hasGesture: _animationHasGesture, - source: MapEventSource.mapController, + source: _animationSource, ); } } @@ -637,6 +654,28 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState> value.animationController?.dispose(); super.dispose(); } + + void _handleAnimationStatus(AnimationStatus status) { + if (status == AnimationStatus.completed) { + final event = switch (_animationSource) { + MapEventSource.doubleTapZoomAnimationController => + MapEventDoubleTapZoomEnd( + camera: camera, + source: _animationSource, + ), + MapEventSource.flingAnimationController => MapEventFlingAnimationEnd( + camera: camera, + source: _animationSource, + ), + _ => MapEventMoveEnd( + camera: camera, + source: _animationSource, + ), + }; + emitMapEvent(event); + _animatedEndedCallback?.call(camera, _animationSource); + } + } } /// The state for the [MapControllerImpl] [ValueNotifier]. @@ -659,3 +698,12 @@ class _MapControllerState { animationController: animationController, ); } + +typedef AnimationEndedCallback = void Function( + MapCamera camera, + MapEventSource eventSource, +); +typedef AnimationCancelledCallback = void Function( + MapCamera camera, + MapEventSource eventSource, +); diff --git a/lib/src/map/gestures/services/double_tap.dart b/lib/src/map/gestures/services/double_tap.dart index bf46882c1..3c6a448e6 100644 --- a/lib/src/map/gestures/services/double_tap.dart +++ b/lib/src/map/gestures/services/double_tap.dart @@ -21,7 +21,7 @@ class DoubleTapGestureService extends _SingleShotGestureService { controller.emitMapEvent( MapEventDoubleTapZoomStart( camera: _camera, - source: MapEventSource.doubleTap, + source: MapEventSource.doubleTapZoomAnimationController, ), ); @@ -29,7 +29,7 @@ class DoubleTapGestureService extends _SingleShotGestureService { newCenter, newZoom, hasGesture: true, - source: MapEventSource.doubleTap, + source: MapEventSource.doubleTapZoomAnimationController, curve: Curves.fastOutSlowIn, duration: const Duration(milliseconds: 200), ); @@ -37,7 +37,7 @@ class DoubleTapGestureService extends _SingleShotGestureService { controller.emitMapEvent( MapEventDoubleTapZoomEnd( camera: _camera, - source: MapEventSource.doubleTap, + source: MapEventSource.doubleTapZoomAnimationController, ), );