@@ -15,6 +15,18 @@ export 'events.dart' show PointerDownEvent, PointerEvent, PointerPanZoomStartEve
1515export 'recognizer.dart' show DragStartBehavior;
1616export 'velocity_tracker.dart' show Velocity;
1717
18+ /// The default conversion factor when treating mouse scrolling as scaling.
19+ ///
20+ /// The value was arbitrarily chosen to feel natural for most mousewheels on
21+ /// all supported platforms.
22+ const double kDefaultMouseScrollToScaleFactor = 200 ;
23+
24+ /// The default conversion factor when treating trackpad scrolling as scaling.
25+ ///
26+ /// This factor matches the default [kDefaultMouseScrollToScaleFactor] of 200 to
27+ /// feel natural for most trackpads, and the convention that scrolling up means
28+ /// zooming in.
29+ const Offset kDefaultTrackpadScrollToScaleFactor = Offset (0 , - 1 / kDefaultMouseScrollToScaleFactor);
1830
1931/// The possible states of a [ScaleGestureRecognizer] .
2032enum _ScaleState {
@@ -36,26 +48,61 @@ enum _ScaleState {
3648}
3749
3850class _PointerPanZoomData {
39- _PointerPanZoomData ({
40- required this .focalPoint,
41- required this .scale,
42- required this .rotation
43- });
44- Offset focalPoint;
45- double scale;
46- double rotation;
51+ _PointerPanZoomData .fromStartEvent (
52+ this .parent,
53+ PointerPanZoomStartEvent event
54+ ) : _position = event.position,
55+ _pan = Offset .zero,
56+ _scale = 1 ,
57+ _rotation = 0 ;
58+
59+ _PointerPanZoomData .fromUpdateEvent (
60+ this .parent,
61+ PointerPanZoomUpdateEvent event
62+ ) : _position = event.position,
63+ _pan = event.pan,
64+ _scale = event.scale,
65+ _rotation = event.rotation;
66+
67+ final ScaleGestureRecognizer parent;
68+ final Offset _position;
69+ final Offset _pan;
70+ final double _scale;
71+ final double _rotation;
72+
73+ Offset get focalPoint {
74+ if (parent.trackpadScrollCausesScale) {
75+ return _position;
76+ }
77+ return _position + _pan;
78+ }
79+
80+ double get scale {
81+ if (parent.trackpadScrollCausesScale) {
82+ return _scale * math.exp (
83+ (_pan.dx * parent.trackpadScrollToScaleFactor.dx) +
84+ (_pan.dy * parent.trackpadScrollToScaleFactor.dy)
85+ );
86+ }
87+ return _scale;
88+ }
89+
90+ double get rotation => _rotation;
4791
4892 @override
49- String toString () => '_PointerPanZoomData(focalPoint : $focalPoint , scale : $scale , angle : $rotation )' ;
93+ String toString () => '_PointerPanZoomData(parent : $parent , _position : $_position , _pan : $_pan , _scale: $ _scale , _rotation: $ _rotation )' ;
5094}
5195
5296/// Details for [GestureScaleStartCallback] .
5397class ScaleStartDetails {
5498 /// Creates details for [GestureScaleStartCallback] .
5599 ///
56100 /// The [focalPoint] argument must not be null.
57- ScaleStartDetails ({ this .focalPoint = Offset .zero, Offset ? localFocalPoint, this .pointerCount = 0 })
58- : assert (focalPoint != null ), localFocalPoint = localFocalPoint ?? focalPoint;
101+ ScaleStartDetails ({
102+ this .focalPoint = Offset .zero,
103+ Offset ? localFocalPoint,
104+ this .pointerCount = 0 ,
105+ }) : assert (focalPoint != null ), localFocalPoint = localFocalPoint ?? focalPoint;
59106
60107 /// The initial focal point of the pointers in contact with the screen.
61108 ///
@@ -201,20 +248,23 @@ class ScaleEndDetails {
201248 /// Creates details for [GestureScaleEndCallback] .
202249 ///
203250 /// The [velocity] argument must not be null.
204- ScaleEndDetails ({ this .velocity = Velocity .zero, this .pointerCount = 0 })
251+ ScaleEndDetails ({ this .velocity = Velocity .zero, this .scaleVelocity = 0 , this . pointerCount = 0 })
205252 : assert (velocity != null );
206253
207254 /// The velocity of the last pointer to be lifted off of the screen.
208255 final Velocity velocity;
209256
257+ /// The final velocity of the scale factor reported by the gesture.
258+ final double scaleVelocity;
259+
210260 /// The number of pointers being tracked by the gesture recognizer.
211261 ///
212262 /// Typically this is the number of fingers being used to pan the widget using the gesture
213263 /// recognizer.
214264 final int pointerCount;
215265
216266 @override
217- String toString () => 'ScaleEndDetails(velocity: $velocity , pointerCount: $pointerCount )' ;
267+ String toString () => 'ScaleEndDetails(velocity: $velocity , scaleVelocity: $ scaleVelocity , pointerCount: $pointerCount )' ;
218268}
219269
220270/// Signature for when the pointers in contact with the screen have established
@@ -285,6 +335,8 @@ class ScaleGestureRecognizer extends OneSequenceGestureRecognizer {
285335 super .kind,
286336 super .supportedDevices,
287337 this .dragStartBehavior = DragStartBehavior .down,
338+ this .trackpadScrollCausesScale = false ,
339+ this .trackpadScrollToScaleFactor = kDefaultTrackpadScrollToScaleFactor,
288340 }) : assert (dragStartBehavior != null );
289341
290342 /// Determines what point is used as the starting point in all calculations
@@ -332,6 +384,26 @@ class ScaleGestureRecognizer extends OneSequenceGestureRecognizer {
332384
333385 Matrix4 ? _lastTransform;
334386
387+ /// {@template flutter.gestures.scale.trackpadScrollCausesScale}
388+ /// Whether scrolling up/down on a trackpad should cause scaling instead of
389+ /// panning.
390+ ///
391+ /// Defaults to false.
392+ /// {@endtemplate}
393+ bool trackpadScrollCausesScale;
394+
395+ /// {@template flutter.gestures.scale.trackpadScrollToScaleFactor}
396+ /// A factor to control the direction and magnitude of scale when converting
397+ /// trackpad scrolling.
398+ ///
399+ /// Incoming trackpad pan offsets will be divided by this factor to get scale
400+ /// values. Increasing this offset will reduce the amount of scaling caused by
401+ /// a fixed amount of trackpad scrolling.
402+ ///
403+ /// Defaults to [kDefaultTrackpadScrollToScaleFactor] .
404+ /// {@endtemplate}
405+ Offset trackpadScrollToScaleFactor;
406+
335407 late Offset _initialFocalPoint;
336408 Offset ? _currentFocalPoint;
337409 late double _initialSpan;
@@ -346,6 +418,7 @@ class ScaleGestureRecognizer extends OneSequenceGestureRecognizer {
346418 final Map <int , Offset > _pointerLocations = < int , Offset > {};
347419 final List <int > _pointerQueue = < int > []; // A queue to sort pointers in order of entrance
348420 final Map <int , VelocityTracker > _velocityTrackers = < int , VelocityTracker > {};
421+ VelocityTracker ? _scaleVelocityTracker;
349422 late Offset _delta;
350423 final Map <int , _PointerPanZoomData > _pointerPanZooms = < int , _PointerPanZoomData > {};
351424 double _initialPanZoomScaleFactor = 1 ;
@@ -466,23 +539,16 @@ class ScaleGestureRecognizer extends OneSequenceGestureRecognizer {
466539 _lastTransform = event.transform;
467540 } else if (event is PointerPanZoomStartEvent ) {
468541 assert (_pointerPanZooms[event.pointer] == null );
469- _pointerPanZooms[event.pointer] = _PointerPanZoomData (
470- focalPoint: event.position,
471- scale: 1 ,
472- rotation: 0
473- );
542+ _pointerPanZooms[event.pointer] = _PointerPanZoomData .fromStartEvent (this , event);
474543 didChangeConfiguration = true ;
475544 shouldStartIfAccepted = true ;
545+ _lastTransform = event.transform;
476546 } else if (event is PointerPanZoomUpdateEvent ) {
477547 assert (_pointerPanZooms[event.pointer] != null );
478- if (! event.synthesized) {
548+ if (! event.synthesized && ! trackpadScrollCausesScale ) {
479549 _velocityTrackers[event.pointer]! .addPosition (event.timeStamp, event.pan);
480550 }
481- _pointerPanZooms[event.pointer] = _PointerPanZoomData (
482- focalPoint: event.position + event.pan,
483- scale: event.scale,
484- rotation: event.rotation
485- );
551+ _pointerPanZooms[event.pointer] = _PointerPanZoomData .fromUpdateEvent (this , event);
486552 _lastTransform = event.transform;
487553 shouldStartIfAccepted = true ;
488554 } else if (event is PointerPanZoomEndEvent ) {
@@ -495,7 +561,7 @@ class ScaleGestureRecognizer extends OneSequenceGestureRecognizer {
495561 _update ();
496562
497563 if (! didChangeConfiguration || _reconfigure (event.pointer)) {
498- _advanceStateMachine (shouldStartIfAccepted, event.kind );
564+ _advanceStateMachine (shouldStartIfAccepted, event);
499565 }
500566 stopTrackingIfPointerNoLongerDown (event);
501567 }
@@ -607,26 +673,28 @@ class ScaleGestureRecognizer extends OneSequenceGestureRecognizer {
607673 if (pixelsPerSecond.distanceSquared > kMaxFlingVelocity * kMaxFlingVelocity) {
608674 velocity = Velocity (pixelsPerSecond: (pixelsPerSecond / pixelsPerSecond.distance) * kMaxFlingVelocity);
609675 }
610- invokeCallback <void >('onEnd' , () => onEnd !(ScaleEndDetails (velocity: velocity, pointerCount: _pointerCount)));
676+ invokeCallback <void >('onEnd' , () => onEnd !(ScaleEndDetails (velocity: velocity, scaleVelocity : _scaleVelocityTracker ? . getVelocity ().pixelsPerSecond.dx ?? - 1 , pointerCount: _pointerCount)));
611677 } else {
612- invokeCallback <void >('onEnd' , () => onEnd !(ScaleEndDetails (pointerCount: _pointerCount)));
678+ invokeCallback <void >('onEnd' , () => onEnd !(ScaleEndDetails (scaleVelocity : _scaleVelocityTracker ? . getVelocity ().pixelsPerSecond.dx ?? - 1 , pointerCount: _pointerCount)));
613679 }
614680 }
615681 _state = _ScaleState .accepted;
682+ _scaleVelocityTracker = VelocityTracker .withKind (PointerDeviceKind .touch); // arbitrary PointerDeviceKind
616683 return false ;
617684 }
685+ _scaleVelocityTracker = VelocityTracker .withKind (PointerDeviceKind .touch); // arbitrary PointerDeviceKind
618686 return true ;
619687 }
620688
621- void _advanceStateMachine (bool shouldStartIfAccepted, PointerDeviceKind pointerDeviceKind ) {
689+ void _advanceStateMachine (bool shouldStartIfAccepted, PointerEvent event ) {
622690 if (_state == _ScaleState .ready) {
623691 _state = _ScaleState .possible;
624692 }
625693
626694 if (_state == _ScaleState .possible) {
627695 final double spanDelta = (_currentSpan - _initialSpan).abs ();
628696 final double focalPointDelta = (_currentFocalPoint! - _initialFocalPoint).distance;
629- if (spanDelta > computeScaleSlop (pointerDeviceKind ) || focalPointDelta > computePanSlop (pointerDeviceKind , gestureSettings) || math.max (_scaleFactor / _pointerScaleFactor, _pointerScaleFactor / _scaleFactor) > 1.05 ) {
697+ if (spanDelta > computeScaleSlop (event.kind ) || focalPointDelta > computePanSlop (event.kind , gestureSettings) || math.max (_scaleFactor / _pointerScaleFactor, _pointerScaleFactor / _scaleFactor) > 1.05 ) {
630698 resolve (GestureDisposition .accepted);
631699 }
632700 } else if (_state.index >= _ScaleState .accepted.index) {
@@ -638,19 +706,22 @@ class ScaleGestureRecognizer extends OneSequenceGestureRecognizer {
638706 _dispatchOnStartCallbackIfNeeded ();
639707 }
640708
641- if (_state == _ScaleState .started && onUpdate != null ) {
642- invokeCallback <void >('onUpdate' , () {
643- onUpdate !(ScaleUpdateDetails (
644- scale: _scaleFactor,
645- horizontalScale: _horizontalScaleFactor,
646- verticalScale: _verticalScaleFactor,
647- focalPoint: _currentFocalPoint! ,
648- localFocalPoint: _localFocalPoint,
649- rotation: _computeRotationFactor (),
650- pointerCount: _pointerCount,
651- focalPointDelta: _delta,
652- ));
653- });
709+ if (_state == _ScaleState .started) {
710+ _scaleVelocityTracker? .addPosition (event.timeStamp, Offset (_scaleFactor, 0 ));
711+ if (onUpdate != null ) {
712+ invokeCallback <void >('onUpdate' , () {
713+ onUpdate !(ScaleUpdateDetails (
714+ scale: _scaleFactor,
715+ horizontalScale: _horizontalScaleFactor,
716+ verticalScale: _verticalScaleFactor,
717+ focalPoint: _currentFocalPoint! ,
718+ localFocalPoint: _localFocalPoint,
719+ rotation: _computeRotationFactor (),
720+ pointerCount: _pointerCount,
721+ focalPointDelta: _delta,
722+ ));
723+ });
724+ }
654725 }
655726 }
656727
0 commit comments