Unverified Commit 91de4bd3 authored by Todd Volkert's avatar Todd Volkert Committed by GitHub

Add onLongPressDown, onLongPressCancel (#81260)

This adds support for GestureDetector.onLongPressDown and
GestureDetector.onLongPressCancel, allowing callers to register
interest in the initial pointer contact that might turn into
a long-press (and the associated cancel event if the gesture
loses).
parent 99f19d05
...@@ -9,16 +9,50 @@ import 'events.dart'; ...@@ -9,16 +9,50 @@ import 'events.dart';
import 'recognizer.dart'; import 'recognizer.dart';
import 'velocity_tracker.dart'; import 'velocity_tracker.dart';
/// Callback signature for [LongPressGestureRecognizer.onLongPressDown].
///
/// Called when a pointer that might cause a long-press has contacted the
/// screen. The position at which the pointer contacted the screen is available
/// in the `details`.
///
/// See also:
///
/// * [GestureDetector.onLongPressDown], which matches this signature.
/// * [GestureLongPressStartCallback], the signature that gets called when the
/// pointer has been in contact with the screen long enough to be considered
/// a long-press.
typedef GestureLongPressDownCallback = void Function(LongPressDownDetails details);
/// Callback signature for [LongPressGestureRecognizer.onLongPressCancel].
///
/// Called when the pointer that previously triggered a
/// [GestureLongPressDownCallback] will not end up causing a long-press.
///
/// See also:
///
/// * [GestureDetector.onLongPressCancel], which matches this signature.
typedef GestureLongPressCancelCallback = void Function();
/// Callback signature for [LongPressGestureRecognizer.onLongPress]. /// Callback signature for [LongPressGestureRecognizer.onLongPress].
/// ///
/// Called when a pointer has remained in contact with the screen at the /// Called 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:
///
/// * [GestureDetector.onLongPress], which matches this signature.
/// * [GestureLongPressStartCallback], which is the same signature but with
/// details of where the long press occurred.
typedef GestureLongPressCallback = void Function(); typedef GestureLongPressCallback = void Function();
/// Callback signature for [LongPressGestureRecognizer.onLongPressUp]. /// Callback signature for [LongPressGestureRecognizer.onLongPressUp].
/// ///
/// Called when a pointer stops contacting the screen after a long press /// Called when a pointer stops contacting the screen after a long press
/// gesture was detected. /// gesture was detected.
///
/// See also:
///
/// * [GestureDetector.onLongPressUp], which matches this signature.
typedef GestureLongPressUpCallback = void Function(); typedef GestureLongPressUpCallback = void Function();
/// Callback signature for [LongPressGestureRecognizer.onLongPressStart]. /// Callback signature for [LongPressGestureRecognizer.onLongPressStart].
...@@ -26,6 +60,12 @@ typedef GestureLongPressUpCallback = void Function(); ...@@ -26,6 +60,12 @@ typedef GestureLongPressUpCallback = void Function();
/// Called when a pointer has remained in contact with the screen at the /// Called when a pointer has remained in contact with the screen at the
/// same location for a long period of time. Also reports the long press down /// same location for a long period of time. Also reports the long press down
/// position. /// position.
///
/// See also:
///
/// * [GestureDetector.onLongPressStart], which matches this signature.
/// * [GestureLongPressCallback], which is the same signature without the
/// details.
typedef GestureLongPressStartCallback = void Function(LongPressStartDetails details); typedef GestureLongPressStartCallback = void Function(LongPressStartDetails details);
/// Callback signature for [LongPressGestureRecognizer.onLongPressMoveUpdate]. /// Callback signature for [LongPressGestureRecognizer.onLongPressMoveUpdate].
...@@ -33,6 +73,10 @@ typedef GestureLongPressStartCallback = void Function(LongPressStartDetails deta ...@@ -33,6 +73,10 @@ typedef GestureLongPressStartCallback = void Function(LongPressStartDetails deta
/// Called when a pointer is moving after being held in contact at the same /// Called when a pointer is moving after being held in contact at the same
/// location for a long period of time. Reports the new position and its offset /// location for a long period of time. Reports the new position and its offset
/// from the original down position. /// from the original down position.
///
/// See also:
///
/// * [GestureDetector.onLongPressMoveUpdate], which matches this signature.
typedef GestureLongPressMoveUpdateCallback = void Function(LongPressMoveUpdateDetails details); typedef GestureLongPressMoveUpdateCallback = void Function(LongPressMoveUpdateDetails details);
/// Callback signature for [LongPressGestureRecognizer.onLongPressEnd]. /// Callback signature for [LongPressGestureRecognizer.onLongPressEnd].
...@@ -40,8 +84,46 @@ typedef GestureLongPressMoveUpdateCallback = void Function(LongPressMoveUpdateDe ...@@ -40,8 +84,46 @@ typedef GestureLongPressMoveUpdateCallback = void Function(LongPressMoveUpdateDe
/// Called when a pointer stops contacting the screen after a long press /// Called when a pointer stops contacting the screen after a long press
/// gesture was detected. Also reports the position where the pointer stopped /// gesture was detected. Also reports the position where the pointer stopped
/// contacting the screen. /// contacting the screen.
///
/// See also:
///
/// * [GestureDetector.onLongPressEnd], which matches this signature.
typedef GestureLongPressEndCallback = void Function(LongPressEndDetails details); typedef GestureLongPressEndCallback = void Function(LongPressEndDetails details);
/// Details for callbacks that use [GestureLongPressDownCallback].
///
/// See also:
///
/// * [LongPressGestureRecognizer.onLongPressDown], whose callback passes
/// these details.
/// * [LongPressGestureRecognizer.onSecondaryLongPressDown], whose callback
/// passes these details.
/// * [LongPressGestureRecognizer.onTertiaryLongPressDown], whose callback
/// passes these details.
class LongPressDownDetails {
/// Creates the details for a [GestureLongPressDownCallback].
///
/// The `globalPosition` argument must not be null.
///
/// If the `localPosition` argument is not specified, it will default to the
/// global position.
const LongPressDownDetails({
this.globalPosition = Offset.zero,
Offset? localPosition,
this.kind,
}) : assert(globalPosition != null),
localPosition = localPosition ?? globalPosition;
/// The global position at which the pointer contacted the screen.
final Offset globalPosition;
/// The kind of the device that initiated the event.
final PointerDeviceKind? kind;
/// The local position at which the pointer contacted the screen.
final Offset localPosition;
}
/// Details for callbacks that use [GestureLongPressStartCallback]. /// Details for callbacks that use [GestureLongPressStartCallback].
/// ///
/// See also: /// See also:
...@@ -59,10 +141,10 @@ class LongPressStartDetails { ...@@ -59,10 +141,10 @@ class LongPressStartDetails {
}) : assert(globalPosition != null), }) : assert(globalPosition != null),
localPosition = localPosition ?? globalPosition; localPosition = localPosition ?? globalPosition;
/// The global position at which the pointer contacted the screen. /// The global position at which the pointer initially contacted the screen.
final Offset globalPosition; final Offset globalPosition;
/// The local position at which the pointer contacted the screen. /// The local position at which the pointer initially contacted the screen.
final Offset localPosition; final Offset localPosition;
} }
...@@ -178,21 +260,63 @@ class LongPressGestureRecognizer extends PrimaryPointerGestureRecognizer { ...@@ -178,21 +260,63 @@ class LongPressGestureRecognizer extends PrimaryPointerGestureRecognizer {
// different set of buttons, the gesture is canceled. // different set of buttons, the gesture is canceled.
int? _initialButtons; int? _initialButtons;
/// Called when a pointer has contacted the screen at a particular location
/// with a primary button, which might be the start of a long-press.
///
/// This triggers after the pointer down event.
///
/// If this recognizer doesn't win the arena, [onLongPressCancel] is called
/// next. Otherwise, [onLongPressStart] is called next.
///
/// See also:
///
/// * [kPrimaryButton], the button this callback responds to.
/// * [onSecondaryLongPressDown], a similar callback but for a secondary button.
/// * [onTertiaryLongPressDown], a similar callback but for a tertiary button.
/// * [LongPressDownDetails], which is passed as an argument to this callback.
/// * [GestureDetector.onLongPressDown], which exposes this callback in a widget.
GestureLongPressDownCallback? onLongPressDown;
/// Called when a pointer that previously triggered [onLongPressDown] will
/// not end up causing a long-press.
///
/// This triggers once the gesture loses the arena if [onLongPressDown] has
/// previously been triggered.
///
/// If this recognizer wins the arena, [onLongPressStart] and [onLongPress]
/// are called instead.
///
/// If the gesture is deactivated due to [postAcceptSlopTolerance] having
/// been exceeded, this callback will not be called, since the gesture will
/// have already won the arena at that point.
///
/// See also:
///
/// * [kPrimaryButton], the button this callback responds to.
GestureLongPressCancelCallback? onLongPressCancel;
/// Called when a long press gesture by a primary button has been recognized. /// Called when a long press gesture by a primary button has been recognized.
/// ///
/// This is equivalent to (and is called immediately after) [onLongPressStart].
/// The only difference between the two is that this callback does not
/// contain details of the position at which the pointer initially contacted
/// the screen.
///
/// See also: /// See also:
/// ///
/// * [kPrimaryButton], the button this callback responds to. /// * [kPrimaryButton], the button this callback responds to.
/// * [onLongPressStart], which has the same timing but has data for the
/// press location.
GestureLongPressCallback? onLongPress; GestureLongPressCallback? onLongPress;
/// Called when a long press gesture by a primary button has been recognized. /// Called when a long press gesture by a primary button has been recognized.
/// ///
/// This is equivalent to (and is called immediately before) [onLongPress].
/// The only difference between the two is that this callback contains
/// details of the position at which the pointer initially contacted the
/// screen, whereas [onLongPress] does not.
///
/// See also: /// See also:
/// ///
/// * [kPrimaryButton], the button this callback responds to. /// * [kPrimaryButton], the button this callback responds to.
/// * [onLongPress], which has the same timing but without details.
/// * [LongPressStartDetails], which is passed as an argument to this callback. /// * [LongPressStartDetails], which is passed as an argument to this callback.
GestureLongPressStartCallback? onLongPressStart; GestureLongPressStartCallback? onLongPressStart;
...@@ -208,40 +332,90 @@ class LongPressGestureRecognizer extends PrimaryPointerGestureRecognizer { ...@@ -208,40 +332,90 @@ class LongPressGestureRecognizer extends PrimaryPointerGestureRecognizer {
/// Called when the pointer stops contacting the screen after a long-press /// Called when the pointer stops contacting the screen after a long-press
/// by a primary button. /// by a primary button.
/// ///
/// This is equivalent to (and is called immediately after) [onLongPressEnd].
/// The only difference between the two is that this callback does not
/// contain details of the state of the pointer when it stopped contacting
/// the screen.
///
/// See also: /// See also:
/// ///
/// * [kPrimaryButton], the button this callback responds to. /// * [kPrimaryButton], the button this callback responds to.
/// * [onLongPressEnd], which has the same timing but has data for the up
/// gesture location.
GestureLongPressUpCallback? onLongPressUp; GestureLongPressUpCallback? onLongPressUp;
/// Called when the pointer stops contacting the screen after a long-press /// Called when the pointer stops contacting the screen after a long-press
/// by a primary button. /// by a primary button.
/// ///
/// This is equivalent to (and is called immediately before) [onLongPressUp].
/// The only difference between the two is that this callback contains
/// details of the state of the pointer when it stopped contacting the
/// screen, whereas [onLongPressUp] does not.
///
/// See also: /// See also:
/// ///
/// * [kPrimaryButton], the button this callback responds to. /// * [kPrimaryButton], the button this callback responds to.
/// * [onLongPressUp], which has the same timing, but without details.
/// * [LongPressEndDetails], which is passed as an argument to this /// * [LongPressEndDetails], which is passed as an argument to this
/// callback. /// callback.
GestureLongPressEndCallback? onLongPressEnd; GestureLongPressEndCallback? onLongPressEnd;
/// Called when a pointer has contacted the screen at a particular location
/// with a secondary button, which might be the start of a long-press.
///
/// This triggers after the pointer down event.
///
/// If this recognizer doesn't win the arena, [onSecondaryLongPressCancel] is
/// called next. Otherwise, [onSecondaryLongPressStart] is called next.
///
/// See also:
///
/// * [kSecondaryButton], the button this callback responds to.
/// * [onLongPressDown], a similar callback but for a primary button.
/// * [onTertiaryLongPressDown], a similar callback but for a tertiary button.
/// * [LongPressDownDetails], which is passed as an argument to this callback.
/// * [GestureDetector.onSecondaryLongPressDown], which exposes this callback
/// in a widget.
GestureLongPressDownCallback? onSecondaryLongPressDown;
/// Called when a pointer that previously triggered [onSecondaryLongPressDown]
/// will not end up causing a long-press.
///
/// This triggers once the gesture loses the arena if
/// [onSecondaryLongPressDown] has previously been triggered.
///
/// If this recognizer wins the arena, [onSecondaryLongPressStart] and
/// [onSecondaryLongPress] are called instead.
///
/// If the gesture is deactivated due to [postAcceptSlopTolerance] having
/// been exceeded, this callback will not be called, since the gesture will
/// have already won the arena at that point.
///
/// See also:
///
/// * [kSecondaryButton], the button this callback responds to.
GestureLongPressCancelCallback? onSecondaryLongPressCancel;
/// Called when a long press gesture by a secondary button has been /// Called when a long press gesture by a secondary button has been
/// recognized. /// recognized.
/// ///
/// This is equivalent to (and is called immediately after)
/// [onSecondaryLongPressStart]. The only difference between the two is that
/// this callback does not contain details of the position at which the
/// pointer initially contacted the screen.
///
/// See also: /// See also:
/// ///
/// * [kSecondaryButton], the button this callback responds to. /// * [kSecondaryButton], the button this callback responds to.
/// * [onSecondaryLongPressStart], which has the same timing but has data for
/// the press location.
GestureLongPressCallback? onSecondaryLongPress; GestureLongPressCallback? onSecondaryLongPress;
/// Called when a long press gesture by a secondary button has been recognized. /// Called when a long press gesture by a secondary button has been recognized.
/// ///
/// This is equivalent to (and is called immediately before)
/// [onSecondaryLongPress]. The only difference between the two is that this
/// callback contains details of the position at which the pointer initially
/// contacted the screen, whereas [onSecondaryLongPress] does not.
///
/// See also: /// See also:
/// ///
/// * [kSecondaryButton], the button this callback responds to. /// * [kSecondaryButton], the button this callback responds to.
/// * [onSecondaryLongPress], which has the same timing but without details.
/// * [LongPressStartDetails], which is passed as an argument to this /// * [LongPressStartDetails], which is passed as an argument to this
/// callback. /// callback.
GestureLongPressStartCallback? onSecondaryLongPressStart; GestureLongPressStartCallback? onSecondaryLongPressStart;
...@@ -259,40 +433,89 @@ class LongPressGestureRecognizer extends PrimaryPointerGestureRecognizer { ...@@ -259,40 +433,89 @@ class LongPressGestureRecognizer extends PrimaryPointerGestureRecognizer {
/// Called when the pointer stops contacting the screen after a long-press by /// Called when the pointer stops contacting the screen after a long-press by
/// a secondary button. /// a secondary button.
/// ///
/// This is equivalent to (and is called immediately after)
/// [onSecondaryLongPressEnd]. The only difference between the two is that
/// this callback does not contain details of the state of the pointer when
/// it stopped contacting the screen.
///
/// See also: /// See also:
/// ///
/// * [kSecondaryButton], the button this callback responds to. /// * [kSecondaryButton], the button this callback responds to.
/// * [onSecondaryLongPressEnd], which has the same timing but has data for
/// the up gesture location.
GestureLongPressUpCallback? onSecondaryLongPressUp; GestureLongPressUpCallback? onSecondaryLongPressUp;
/// Called when the pointer stops contacting the screen after a long-press by /// Called when the pointer stops contacting the screen after a long-press by
/// a secondary button. /// a secondary button.
/// ///
/// This is equivalent to (and is called immediately before)
/// [onSecondaryLongPressUp]. The only difference between the two is that
/// this callback contains details of the state of the pointer when it
/// stopped contacting the screen, whereas [onSecondaryLongPressUp] does not.
///
/// See also: /// See also:
/// ///
/// * [kSecondaryButton], the button this callback responds to. /// * [kSecondaryButton], the button this callback responds to.
/// * [onSecondaryLongPressUp], which has the same timing, but without
/// details.
/// * [LongPressEndDetails], which is passed as an argument to this callback. /// * [LongPressEndDetails], which is passed as an argument to this callback.
GestureLongPressEndCallback? onSecondaryLongPressEnd; GestureLongPressEndCallback? onSecondaryLongPressEnd;
/// Called when a pointer has contacted the screen at a particular location
/// with a tertiary button, which might be the start of a long-press.
///
/// This triggers after the pointer down event.
///
/// If this recognizer doesn't win the arena, [onTertiaryLongPressCancel] is
/// called next. Otherwise, [onTertiaryLongPressStart] is called next.
///
/// See also:
///
/// * [kTertiaryButton], the button this callback responds to.
/// * [onLongPressDown], a similar callback but for a primary button.
/// * [onSecondaryLongPressDown], a similar callback but for a secondary button.
/// * [LongPressDownDetails], which is passed as an argument to this callback.
/// * [GestureDetector.onTertiaryLongPressDown], which exposes this callback
/// in a widget.
GestureLongPressDownCallback? onTertiaryLongPressDown;
/// Called when a pointer that previously triggered [onTertiaryLongPressDown]
/// will not end up causing a long-press.
///
/// This triggers once the gesture loses the arena if
/// [onTertiaryLongPressDown] has previously been triggered.
///
/// If this recognizer wins the arena, [onTertiaryLongPressStart] and
/// [onTertiaryLongPress] are called instead.
///
/// If the gesture is deactivated due to [postAcceptSlopTolerance] having
/// been exceeded, this callback will not be called, since the gesture will
/// have already won the arena at that point.
///
/// See also:
///
/// * [kTertiaryButton], the button this callback responds to.
GestureLongPressCancelCallback? onTertiaryLongPressCancel;
/// Called when a long press gesture by a tertiary button has been /// Called when a long press gesture by a tertiary button has been
/// recognized. /// recognized.
/// ///
/// This is equivalent to (and is called immediately after)
/// [onTertiaryLongPressStart]. The only difference between the two is that
/// this callback does not contain details of the position at which the
/// pointer initially contacted the screen.
///
/// See also: /// See also:
/// ///
/// * [kTertiaryButton], the button this callback responds to. /// * [kTertiaryButton], the button this callback responds to.
/// * [onTertiaryLongPressStart], which has the same timing but has data for
/// the press location.
GestureLongPressCallback? onTertiaryLongPress; GestureLongPressCallback? onTertiaryLongPress;
/// Called when a long press gesture by a tertiary button has been recognized. /// Called when a long press gesture by a tertiary button has been recognized.
/// ///
/// This is equivalent to (and is called immediately before)
/// [onTertiaryLongPress]. The only difference between the two is that this
/// callback contains details of the position at which the pointer initially
/// contacted the screen, whereas [onTertiaryLongPress] does not.
///
/// See also: /// See also:
/// ///
/// * [kTertiaryButton], the button this callback responds to. /// * [kTertiaryButton], the button this callback responds to.
/// * [onTertiaryLongPress], which has the same timing but without details.
/// * [LongPressStartDetails], which is passed as an argument to this /// * [LongPressStartDetails], which is passed as an argument to this
/// callback. /// callback.
GestureLongPressStartCallback? onTertiaryLongPressStart; GestureLongPressStartCallback? onTertiaryLongPressStart;
...@@ -310,21 +533,27 @@ class LongPressGestureRecognizer extends PrimaryPointerGestureRecognizer { ...@@ -310,21 +533,27 @@ class LongPressGestureRecognizer extends PrimaryPointerGestureRecognizer {
/// Called when the pointer stops contacting the screen after a long-press by /// Called when the pointer stops contacting the screen after a long-press by
/// a tertiary button. /// a tertiary button.
/// ///
/// This is equivalent to (and is called immediately after)
/// [onTertiaryLongPressEnd]. The only difference between the two is that
/// this callback does not contain details of the state of the pointer when
/// it stopped contacting the screen.
///
/// See also: /// See also:
/// ///
/// * [kTertiaryButton], the button this callback responds to. /// * [kTertiaryButton], the button this callback responds to.
/// * [onTertiaryLongPressEnd], which has the same timing but has data for
/// the up gesture location.
GestureLongPressUpCallback? onTertiaryLongPressUp; GestureLongPressUpCallback? onTertiaryLongPressUp;
/// Called when the pointer stops contacting the screen after a long-press by /// Called when the pointer stops contacting the screen after a long-press by
/// a tertiary button. /// a tertiary button.
/// ///
/// This is equivalent to (and is called immediately before)
/// [onTertiaryLongPressUp]. The only difference between the two is that
/// this callback contains details of the state of the pointer when it
/// stopped contacting the screen, whereas [onTertiaryLongPressUp] does not.
///
/// See also: /// See also:
/// ///
/// * [kTertiaryButton], the button this callback responds to. /// * [kTertiaryButton], the button this callback responds to.
/// * [onTertiaryLongPressUp], which has the same timing, but without
/// details.
/// * [LongPressEndDetails], which is passed as an argument to this callback. /// * [LongPressEndDetails], which is passed as an argument to this callback.
GestureLongPressEndCallback? onTertiaryLongPressEnd; GestureLongPressEndCallback? onTertiaryLongPressEnd;
...@@ -334,7 +563,9 @@ class LongPressGestureRecognizer extends PrimaryPointerGestureRecognizer { ...@@ -334,7 +563,9 @@ class LongPressGestureRecognizer extends PrimaryPointerGestureRecognizer {
bool isPointerAllowed(PointerDownEvent event) { bool isPointerAllowed(PointerDownEvent event) {
switch (event.buttons) { switch (event.buttons) {
case kPrimaryButton: case kPrimaryButton:
if (onLongPressStart == null && if (onLongPressDown == null &&
onLongPressCancel == null &&
onLongPressStart == null &&
onLongPress == null && onLongPress == null &&
onLongPressMoveUpdate == null && onLongPressMoveUpdate == null &&
onLongPressEnd == null && onLongPressEnd == null &&
...@@ -342,7 +573,9 @@ class LongPressGestureRecognizer extends PrimaryPointerGestureRecognizer { ...@@ -342,7 +573,9 @@ class LongPressGestureRecognizer extends PrimaryPointerGestureRecognizer {
return false; return false;
break; break;
case kSecondaryButton: case kSecondaryButton:
if (onSecondaryLongPressStart == null && if (onSecondaryLongPressDown == null &&
onSecondaryLongPressCancel == null &&
onSecondaryLongPressStart == null &&
onSecondaryLongPress == null && onSecondaryLongPress == null &&
onSecondaryLongPressMoveUpdate == null && onSecondaryLongPressMoveUpdate == null &&
onSecondaryLongPressEnd == null && onSecondaryLongPressEnd == null &&
...@@ -350,7 +583,9 @@ class LongPressGestureRecognizer extends PrimaryPointerGestureRecognizer { ...@@ -350,7 +583,9 @@ class LongPressGestureRecognizer extends PrimaryPointerGestureRecognizer {
return false; return false;
break; break;
case kTertiaryButton: case kTertiaryButton:
if (onTertiaryLongPressStart == null && if (onTertiaryLongPressDown == null &&
onTertiaryLongPressCancel == null &&
onTertiaryLongPressStart == null &&
onTertiaryLongPress == null && onTertiaryLongPress == null &&
onTertiaryLongPressMoveUpdate == null && onTertiaryLongPressMoveUpdate == null &&
onTertiaryLongPressEnd == null && onTertiaryLongPressEnd == null &&
...@@ -394,11 +629,13 @@ class LongPressGestureRecognizer extends PrimaryPointerGestureRecognizer { ...@@ -394,11 +629,13 @@ class LongPressGestureRecognizer extends PrimaryPointerGestureRecognizer {
} }
_reset(); _reset();
} else if (event is PointerCancelEvent) { } else if (event is PointerCancelEvent) {
_checkLongPressCancel();
_reset(); _reset();
} else if (event is PointerDownEvent) { } else if (event is PointerDownEvent) {
// The first touch. // The first touch.
_longPressOrigin = OffsetPair.fromEventPosition(event); _longPressOrigin = OffsetPair.fromEventPosition(event);
_initialButtons = event.buttons; _initialButtons = event.buttons;
_checkLongPressDown(event);
} else if (event is PointerMoveEvent) { } else if (event is PointerMoveEvent) {
if (event.buttons != _initialButtons) { if (event.buttons != _initialButtons) {
resolve(GestureDisposition.rejected); resolve(GestureDisposition.rejected);
...@@ -409,6 +646,52 @@ class LongPressGestureRecognizer extends PrimaryPointerGestureRecognizer { ...@@ -409,6 +646,52 @@ class LongPressGestureRecognizer extends PrimaryPointerGestureRecognizer {
} }
} }
void _checkLongPressDown(PointerDownEvent event) {
assert(_longPressOrigin != null);
final LongPressDownDetails details = LongPressDownDetails(
globalPosition: _longPressOrigin!.global,
localPosition: _longPressOrigin!.local,
kind: getKindForPointer(event.pointer),
);
switch (_initialButtons) {
case kPrimaryButton:
if (onLongPressDown != null)
invokeCallback<void>('onLongPressDown', () => onLongPressDown!(details));
break;
case kSecondaryButton:
if (onSecondaryLongPressDown != null)
invokeCallback<void>('onSecondaryLongPressDown', () => onSecondaryLongPressDown!(details));
break;
case kTertiaryButton:
if (onTertiaryLongPressDown != null)
invokeCallback<void>('onTertiaryLongPressDown', () => onTertiaryLongPressDown!(details));
break;
default:
assert(false, 'Unhandled button $_initialButtons');
}
}
void _checkLongPressCancel() {
if (state == GestureRecognizerState.possible) {
switch (_initialButtons) {
case kPrimaryButton:
if (onLongPressCancel != null)
invokeCallback<void>('onLongPressCancel', onLongPressCancel!);
break;
case kSecondaryButton:
if (onSecondaryLongPressCancel != null)
invokeCallback<void>('onSecondaryLongPressCancel', onSecondaryLongPressCancel!);
break;
case kTertiaryButton:
if (onTertiaryLongPressCancel != null)
invokeCallback<void>('onTertiaryLongPressCancel', onTertiaryLongPressCancel!);
break;
default:
assert(false, 'Unhandled button $_initialButtons');
}
}
}
void _checkLongPressStart() { void _checkLongPressStart() {
switch (_initialButtons) { switch (_initialButtons) {
case kPrimaryButton: case kPrimaryButton:
...@@ -531,10 +814,14 @@ class LongPressGestureRecognizer extends PrimaryPointerGestureRecognizer { ...@@ -531,10 +814,14 @@ class LongPressGestureRecognizer extends PrimaryPointerGestureRecognizer {
@override @override
void resolve(GestureDisposition disposition) { void resolve(GestureDisposition disposition) {
if (_longPressAccepted && disposition == GestureDisposition.rejected) { if (disposition == GestureDisposition.rejected) {
// This can happen if the gesture has been canceled. For example when if (_longPressAccepted) {
// the buttons have changed. // This can happen if the gesture has been canceled. For example when
_reset(); // the buttons have changed.
_reset();
} else {
_checkLongPressCancel();
}
} }
super.resolve(disposition); super.resolve(disposition);
} }
......
...@@ -394,6 +394,7 @@ class TapGestureRecognizer extends BaseTapGestureRecognizer { ...@@ -394,6 +394,7 @@ class TapGestureRecognizer extends BaseTapGestureRecognizer {
/// See also: /// See also:
/// ///
/// * [kPrimaryButton], the button this callback responds to. /// * [kPrimaryButton], the button this callback responds to.
/// * [onSecondaryTap], a similar callback but for a secondary button.
/// * [onTapUp], which has the same timing but with details. /// * [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;
......
...@@ -230,16 +230,27 @@ class GestureDetector extends StatelessWidget { ...@@ -230,16 +230,27 @@ class GestureDetector extends StatelessWidget {
this.onDoubleTapDown, this.onDoubleTapDown,
this.onDoubleTap, this.onDoubleTap,
this.onDoubleTapCancel, this.onDoubleTapCancel,
this.onLongPressDown,
this.onLongPressCancel,
this.onLongPress, this.onLongPress,
this.onLongPressStart, this.onLongPressStart,
this.onLongPressMoveUpdate, this.onLongPressMoveUpdate,
this.onLongPressUp, this.onLongPressUp,
this.onLongPressEnd, this.onLongPressEnd,
this.onSecondaryLongPressDown,
this.onSecondaryLongPressCancel,
this.onSecondaryLongPress, this.onSecondaryLongPress,
this.onSecondaryLongPressStart, this.onSecondaryLongPressStart,
this.onSecondaryLongPressMoveUpdate, this.onSecondaryLongPressMoveUpdate,
this.onSecondaryLongPressUp, this.onSecondaryLongPressUp,
this.onSecondaryLongPressEnd, this.onSecondaryLongPressEnd,
this.onTertiaryLongPressDown,
this.onTertiaryLongPressCancel,
this.onTertiaryLongPress,
this.onTertiaryLongPressStart,
this.onTertiaryLongPressMoveUpdate,
this.onTertiaryLongPressUp,
this.onTertiaryLongPressEnd,
this.onVerticalDragDown, this.onVerticalDragDown,
this.onVerticalDragStart, this.onVerticalDragStart,
this.onVerticalDragUpdate, this.onVerticalDragUpdate,
...@@ -458,15 +469,55 @@ class GestureDetector extends StatelessWidget { ...@@ -458,15 +469,55 @@ class GestureDetector extends StatelessWidget {
/// * [kPrimaryButton], the button this callback responds to. /// * [kPrimaryButton], the button this callback responds to.
final GestureTapCancelCallback? onDoubleTapCancel; final GestureTapCancelCallback? onDoubleTapCancel;
/// The pointer has contacted the screen with a primary button, which might
/// be the start of a long-press.
///
/// This triggers after the pointer down event.
///
/// If the user completes the long-press, and this gesture wins,
/// [onLongPressStart] will be called after this callback. Otherwise,
/// [onLongPressCancel] will be called after this callback.
///
/// See also:
///
/// * [kPrimaryButton], the button this callback responds to.
/// * [onSecondaryLongPressDown], a similar callback but for a secondary button.
/// * [onTertiaryLongPressDown], a similar callback but for a tertiary button.
/// * [LongPressGestureRecognizer.onLongPressDown], which exposes this
/// callback at the gesture layer.
final GestureLongPressDownCallback? onLongPressDown;
/// A pointer that previously triggered [onLongPressDown] will not end up
/// causing a long-press.
///
/// This triggers once the gesture loses if [onLongPressDown] has previously
/// been triggered.
///
/// If the user completed the long-press, and the gesture won, then
/// [onLongPressStart] and [onLongPress] are called instead.
///
/// See also:
///
/// * [kPrimaryButton], the button this callback responds to.
/// * [LongPressGestureRecognizer.onLongPressCancel], which exposes this
/// callback at the gesture layer.
final GestureLongPressCancelCallback? onLongPressCancel;
/// Called when a long press gesture with a primary button has been recognized. /// Called when a long press gesture with a primary button 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.
/// ///
/// This is equivalent to (and is called immediately after) [onLongPressStart].
/// The only difference between the two is that this callback does not
/// contain details of the position at which the pointer initially contacted
/// the screen.
///
/// See also: /// See also:
/// ///
/// * [kPrimaryButton], the button this callback responds to. /// * [kPrimaryButton], the button this callback responds to.
/// * [onLongPressStart], which has the same timing but has gesture details. /// * [LongPressGestureRecognizer.onLongPress], which exposes this
/// callback at the gesture layer.
final GestureLongPressCallback? onLongPress; final GestureLongPressCallback? onLongPress;
/// Called when a long press gesture with a primary button has been recognized. /// Called when a long press gesture with a primary button has been recognized.
...@@ -474,49 +525,107 @@ class GestureDetector extends StatelessWidget { ...@@ -474,49 +525,107 @@ class GestureDetector extends StatelessWidget {
/// 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.
/// ///
/// This is equivalent to (and is called immediately before) [onLongPress].
/// The only difference between the two is that this callback contains
/// details of the position at which the pointer initially contacted the
/// screen, whereas [onLongPress] does not.
///
/// See also: /// See also:
/// ///
/// * [kPrimaryButton], the button this callback responds to. /// * [kPrimaryButton], the button this callback responds to.
/// * [onLongPress], which has the same timing but without the gesture details. /// * [LongPressGestureRecognizer.onLongPressStart], which exposes this
/// callback at the gesture layer.
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 with a primary button.
/// ///
/// See also: /// See also:
/// ///
/// * [kPrimaryButton], the button this callback responds to. /// * [kPrimaryButton], the button this callback responds to.
/// * [LongPressGestureRecognizer.onLongPressMoveUpdate], which exposes this
/// callback at the gesture layer.
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 with a primary button has
/// stopped contacting the screen. /// stopped contacting the screen.
/// ///
/// This is equivalent to (and is called immediately after) [onLongPressEnd].
/// The only difference between the two is that this callback does not
/// contain details of the state of the pointer when it stopped contacting
/// the screen.
///
/// See also: /// See also:
/// ///
/// * [kPrimaryButton], the button this callback responds to. /// * [kPrimaryButton], the button this callback responds to.
/// * [onLongPressEnd], which has the same timing but has gesture details. /// * [LongPressGestureRecognizer.onLongPressUp], which exposes this
/// callback at the gesture layer.
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 with a primary button has
/// stopped contacting the screen. /// stopped contacting the screen.
/// ///
/// This is equivalent to (and is called immediately before) [onLongPressUp].
/// The only difference between the two is that this callback contains
/// details of the state of the pointer when it stopped contacting the
/// screen, whereas [onLongPressUp] does not.
///
/// See also: /// See also:
/// ///
/// * [kPrimaryButton], the button this callback responds to. /// * [kPrimaryButton], the button this callback responds to.
/// * [onLongPressUp], which has the same timing but without the gesture /// * [LongPressGestureRecognizer.onLongPressEnd], which exposes this
/// details. /// callback at the gesture layer.
final GestureLongPressEndCallback? onLongPressEnd; final GestureLongPressEndCallback? onLongPressEnd;
/// The pointer has contacted the screen with a secondary button, which might
/// be the start of a long-press.
///
/// This triggers after the pointer down event.
///
/// If the user completes the long-press, and this gesture wins,
/// [onSecondaryLongPressStart] will be called after this callback. Otherwise,
/// [onSecondaryLongPressCancel] will be called after this callback.
///
/// See also:
///
/// * [kSecondaryButton], the button this callback responds to.
/// * [onLongPressDown], a similar callback but for a secondary button.
/// * [onTertiaryLongPressDown], a similar callback but for a tertiary button.
/// * [LongPressGestureRecognizer.onSecondaryLongPressDown], which exposes
/// this callback at the gesture layer.
final GestureLongPressDownCallback? onSecondaryLongPressDown;
/// A pointer that previously triggered [onSecondaryLongPressDown] will not
/// end up causing a long-press.
///
/// This triggers once the gesture loses if [onSecondaryLongPressDown] has
/// previously been triggered.
///
/// If the user completed the long-press, and the gesture won, then
/// [onSecondaryLongPressStart] and [onSecondaryLongPress] are called instead.
///
/// See also:
///
/// * [kSecondaryButton], the button this callback responds to.
/// * [LongPressGestureRecognizer.onSecondaryLongPressCancel], which exposes
/// this callback at the gesture layer.
final GestureLongPressCancelCallback? onSecondaryLongPressCancel;
/// Called when a long press gesture with a secondary button has been /// Called when a long press gesture with a secondary button has been
/// recognized. /// 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.
/// ///
/// This is equivalent to (and is called immediately after)
/// [onSecondaryLongPressStart]. The only difference between the two is that
/// this callback does not contain details of the position at which the
/// pointer initially contacted the screen.
///
/// See also: /// See also:
/// ///
/// * [kSecondaryButton], the button this callback responds to. /// * [kSecondaryButton], the button this callback responds to.
/// * [onSecondaryLongPressStart], which has the same timing but has gesture /// * [LongPressGestureRecognizer.onSecondaryLongPress], which exposes
/// details. /// this callback at the gesture layer.
final GestureLongPressCallback? onSecondaryLongPress; final GestureLongPressCallback? onSecondaryLongPress;
/// Called when a long press gesture with a secondary button has been /// Called when a long press gesture with a secondary button has been
...@@ -525,11 +634,16 @@ class GestureDetector extends StatelessWidget { ...@@ -525,11 +634,16 @@ class GestureDetector extends StatelessWidget {
/// 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.
/// ///
/// This is equivalent to (and is called immediately before)
/// [onSecondaryLongPress]. The only difference between the two is that this
/// callback contains details of the position at which the pointer initially
/// contacted the screen, whereas [onSecondaryLongPress] does not.
///
/// See also: /// See also:
/// ///
/// * [kSecondaryButton], the button this callback responds to. /// * [kSecondaryButton], the button this callback responds to.
/// * [onSecondaryLongPress], which has the same timing but without the /// * [LongPressGestureRecognizer.onSecondaryLongPressStart], which exposes
/// gesture details. /// this callback at the gesture layer.
final GestureLongPressStartCallback? onSecondaryLongPressStart; final GestureLongPressStartCallback? onSecondaryLongPressStart;
/// A pointer has been drag-moved after a long press with a secondary button. /// A pointer has been drag-moved after a long press with a secondary button.
...@@ -537,28 +651,149 @@ class GestureDetector extends StatelessWidget { ...@@ -537,28 +651,149 @@ class GestureDetector extends StatelessWidget {
/// See also: /// See also:
/// ///
/// * [kSecondaryButton], the button this callback responds to. /// * [kSecondaryButton], the button this callback responds to.
/// * [LongPressGestureRecognizer.onSecondaryLongPressMoveUpdate], which exposes
/// this callback at the gesture layer.
final GestureLongPressMoveUpdateCallback? onSecondaryLongPressMoveUpdate; final GestureLongPressMoveUpdateCallback? onSecondaryLongPressMoveUpdate;
/// A pointer that has triggered a long-press with a secondary button has /// A pointer that has triggered a long-press with a secondary button has
/// stopped contacting the screen. /// stopped contacting the screen.
/// ///
/// This is equivalent to (and is called immediately after)
/// [onSecondaryLongPressEnd]. The only difference between the two is that
/// this callback does not contain details of the state of the pointer when
/// it stopped contacting the screen.
///
/// See also: /// See also:
/// ///
/// * [kSecondaryButton], the button this callback responds to. /// * [kSecondaryButton], the button this callback responds to.
/// * [onSecondaryLongPressEnd], which has the same timing but has gesture /// * [LongPressGestureRecognizer.onSecondaryLongPressUp], which exposes
/// details. /// this callback at the gesture layer.
final GestureLongPressUpCallback? onSecondaryLongPressUp; final GestureLongPressUpCallback? onSecondaryLongPressUp;
/// A pointer that has triggered a long-press with a secondary button has /// A pointer that has triggered a long-press with a secondary button has
/// stopped contacting the screen. /// stopped contacting the screen.
/// ///
/// This is equivalent to (and is called immediately before)
/// [onSecondaryLongPressUp]. The only difference between the two is that
/// this callback contains details of the state of the pointer when it
/// stopped contacting the screen, whereas [onSecondaryLongPressUp] does not.
///
/// See also: /// See also:
/// ///
/// * [kSecondaryButton], the button this callback responds to. /// * [kSecondaryButton], the button this callback responds to.
/// * [onSecondaryLongPressUp], which has the same timing but without the /// * [LongPressGestureRecognizer.onSecondaryLongPressEnd], which exposes
/// gesture details. /// this callback at the gesture layer.
final GestureLongPressEndCallback? onSecondaryLongPressEnd; final GestureLongPressEndCallback? onSecondaryLongPressEnd;
/// The pointer has contacted the screen with a tertiary button, which might
/// be the start of a long-press.
///
/// This triggers after the pointer down event.
///
/// If the user completes the long-press, and this gesture wins,
/// [onTertiaryLongPressStart] will be called after this callback. Otherwise,
/// [onTertiaryLongPressCancel] will be called after this callback.
///
/// See also:
///
/// * [kTertiaryButton], the button this callback responds to.
/// * [onLongPressDown], a similar callback but for a primary button.
/// * [onSecondaryLongPressDown], a similar callback but for a secondary button.
/// * [LongPressGestureRecognizer.onTertiaryLongPressDown], which exposes
/// this callback at the gesture layer.
final GestureLongPressDownCallback? onTertiaryLongPressDown;
/// A pointer that previously triggered [onTertiaryLongPressDown] will not
/// end up causing a long-press.
///
/// This triggers once the gesture loses if [onTertiaryLongPressDown] has
/// previously been triggered.
///
/// If the user completed the long-press, and the gesture won, then
/// [onTertiaryLongPressStart] and [onTertiaryLongPress] are called instead.
///
/// See also:
///
/// * [kTertiaryButton], the button this callback responds to.
/// * [LongPressGestureRecognizer.onTertiaryLongPressCancel], which exposes
/// this callback at the gesture layer.
final GestureLongPressCancelCallback? onTertiaryLongPressCancel;
/// Called when a long press gesture with a tertiary button has been
/// recognized.
///
/// Triggered when a pointer has remained in contact with the screen at the
/// same location for a long period of time.
///
/// This is equivalent to (and is called immediately after)
/// [onTertiaryLongPressStart]. The only difference between the two is that
/// this callback does not contain details of the position at which the
/// pointer initially contacted the screen.
///
/// See also:
///
/// * [kTertiaryButton], the button this callback responds to.
/// * [LongPressGestureRecognizer.onTertiaryLongPress], which exposes
/// this callback at the gesture layer.
final GestureLongPressCallback? onTertiaryLongPress;
/// Called when a long press gesture with a tertiary button has been
/// recognized.
///
/// Triggered when a pointer has remained in contact with the screen at the
/// same location for a long period of time.
///
/// This is equivalent to (and is called immediately before)
/// [onTertiaryLongPress]. The only difference between the two is that this
/// callback contains details of the position at which the pointer initially
/// contacted the screen, whereas [onTertiaryLongPress] does not.
///
/// See also:
///
/// * [kTertiaryButton], the button this callback responds to.
/// * [LongPressGestureRecognizer.onTertiaryLongPressStart], which exposes
/// this callback at the gesture layer.
final GestureLongPressStartCallback? onTertiaryLongPressStart;
/// A pointer has been drag-moved after a long press with a tertiary button.
///
/// See also:
///
/// * [kTertiaryButton], the button this callback responds to.
/// * [LongPressGestureRecognizer.onTertiaryLongPressMoveUpdate], which exposes
/// this callback at the gesture layer.
final GestureLongPressMoveUpdateCallback? onTertiaryLongPressMoveUpdate;
/// A pointer that has triggered a long-press with a tertiary button has
/// stopped contacting the screen.
///
/// This is equivalent to (and is called immediately after)
/// [onTertiaryLongPressEnd]. The only difference between the two is that
/// this callback does not contain details of the state of the pointer when
/// it stopped contacting the screen.
///
/// See also:
///
/// * [kTertiaryButton], the button this callback responds to.
/// * [LongPressGestureRecognizer.onTertiaryLongPressUp], which exposes
/// this callback at the gesture layer.
final GestureLongPressUpCallback? onTertiaryLongPressUp;
/// A pointer that has triggered a long-press with a tertiary button has
/// stopped contacting the screen.
///
/// This is equivalent to (and is called immediately before)
/// [onTertiaryLongPressUp]. The only difference between the two is that
/// this callback contains details of the state of the pointer when it
/// stopped contacting the screen, whereas [onTertiaryLongPressUp] does not.
///
/// See also:
///
/// * [kTertiaryButton], the button this callback responds to.
/// * [LongPressGestureRecognizer.onTertiaryLongPressEnd], which exposes
/// this callback at the gesture layer.
final GestureLongPressEndCallback? onTertiaryLongPressEnd;
/// A pointer has contacted the screen with a primary button and might begin /// A pointer has contacted the screen with a primary button and might begin
/// to move vertically. /// to move vertically.
/// ///
...@@ -804,30 +1039,52 @@ class GestureDetector extends StatelessWidget { ...@@ -804,30 +1039,52 @@ class GestureDetector extends StatelessWidget {
); );
} }
if (onLongPress != null || if (onLongPressDown != null ||
onLongPressUp != null || onLongPressCancel != null ||
onLongPress != null ||
onLongPressStart != null || onLongPressStart != null ||
onLongPressMoveUpdate != null || onLongPressMoveUpdate != null ||
onLongPressUp != null ||
onLongPressEnd != null || onLongPressEnd != null ||
onSecondaryLongPressDown != null ||
onSecondaryLongPressCancel != null ||
onSecondaryLongPress != null || onSecondaryLongPress != null ||
onSecondaryLongPressUp != null ||
onSecondaryLongPressStart != null || onSecondaryLongPressStart != null ||
onSecondaryLongPressMoveUpdate != null || onSecondaryLongPressMoveUpdate != null ||
onSecondaryLongPressEnd != null) { onSecondaryLongPressUp != null ||
onSecondaryLongPressEnd != null ||
onTertiaryLongPressDown != null ||
onTertiaryLongPressCancel != null ||
onTertiaryLongPress != null ||
onTertiaryLongPressStart != null ||
onTertiaryLongPressMoveUpdate != null ||
onTertiaryLongPressUp != null ||
onTertiaryLongPressEnd != null) {
gestures[LongPressGestureRecognizer] = GestureRecognizerFactoryWithHandlers<LongPressGestureRecognizer>( gestures[LongPressGestureRecognizer] = GestureRecognizerFactoryWithHandlers<LongPressGestureRecognizer>(
() => LongPressGestureRecognizer(debugOwner: this), () => LongPressGestureRecognizer(debugOwner: this),
(LongPressGestureRecognizer instance) { (LongPressGestureRecognizer instance) {
instance instance
..onLongPressDown = onLongPressDown
..onLongPressCancel = onLongPressCancel
..onLongPress = onLongPress ..onLongPress = onLongPress
..onLongPressStart = onLongPressStart ..onLongPressStart = onLongPressStart
..onLongPressMoveUpdate = onLongPressMoveUpdate ..onLongPressMoveUpdate = onLongPressMoveUpdate
..onLongPressEnd = onLongPressEnd
..onLongPressUp = onLongPressUp ..onLongPressUp = onLongPressUp
..onLongPressEnd = onLongPressEnd
..onSecondaryLongPressDown = onSecondaryLongPressDown
..onSecondaryLongPressCancel = onSecondaryLongPressCancel
..onSecondaryLongPress = onSecondaryLongPress ..onSecondaryLongPress = onSecondaryLongPress
..onSecondaryLongPressStart = onSecondaryLongPressStart ..onSecondaryLongPressStart = onSecondaryLongPressStart
..onSecondaryLongPressMoveUpdate = onSecondaryLongPressMoveUpdate ..onSecondaryLongPressMoveUpdate = onSecondaryLongPressMoveUpdate
..onSecondaryLongPressUp = onSecondaryLongPressUp
..onSecondaryLongPressEnd = onSecondaryLongPressEnd ..onSecondaryLongPressEnd = onSecondaryLongPressEnd
..onSecondaryLongPressUp = onSecondaryLongPressUp; ..onTertiaryLongPressDown = onTertiaryLongPressDown
..onTertiaryLongPressCancel = onTertiaryLongPressCancel
..onTertiaryLongPress = onTertiaryLongPress
..onTertiaryLongPressStart = onTertiaryLongPressStart
..onTertiaryLongPressMoveUpdate = onTertiaryLongPressMoveUpdate
..onTertiaryLongPressUp = onTertiaryLongPressUp
..onTertiaryLongPressEnd = onTertiaryLongPressEnd;
}, },
); );
} }
...@@ -1029,7 +1286,8 @@ class RawGestureDetector extends StatefulWidget { ...@@ -1029,7 +1286,8 @@ class RawGestureDetector extends StatefulWidget {
/// * During a semantic tap, it calls [TapGestureRecognizer]'s /// * During a semantic tap, it calls [TapGestureRecognizer]'s
/// `onTapDown`, `onTapUp`, and `onTap`. /// `onTapDown`, `onTapUp`, and `onTap`.
/// * During a semantic long press, it calls [LongPressGestureRecognizer]'s /// * During a semantic long press, it calls [LongPressGestureRecognizer]'s
/// `onLongPressStart`, `onLongPress`, `onLongPressEnd` and `onLongPressUp`. /// `onLongPressDown`, `onLongPressStart`, `onLongPress`, `onLongPressEnd`
/// and `onLongPressUp`.
/// * During a semantic horizontal drag, it calls [HorizontalDragGestureRecognizer]'s /// * During a semantic horizontal drag, it calls [HorizontalDragGestureRecognizer]'s
/// `onDown`, `onStart`, `onUpdate` and `onEnd`, then /// `onDown`, `onStart`, `onUpdate` and `onEnd`, then
/// [PanGestureRecognizer]'s `onDown`, `onStart`, `onUpdate` and `onEnd`. /// [PanGestureRecognizer]'s `onDown`, `onStart`, `onUpdate` and `onEnd`.
...@@ -1344,6 +1602,7 @@ class _DefaultSemanticsGestureDelegate extends SemanticsGestureDelegate { ...@@ -1344,6 +1602,7 @@ class _DefaultSemanticsGestureDelegate extends SemanticsGestureDelegate {
return null; return null;
return () { return () {
longPress.onLongPressDown?.call(const LongPressDownDetails());
longPress.onLongPressStart?.call(const LongPressStartDetails()); longPress.onLongPressStart?.call(const LongPressStartDetails());
longPress.onLongPress?.call(); longPress.onLongPress?.call();
longPress.onLongPressEnd?.call(const LongPressEndDetails()); longPress.onLongPressEnd?.call(const LongPressEndDetails());
......
...@@ -62,344 +62,342 @@ void main() { ...@@ -62,344 +62,342 @@ void main() {
setUp(ensureGestureBinding); setUp(ensureGestureBinding);
group('Long press', () { group('Long press', () {
late LongPressGestureRecognizer longPress; late LongPressGestureRecognizer gesture;
late bool longPressDown; late List<String> recognized;
late bool longPressUp;
void setUpHandlers() {
gesture
..onLongPressDown = (LongPressDownDetails details) {
recognized.add('down');
}
..onLongPressCancel = () {
recognized.add('cancel');
}
..onLongPress = () {
recognized.add('start');
}
..onLongPressMoveUpdate = (LongPressMoveUpdateDetails details) {
recognized.add('move');
}
..onLongPressUp = () {
recognized.add('end');
};
}
setUp(() { setUp(() {
longPress = LongPressGestureRecognizer(); recognized = <String>[];
longPressDown = false; gesture = LongPressGestureRecognizer();
longPress.onLongPress = () { setUpHandlers();
longPressDown = true;
};
longPressUp = false;
longPress.onLongPressUp = () {
longPressUp = true;
};
}); });
testGesture('Should recognize long press', (GestureTester tester) { testGesture('Should recognize long press', (GestureTester tester) {
longPress.addPointer(down); gesture.addPointer(down);
tester.closeArena(5); tester.closeArena(5);
expect(longPressDown, isFalse); expect(recognized, const <String>[]);
tester.route(down); tester.route(down);
expect(longPressDown, isFalse); expect(recognized, const <String>['down']);
tester.async.elapse(const Duration(milliseconds: 300)); tester.async.elapse(const Duration(milliseconds: 300));
expect(longPressDown, isFalse); expect(recognized, const <String>['down']);
tester.async.elapse(const Duration(milliseconds: 700)); tester.async.elapse(const Duration(milliseconds: 700));
expect(longPressDown, isTrue); expect(recognized, const <String>['down', 'start']);
gesture.dispose();
longPress.dispose(); expect(recognized, const <String>['down', 'start']);
}); });
testGesture('Should recognize long press with altered duration', (GestureTester tester) { testGesture('Should recognize long press with altered duration', (GestureTester tester) {
longPress = LongPressGestureRecognizer(duration: const Duration(milliseconds: 100)); gesture = LongPressGestureRecognizer(duration: const Duration(milliseconds: 100));
longPressDown = false; setUpHandlers();
longPress.onLongPress = () { gesture.addPointer(down);
longPressDown = true;
};
longPressUp = false;
longPress.onLongPressUp = () {
longPressUp = true;
};
longPress.addPointer(down);
tester.closeArena(5); tester.closeArena(5);
expect(longPressDown, isFalse); expect(recognized, const <String>[]);
tester.route(down); tester.route(down);
expect(longPressDown, isFalse); expect(recognized, const <String>['down']);
tester.async.elapse(const Duration(milliseconds: 50)); tester.async.elapse(const Duration(milliseconds: 50));
expect(longPressDown, isFalse); expect(recognized, const <String>['down']);
tester.async.elapse(const Duration(milliseconds: 50)); tester.async.elapse(const Duration(milliseconds: 50));
expect(longPressDown, isTrue); expect(recognized, const <String>['down', 'start']);
gesture.dispose();
longPress.dispose(); expect(recognized, const <String>['down', 'start']);
}); });
testGesture('Up cancels long press', (GestureTester tester) { testGesture('Up cancels long press', (GestureTester tester) {
longPress.addPointer(down); gesture.addPointer(down);
tester.closeArena(5); tester.closeArena(5);
expect(longPressDown, isFalse); expect(recognized, const <String>[]);
tester.route(down); tester.route(down);
expect(longPressDown, isFalse); expect(recognized, const <String>['down']);
tester.async.elapse(const Duration(milliseconds: 300)); tester.async.elapse(const Duration(milliseconds: 300));
expect(longPressDown, isFalse); expect(recognized, const <String>['down']);
tester.route(up); tester.route(up);
expect(longPressDown, isFalse); expect(recognized, const <String>['down', 'cancel']);
tester.async.elapse(const Duration(seconds: 1)); tester.async.elapse(const Duration(seconds: 1));
expect(longPressDown, isFalse); gesture.dispose();
expect(recognized, const <String>['down', 'cancel']);
longPress.dispose();
}); });
testGesture('Moving before accept cancels', (GestureTester tester) { testGesture('Moving before accept cancels', (GestureTester tester) {
longPress.addPointer(down); gesture.addPointer(down);
tester.closeArena(5); tester.closeArena(5);
expect(longPressDown, isFalse); expect(recognized, const <String>[]);
tester.route(down); tester.route(down);
expect(longPressDown, isFalse); expect(recognized, const <String>['down']);
tester.async.elapse(const Duration(milliseconds: 300)); tester.async.elapse(const Duration(milliseconds: 300));
expect(longPressDown, isFalse); expect(recognized, const <String>['down']);
tester.route(move); tester.route(move);
expect(longPressDown, isFalse); expect(recognized, const <String>['down', 'cancel']);
tester.async.elapse(const Duration(seconds: 1)); tester.async.elapse(const Duration(seconds: 1));
tester.route(up); tester.route(up);
tester.async.elapse(const Duration(milliseconds: 300)); tester.async.elapse(const Duration(milliseconds: 300));
expect(longPressDown, isFalse); expect(recognized, const <String>['down', 'cancel']);
expect(longPressUp, isFalse); gesture.dispose();
expect(recognized, const <String>['down', 'cancel']);
longPress.dispose();
}); });
testGesture('Moving after accept is ok', (GestureTester tester) { testGesture('Moving after accept is ok', (GestureTester tester) {
longPress.addPointer(down); gesture.addPointer(down);
tester.closeArena(5); tester.closeArena(5);
expect(longPressDown, isFalse); expect(recognized, const <String>[]);
tester.route(down); tester.route(down);
expect(longPressDown, isFalse); expect(recognized, const <String>['down']);
tester.async.elapse(const Duration(seconds: 1)); tester.async.elapse(const Duration(seconds: 1));
expect(longPressDown, isTrue); expect(recognized, const <String>['down', 'start']);
tester.route(move); tester.route(move);
expect(recognized, const <String>['down', 'start', 'move']);
tester.route(up); tester.route(up);
expect(recognized, const <String>['down', 'start', 'move', 'end']);
tester.async.elapse(const Duration(milliseconds: 300)); tester.async.elapse(const Duration(milliseconds: 300));
expect(longPressDown, isTrue); expect(recognized, const <String>['down', 'start', 'move', 'end']);
expect(longPressUp, isTrue); gesture.dispose();
expect(recognized, const <String>['down', 'start', 'move', 'end']);
longPress.dispose();
}); });
testGesture('Should recognize both tap down and long press', (GestureTester tester) { testGesture('Should recognize both tap down and long press', (GestureTester tester) {
final TapGestureRecognizer tap = TapGestureRecognizer(); final TapGestureRecognizer tap = TapGestureRecognizer();
bool tapDownRecognized = false;
tap.onTapDown = (_) { tap.onTapDown = (_) {
tapDownRecognized = true; recognized.add('tap_down');
}; };
tap.addPointer(down); tap.addPointer(down);
longPress.addPointer(down); gesture.addPointer(down);
tester.closeArena(5); tester.closeArena(5);
expect(tapDownRecognized, isFalse); expect(recognized, const <String>[]);
expect(longPressDown, isFalse);
tester.route(down); tester.route(down);
expect(tapDownRecognized, isFalse); expect(recognized, const <String>['down']);
expect(longPressDown, isFalse);
tester.async.elapse(const Duration(milliseconds: 300)); tester.async.elapse(const Duration(milliseconds: 300));
expect(tapDownRecognized, isTrue); expect(recognized, const <String>['down', 'tap_down']);
expect(longPressDown, isFalse);
tester.async.elapse(const Duration(milliseconds: 700)); tester.async.elapse(const Duration(milliseconds: 700));
expect(tapDownRecognized, isTrue); expect(recognized, const <String>['down', 'tap_down', 'start']);
expect(longPressDown, isTrue);
tap.dispose(); tap.dispose();
longPress.dispose(); gesture.dispose();
expect(recognized, const <String>['down', 'tap_down', 'start']);
}); });
testGesture('Drag start delayed by microtask', (GestureTester tester) { testGesture('Drag start delayed by microtask', (GestureTester tester) {
final HorizontalDragGestureRecognizer drag = HorizontalDragGestureRecognizer(); final HorizontalDragGestureRecognizer drag = HorizontalDragGestureRecognizer();
bool isDangerousStack = false; bool isDangerousStack = false;
bool dragStartRecognized = false;
drag.onStart = (DragStartDetails details) { drag.onStart = (DragStartDetails details) {
expect(isDangerousStack, isFalse); expect(isDangerousStack, isFalse);
dragStartRecognized = true; recognized.add('drag_start');
}; };
drag.addPointer(down); drag.addPointer(down);
longPress.addPointer(down); gesture.addPointer(down);
tester.closeArena(5); tester.closeArena(5);
expect(dragStartRecognized, isFalse); expect(recognized, const <String>[]);
expect(longPressDown, isFalse);
tester.route(down); tester.route(down);
expect(dragStartRecognized, isFalse); expect(recognized, const <String>['down']);
expect(longPressDown, isFalse);
tester.async.elapse(const Duration(milliseconds: 300)); tester.async.elapse(const Duration(milliseconds: 300));
expect(dragStartRecognized, isFalse); expect(recognized, const <String>['down']);
expect(longPressDown, isFalse);
isDangerousStack = true; isDangerousStack = true;
longPress.dispose(); gesture.dispose();
isDangerousStack = false; isDangerousStack = false;
expect(dragStartRecognized, isFalse); expect(recognized, const <String>['down', 'cancel']);
expect(longPressDown, isFalse);
tester.async.flushMicrotasks(); tester.async.flushMicrotasks();
expect(dragStartRecognized, isTrue); expect(recognized, const <String>['down', 'cancel', 'drag_start']);
expect(longPressDown, isFalse);
drag.dispose(); drag.dispose();
}); });
testGesture('Should recognize long press up', (GestureTester tester) { testGesture('Should recognize long press up', (GestureTester tester) {
bool longPressUpRecognized = false; gesture.addPointer(down);
longPress.onLongPressUp = () {
longPressUpRecognized = true;
};
longPress.addPointer(down);
tester.closeArena(5); tester.closeArena(5);
expect(longPressUpRecognized, isFalse); expect(recognized, const <String>[]);
tester.route(down); // kLongPressTimeout = 500; tester.route(down); // kLongPressTimeout = 500;
expect(longPressUpRecognized, isFalse); expect(recognized, const <String>['down']);
tester.async.elapse(const Duration(milliseconds: 300)); tester.async.elapse(const Duration(milliseconds: 300));
expect(longPressUpRecognized, isFalse); expect(recognized, const <String>['down']);
tester.async.elapse(const Duration(milliseconds: 700)); tester.async.elapse(const Duration(milliseconds: 700));
expect(recognized, const <String>['down', 'start']);
tester.route(up); tester.route(up);
expect(longPressUpRecognized, isTrue); expect(recognized, const <String>['down', 'start', 'end']);
gesture.dispose();
longPress.dispose(); expect(recognized, const <String>['down', 'start', 'end']);
}); });
testGesture('Should not recognize long press with more than one buttons', (GestureTester tester) { testGesture('Should not recognize long press with more than one buttons', (GestureTester tester) {
longPress.addPointer(const PointerDownEvent( gesture.addPointer(const PointerDownEvent(
pointer: 5, pointer: 5,
kind: PointerDeviceKind.mouse, kind: PointerDeviceKind.mouse,
buttons: kSecondaryMouseButton | kTertiaryButton, buttons: kSecondaryMouseButton | kTertiaryButton,
position: Offset(10, 10), position: Offset(10, 10),
)); ));
tester.closeArena(5); tester.closeArena(5);
expect(longPressDown, isFalse); expect(recognized, const <String>[]);
tester.route(down); tester.route(down);
expect(longPressDown, isFalse); expect(recognized, const <String>[]);
tester.async.elapse(const Duration(milliseconds: 1000)); tester.async.elapse(const Duration(milliseconds: 1000));
expect(longPressDown, isFalse); expect(recognized, const <String>[]);
tester.route(up); tester.route(up);
expect(longPressUp, isFalse); expect(recognized, const <String>[]);
gesture.dispose();
longPress.dispose(); expect(recognized, const <String>[]);
}); });
testGesture('Should cancel long press when buttons change before acceptance', (GestureTester tester) { testGesture('Should cancel long press when buttons change before acceptance', (GestureTester tester) {
longPress.addPointer(down); gesture.addPointer(down);
tester.closeArena(5); tester.closeArena(5);
expect(longPressDown, isFalse); expect(recognized, const <String>[]);
tester.route(down); tester.route(down);
expect(longPressDown, isFalse); expect(recognized, const <String>['down']);
tester.async.elapse(const Duration(milliseconds: 300)); tester.async.elapse(const Duration(milliseconds: 300));
expect(longPressDown, isFalse); expect(recognized, const <String>['down']);
tester.route(const PointerMoveEvent( tester.route(const PointerMoveEvent(
pointer: 5, pointer: 5,
kind: PointerDeviceKind.mouse, kind: PointerDeviceKind.mouse,
buttons: kTertiaryButton, buttons: kTertiaryButton,
position: Offset(10, 10), position: Offset(10, 10),
)); ));
expect(longPressDown, isFalse); expect(recognized, const <String>['down', 'cancel']);
tester.async.elapse(const Duration(milliseconds: 700)); tester.async.elapse(const Duration(milliseconds: 700));
expect(longPressDown, isFalse); expect(recognized, const <String>['down', 'cancel']);
tester.route(up); tester.route(up);
expect(longPressUp, isFalse); expect(recognized, const <String>['down', 'cancel']);
gesture.dispose();
longPress.dispose(); expect(recognized, const <String>['down', 'cancel']);
}); });
testGesture('non-allowed pointer does not inadvertently reset the recognizer', (GestureTester tester) { testGesture('non-allowed pointer does not inadvertently reset the recognizer', (GestureTester tester) {
longPress = LongPressGestureRecognizer(kind: PointerDeviceKind.touch)..onLongPress = () {}; gesture = LongPressGestureRecognizer(kind: PointerDeviceKind.touch);
setUpHandlers();
// Accept a long-press gesture // Accept a long-press gesture
longPress.addPointer(down); gesture.addPointer(down);
tester.closeArena(5); tester.closeArena(5);
tester.route(down); tester.route(down);
tester.async.elapse(const Duration(milliseconds: 500)); tester.async.elapse(const Duration(milliseconds: 500));
expect(recognized, const <String>['down', 'start']);
// Add a non-allowed pointer (doesn't match the kind filter) // Add a non-allowed pointer (doesn't match the kind filter)
longPress.addPointer(const PointerDownEvent( gesture.addPointer(const PointerDownEvent(
pointer: 101, pointer: 101,
kind: PointerDeviceKind.mouse, kind: PointerDeviceKind.mouse,
position: Offset(10, 10), position: Offset(10, 10),
)); ));
expect(recognized, const <String>['down', 'start']);
// Moving the primary pointer should result in a normal event // Moving the primary pointer should result in a normal event
tester.route(const PointerMoveEvent( tester.route(const PointerMoveEvent(
pointer: 5, pointer: 5,
position: Offset(15, 15), position: Offset(15, 15),
)); ));
expect(recognized, const <String>['down', 'start', 'move']);
}); });
}); });
group('long press drag', () { group('long press drag', () {
late LongPressGestureRecognizer longPressDrag; late LongPressGestureRecognizer gesture;
late bool longPressStart;
late bool longPressUp;
Offset? longPressDragUpdate; Offset? longPressDragUpdate;
late List<String> recognized;
void setUpHandlers() {
gesture
..onLongPressDown = (LongPressDownDetails details) {
recognized.add('down');
}
..onLongPressCancel = () {
recognized.add('cancel');
}
..onLongPress = () {
recognized.add('start');
}
..onLongPressMoveUpdate = (LongPressMoveUpdateDetails details) {
recognized.add('move');
longPressDragUpdate = details.globalPosition;
}
..onLongPressUp = () {
recognized.add('end');
};
}
setUp(() { setUp(() {
longPressDrag = LongPressGestureRecognizer(); gesture = LongPressGestureRecognizer();
longPressStart = false; setUpHandlers();
longPressDrag.onLongPressStart = (LongPressStartDetails details) { recognized = <String>[];
longPressStart = true;
};
longPressUp = false;
longPressDrag.onLongPressEnd = (LongPressEndDetails details) {
longPressUp = true;
};
longPressDragUpdate = null;
longPressDrag.onLongPressMoveUpdate = (LongPressMoveUpdateDetails details) {
longPressDragUpdate = details.globalPosition;
};
}); });
testGesture('Should recognize long press down', (GestureTester tester) { testGesture('Should recognize long press down', (GestureTester tester) {
longPressDrag.addPointer(down); gesture.addPointer(down);
tester.closeArena(5); tester.closeArena(5);
expect(longPressStart, isFalse); expect(recognized, const <String>[]);
tester.route(down); tester.route(down);
expect(longPressStart, isFalse); expect(recognized, const <String>['down']);
tester.async.elapse(const Duration(milliseconds: 300)); tester.async.elapse(const Duration(milliseconds: 300));
expect(longPressStart, isFalse); expect(recognized, const <String>['down']);
tester.async.elapse(const Duration(milliseconds: 700)); tester.async.elapse(const Duration(milliseconds: 700));
expect(longPressStart, isTrue); expect(recognized, const <String>['down', 'start']);
gesture.dispose();
longPressDrag.dispose(); expect(recognized, const <String>['down', 'start']);
}); });
testGesture('Short up cancels long press', (GestureTester tester) { testGesture('Short up cancels long press', (GestureTester tester) {
longPressDrag.addPointer(down); gesture.addPointer(down);
tester.closeArena(5); tester.closeArena(5);
expect(longPressStart, isFalse); expect(recognized, const <String>[]);
tester.route(down); tester.route(down);
expect(longPressStart, isFalse); expect(recognized, const <String>['down']);
tester.async.elapse(const Duration(milliseconds: 300)); tester.async.elapse(const Duration(milliseconds: 300));
expect(longPressStart, isFalse); expect(recognized, const <String>['down']);
tester.route(up); tester.route(up);
expect(longPressStart, isFalse); expect(recognized, const <String>['down', 'cancel']);
tester.async.elapse(const Duration(seconds: 1)); tester.async.elapse(const Duration(seconds: 1));
expect(longPressStart, isFalse); expect(recognized, const <String>['down', 'cancel']);
gesture.dispose();
longPressDrag.dispose(); expect(recognized, const <String>['down', 'cancel']);
}); });
testGesture('Moving before accept cancels', (GestureTester tester) { testGesture('Moving before accept cancels', (GestureTester tester) {
longPressDrag.addPointer(down); gesture.addPointer(down);
tester.closeArena(5); tester.closeArena(5);
expect(longPressStart, isFalse); expect(recognized, const <String>[]);
tester.route(down); tester.route(down);
expect(longPressStart, isFalse); expect(recognized, const <String>['down']);
tester.async.elapse(const Duration(milliseconds: 300)); tester.async.elapse(const Duration(milliseconds: 300));
expect(longPressStart, isFalse); expect(recognized, const <String>['down']);
tester.route(move); tester.route(move);
expect(longPressStart, isFalse); expect(recognized, const <String>['down', 'cancel']);
tester.async.elapse(const Duration(seconds: 1)); tester.async.elapse(const Duration(seconds: 1));
tester.route(up); tester.route(up);
tester.async.elapse(const Duration(milliseconds: 300)); tester.async.elapse(const Duration(milliseconds: 300));
expect(longPressStart, isFalse); expect(recognized, const <String>['down', 'cancel']);
expect(longPressUp, isFalse); gesture.dispose();
expect(recognized, const <String>['down', 'cancel']);
longPressDrag.dispose();
}); });
testGesture('Moving after accept does not cancel', (GestureTester tester) { testGesture('Moving after accept does not cancel', (GestureTester tester) {
longPressDrag.addPointer(down); gesture.addPointer(down);
tester.closeArena(5); tester.closeArena(5);
expect(longPressStart, isFalse); expect(recognized, const <String>[]);
tester.route(down); tester.route(down);
expect(longPressStart, isFalse); expect(recognized, const <String>['down']);
tester.async.elapse(const Duration(seconds: 1)); tester.async.elapse(const Duration(seconds: 1));
expect(longPressStart, isTrue); expect(recognized, const <String>['down', 'start']);
tester.route(move); tester.route(move);
expect(recognized, const <String>['down', 'start', 'move']);
expect(longPressDragUpdate, const Offset(100, 200)); expect(longPressDragUpdate, const Offset(100, 200));
tester.route(up); tester.route(up);
tester.async.elapse(const Duration(milliseconds: 300)); tester.async.elapse(const Duration(milliseconds: 300));
expect(longPressStart, isTrue); expect(recognized, const <String>['down', 'start', 'move', 'end']);
expect(longPressUp, isTrue); gesture.dispose();
expect(recognized, const <String>['down', 'start', 'move', 'end']);
longPressDrag.dispose();
}); });
}); });
...@@ -411,12 +409,17 @@ void main() { ...@@ -411,12 +409,17 @@ void main() {
position: Offset(10, 10), position: Offset(10, 10),
); );
late LongPressGestureRecognizer gesture;
final List<String> recognized = <String>[]; final List<String> recognized = <String>[];
late LongPressGestureRecognizer longPress;
setUp(() { setUp(() {
longPress = LongPressGestureRecognizer() gesture = LongPressGestureRecognizer()
..onLongPressDown = (LongPressDownDetails details) {
recognized.add('down');
}
..onLongPressCancel = () {
recognized.add('cancel');
}
..onLongPressStart = (LongPressStartDetails details) { ..onLongPressStart = (LongPressStartDetails details) {
recognized.add('start'); recognized.add('start');
} }
...@@ -426,40 +429,43 @@ void main() { ...@@ -426,40 +429,43 @@ void main() {
}); });
tearDown(() { tearDown(() {
longPress.dispose(); gesture.dispose();
recognized.clear(); recognized.clear();
}); });
testGesture('Should cancel long press when buttons change before acceptance', (GestureTester tester) { testGesture('Should cancel long press when buttons change before acceptance', (GestureTester tester) {
// First press // First press
longPress.addPointer(down); gesture.addPointer(down);
tester.closeArena(down.pointer); tester.closeArena(down.pointer);
tester.route(down); tester.route(down);
tester.async.elapse(const Duration(milliseconds: 300)); tester.async.elapse(const Duration(milliseconds: 300));
tester.route(moveR); tester.route(moveR);
expect(recognized, <String>[]); expect(recognized, const <String>['down', 'cancel']);
tester.async.elapse(const Duration(milliseconds: 700)); tester.async.elapse(const Duration(milliseconds: 700));
tester.route(up); tester.route(up);
expect(recognized, <String>[]); expect(recognized, const <String>['down', 'cancel']);
}); });
testGesture('Buttons change before acceptance should not prevent the next long press', (GestureTester tester) { testGesture('Buttons change before acceptance should not prevent the next long press', (GestureTester tester) {
// First press // First press
longPress.addPointer(down); gesture.addPointer(down);
tester.closeArena(down.pointer); tester.closeArena(down.pointer);
tester.route(down); tester.route(down);
expect(recognized, <String>['down']);
tester.async.elapse(const Duration(milliseconds: 300)); tester.async.elapse(const Duration(milliseconds: 300));
tester.route(moveR); tester.route(moveR);
expect(recognized, <String>['down', 'cancel']);
tester.async.elapse(const Duration(milliseconds: 700)); tester.async.elapse(const Duration(milliseconds: 700));
tester.route(up); tester.route(up);
recognized.clear(); recognized.clear();
// Second press // Second press
longPress.addPointer(down2); gesture.addPointer(down2);
tester.closeArena(down2.pointer); tester.closeArena(down2.pointer);
tester.route(down2); tester.route(down2);
expect(recognized, <String>['down']);
tester.async.elapse(const Duration(milliseconds: 1000)); tester.async.elapse(const Duration(milliseconds: 1000));
expect(recognized, <String>['start']); expect(recognized, <String>['down', 'start']);
recognized.clear(); recognized.clear();
tester.route(up2); tester.route(up2);
...@@ -468,11 +474,11 @@ void main() { ...@@ -468,11 +474,11 @@ void main() {
testGesture('Should cancel long press when buttons change after acceptance', (GestureTester tester) { testGesture('Should cancel long press when buttons change after acceptance', (GestureTester tester) {
// First press // First press
longPress.addPointer(down); gesture.addPointer(down);
tester.closeArena(down.pointer); tester.closeArena(down.pointer);
tester.route(down); tester.route(down);
tester.async.elapse(const Duration(milliseconds: 1000)); tester.async.elapse(const Duration(milliseconds: 1000));
expect(recognized, <String>['start']); expect(recognized, <String>['down', 'start']);
recognized.clear(); recognized.clear();
tester.route(moveR); tester.route(moveR);
...@@ -483,7 +489,7 @@ void main() { ...@@ -483,7 +489,7 @@ void main() {
testGesture('Buttons change after acceptance should not prevent the next long press', (GestureTester tester) { testGesture('Buttons change after acceptance should not prevent the next long press', (GestureTester tester) {
// First press // First press
longPress.addPointer(down); gesture.addPointer(down);
tester.closeArena(down.pointer); tester.closeArena(down.pointer);
tester.route(down); tester.route(down);
tester.async.elapse(const Duration(milliseconds: 1000)); tester.async.elapse(const Duration(milliseconds: 1000));
...@@ -492,11 +498,11 @@ void main() { ...@@ -492,11 +498,11 @@ void main() {
recognized.clear(); recognized.clear();
// Second press // Second press
longPress.addPointer(down2); gesture.addPointer(down2);
tester.closeArena(down2.pointer); tester.closeArena(down2.pointer);
tester.route(down2); tester.route(down2);
tester.async.elapse(const Duration(milliseconds: 1000)); tester.async.elapse(const Duration(milliseconds: 1000));
expect(recognized, <String>['start']); expect(recognized, <String>['down', 'start']);
recognized.clear(); recognized.clear();
tester.route(up2); tester.route(up2);
......
...@@ -41,6 +41,7 @@ void main() { ...@@ -41,6 +41,7 @@ void main() {
editable.attach(owner); editable.attach(owner);
// This should register pointer into GestureBinding.instance.pointerRouter. // This should register pointer into GestureBinding.instance.pointerRouter.
editable.handleEvent(const PointerDownEvent(), BoxHitTestEntry(editable, const Offset(10,10))); editable.handleEvent(const PointerDownEvent(), BoxHitTestEntry(editable, const Offset(10,10)));
GestureBinding.instance!.pointerRouter.route(const PointerDownEvent());
expect(spy.routeCount, greaterThan(0)); expect(spy.routeCount, greaterThan(0));
editable.detach(); editable.detach();
expect(spy.routeCount, 0); expect(spy.routeCount, 0);
......
...@@ -922,6 +922,7 @@ void main() { ...@@ -922,6 +922,7 @@ void main() {
// Move back to reset. // Move back to reset.
await dragScrollbarGesture.moveBy(const Offset(0.0, -scrollAmount)); await dragScrollbarGesture.moveBy(const Offset(0.0, -scrollAmount));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
await dragScrollbarGesture.up();
expect(scrollController.offset, 0.0); expect(scrollController.offset, 0.0);
expect( expect(
find.byType(RawScrollbar), find.byType(RawScrollbar),
......
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