diff --git a/packages/flutter/lib/src/gestures/drag_details.dart b/packages/flutter/lib/src/gestures/drag_details.dart index 7cd2ef2ce3..d18a05fe74 100644 --- a/packages/flutter/lib/src/gestures/drag_details.dart +++ b/packages/flutter/lib/src/gestures/drag_details.dart @@ -52,9 +52,15 @@ class DragStartDetails { /// Creates details for a [GestureDragStartCallback]. /// /// The [globalPosition] argument must not be null. - DragStartDetails({ this.globalPosition: Offset.zero }) + DragStartDetails({ this.sourceTimeStamp, this.globalPosition: Offset.zero }) : assert(globalPosition != null); + /// Recorded timestamp of the source pointer event that triggered the drag + /// event. + /// + /// Could be null if triggered from proxied events such as accessibility. + final Duration sourceTimeStamp; + /// The global position at which the pointer contacted the screen. /// /// Defaults to the origin if not specified in the constructor. @@ -94,6 +100,7 @@ class DragUpdateDetails { /// /// The [globalPosition] argument must be provided and must not be null. DragUpdateDetails({ + this.sourceTimeStamp, this.delta: Offset.zero, this.primaryDelta, @required this.globalPosition @@ -102,6 +109,12 @@ class DragUpdateDetails { || (primaryDelta == delta.dx && delta.dy == 0.0) || (primaryDelta == delta.dy && delta.dx == 0.0)); + /// Recorded timestamp of the source pointer event that triggered the drag + /// event. + /// + /// Could be null if triggered from proxied events such as accessibility. + final Duration sourceTimeStamp; + /// The amount the pointer has moved since the previous update. /// /// If the [GestureDragUpdateCallback] is for a one-dimensional drag (e.g., diff --git a/packages/flutter/lib/src/gestures/monodrag.dart b/packages/flutter/lib/src/gestures/monodrag.dart index b47ca050e5..f4efcc90bc 100644 --- a/packages/flutter/lib/src/gestures/monodrag.dart +++ b/packages/flutter/lib/src/gestures/monodrag.dart @@ -101,6 +101,7 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer { _DragState _state = _DragState.ready; Offset _initialPosition; Offset _pendingDragOffset; + Duration _lastPendingEventTimestamp; bool _isFlingGesture(VelocityEstimate estimate); Offset _getDeltaForDetails(Offset delta); @@ -117,6 +118,7 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer { _state = _DragState.possible; _initialPosition = event.position; _pendingDragOffset = Offset.zero; + _lastPendingEventTimestamp = event.timeStamp; if (onDown != null) invokeCallback('onDown', () => onDown(new DragDownDetails(globalPosition: _initialPosition))); // ignore: STRONG_MODE_INVALID_CAST_FUNCTION_EXPR, https://github.com/dart-lang/sdk/issues/27504 } else if (_state == _DragState.accepted) { @@ -139,6 +141,7 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer { if (_state == _DragState.accepted) { if (onUpdate != null) { invokeCallback('onUpdate', () => onUpdate(new DragUpdateDetails( // ignore: STRONG_MODE_INVALID_CAST_FUNCTION_EXPR, https://github.com/dart-lang/sdk/issues/27504 + sourceTimeStamp: event.timeStamp, delta: _getDeltaForDetails(delta), primaryDelta: _getPrimaryValueFromOffset(delta), globalPosition: event.position, @@ -146,6 +149,7 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer { } } else { _pendingDragOffset += delta; + _lastPendingEventTimestamp = event.timeStamp; if (_hasSufficientPendingDragDeltaToAccept) resolve(GestureDisposition.accepted); } @@ -158,14 +162,18 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer { if (_state != _DragState.accepted) { _state = _DragState.accepted; final Offset delta = _pendingDragOffset; + final Duration timestamp = _lastPendingEventTimestamp; _pendingDragOffset = Offset.zero; + _lastPendingEventTimestamp = null; if (onStart != null) { invokeCallback('onStart', () => onStart(new DragStartDetails( // ignore: STRONG_MODE_INVALID_CAST_FUNCTION_EXPR, https://github.com/dart-lang/sdk/issues/27504 + sourceTimeStamp: timestamp, globalPosition: _initialPosition, ))); } if (delta != Offset.zero && onUpdate != null) { invokeCallback('onUpdate', () => onUpdate(new DragUpdateDetails( // ignore: STRONG_MODE_INVALID_CAST_FUNCTION_EXPR, https://github.com/dart-lang/sdk/issues/27504 + sourceTimeStamp: timestamp, delta: _getDeltaForDetails(delta), primaryDelta: _getPrimaryValueFromOffset(delta), globalPosition: _initialPosition, diff --git a/packages/flutter/lib/src/gestures/multidrag.dart b/packages/flutter/lib/src/gestures/multidrag.dart index 9ab4650087..2086593841 100644 --- a/packages/flutter/lib/src/gestures/multidrag.dart +++ b/packages/flutter/lib/src/gestures/multidrag.dart @@ -45,6 +45,8 @@ abstract class MultiDragPointerState { Offset get pendingDelta => _pendingDelta; Offset _pendingDelta = Offset.zero; + Duration _lastPendingEventTimestamp; + GestureArenaEntry _arenaEntry; void _setArenaEntry(GestureArenaEntry entry) { assert(_arenaEntry == null); @@ -68,12 +70,14 @@ abstract class MultiDragPointerState { assert(pendingDelta == null); // Call client last to avoid reentrancy. _client.update(new DragUpdateDetails( + sourceTimeStamp: event.timeStamp, delta: event.delta, globalPosition: event.position, )); } else { assert(pendingDelta != null); _pendingDelta += event.delta; + _lastPendingEventTimestamp = event.timeStamp; checkForResolutionAfterMove(); } } @@ -101,6 +105,7 @@ abstract class MultiDragPointerState { assert(_client == null); assert(pendingDelta != null); _pendingDelta = null; + _lastPendingEventTimestamp = null; _arenaEntry = null; } @@ -111,10 +116,12 @@ abstract class MultiDragPointerState { assert(pendingDelta != null); _client = client; final DragUpdateDetails details = new DragUpdateDetails( + sourceTimeStamp: _lastPendingEventTimestamp, delta: pendingDelta, globalPosition: initialPosition, ); _pendingDelta = null; + _lastPendingEventTimestamp = null; // Call client last to avoid reentrancy. _client.update(details); } @@ -131,6 +138,7 @@ abstract class MultiDragPointerState { } else { assert(pendingDelta != null); _pendingDelta = null; + _lastPendingEventTimestamp = null; } } @@ -145,6 +153,7 @@ abstract class MultiDragPointerState { } else { assert(pendingDelta != null); _pendingDelta = null; + _lastPendingEventTimestamp = null; } } diff --git a/packages/flutter/test/gestures/drag_test.dart b/packages/flutter/test/gestures/drag_test.dart index 82c6453b43..048705ee21 100644 --- a/packages/flutter/test/gestures/drag_test.dart +++ b/packages/flutter/test/gestures/drag_test.dart @@ -133,6 +133,37 @@ void main() { drag.dispose(); }); + testGesture('Should report original timestamps', (GestureTester tester) { + final HorizontalDragGestureRecognizer drag = new HorizontalDragGestureRecognizer(); + + Duration startTimestamp; + drag.onStart = (DragStartDetails details) { + startTimestamp = details.sourceTimeStamp; + }; + + Duration updatedTimestamp; + drag.onUpdate = (DragUpdateDetails details) { + updatedTimestamp = details.sourceTimeStamp; + }; + + final TestPointer pointer = new TestPointer(5); + final PointerDownEvent down = pointer.down(const Offset(10.0, 10.0), timeStamp: const Duration(milliseconds: 100)); + drag.addPointer(down); + tester.closeArena(5); + expect(startTimestamp, isNull); + + tester.route(down); + expect(startTimestamp, const Duration(milliseconds: 100)); + + tester.route(pointer.move(const Offset(20.0, 25.0), timeStamp: const Duration(milliseconds: 200))); + expect(updatedTimestamp, const Duration(milliseconds: 200)); + + tester.route(pointer.move(const Offset(20.0, 25.0), timeStamp: const Duration(milliseconds: 300))); + expect(updatedTimestamp, const Duration(milliseconds: 300)); + + drag.dispose(); + }); + testGesture('Drag with multiple pointers', (GestureTester tester) { final HorizontalDragGestureRecognizer drag1 = new HorizontalDragGestureRecognizer(); final VerticalDragGestureRecognizer drag2 = new VerticalDragGestureRecognizer();