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 {
if (_client != null) {
assert(pendingDelta == null);
// Call client last to avoid reentrancy.
_client.update(new DragUpdateDetails(delta: event.delta));
_client.update(new DragUpdateDetails(
delta: event.delta,
globalPosition: event.position,
));
} else {
assert(pendingDelta != null);
_pendingDelta += event.delta;
......@@ -124,7 +127,10 @@ abstract class MultiDragPointerState {
assert(client != null);
assert(pendingDelta != null);
_client = client;
final DragUpdateDetails details = new DragUpdateDetails(delta: pendingDelta);
final DragUpdateDetails details = new DragUpdateDetails(
delta: pendingDelta,
globalPosition: initialPosition,
);
_pendingDelta = null;
// Call client last to avoid reentrancy.
_client.update(details);
......
......@@ -16,6 +16,10 @@ import 'pointer_router.dart';
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>();
/// The base class that all GestureRecognizers should inherit from.
......@@ -51,7 +55,8 @@ abstract class GestureRecognizer extends GestureArenaMember {
/// recognizer looks for, like 'tap' or 'horizontal drag'.
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
dynamic/*=T*/ invokeCallback/*<T>*/(String name, RecognizerCallback<dynamic/*=T*/> callback) {
dynamic/*=T*/ result;
......
......@@ -25,28 +25,32 @@ abstract class _VelocityTrackerStrategy {
}
class _Movement {
Duration eventTime = Duration.ZERO;
Point position = Point.origin;
const _Movement(this.eventTime, this.position);
final Duration eventTime;
final Point position;
@override
String toString() => 'Movement($position at $eventTime)';
}
class _LeastSquaresVelocityTrackerStrategy extends _VelocityTrackerStrategy {
static const int kHistorySize = 20;
static const int kHorizonMilliseconds = 100;
_LeastSquaresVelocityTrackerStrategy(this.degree);
final int degree;
final List<_Movement> _movements = new List<_Movement>(kHistorySize);
int _index = 0;
static const int kHistorySize = 20;
static const int kHorizonMilliseconds = 40;
@override
void addMovement(Duration timeStamp, Point position) {
_index += 1;
if (_index == kHistorySize)
_index = 0;
_Movement movement = _getMovement(_index);
movement.eventTime = timeStamp;
movement.position = position;
_movements[_index] = new _Movement(timeStamp, position);
}
@override
......@@ -58,9 +62,15 @@ class _LeastSquaresVelocityTrackerStrategy extends _VelocityTrackerStrategy {
List<double> time = new List<double>();
int m = 0;
int index = _index;
_Movement newestMovement = _getMovement(index);
_Movement newestMovement = _movements[index];
if (newestMovement == null)
return null;
do {
_Movement movement = _getMovement(index);
_Movement movement = _movements[index];
if (movement == null)
break;
double age = (newestMovement.eventTime - movement.eventTime).inMilliseconds.toDouble();
if (age > kHorizonMilliseconds)
......@@ -76,9 +86,6 @@ class _LeastSquaresVelocityTrackerStrategy extends _VelocityTrackerStrategy {
m += 1;
} 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.
int n = degree;
if (n > m - 1)
......@@ -118,15 +125,6 @@ class _LeastSquaresVelocityTrackerStrategy extends _VelocityTrackerStrategy {
_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.
......@@ -184,25 +182,26 @@ class Velocity {
/// The quality of the velocity estimation will be better if more data
/// points have been received.
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.
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;
/// 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) {
if (timeStamp - _lastTimeStamp >= kAssumePointerMoveStoppedTime)
_strategy.clear();
_lastTimeStamp = timeStamp;
_strategy.addMovement(timeStamp, position);
}
......@@ -225,8 +224,4 @@ class VelocityTracker {
}
return null;
}
static _VelocityTrackerStrategy _createStrategy() {
return new _LeastSquaresVelocityTrackerStrategy(2);
}
}
......@@ -2552,7 +2552,8 @@ class RenderSemanticsGestureHandler extends RenderProxyBox implements SemanticsA
if (onHorizontalDragUpdate != null) {
final double primaryDelta = size.width * -scrollFactor;
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;
......@@ -2560,7 +2561,8 @@ class RenderSemanticsGestureHandler extends RenderProxyBox implements SemanticsA
if (onHorizontalDragUpdate != null) {
final double primaryDelta = size.width * scrollFactor;
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;
......@@ -2568,7 +2570,8 @@ class RenderSemanticsGestureHandler extends RenderProxyBox implements SemanticsA
if (onVerticalDragUpdate != null) {
final double primaryDelta = size.height * -scrollFactor;
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;
......@@ -2576,7 +2579,8 @@ class RenderSemanticsGestureHandler extends RenderProxyBox implements SemanticsA
if (onVerticalDragUpdate != null) {
final double primaryDelta = size.height * scrollFactor;
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;
......
......@@ -133,8 +133,10 @@ void main() {
HorizontalDragGestureRecognizer drag = new HorizontalDragGestureRecognizer();
Velocity velocity;
double primaryVelocity;
drag.onEnd = (DragEndDetails details) {
velocity = details.velocity;
primaryVelocity = details.primaryVelocity;
};
TestPointer pointer = new TestPointer(5);
......@@ -155,7 +157,16 @@ void main() {
tester.route(pointer.move(const Point(120.0, 25.0), 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.dy, moreOrLessEquals(0.0));
expect(primaryVelocity, velocity.pixelsPerSecond.dx);
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