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
This diff is collapsed.
...@@ -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