Skip to content

Commit

Permalink
Added more customization to cursor/keyboard rotation (#1642)
Browse files Browse the repository at this point in the history
  • Loading branch information
JaffaKetchup authored Oct 2, 2023
1 parent caa0787 commit b75166a
Show file tree
Hide file tree
Showing 12 changed files with 312 additions and 205 deletions.
15 changes: 7 additions & 8 deletions example/lib/pages/interactive_test_page.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:flutter_map_example/widgets/drawer.dart';
import 'package:latlong2/latlong.dart';
Expand Down Expand Up @@ -124,13 +123,13 @@ class _InteractiveTestPageState extends State<InteractiveTestPage> {
initialZoom: 11,
interactionOptions: InteractionOptions(
flags: flags,
isCursorRotationKeyboardKeyTrigger: (key) =>
keyboardCursorRotate &&
{
LogicalKeyboardKey.control,
LogicalKeyboardKey.controlLeft,
LogicalKeyboardKey.controlRight
}.contains(key),
cursorKeyboardRotationOptions:
CursorKeyboardRotationOptions(
isKeyTrigger: (key) =>
keyboardCursorRotate &&
CursorKeyboardRotationOptions.defaultTriggerKeys
.contains(key),
),
),
),
children: [
Expand Down
4 changes: 3 additions & 1 deletion lib/flutter_map.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ 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/map_controller.dart';
export 'package:flutter_map/src/map/options.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/widget.dart';
export 'package:flutter_map/src/misc/center_zoom.dart';
export 'package:flutter_map/src/misc/fit_bounds_options.dart';
Expand Down
107 changes: 67 additions & 40 deletions lib/src/gestures/flutter_map_interactive_viewer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ import 'package:flutter_map/src/gestures/map_events.dart';
import 'package:flutter_map/src/gestures/multi_finger_gesture.dart';
import 'package:flutter_map/src/map/camera/camera.dart';
import 'package:flutter_map/src/map/internal_controller.dart';
import 'package:flutter_map/src/map/options.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:flutter_map/src/misc/private/positioned_tap_detector_2.dart';
import 'package:latlong2/latlong.dart';
Expand Down Expand Up @@ -81,16 +83,10 @@ class FlutterMapInteractiveViewerState
late Animation<double> _doubleTapZoomAnimation;
late Animation<LatLng> _doubleTapCenterAnimation;

// 'CR' = cursor rotation
final _defaultCRTriggerKeys = {
LogicalKeyboardKey.control,
LogicalKeyboardKey.controlLeft,
LogicalKeyboardKey.controlRight
};
final crRotationTriggered = ValueNotifier(false);
double crDegrees = 0;
double crClickDegrees = 0;
double crDragDegrees = 0;
// 'ckr' = cursor/keyboard rotation
final _ckrTriggered = ValueNotifier(false);
double _ckrClickDegrees = 0;
double _ckrInitialDegrees = 0;

int _tapUpCounter = 0;
Timer? _doubleTapHoldMaxDelay;
Expand All @@ -114,7 +110,7 @@ class FlutterMapInteractiveViewerState
..addStatusListener(_doubleTapZoomStatusListener);

ServicesBinding.instance.keyboard
.addHandler(keyboardRotationTriggerKeyHandler);
.addHandler(cursorKeyboardRotationTriggerHandler);
}

@override
Expand All @@ -132,41 +128,44 @@ class FlutterMapInteractiveViewerState
widget.controller.removeListener(onMapStateChange);
_flingController.dispose();
_doubleTapController.dispose();
crRotationTriggered.dispose();

_ckrTriggered.dispose();
ServicesBinding.instance.keyboard
.removeHandler(keyboardRotationTriggerKeyHandler);
.removeHandler(cursorKeyboardRotationTriggerHandler);

super.dispose();
}

void onMapStateChange() => setState(() {});

