Skip to content

Commit

Permalink
InteractiveViewer parameter to return to pre-3.3 trackpad/Magic Mouse…
Browse files Browse the repository at this point in the history
… behaviour (flutter#114280)

* trackpadPanShouldActAsZoom

* Address feedback

* Move constant, add blank lines
  • Loading branch information
moffatman committed Jan 6, 2023
1 parent 71f9207 commit 7ddf42e
Show file tree
Hide file tree
Showing 5 changed files with 501 additions and 79 deletions.
155 changes: 113 additions & 42 deletions packages/flutter/lib/src/gestures/scale.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,18 @@ export 'events.dart' show PointerDownEvent, PointerEvent, PointerPanZoomStartEve
export 'recognizer.dart' show DragStartBehavior;
export 'velocity_tracker.dart' show Velocity;

/// The default conversion factor when treating mouse scrolling as scaling.
///
/// The value was arbitrarily chosen to feel natural for most mousewheels on
/// all supported platforms.
const double kDefaultMouseScrollToScaleFactor = 200;

/// The default conversion factor when treating trackpad scrolling as scaling.
///
/// This factor matches the default [kDefaultMouseScrollToScaleFactor] of 200 to
/// feel natural for most trackpads, and the convention that scrolling up means
/// zooming in.
const Offset kDefaultTrackpadScrollToScaleFactor = Offset(0, -1/kDefaultMouseScrollToScaleFactor);

/// The possible states of a [ScaleGestureRecognizer].
enum _ScaleState {
Expand All @@ -36,26 +48,61 @@ enum _ScaleState {
}

class _PointerPanZoomData {
_PointerPanZoomData({
required this.focalPoint,
required this.scale,
required this.rotation
});
Offset focalPoint;
double scale;
double rotation;
_PointerPanZoomData.fromStartEvent(
this.parent,
PointerPanZoomStartEvent event
) : _position = event.position,
_pan = Offset.zero,
_scale = 1,
_rotation = 0;

_PointerPanZoomData.fromUpdateEvent(
this.parent,
PointerPanZoomUpdateEvent event
) : _position = event.position,
_pan = event.pan,
_scale = event.scale,
_rotation = event.rotation;

final ScaleGestureRecognizer parent;
final Offset _position;
final Offset _pan;
final double _scale;
final double _rotation;

Offset get focalPoint {
if (parent.trackpadScrollCausesScale) {
return _position;
}
return _position + _pan;
}

double get scale {
if (parent.trackpadScrollCausesScale) {
return _scale * math.exp(
(_pan.dx * parent.trackpadScrollToScaleFactor.dx) +
(_pan.dy * parent.trackpadScrollToScaleFactor.dy)
);
}
return _scale;
}

double get rotation => _rotation;

@override
String toString() => '_PointerPanZoomData(focalPoint: $focalPoint, scale: $scale, angle: $rotation)';
String toString() => '_PointerPanZoomData(parent: $parent, _position: $_position, _pan: $_pan, _scale: $_scale, _rotation: $_rotation)';
}

/// Details for [GestureScaleStartCallback].
class ScaleStartDetails {
/// Creates details for [GestureScaleStartCallback].
///
/// The [focalPoint] argument must not be null.
ScaleStartDetails({ this.focalPoint = Offset.zero, Offset? localFocalPoint, this.pointerCount = 0 })
: assert(focalPoint != null), localFocalPoint = localFocalPoint ?? focalPoint;
ScaleStartDetails({
this.focalPoint = Offset.zero,
Offset? localFocalPoint,
this.pointerCount = 0,
}) : assert(focalPoint != null), localFocalPoint = localFocalPoint ?? focalPoint;

/// The initial focal point of the pointers in contact with the screen.
///
Expand Down Expand Up @@ -201,20 +248,23 @@ class ScaleEndDetails {
/// Creates details for [GestureScaleEndCallback].
///
/// The [velocity] argument must not be null.
ScaleEndDetails({ this.velocity = Velocity.zero, this.pointerCount = 0 })
ScaleEndDetails({ this.velocity = Velocity.zero, this.scaleVelocity = 0, this.pointerCount = 0 })
: assert(velocity != null);

/// The velocity of the last pointer to be lifted off of the screen.
final Velocity velocity;

/// The final velocity of the scale factor reported by the gesture.
final double scaleVelocity;

/// The number of pointers being tracked by the gesture recognizer.
///
/// Typically this is the number of fingers being used to pan the widget using the gesture
/// recognizer.
final int pointerCount;

@override
String toString() => 'ScaleEndDetails(velocity: $velocity, pointerCount: $pointerCount)';
String toString() => 'ScaleEndDetails(velocity: $velocity, scaleVelocity: $scaleVelocity, pointerCount: $pointerCount)';
}

/// Signature for when the pointers in contact with the screen have established
Expand Down Expand Up @@ -285,6 +335,8 @@ class ScaleGestureRecognizer extends OneSequenceGestureRecognizer {
super.kind,
super.supportedDevices,
this.dragStartBehavior = DragStartBehavior.down,
this.trackpadScrollCausesScale = false,
this.trackpadScrollToScaleFactor = kDefaultTrackpadScrollToScaleFactor,
}) : assert(dragStartBehavior != null);

/// Determines what point is used as the starting point in all calculations
Expand Down Expand Up @@ -332,6 +384,26 @@ class ScaleGestureRecognizer extends OneSequenceGestureRecognizer {

Matrix4? _lastTransform;

/// {@template flutter.gestures.scale.trackpadScrollCausesScale}
/// Whether scrolling up/down on a trackpad should cause scaling instead of
/// panning.
///
/// Defaults to false.
/// {@endtemplate}
bool trackpadScrollCausesScale;

/// {@template flutter.gestures.scale.trackpadScrollToScaleFactor}
/// A factor to control the direction and magnitude of scale when converting
/// trackpad scrolling.
///
/// Incoming trackpad pan offsets will be divided by this factor to get scale
/// values. Increasing this offset will reduce the amount of scaling caused by
/// a fixed amount of trackpad scrolling.
///
/// Defaults to [kDefaultTrackpadScrollToScaleFactor].
/// {@endtemplate}
Offset trackpadScrollToScaleFactor;

late Offset _initialFocalPoint;
Offset? _currentFocalPoint;
late double _initialSpan;
Expand All @@ -346,6 +418,7 @@ class ScaleGestureRecognizer extends OneSequenceGestureRecognizer {
final Map<int, Offset> _pointerLocations = <int, Offset>{};
final List<int> _pointerQueue = <int>[]; // A queue to sort pointers in order of entrance
final Map<int, VelocityTracker> _velocityTrackers = <int, VelocityTracker>{};
VelocityTracker? _scaleVelocityTracker;
late Offset _delta;
final Map<int, _PointerPanZoomData> _pointerPanZooms = <int, _PointerPanZoomData>{};
double _initialPanZoomScaleFactor = 1;
Expand Down Expand Up @@ -466,23 +539,16 @@ class ScaleGestureRecognizer extends OneSequenceGestureRecognizer {
_lastTransform = event.transform;
} else if (event is PointerPanZoomStartEvent) {
assert(_pointerPanZooms[event.pointer] == null);
_pointerPanZooms[event.pointer] = _PointerPanZoomData(
focalPoint: event.position,
scale: 1,
rotation: 0
);
_pointerPanZooms[event.pointer] = _PointerPanZoomData.fromStartEvent(this, event);
didChangeConfiguration = true;
shouldStartIfAccepted = true;
_lastTransform = event.transform;
} else if (event is PointerPanZoomUpdateEvent) {
assert(_pointerPanZooms[event.pointer] != null);
if (!event.synthesized) {
if (!event.synthesized && !trackpadScrollCausesScale) {
_velocityTrackers[event.pointer]!.addPosition(event.timeStamp, event.pan);
}
_pointerPanZooms[event.pointer] = _PointerPanZoomData(
focalPoint: event.position + event.pan,
scale: event.scale,
rotation: event.rotation
);
_pointerPanZooms[event.pointer] = _PointerPanZoomData.fromUpdateEvent(this, event);
_lastTransform = event.transform;
shouldStartIfAccepted = true;
} else if (event is PointerPanZoomEndEvent) {
Expand All @@ -495,7 +561,7 @@ class ScaleGestureRecognizer extends OneSequenceGestureRecognizer {
_update();

if (!didChangeConfiguration || _reconfigure(event.pointer)) {
_advanceStateMachine(shouldStartIfAccepted, event.kind);
_advanceStateMachine(shouldStartIfAccepted, event);
}
stopTrackingIfPointerNoLongerDown(event);
}
Expand Down Expand Up @@ -607,26 +673,28 @@ class ScaleGestureRecognizer extends OneSequenceGestureRecognizer {
if (pixelsPerSecond.distanceSquared > kMaxFlingVelocity * kMaxFlingVelocity) {
velocity = Velocity(pixelsPerSecond: (pixelsPerSecond / pixelsPerSecond.distance) * kMaxFlingVelocity);
}
invokeCallback<void>('onEnd', () => onEnd!(ScaleEndDetails(velocity: velocity, pointerCount: _pointerCount)));
invokeCallback<void>('onEnd', () => onEnd!(ScaleEndDetails(velocity: velocity, scaleVelocity: _scaleVelocityTracker?.getVelocity().pixelsPerSecond.dx ?? -1, pointerCount: _pointerCount)));
} else {
invokeCallback<void>('onEnd', () => onEnd!(ScaleEndDetails(pointerCount: _pointerCount)));
invokeCallback<void>('onEnd', () => onEnd!(ScaleEndDetails(scaleVelocity: _scaleVelocityTracker?.getVelocity().pixelsPerSecond.dx ?? -1, pointerCount: _pointerCount)));
}
}
_state = _ScaleState.accepted;
_scaleVelocityTracker = VelocityTracker.withKind(PointerDeviceKind.touch); // arbitrary PointerDeviceKind
return false;
}
_scaleVelocityTracker = VelocityTracker.withKind(PointerDeviceKind.touch); // arbitrary PointerDeviceKind
return true;
}

void _advanceStateMachine(bool shouldStartIfAccepted, PointerDeviceKind pointerDeviceKind) {
void _advanceStateMachine(bool shouldStartIfAccepted, PointerEvent event) {
if (_state == _ScaleState.ready) {
_state = _ScaleState.possible;
}

if (_state == _ScaleState.possible) {
final double spanDelta = (_currentSpan - _initialSpan).abs();
final double focalPointDelta = (_currentFocalPoint! - _initialFocalPoint).distance;
if (spanDelta > computeScaleSlop(pointerDeviceKind) || focalPointDelta > computePanSlop(pointerDeviceKind, gestureSettings) || math.max(_scaleFactor / _pointerScaleFactor, _pointerScaleFactor / _scaleFactor) > 1.05) {
if (spanDelta > computeScaleSlop(event.kind) || focalPointDelta > computePanSlop(event.kind, gestureSettings) || math.max(_scaleFactor / _pointerScaleFactor, _pointerScaleFactor / _scaleFactor) > 1.05) {
resolve(GestureDisposition.accepted);
}
} else if (_state.index >= _ScaleState.accepted.index) {
Expand All @@ -638,19 +706,22 @@ class ScaleGestureRecognizer extends OneSequenceGestureRecognizer {
_dispatchOnStartCallbackIfNeeded();
}

if (_state == _ScaleState.started && onUpdate != null) {
invokeCallback<void>('onUpdate', () {
onUpdate!(ScaleUpdateDetails(
scale: _scaleFactor,
horizontalScale: _horizontalScaleFactor,
verticalScale: _verticalScaleFactor,
focalPoint: _currentFocalPoint!,
localFocalPoint: _localFocalPoint,
rotation: _computeRotationFactor(),
pointerCount: _pointerCount,
focalPointDelta: _delta,
));
});
if (_state == _ScaleState.started) {
_scaleVelocityTracker?.addPosition(event.timeStamp, Offset(_scaleFactor, 0));
if (onUpdate != null) {
invokeCallback<void>('onUpdate', () {
onUpdate!(ScaleUpdateDetails(
scale: _scaleFactor,
horizontalScale: _horizontalScaleFactor,
verticalScale: _verticalScaleFactor,
focalPoint: _currentFocalPoint!,
localFocalPoint: _localFocalPoint,
rotation: _computeRotationFactor(),
pointerCount: _pointerCount,
focalPointDelta: _delta,
));
});
}
}
}

Expand Down
12 changes: 11 additions & 1 deletion packages/flutter/lib/src/widgets/gesture_detector.dart
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,8 @@ class GestureDetector extends StatelessWidget {
this.behavior,
this.excludeFromSemantics = false,
this.dragStartBehavior = DragStartBehavior.start,
this.trackpadScrollCausesScale = false,
this.trackpadScrollToScaleFactor = kDefaultTrackpadScrollToScaleFactor,
this.supportedDevices,
}) : assert(excludeFromSemantics != null),
assert(dragStartBehavior != null),
Expand Down Expand Up @@ -1014,6 +1016,12 @@ class GestureDetector extends StatelessWidget {
/// If set to null, events from all device types will be recognized. Defaults to null.
final Set<PointerDeviceKind>? supportedDevices;

/// {@macro flutter.gestures.scale.trackpadScrollCausesScale}
final bool trackpadScrollCausesScale;

/// {@macro flutter.gestures.scale.trackpadScrollToScaleFactor}
final Offset trackpadScrollToScaleFactor;

@override
Widget build(BuildContext context) {
final Map<Type, GestureRecognizerFactory> gestures = <Type, GestureRecognizerFactory>{};
Expand Down Expand Up @@ -1186,7 +1194,9 @@ class GestureDetector extends StatelessWidget {
..onUpdate = onScaleUpdate
..onEnd = onScaleEnd
..dragStartBehavior = dragStartBehavior
..gestureSettings = gestureSettings;
..gestureSettings = gestureSettings
..trackpadScrollCausesScale = trackpadScrollCausesScale
..trackpadScrollToScaleFactor = trackpadScrollToScaleFactor;
},
);
}
Expand Down
Loading

0 comments on commit 7ddf42e

Please sign in to comment.