Commit 4fb47600 authored by Adam Barth's avatar Adam Barth

Add a Velocity class to be explicit about units

We were using an Offset, which represented pixels/second, but it wasn't
clear to clients whether that was pixels/ms. Now we use a Velocity class
that is explict about the units.

Fixes #1510
Fixes #785
parent a724d6cc
......@@ -16,19 +16,19 @@ enum DragState {
typedef void GestureDragStartCallback(Point globalPosition);
typedef void GestureDragUpdateCallback(double delta);
typedef void GestureDragEndCallback(Offset velocity);
typedef void GestureDragEndCallback(Velocity velocity);
typedef void GesturePanStartCallback(Point globalPosition);
typedef void GesturePanUpdateCallback(Offset delta);
typedef void GesturePanEndCallback(Offset velocity);
typedef void GesturePanEndCallback(Velocity velocity);
typedef void _GesturePolymorphicUpdateCallback<T>(T delta);
bool _isFlingGesture(Offset velocity) {
bool _isFlingGesture(Velocity velocity) {
assert(velocity != null);
double velocitySquared = velocity.dx * velocity.dx + velocity.dy * velocity.dy;
return velocitySquared > kMinFlingVelocity * kMinFlingVelocity
&& velocitySquared < kMaxFlingVelocity * kMaxFlingVelocity;
final double speedSquared = velocity.pixelsPerSecond.distanceSquared;
return speedSquared > kMinFlingVelocity * kMinFlingVelocity
&& speedSquared < kMaxFlingVelocity * kMaxFlingVelocity;
}
abstract class _DragGestureRecognizer<T extends dynamic> extends OneSequenceGestureRecognizer {
......@@ -110,11 +110,11 @@ abstract class _DragGestureRecognizer<T extends dynamic> extends OneSequenceGest
VelocityTracker tracker = _velocityTrackers[pointer];
assert(tracker != null);
Offset velocity = tracker.getVelocity();
Velocity velocity = tracker.getVelocity();
if (velocity != null && _isFlingGesture(velocity))
onEnd(velocity);
else
onEnd(Offset.zero);
onEnd(Velocity.zero);
}
_velocityTrackers.clear();
}
......
......@@ -16,7 +16,7 @@ typedef Drag GestureMultiDragStartCallback(Point position);
class Drag {
void move(Offset offset) { }
void end(Offset velocity) { }
void end(Velocity velocity) { }
void cancel() { }
}
......
......@@ -208,6 +208,28 @@ class _LeastSquaresVelocityTrackerStrategy extends _VelocityTrackerStrategy {
}
/// A velocity in two dimensions.
class Velocity {
const Velocity({ this.pixelsPerSecond });
/// A velocity that isn't moving at all.
static const Velocity zero = const Velocity(pixelsPerSecond: Offset.zero);
/// The number of pixels per second of velocity in the x and y directions.
final Offset pixelsPerSecond;
bool operator ==(dynamic other) {
if (other is! Velocity)
return false;
final Velocity typedOther = other;
return pixelsPerSecond == typedOther.pixelsPerSecond;
}
int get hashCode => pixelsPerSecond.hashCode;
String toString() => 'Velocity(${pixelsPerSecond.dx.toStringAsFixed(1)}, ${pixelsPerSecond.dy.toStringAsFixed(1)})';
}
/// Computes a pointer velocity based on data from PointerMove events.
///
/// The input data is provided by calling addPosition(). Adding data
......@@ -248,12 +270,14 @@ class VelocityTracker {
///
/// getVelocity() will return null if no estimate is available or if
/// the velocity is zero.
Offset getVelocity() {
Velocity getVelocity() {
_Estimate estimate = _strategy.getEstimate();
if (estimate != null && estimate.degree >= 1) {
return new Offset( // convert from pixels/ms to pixels/s
estimate.xCoefficients[1] * 1000,
estimate.yCoefficients[1] * 1000
return new Velocity(
pixelsPerSecond: new Offset( // convert from pixels/ms to pixels/s
estimate.xCoefficients[1] * 1000,
estimate.yCoefficients[1] * 1000
)
);
}
return null;
......
......@@ -59,11 +59,11 @@ class _BottomSheetState extends State<BottomSheet> {
config.animationController.value -= delta / (_childHeight ?? delta);
}
void _handleDragEnd(Offset velocity) {
void _handleDragEnd(Velocity velocity) {
if (_dismissUnderway)
return;
if (velocity.dy > _kMinFlingVelocity) {
double flingVelocity = -velocity.dy / _childHeight;
if (velocity.pixelsPerSecond.dy > _kMinFlingVelocity) {
double flingVelocity = -velocity.pixelsPerSecond.dy / _childHeight;
config.animationController.fling(velocity: flingVelocity);
if (flingVelocity < 0.0)
config.onClosing();
......
......@@ -135,11 +135,11 @@ class DrawerControllerState extends State<DrawerController> {
_controller.value += delta / _width;
}
void _settle(Offset velocity) {
void _settle(Velocity velocity) {
if (_controller.isDismissed)
return;
if (velocity.dx.abs() >= _kMinFlingVelocity) {
_controller.fling(velocity: velocity.dx / _width);
if (velocity.pixelsPerSecond.dx.abs() >= _kMinFlingVelocity) {
_controller.fling(velocity: velocity.pixelsPerSecond.dx / _width);
} else if (_controller.value < 0.5) {
close();
} else {
......
......@@ -145,7 +145,7 @@ class _RenderSlider extends RenderConstrainedBox {
}
}
void _handleDragEnd(Offset velocity) {
void _handleDragEnd(Velocity velocity) {
if (_active) {
_active = false;
_currentDragValue = 0.0;
......
......@@ -156,7 +156,7 @@ class _RenderSwitch extends RenderToggleable {
}
}
void _handleDragEnd(Offset velocity) {
void _handleDragEnd(Velocity velocity) {
if (position.value >= 0.5)
positionController.forward();
else
......
......@@ -425,7 +425,7 @@ class _DialState extends State<_Dial> {
_notifyOnChangedIfNeeded();
}
void _handlePanEnd(Offset velocity) {
void _handlePanEnd(Velocity velocity) {
assert(_dragging);
_dragging = false;
_position = null;
......
......@@ -182,9 +182,9 @@ class _DismissableState extends State<Dismissable> {
_dismissController.value = _dragExtent.abs() / _size.width;
}
bool _isFlingGesture(Offset velocity) {
double vx = velocity.dx;
double vy = velocity.dy;
bool _isFlingGesture(Velocity velocity) {
double vx = velocity.pixelsPerSecond.dx;
double vy = velocity.pixelsPerSecond.dy;
if (_directionIsYAxis) {
if (vy.abs() - vx.abs() < _kMinFlingVelocityDelta)
return false;
......@@ -211,7 +211,7 @@ class _DismissableState extends State<Dismissable> {
return false;
}
void _handleDragEnd(Offset velocity) {
void _handleDragEnd(Velocity velocity) {
if (!_isActive || _dismissController.isAnimating)
return;
setState(() {
......@@ -219,7 +219,7 @@ class _DismissableState extends State<Dismissable> {
if (_dismissController.isCompleted) {
_startResizeAnimation();
} else if (_isFlingGesture(velocity)) {
double flingVelocity = _directionIsYAxis ? velocity.dy : velocity.dx;
double flingVelocity = _directionIsYAxis ? velocity.pixelsPerSecond.dy : velocity.pixelsPerSecond.dx;
_dragExtent = flingVelocity.sign;
_dismissController.fling(velocity: flingVelocity.abs() * _kFlingVelocityScale);
} else if (_dismissController.value > _kDismissCardThreshold) {
......
......@@ -396,7 +396,7 @@ class _DragAvatar<T> extends Drag {
_position += offset;
update(_position);
}
void end(Offset velocity) {
void end(Velocity velocity) {
finish(_DragEndKind.dropped);
}
void cancel() {
......
......@@ -25,7 +25,8 @@ export 'package:flutter/gestures.dart' show
GesturePanEndCallback,
GestureScaleStartCallback,
GestureScaleUpdateCallback,
GestureScaleEndCallback;
GestureScaleEndCallback,
Velocity;
/// A widget that detects gestures.
///
......@@ -414,7 +415,7 @@ class _GestureSemantics extends OneChildRenderObjectWidget {
if (onHorizontalDragUpdate != null)
onHorizontalDragUpdate(delta);
if (onHorizontalDragEnd != null)
onHorizontalDragEnd(Offset.zero);
onHorizontalDragEnd(Velocity.zero);
} else {
assert(_watchingPans);
if (onPanStart != null)
......@@ -422,7 +423,7 @@ class _GestureSemantics extends OneChildRenderObjectWidget {
if (onPanUpdate != null)
onPanUpdate(new Offset(delta, 0.0));
if (onPanEnd != null)
onPanEnd(Offset.zero);
onPanEnd(Velocity.zero);
}
}
......@@ -433,7 +434,7 @@ class _GestureSemantics extends OneChildRenderObjectWidget {
if (onVerticalDragUpdate != null)
onVerticalDragUpdate(delta);
if (onVerticalDragEnd != null)
onVerticalDragEnd(Offset.zero);
onVerticalDragEnd(Velocity.zero);
} else {
assert(_watchingPans);
if (onPanStart != null)
......@@ -441,7 +442,7 @@ class _GestureSemantics extends OneChildRenderObjectWidget {
if (onPanUpdate != null)
onPanUpdate(new Offset(0.0, delta));
if (onPanEnd != null)
onPanEnd(Offset.zero);
onPanEnd(Velocity.zero);
}
}
......
......@@ -450,8 +450,8 @@ abstract class ScrollableState<T extends Scrollable> extends State<T> {
scrollBy(pixelOffsetToScrollOffset(delta));
}
Future _handleDragEnd(Offset velocity) {
double scrollVelocity = pixelDeltaToScrollOffset(velocity) / Duration.MILLISECONDS_PER_SECOND;
Future _handleDragEnd(Velocity velocity) {
double scrollVelocity = pixelDeltaToScrollOffset(velocity.pixelsPerSecond) / Duration.MILLISECONDS_PER_SECOND;
// The gesture velocity properties are pixels/second, config min,max limits are pixels/ms
return fling(scrollVelocity.clamp(-kMaxFlingVelocity, kMaxFlingVelocity)).then((_) {
dispatchOnScrollEnd();
......
......@@ -58,7 +58,7 @@ class _SemanticsDebuggerState extends State<SemanticsDebugger> {
_lastPointerDownLocation = null;
});
}
void _handlePanEnd(Offset velocity) {
void _handlePanEnd(Velocity velocity) {
assert(_lastPointerDownLocation != null);
_SemanticsDebuggerListener.instance.handlePanEnd(_lastPointerDownLocation, velocity);
setState(() {
......@@ -329,16 +329,18 @@ class _SemanticsDebuggerListener implements mojom.SemanticsListener {
void handleLongPress(Point position) {
_server.longPress(_hitTest(position, (_SemanticsDebuggerEntry entry) => entry.canBeLongPressed)?.id ?? 0);
}
void handlePanEnd(Point position, Offset velocity) {
if (velocity.dx.abs() == velocity.dy.abs())
void handlePanEnd(Point position, Velocity velocity) {
double vx = velocity.pixelsPerSecond.dx;
double vy = velocity.pixelsPerSecond.dy;
if (vx.abs() == vy.abs())
return;
if (velocity.dx.abs() > velocity.dy.abs()) {
if (velocity.dx.sign < 0)
if (vx.abs() > vy.abs()) {
if (vx.sign < 0)
_server.scrollLeft(_hitTest(position, (_SemanticsDebuggerEntry entry) => entry.canBeScrolledHorizontally)?.id ?? 0);
else
_server.scrollRight(_hitTest(position, (_SemanticsDebuggerEntry entry) => entry.canBeScrolledHorizontally)?.id ?? 0);
} else {
if (velocity.dy.sign < 0)
if (vy.sign < 0)
_server.scrollUp(_hitTest(position, (_SemanticsDebuggerEntry entry) => entry.canBeScrolledVertically)?.id ?? 0);
else
_server.scrollDown(_hitTest(position, (_SemanticsDebuggerEntry entry) => entry.canBeScrolledVertically)?.id ?? 0);
......
......@@ -30,7 +30,7 @@ void main() {
};
bool didEndPan = false;
pan.onEnd = (Offset velocity) {
pan.onEnd = (Velocity velocity) {
didEndPan = true;
};
......
......@@ -12,10 +12,10 @@ bool _withinTolerance(double actual, double expected) {
return diff.abs() < kTolerance;
}
bool _checkVelocity(Offset actual, Offset expected) {
bool _checkVelocity(Velocity actual, Offset expected) {
return (actual != null)
&& _withinTolerance(actual.dx, expected.dx)
&& _withinTolerance(actual.dy, expected.dy);
&& _withinTolerance(actual.pixelsPerSecond.dx, expected.dx)
&& _withinTolerance(actual.pixelsPerSecond.dy, expected.dy);
}
void main() {
......
......@@ -22,7 +22,7 @@ void main() {
onVerticalDragUpdate: (double scrollDelta) {
updatedDragDelta = scrollDelta;
},
onVerticalDragEnd: (Offset velocity) {
onVerticalDragEnd: (Velocity velocity) {
didEndDrag = true;
},
child: new Container(
......@@ -73,9 +73,9 @@ void main() {
Widget widget = new GestureDetector(
onVerticalDragUpdate: (double delta) { dragDistance += delta; },
onVerticalDragEnd: (Offset velocity) { gestureCount += 1; },
onVerticalDragEnd: (Velocity velocity) { gestureCount += 1; },
onHorizontalDragUpdate: (_) { fail("gesture should not match"); },
onHorizontalDragEnd: (Offset velocity) { fail("gesture should not match"); },
onHorizontalDragEnd: (Velocity velocity) { fail("gesture should not match"); },
child: new Container(
decoration: const BoxDecoration(
backgroundColor: const Color(0xFF00FF00)
......
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