Unverified Commit 96b6aafb authored by creativecreatorormaybenot's avatar creativecreatorormaybenot Committed by GitHub

Add onTertiaryTap* to TapGestureRecognizer and GestureDetector (#62788)

parent 4aed9768
...@@ -23,6 +23,8 @@ export 'dart:ui' show Offset, PointerDeviceKind; ...@@ -23,6 +23,8 @@ export 'dart:ui' show Offset, PointerDeviceKind;
/// ///
/// * [kSecondaryButton], which describes a cross-device behavior of /// * [kSecondaryButton], which describes a cross-device behavior of
/// "secondary operation". /// "secondary operation".
/// * [kTertiaryButton], which describes a cross-device behavior of
/// "tertiary operation".
const int kPrimaryButton = 0x01; const int kPrimaryButton = 0x01;
/// The bit of [PointerEvent.buttons] that corresponds to a cross-device /// The bit of [PointerEvent.buttons] that corresponds to a cross-device
...@@ -37,6 +39,8 @@ const int kPrimaryButton = 0x01; ...@@ -37,6 +39,8 @@ const int kPrimaryButton = 0x01;
/// ///
/// * [kPrimaryButton], which describes a cross-device behavior of /// * [kPrimaryButton], which describes a cross-device behavior of
/// "primary operation". /// "primary operation".
/// * [kTertiaryButton], which describes a cross-device behavior of
/// "tertiary operation".
const int kSecondaryButton = 0x02; 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.
...@@ -81,18 +85,46 @@ const int kStylusContact = kPrimaryButton; ...@@ -81,18 +85,46 @@ const int kStylusContact = kPrimaryButton;
/// concept. /// concept.
const int kPrimaryStylusButton = kSecondaryButton; const int kPrimaryStylusButton = kSecondaryButton;
/// The bit of [PointerEvent.buttons] that corresponds to a cross-device
/// behavior of "tertiary operation".
///
/// It is equivalent to:
///
/// * [kMiddleMouseButton]: The tertiary mouseButton.
/// * [kSecondaryStylusButton]: The secondary button on a stylus. This is considered
/// a tertiary button as the primary button of a stylus already corresponds to a
/// "secondary operation" (where stylus contact is the primary operation).
///
/// See also:
///
/// * [kPrimaryButton], which describes a cross-device behavior of
/// "primary operation".
/// * [kSecondaryButton], which describes a cross-device behavior of
/// "secondary operation".
const int kTertiaryButton = 0x04;
/// The bit of [PointerEvent.buttons] that corresponds to the middle mouse button. /// The bit of [PointerEvent.buttons] that corresponds to the middle mouse button.
/// ///
/// The middle mouse button is typically between the left and right buttons on /// The middle mouse button is typically between the left and right buttons on
/// the top of the mouse but can be reconfigured to be a different physical /// the top of the mouse but can be reconfigured to be a different physical
/// button. /// button.
const int kMiddleMouseButton = 0x04; ///
/// See also:
///
/// * [kTertiaryButton], which has the same value but describes its cross-device
/// concept.
const int kMiddleMouseButton = kTertiaryButton;
/// The bit of [PointerEvent.buttons] that corresponds to the secondary stylus button. /// The bit of [PointerEvent.buttons] that corresponds to the secondary stylus button.
/// ///
/// The secondary stylus button is typically on the end of the stylus farthest /// The secondary stylus button is typically on the end of the stylus farthest
/// from the tip but can be reconfigured to be a different physical button. /// from the tip but can be reconfigured to be a different physical button.
const int kSecondaryStylusButton = 0x04; ///
/// See also:
///
/// * [kTertiaryButton], which has the same value but describes its cross-device
/// concept.
const int kSecondaryStylusButton = kTertiaryButton;
/// The bit of [PointerEvent.buttons] that corresponds to the back mouse button. /// The bit of [PointerEvent.buttons] that corresponds to the back mouse button.
/// ///
......
...@@ -143,8 +143,8 @@ class LongPressEndDetails { ...@@ -143,8 +143,8 @@ class LongPressEndDetails {
/// [postAcceptSlopTolerance] constructor argument is specified. /// [postAcceptSlopTolerance] constructor argument is specified.
/// ///
/// [LongPressGestureRecognizer] may compete on pointer events of /// [LongPressGestureRecognizer] may compete on pointer events of
/// [kPrimaryButton] and/or [kSecondaryButton] if at least one corresponding /// [kPrimaryButton], [kSecondaryButton], and/or [kTertiaryButton] if at least
/// callback is non-null. If it has no callbacks, it is a no-op. /// one corresponding callback is non-null. 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.
/// ///
...@@ -277,6 +277,57 @@ class LongPressGestureRecognizer extends PrimaryPointerGestureRecognizer { ...@@ -277,6 +277,57 @@ class LongPressGestureRecognizer extends PrimaryPointerGestureRecognizer {
/// * [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 long press gesture by a tertiary button has been
/// recognized.
///
/// See also:
///
/// * [kTertiaryButton], the button this callback responds to.
/// * [onTertiaryLongPressStart], which has the same timing but has data for
/// the press location.
GestureLongPressCallback? onTertiaryLongPress;
/// Called when a long press gesture by a tertiary button has been recognized.
///
/// See also:
///
/// * [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
/// callback.
GestureLongPressStartCallback? onTertiaryLongPressStart;
/// Called when moving after the long press by a tertiary button is
/// recognized.
///
/// See also:
///
/// * [kTertiaryButton], the button this callback responds to.
/// * [LongPressMoveUpdateDetails], which is passed as an argument to this
/// callback.
GestureLongPressMoveUpdateCallback? onTertiaryLongPressMoveUpdate;
/// Called when the pointer stops contacting the screen after a long-press by
/// a tertiary button.
///
/// See also:
///
/// * [kTertiaryButton], the button this callback responds to.
/// * [onTertiaryLongPressEnd], which has the same timing but has data for
/// the up gesture location.
GestureLongPressUpCallback? onTertiaryLongPressUp;
/// Called when the pointer stops contacting the screen after a long-press by
/// a tertiary button.
///
/// See also:
///
/// * [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.
GestureLongPressEndCallback? onTertiaryLongPressEnd;
VelocityTracker? _velocityTracker; VelocityTracker? _velocityTracker;
@override @override
...@@ -298,6 +349,14 @@ class LongPressGestureRecognizer extends PrimaryPointerGestureRecognizer { ...@@ -298,6 +349,14 @@ class LongPressGestureRecognizer extends PrimaryPointerGestureRecognizer {
onSecondaryLongPressUp == null) onSecondaryLongPressUp == null)
return false; return false;
break; break;
case kTertiaryButton:
if (onTertiaryLongPressStart == null &&
onTertiaryLongPress == null &&
onTertiaryLongPressMoveUpdate == null &&
onTertiaryLongPressEnd == null &&
onTertiaryLongPressUp == null)
return false;
break;
default: default:
return false; return false;
} }
...@@ -377,6 +436,19 @@ class LongPressGestureRecognizer extends PrimaryPointerGestureRecognizer { ...@@ -377,6 +436,19 @@ class LongPressGestureRecognizer extends PrimaryPointerGestureRecognizer {
invokeCallback<void>('onSecondaryLongPress', onSecondaryLongPress!); invokeCallback<void>('onSecondaryLongPress', onSecondaryLongPress!);
} }
break; break;
case kTertiaryButton:
if (onTertiaryLongPressStart != null) {
final LongPressStartDetails details = LongPressStartDetails(
globalPosition: _longPressOrigin!.global,
localPosition: _longPressOrigin!.local,
);
invokeCallback<void>(
'onTertiaryLongPressStart', () => onTertiaryLongPressStart!(details));
}
if (onTertiaryLongPress != null) {
invokeCallback<void>('onTertiaryLongPress', onTertiaryLongPress!);
}
break;
default: default:
assert(false, 'Unhandled button $_initialButtons'); assert(false, 'Unhandled button $_initialButtons');
} }
...@@ -402,6 +474,12 @@ class LongPressGestureRecognizer extends PrimaryPointerGestureRecognizer { ...@@ -402,6 +474,12 @@ class LongPressGestureRecognizer extends PrimaryPointerGestureRecognizer {
() => onSecondaryLongPressMoveUpdate!(details)); () => onSecondaryLongPressMoveUpdate!(details));
} }
break; break;
case kTertiaryButton:
if (onTertiaryLongPressMoveUpdate != null) {
invokeCallback<void>('onTertiaryLongPressMoveUpdate',
() => onTertiaryLongPressMoveUpdate!(details));
}
break;
default: default:
assert(false, 'Unhandled button $_initialButtons'); assert(false, 'Unhandled button $_initialButtons');
} }
...@@ -436,6 +514,14 @@ class LongPressGestureRecognizer extends PrimaryPointerGestureRecognizer { ...@@ -436,6 +514,14 @@ class LongPressGestureRecognizer extends PrimaryPointerGestureRecognizer {
invokeCallback<void>('onSecondaryLongPressUp', onSecondaryLongPressUp!); invokeCallback<void>('onSecondaryLongPressUp', onSecondaryLongPressUp!);
} }
break; break;
case kTertiaryButton:
if (onTertiaryLongPressEnd != null) {
invokeCallback<void>('onTertiaryLongPressEnd', () => onTertiaryLongPressEnd!(details));
}
if (onTertiaryLongPressUp != null) {
invokeCallback<void>('onTertiaryLongPressUp', onTertiaryLongPressUp!);
}
break;
default: default:
assert(false, 'Unhandled button $_initialButtons'); assert(false, 'Unhandled button $_initialButtons');
} }
......
...@@ -160,7 +160,7 @@ abstract class BaseTapGestureRecognizer extends PrimaryPointerGestureRecognizer ...@@ -160,7 +160,7 @@ abstract class BaseTapGestureRecognizer extends PrimaryPointerGestureRecognizer
/// A pointer has stopped contacting the screen, which is recognized as a tap. /// A pointer has stopped contacting the screen, which is recognized as a tap.
/// ///
/// This triggers on the up event, if the recognizer wins the arena with it /// This triggers on the up event if the recognizer wins the arena with it
/// or has previously won. /// or has previously won.
/// ///
/// The parameter `down` is the down event of the primary pointer that started /// The parameter `down` is the down event of the primary pointer that started
...@@ -174,7 +174,7 @@ abstract class BaseTapGestureRecognizer extends PrimaryPointerGestureRecognizer ...@@ -174,7 +174,7 @@ abstract class BaseTapGestureRecognizer extends PrimaryPointerGestureRecognizer
/// A pointer that previously triggered [handleTapDown] will not end up /// A pointer that previously triggered [handleTapDown] will not end up
/// causing a tap. /// causing a tap.
/// ///
/// This triggers once the gesture loses the arena, if [handleTapDown] has /// This triggers once the gesture loses the arena if [handleTapDown] has
/// been previously triggered. /// been previously triggered.
/// ///
/// The parameter `down` is the down event of the primary pointer that started /// The parameter `down` is the down event of the primary pointer that started
...@@ -325,9 +325,11 @@ abstract class BaseTapGestureRecognizer extends PrimaryPointerGestureRecognizer ...@@ -325,9 +325,11 @@ abstract class BaseTapGestureRecognizer extends PrimaryPointerGestureRecognizer
/// 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 /// [TapGestureRecognizer] competes on pointer events of [kPrimaryButton] only
/// when it has at least one non-null `onTap*` callback, and events of /// when it has at least one non-null `onTap*` callback, on events of
/// [kSecondaryButton] only when it has at least one non-null `onSecondaryTap*` /// [kSecondaryButton] only when it has at least one non-null `onSecondaryTap*`
/// callback. If it has no callbacks, it is a no-op. /// callback, and on events of [kTertiaryButton] only when it has at least
/// one non-null `onTertiaryTap*` callback. If it has no callbacks, it is a
/// no-op.
/// ///
/// See also: /// See also:
/// ///
...@@ -350,6 +352,7 @@ class TapGestureRecognizer extends BaseTapGestureRecognizer { ...@@ -350,6 +352,7 @@ class TapGestureRecognizer extends BaseTapGestureRecognizer {
/// ///
/// * [kPrimaryButton], the button this callback responds to. /// * [kPrimaryButton], the button this callback responds to.
/// * [onSecondaryTapDown], a similar callback but for a secondary button. /// * [onSecondaryTapDown], a similar callback but for a secondary button.
/// * [onTertiaryTapDown], a similar callback but for a tertiary button.
/// * [TapDownDetails], which is passed as an argument to this callback. /// * [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;
...@@ -366,6 +369,7 @@ class TapGestureRecognizer extends BaseTapGestureRecognizer { ...@@ -366,6 +369,7 @@ class TapGestureRecognizer extends BaseTapGestureRecognizer {
/// ///
/// * [kPrimaryButton], the button this callback responds to. /// * [kPrimaryButton], the button this callback responds to.
/// * [onSecondaryTapUp], a similar callback but for a secondary button. /// * [onSecondaryTapUp], a similar callback but for a secondary button.
/// * [onTertiaryTapUp], a similar callback but for a tertiary button.
/// * [TapUpDetails], which is passed as an argument to this callback. /// * [TapUpDetails], which is passed as an argument to this callback.
/// * [GestureDetector.onTapUp], which exposes this callback. /// * [GestureDetector.onTapUp], which exposes this callback.
GestureTapUpCallback? onTapUp; GestureTapUpCallback? onTapUp;
...@@ -388,7 +392,7 @@ class TapGestureRecognizer extends BaseTapGestureRecognizer { ...@@ -388,7 +392,7 @@ class TapGestureRecognizer extends BaseTapGestureRecognizer {
/// A pointer that previously triggered [onTapDown] will not end up causing /// A pointer that previously triggered [onTapDown] will not end up causing
/// a tap. /// a tap.
/// ///
/// This triggers once the gesture loses the arena, if [onTapDown] has /// This triggers once the gesture loses the arena if [onTapDown] has
/// previously been triggered. /// previously been triggered.
/// ///
/// If this recognizer wins the arena, [onTapUp] and [onTap] are called /// If this recognizer wins the arena, [onTapUp] and [onTap] are called
...@@ -398,6 +402,7 @@ class TapGestureRecognizer extends BaseTapGestureRecognizer { ...@@ -398,6 +402,7 @@ class TapGestureRecognizer extends BaseTapGestureRecognizer {
/// ///
/// * [kPrimaryButton], the button this callback responds to. /// * [kPrimaryButton], the button this callback responds to.
/// * [onSecondaryTapCancel], a similar callback but for a secondary button. /// * [onSecondaryTapCancel], a similar callback but for a secondary button.
/// * [onTertiaryTapCancel], a similar callback but for a tertiary button.
/// * [GestureDetector.onTapCancel], which exposes this callback. /// * [GestureDetector.onTapCancel], which exposes this callback.
GestureTapCancelCallback? onTapCancel; GestureTapCancelCallback? onTapCancel;
...@@ -430,6 +435,7 @@ class TapGestureRecognizer extends BaseTapGestureRecognizer { ...@@ -430,6 +435,7 @@ class TapGestureRecognizer extends BaseTapGestureRecognizer {
/// ///
/// * [kSecondaryButton], the button this callback responds to. /// * [kSecondaryButton], the button this callback responds to.
/// * [onTapDown], a similar callback but for a primary button. /// * [onTapDown], a similar callback but for a primary button.
/// * [onTertiaryTapDown], a similar callback but for a tertiary button.
/// * [TapDownDetails], which is passed as an argument to this callback. /// * [TapDownDetails], which is passed as an argument to this callback.
/// * [GestureDetector.onSecondaryTapDown], which exposes this callback. /// * [GestureDetector.onSecondaryTapDown], which exposes this callback.
GestureTapDownCallback? onSecondaryTapDown; GestureTapDownCallback? onSecondaryTapDown;
...@@ -437,7 +443,7 @@ class TapGestureRecognizer extends BaseTapGestureRecognizer { ...@@ -437,7 +443,7 @@ class TapGestureRecognizer extends BaseTapGestureRecognizer {
/// A pointer has stopped contacting the screen at a particular location, /// A pointer has stopped contacting the screen at a particular location,
/// which is recognized as a tap of a secondary button. /// which is recognized as a tap of a secondary button.
/// ///
/// This triggers on the up event, if the recognizer wins the arena with it /// This triggers on the up event if the recognizer wins the arena with it
/// or has previously won. /// or has previously won.
/// ///
/// If this recognizer doesn't win the arena, [onSecondaryTapCancel] is called /// If this recognizer doesn't win the arena, [onSecondaryTapCancel] is called
...@@ -449,6 +455,7 @@ class TapGestureRecognizer extends BaseTapGestureRecognizer { ...@@ -449,6 +455,7 @@ class TapGestureRecognizer extends BaseTapGestureRecognizer {
/// pass any details about the tap. /// pass any details about the tap.
/// * [kSecondaryButton], the button this callback responds to. /// * [kSecondaryButton], the button this callback responds to.
/// * [onTapUp], a similar callback but for a primary button. /// * [onTapUp], a similar callback but for a primary button.
/// * [onTertiaryTapUp], a similar callback but for a tertiary button.
/// * [TapUpDetails], which is passed as an argument to this callback. /// * [TapUpDetails], which is passed as an argument to this callback.
/// * [GestureDetector.onSecondaryTapUp], which exposes this callback. /// * [GestureDetector.onSecondaryTapUp], which exposes this callback.
GestureTapUpCallback? onSecondaryTapUp; GestureTapUpCallback? onSecondaryTapUp;
...@@ -456,7 +463,7 @@ class TapGestureRecognizer extends BaseTapGestureRecognizer { ...@@ -456,7 +463,7 @@ class TapGestureRecognizer extends BaseTapGestureRecognizer {
/// A pointer that previously triggered [onSecondaryTapDown] will not end up /// A pointer that previously triggered [onSecondaryTapDown] will not end up
/// causing a tap. /// causing a tap.
/// ///
/// This triggers once the gesture loses the arena, if [onSecondaryTapDown] /// This triggers once the gesture loses the arena if [onSecondaryTapDown]
/// has previously been triggered. /// has previously been triggered.
/// ///
/// If this recognizer wins the arena, [onSecondaryTapUp] is called instead. /// If this recognizer wins the arena, [onSecondaryTapUp] is called instead.
...@@ -465,9 +472,62 @@ class TapGestureRecognizer extends BaseTapGestureRecognizer { ...@@ -465,9 +472,62 @@ class TapGestureRecognizer extends BaseTapGestureRecognizer {
/// ///
/// * [kSecondaryButton], the button this callback responds to. /// * [kSecondaryButton], the button this callback responds to.
/// * [onTapCancel], a similar callback but for a primary button. /// * [onTapCancel], a similar callback but for a primary button.
/// * [GestureDetector.onTapCancel], which exposes this callback. /// * [onTertiaryTapCancel], a similar callback but for a tertiary button.
/// * [GestureDetector.onSecondaryTapCancel], which exposes this callback.
GestureTapCancelCallback? onSecondaryTapCancel; GestureTapCancelCallback? onSecondaryTapCancel;
/// A pointer has contacted the screen at a particular location with a
/// tertiary button, which might be the start of a tertiary tap.
///
/// This triggers after the down event, once a short timeout ([deadline]) has
/// elapsed, or once the gestures has won the arena, whichever comes first.
///
/// If this recognizer doesn't win the arena, [onTertiaryTapCancel] is called
/// next. Otherwise, [onTertiaryTapUp] is called next.
///
/// See also:
///
/// * [kTertiaryButton], the button this callback responds to.
/// * [onTapDown], a similar callback but for a primary button.
/// * [onSecondaryTapDown], a similar callback but for a secondary button.
/// * [TapDownDetails], which is passed as an argument to this callback.
/// * [GestureDetector.onTertiaryTapDown], which exposes this callback.
GestureTapDownCallback? onTertiaryTapDown;
/// A pointer has stopped contacting the screen at a particular location,
/// which is recognized as a tap of a tertiary button.
///
/// This triggers on the up event if the recognizer wins the arena with it
/// or has previously won.
///
/// If this recognizer doesn't win the arena, [onTertiaryTapCancel] is called
/// instead.
///
/// See also:
///
/// * [kTertiaryButton], the button this callback responds to.
/// * [onTapUp], a similar callback but for a primary button.
/// * [onSecondaryTapUp], a similar callback but for a secondary button.
/// * [TapUpDetails], which is passed as an argument to this callback.
/// * [GestureDetector.onTertiaryTapUp], which exposes this callback.
GestureTapUpCallback? onTertiaryTapUp;
/// A pointer that previously triggered [onTertiaryTapDown] will not end up
/// causing a tap.
///
/// This triggers once the gesture loses the arena if [onTertiaryTapDown]
/// has previously been triggered.
///
/// If this recognizer wins the arena, [onTertiaryTapUp] is called instead.
///
/// See also:
///
/// * [kSecondaryButton], the button this callback responds to.
/// * [onTapCancel], a similar callback but for a primary button.
/// * [onSecondaryTapCancel], a similar callback but for a secondary button.
/// * [GestureDetector.onTertiaryTapCancel], which exposes this callback.
GestureTapCancelCallback? onTertiaryTapCancel;
@override @override
bool isPointerAllowed(PointerDownEvent event) { bool isPointerAllowed(PointerDownEvent event) {
switch (event.buttons) { switch (event.buttons) {
...@@ -485,6 +545,12 @@ class TapGestureRecognizer extends BaseTapGestureRecognizer { ...@@ -485,6 +545,12 @@ class TapGestureRecognizer extends BaseTapGestureRecognizer {
onSecondaryTapCancel == null) onSecondaryTapCancel == null)
return false; return false;
break; break;
case kTertiaryButton:
if (onTertiaryTapDown == null &&
onTertiaryTapUp == null &&
onTertiaryTapCancel == null)
return false;
break;
default: default:
return false; return false;
} }
...@@ -508,6 +574,10 @@ class TapGestureRecognizer extends BaseTapGestureRecognizer { ...@@ -508,6 +574,10 @@ class TapGestureRecognizer extends BaseTapGestureRecognizer {
if (onSecondaryTapDown != null) if (onSecondaryTapDown != null)
invokeCallback<void>('onSecondaryTapDown', () => onSecondaryTapDown!(details)); invokeCallback<void>('onSecondaryTapDown', () => onSecondaryTapDown!(details));
break; break;
case kTertiaryButton:
if (onTertiaryTapDown != null)
invokeCallback<void>('onTertiaryTapDown', () => onTertiaryTapDown!(details));
break;
default: default:
} }
} }
...@@ -533,6 +603,10 @@ class TapGestureRecognizer extends BaseTapGestureRecognizer { ...@@ -533,6 +603,10 @@ class TapGestureRecognizer extends BaseTapGestureRecognizer {
if (onSecondaryTap != null) if (onSecondaryTap != null)
invokeCallback<void>('onSecondaryTap', () => onSecondaryTap!()); invokeCallback<void>('onSecondaryTap', () => onSecondaryTap!());
break; break;
case kTertiaryButton:
if (onTertiaryTapUp != null)
invokeCallback<void>('onTertiaryTapUp', () => onTertiaryTapUp!(details));
break;
default: default:
} }
} }
...@@ -550,6 +624,10 @@ class TapGestureRecognizer extends BaseTapGestureRecognizer { ...@@ -550,6 +624,10 @@ class TapGestureRecognizer extends BaseTapGestureRecognizer {
if (onSecondaryTapCancel != null) if (onSecondaryTapCancel != null)
invokeCallback<void>('${note}onSecondaryTapCancel', onSecondaryTapCancel!); invokeCallback<void>('${note}onSecondaryTapCancel', onSecondaryTapCancel!);
break; break;
case kTertiaryButton:
if (onTertiaryTapCancel != null)
invokeCallback<void>('${note}onTertiaryTapCancel', onTertiaryTapCancel!);
break;
default: default:
} }
} }
......
...@@ -226,6 +226,9 @@ class GestureDetector extends StatelessWidget { ...@@ -226,6 +226,9 @@ class GestureDetector extends StatelessWidget {
this.onSecondaryTapDown, this.onSecondaryTapDown,
this.onSecondaryTapUp, this.onSecondaryTapUp,
this.onSecondaryTapCancel, this.onSecondaryTapCancel,
this.onTertiaryTapDown,
this.onTertiaryTapUp,
this.onTertiaryTapCancel,
this.onDoubleTap, this.onDoubleTap,
this.onLongPress, this.onLongPress,
this.onLongPressStart, this.onLongPressStart,
...@@ -391,6 +394,40 @@ class GestureDetector extends StatelessWidget { ...@@ -391,6 +394,40 @@ class GestureDetector extends StatelessWidget {
/// * [kSecondaryButton], the button this callback responds to. /// * [kSecondaryButton], the button this callback responds to.
final GestureTapCancelCallback onSecondaryTapCancel; final GestureTapCancelCallback onSecondaryTapCancel;
/// A pointer that might cause a tap with a tertiary button has contacted the
/// screen at a particular location.
///
/// This is called after a short timeout, even if the winning gesture has not
/// yet been selected. If the tap gesture wins, [onTertiaryTapUp] will be
/// called, otherwise [onTertiaryTapCancel] will be called.
///
/// See also:
///
/// * [kTertiaryButton], the button this callback responds to.
final GestureTapDownCallback onTertiaryTapDown;
/// A pointer that will trigger a tap with a tertiary 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, [onTertiaryTapCancel] is called instead.
///
/// See also:
///
/// * [kTertiaryButton], the button this callback responds to.
final GestureTapUpCallback onTertiaryTapUp;
/// The pointer that previously triggered [onTertiaryTapDown] will not end up
/// causing a tap.
///
/// This is called after [onTertiaryTapDown], and instead of
/// [onTertiaryTapUp], if the tap gesture did not win.
///
/// See also:
///
/// * [kTertiaryButton], the button this callback responds to.
final GestureTapCancelCallback onTertiaryTapCancel;
/// The user has tapped the screen with a primary button at the same location /// The user has tapped the screen with a primary button at the same location
/// twice in quick succession. /// twice in quick succession.
/// ///
...@@ -709,7 +746,10 @@ class GestureDetector extends StatelessWidget { ...@@ -709,7 +746,10 @@ class GestureDetector extends StatelessWidget {
onSecondaryTap != null || onSecondaryTap != null ||
onSecondaryTapDown != null || onSecondaryTapDown != null ||
onSecondaryTapUp != null || onSecondaryTapUp != null ||
onSecondaryTapCancel != null onSecondaryTapCancel != null||
onTertiaryTapDown != null ||
onTertiaryTapUp != null ||
onTertiaryTapCancel != null
) { ) {
gestures[TapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>( gestures[TapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
() => TapGestureRecognizer(debugOwner: this), () => TapGestureRecognizer(debugOwner: this),
...@@ -722,7 +762,10 @@ class GestureDetector extends StatelessWidget { ...@@ -722,7 +762,10 @@ class GestureDetector extends StatelessWidget {
..onSecondaryTap = onSecondaryTap ..onSecondaryTap = onSecondaryTap
..onSecondaryTapDown = onSecondaryTapDown ..onSecondaryTapDown = onSecondaryTapDown
..onSecondaryTapUp = onSecondaryTapUp ..onSecondaryTapUp = onSecondaryTapUp
..onSecondaryTapCancel = onSecondaryTapCancel; ..onSecondaryTapCancel = onSecondaryTapCancel
..onTertiaryTapDown = onTertiaryTapDown
..onTertiaryTapUp = onTertiaryTapUp
..onTertiaryTapCancel = onTertiaryTapCancel;
}, },
); );
} }
......
...@@ -48,6 +48,18 @@ const PointerUpEvent up3 = PointerUpEvent( ...@@ -48,6 +48,18 @@ const PointerUpEvent up3 = PointerUpEvent(
position: Offset(31, 29), position: Offset(31, 29),
); );
// Down/up pair 4: tap sequence with tertiary button
const PointerDownEvent down4 = PointerDownEvent(
pointer: 8,
position: Offset(42, 24),
buttons: kTertiaryButton,
);
const PointerUpEvent up4 = PointerUpEvent(
pointer: 8,
position: Offset(43, 23),
);
void main() { void main() {
setUp(ensureGestureBinding); setUp(ensureGestureBinding);
...@@ -241,7 +253,7 @@ void main() { ...@@ -241,7 +253,7 @@ void main() {
longPress.addPointer(const PointerDownEvent( longPress.addPointer(const PointerDownEvent(
pointer: 5, pointer: 5,
kind: PointerDeviceKind.mouse, kind: PointerDeviceKind.mouse,
buttons: kSecondaryMouseButton | kMiddleMouseButton, buttons: kSecondaryMouseButton | kTertiaryButton,
position: Offset(10, 10), position: Offset(10, 10),
)); ));
tester.closeArena(5); tester.closeArena(5);
...@@ -267,7 +279,7 @@ void main() { ...@@ -267,7 +279,7 @@ void main() {
tester.route(const PointerMoveEvent( tester.route(const PointerMoveEvent(
pointer: 5, pointer: 5,
kind: PointerDeviceKind.mouse, kind: PointerDeviceKind.mouse,
buttons: kMiddleMouseButton, buttons: kTertiaryButton,
position: Offset(10, 10), position: Offset(10, 10),
)); ));
expect(longPressDown, isFalse); expect(longPressDown, isFalse);
...@@ -613,4 +625,66 @@ void main() { ...@@ -613,4 +625,66 @@ void main() {
longPress.dispose(); longPress.dispose();
recognized.clear(); recognized.clear();
}); });
testGesture('A tertiary long press should not trigger primary or secondary', (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');
}
..onSecondaryLongPressStart = (LongPressStartDetails details) {
recognized.add('secondaryStart');
}
..onSecondaryLongPress = () {
recognized.add('secondary');
}
..onSecondaryLongPressMoveUpdate = (LongPressMoveUpdateDetails details) {
recognized.add('secondaryUpdate');
}
..onSecondaryLongPressEnd = (LongPressEndDetails details) {
recognized.add('secondaryEnd');
}
..onSecondaryLongPressUp = () {
recognized.add('secondaryUp');
};
const PointerDownEvent down2 = PointerDownEvent(
pointer: 2,
buttons: kTertiaryButton,
position: Offset(30.0, 30.0),
);
const PointerMoveEvent move2 = PointerMoveEvent(
pointer: 2,
buttons: kTertiaryButton,
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();
});
} }
...@@ -87,6 +87,18 @@ void main() { ...@@ -87,6 +87,18 @@ void main() {
position: Offset(20.0, 20.0), position: Offset(20.0, 20.0),
); );
// Down/up sequence 6: tap sequence with tertiary button
const PointerDownEvent down6 = PointerDownEvent(
pointer: 6,
position: Offset(20.0, 20.0),
buttons: kTertiaryButton,
);
const PointerUpEvent up6 = PointerUpEvent(
pointer: 6,
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();
...@@ -777,6 +789,7 @@ void main() { ...@@ -777,6 +789,7 @@ void main() {
TapGestureRecognizer primary; TapGestureRecognizer primary;
TapGestureRecognizer primary2; TapGestureRecognizer primary2;
TapGestureRecognizer secondary; TapGestureRecognizer secondary;
TapGestureRecognizer tertiary;
setUp(() { setUp(() {
primary = TapGestureRecognizer() primary = TapGestureRecognizer()
..onTapDown = (TapDownDetails details) { ..onTapDown = (TapDownDetails details) {
...@@ -808,6 +821,16 @@ void main() { ...@@ -808,6 +821,16 @@ void main() {
..onSecondaryTapCancel = () { ..onSecondaryTapCancel = () {
recognized.add('secondaryCancel'); recognized.add('secondaryCancel');
}; };
tertiary = TapGestureRecognizer()
..onTertiaryTapDown = (TapDownDetails details) {
recognized.add('tertiaryDown');
}
..onTertiaryTapUp = (TapUpDetails details) {
recognized.add('tertiaryUp');
}
..onTertiaryTapCancel = () {
recognized.add('tertiaryCancel');
};
}); });
tearDown(() { tearDown(() {
...@@ -815,6 +838,7 @@ void main() { ...@@ -815,6 +838,7 @@ void main() {
primary.dispose(); primary.dispose();
primary2.dispose(); primary2.dispose();
secondary.dispose(); secondary.dispose();
tertiary.dispose();
}); });
testGesture('A primary tap recognizer does not form competition with a secondary tap recognizer', (GestureTester tester) { testGesture('A primary tap recognizer does not form competition with a secondary tap recognizer', (GestureTester tester) {
...@@ -830,6 +854,19 @@ void main() { ...@@ -830,6 +854,19 @@ void main() {
expect(recognized, <String>['primaryUp']); expect(recognized, <String>['primaryUp']);
}); });
testGesture('A primary tap recognizer does not form competition with a tertiary tap recognizer', (GestureTester tester) {
primary.addPointer(down1);
tertiary.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 competition with another primary tap recognizer', (GestureTester tester) { testGesture('A primary tap recognizer forms competition with another primary tap recognizer', (GestureTester tester) {
primary.addPointer(down1); primary.addPointer(down1);
primary2.addPointer(down1); primary2.addPointer(down1);
...@@ -852,6 +889,9 @@ void main() { ...@@ -852,6 +889,9 @@ void main() {
const PointerCancelEvent cancel5 = PointerCancelEvent( const PointerCancelEvent cancel5 = PointerCancelEvent(
pointer: 5, pointer: 5,
); );
const PointerCancelEvent cancel6 = PointerCancelEvent(
pointer: 6,
);
setUp(() { setUp(() {
tap = TapGestureRecognizer() tap = TapGestureRecognizer()
...@@ -875,6 +915,15 @@ void main() { ...@@ -875,6 +915,15 @@ void main() {
} }
..onSecondaryTapCancel = () { ..onSecondaryTapCancel = () {
recognized.add('secondaryCancel'); recognized.add('secondaryCancel');
}
..onTertiaryTapDown = (TapDownDetails details) {
recognized.add('tertiaryDown');
}
..onTertiaryTapUp = (TapUpDetails details) {
recognized.add('tertiaryUp');
}
..onTertiaryTapCancel = () {
recognized.add('tertiaryCancel');
}; };
}); });
...@@ -922,6 +971,19 @@ void main() { ...@@ -922,6 +971,19 @@ void main() {
expect(recognized, <String>['secondaryUp']); expect(recognized, <String>['secondaryUp']);
}); });
testGesture('A tertiary tap should trigger tertiary callbacks', (GestureTester tester) {
tap.addPointer(down6);
tester.closeArena(down6.pointer);
expect(recognized, <String>[]);
tester.async.elapse(const Duration(milliseconds: 500));
expect(recognized, <String>['tertiaryDown']);
recognized.clear();
tester.route(up6);
GestureBinding.instance.gestureArena.sweep(down6.pointer);
expect(recognized, <String>['tertiaryUp']);
});
testGesture('A secondary tap cancel should trigger secondary callbacks', (GestureTester tester) { testGesture('A secondary tap cancel should trigger secondary callbacks', (GestureTester tester) {
tap.addPointer(down5); tap.addPointer(down5);
tester.closeArena(down5.pointer); tester.closeArena(down5.pointer);
...@@ -934,6 +996,19 @@ void main() { ...@@ -934,6 +996,19 @@ void main() {
GestureBinding.instance.gestureArena.sweep(down5.pointer); GestureBinding.instance.gestureArena.sweep(down5.pointer);
expect(recognized, <String>['secondaryCancel']); expect(recognized, <String>['secondaryCancel']);
}); });
testGesture('A tertiary tap cancel should trigger tertiary callbacks', (GestureTester tester) {
tap.addPointer(down6);
tester.closeArena(down6.pointer);
expect(recognized, <String>[]);
tester.async.elapse(const Duration(milliseconds: 500));
expect(recognized, <String>['tertiaryDown']);
recognized.clear();
tester.route(cancel6);
GestureBinding.instance.gestureArena.sweep(down6.pointer);
expect(recognized, <String>['tertiaryCancel']);
});
}); });
testGesture('A second tap after rejection is ignored', (GestureTester tester) { testGesture('A second tap after rejection is ignored', (GestureTester tester) {
......
...@@ -379,7 +379,8 @@ void main() { ...@@ -379,7 +379,8 @@ void main() {
..onTapUp = (_) {logs.add('tapUp');} ..onTapUp = (_) {logs.add('tapUp');}
..onTapDown = (_) {logs.add('tapDown');} ..onTapDown = (_) {logs.add('tapDown');}
..onTapCancel = () {logs.add('WRONG');} ..onTapCancel = () {logs.add('WRONG');}
..onSecondaryTapDown = (_) {logs.add('WRONG');}; ..onSecondaryTapDown = (_) {logs.add('WRONG');}
..onTertiaryTapDown = (_) {logs.add('WRONG');};
}, },
), ),
child: Container(), child: Container(),
......
...@@ -129,10 +129,11 @@ void main() { ...@@ -129,10 +129,11 @@ void main() {
group('Tap', () { group('Tap', () {
final ButtonVariant buttonVariant = ButtonVariant( final ButtonVariant buttonVariant = ButtonVariant(
values: <int>[kPrimaryButton, kSecondaryButton], values: <int>[kPrimaryButton, kSecondaryButton, kTertiaryButton],
descriptions: <int, String>{ descriptions: <int, String>{
kPrimaryButton: 'primary', kPrimaryButton: 'primary',
kSecondaryButton: 'secondary', kSecondaryButton: 'secondary',
kTertiaryButton: 'tertiary',
}, },
); );
...@@ -166,6 +167,9 @@ void main() { ...@@ -166,6 +167,9 @@ void main() {
onSecondaryTap: ButtonVariant.button == kSecondaryButton ? () { onSecondaryTap: ButtonVariant.button == kSecondaryButton ? () {
didTap = true; didTap = true;
} : null, } : null,
onTertiaryTapDown: ButtonVariant.button == kTertiaryButton ? (_) {
didTap = true;
} : null,
behavior: behavior, behavior: behavior,
), ),
), ),
...@@ -215,6 +219,9 @@ void main() { ...@@ -215,6 +219,9 @@ void main() {
onSecondaryTap: ButtonVariant.button == kSecondaryButton ? () { onSecondaryTap: ButtonVariant.button == kSecondaryButton ? () {
didTap = true; didTap = true;
} : null, } : null,
onTertiaryTapUp: ButtonVariant.button == kTertiaryButton ? (_) {
didTap = true;
} : null,
), ),
), ),
); );
...@@ -234,6 +241,9 @@ void main() { ...@@ -234,6 +241,9 @@ void main() {
onSecondaryTap: ButtonVariant.button == kSecondaryButton ? () { onSecondaryTap: ButtonVariant.button == kSecondaryButton ? () {
didTap = true; didTap = true;
} : null, } : null,
onTertiaryTapUp: ButtonVariant.button == kTertiaryButton ? (_) {
didTap = true;
} : null,
child: Container(), child: Container(),
), ),
), ),
...@@ -251,6 +261,7 @@ void main() { ...@@ -251,6 +261,7 @@ void main() {
child: GestureDetector( child: GestureDetector(
onTap: ButtonVariant.button == kPrimaryButton ? inputCallback : null, onTap: ButtonVariant.button == kPrimaryButton ? inputCallback : null,
onSecondaryTap: ButtonVariant.button == kSecondaryButton ? inputCallback : null, onSecondaryTap: ButtonVariant.button == kSecondaryButton ? inputCallback : null,
onTertiaryTapUp: ButtonVariant.button == kTertiaryButton ? (_) => inputCallback() : null,
child: Container(), child: Container(),
), ),
), ),
...@@ -263,6 +274,7 @@ void main() { ...@@ -263,6 +274,7 @@ void main() {
child: GestureDetector( child: GestureDetector(
onTap: ButtonVariant.button == kPrimaryButton ? inputCallback : null, onTap: ButtonVariant.button == kPrimaryButton ? inputCallback : null,
onSecondaryTap: ButtonVariant.button == kSecondaryButton ? inputCallback : null, onSecondaryTap: ButtonVariant.button == kSecondaryButton ? inputCallback : null,
onTertiaryTapUp: ButtonVariant.button == kTertiaryButton ? (_) => inputCallback() : null,
child: Container(), child: Container(),
), ),
), ),
...@@ -286,31 +298,64 @@ void main() { ...@@ -286,31 +298,64 @@ void main() {
alignment: Alignment.center, alignment: Alignment.center,
height: 100.0, height: 100.0,
color: const Color(0xFF00FF00), color: const Color(0xFF00FF00),
child: GestureDetector( child: RawGestureDetector(
onTapDown: ButtonVariant.button == kPrimaryButton ? (TapDownDetails details) { behavior: HitTestBehavior.translucent,
tapDown += 1; // Adding long press callbacks here will cause the on*TapDown callbacks to be executed only after
} : null, // kPressTimeout has passed. Without the long press callbacks, there would be no press pointers
onSecondaryTapDown: ButtonVariant.button == kSecondaryButton ? (TapDownDetails details) { // competing in the arena. Hence, we add them to the arena to test this behavior.
tapDown += 1; //
} : null, // We use a raw gesture detector directly here because gesture detector does
onTap: ButtonVariant.button == kPrimaryButton ? () { // not expose callbacks for the tertiary variant of long presses, i.e. no onTertiaryLongPress*
tap += 1; // callbacks are exposed in GestureDetector.
} : null, //
onSecondaryTap: ButtonVariant.button == kSecondaryButton ? () { // The primary and secondary long press callbacks could also be put into the gesture detector below,
tap += 1; // however, it is clearer when they are all in one place.
} : null, gestures: <Type, GestureRecognizerFactory>{
onTapCancel: ButtonVariant.button == kPrimaryButton ? () { LongPressGestureRecognizer: GestureRecognizerFactoryWithHandlers<LongPressGestureRecognizer>(
tapCancel += 1; () => LongPressGestureRecognizer(),
} : null, (LongPressGestureRecognizer instance) {
onSecondaryTapCancel: ButtonVariant.button == kSecondaryButton ? () { instance
tapCancel += 1; ..onLongPress = ButtonVariant.button == kPrimaryButton ? () {
} : null, longPress += 1;
onLongPress: ButtonVariant.button == kPrimaryButton ? () { } : null
longPress += 1; ..onSecondaryLongPress = ButtonVariant.button == kSecondaryButton ? () {
} : null, longPress += 1;
onSecondaryLongPress: ButtonVariant.button == kSecondaryButton ? () { } : null
longPress += 1; ..onTertiaryLongPress = ButtonVariant.button == kTertiaryButton ? () {
} : null, longPress += 1;
} : null;
},
),
},
child: GestureDetector(
onTapDown: ButtonVariant.button == kPrimaryButton ? (TapDownDetails details) {
tapDown += 1;
} : null,
onSecondaryTapDown: ButtonVariant.button == kSecondaryButton ? (TapDownDetails details) {
tapDown += 1;
} : null,
onTertiaryTapDown: ButtonVariant.button == kTertiaryButton ? (TapDownDetails details) {
tapDown += 1;
} : null,
onTap: ButtonVariant.button == kPrimaryButton ? () {
tap += 1;
} : null,
onSecondaryTap: ButtonVariant.button == kSecondaryButton ? () {
tap += 1;
} : null,
onTertiaryTapUp: ButtonVariant.button == kTertiaryButton ? (TapUpDetails details) {
tap += 1;
} : null,
onTapCancel: ButtonVariant.button == kPrimaryButton ? () {
tapCancel += 1;
} : null,
onSecondaryTapCancel: ButtonVariant.button == kSecondaryButton ? () {
tapCancel += 1;
} : null,
onTertiaryTapCancel: ButtonVariant.button == kTertiaryButton ? () {
tapCancel += 1;
} : null,
),
), ),
), ),
), ),
...@@ -358,13 +403,30 @@ void main() { ...@@ -358,13 +403,30 @@ void main() {
alignment: Alignment.center, alignment: Alignment.center,
height: 100.0, height: 100.0,
color: const Color(0xFF00FF00), color: const Color(0xFF00FF00),
child: GestureDetector( child: RawGestureDetector(
onLongPressUp: ButtonVariant.button == kPrimaryButton ? () { // We use a raw gesture detector directly here because gesture detector does
longPressUp += 1; // not expose callbacks for the tertiary variant of long presses, i.e. no onTertiaryLongPress*
} : null, // callbacks are exposed in GestureDetector, and we want to test all three variants.
onSecondaryLongPressUp: ButtonVariant.button == kSecondaryButton ? () { //
longPressUp += 1; // The primary and secondary long press callbacks could also be put into the gesture detector below,
} : null, // however, it is more convenient to have them all in one place.
gestures: <Type, GestureRecognizerFactory>{
LongPressGestureRecognizer: GestureRecognizerFactoryWithHandlers<LongPressGestureRecognizer>(
() => LongPressGestureRecognizer(),
(LongPressGestureRecognizer instance) {
instance
..onLongPressUp = ButtonVariant.button == kPrimaryButton ? () {
longPressUp += 1;
} : null
..onSecondaryLongPressUp = ButtonVariant.button == kSecondaryButton ? () {
longPressUp += 1;
} : null
..onTertiaryLongPressUp = ButtonVariant.button == kTertiaryButton ? () {
longPressUp += 1;
} : null;
},
),
},
), ),
), ),
), ),
......
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