Commit 11d1d54c authored by Ian Hickson's avatar Ian Hickson Committed by GitHub

Changes to the drag gesture detectors and the velocity tracker (#7363)

- more dartdocs for the drag typedefs

- more toStrings to aid debugging

- require the position for DragUpdateDetails since we were omitting it
  in some places

- add the primaryVelocity to DragEndDetails so that consumers don't
  have to themselves track the axis in question

- fix the velocity tracker so that it doesn't walk the null data.
  Previously, near time t=0 (which pretty much only matters in tests,
  but it does matter there) we would walk the velocity data and then
  also walk missing data, treating it as Point.zero with t=0.

- simplify some of the velocity tracker; e.g. instead of trying (and
  failing?) to clear the velocity tracker when the pointer stalls,
  just drop the data before a stall during the velocity estimation
  (where we redundantly had another bigger horizon anyway).
parent 629255eb
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'package:flutter/foundation.dart';
import 'arena.dart'; import 'arena.dart';
import 'recognizer.dart'; import 'recognizer.dart';
import 'constants.dart'; import 'constants.dart';
...@@ -14,7 +16,14 @@ enum _DragState { ...@@ -14,7 +16,14 @@ enum _DragState {
accepted, accepted,
} }
/// Details for [GestureDragDownCallback]. /// Details object for callbacks that use [GestureDragDownCallback].
///
/// See also:
///
/// * [DragGestureRecognizer.onDown], which uses [GestureDragDownCallback].
/// * [DragStartDetails], the details for [GestureDragStartCallback].
/// * [DragUpdateDetails], the details for [GestureDragUpdateCallback].
/// * [DragEndDetails], the details for [GestureDragEndCallback].
class DragDownDetails { class DragDownDetails {
/// Creates details for a [GestureDragDownCallback]. /// Creates details for a [GestureDragDownCallback].
/// ///
...@@ -24,15 +33,30 @@ class DragDownDetails { ...@@ -24,15 +33,30 @@ class DragDownDetails {
} }
/// The global position at which the pointer contacted the screen. /// The global position at which the pointer contacted the screen.
///
/// Defaults to the origin if not specified in the constructor.
final Point globalPosition; final Point globalPosition;
@override
String toString() => '$runtimeType($globalPosition)';
} }
/// Signature for when a pointer has contacted the screen and might begin to move. /// Signature for when a pointer has contacted the screen and might begin to
/// move.
///
/// The `details` object provides the position of the touch.
/// ///
/// See [DragGestureRecognizer.onDown]. /// See [DragGestureRecognizer.onDown].
typedef void GestureDragDownCallback(DragDownDetails details); typedef void GestureDragDownCallback(DragDownDetails details);
/// Details for [GestureDragStartCallback]. /// Details object for callbacks that use [GestureDragStartCallback].
///
/// See also:
///
/// * [DragGestureRecognizer.onStart], which uses [GestureDragStartCallback].
/// * [DragDownDetails], the details for [GestureDragDownCallback].
/// * [DragUpdateDetails], the details for [GestureDragUpdateCallback].
/// * [DragEndDetails], the details for [GestureDragEndCallback].
class DragStartDetails { class DragStartDetails {
/// Creates details for a [GestureDragStartCallback]. /// Creates details for a [GestureDragStartCallback].
/// ///
...@@ -42,15 +66,30 @@ class DragStartDetails { ...@@ -42,15 +66,30 @@ class DragStartDetails {
} }
/// The global position at which the pointer contacted the screen. /// The global position at which the pointer contacted the screen.
///
/// Defaults to the origin if not specified in the constructor.
final Point globalPosition; final Point globalPosition;
@override
String toString() => '$runtimeType($globalPosition)';
} }
/// Signature for when a pointer has contacted the screen and has begun to move. /// Signature for when a pointer has contacted the screen and has begun to move.
/// ///
/// The `details` object provides the position of the touch when it first
/// touched the surface.
///
/// See [DragGestureRecognizer.onStart]. /// See [DragGestureRecognizer.onStart].
typedef void GestureDragStartCallback(DragStartDetails details); typedef void GestureDragStartCallback(DragStartDetails details);
/// Details for [GestureDragUpdateCallback]. /// Details object for callbacks that use [GestureDragUpdateCallback].
///
/// See also:
///
/// * [DragGestureRecognizer.onUpdate], which uses [GestureDragUpdateCallback].
/// * [DragDownDetails], the details for [GestureDragDownCallback].
/// * [DragStartDetails], the details for [GestureDragStartCallback].
/// * [DragEndDetails], the details for [GestureDragEndCallback].
class DragUpdateDetails { class DragUpdateDetails {
/// Creates details for a [DragUpdateDetails]. /// Creates details for a [DragUpdateDetails].
/// ///
...@@ -58,10 +97,12 @@ class DragUpdateDetails { ...@@ -58,10 +97,12 @@ class DragUpdateDetails {
/// ///
/// If [primaryDelta] is non-null, then its value must match one of the /// If [primaryDelta] is non-null, then its value must match one of the
/// coordinates of [delta] and the other coordinate must be zero. /// coordinates of [delta] and the other coordinate must be zero.
///
/// The [globalPosition] argument must be provided and must not be null.
DragUpdateDetails({ DragUpdateDetails({
this.delta: Offset.zero, this.delta: Offset.zero,
this.primaryDelta, this.primaryDelta,
this.globalPosition @required this.globalPosition
}) { }) {
assert(primaryDelta == null assert(primaryDelta == null
|| (primaryDelta == delta.dx && delta.dy == 0.0) || (primaryDelta == delta.dx && delta.dy == 0.0)
...@@ -73,6 +114,8 @@ class DragUpdateDetails { ...@@ -73,6 +114,8 @@ class DragUpdateDetails {
/// If the [GestureDragUpdateCallback] is for a one-dimensional drag (e.g., /// If the [GestureDragUpdateCallback] is for a one-dimensional drag (e.g.,
/// a horizontal or vertical drag), then this offset contains only the delta /// a horizontal or vertical drag), then this offset contains only the delta
/// in that direction (i.e., the coordinate in the other direction is zero). /// in that direction (i.e., the coordinate in the other direction is zero).
///
/// Defaults to zero if not specified in the constructor.
final Offset delta; final Offset delta;
/// The amount the pointer has moved along the primary axis since the previous /// The amount the pointer has moved along the primary axis since the previous
...@@ -83,29 +126,67 @@ class DragUpdateDetails { ...@@ -83,29 +126,67 @@ class DragUpdateDetails {
/// [delta] along the primary axis (e.g., horizontal or vertical, /// [delta] along the primary axis (e.g., horizontal or vertical,
/// respectively). Otherwise, if the [GestureDragUpdateCallback] is for a /// respectively). Otherwise, if the [GestureDragUpdateCallback] is for a
/// two-dimensional drag (e.g., a pan), then this value is null. /// two-dimensional drag (e.g., a pan), then this value is null.
///
/// Defaults to null if not specified in the constructor.
final double primaryDelta; final double primaryDelta;
/// The pointer's global position. /// The pointer's global position when it triggered this update.
final Point globalPosition; final Point globalPosition;
@override
String toString() => '$runtimeType($delta)';
} }
/// Signature for when a pointer that is in contact with the screen and moving /// Signature for when a pointer that is in contact with the screen and moving
/// has moved again. /// has moved again.
/// ///
/// The `details` object provides the position of the touch and the distance it
/// has travelled since the last update.
///
/// See [DragGestureRecognizer.onUpdate]. /// See [DragGestureRecognizer.onUpdate].
typedef void GestureDragUpdateCallback(DragUpdateDetails details); typedef void GestureDragUpdateCallback(DragUpdateDetails details);
/// Details for [GestureDragEndCallback]. /// Details object for callbacks that use [GestureDragEndCallback].
///
/// See also:
///
/// * [DragGestureRecognizer.onEnd], which uses [GestureDragEndCallback].
/// * [DragDownDetails], the details for [GestureDragDownCallback].
/// * [DragStartDetails], the details for [GestureDragStartCallback].
/// * [DragUpdateDetails], the details for [GestureDragUpdateCallback].
class DragEndDetails { class DragEndDetails {
/// Creates details for a [GestureDragEndCallback]. /// Creates details for a [GestureDragEndCallback].
/// ///
/// The [velocity] argument must not be null. /// The [velocity] argument must not be null.
DragEndDetails({ this.velocity: Velocity.zero }) { DragEndDetails({
this.velocity: Velocity.zero,
this.primaryVelocity,
}) {
assert(velocity != null); assert(velocity != null);
assert(primaryVelocity == null
|| primaryVelocity == velocity.pixelsPerSecond.dx
|| primaryVelocity == velocity.pixelsPerSecond.dy);
} }
/// The velocity the pointer was moving when it stopped contacting the screen. /// The velocity the pointer was moving when it stopped contacting the screen.
///
/// Defaults to zero if not specified in the constructor.
final Velocity velocity; final Velocity velocity;
/// The velocity the pointer was moving along the primary axis when it stopped
/// contacting the screen, in logical pixels per second.
///
/// If the [GestureDragEndCallback] is for a one-dimensional drag (e.g., a
/// horizontal or vertical drag), then this value contains the component of
/// [velocity] along the primary axis (e.g., horizontal or vertical,
/// respectively). Otherwise, if the [GestureDragEndCallback] is for a
/// two-dimensional drag (e.g., a pan), then this value is null.
///
/// Defaults to null if not specified in the constructor.
final double primaryVelocity;
@override
String toString() => '$runtimeType($velocity)';
} }
/// Signature for when a pointer that was previously in contact with the screen /// Signature for when a pointer that was previously in contact with the screen
...@@ -147,17 +228,29 @@ bool _isFlingGesture(Velocity velocity) { ...@@ -147,17 +228,29 @@ bool _isFlingGesture(Velocity velocity) {
/// * [PanGestureRecognizer] /// * [PanGestureRecognizer]
abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer { abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer {
/// A pointer has contacted the screen and might begin to move. /// A pointer has contacted the screen and might begin to move.
///
/// The position of the pointer is provided in the callback's `details`
/// argument, which is a [DragDownDetails] object.
GestureDragDownCallback onDown; GestureDragDownCallback onDown;
/// A pointer has contacted the screen and has begun to move. /// A pointer has contacted the screen and has begun to move.
///
/// The position of the pointer is provided in the callback's `details`
/// argument, which is a [DragStartDetails] object.
GestureDragStartCallback onStart; GestureDragStartCallback onStart;
/// A pointer that is in contact with the screen and moving has moved again. /// A pointer that is in contact with the screen and moving has moved again.
///
/// The distance travelled by the pointer since the last update is provided in
/// the callback's `details` argument, which is a [DragUpdateDetails] object.
GestureDragUpdateCallback onUpdate; GestureDragUpdateCallback onUpdate;
/// A pointer that was previously in contact with the screen and moving is no /// A pointer that was previously in contact with the screen and moving is no
/// longer in contact with the screen and was moving at a specific velocity /// longer in contact with the screen and was moving at a specific velocity
/// when it stopped contacting the screen. /// when it stopped contacting the screen.
///
/// The velocity is provided in the callback's `details` argument, which is a
/// [DragEndDetails] object.
GestureDragEndCallback onEnd; GestureDragEndCallback onEnd;
/// The pointer that previously triggered [onDown] did not complete. /// The pointer that previously triggered [onDown] did not complete.
...@@ -168,7 +261,7 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer { ...@@ -168,7 +261,7 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer {
Offset _pendingDragOffset; Offset _pendingDragOffset;
Offset _getDeltaForDetails(Offset delta); Offset _getDeltaForDetails(Offset delta);
double _getPrimaryDeltaForDetails(Offset delta); double _getPrimaryValueFromOffset(Offset value);
bool get _hasSufficientPendingDragDeltaToAccept; bool get _hasSufficientPendingDragDeltaToAccept;
Map<int, VelocityTracker> _velocityTrackers = new Map<int, VelocityTracker>(); Map<int, VelocityTracker> _velocityTrackers = new Map<int, VelocityTracker>();
...@@ -198,8 +291,8 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer { ...@@ -198,8 +291,8 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer {
if (onUpdate != null) { if (onUpdate != null) {
invokeCallback/*<Null>*/('onUpdate', () => onUpdate(new DragUpdateDetails( // ignore: STRONG_MODE_INVALID_CAST_FUNCTION_EXPR, https://github.com/dart-lang/sdk/issues/27504 invokeCallback/*<Null>*/('onUpdate', () => onUpdate(new DragUpdateDetails( // ignore: STRONG_MODE_INVALID_CAST_FUNCTION_EXPR, https://github.com/dart-lang/sdk/issues/27504
delta: _getDeltaForDetails(delta), delta: _getDeltaForDetails(delta),
primaryDelta: _getPrimaryDeltaForDetails(delta), primaryDelta: _getPrimaryValueFromOffset(delta),
globalPosition: event.position globalPosition: event.position,
))); )));
} }
} else { } else {
...@@ -217,12 +310,16 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer { ...@@ -217,12 +310,16 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer {
_state = _DragState.accepted; _state = _DragState.accepted;
Offset delta = _pendingDragOffset; Offset delta = _pendingDragOffset;
_pendingDragOffset = Offset.zero; _pendingDragOffset = Offset.zero;
if (onStart != null) if (onStart != null) {
invokeCallback/*<Null>*/('onStart', () => onStart(new DragStartDetails(globalPosition: _initialPosition))); // ignore: STRONG_MODE_INVALID_CAST_FUNCTION_EXPR, https://github.com/dart-lang/sdk/issues/27504 invokeCallback/*<Null>*/('onStart', () => onStart(new DragStartDetails( // ignore: STRONG_MODE_INVALID_CAST_FUNCTION_EXPR, https://github.com/dart-lang/sdk/issues/27504
globalPosition: _initialPosition,
)));
}
if (delta != Offset.zero && onUpdate != null) { if (delta != Offset.zero && onUpdate != null) {
invokeCallback/*<Null>*/('onUpdate', () => onUpdate(new DragUpdateDetails( // ignore: STRONG_MODE_INVALID_CAST_FUNCTION_EXPR, https://github.com/dart-lang/sdk/issues/27504 invokeCallback/*<Null>*/('onUpdate', () => onUpdate(new DragUpdateDetails( // ignore: STRONG_MODE_INVALID_CAST_FUNCTION_EXPR, https://github.com/dart-lang/sdk/issues/27504
delta: _getDeltaForDetails(delta), delta: _getDeltaForDetails(delta),
primaryDelta: _getPrimaryDeltaForDetails(delta) primaryDelta: _getPrimaryValueFromOffset(delta),
globalPosition: _initialPosition,
))); )));
} }
} }
...@@ -253,9 +350,15 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer { ...@@ -253,9 +350,15 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer {
final Offset pixelsPerSecond = velocity.pixelsPerSecond; final Offset pixelsPerSecond = velocity.pixelsPerSecond;
if (pixelsPerSecond.distanceSquared > kMaxFlingVelocity * kMaxFlingVelocity) if (pixelsPerSecond.distanceSquared > kMaxFlingVelocity * kMaxFlingVelocity)
velocity = new Velocity(pixelsPerSecond: (pixelsPerSecond / pixelsPerSecond.distance) * kMaxFlingVelocity); velocity = new Velocity(pixelsPerSecond: (pixelsPerSecond / pixelsPerSecond.distance) * kMaxFlingVelocity);
invokeCallback/*<Null>*/('onEnd', () => onEnd(new DragEndDetails(velocity: velocity))); // ignore: STRONG_MODE_INVALID_CAST_FUNCTION_EXPR, https://github.com/dart-lang/sdk/issues/27504 invokeCallback/*<Null>*/('onEnd', () => onEnd(new DragEndDetails( // ignore: STRONG_MODE_INVALID_CAST_FUNCTION_EXPR, https://github.com/dart-lang/sdk/issues/27504
velocity: velocity,
primaryVelocity: _getPrimaryValueFromOffset(velocity.pixelsPerSecond),
)));
} else { } else {
invokeCallback/*<Null>*/('onEnd', () => onEnd(new DragEndDetails(velocity: Velocity.zero))); // ignore: STRONG_MODE_INVALID_CAST_FUNCTION_EXPR, https://github.com/dart-lang/sdk/issues/27504 invokeCallback/*<Null>*/('onEnd', () => onEnd(new DragEndDetails( // ignore: STRONG_MODE_INVALID_CAST_FUNCTION_EXPR, https://github.com/dart-lang/sdk/issues/27504
velocity: Velocity.zero,
primaryVelocity: 0.0,
)));
} }
} }
_velocityTrackers.clear(); _velocityTrackers.clear();
...@@ -283,7 +386,7 @@ class VerticalDragGestureRecognizer extends DragGestureRecognizer { ...@@ -283,7 +386,7 @@ class VerticalDragGestureRecognizer extends DragGestureRecognizer {
Offset _getDeltaForDetails(Offset delta) => new Offset(0.0, delta.dy); Offset _getDeltaForDetails(Offset delta) => new Offset(0.0, delta.dy);
@override @override
double _getPrimaryDeltaForDetails(Offset delta) => delta.dy; double _getPrimaryValueFromOffset(Offset value) => value.dy;
@override @override
String toStringShort() => 'vertical drag'; String toStringShort() => 'vertical drag';
...@@ -304,7 +407,7 @@ class HorizontalDragGestureRecognizer extends DragGestureRecognizer { ...@@ -304,7 +407,7 @@ class HorizontalDragGestureRecognizer extends DragGestureRecognizer {
Offset _getDeltaForDetails(Offset delta) => new Offset(delta.dx, 0.0); Offset _getDeltaForDetails(Offset delta) => new Offset(delta.dx, 0.0);
@override @override
double _getPrimaryDeltaForDetails(Offset delta) => delta.dx; double _getPrimaryValueFromOffset(Offset value) => value.dx;
@override @override
String toStringShort() => 'horizontal drag'; String toStringShort() => 'horizontal drag';
...@@ -326,7 +429,7 @@ class PanGestureRecognizer extends DragGestureRecognizer { ...@@ -326,7 +429,7 @@ class PanGestureRecognizer extends DragGestureRecognizer {
Offset _getDeltaForDetails(Offset delta) => delta; Offset _getDeltaForDetails(Offset delta) => delta;
@override @override
double _getPrimaryDeltaForDetails(Offset delta) => null; double _getPrimaryValueFromOffset(Offset value) => null;
@override @override
String toStringShort() => 'pan'; String toStringShort() => 'pan';
......
...@@ -84,7 +84,10 @@ abstract class MultiDragPointerState { ...@@ -84,7 +84,10 @@ abstract class MultiDragPointerState {
if (_client != null) { if (_client != null) {
assert(pendingDelta == null); assert(pendingDelta == null);
// Call client last to avoid reentrancy. // Call client last to avoid reentrancy.
_client.update(new DragUpdateDetails(delta: event.delta)); _client.update(new DragUpdateDetails(
delta: event.delta,
globalPosition: event.position,
));
} else { } else {
assert(pendingDelta != null); assert(pendingDelta != null);
_pendingDelta += event.delta; _pendingDelta += event.delta;
...@@ -124,7 +127,10 @@ abstract class MultiDragPointerState { ...@@ -124,7 +127,10 @@ abstract class MultiDragPointerState {
assert(client != null); assert(client != null);
assert(pendingDelta != null); assert(pendingDelta != null);
_client = client; _client = client;
final DragUpdateDetails details = new DragUpdateDetails(delta: pendingDelta); final DragUpdateDetails details = new DragUpdateDetails(
delta: pendingDelta,
globalPosition: initialPosition,
);
_pendingDelta = null; _pendingDelta = null;
// Call client last to avoid reentrancy. // Call client last to avoid reentrancy.
_client.update(details); _client.update(details);
......
...@@ -16,6 +16,10 @@ import 'pointer_router.dart'; ...@@ -16,6 +16,10 @@ import 'pointer_router.dart';
export 'pointer_router.dart' show PointerRouter; export 'pointer_router.dart' show PointerRouter;
/// Generic signature for callbacks passed to
/// [GestureRecognizer.invokeCallback]. This allows the
/// [GestureRecognizer.invokeCallback] mechanism to be generically used with
/// anonymous functions that return objects of particular types.
typedef T RecognizerCallback<T>(); typedef T RecognizerCallback<T>();
/// The base class that all GestureRecognizers should inherit from. /// The base class that all GestureRecognizers should inherit from.
...@@ -51,7 +55,8 @@ abstract class GestureRecognizer extends GestureArenaMember { ...@@ -51,7 +55,8 @@ abstract class GestureRecognizer extends GestureArenaMember {
/// recognizer looks for, like 'tap' or 'horizontal drag'. /// recognizer looks for, like 'tap' or 'horizontal drag'.
String toStringShort() => toString(); String toStringShort() => toString();
/// Invoke a callback provided by the application and log any exceptions. /// Invoke a callback provided by the application, catching and logging any
/// exceptions.
@protected @protected
dynamic/*=T*/ invokeCallback/*<T>*/(String name, RecognizerCallback<dynamic/*=T*/> callback) { dynamic/*=T*/ invokeCallback/*<T>*/(String name, RecognizerCallback<dynamic/*=T*/> callback) {
dynamic/*=T*/ result; dynamic/*=T*/ result;
......
...@@ -25,28 +25,32 @@ abstract class _VelocityTrackerStrategy { ...@@ -25,28 +25,32 @@ abstract class _VelocityTrackerStrategy {
} }
class _Movement { class _Movement {
Duration eventTime = Duration.ZERO; const _Movement(this.eventTime, this.position);
Point position = Point.origin;
final Duration eventTime;
final Point position;
@override
String toString() => 'Movement($position at $eventTime)';
} }
class _LeastSquaresVelocityTrackerStrategy extends _VelocityTrackerStrategy { class _LeastSquaresVelocityTrackerStrategy extends _VelocityTrackerStrategy {
static const int kHistorySize = 20;
static const int kHorizonMilliseconds = 100;
_LeastSquaresVelocityTrackerStrategy(this.degree); _LeastSquaresVelocityTrackerStrategy(this.degree);
final int degree; final int degree;
final List<_Movement> _movements = new List<_Movement>(kHistorySize); final List<_Movement> _movements = new List<_Movement>(kHistorySize);
int _index = 0; int _index = 0;
static const int kHistorySize = 20;
static const int kHorizonMilliseconds = 40;
@override @override
void addMovement(Duration timeStamp, Point position) { void addMovement(Duration timeStamp, Point position) {
_index += 1; _index += 1;
if (_index == kHistorySize) if (_index == kHistorySize)
_index = 0; _index = 0;
_Movement movement = _getMovement(_index); _movements[_index] = new _Movement(timeStamp, position);
movement.eventTime = timeStamp;
movement.position = position;
} }
@override @override
...@@ -58,9 +62,15 @@ class _LeastSquaresVelocityTrackerStrategy extends _VelocityTrackerStrategy { ...@@ -58,9 +62,15 @@ class _LeastSquaresVelocityTrackerStrategy extends _VelocityTrackerStrategy {
List<double> time = new List<double>(); List<double> time = new List<double>();
int m = 0; int m = 0;
int index = _index; int index = _index;
_Movement newestMovement = _getMovement(index);
_Movement newestMovement = _movements[index];
if (newestMovement == null)
return null;
do { do {
_Movement movement = _getMovement(index); _Movement movement = _movements[index];
if (movement == null)
break;
double age = (newestMovement.eventTime - movement.eventTime).inMilliseconds.toDouble(); double age = (newestMovement.eventTime - movement.eventTime).inMilliseconds.toDouble();
if (age > kHorizonMilliseconds) if (age > kHorizonMilliseconds)
...@@ -76,9 +86,6 @@ class _LeastSquaresVelocityTrackerStrategy extends _VelocityTrackerStrategy { ...@@ -76,9 +86,6 @@ class _LeastSquaresVelocityTrackerStrategy extends _VelocityTrackerStrategy {
m += 1; m += 1;
} while (m < kHistorySize); } while (m < kHistorySize);
if (m == 0) // because we broke out of the loop above after age > kHorizonMilliseconds
return null; // no data
// Calculate a least squares polynomial fit. // Calculate a least squares polynomial fit.
int n = degree; int n = degree;
if (n > m - 1) if (n > m - 1)
...@@ -118,15 +125,6 @@ class _LeastSquaresVelocityTrackerStrategy extends _VelocityTrackerStrategy { ...@@ -118,15 +125,6 @@ class _LeastSquaresVelocityTrackerStrategy extends _VelocityTrackerStrategy {
_index = -1; _index = -1;
} }
_Movement _getMovement(int i) {
_Movement result = _movements[i];
if (result == null) {
result = new _Movement();
_movements[i] = result;
}
return result;
}
} }
/// A velocity in two dimensions. /// A velocity in two dimensions.
...@@ -184,25 +182,26 @@ class Velocity { ...@@ -184,25 +182,26 @@ class Velocity {
/// The quality of the velocity estimation will be better if more data /// The quality of the velocity estimation will be better if more data
/// points have been received. /// points have been received.
class VelocityTracker { class VelocityTracker {
/// The maximum length of time between two move events to allow
/// before assuming the pointer stopped.
static const Duration kAssumePointerMoveStoppedTime = const Duration(milliseconds: 40);
/// Creates a velocity tracker. /// Creates a velocity tracker.
VelocityTracker() : _strategy = _createStrategy(); VelocityTracker() : _strategy = _createStrategy();
Duration _lastTimeStamp = const Duration(); // VelocityTracker is designed to easily be adapted to using different
// algorithms in the future, potentially picking algorithms on the fly based
// on hardware or other environment factors.
//
// For now, though, we just use the _LeastSquaresVelocityTrackerStrategy
// defined above.
// TODO(ianh): Simplify this. We don't see to need multiple stategies.
static _VelocityTrackerStrategy _createStrategy() {
return new _LeastSquaresVelocityTrackerStrategy(2);
}
_VelocityTrackerStrategy _strategy; _VelocityTrackerStrategy _strategy;
/// Add a given position corresponding to a specific time. /// Add a given position corresponding to a specific time.
///
/// If [kAssumePointerMoveStoppedTime] has elapsed since the last
/// call, then earlier data will be discarded.
void addPosition(Duration timeStamp, Point position) { void addPosition(Duration timeStamp, Point position) {
if (timeStamp - _lastTimeStamp >= kAssumePointerMoveStoppedTime)
_strategy.clear();
_lastTimeStamp = timeStamp;
_strategy.addMovement(timeStamp, position); _strategy.addMovement(timeStamp, position);
} }
...@@ -225,8 +224,4 @@ class VelocityTracker { ...@@ -225,8 +224,4 @@ class VelocityTracker {
} }
return null; return null;
} }
static _VelocityTrackerStrategy _createStrategy() {
return new _LeastSquaresVelocityTrackerStrategy(2);
}
} }
...@@ -2552,7 +2552,8 @@ class RenderSemanticsGestureHandler extends RenderProxyBox implements SemanticsA ...@@ -2552,7 +2552,8 @@ class RenderSemanticsGestureHandler extends RenderProxyBox implements SemanticsA
if (onHorizontalDragUpdate != null) { if (onHorizontalDragUpdate != null) {
final double primaryDelta = size.width * -scrollFactor; final double primaryDelta = size.width * -scrollFactor;
onHorizontalDragUpdate(new DragUpdateDetails( onHorizontalDragUpdate(new DragUpdateDetails(
delta: new Offset(primaryDelta, 0.0), primaryDelta: primaryDelta delta: new Offset(primaryDelta, 0.0), primaryDelta: primaryDelta,
globalPosition: localToGlobal(size.center(Point.origin)),
)); ));
} }
break; break;
...@@ -2560,7 +2561,8 @@ class RenderSemanticsGestureHandler extends RenderProxyBox implements SemanticsA ...@@ -2560,7 +2561,8 @@ class RenderSemanticsGestureHandler extends RenderProxyBox implements SemanticsA
if (onHorizontalDragUpdate != null) { if (onHorizontalDragUpdate != null) {
final double primaryDelta = size.width * scrollFactor; final double primaryDelta = size.width * scrollFactor;
onHorizontalDragUpdate(new DragUpdateDetails( onHorizontalDragUpdate(new DragUpdateDetails(
delta: new Offset(primaryDelta, 0.0), primaryDelta: primaryDelta delta: new Offset(primaryDelta, 0.0), primaryDelta: primaryDelta,
globalPosition: localToGlobal(size.center(Point.origin)),
)); ));
} }
break; break;
...@@ -2568,7 +2570,8 @@ class RenderSemanticsGestureHandler extends RenderProxyBox implements SemanticsA ...@@ -2568,7 +2570,8 @@ class RenderSemanticsGestureHandler extends RenderProxyBox implements SemanticsA
if (onVerticalDragUpdate != null) { if (onVerticalDragUpdate != null) {
final double primaryDelta = size.height * -scrollFactor; final double primaryDelta = size.height * -scrollFactor;
onVerticalDragUpdate(new DragUpdateDetails( onVerticalDragUpdate(new DragUpdateDetails(
delta: new Offset(0.0, primaryDelta), primaryDelta: primaryDelta delta: new Offset(0.0, primaryDelta), primaryDelta: primaryDelta,
globalPosition: localToGlobal(size.center(Point.origin)),
)); ));
} }
break; break;
...@@ -2576,7 +2579,8 @@ class RenderSemanticsGestureHandler extends RenderProxyBox implements SemanticsA ...@@ -2576,7 +2579,8 @@ class RenderSemanticsGestureHandler extends RenderProxyBox implements SemanticsA
if (onVerticalDragUpdate != null) { if (onVerticalDragUpdate != null) {
final double primaryDelta = size.height * scrollFactor; final double primaryDelta = size.height * scrollFactor;
onVerticalDragUpdate(new DragUpdateDetails( onVerticalDragUpdate(new DragUpdateDetails(
delta: new Offset(0.0, primaryDelta), primaryDelta: primaryDelta delta: new Offset(0.0, primaryDelta), primaryDelta: primaryDelta,
globalPosition: localToGlobal(size.center(Point.origin)),
)); ));
} }
break; break;
......
...@@ -133,8 +133,10 @@ void main() { ...@@ -133,8 +133,10 @@ void main() {
HorizontalDragGestureRecognizer drag = new HorizontalDragGestureRecognizer(); HorizontalDragGestureRecognizer drag = new HorizontalDragGestureRecognizer();
Velocity velocity; Velocity velocity;
double primaryVelocity;
drag.onEnd = (DragEndDetails details) { drag.onEnd = (DragEndDetails details) {
velocity = details.velocity; velocity = details.velocity;
primaryVelocity = details.primaryVelocity;
}; };
TestPointer pointer = new TestPointer(5); TestPointer pointer = new TestPointer(5);
...@@ -155,7 +157,16 @@ void main() { ...@@ -155,7 +157,16 @@ void main() {
tester.route(pointer.move(const Point(120.0, 25.0), timeStamp: const Duration(milliseconds: 20))); tester.route(pointer.move(const Point(120.0, 25.0), timeStamp: const Duration(milliseconds: 20)));
tester.route(pointer.up(timeStamp: const Duration(milliseconds: 20))); tester.route(pointer.up(timeStamp: const Duration(milliseconds: 20)));
expect(velocity.pixelsPerSecond.dx, inInclusiveRange(0.99 * kMaxFlingVelocity, kMaxFlingVelocity)); expect(velocity.pixelsPerSecond.dx, inInclusiveRange(0.99 * kMaxFlingVelocity, kMaxFlingVelocity));
expect(velocity.pixelsPerSecond.dy, moreOrLessEquals(0.0));
expect(primaryVelocity, velocity.pixelsPerSecond.dx);
drag.dispose(); drag.dispose();
}); });
testGesture('Drag details', (GestureTester tester) {
expect(new DragDownDetails(), hasOneLineDescription);
expect(new DragStartDetails(), hasOneLineDescription);
expect(new DragUpdateDetails(globalPosition: Point.origin), hasOneLineDescription);
expect(new DragEndDetails(), hasOneLineDescription);
});
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment