Unverified Commit 60a1b2b9 authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

Revert "Redo: Add buttons to gestures (#31819)" (#31912)

This reverts commit fea2c7d6.
parent df4dc7cd
...@@ -47,16 +47,12 @@ class _PointerState { ...@@ -47,16 +47,12 @@ class _PointerState {
// https://github.com/flutter/flutter/issues/30454 // https://github.com/flutter/flutter/issues/30454
int _synthesiseDownButtons(int buttons, PointerDeviceKind kind) { int _synthesiseDownButtons(int buttons, PointerDeviceKind kind) {
switch (kind) { switch (kind) {
case PointerDeviceKind.mouse:
return buttons;
case PointerDeviceKind.touch: case PointerDeviceKind.touch:
case PointerDeviceKind.stylus: case PointerDeviceKind.stylus:
case PointerDeviceKind.invertedStylus: case PointerDeviceKind.invertedStylus:
return buttons | kPrimaryButton; return buttons | kPrimaryButton;
default: default:
// We have no information about the device but we know we never want return buttons;
// buttons to be 0 when the pointer is down.
return buttons == 0 ? kPrimaryButton : buttons;
} }
} }
......
...@@ -8,35 +8,22 @@ import 'package:flutter/foundation.dart'; ...@@ -8,35 +8,22 @@ import 'package:flutter/foundation.dart';
export 'dart:ui' show Offset, PointerDeviceKind; export 'dart:ui' show Offset, PointerDeviceKind;
/// The bit of [PointerEvent.buttons] that corresponds to a cross-device /// The bit of [PointerEvent.buttons] that corresponds to the "primary
/// behavior of "primary operation". /// action" on any device.
/// ///
/// More specifially, it includes: /// More specifially,
/// ///
/// * [kTouchContact]: The pointer contacts the touch screen. /// * For touch screen, it's when the pointer contacts the screen.
/// * [kStylusContact]: The stylus contacts the screen. /// * For stylus and inverted stylus, it's when the pen contacts the screen.
/// * [kPrimaryMouseButton]: The primary mouse button. /// * For mouse, it's when the primary button is pressed.
/// ///
/// See also: /// See also:
/// ///
/// * [kSecondaryButton], which describes a cross-device behavior of /// * [kTouchContact]: an alias of this constant when used by touch screen.
/// "secondary operation". /// * [kStylusContact]: an alias of this constant when used by stylus.
/// * [kPrimaryMouseButton]: an alias of this constant when used by mouse.
const int kPrimaryButton = 0x01; const int kPrimaryButton = 0x01;
/// The bit of [PointerEvent.buttons] that corresponds to a cross-device
/// behavior of "secondary operation".
///
/// It is equivalent to:
///
/// * [kPrimaryStylusButton]: The stylus contacts the screen.
/// * [kSecondaryMouseButton]: The primary mouse button.
///
/// See also:
///
/// * [kPrimaryButton], which describes a cross-device behavior of
/// "primary operation".
const int kSecondaryButton = 0x02;
/// The bit of [PointerEvent.buttons] that corresponds to the primary mouse button. /// The bit of [PointerEvent.buttons] that corresponds to the primary mouse button.
/// ///
/// The primary mouse button is typically the left button on the top of the /// The primary mouse button is typically the left button on the top of the
...@@ -44,40 +31,28 @@ const int kSecondaryButton = 0x02; ...@@ -44,40 +31,28 @@ const int kSecondaryButton = 0x02;
/// ///
/// See also: /// See also:
/// ///
/// * [kPrimaryButton], which has the same value but describes its cross-device /// * [kTouchContact]: an alias of this constant when used by touch screen.
/// concept.
const int kPrimaryMouseButton = kPrimaryButton; const int kPrimaryMouseButton = kPrimaryButton;
/// The bit of [PointerEvent.buttons] that corresponds to the secondary mouse button. /// The bit of [PointerEvent.buttons] that corresponds to the secondary mouse button.
/// ///
/// The secondary mouse button is typically the right button on the top of the /// The secondary mouse button is typically the right button on the top of the
/// mouse but can be reconfigured to be a different physical button. /// mouse but can be reconfigured to be a different physical button.
/// const int kSecondaryMouseButton = 0x02;
/// See also:
///
/// * [kSecondaryButton], which has the same value but describes its cross-device
/// concept.
const int kSecondaryMouseButton = kSecondaryButton;
/// The bit of [PointerEvent.buttons] that corresponds to when a stylus /// The bit of [PointerEvent.buttons] that corresponds to when a stylus
/// contacting the screen. /// contacting the screen.
/// ///
/// See also: /// See also:
/// ///
/// * [kPrimaryButton], which has the same value but describes its cross-device /// * [kPrimaryButton]: an alias of this constant for any device.
/// concept.
const int kStylusContact = kPrimaryButton; const int kStylusContact = kPrimaryButton;
/// The bit of [PointerEvent.buttons] that corresponds to the primary stylus button. /// The bit of [PointerEvent.buttons] that corresponds to the primary stylus button.
/// ///
/// The primary stylus button is typically the top of the stylus and near the /// The primary stylus button is typically the top of the stylus and near the
/// tip but can be reconfigured to be a different physical button. /// tip but can be reconfigured to be a different physical button.
/// const int kPrimaryStylusButton = 0x02;
/// See also:
///
/// * [kSecondaryButton], which has the same value but describes its cross-device
/// concept.
const int kPrimaryStylusButton = kSecondaryButton;
/// The bit of [PointerEvent.buttons] that corresponds to the middle mouse button. /// The bit of [PointerEvent.buttons] that corresponds to the middle mouse button.
/// ///
...@@ -109,8 +84,7 @@ const int kForwardMouseButton = 0x10; ...@@ -109,8 +84,7 @@ const int kForwardMouseButton = 0x10;
/// ///
/// See also: /// See also:
/// ///
/// * [kPrimaryButton], which has the same value but describes its cross-device /// * [kPrimaryButton]: an alias of this constant for any device.
/// concept.
const int kTouchContact = kPrimaryButton; const int kTouchContact = kPrimaryButton;
/// The bit of [PointerEvent.buttons] that corresponds to the nth mouse button. /// The bit of [PointerEvent.buttons] that corresponds to the nth mouse button.
...@@ -130,47 +104,6 @@ int nthMouseButton(int number) => (kPrimaryMouseButton << (number - 1)) & kMaxUn ...@@ -130,47 +104,6 @@ int nthMouseButton(int number) => (kPrimaryMouseButton << (number - 1)) & kMaxUn
/// for some stylus buttons. /// for some stylus buttons.
int nthStylusButton(int number) => (kPrimaryStylusButton << (number - 1)) & kMaxUnsignedSMI; int nthStylusButton(int number) => (kPrimaryStylusButton << (number - 1)) & kMaxUnsignedSMI;
/// Returns the button of `buttons` with the smallest integer.
///
/// The `buttons` parameter is a bitfield where each set bit represents a button.
/// This function returns the set bit closest to the least significant bit.
///
/// It returns zero when `buttons` is zero.
///
/// Example:
///
/// ```dart
/// assert(rightmostButton(0x1) == 0x1);
/// assert(rightmostButton(0x11) == 0x1);
/// assert(rightmostButton(0) == 0);
/// ```
///
/// See also:
///
/// * [isSingleButton], which checks if a `buttons` contains exactly one button.
int smallestButton(int buttons) => buttons & (-buttons);
/// Returns whether `buttons` contains one and only one button.
///
/// The `buttons` parameter is a bitfield where each set bit represents a button.
/// This function returns whether there is only one set bit in the given integer.
///
/// It returns false when `buttons` is zero.
///
/// Example:
///
/// ```dart
/// assert(isSingleButton(0x1) == true);
/// assert(isSingleButton(0x11) == false);
/// assert(isSingleButton(0) == false);
/// ```
///
/// See also:
///
/// * [smallestButton], which returns the button in a `buttons` bitfield with
/// the smallest integer button.
bool isSingleButton(int buttons) => buttons != 0 && (smallestButton(buttons) == buttons);
/// Base class for touch, stylus, or mouse events. /// Base class for touch, stylus, or mouse events.
/// ///
/// Pointer events operate in the coordinate space of the screen, scaled to /// Pointer events operate in the coordinate space of the screen, scaled to
......
...@@ -109,10 +109,6 @@ class LongPressEndDetails { ...@@ -109,10 +109,6 @@ class LongPressEndDetails {
/// until it's recognized. Once the gesture is accepted, the finger can be /// until it's recognized. Once the gesture is accepted, the finger can be
/// moved, triggering [onLongPressMoveUpdate] callbacks, unless the /// moved, triggering [onLongPressMoveUpdate] callbacks, unless the
/// [postAcceptSlopTolerance] constructor argument is specified. /// [postAcceptSlopTolerance] constructor argument is specified.
///
/// [LongPressGestureRecognizer] competes on pointer events of [kPrimaryButton]
/// only when it has at least one non-null callback. If it has no callbacks, it
/// is a no-op.
class LongPressGestureRecognizer extends PrimaryPointerGestureRecognizer { class LongPressGestureRecognizer extends PrimaryPointerGestureRecognizer {
/// Creates a long-press gesture recognizer. /// Creates a long-press gesture recognizer.
/// ///
...@@ -137,159 +133,89 @@ class LongPressGestureRecognizer extends PrimaryPointerGestureRecognizer { ...@@ -137,159 +133,89 @@ class LongPressGestureRecognizer extends PrimaryPointerGestureRecognizer {
); );
bool _longPressAccepted = false; bool _longPressAccepted = false;
Offset _longPressOrigin; Offset _longPressOrigin;
// The buttons sent by `PointerDownEvent`. If a `PointerMoveEvent` comes with a
// different set of buttons, the gesture is canceled.
int _initialButtons;
/// Called when a long press gesture by a primary button has been recognized. /// Called when a long press gesture has been recognized.
/// ///
/// See also: /// See also:
/// ///
/// * [kPrimaryButton], the button this callback responds to.
/// * [onLongPressStart], which has the same timing but has data for the /// * [onLongPressStart], which has the same timing but has data for the
/// press location. /// press location.
GestureLongPressCallback onLongPress; GestureLongPressCallback onLongPress;
/// Called when a long press gesture by a primary button has been recognized. /// Callback for long press start with gesture location.
/// ///
/// See also: /// See also:
/// ///
/// * [kPrimaryButton], the button this callback responds to. /// * [onLongPress], which has the same timing but without the location data.
/// * [onLongPress], which has the same timing but without details.
/// * [LongPressStartDetails], which is passed as an argument to this callback.
GestureLongPressStartCallback onLongPressStart; GestureLongPressStartCallback onLongPressStart;
/// Called when moving after the long press by a primary button is recognized. /// Callback for moving the gesture after the lang press is recognized.
///
/// See also:
///
/// * [kPrimaryButton], the button this callback responds to.
/// * [LongPressMoveUpdateDetails], which is passed as an argument to this
/// callback.
GestureLongPressMoveUpdateCallback onLongPressMoveUpdate; GestureLongPressMoveUpdateCallback onLongPressMoveUpdate;
/// Called when the pointer stops contacting the screen after a long-press /// Called when the pointer stops contacting the screen after the long-press.
/// by a primary button.
/// ///
/// See also: /// See also:
/// ///
/// * [kPrimaryButton], the button this callback responds to.
/// * [onLongPressEnd], which has the same timing but has data for the up /// * [onLongPressEnd], which has the same timing but has data for the up
/// gesture location. /// gesture location.
GestureLongPressUpCallback onLongPressUp; GestureLongPressUpCallback onLongPressUp;
/// Called when the pointer stops contacting the screen after a long-press /// Callback for long press end with gesture location.
/// by a primary button.
/// ///
/// See also: /// See also:
/// ///
/// * [kPrimaryButton], the button this callback responds to. /// * [onLongPressUp], which has the same timing but without the location data.
/// * [onLongPressUp], which has the same timing, but without details.
/// * [LongPressEndDetails], which is passed as an argument to this
/// callback.
GestureLongPressEndCallback onLongPressEnd; GestureLongPressEndCallback onLongPressEnd;
@override
bool isPointerAllowed(PointerDownEvent event) {
switch (event.buttons) {
case kPrimaryButton:
if (onLongPressStart == null &&
onLongPress == null &&
onLongPressMoveUpdate == null &&
onLongPressEnd == null &&
onLongPressUp == null)
return false;
break;
default:
return false;
}
return super.isPointerAllowed(event);
}
@override @override
void didExceedDeadline() { void didExceedDeadline() {
// Exceeding the deadline puts the gesture in the accepted state.
resolve(GestureDisposition.accepted); resolve(GestureDisposition.accepted);
_longPressAccepted = true; _longPressAccepted = true;
super.acceptGesture(primaryPointer); super.acceptGesture(primaryPointer);
_checkLongPressStart(); if (onLongPress != null) {
invokeCallback<void>('onLongPress', onLongPress);
}
if (onLongPressStart != null) {
invokeCallback<void>('onLongPressStart', () {
onLongPressStart(LongPressStartDetails(
globalPosition: _longPressOrigin,
));
});
}
} }
@override @override
void handlePrimaryPointer(PointerEvent event) { void handlePrimaryPointer(PointerEvent event) {
if (event is PointerUpEvent) { if (event is PointerUpEvent) {
if (_longPressAccepted == true) { if (_longPressAccepted == true) {
_checkLongPressEnd(event); if (onLongPressUp != null) {
invokeCallback<void>('onLongPressUp', onLongPressUp);
}
if (onLongPressEnd != null) {
invokeCallback<void>('onLongPressEnd', () {
onLongPressEnd(LongPressEndDetails(
globalPosition: event.position,
));
});
}
_longPressAccepted = false;
} else { } else {
// Pointer is lifted before timeout.
resolve(GestureDisposition.rejected); resolve(GestureDisposition.rejected);
} }
_reset(); } else if (event is PointerDownEvent || event is PointerCancelEvent) {
} else if (event is PointerCancelEvent) {
_reset();
} else if (event is PointerDownEvent) {
// The first touch. // The first touch.
_longPressAccepted = false;
_longPressOrigin = event.position; _longPressOrigin = event.position;
_initialButtons = event.buttons; } else if (event is PointerMoveEvent && _longPressAccepted && onLongPressMoveUpdate != null) {
} else if (event is PointerMoveEvent) { invokeCallback<void>('onLongPressMoveUpdate', () {
if (event.buttons != _initialButtons) { onLongPressMoveUpdate(LongPressMoveUpdateDetails(
resolve(GestureDisposition.rejected); globalPosition: event.position,
stopTrackingPointer(primaryPointer); offsetFromOrigin: event.position - _longPressOrigin,
} else if (_longPressAccepted) { ));
_checkLongPressMoveUpdate(event); });
}
}
}
void _checkLongPressStart() {
assert(_initialButtons == kPrimaryButton);
final LongPressStartDetails details = LongPressStartDetails(
globalPosition: _longPressOrigin,
);
if (onLongPressStart != null)
invokeCallback<void>('onLongPressStart',
() => onLongPressStart(details));
if (onLongPress != null)
invokeCallback<void>('onLongPress', onLongPress);
}
void _checkLongPressMoveUpdate(PointerEvent event) {
assert(_initialButtons == kPrimaryButton);
final LongPressMoveUpdateDetails details = LongPressMoveUpdateDetails(
globalPosition: event.position,
offsetFromOrigin: event.position - _longPressOrigin,
);
if (onLongPressMoveUpdate != null)
invokeCallback<void>('onLongPressMoveUpdate',
() => onLongPressMoveUpdate(details));
}
void _checkLongPressEnd(PointerEvent event) {
assert(_initialButtons == kPrimaryButton);
final LongPressEndDetails details = LongPressEndDetails(
globalPosition: event.position,
);
if (onLongPressEnd != null)
invokeCallback<void>('onLongPressEnd', () => onLongPressEnd(details));
if (onLongPressUp != null)
invokeCallback<void>('onLongPressUp', onLongPressUp);
}
void _reset() {
_longPressAccepted = false;
_longPressOrigin = null;
_initialButtons = null;
}
@override
void resolve(GestureDisposition disposition) {
if (_longPressAccepted && disposition == GestureDisposition.rejected) {
// This can happen if the gesture has been canceled. For example when
// the buttons have changed.
_reset();
} }
super.resolve(disposition);
} }
@override @override
......
...@@ -43,10 +43,6 @@ typedef GestureDragCancelCallback = void Function(); ...@@ -43,10 +43,6 @@ typedef GestureDragCancelCallback = void Function();
/// consider using one of its subclasses to recognize specific types for drag /// consider using one of its subclasses to recognize specific types for drag
/// gestures. /// gestures.
/// ///
/// [DragGestureRecognizer] competes on pointer events of [kPrimaryButton]
/// only when it has at least one non-null callback. If it has no callbacks, it
/// is a no-op.
///
/// See also: /// See also:
/// ///
/// * [HorizontalDragGestureRecognizer], for left and right drags. /// * [HorizontalDragGestureRecognizer], for left and right drags.
...@@ -88,20 +84,13 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer { ...@@ -88,20 +84,13 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer {
/// at (510.0, 500.0). /// at (510.0, 500.0).
DragStartBehavior dragStartBehavior; DragStartBehavior dragStartBehavior;
/// A pointer has contacted the screen with a primary button and might begin /// A pointer has contacted the screen and might begin to move.
/// to move.
/// ///
/// The position of the pointer is provided in the callback's `details` /// The position of the pointer is provided in the callback's `details`
/// argument, which is a [DragDownDetails] object. /// argument, which is a [DragDownDetails] object.
///
/// See also:
///
/// * [kPrimaryButton], the button this callback responds to.
/// * [DragDownDetails], which is passed as an argument to this callback.
GestureDragDownCallback onDown; GestureDragDownCallback onDown;
/// A pointer has contacted the screen with a primary button and has begun to /// A pointer has contacted the screen and has begun to move.
/// move.
/// ///
/// The position of the pointer is provided in the callback's `details` /// The position of the pointer is provided in the callback's `details`
/// argument, which is a [DragStartDetails] object. /// argument, which is a [DragStartDetails] object.
...@@ -110,43 +99,23 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer { ...@@ -110,43 +99,23 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer {
/// called on the initial touch down, if set to [DragStartBehavior.down] or /// called on the initial touch down, if set to [DragStartBehavior.down] or
/// when the drag gesture is first detected, if set to /// when the drag gesture is first detected, if set to
/// [DragStartBehavior.start]. /// [DragStartBehavior.start].
///
/// See also:
///
/// * [kPrimaryButton], the button this callback responds to.
/// * [DragStartDetails], which is passed as an argument to this callback.
GestureDragStartCallback onStart; GestureDragStartCallback onStart;
/// A pointer that is in contact with the screen with a primary button and /// A pointer that is in contact with the screen and moving has moved again.
/// moving has moved again.
/// ///
/// The distance travelled by the pointer since the last update is provided in /// The distance travelled by the pointer since the last update is provided in
/// the callback's `details` argument, which is a [DragUpdateDetails] object. /// the callback's `details` argument, which is a [DragUpdateDetails] object.
///
/// See also:
///
/// * [kPrimaryButton], the button this callback responds to.
/// * [DragUpdateDetails], which is passed as an argument to this callback.
GestureDragUpdateCallback onUpdate; GestureDragUpdateCallback onUpdate;
/// A pointer that was previously in contact with the screen with a primary /// A pointer that was previously in contact with the screen and moving is no
/// button and moving is no longer in contact with the screen and was moving /// longer in contact with the screen and was moving at a specific velocity
/// 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 /// The velocity is provided in the callback's `details` argument, which is a
/// [DragEndDetails] object. /// [DragEndDetails] object.
///
/// See also:
///
/// * [kPrimaryButton], the button this callback responds to.
/// * [DragEndDetails], which is passed as an argument to this callback.
GestureDragEndCallback onEnd; GestureDragEndCallback onEnd;
/// The pointer that previously triggered [onDown] did not complete. /// The pointer that previously triggered [onDown] did not complete.
///
/// See also:
///
/// * [kPrimaryButton], the button this callback responds to.
GestureDragCancelCallback onCancel; GestureDragCancelCallback onCancel;
/// The minimum distance an input pointer drag must have moved to /// The minimum distance an input pointer drag must have moved to
...@@ -172,9 +141,6 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer { ...@@ -172,9 +141,6 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer {
Offset _initialPosition; Offset _initialPosition;
Offset _pendingDragOffset; Offset _pendingDragOffset;
Duration _lastPendingEventTimestamp; Duration _lastPendingEventTimestamp;
// The buttons sent by `PointerDownEvent`. If a `PointerMoveEvent` comes with a
// different set of buttons, the gesture is canceled.
int _initialButtons;
bool _isFlingGesture(VelocityEstimate estimate); bool _isFlingGesture(VelocityEstimate estimate);
Offset _getDeltaForDetails(Offset delta); Offset _getDeltaForDetails(Offset delta);
...@@ -183,30 +149,6 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer { ...@@ -183,30 +149,6 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer {
final Map<int, VelocityTracker> _velocityTrackers = <int, VelocityTracker>{}; final Map<int, VelocityTracker> _velocityTrackers = <int, VelocityTracker>{};
@override
bool isPointerAllowed(PointerEvent event) {
if (_initialButtons == null) {
switch (event.buttons) {
case kPrimaryButton:
if (onDown == null &&
onStart == null &&
onUpdate == null &&
onEnd == null &&
onCancel == null)
return false;
break;
default:
return false;
}
} else {
// There can be multiple drags simultaneously. Their effects are combined.
if (event.buttons != _initialButtons) {
return false;
}
}
return super.isPointerAllowed(event);
}
@override @override
void addAllowedPointer(PointerEvent event) { void addAllowedPointer(PointerEvent event) {
startTrackingPointer(event.pointer); startTrackingPointer(event.pointer);
...@@ -214,10 +156,10 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer { ...@@ -214,10 +156,10 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer {
if (_state == _DragState.ready) { if (_state == _DragState.ready) {
_state = _DragState.possible; _state = _DragState.possible;
_initialPosition = event.position; _initialPosition = event.position;
_initialButtons = event.buttons;
_pendingDragOffset = Offset.zero; _pendingDragOffset = Offset.zero;
_lastPendingEventTimestamp = event.timeStamp; _lastPendingEventTimestamp = event.timeStamp;
_checkDown(); if (onDown != null)
invokeCallback<void>('onDown', () => onDown(DragDownDetails(globalPosition: _initialPosition)));
} else if (_state == _DragState.accepted) { } else if (_state == _DragState.accepted) {
resolve(GestureDisposition.accepted); resolve(GestureDisposition.accepted);
} }
...@@ -234,19 +176,16 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer { ...@@ -234,19 +176,16 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer {
} }
if (event is PointerMoveEvent) { if (event is PointerMoveEvent) {
if (event.buttons != _initialButtons) {
resolve(GestureDisposition.rejected);
stopTrackingPointer(event.pointer);
return;
}
final Offset delta = event.delta; final Offset delta = event.delta;
if (_state == _DragState.accepted) { if (_state == _DragState.accepted) {
_checkUpdate( if (onUpdate != null) {
sourceTimeStamp: event.timeStamp, invokeCallback<void>('onUpdate', () => onUpdate(DragUpdateDetails(
delta: _getDeltaForDetails(delta), sourceTimeStamp: event.timeStamp,
primaryDelta: _getPrimaryValueFromOffset(delta), delta: _getDeltaForDetails(delta),
globalPosition: event.position, primaryDelta: _getPrimaryValueFromOffset(delta),
); globalPosition: event.position,
)));
}
} else { } else {
_pendingDragOffset += delta; _pendingDragOffset += delta;
_lastPendingEventTimestamp = event.timeStamp; _lastPendingEventTimestamp = event.timeStamp;
...@@ -275,14 +214,19 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer { ...@@ -275,14 +214,19 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer {
} }
_pendingDragOffset = Offset.zero; _pendingDragOffset = Offset.zero;
_lastPendingEventTimestamp = null; _lastPendingEventTimestamp = null;
_checkStart(timestamp); if (onStart != null) {
if (updateDelta != Offset.zero) { invokeCallback<void>('onStart', () => onStart(DragStartDetails(
_checkUpdate( sourceTimeStamp: timestamp,
globalPosition: _initialPosition,
)));
}
if (updateDelta != Offset.zero && onUpdate != null) {
invokeCallback<void>('onUpdate', () => onUpdate(DragUpdateDetails(
sourceTimeStamp: timestamp, sourceTimeStamp: timestamp,
delta: updateDelta, delta: updateDelta,
primaryDelta: _getPrimaryValueFromOffset(updateDelta), primaryDelta: _getPrimaryValueFromOffset(updateDelta),
globalPosition: _initialPosition + updateDelta, // Only adds delta for down behaviour globalPosition: _initialPosition + updateDelta, // Only adds delta for down behaviour
); )));
} }
} }
} }
...@@ -294,101 +238,41 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer { ...@@ -294,101 +238,41 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer {
@override @override
void didStopTrackingLastPointer(int pointer) { void didStopTrackingLastPointer(int pointer) {
assert(_state != _DragState.ready); if (_state == _DragState.possible) {
switch(_state) { resolve(GestureDisposition.rejected);
case _DragState.ready: _state = _DragState.ready;
break; if (onCancel != null)
invokeCallback<void>('onCancel', onCancel);
case _DragState.possible: return;
resolve(GestureDisposition.rejected);
_checkCancel();
break;
case _DragState.accepted:
_checkEnd(pointer);
break;
} }
_velocityTrackers.clear(); final bool wasAccepted = _state == _DragState.accepted;
_initialButtons = null;
_state = _DragState.ready; _state = _DragState.ready;
} if (wasAccepted && onEnd != null) {
final VelocityTracker tracker = _velocityTrackers[pointer];
void _checkDown() { assert(tracker != null);
assert(_initialButtons == kPrimaryButton);
final DragDownDetails details = DragDownDetails(
globalPosition: _initialPosition,
);
if (onDown != null)
invokeCallback<void>('onDown', () => onDown(details));
}
void _checkStart(Duration timestamp) {
assert(_initialButtons == kPrimaryButton);
final DragStartDetails details = DragStartDetails(
sourceTimeStamp: timestamp,
globalPosition: _initialPosition,
);
if (onStart != null)
invokeCallback<void>('onStart', () => onStart(details));
}
void _checkUpdate({
Duration sourceTimeStamp,
Offset delta,
double primaryDelta,
Offset globalPosition,
}) {
assert(_initialButtons == kPrimaryButton);
final DragUpdateDetails details = DragUpdateDetails(
sourceTimeStamp: sourceTimeStamp,
delta: delta,
primaryDelta: primaryDelta,
globalPosition: globalPosition,
);
if (onUpdate != null)
invokeCallback<void>('onUpdate', () => onUpdate(details));
}
void _checkEnd(int pointer) {
assert(_initialButtons == kPrimaryButton);
if (onEnd == null)
return;
final VelocityTracker tracker = _velocityTrackers[pointer]; final VelocityEstimate estimate = tracker.getVelocityEstimate();
assert(tracker != null); if (estimate != null && _isFlingGesture(estimate)) {
final Velocity velocity = Velocity(pixelsPerSecond: estimate.pixelsPerSecond)
DragEndDetails details; .clampMagnitude(minFlingVelocity ?? kMinFlingVelocity, maxFlingVelocity ?? kMaxFlingVelocity);
void Function() debugReport; invokeCallback<void>('onEnd', () => onEnd(DragEndDetails(
velocity: velocity,
final VelocityEstimate estimate = tracker.getVelocityEstimate(); primaryVelocity: _getPrimaryValueFromOffset(velocity.pixelsPerSecond),
if (estimate != null && _isFlingGesture(estimate)) { )), debugReport: () {
final Velocity velocity = Velocity(pixelsPerSecond: estimate.pixelsPerSecond) return '$estimate; fling at $velocity.';
.clampMagnitude(minFlingVelocity ?? kMinFlingVelocity, maxFlingVelocity ?? kMaxFlingVelocity); });
details = DragEndDetails( } else {
velocity: velocity, invokeCallback<void>('onEnd', () => onEnd(DragEndDetails(
primaryVelocity: _getPrimaryValueFromOffset(velocity.pixelsPerSecond), velocity: Velocity.zero,
); primaryVelocity: 0.0,
debugReport = () { )), debugReport: () {
return '$estimate; fling at $velocity.'; if (estimate == null)
}; return 'Could not estimate velocity.';
} else { return '$estimate; judged to not be a fling.';
details = DragEndDetails( });
velocity: Velocity.zero, }
primaryVelocity: 0.0,
);
debugReport = () {
if (estimate == null)
return 'Could not estimate velocity.';
return '$estimate; judged to not be a fling.';
};
} }
invokeCallback<void>('onEnd', () => onEnd(details), debugReport: debugReport); _velocityTrackers.clear();
}
void _checkCancel() {
assert(_initialButtons == kPrimaryButton);
if (onCancel != null)
invokeCallback<void>('onCancel', onCancel);
} }
@override @override
......
...@@ -16,10 +16,6 @@ import 'tap.dart'; ...@@ -16,10 +16,6 @@ import 'tap.dart';
/// Signature for callback when the user has tapped the screen at the same /// Signature for callback when the user has tapped the screen at the same
/// location twice in quick succession. /// location twice in quick succession.
///
/// See also:
///
/// * [GestureDetector.onDoubleTap], which matches this signature.
typedef GestureDoubleTapCallback = void Function(); typedef GestureDoubleTapCallback = void Function();
/// Signature used by [MultiTapGestureRecognizer] for when a pointer that might /// Signature used by [MultiTapGestureRecognizer] for when a pointer that might
...@@ -64,16 +60,13 @@ class _TapTracker { ...@@ -64,16 +60,13 @@ class _TapTracker {
@required Duration doubleTapMinTime, @required Duration doubleTapMinTime,
}) : assert(doubleTapMinTime != null), }) : assert(doubleTapMinTime != null),
assert(event != null), assert(event != null),
assert(event.buttons != null),
pointer = event.pointer, pointer = event.pointer,
_initialPosition = event.position, _initialPosition = event.position,
initialButtons = event.buttons,
_doubleTapMinTimeCountdown = _CountdownZoned(duration: doubleTapMinTime); _doubleTapMinTimeCountdown = _CountdownZoned(duration: doubleTapMinTime);
final int pointer; final int pointer;
final GestureArenaEntry entry; final GestureArenaEntry entry;
final Offset _initialPosition; final Offset _initialPosition;
final int initialButtons;
final _CountdownZoned _doubleTapMinTimeCountdown; final _CountdownZoned _doubleTapMinTimeCountdown;
bool _isTrackingPointer = false; bool _isTrackingPointer = false;
...@@ -100,18 +93,10 @@ class _TapTracker { ...@@ -100,18 +93,10 @@ class _TapTracker {
bool hasElapsedMinTime() { bool hasElapsedMinTime() {
return _doubleTapMinTimeCountdown.timeout; return _doubleTapMinTimeCountdown.timeout;
} }
bool hasSameButton(PointerDownEvent event) {
return event.buttons == initialButtons;
}
} }
/// Recognizes when the user has tapped the screen at the same location twice in /// Recognizes when the user has tapped the screen at the same location twice in
/// quick succession. /// quick succession.
///
/// [DoubleTapGestureRecognizer] competes on pointer events of [kPrimaryButton]
/// only when it has a non-null callback. If it has no callbacks, it is a no-op.
///
class DoubleTapGestureRecognizer extends GestureRecognizer { class DoubleTapGestureRecognizer extends GestureRecognizer {
/// Create a gesture recognizer for double taps. /// Create a gesture recognizer for double taps.
/// ///
...@@ -141,53 +126,26 @@ class DoubleTapGestureRecognizer extends GestureRecognizer { ...@@ -141,53 +126,26 @@ class DoubleTapGestureRecognizer extends GestureRecognizer {
// - The long timer between taps expires // - The long timer between taps expires
// - The gesture arena decides we have been rejected wholesale // - The gesture arena decides we have been rejected wholesale
/// Called when the user has tapped the screen with a primary button at the /// Called when the user has tapped the screen at the same location twice in
/// same location twice in quick succession. /// quick succession.
///
/// This triggers when the pointer stops contacting the device after the 2nd tap,
/// immediately after [onDoubleTapUp].
///
/// See also:
///
/// * [kPrimaryButton], the button this callback responds to.
GestureDoubleTapCallback onDoubleTap; GestureDoubleTapCallback onDoubleTap;
Timer _doubleTapTimer; Timer _doubleTapTimer;
_TapTracker _firstTap; _TapTracker _firstTap;
final Map<int, _TapTracker> _trackers = <int, _TapTracker>{}; final Map<int, _TapTracker> _trackers = <int, _TapTracker>{};
@override
bool isPointerAllowed(PointerEvent event) {
if (_firstTap == null) {
switch (event.buttons) {
case kPrimaryButton:
if (onDoubleTap == null)
return false;
break;
default:
return false;
}
}
return super.isPointerAllowed(event);
}
@override @override
void addAllowedPointer(PointerEvent event) { void addAllowedPointer(PointerEvent event) {
if (_firstTap != null) { if (_firstTap != null) {
if (!_firstTap.isWithinTolerance(event, kDoubleTapSlop)) { if (!_firstTap.isWithinTolerance(event, kDoubleTapSlop)) {
// Ignore out-of-bounds second taps. // Ignore out-of-bounds second taps.
return; return;
} else if (!_firstTap.hasElapsedMinTime() || !_firstTap.hasSameButton(event)) { } else if (!_firstTap.hasElapsedMinTime()) {
// Restart when the second tap is too close to the first, or when buttons // Restart when the second tap is too close to the first.
// mismatch.
_reset(); _reset();
return _trackFirstTap(event); return addAllowedPointer(event);
} }
} }
_trackFirstTap(event);
}
void _trackFirstTap(PointerEvent event) {
_stopDoubleTapTimer(); _stopDoubleTapTimer();
final _TapTracker tracker = _TapTracker( final _TapTracker tracker = _TapTracker(
event: event, event: event,
...@@ -277,7 +235,8 @@ class DoubleTapGestureRecognizer extends GestureRecognizer { ...@@ -277,7 +235,8 @@ class DoubleTapGestureRecognizer extends GestureRecognizer {
tracker.entry.resolve(GestureDisposition.accepted); tracker.entry.resolve(GestureDisposition.accepted);
_freezeTracker(tracker); _freezeTracker(tracker);
_trackers.remove(tracker.pointer); _trackers.remove(tracker.pointer);
_checkUp(tracker.initialButtons); if (onDoubleTap != null)
invokeCallback<void>('onDoubleTap', onDoubleTap);
_reset(); _reset();
} }
...@@ -301,12 +260,6 @@ class DoubleTapGestureRecognizer extends GestureRecognizer { ...@@ -301,12 +260,6 @@ class DoubleTapGestureRecognizer extends GestureRecognizer {
} }
} }
void _checkUp(int buttons) {
assert(buttons == kPrimaryButton);
if (onDoubleTap != null)
invokeCallback<void>('onDoubleTap', onDoubleTap);
}
@override @override
String get debugDescription => 'double tap'; String get debugDescription => 'double tap';
} }
......
...@@ -9,7 +9,7 @@ import 'constants.dart'; ...@@ -9,7 +9,7 @@ import 'constants.dart';
import 'events.dart'; import 'events.dart';
import 'recognizer.dart'; import 'recognizer.dart';
/// Details for [GestureTapDownCallback], such as position /// Details for [GestureTapDownCallback], such as position.
/// ///
/// See also: /// See also:
/// ///
...@@ -45,6 +45,8 @@ typedef GestureTapDownCallback = void Function(TapDownDetails details); ...@@ -45,6 +45,8 @@ typedef GestureTapDownCallback = void Function(TapDownDetails details);
/// * [GestureDetector.onTapUp], which receives this information. /// * [GestureDetector.onTapUp], which receives this information.
/// * [TapGestureRecognizer], which passes this information to one of its callbacks. /// * [TapGestureRecognizer], which passes this information to one of its callbacks.
class TapUpDetails { class TapUpDetails {
/// Creates details for a [GestureTapUpCallback].
///
/// The [globalPosition] argument must not be null. /// The [globalPosition] argument must not be null.
TapUpDetails({ this.globalPosition = Offset.zero }) TapUpDetails({ this.globalPosition = Offset.zero })
: assert(globalPosition != null); : assert(globalPosition != null);
...@@ -93,10 +95,14 @@ typedef GestureTapCancelCallback = void Function(); ...@@ -93,10 +95,14 @@ typedef GestureTapCancelCallback = void Function();
/// pointer interactions during a tap sequence are not recognized as additional /// pointer interactions during a tap sequence are not recognized as additional
/// taps. For example, down-1, down-2, up-1, up-2 produces only one tap on up-1. /// taps. For example, down-1, down-2, up-1, up-2 produces only one tap on up-1.
/// ///
/// [TapGestureRecognizer] competes on pointer events of [kPrimaryButton] only /// The lifecycle of events for a tap gesture is as follows:
/// when it has at least one non-null `onTap*` callback, and events of ///
/// [kSecondaryButton] only when it has at least one non-null `onSecondaryTap*` /// * [onTapDown], which triggers after a short timeout ([deadline]) even if the
/// callback. If it has no callbacks, it is a no-op. /// gesture has not won its arena yet.
/// * [onTapUp] and [onTap], which trigger when the pointer is released if the
/// gesture wins the arena.
/// * [onTapCancel], which triggers instead of [onTapUp] and [onTap] in the case
/// of the gesture not winning the arena.
/// ///
/// See also: /// See also:
/// ///
...@@ -106,25 +112,22 @@ class TapGestureRecognizer extends PrimaryPointerGestureRecognizer { ...@@ -106,25 +112,22 @@ class TapGestureRecognizer extends PrimaryPointerGestureRecognizer {
/// Creates a tap gesture recognizer. /// Creates a tap gesture recognizer.
TapGestureRecognizer({ Object debugOwner }) : super(deadline: kPressTimeout, debugOwner: debugOwner); TapGestureRecognizer({ Object debugOwner }) : super(deadline: kPressTimeout, debugOwner: debugOwner);
/// A pointer that might cause a tap of a primary button has contacted the /// A pointer that might cause a tap has contacted the screen at a particular
/// screen at a particular location. /// location.
/// ///
/// This triggers once a short timeout ([deadline]) has elapsed, or once /// This triggers before the gesture has won the arena, after a short timeout
/// the gestures has won the arena, whichever comes first. /// ([deadline]).
/// ///
/// If the gesture doesn't win the arena, [onTapCancel] is called next. /// If the gesture doesn't win the arena, [onTapCancel] is called next.
/// Otherwise, [onTapUp] is called next. /// Otherwise, [onTapUp] is called next.
/// ///
/// See also: /// See also:
/// ///
/// * [kPrimaryButton], the button this callback responds to.
/// * [onSecondaryTapDown], a similar callback but for a secondary button.
/// * [TapDownDetails], which is passed as an argument to this callback.
/// * [GestureDetector.onTapDown], which exposes this callback. /// * [GestureDetector.onTapDown], which exposes this callback.
GestureTapDownCallback onTapDown; GestureTapDownCallback onTapDown;
/// A pointer that will trigger a tap of a primary button has stopped /// A pointer that will trigger a tap has stopped contacting the screen at a
/// contacting the screen at a particular location. /// particular location.
/// ///
/// This triggers once the gesture has won the arena, immediately before /// This triggers once the gesture has won the arena, immediately before
/// [onTap]. /// [onTap].
...@@ -133,13 +136,11 @@ class TapGestureRecognizer extends PrimaryPointerGestureRecognizer { ...@@ -133,13 +136,11 @@ class TapGestureRecognizer extends PrimaryPointerGestureRecognizer {
/// ///
/// See also: /// See also:
/// ///
/// * [kPrimaryButton], the button this callback responds to.
/// * [onSecondaryTapUp], a similar callback but for a secondary button.
/// * [TapUpDetails], which is passed as an argument to this callback.
/// * [GestureDetector.onTapUp], which exposes this callback. /// * [GestureDetector.onTapUp], which exposes this callback.
/// * [TapUpDetails], which is passed as an argument to this callback.
GestureTapUpCallback onTapUp; GestureTapUpCallback onTapUp;
/// A tap of a primary button has occurred. /// A tap has occurred.
/// ///
/// This triggers once the gesture has won the arena, immediately after /// This triggers once the gesture has won the arena, immediately after
/// [onTapUp]. /// [onTapUp].
...@@ -148,8 +149,6 @@ class TapGestureRecognizer extends PrimaryPointerGestureRecognizer { ...@@ -148,8 +149,6 @@ class TapGestureRecognizer extends PrimaryPointerGestureRecognizer {
/// ///
/// See also: /// See also:
/// ///
/// * [kPrimaryButton], the button this callback responds to.
/// * [onTapUp], which has the same timing but with details.
/// * [GestureDetector.onTap], which exposes this callback. /// * [GestureDetector.onTap], which exposes this callback.
GestureTapCallback onTap; GestureTapCallback onTap;
...@@ -162,121 +161,37 @@ class TapGestureRecognizer extends PrimaryPointerGestureRecognizer { ...@@ -162,121 +161,37 @@ class TapGestureRecognizer extends PrimaryPointerGestureRecognizer {
/// ///
/// See also: /// See also:
/// ///
/// * [kPrimaryButton], the button this callback responds to.
/// * [onSecondaryTapCancel], a similar callback but for a secondary button.
/// * [GestureDetector.onTapCancel], which exposes this callback. /// * [GestureDetector.onTapCancel], which exposes this callback.
GestureTapCancelCallback onTapCancel; GestureTapCancelCallback onTapCancel;
/// A pointer that might cause a tap of a secondary button has contacted the
/// screen at a particular location.
///
/// This triggers once a short timeout ([deadline]) has elapsed, or once
/// the gestures has won the arena, whichever comes first.
///
/// If the gesture doesn't win the arena, [onSecondaryTapCancel] is called next.
/// Otherwise, [onSecondaryTapUp] is called next.
///
/// See also:
///
/// * [kSecondaryButton], the button this callback responds to.
/// * [onPrimaryTapDown], a similar callback but for a primary button.
/// * [TapDownDetails], which is passed as an argument to this callback.
/// * [GestureDetector.onSecondaryTapDown], which exposes this callback.
GestureTapDownCallback onSecondaryTapDown;
/// A pointer that will trigger a tap of a secondary button has stopped
/// contacting the screen at a particular location.
///
/// This triggers once the gesture has won the arena.
///
/// If the gesture doesn't win the arena, [onSecondaryTapCancel] is called
/// instead.
///
/// See also:
///
/// * [kSecondaryButton], the button this callback responds to.
/// * [onPrimaryTapUp], a similar callback but for a primary button.
/// * [TapUpDetails], which is passed as an argument to this callback.
/// * [GestureDetector.onSecondaryTapUp], which exposes this callback.
GestureTapUpCallback onSecondaryTapUp;
/// The pointer that previously triggered [onSecondaryTapDown] will not end up
/// causing a tap.
///
/// This triggers if the gesture loses the arena.
///
/// If the gesture wins the arena, [onSecondaryTapUp] is called instead.
///
/// See also:
///
/// * [kSecondaryButton], the button this callback responds to.
/// * [onPrimaryTapCancel], a similar callback but for a primary button.
/// * [GestureDetector.onTapCancel], which exposes this callback.
GestureTapCancelCallback onSecondaryTapCancel;
bool _sentTapDown = false; bool _sentTapDown = false;
bool _wonArenaForPrimaryPointer = false; bool _wonArenaForPrimaryPointer = false;
Offset _finalPosition; Offset _finalPosition;
// The buttons sent by `PointerDownEvent`. If a `PointerMoveEvent` comes with a
// different set of buttons, the gesture is canceled.
int _initialButtons;
@override
bool isPointerAllowed(PointerDownEvent event) {
switch (event.buttons) {
case kPrimaryButton:
if (onTapDown == null &&
onTap == null &&
onTapUp == null &&
onTapCancel == null)
return false;
break;
case kSecondaryButton:
if (onSecondaryTapDown == null &&
onSecondaryTapUp == null &&
onSecondaryTapCancel == null)
return false;
break;
default:
return false;
}
return super.isPointerAllowed(event);
}
@override
void addAllowedPointer(PointerDownEvent event) {
super.addAllowedPointer(event);
// `_initialButtons` must be assigned here instead of `handlePrimaryPointer`,
// because `acceptGesture` might be called before `handlePrimaryPointer`,
// which relies on `_initialButtons` to create `TapDownDetails`.
_initialButtons = event.buttons;
}
@override @override
void handlePrimaryPointer(PointerEvent event) { void handlePrimaryPointer(PointerEvent event) {
if (event is PointerUpEvent) { if (event is PointerUpEvent) {
_finalPosition = event.position; _finalPosition = event.position;
_checkUp(); if (_wonArenaForPrimaryPointer) {
resolve(GestureDisposition.accepted);
_checkUp();
}
} else if (event is PointerCancelEvent) { } else if (event is PointerCancelEvent) {
resolve(GestureDisposition.rejected); if (_sentTapDown && onTapCancel != null) {
if (_sentTapDown) { invokeCallback<void>('onTapCancel', onTapCancel);
_checkCancel('');
} }
_reset(); _reset();
} else if (event.buttons != _initialButtons) {
resolve(GestureDisposition.rejected);
stopTrackingPointer(primaryPointer);
} }
} }
@override @override
void resolve(GestureDisposition disposition) { void resolve(GestureDisposition disposition) {
if (_wonArenaForPrimaryPointer && disposition == GestureDisposition.rejected) { if (_wonArenaForPrimaryPointer && disposition == GestureDisposition.rejected) {
// This can happen if the gesture has been canceled. For example, when // This can happen if the superclass decides the primary pointer
// the pointer has exceeded the touch slop, the buttons have been changed, // exceeded the touch slop, or if the recognizer is disposed.
// or if the recognizer is disposed.
assert(_sentTapDown); assert(_sentTapDown);
_checkCancel('spontaneous '); if (onTapCancel != null)
invokeCallback<void>('spontaneous onTapCancel', onTapCancel);
_reset(); _reset();
} }
super.resolve(disposition); super.resolve(disposition);
...@@ -303,70 +218,27 @@ class TapGestureRecognizer extends PrimaryPointerGestureRecognizer { ...@@ -303,70 +218,27 @@ class TapGestureRecognizer extends PrimaryPointerGestureRecognizer {
if (pointer == primaryPointer) { if (pointer == primaryPointer) {
// Another gesture won the arena. // Another gesture won the arena.
assert(state != GestureRecognizerState.possible); assert(state != GestureRecognizerState.possible);
if (_sentTapDown) if (_sentTapDown && onTapCancel != null)
_checkCancel('forced '); invokeCallback<void>('forced onTapCancel', onTapCancel);
_reset(); _reset();
} }
} }
void _checkDown() { void _checkDown() {
if (_sentTapDown) { if (!_sentTapDown) {
return; if (onTapDown != null)
} invokeCallback<void>('onTapDown', () { onTapDown(TapDownDetails(globalPosition: initialPosition)); });
final TapDownDetails details = TapDownDetails( _sentTapDown = true;
globalPosition: initialPosition,
);
switch (_initialButtons) {
case kPrimaryButton:
if (onTapDown != null)
invokeCallback<void>('onTapDown', () => onTapDown(details));
break;
case kSecondaryButton:
if (onSecondaryTapDown != null)
invokeCallback<void>('onSecondaryTapDown',
() => onSecondaryTapDown(details));
break;
default:
} }
_sentTapDown = true;
} }
void _checkUp() { void _checkUp() {
if (!_wonArenaForPrimaryPointer || _finalPosition == null) { if (_finalPosition != null) {
return; if (onTapUp != null)
} invokeCallback<void>('onTapUp', () { onTapUp(TapUpDetails(globalPosition: _finalPosition)); });
final TapUpDetails details = TapUpDetails( if (onTap != null)
globalPosition: _finalPosition, invokeCallback<void>('onTap', onTap);
); _reset();
switch (_initialButtons) {
case kPrimaryButton:
if (onTapUp != null)
invokeCallback<void>('onTapUp', () => onTapUp(details));
if (onTap != null)
invokeCallback<void>('onTap', onTap);
break;
case kSecondaryButton:
if (onSecondaryTapUp != null)
invokeCallback<void>('onSecondaryTapUp',
() => onSecondaryTapUp(details));
break;
default:
}
_reset();
}
void _checkCancel(String note) {
switch (_initialButtons) {
case kPrimaryButton:
if (onTapCancel != null)
invokeCallback<void>('${note}onTapCancel', onTapCancel);
break;
case kSecondaryButton:
if (onSecondaryTapCancel != null)
invokeCallback<void>('${note}onSecondaryTapCancel',
onSecondaryTapCancel);
break;
default:
} }
} }
...@@ -374,7 +246,6 @@ class TapGestureRecognizer extends PrimaryPointerGestureRecognizer { ...@@ -374,7 +246,6 @@ class TapGestureRecognizer extends PrimaryPointerGestureRecognizer {
_sentTapDown = false; _sentTapDown = false;
_wonArenaForPrimaryPointer = false; _wonArenaForPrimaryPointer = false;
_finalPosition = null; _finalPosition = null;
_initialButtons = null;
} }
@override @override
...@@ -386,6 +257,5 @@ class TapGestureRecognizer extends PrimaryPointerGestureRecognizer { ...@@ -386,6 +257,5 @@ class TapGestureRecognizer extends PrimaryPointerGestureRecognizer {
properties.add(FlagProperty('wonArenaForPrimaryPointer', value: _wonArenaForPrimaryPointer, ifTrue: 'won arena')); properties.add(FlagProperty('wonArenaForPrimaryPointer', value: _wonArenaForPrimaryPointer, ifTrue: 'won arena'));
properties.add(DiagnosticsProperty<Offset>('finalPosition', _finalPosition, defaultValue: null)); properties.add(DiagnosticsProperty<Offset>('finalPosition', _finalPosition, defaultValue: null));
properties.add(FlagProperty('sentTapDown', value: _sentTapDown, ifTrue: 'sent tap down')); properties.add(FlagProperty('sentTapDown', value: _sentTapDown, ifTrue: 'sent tap down'));
// TODO(tongmu): Add property _initialButtons and update related tests
} }
} }
...@@ -166,9 +166,6 @@ class GestureDetector extends StatelessWidget { ...@@ -166,9 +166,6 @@ class GestureDetector extends StatelessWidget {
this.onTapUp, this.onTapUp,
this.onTap, this.onTap,
this.onTapCancel, this.onTapCancel,
this.onSecondaryTapDown,
this.onSecondaryTapUp,
this.onSecondaryTapCancel,
this.onDoubleTap, this.onDoubleTap,
this.onLongPress, this.onLongPress,
this.onLongPressStart, this.onLongPressStart,
...@@ -232,37 +229,28 @@ class GestureDetector extends StatelessWidget { ...@@ -232,37 +229,28 @@ class GestureDetector extends StatelessWidget {
/// {@macro flutter.widgets.child} /// {@macro flutter.widgets.child}
final Widget child; final Widget child;
/// A pointer that might cause a tap with a primary button has contacted the /// A pointer that might cause a tap has contacted the screen at a particular
/// screen at a particular location. /// location.
/// ///
/// This is called after a short timeout, even if the winning gesture has not /// This is called after a short timeout, even if the winning gesture has not
/// yet been selected. If the tap gesture wins, [onTapUp] will be called, /// yet been selected. If the tap gesture wins, [onTapUp] will be called,
/// otherwise [onTapCancel] will be called. /// otherwise [onTapCancel] will be called.
///
/// See also:
///
/// * [kPrimaryButton], the button this callback responds to.
final GestureTapDownCallback onTapDown; final GestureTapDownCallback onTapDown;
/// A pointer that will trigger a tap with a primary button has stopped /// A pointer that will trigger a tap has stopped contacting the screen at a
/// contacting the screen at a particular location. /// particular location.
/// ///
/// This triggers immediately before [onTap] in the case of the tap gesture /// This triggers immediately before [onTap] in the case of the tap gesture
/// winning. If the tap gesture did not win, [onTapCancel] is called instead. /// winning. If the tap gesture did not win, [onTapCancel] is called instead.
///
/// See also:
///
/// * [kPrimaryButton], the button this callback responds to.
final GestureTapUpCallback onTapUp; final GestureTapUpCallback onTapUp;
/// A tap with a primary button has occurred. /// A tap has occurred.
/// ///
/// This triggers when the tap gesture wins. If the tap gesture did not win, /// This triggers when the tap gesture wins. If the tap gesture did not win,
/// [onTapCancel] is called instead. /// [onTapCancel] is called instead.
/// ///
/// See also: /// See also:
/// ///
/// * [kPrimaryButton], the button this callback responds to.
/// * [onTapUp], which is called at the same time but includes details /// * [onTapUp], which is called at the same time but includes details
/// regarding the pointer position. /// regarding the pointer position.
final GestureTapCallback onTap; final GestureTapCallback onTap;
...@@ -272,222 +260,104 @@ class GestureDetector extends StatelessWidget { ...@@ -272,222 +260,104 @@ class GestureDetector extends StatelessWidget {
/// ///
/// This is called after [onTapDown], and instead of [onTapUp] and [onTap], if /// This is called after [onTapDown], and instead of [onTapUp] and [onTap], if
/// the tap gesture did not win. /// the tap gesture did not win.
///
/// See also:
///
/// * [kPrimaryButton], the button this callback responds to.
final GestureTapCancelCallback onTapCancel; final GestureTapCancelCallback onTapCancel;
/// A pointer that might cause a tap with a secondary button has contacted the /// The user has tapped the screen at the same location twice in quick
/// screen at a particular location. /// succession.
///
/// This is called after a short timeout, even if the winning gesture has not
/// yet been selected. If the tap gesture wins, [onSecondaryTapUp] will be
/// called, otherwise [onSecondaryTapCancel] will be called.
///
/// See also:
///
/// * [kSecondaryButton], the button this callback responds to.
final GestureTapDownCallback onSecondaryTapDown;
/// A pointer that will trigger a tap with a secondary button has stopped
/// contacting the screen at a particular location.
///
/// This triggers in the case of the tap gesture winning. If the tap gesture
/// did not win, [onSecondaryTapCancel] is called instead.
///
/// See also:
///
/// * [kSecondaryButton], the button this callback responds to.
final GestureTapUpCallback onSecondaryTapUp;
/// The pointer that previously triggered [onSecondaryTapDown] will not end up
/// causing a tap.
///
/// This is called after [onSecondaryTapDown], and instead of
/// [onSecondaryTapUp], if the tap gesture did not win.
///
/// See also:
///
/// * [kSecondaryButton], the button this callback responds to.
final GestureTapCancelCallback onSecondaryTapCancel;
/// The user has tapped the screen with a primary button at the same location
/// twice in quick succession.
///
/// See also:
///
/// * [kPrimaryButton], the button this callback responds to.
final GestureTapCallback onDoubleTap; final GestureTapCallback onDoubleTap;
/// Called when a long press gesture with a primary button has been recognized. /// Called when a long press gesture has been recognized.
/// ///
/// Triggered when a pointer has remained in contact with the screen at the /// Triggered when a pointer has remained in contact with the screen at the
/// same location for a long period of time. /// same location for a long period of time.
/// ///
/// See also: /// See also:
/// ///
/// * [kPrimaryButton], the button this callback responds to. /// * [onLongPressStart], which has the same timing but has data for the
/// * [onLongPressStart], which has the same timing but has gesture details. /// press location.
final GestureLongPressCallback onLongPress; final GestureLongPressCallback onLongPress;
/// Called when a long press gesture with a primary button has been recognized. /// Callback for long press start with gesture location.
/// ///
/// Triggered when a pointer has remained in contact with the screen at the /// Triggered when a pointer has remained in contact with the screen at the
/// same location for a long period of time. /// same location for a long period of time.
/// ///
/// See also: /// See also:
/// ///
/// * [kPrimaryButton], the button this callback responds to. /// * [onLongPress], which has the same timing but without the location data.
/// * [onLongPress], which has the same timing but without the gesture details.
final GestureLongPressStartCallback onLongPressStart; final GestureLongPressStartCallback onLongPressStart;
/// A pointer has been drag-moved after a long press with a primary button. /// A pointer has been drag-moved after a long press.
///
/// See also:
///
/// * [kPrimaryButton], the button this callback responds to.
final GestureLongPressMoveUpdateCallback onLongPressMoveUpdate; final GestureLongPressMoveUpdateCallback onLongPressMoveUpdate;
/// A pointer that has triggered a long-press with a primary button has /// A pointer that has triggered a long-press has stopped contacting the screen.
/// stopped contacting the screen.
/// ///
/// See also: /// See also:
/// ///
/// * [kPrimaryButton], the button this callback responds to. /// * [onLongPressEnd], which has the same timing but has data for the up
/// * [onLongPressEnd], which has the same timing but has gesture details. /// gesture location.
final GestureLongPressUpCallback onLongPressUp; final GestureLongPressUpCallback onLongPressUp;
/// A pointer that has triggered a long-press with a primary button has /// A pointer that has triggered a long-press has stopped contacting the screen.
/// stopped contacting the screen.
/// ///
/// See also: /// See also:
/// ///
/// * [kPrimaryButton], the button this callback responds to. /// * [onLongPressUp], which has the same timing but without the location data.
/// * [onLongPressUp], which has the same timing but without the gesture
/// details.
final GestureLongPressEndCallback onLongPressEnd; final GestureLongPressEndCallback onLongPressEnd;
/// A pointer has contacted the screen with a primary button and might begin /// A pointer has contacted the screen and might begin to move vertically.
/// to move vertically.
///
/// See also:
///
/// * [kPrimaryButton], the button this callback responds to.
final GestureDragDownCallback onVerticalDragDown; final GestureDragDownCallback onVerticalDragDown;
/// A pointer has contacted the screen with a primary button and has begun to /// A pointer has contacted the screen and has begun to move vertically.
/// move vertically.
///
/// See also:
///
/// * [kPrimaryButton], the button this callback responds to.
final GestureDragStartCallback onVerticalDragStart; final GestureDragStartCallback onVerticalDragStart;
/// A pointer that is in contact with the screen with a primary button and /// A pointer that is in contact with the screen and moving vertically has
/// moving vertically has moved in the vertical direction. /// moved in the vertical direction.
///
/// See also:
///
/// * [kPrimaryButton], the button this callback responds to.
final GestureDragUpdateCallback onVerticalDragUpdate; final GestureDragUpdateCallback onVerticalDragUpdate;
/// A pointer that was previously in contact with the screen with a primary /// A pointer that was previously in contact with the screen and moving
/// button and moving vertically is no longer in contact with the screen and /// vertically is no longer in contact with the screen and was moving at a
/// was moving at a specific velocity when it stopped contacting the screen. /// specific velocity when it stopped contacting the screen.
///
/// See also:
///
/// * [kPrimaryButton], the button this callback responds to.
final GestureDragEndCallback onVerticalDragEnd; final GestureDragEndCallback onVerticalDragEnd;
/// The pointer that previously triggered [onVerticalDragDown] did not /// The pointer that previously triggered [onVerticalDragDown] did not
/// complete. /// complete.
///
/// See also:
///
/// * [kPrimaryButton], the button this callback responds to.
final GestureDragCancelCallback onVerticalDragCancel; final GestureDragCancelCallback onVerticalDragCancel;
/// A pointer has contacted the screen with a primary button and might begin /// A pointer has contacted the screen and might begin to move horizontally.
/// to move horizontally.
///
/// See also:
///
/// * [kPrimaryButton], the button this callback responds to.
final GestureDragDownCallback onHorizontalDragDown; final GestureDragDownCallback onHorizontalDragDown;
/// A pointer has contacted the screen with a primary button and has begun to /// A pointer has contacted the screen and has begun to move horizontally.
/// move horizontally.
///
/// See also:
///
/// * [kPrimaryButton], the button this callback responds to.
final GestureDragStartCallback onHorizontalDragStart; final GestureDragStartCallback onHorizontalDragStart;
/// A pointer that is in contact with the screen with a primary button and /// A pointer that is in contact with the screen and moving horizontally has
/// moving horizontally has moved in the horizontal direction. /// moved in the horizontal direction.
///
/// See also:
///
/// * [kPrimaryButton], the button this callback responds to.
final GestureDragUpdateCallback onHorizontalDragUpdate; final GestureDragUpdateCallback onHorizontalDragUpdate;
/// A pointer that was previously in contact with the screen with a primary /// A pointer that was previously in contact with the screen and moving
/// button and moving horizontally is no longer in contact with the screen and /// horizontally is no longer in contact with the screen and was moving at a
/// was moving at a specific velocity when it stopped contacting the screen. /// specific velocity when it stopped contacting the screen.
///
/// See also:
///
/// * [kPrimaryButton], the button this callback responds to.
final GestureDragEndCallback onHorizontalDragEnd; final GestureDragEndCallback onHorizontalDragEnd;
/// The pointer that previously triggered [onHorizontalDragDown] did not /// The pointer that previously triggered [onHorizontalDragDown] did not
/// complete. /// complete.
///
/// See also:
///
/// * [kPrimaryButton], the button this callback responds to.
final GestureDragCancelCallback onHorizontalDragCancel; final GestureDragCancelCallback onHorizontalDragCancel;
/// A pointer has contacted the screen with a primary button and might begin /// A pointer has contacted the screen and might begin to move.
/// to move.
///
/// See also:
///
/// * [kPrimaryButton], the button this callback responds to.
final GestureDragDownCallback onPanDown; final GestureDragDownCallback onPanDown;
/// A pointer has contacted the screen with a primary button and has begun to /// A pointer has contacted the screen and has begun to move.
/// move.
///
/// See also:
///
/// * [kPrimaryButton], the button this callback responds to.
final GestureDragStartCallback onPanStart; final GestureDragStartCallback onPanStart;
/// A pointer that is in contact with the screen with a primary button and /// A pointer that is in contact with the screen and moving has moved again.
/// moving has moved again.
///
/// See also:
///
/// * [kPrimaryButton], the button this callback responds to.
final GestureDragUpdateCallback onPanUpdate; final GestureDragUpdateCallback onPanUpdate;
/// A pointer that was previously in contact with the screen with a primary /// A pointer that was previously in contact with the screen and moving
/// button and moving is no longer in contact with the screen and was moving /// is no longer in contact with the screen and was moving at a specific
/// at a specific velocity when it stopped contacting the screen. /// velocity when it stopped contacting the screen.
///
/// See also:
///
/// * [kPrimaryButton], the button this callback responds to.
final GestureDragEndCallback onPanEnd; final GestureDragEndCallback onPanEnd;
/// The pointer that previously triggered [onPanDown] did not complete. /// The pointer that previously triggered [onPanDown] did not complete.
///
/// See also:
///
/// * [kPrimaryButton], the button this callback responds to.
final GestureDragCancelCallback onPanCancel; final GestureDragCancelCallback onPanCancel;
/// The pointers in contact with the screen have established a focal point and /// The pointers in contact with the screen have established a focal point and
...@@ -570,15 +440,7 @@ class GestureDetector extends StatelessWidget { ...@@ -570,15 +440,7 @@ class GestureDetector extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final Map<Type, GestureRecognizerFactory> gestures = <Type, GestureRecognizerFactory>{}; final Map<Type, GestureRecognizerFactory> gestures = <Type, GestureRecognizerFactory>{};
if ( if (onTapDown != null || onTapUp != null || onTap != null || onTapCancel != null) {
onTapDown != null ||
onTapUp != null ||
onTap != null ||
onTapCancel != null ||
onSecondaryTapDown != null ||
onSecondaryTapUp != null ||
onSecondaryTapCancel != null
) {
gestures[TapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>( gestures[TapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
() => TapGestureRecognizer(debugOwner: this), () => TapGestureRecognizer(debugOwner: this),
(TapGestureRecognizer instance) { (TapGestureRecognizer instance) {
...@@ -586,10 +448,7 @@ class GestureDetector extends StatelessWidget { ...@@ -586,10 +448,7 @@ class GestureDetector extends StatelessWidget {
..onTapDown = onTapDown ..onTapDown = onTapDown
..onTapUp = onTapUp ..onTapUp = onTapUp
..onTap = onTap ..onTap = onTap
..onTapCancel = onTapCancel ..onTapCancel = onTapCancel;
..onSecondaryTapDown = onSecondaryTapDown
..onSecondaryTapUp = onSecondaryTapUp
..onSecondaryTapCancel = onSecondaryTapCancel;
}, },
); );
} }
...@@ -938,14 +797,8 @@ class RawGestureDetectorState extends State<RawGestureDetector> { ...@@ -938,14 +797,8 @@ class RawGestureDetectorState extends State<RawGestureDetector> {
void _handleSemanticsLongPress() { void _handleSemanticsLongPress() {
final LongPressGestureRecognizer recognizer = _recognizers[LongPressGestureRecognizer]; final LongPressGestureRecognizer recognizer = _recognizers[LongPressGestureRecognizer];
assert(recognizer != null); assert(recognizer != null);
if (recognizer.onLongPressStart != null)
recognizer.onLongPressStart(const LongPressStartDetails());
if (recognizer.onLongPress != null) if (recognizer.onLongPress != null)
recognizer.onLongPress(); recognizer.onLongPress();
if (recognizer.onLongPressEnd != null)
recognizer.onLongPressEnd(const LongPressEndDetails());
if (recognizer.onLongPressUp != null)
recognizer.onLongPressUp();
} }
void _handleSemanticsHorizontalDragUpdate(DragUpdateDetails updateDetails) { void _handleSemanticsHorizontalDragUpdate(DragUpdateDetails updateDetails) {
......
...@@ -147,8 +147,7 @@ void main() { ...@@ -147,8 +147,7 @@ void main() {
}); });
test('TapGestureRecognizer _sentTapDown toString', () { test('TapGestureRecognizer _sentTapDown toString', () {
final TapGestureRecognizer tap = TapGestureRecognizer() final TapGestureRecognizer tap = TapGestureRecognizer();
..onTap = () {}; // Add a callback so that event can be added
expect(tap.toString(), equalsIgnoringHashCodes('TapGestureRecognizer#00000(state: ready)')); expect(tap.toString(), equalsIgnoringHashCodes('TapGestureRecognizer#00000(state: ready)'));
const PointerEvent event = PointerDownEvent(pointer: 1, position: Offset(10.0, 10.0)); const PointerEvent event = PointerDownEvent(pointer: 1, position: Offset(10.0, 10.0));
tap.addPointer(event); tap.addPointer(event);
......
...@@ -75,7 +75,7 @@ void main() { ...@@ -75,7 +75,7 @@ void main() {
position: Offset(25.0, 25.0), position: Offset(25.0, 25.0),
); );
// Down/up pair 5: normal tap sequence identical to pair 1 // Down/up pair 5: normal tap sequence identical to pair 1 with different pointer
const PointerDownEvent down5 = PointerDownEvent( const PointerDownEvent down5 = PointerDownEvent(
pointer: 5, pointer: 5,
position: Offset(10.0, 10.0), position: Offset(10.0, 10.0),
...@@ -86,18 +86,6 @@ void main() { ...@@ -86,18 +86,6 @@ void main() {
position: Offset(11.0, 9.0), position: Offset(11.0, 9.0),
); );
// Down/up pair 6: normal tap sequence close to pair 1 but on secondary button
const PointerDownEvent down6 = PointerDownEvent(
pointer: 6,
position: Offset(10.0, 10.0),
buttons: kSecondaryMouseButton,
);
const PointerUpEvent up6 = PointerUpEvent(
pointer: 6,
position: Offset(11.0, 9.0),
);
testGesture('Should recognize double tap', (GestureTester tester) { testGesture('Should recognize double tap', (GestureTester tester) {
final DoubleTapGestureRecognizer tap = DoubleTapGestureRecognizer(); final DoubleTapGestureRecognizer tap = DoubleTapGestureRecognizer();
...@@ -618,183 +606,4 @@ void main() { ...@@ -618,183 +606,4 @@ void main() {
tap.dispose(); tap.dispose();
}); });
group('Enforce consistent-button restriction:', () {
testGesture('Button change should interrupt existing sequence', (GestureTester tester) {
// Down1 -> down6 (different button from 1) -> down2 (same button as 1)
// Down1 and down2 could've been a double tap, but is interrupted by down 6.
const Duration interval = Duration(milliseconds: 100);
assert(interval * 2 < kDoubleTapTimeout);
assert(interval > kDoubleTapMinTime);
final DoubleTapGestureRecognizer tap = DoubleTapGestureRecognizer();
bool doubleTapRecognized = false;
tap.onDoubleTap = () {
doubleTapRecognized = true;
};
tap.addPointer(down1);
tester.closeArena(1);
tester.route(down1);
tester.route(up1);
GestureBinding.instance.gestureArena.sweep(1);
tester.async.elapse(interval);
tap.addPointer(down6);
tester.closeArena(6);
tester.route(down6);
tester.route(up6);
GestureBinding.instance.gestureArena.sweep(6);
tester.async.elapse(interval);
expect(doubleTapRecognized, isFalse);
tap.addPointer(down2);
tester.closeArena(2);
tester.route(down2);
tester.route(up2);
GestureBinding.instance.gestureArena.sweep(2);
expect(doubleTapRecognized, isFalse);
tap.dispose();
});
testGesture('Button change should start a valid sequence', (GestureTester tester) {
// Down6 -> down1 (different button from 6) -> down2 (same button as 1)
const Duration interval = Duration(milliseconds: 100);
assert(interval * 2 < kDoubleTapTimeout);
assert(interval > kDoubleTapMinTime);
final DoubleTapGestureRecognizer tap = DoubleTapGestureRecognizer();
bool doubleTapRecognized = false;
tap.onDoubleTap = () {
doubleTapRecognized = true;
};
tap.addPointer(down6);
tester.closeArena(6);
tester.route(down6);
tester.route(up6);
GestureBinding.instance.gestureArena.sweep(6);
tester.async.elapse(interval);
tap.addPointer(down1);
tester.closeArena(1);
tester.route(down1);
tester.route(up1);
GestureBinding.instance.gestureArena.sweep(1);
expect(doubleTapRecognized, isFalse);
tester.async.elapse(interval);
tap.addPointer(down2);
tester.closeArena(2);
tester.route(down2);
tester.route(up2);
GestureBinding.instance.gestureArena.sweep(2);
expect(doubleTapRecognized, isTrue);
tap.dispose();
});
});
group('Recognizers listening on different buttons do not form competition:', () {
// This test is assisted by tap recognizers. If a tap gesture has
// no competing recognizers, a pointer down event triggers its onTapDown
// immediately; if there are competitors, onTapDown is triggered after a
// timeout.
// The following tests make sure that double tap recognizers do not form
// competition with a tap gesture recognizer listening on a different button.
final List<String> recognized = <String>[];
TapGestureRecognizer tapPrimary;
TapGestureRecognizer tapSecondary;
DoubleTapGestureRecognizer doubleTap;
setUp(() {
tapPrimary = TapGestureRecognizer()
..onTapDown = (TapDownDetails details) {
recognized.add('tapPrimary');
};
tapSecondary = TapGestureRecognizer()
..onSecondaryTapDown = (TapDownDetails details) {
recognized.add('tapSecondary');
};
doubleTap = DoubleTapGestureRecognizer()
..onDoubleTap = () {
recognized.add('doubleTap');
};
});
tearDown(() {
recognized.clear();
tapPrimary.dispose();
tapSecondary.dispose();
doubleTap.dispose();
});
testGesture('A primary double tap recognizer does not form competion with a secondary tap recognizer', (GestureTester tester) {
doubleTap.addPointer(down6);
tapSecondary.addPointer(down6);
tester.closeArena(down6.pointer);
tester.route(down6);
expect(recognized, <String>['tapSecondary']);
});
testGesture('A primary double tap recognizer forms competion with a primary tap recognizer', (GestureTester tester) {
doubleTap.addPointer(down1);
tapPrimary.addPointer(down1);
tester.closeArena(down1.pointer);
tester.route(down1);
expect(recognized, <String>[]);
tester.async.elapse(const Duration(milliseconds: 300));
expect(recognized, <String>['tapPrimary']);
});
});
testGesture('A secondary double tap should not trigger primary', (GestureTester tester) {
final List<String> recognized = <String>[];
final DoubleTapGestureRecognizer doubleTap = DoubleTapGestureRecognizer()
..onDoubleTap = () {
recognized.add('primary');
};
// Down/up pair 7: normal tap sequence close to pair 6
const PointerDownEvent down7 = PointerDownEvent(
pointer: 7,
position: Offset(10.0, 10.0),
buttons: kSecondaryMouseButton,
);
const PointerUpEvent up7 = PointerUpEvent(
pointer: 7,
position: Offset(11.0, 9.0),
);
doubleTap.addPointer(down6);
tester.closeArena(6);
tester.route(down6);
tester.route(up6);
GestureBinding.instance.gestureArena.sweep(6);
tester.async.elapse(const Duration(milliseconds: 100));
doubleTap.addPointer(down7);
tester.closeArena(7);
tester.route(down7);
tester.route(up7);
expect(recognized, <String>[]);
recognized.clear();
doubleTap.dispose();
});
} }
...@@ -13,7 +13,7 @@ void main() { ...@@ -13,7 +13,7 @@ void main() {
testGesture('Should recognize pan', (GestureTester tester) { testGesture('Should recognize pan', (GestureTester tester) {
final PanGestureRecognizer pan = PanGestureRecognizer(); final PanGestureRecognizer pan = PanGestureRecognizer();
final TapGestureRecognizer tap = TapGestureRecognizer()..onTap = () {}; final TapGestureRecognizer tap = TapGestureRecognizer();
bool didStartPan = false; bool didStartPan = false;
pan.onStart = (_) { pan.onStart = (_) {
...@@ -81,8 +81,7 @@ void main() { ...@@ -81,8 +81,7 @@ void main() {
testGesture('Should report most recent point to onStart by default', (GestureTester tester) { testGesture('Should report most recent point to onStart by default', (GestureTester tester) {
final HorizontalDragGestureRecognizer drag = HorizontalDragGestureRecognizer(); final HorizontalDragGestureRecognizer drag = HorizontalDragGestureRecognizer();
final VerticalDragGestureRecognizer competingDrag = VerticalDragGestureRecognizer() final VerticalDragGestureRecognizer competingDrag = VerticalDragGestureRecognizer();
..onStart = (_) {};
Offset positionAtOnStart; Offset positionAtOnStart;
drag.onStart = (DragStartDetails details) { drag.onStart = (DragStartDetails details) {
...@@ -104,9 +103,9 @@ void main() { ...@@ -104,9 +103,9 @@ void main() {
}); });
testGesture('Should report most recent point to onStart with a start configuration', (GestureTester tester) { testGesture('Should report most recent point to onStart with a start configuration', (GestureTester tester) {
final HorizontalDragGestureRecognizer drag = HorizontalDragGestureRecognizer(); final HorizontalDragGestureRecognizer drag =
final VerticalDragGestureRecognizer competingDrag = VerticalDragGestureRecognizer() HorizontalDragGestureRecognizer();
..onStart = (_) {}; final VerticalDragGestureRecognizer competingDrag = VerticalDragGestureRecognizer();
Offset positionAtOnStart; Offset positionAtOnStart;
drag.onStart = (DragStartDetails details) { drag.onStart = (DragStartDetails details) {
...@@ -219,11 +218,9 @@ void main() { ...@@ -219,11 +218,9 @@ void main() {
// TODO(jslavitz): Revert these tests. // TODO(jslavitz): Revert these tests.
testGesture('Should report initial down point to onStart with a down configuration', (GestureTester tester) { testGesture('Should report initial down point to onStart with a down configuration', (GestureTester tester) {
final HorizontalDragGestureRecognizer drag = HorizontalDragGestureRecognizer() final HorizontalDragGestureRecognizer drag =
..dragStartBehavior = DragStartBehavior.down; HorizontalDragGestureRecognizer() ..dragStartBehavior = DragStartBehavior.down;
final VerticalDragGestureRecognizer competingDrag = VerticalDragGestureRecognizer() final VerticalDragGestureRecognizer competingDrag = VerticalDragGestureRecognizer() ..dragStartBehavior = DragStartBehavior.down;
..dragStartBehavior = DragStartBehavior.down
..onStart = (_) {};
Offset positionAtOnStart; Offset positionAtOnStart;
drag.onStart = (DragStartDetails details) { drag.onStart = (DragStartDetails details) {
...@@ -599,248 +596,4 @@ void main() { ...@@ -599,248 +596,4 @@ void main() {
drag.dispose(); drag.dispose();
}); });
group('Enforce consistent-button restriction:', () {
PanGestureRecognizer pan;
TapGestureRecognizer tap;
final List<String> logs = <String>[];
setUp(() {
tap = TapGestureRecognizer()
..onTap = () {}; // Need a callback to enable competition
pan = PanGestureRecognizer()
..onStart = (DragStartDetails details) {
logs.add('start');
}
..onDown = (DragDownDetails details) {
logs.add('down');
}
..onUpdate = (DragUpdateDetails details) {
logs.add('update');
}
..onCancel = () {
logs.add('cancel');
}
..onEnd = (DragEndDetails details) {
logs.add('end');
};
});
tearDown(() {
pan.dispose();
tap.dispose();
logs.clear();
});
testGesture('Button change before acceptance should lead to immediate cancel', (GestureTester tester) {
final TestPointer pointer = TestPointer(5, PointerDeviceKind.mouse, kPrimaryButton);
final PointerDownEvent down = pointer.down(const Offset(10.0, 10.0));
pan.addPointer(down);
tap.addPointer(down);
tester.closeArena(5);
tester.route(down);
expect(logs, <String>['down']);
// Move out of slop so make sure button changes takes priority over slops
tester.route(pointer.move(const Offset(30.0, 30.0), buttons: kSecondaryButton));
expect(logs, <String>['down', 'cancel']);
tester.route(pointer.up());
});
testGesture('Button change before acceptance should not prevent the next drag', (GestureTester tester) {
{ // First drag (which is canceled)
final TestPointer pointer = TestPointer(5, PointerDeviceKind.mouse, kPrimaryButton);
final PointerDownEvent down = pointer.down(const Offset(10.0, 10.0));
pan.addPointer(down);
tap.addPointer(down);
tester.closeArena(down.pointer);
tester.route(down);
tester.route(pointer.move(const Offset(10.0, 10.0), buttons: kSecondaryButton));
tester.route(pointer.up());
expect(logs, <String>['down', 'cancel']);
}
logs.clear();
final TestPointer pointer2 = TestPointer(6, PointerDeviceKind.mouse, kPrimaryButton);
final PointerDownEvent down2 = pointer2.down(const Offset(10.0, 10.0));
pan.addPointer(down2);
tap.addPointer(down2);
tester.closeArena(down2.pointer);
tester.route(down2);
expect(logs, <String>['down']);
tester.route(pointer2.move(const Offset(30.0, 30.0)));
expect(logs, <String>['down', 'start']);
tester.route(pointer2.up());
expect(logs, <String>['down', 'start', 'end']);
});
testGesture('Button change after acceptance should lead to immediate end', (GestureTester tester) {
final TestPointer pointer = TestPointer(5, PointerDeviceKind.mouse, kPrimaryButton);
final PointerDownEvent down = pointer.down(const Offset(10.0, 10.0));
pan.addPointer(down);
tap.addPointer(down);
tester.closeArena(down.pointer);
tester.route(down);
expect(logs, <String>['down']);
tester.route(pointer.move(const Offset(30.0, 30.0)));
expect(logs, <String>['down', 'start']);
tester.route(pointer.move(const Offset(30.0, 30.0), buttons: kSecondaryButton));
expect(logs, <String>['down', 'start', 'end']);
// Make sure no further updates are sent
tester.route(pointer.move(const Offset(50.0, 50.0)));
expect(logs, <String>['down', 'start', 'end']);
tester.route(pointer.up());
});
testGesture('Button change after acceptance should not prevent the next drag', (GestureTester tester) {
{ // First drag (which is canceled)
final TestPointer pointer = TestPointer(5, PointerDeviceKind.mouse, kPrimaryButton);
final PointerDownEvent down = pointer.down(const Offset(10.0, 10.0));
pan.addPointer(down);
tap.addPointer(down);
tester.closeArena(down.pointer);
tester.route(down);
tester.route(pointer.move(const Offset(30.0, 30.0)));
tester.route(pointer.move(const Offset(30.0, 31.0), buttons: kSecondaryButton));
tester.route(pointer.up());
expect(logs, <String>['down', 'start', 'end']);
}
logs.clear();
final TestPointer pointer2 = TestPointer(6, PointerDeviceKind.mouse, kPrimaryButton);
final PointerDownEvent down2 = pointer2.down(const Offset(10.0, 10.0));
pan.addPointer(down2);
tap.addPointer(down2);
tester.closeArena(down2.pointer);
tester.route(down2);
expect(logs, <String>['down']);
tester.route(pointer2.move(const Offset(30.0, 30.0)));
expect(logs, <String>['down', 'start']);
tester.route(pointer2.up());
expect(logs, <String>['down', 'start', 'end']);
});
});
group('Recognizers listening on different buttons do not form competition:', () {
// This test is assisted by tap recognizers. If a tap gesture has
// no competing recognizers, a pointer down event triggers its onTapDown
// immediately; if there are competitors, onTapDown is triggered after a
// timeout.
// The following tests make sure that drag recognizers do not form
// competition with a tap gesture recognizer listening on a different button.
final List<String> recognized = <String>[];
TapGestureRecognizer tapPrimary;
TapGestureRecognizer tapSecondary;
PanGestureRecognizer pan;
setUp(() {
tapPrimary = TapGestureRecognizer()
..onTapDown = (TapDownDetails details) {
recognized.add('tapPrimary');
};
tapSecondary = TapGestureRecognizer()
..onSecondaryTapDown = (TapDownDetails details) {
recognized.add('tapSecondary');
};
pan = PanGestureRecognizer()
..onStart = (_) {
recognized.add('drag');
};
});
tearDown(() {
recognized.clear();
tapPrimary.dispose();
tapSecondary.dispose();
pan.dispose();
});
testGesture('A primary pan recognizer does not form competion with a secondary tap recognizer', (GestureTester tester) {
final TestPointer pointer = TestPointer(
1,
PointerDeviceKind.touch,
kSecondaryButton,
);
final PointerDownEvent down = pointer.down(const Offset(10, 10));
pan.addPointer(down);
tapSecondary.addPointer(down);
tester.closeArena(down.pointer);
tester.route(down);
expect(recognized, <String>['tapSecondary']);
});
testGesture('A primary pan recognizer forms competion with a primary tap recognizer', (GestureTester tester) {
final TestPointer pointer = TestPointer(
1,
PointerDeviceKind.touch,
kPrimaryButton,
);
final PointerDownEvent down = pointer.down(const Offset(10, 10));
pan.addPointer(down);
tapPrimary.addPointer(down);
tester.closeArena(down.pointer);
tester.route(down);
expect(recognized, <String>[]);
tester.route(pointer.up());
expect(recognized, <String>['tapPrimary']);
});
});
testGesture('A secondary drag should not trigger primary', (GestureTester tester) {
final List<String> recognized = <String>[];
final TapGestureRecognizer tap = TapGestureRecognizer()
..onTap = () {}; // Need a listener to enable competetion.
final PanGestureRecognizer pan = PanGestureRecognizer()
..onDown = (DragDownDetails details) {
recognized.add('primaryDown');
}
..onStart = (DragStartDetails details) {
recognized.add('primaryStart');
}
..onUpdate = (DragUpdateDetails details) {
recognized.add('primaryUpdate');
}
..onEnd = (DragEndDetails details) {
recognized.add('primaryEnd');
}
..onCancel = () {
recognized.add('primaryCancel');
};
final TestPointer pointer = TestPointer(
5,
PointerDeviceKind.touch,
kSecondaryButton,
);
final PointerDownEvent down = pointer.down(const Offset(10.0, 10.0));
pan.addPointer(down);
tap.addPointer(down);
tester.closeArena(5);
tester.route(down);
tester.route(pointer.move(const Offset(20.0, 30.0)));
tester.route(pointer.move(const Offset(20.0, 25.0)));
tester.route(pointer.up());
expect(recognized, <String>[]);
recognized.clear();
pan.dispose();
tap.dispose();
recognized.clear();
});
} }
...@@ -20,20 +20,6 @@ void main() { ...@@ -20,20 +20,6 @@ void main() {
expect(nthStylusButton(2), kSecondaryStylusButton); expect(nthStylusButton(2), kSecondaryStylusButton);
}); });
testGesture('smallestButton tests', (GestureTester tester) {
expect(smallestButton(0x0), equals(0x0));
expect(smallestButton(0x1), equals(0x1));
expect(smallestButton(0x200), equals(0x200));
expect(smallestButton(0x220), equals(0x20));
});
testGesture('isSingleButton tests', (GestureTester tester) {
expect(isSingleButton(0x0), isFalse);
expect(isSingleButton(0x1), isTrue);
expect(isSingleButton(0x200), isTrue);
expect(isSingleButton(0x220), isFalse);
});
group('Default values of PointerEvents:', () { group('Default values of PointerEvents:', () {
// Some parameters are intentionally set to a non-trivial value. // Some parameters are intentionally set to a non-trivial value.
......
...@@ -318,38 +318,7 @@ void main() { ...@@ -318,38 +318,7 @@ void main() {
} }
}); });
test('Should synthesise kPrimaryButton for unknown devices', () { test('Should not synthesise kPrimaryButton for certain devices', () {
final Offset location = const Offset(10.0, 10.0) * ui.window.devicePixelRatio;
const PointerDeviceKind kind = PointerDeviceKind.unknown;
final ui.PointerDataPacket packet = ui.PointerDataPacket(
data: <ui.PointerData>[
ui.PointerData(change: ui.PointerChange.add, kind: kind, physicalX: location.dx, physicalY: location.dy),
ui.PointerData(change: ui.PointerChange.hover, kind: kind, physicalX: location.dx, physicalY: location.dy),
ui.PointerData(change: ui.PointerChange.down, kind: kind, physicalX: location.dx, physicalY: location.dy),
ui.PointerData(change: ui.PointerChange.move, kind: kind, physicalX: location.dx, physicalY: location.dy),
ui.PointerData(change: ui.PointerChange.up, kind: kind, physicalX: location.dx, physicalY: location.dy),
]
);
final List<PointerEvent> events = PointerEventConverter.expand(
packet.data, ui.window.devicePixelRatio).toList();
expect(events.length, 5);
expect(events[0].runtimeType, equals(PointerAddedEvent));
expect(events[0].buttons, equals(0));
expect(events[1].runtimeType, equals(PointerHoverEvent));
expect(events[1].buttons, equals(0));
expect(events[2].runtimeType, equals(PointerDownEvent));
expect(events[2].buttons, equals(kPrimaryButton));
expect(events[3].runtimeType, equals(PointerMoveEvent));
expect(events[3].buttons, equals(kPrimaryButton));
expect(events[4].runtimeType, equals(PointerUpEvent));
expect(events[4].buttons, equals(0));
PointerEventConverter.clearPointers();
});
test('Should not synthesise kPrimaryButton for mouse', () {
final Offset location = const Offset(10.0, 10.0) * ui.window.devicePixelRatio; final Offset location = const Offset(10.0, 10.0) * ui.window.devicePixelRatio;
for (PointerDeviceKind kind in <PointerDeviceKind>[ for (PointerDeviceKind kind in <PointerDeviceKind>[
PointerDeviceKind.mouse, PointerDeviceKind.mouse,
......
...@@ -7,7 +7,6 @@ import 'package:flutter/gestures.dart'; ...@@ -7,7 +7,6 @@ import 'package:flutter/gestures.dart';
import '../flutter_test_alternative.dart'; import '../flutter_test_alternative.dart';
import 'gesture_tester.dart'; import 'gesture_tester.dart';
// Down/move/up pair 1: normal tap sequence
const PointerDownEvent down = PointerDownEvent( const PointerDownEvent down = PointerDownEvent(
pointer: 5, pointer: 5,
position: Offset(10, 10), position: Offset(10, 10),
...@@ -23,29 +22,6 @@ const PointerMoveEvent move = PointerMoveEvent( ...@@ -23,29 +22,6 @@ const PointerMoveEvent move = PointerMoveEvent(
position: Offset(100, 200), position: Offset(100, 200),
); );
// Down/up pair 2: normal tap sequence far away from pair 1
const PointerDownEvent down2 = PointerDownEvent(
pointer: 6,
position: Offset(10, 10),
);
const PointerUpEvent up2 = PointerUpEvent(
pointer: 6,
position: Offset(11, 9),
);
// Down/up pair 3: tap sequence with secondary button
const PointerDownEvent down3 = PointerDownEvent(
pointer: 7,
position: Offset(30, 30),
buttons: kSecondaryButton,
);
const PointerUpEvent up3 = PointerUpEvent(
pointer: 7,
position: Offset(31, 29),
);
void main() { void main() {
setUp(ensureGestureBinding); setUp(ensureGestureBinding);
...@@ -211,48 +187,6 @@ void main() { ...@@ -211,48 +187,6 @@ void main() {
longPress.dispose(); longPress.dispose();
}); });
testGesture('Should not recognize long press with more than one buttons', (GestureTester tester) {
longPress.addPointer(const PointerDownEvent(
pointer: 5,
kind: PointerDeviceKind.mouse,
buttons: kSecondaryMouseButton | kMiddleMouseButton,
position: Offset(10, 10),
));
tester.closeArena(5);
expect(longPressDown, isFalse);
tester.route(down);
expect(longPressDown, isFalse);
tester.async.elapse(const Duration(milliseconds: 1000));
expect(longPressDown, isFalse);
tester.route(up);
expect(longPressUp, isFalse);
longPress.dispose();
});
testGesture('Should cancel long press when buttons change before acceptance', (GestureTester tester) {
longPress.addPointer(down);
tester.closeArena(5);
expect(longPressDown, isFalse);
tester.route(down);
expect(longPressDown, isFalse);
tester.async.elapse(const Duration(milliseconds: 300));
expect(longPressDown, isFalse);
tester.route(const PointerMoveEvent(
pointer: 5,
kind: PointerDeviceKind.mouse,
buttons: kMiddleMouseButton,
position: Offset(10, 10),
));
expect(longPressDown, isFalse);
tester.async.elapse(const Duration(milliseconds: 700));
expect(longPressDown, isFalse);
tester.route(up);
expect(longPressUp, isFalse);
longPress.dispose();
});
}); });
group('long press drag', () { group('long press drag', () {
...@@ -345,107 +279,6 @@ void main() { ...@@ -345,107 +279,6 @@ void main() {
}); });
}); });
group('Enforce consistent-button restriction:', () {
// In sequence between `down` and `up` but with buttons changed
const PointerMoveEvent moveR = PointerMoveEvent(
pointer: 5,
buttons: kSecondaryButton,
position: Offset(10, 10),
);
final List<String> recognized = <String>[];
LongPressGestureRecognizer longPress;
setUp(() {
longPress = LongPressGestureRecognizer()
..onLongPressStart = (LongPressStartDetails details) {
recognized.add('start');
}
..onLongPressEnd = (LongPressEndDetails details) {
recognized.add('end');
};
});
tearDown(() {
longPress.dispose();
recognized.clear();
});
testGesture('Should cancel long press when buttons change before acceptance', (GestureTester tester) {
// First press
longPress.addPointer(down);
tester.closeArena(down.pointer);
tester.route(down);
tester.async.elapse(const Duration(milliseconds: 300));
tester.route(moveR);
expect(recognized, <String>[]);
tester.async.elapse(const Duration(milliseconds: 700));
tester.route(up);
expect(recognized, <String>[]);
});
testGesture('Buttons change before acceptance should not prevent the next long press', (GestureTester tester) {
// First press
longPress.addPointer(down);
tester.closeArena(down.pointer);
tester.route(down);
tester.async.elapse(const Duration(milliseconds: 300));
tester.route(moveR);
tester.async.elapse(const Duration(milliseconds: 700));
tester.route(up);
recognized.clear();
// Second press
longPress.addPointer(down2);
tester.closeArena(down2.pointer);
tester.route(down2);
tester.async.elapse(const Duration(milliseconds: 1000));
expect(recognized, <String>['start']);
recognized.clear();
tester.route(up2);
expect(recognized, <String>['end']);
});
testGesture('Should cancel long press when buttons change after acceptance', (GestureTester tester) {
// First press
longPress.addPointer(down);
tester.closeArena(down.pointer);
tester.route(down);
tester.async.elapse(const Duration(milliseconds: 1000));
expect(recognized, <String>['start']);
recognized.clear();
tester.route(moveR);
expect(recognized, <String>[]);
tester.route(up);
expect(recognized, <String>[]);
});
testGesture('Buttons change after acceptance should not prevent the next long press', (GestureTester tester) {
// First press
longPress.addPointer(down);
tester.closeArena(down.pointer);
tester.route(down);
tester.async.elapse(const Duration(milliseconds: 1000));
tester.route(moveR);
tester.route(up);
recognized.clear();
// Second press
longPress.addPointer(down2);
tester.closeArena(down2.pointer);
tester.route(down2);
tester.async.elapse(const Duration(milliseconds: 1000));
expect(recognized, <String>['start']);
recognized.clear();
tester.route(up2);
expect(recognized, <String>['end']);
});
});
testGesture('Can filter long press based on device kind', (GestureTester tester) { testGesture('Can filter long press based on device kind', (GestureTester tester) {
final LongPressGestureRecognizer mouseLongPress = LongPressGestureRecognizer(kind: PointerDeviceKind.mouse); final LongPressGestureRecognizer mouseLongPress = LongPressGestureRecognizer(kind: PointerDeviceKind.mouse);
...@@ -485,107 +318,4 @@ void main() { ...@@ -485,107 +318,4 @@ void main() {
mouseLongPress.dispose(); mouseLongPress.dispose();
}); });
group('Recognizers listening on different buttons do not form competition:', () {
// This test is assisted by tap recognizers. If a tap gesture has
// no competing recognizers, a pointer down event triggers its onTapDown
// immediately; if there are competitors, onTapDown is triggered after a
// timeout.
// The following tests make sure that long press recognizers do not form
// competition with a tap gesture recognizer listening on a different button.
final List<String> recognized = <String>[];
TapGestureRecognizer tapPrimary;
TapGestureRecognizer tapSecondary;
LongPressGestureRecognizer longPress;
setUp(() {
tapPrimary = TapGestureRecognizer()
..onTapDown = (TapDownDetails details) {
recognized.add('tapPrimary');
};
tapSecondary = TapGestureRecognizer()
..onSecondaryTapDown = (TapDownDetails details) {
recognized.add('tapSecondary');
};
longPress = LongPressGestureRecognizer()
..onLongPressStart = (_) {
recognized.add('longPress');
};
});
tearDown(() {
recognized.clear();
tapPrimary.dispose();
tapSecondary.dispose();
longPress.dispose();
});
testGesture('A primary long press recognizer does not form competion with a secondary tap recognizer', (GestureTester tester) {
longPress.addPointer(down3);
tapSecondary.addPointer(down3);
tester.closeArena(down3.pointer);
tester.route(down3);
expect(recognized, <String>['tapSecondary']);
});
testGesture('A primary long press recognizer forms competion with a primary tap recognizer', (GestureTester tester) {
longPress.addPointer(down);
tapPrimary.addPointer(down);
tester.closeArena(down.pointer);
tester.route(down);
expect(recognized, <String>[]);
tester.route(up);
expect(recognized, <String>['tapPrimary']);
});
});
testGesture('A secondary long press should not trigger primary', (GestureTester tester) {
final List<String> recognized = <String>[];
final LongPressGestureRecognizer longPress = LongPressGestureRecognizer()
..onLongPressStart = (LongPressStartDetails details) {
recognized.add('primaryStart');
}
..onLongPress = () {
recognized.add('primary');
}
..onLongPressMoveUpdate = (LongPressMoveUpdateDetails details) {
recognized.add('primaryUpdate');
}
..onLongPressEnd = (LongPressEndDetails details) {
recognized.add('primaryEnd');
}
..onLongPressUp = () {
recognized.add('primaryUp');
};
const PointerDownEvent down2 = PointerDownEvent(
pointer: 2,
buttons: kSecondaryButton,
position: Offset(30.0, 30.0),
);
const PointerMoveEvent move2 = PointerMoveEvent(
pointer: 2,
buttons: kSecondaryButton,
position: Offset(100, 200),
);
const PointerUpEvent up2 = PointerUpEvent(
pointer: 2,
position: Offset(100, 201),
);
longPress.addPointer(down2);
tester.closeArena(2);
tester.route(down2);
tester.async.elapse(const Duration(milliseconds: 700));
tester.route(move2);
tester.route(up2);
expect(recognized, <String>[]);
longPress.dispose();
recognized.clear();
});
} }
...@@ -73,18 +73,6 @@ void main() { ...@@ -73,18 +73,6 @@ void main() {
position: Offset(22.0, 22.0), position: Offset(22.0, 22.0),
); );
// Down/up sequence 5: tap sequence with secondary button
const PointerDownEvent down5 = PointerDownEvent(
pointer: 5,
position: Offset(20.0, 20.0),
buttons: kSecondaryButton,
);
const PointerUpEvent up5 = PointerUpEvent(
pointer: 5,
position: Offset(20.0, 20.0),
);
testGesture('Should recognize tap', (GestureTester tester) { testGesture('Should recognize tap', (GestureTester tester) {
final TapGestureRecognizer tap = TapGestureRecognizer(); final TapGestureRecognizer tap = TapGestureRecognizer();
...@@ -451,48 +439,6 @@ void main() { ...@@ -451,48 +439,6 @@ void main() {
tap.dispose(); tap.dispose();
}); });
testGesture('PointerCancelEvent after exceeding deadline cancels tap', (GestureTester tester) {
const PointerDownEvent down = PointerDownEvent(
pointer: 5,
position: Offset(10.0, 10.0),
);
const PointerCancelEvent cancel = PointerCancelEvent(
pointer: 5,
position: Offset(10.0, 10.0),
);
final TapGestureRecognizer tap = TapGestureRecognizer();
final HorizontalDragGestureRecognizer drag = HorizontalDragGestureRecognizer()
..onStart = (_) {}; // Need a callback to compete
final List<String> recognized = <String>[];
tap.onTapDown = (_) {
recognized.add('down');
};
tap.onTapUp = (_) {
recognized.add('up');
};
tap.onTap = () {
recognized.add('tap');
};
tap.onTapCancel = () {
recognized.add('cancel');
};
tap.addPointer(down);
drag.addPointer(down);
tester.closeArena(5);
tester.route(down);
expect(recognized, <String>[]);
tester.async.elapse(const Duration(milliseconds: 1000));
expect(recognized, <String>['down']);
tester.route(cancel);
expect(recognized, <String>['down', 'cancel']);
tap.dispose();
drag.dispose();
});
testGesture('losing tap gesture recognizer does not send onTapCancel', (GestureTester tester) { testGesture('losing tap gesture recognizer does not send onTapCancel', (GestureTester tester) {
final TapGestureRecognizer tap = TapGestureRecognizer(); final TapGestureRecognizer tap = TapGestureRecognizer();
final HorizontalDragGestureRecognizer drag = HorizontalDragGestureRecognizer(); final HorizontalDragGestureRecognizer drag = HorizontalDragGestureRecognizer();
...@@ -521,282 +467,4 @@ void main() { ...@@ -521,282 +467,4 @@ void main() {
tap.dispose(); tap.dispose();
drag.dispose(); drag.dispose();
}); });
group('Enforce consistent-button restriction:', () {
// Change buttons during down-up sequence 1
const PointerMoveEvent move1lr = PointerMoveEvent(
pointer: 1,
position: Offset(10.0, 10.0),
buttons: kPrimaryMouseButton | kSecondaryMouseButton,
);
const PointerMoveEvent move1r = PointerMoveEvent(
pointer: 1,
position: Offset(10.0, 10.0),
buttons: kSecondaryMouseButton,
);
final List<String> recognized = <String>[];
TapGestureRecognizer tap;
setUp(() {
tap = TapGestureRecognizer()
..onTapDown = (TapDownDetails details) {
recognized.add('down');
}
..onTapUp = (TapUpDetails details) {
recognized.add('up');
}
..onTapCancel = () {
recognized.add('cancel');
};
});
tearDown(() {
tap.dispose();
recognized.clear();
});
testGesture('changing buttons before TapDown should cancel gesture without sending cancel', (GestureTester tester) {
tap.addPointer(down1);
tester.closeArena(1);
expect(recognized, <String>[]);
tester.route(move1lr);
expect(recognized, <String>[]);
tester.route(move1r);
expect(recognized, <String>[]);
tester.route(up1);
expect(recognized, <String>[]);
tap.dispose();
});
testGesture('changing buttons before TapDown should not prevent the next tap', (GestureTester tester) {
tap.addPointer(down1);
tester.closeArena(1);
tester.route(move1lr);
tester.route(move1r);
tester.route(up1);
expect(recognized, <String>[]);
tap.addPointer(down2);
tester.closeArena(2);
tester.async.elapse(const Duration(milliseconds: 1000));
tester.route(up2);
expect(recognized, <String>['down', 'up']);
tap.dispose();
});
testGesture('changing buttons after TapDown should cancel gesture and send cancel', (GestureTester tester) {
tap.addPointer(down1);
tester.closeArena(1);
expect(recognized, <String>[]);
tester.async.elapse(const Duration(milliseconds: 1000));
expect(recognized, <String>['down']);
tester.route(move1lr);
expect(recognized, <String>['down', 'cancel']);
tester.route(move1r);
expect(recognized, <String>['down', 'cancel']);
tester.route(up1);
expect(recognized, <String>['down', 'cancel']);
tap.dispose();
});
testGesture('changing buttons after TapDown should not prevent the next tap', (GestureTester tester) {
tap.addPointer(down1);
tester.closeArena(1);
tester.async.elapse(const Duration(milliseconds: 1000));
tester.route(move1lr);
tester.route(move1r);
tester.route(up1);
GestureBinding.instance.gestureArena.sweep(1);
expect(recognized, <String>['down', 'cancel']);
tap.addPointer(down2);
tester.closeArena(2);
tester.async.elapse(const Duration(milliseconds: 1000));
tester.route(up2);
GestureBinding.instance.gestureArena.sweep(2);
expect(recognized, <String>['down', 'cancel', 'down', 'up']);
tap.dispose();
});
});
group('Recognizers listening on different buttons do not form competition:', () {
// If a tap gesture has no competitors, a pointer down event triggers
// onTapDown immediately; if there are competitors, onTapDown is triggered
// after a timeout. The following tests make sure that tap recognizers
// listening on different buttons do not form competition.
final List<String> recognized = <String>[];
TapGestureRecognizer primary;
TapGestureRecognizer primary2;
TapGestureRecognizer secondary;
setUp(() {
primary = TapGestureRecognizer()
..onTapDown = (TapDownDetails details) {
recognized.add('primaryDown');
}
..onTapUp = (TapUpDetails details) {
recognized.add('primaryUp');
}
..onTapCancel = () {
recognized.add('primaryCancel');
};
primary2 = TapGestureRecognizer()
..onTapDown = (TapDownDetails details) {
recognized.add('primary2Down');
}
..onTapUp = (TapUpDetails details) {
recognized.add('primary2Up');
}
..onTapCancel = () {
recognized.add('primary2Cancel');
};
secondary = TapGestureRecognizer()
..onSecondaryTapDown = (TapDownDetails details) {
recognized.add('secondaryDown');
}
..onSecondaryTapUp = (TapUpDetails details) {
recognized.add('secondaryUp');
}
..onSecondaryTapCancel = () {
recognized.add('secondaryCancel');
};
});
tearDown(() {
recognized.clear();
primary.dispose();
primary2.dispose();
secondary.dispose();
});
testGesture('A primary tap recognizer does not form competion with a secondary tap recognizer', (GestureTester tester) {
primary.addPointer(down1);
secondary.addPointer(down1);
tester.closeArena(1);
tester.route(down1);
expect(recognized, <String>['primaryDown']);
recognized.clear();
tester.route(up1);
expect(recognized, <String>['primaryUp']);
});
testGesture('A primary tap recognizer forms competion with another primary tap recognizer', (GestureTester tester) {
primary.addPointer(down1);
primary2.addPointer(down1);
tester.closeArena(1);
tester.route(down1);
expect(recognized, <String>[]);
tester.async.elapse(const Duration(milliseconds: 500));
expect(recognized, <String>['primaryDown', 'primary2Down']);
});
});
group('Gestures of different buttons trigger correct callbacks:', () {
final List<String> recognized = <String>[];
TapGestureRecognizer tap;
const PointerCancelEvent cancel1 = PointerCancelEvent(
pointer: 1,
);
const PointerCancelEvent cancel5 = PointerCancelEvent(
pointer: 5,
);
setUp(() {
tap = TapGestureRecognizer()
..onTapDown = (TapDownDetails details) {
recognized.add('primaryDown');
}
..onTap = () {
recognized.add('primary');
}
..onTapUp = (TapUpDetails details) {
recognized.add('primaryUp');
}
..onTapCancel = () {
recognized.add('primaryCancel');
}
..onSecondaryTapDown = (TapDownDetails details) {
recognized.add('secondaryDown');
}
..onSecondaryTapUp = (TapUpDetails details) {
recognized.add('secondaryUp');
}
..onSecondaryTapCancel = () {
recognized.add('secondaryCancel');
};
});
tearDown(() {
recognized.clear();
tap.dispose();
});
testGesture('A primary tap should trigger primary callbacks', (GestureTester tester) {
tap.addPointer(down1);
tester.closeArena(down1.pointer);
expect(recognized, <String>[]);
tester.async.elapse(const Duration(milliseconds: 500));
expect(recognized, <String>['primaryDown']);
recognized.clear();
tester.route(up1);
expect(recognized, <String>['primaryUp', 'primary']);
GestureBinding.instance.gestureArena.sweep(down1.pointer);
});
testGesture('A primary tap cancel trigger primary callbacks', (GestureTester tester) {
tap.addPointer(down1);
tester.closeArena(down1.pointer);
expect(recognized, <String>[]);
tester.async.elapse(const Duration(milliseconds: 500));
expect(recognized, <String>['primaryDown']);
recognized.clear();
tester.route(cancel1);
expect(recognized, <String>['primaryCancel']);
GestureBinding.instance.gestureArena.sweep(down1.pointer);
});
testGesture('A secondary tap should trigger secondary callbacks', (GestureTester tester) {
tap.addPointer(down5);
tester.closeArena(down5.pointer);
expect(recognized, <String>[]);
tester.async.elapse(const Duration(milliseconds: 500));
expect(recognized, <String>['secondaryDown']);
recognized.clear();
tester.route(up5);
GestureBinding.instance.gestureArena.sweep(down5.pointer);
expect(recognized, <String>['secondaryUp']);
});
testGesture('A secondary tap cancel should trigger secondary callbacks', (GestureTester tester) {
tap.addPointer(down5);
tester.closeArena(down5.pointer);
expect(recognized, <String>[]);
tester.async.elapse(const Duration(milliseconds: 500));
expect(recognized, <String>['secondaryDown']);
recognized.clear();
tester.route(cancel5);
GestureBinding.instance.gestureArena.sweep(down5.pointer);
expect(recognized, <String>['secondaryCancel']);
});
});
} }
...@@ -592,10 +592,7 @@ void main() { ...@@ -592,10 +592,7 @@ void main() {
viewType: 'webview', viewType: 'webview',
gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>{ gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>{
Factory<VerticalDragGestureRecognizer>( Factory<VerticalDragGestureRecognizer>(
() { () => VerticalDragGestureRecognizer(),
return VerticalDragGestureRecognizer()
..onStart = (_) {}; // Add callback to enable recognizer
},
), ),
}, },
layoutDirection: TextDirection.ltr, layoutDirection: TextDirection.ltr,
...@@ -1266,10 +1263,7 @@ void main() { ...@@ -1266,10 +1263,7 @@ void main() {
viewType: 'webview', viewType: 'webview',
gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>{ gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>{
Factory<VerticalDragGestureRecognizer>( Factory<VerticalDragGestureRecognizer>(
() { () => VerticalDragGestureRecognizer(),
return VerticalDragGestureRecognizer()
..onStart = (_) {}; // Add callback to enable recognizer
},
), ),
}, },
layoutDirection: TextDirection.ltr, layoutDirection: TextDirection.ltr,
......
...@@ -22,15 +22,9 @@ class TestPointer { ...@@ -22,15 +22,9 @@ class TestPointer {
/// ///
/// Multiple [TestPointer]s created with the same pointer identifier will /// Multiple [TestPointer]s created with the same pointer identifier will
/// interfere with each other if they are used in parallel. /// interfere with each other if they are used in parallel.
TestPointer([ TestPointer([this.pointer = 1, this.kind = PointerDeviceKind.touch])
this.pointer = 1,
this.kind = PointerDeviceKind.touch,
int buttons = kPrimaryButton,
])
: assert(kind != null), : assert(kind != null),
assert(pointer != null), assert(pointer != null);
assert(buttons != null),
_buttons = buttons;
/// The pointer identifier used for events generated by this object. /// The pointer identifier used for events generated by this object.
/// ///
...@@ -41,11 +35,6 @@ class TestPointer { ...@@ -41,11 +35,6 @@ class TestPointer {
/// [PointerDeviceKind.touch]. /// [PointerDeviceKind.touch].
final PointerDeviceKind kind; final PointerDeviceKind kind;
/// The kind of buttons to simulate on Down and Move events. Defaults to
/// [kPrimaryButton].
int get buttons => _buttons;
int _buttons;
/// Whether the pointer simulated by this object is currently down. /// Whether the pointer simulated by this object is currently down.
/// ///
/// A pointer is released (goes up) by calling [up] or [cancel]. /// A pointer is released (goes up) by calling [up] or [cancel].
...@@ -62,14 +51,8 @@ class TestPointer { ...@@ -62,14 +51,8 @@ class TestPointer {
/// If a custom event is created outside of this class, this function is used /// If a custom event is created outside of this class, this function is used
/// to set the [isDown]. /// to set the [isDown].
bool setDownInfo( bool setDownInfo(PointerEvent event, Offset newLocation) {
PointerEvent event,
Offset newLocation, {
int buttons,
}) {
_location = newLocation; _location = newLocation;
if (buttons != null)
_buttons = buttons;
switch (event.runtimeType) { switch (event.runtimeType) {
case PointerDownEvent: case PointerDownEvent:
assert(!isDown); assert(!isDown);
...@@ -89,25 +72,15 @@ class TestPointer { ...@@ -89,25 +72,15 @@ class TestPointer {
/// ///
/// By default, the time stamp on the event is [Duration.zero]. You can give a /// By default, the time stamp on the event is [Duration.zero]. You can give a
/// specific time stamp by passing the `timeStamp` argument. /// specific time stamp by passing the `timeStamp` argument.
/// PointerDownEvent down(Offset newLocation, {Duration timeStamp = Duration.zero}) {
/// By default, the set of buttons in the last down or move event is used.
/// You can give a specific set of buttons by passing the `buttons` argument.
PointerDownEvent down(
Offset newLocation, {
Duration timeStamp = Duration.zero,
int buttons,
}) {
assert(!isDown); assert(!isDown);
_isDown = true; _isDown = true;
_location = newLocation; _location = newLocation;
if (buttons != null)
_buttons = buttons;
return PointerDownEvent( return PointerDownEvent(
timeStamp: timeStamp, timeStamp: timeStamp,
kind: kind, kind: kind,
pointer: pointer, pointer: pointer,
position: location, position: location,
buttons: _buttons,
); );
} }
...@@ -118,14 +91,7 @@ class TestPointer { ...@@ -118,14 +91,7 @@ class TestPointer {
/// ///
/// [isDown] must be true when this is called, since move events can only /// [isDown] must be true when this is called, since move events can only
/// be generated when the pointer is down. /// be generated when the pointer is down.
/// PointerMoveEvent move(Offset newLocation, {Duration timeStamp = Duration.zero}) {
/// By default, the set of buttons in the last down or move event is used.
/// You can give a specific set of buttons by passing the `buttons` argument.
PointerMoveEvent move(
Offset newLocation, {
Duration timeStamp = Duration.zero,
int buttons,
}) {
assert( assert(
isDown, isDown,
'Move events can only be generated when the pointer is down. To ' 'Move events can only be generated when the pointer is down. To '
...@@ -133,15 +99,12 @@ class TestPointer { ...@@ -133,15 +99,12 @@ class TestPointer {
'up, use hover() instead.'); 'up, use hover() instead.');
final Offset delta = newLocation - location; final Offset delta = newLocation - location;
_location = newLocation; _location = newLocation;
if (buttons != null)
_buttons = buttons;
return PointerMoveEvent( return PointerMoveEvent(
timeStamp: timeStamp, timeStamp: timeStamp,
kind: kind, kind: kind,
pointer: pointer, pointer: pointer,
position: newLocation, position: newLocation,
delta: delta, delta: delta,
buttons: _buttons,
); );
} }
......
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