bool keyboardRotationTriggerKeyHandler(KeyEvent event) {
crRotationTriggered.value =
(event is KeyRepeatEvent || event is KeyDownEvent) &&
(_interactionOptions.isCursorRotationKeyboardKeyTrigger ??
(key) => _defaultCRTriggerKeys.contains(key))(event.logicalKey);
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,
) {
if (newOptions.dragEnabled != oldOptions.dragEnabled) {
_gestures = _createGestures(dragEnabled: newOptions.dragEnabled);
final newHasDrag = InteractiveFlag.hasDrag(newOptions.flags);
if (newHasDrag != InteractiveFlag.hasDrag(oldOptions.flags)) {
_gestures = _createGestures(dragEnabled: newHasDrag);
}

if (!newOptions.flingEnabled) {
if (!InteractiveFlag.hasFlingAnimation(newOptions.flags)) {
_closeFlingAnimationController(MapEventSource.interactiveFlagsChanged);
}
if (newOptions.doubleTapZoomEnabled) {
if (InteractiveFlag.hasDoubleTapZoom(newOptions.flags)) {
_closeDoubleTapController(MapEventSource.interactiveFlagsChanged);
}

final gestures = _getMultiFingerGestureFlags(newOptions);

if (_rotationStarted &&
!newOptions.rotateEnabled &&
!InteractiveFlag.hasRotate(newOptions.flags) &&
!MultiFingerGesture.hasRotate(gestures)) {
_rotationStarted = false;

Expand All @@ -180,7 +179,7 @@ class FlutterMapInteractiveViewerState
var emitMapEventMoveEnd = false;

if (_pinchZoomStarted &&
!newOptions.pinchZoomEnabled &&
!InteractiveFlag.hasPinchZoom(newOptions.flags) &&
!MultiFingerGesture.hasPinchZoom(gestures)) {
_pinchZoomStarted = false;
emitMapEventMoveEnd = true;
Expand All @@ -191,7 +190,7 @@ class FlutterMapInteractiveViewerState
}

if (_pinchMoveStarted &&
!newOptions.pinchMoveEnabled &&
!InteractiveFlag.hasPinchMove(newOptions.flags) &&
!MultiFingerGesture.hasPinchMove(gestures)) {
_pinchMoveStarted = false;
emitMapEventMoveEnd = true;
Expand All @@ -201,7 +200,7 @@ class FlutterMapInteractiveViewerState
}
}

if (_dragStarted && !newOptions.dragEnabled) {
if (_dragStarted && !newHasDrag) {
_dragStarted = false;
emitMapEventMoveEnd = true;
}
Expand All @@ -210,11 +209,12 @@ class FlutterMapInteractiveViewerState
widget.controller.moveEnded(MapEventSource.interactiveFlagsChanged);
}

// No way to detect whether two functions are equal, so assume they aren't
// No way to detect whether the [CursorKeyboardRotationOptions.isKeyTrigger]s
// are equal, so assume they aren't
ServicesBinding.instance.keyboard
.removeHandler(keyboardRotationTriggerKeyHandler);
.removeHandler(cursorKeyboardRotationTriggerHandler);
ServicesBinding.instance.keyboard
.addHandler(keyboardRotationTriggerKeyHandler);
.addHandler(cursorKeyboardRotationTriggerHandler);
}

Map<Type, GestureRecognizerFactory> _createGestures({
Expand Down Expand Up @@ -283,9 +283,6 @@ class FlutterMapInteractiveViewerState

@override
Widget build(BuildContext context) {
crClickDegrees = 0;
crDragDegrees = 0;

return Listener(
onPointerDown: _onPointerDown,
onPointerUp: _onPointerUp,
Expand Down Expand Up @@ -317,7 +314,12 @@ class FlutterMapInteractiveViewerState

void _onPointerDown(PointerDownEvent event) {
++_pointerCounter;
crClickDegrees = getCursorRotationDegrees(event.localPosition) - crDegrees;

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);
Expand All @@ -327,7 +329,17 @@ class FlutterMapInteractiveViewerState

void _onPointerUp(PointerUpEvent event) {
--_pointerCounter;
crDegrees = crDragDegrees;

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);
Expand All @@ -352,15 +364,23 @@ class FlutterMapInteractiveViewerState
}

void _onPointerMove(PointerMoveEvent event) {
if (!crRotationTriggered.value) return;
if (!_ckrTriggered.value) return;

final baseSetNorth =
getCursorRotationDegrees(event.localPosition) - _ckrClickDegrees;

widget.controller.rotate(
crDragDegrees =
getCursorRotationDegrees(event.localPosition) - crClickDegrees,
_interactionOptions.cursorKeyboardRotationOptions.behaviour ==
CursorRotationBehaviour.setNorth
? baseSetNorth
: (_ckrInitialDegrees + baseSetNorth) % 360,
hasGesture: true,
source: MapEventSource.cursorRotation,
source: MapEventSource.cursorKeyboardRotation,
id: null,
);

if (_interactionOptions.cursorKeyboardRotationOptions.behaviour ==
CursorRotationBehaviour.setNorth) _ckrClickDegrees = 0;
}

void _onPointerSignal(PointerSignalEvent pointerSignal) {
Expand Down Expand Up @@ -494,7 +514,7 @@ class FlutterMapInteractiveViewerState
}

void _handleScaleDragUpdate(ScaleUpdateDetails details) {
if (crRotationTriggered.value) return;
if (_ckrTriggered.value) return;

const eventSource = MapEventSource.onDrag;

Expand Down Expand Up @@ -702,6 +722,9 @@ class FlutterMapInteractiveViewerState
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);

Expand Down Expand Up @@ -734,6 +757,8 @@ class FlutterMapInteractiveViewerState
}

void _handleTap(TapPosition position) {
if (_ckrTriggered.value) return;

_closeFlingAnimationController(MapEventSource.tap);
_closeDoubleTapController(MapEventSource.tap);

Expand Down Expand Up @@ -762,6 +787,8 @@ class FlutterMapInteractiveViewerState
}

void _handleLongPress(TapPosition position) {
if (_ckrTriggered.value) return;

_resetDoubleTapHold();

_closeFlingAnimationController(MapEventSource.longPress);
Expand Down
4 changes: 2 additions & 2 deletions lib/src/gestures/interactive_flag.dart
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ abstract class InteractiveFlag {

/// Enable rotation with two-finger twist gesture
///
/// For controlling rotation where a keyboard/cursor combination is used, see
/// [InteractionOptions.isCursorRotationKeyboardKeyTrigger].
/// For controlling cursor/keyboard rotation, see
/// [InteractionOptions.cursorKeyboardRotationOptions].
static const int rotate = 1 << 7;

/// Flags pertaining to gestures which require multiple fingers.
Expand Down
2 changes: 1 addition & 1 deletion lib/src/gestures/map_events.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ enum MapEventSource {
custom,
scrollWheel,
nonRotatedSizeChange,
cursorRotation,
cursorKeyboardRotation,
}

/// Base event class which is emitted by MapController instance, the event
Expand Down
2 changes: 1 addition & 1 deletion lib/src/map/camera/camera.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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.dart';
import 'package:flutter_map/src/map/options/options.dart';
import 'package:flutter_map/src/misc/point_extensions.dart';
import 'package:flutter_map/src/misc/private/bounds.dart';
import 'package:latlong2/latlong.dart';
Expand Down
2 changes: 1 addition & 1 deletion lib/src/map/inherited_model.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_map/src/map/camera/camera.dart';
import 'package:flutter_map/src/map/map_controller.dart';
import 'package:flutter_map/src/map/options.dart';
import 'package:flutter_map/src/map/options/options.dart';

/// Allows descendents of [FlutterMap] to access the [MapCamera], [MapOptions]
/// and [MapController]. Those classes provide of/maybeOf methods for users to
Expand Down
Loading

0 comments on commit b75166a

Please sign in to comment.