Unverified Commit 6a75dc44 authored by Greg Spencer's avatar Greg Spencer Committed by GitHub

Add onSecondaryTap to gesture recognizer and gesture detector. (#55494)

parent f1c24ed9
...@@ -30,7 +30,7 @@ const int kPrimaryButton = 0x01; ...@@ -30,7 +30,7 @@ const int kPrimaryButton = 0x01;
/// It is equivalent to: /// It is equivalent to:
/// ///
/// * [kPrimaryStylusButton]: The stylus contacts the screen. /// * [kPrimaryStylusButton]: The stylus contacts the screen.
/// * [kSecondaryMouseButton]: The primary mouse button. /// * [kSecondaryMouseButton]: The secondary mouse button.
/// ///
/// See also: /// See also:
/// ///
......
...@@ -141,9 +141,9 @@ class LongPressEndDetails { ...@@ -141,9 +141,9 @@ class LongPressEndDetails {
/// moved, triggering [onLongPressMoveUpdate] callbacks, unless the /// moved, triggering [onLongPressMoveUpdate] callbacks, unless the
/// [postAcceptSlopTolerance] constructor argument is specified. /// [postAcceptSlopTolerance] constructor argument is specified.
/// ///
/// [LongPressGestureRecognizer] competes on pointer events of [kPrimaryButton] /// [LongPressGestureRecognizer] may compete on pointer events of
/// only when it has at least one non-null callback. If it has no callbacks, it /// [kPrimaryButton] and/or [kSecondaryButton] if at least one corresponding
/// is a no-op. /// 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.
/// ///
...@@ -225,6 +225,57 @@ class LongPressGestureRecognizer extends PrimaryPointerGestureRecognizer { ...@@ -225,6 +225,57 @@ class LongPressGestureRecognizer extends PrimaryPointerGestureRecognizer {
/// callback. /// callback.
GestureLongPressEndCallback onLongPressEnd; GestureLongPressEndCallback onLongPressEnd;
/// Called when a long press gesture by a secondary button has been
/// recognized.
///
/// See also:
///
/// * [kSecondaryButton], the button this callback responds to.
/// * [onSecondaryLongPressStart], which has the same timing but has data for
/// the press location.
GestureLongPressCallback onSecondaryLongPress;
/// Called when a long press gesture by a secondary button has been recognized.
///
/// See also:
///
/// * [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
/// callback.
GestureLongPressStartCallback onSecondaryLongPressStart;
/// Called when moving after the long press by a secondary button is
/// recognized.
///
/// See also:
///
/// * [kSecondaryButton], the button this callback responds to.
/// * [LongPressMoveUpdateDetails], which is passed as an argument to this
/// callback.
GestureLongPressMoveUpdateCallback onSecondaryLongPressMoveUpdate;
/// Called when the pointer stops contacting the screen after a long-press by
/// a secondary button.
///
/// See also:
///
/// * [kSecondaryButton], the button this callback responds to.
/// * [onSecondaryLongPressEnd], which has the same timing but has data for
/// the up gesture location.
GestureLongPressUpCallback onSecondaryLongPressUp;
/// Called when the pointer stops contacting the screen after a long-press by
/// a secondary button.
///
/// See also:
///
/// * [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.
GestureLongPressEndCallback onSecondaryLongPressEnd;
VelocityTracker _velocityTracker; VelocityTracker _velocityTracker;
@override @override
...@@ -238,6 +289,14 @@ class LongPressGestureRecognizer extends PrimaryPointerGestureRecognizer { ...@@ -238,6 +289,14 @@ class LongPressGestureRecognizer extends PrimaryPointerGestureRecognizer {
onLongPressUp == null) onLongPressUp == null)
return false; return false;
break; break;
case kSecondaryButton:
if (onSecondaryLongPressStart == null &&
onSecondaryLongPress == null &&
onSecondaryLongPressMoveUpdate == null &&
onSecondaryLongPressEnd == null &&
onSecondaryLongPressUp == null)
return false;
break;
default: default:
return false; return false;
} }
...@@ -291,37 +350,67 @@ class LongPressGestureRecognizer extends PrimaryPointerGestureRecognizer { ...@@ -291,37 +350,67 @@ class LongPressGestureRecognizer extends PrimaryPointerGestureRecognizer {
} }
void _checkLongPressStart() { void _checkLongPressStart() {
assert(_initialButtons == kPrimaryButton); switch (_initialButtons) {
case kPrimaryButton:
if (onLongPressStart != null) { if (onLongPressStart != null) {
final LongPressStartDetails details = LongPressStartDetails( final LongPressStartDetails details = LongPressStartDetails(
globalPosition: _longPressOrigin.global, globalPosition: _longPressOrigin.global,
localPosition: _longPressOrigin.local, localPosition: _longPressOrigin.local,
); );
invokeCallback<void>('onLongPressStart', invokeCallback<void>('onLongPressStart', () => onLongPressStart(details));
() => onLongPressStart(details));
} }
if (onLongPress != null) if (onLongPress != null) {
invokeCallback<void>('onLongPress', onLongPress); invokeCallback<void>('onLongPress', onLongPress);
} }
break;
case kSecondaryButton:
if (onSecondaryLongPressStart != null) {
final LongPressStartDetails details = LongPressStartDetails(
globalPosition: _longPressOrigin.global,
localPosition: _longPressOrigin.local,
);
invokeCallback<void>(
'onSecondaryLongPressStart', () => onSecondaryLongPressStart(details));
}
if (onSecondaryLongPress != null) {
invokeCallback<void>('onSecondaryLongPress', onSecondaryLongPress);
}
break;
default:
assert(false, 'Unhandled button $_initialButtons');
}
}
void _checkLongPressMoveUpdate(PointerEvent event) { void _checkLongPressMoveUpdate(PointerEvent event) {
assert(_initialButtons == kPrimaryButton);
final LongPressMoveUpdateDetails details = LongPressMoveUpdateDetails( final LongPressMoveUpdateDetails details = LongPressMoveUpdateDetails(
globalPosition: event.position, globalPosition: event.position,
localPosition: event.localPosition, localPosition: event.localPosition,
offsetFromOrigin: event.position - _longPressOrigin.global, offsetFromOrigin: event.position - _longPressOrigin.global,
localOffsetFromOrigin: event.localPosition - _longPressOrigin.local, localOffsetFromOrigin: event.localPosition - _longPressOrigin.local,
); );
if (onLongPressMoveUpdate != null) switch (_initialButtons) {
case kPrimaryButton:
if (onLongPressMoveUpdate != null) {
invokeCallback<void>('onLongPressMoveUpdate', invokeCallback<void>('onLongPressMoveUpdate',
() => onLongPressMoveUpdate(details)); () => onLongPressMoveUpdate(details));
} }
break;
case kSecondaryButton:
if (onSecondaryLongPressMoveUpdate != null) {
invokeCallback<void>('onSecondaryLongPressMoveUpdate',
() => onSecondaryLongPressMoveUpdate(details));
}
break;
default:
assert(false, 'Unhandled button $_initialButtons');
}
}
void _checkLongPressEnd(PointerEvent event) { void _checkLongPressEnd(PointerEvent event) {
assert(_initialButtons == kPrimaryButton);
final VelocityEstimate estimate = _velocityTracker.getVelocityEstimate(); final VelocityEstimate estimate = _velocityTracker.getVelocityEstimate();
final Velocity velocity = estimate == null ? Velocity.zero : Velocity(pixelsPerSecond: estimate.pixelsPerSecond); final Velocity velocity = estimate == null
? Velocity.zero
: Velocity(pixelsPerSecond: estimate.pixelsPerSecond);
final LongPressEndDetails details = LongPressEndDetails( final LongPressEndDetails details = LongPressEndDetails(
globalPosition: event.position, globalPosition: event.position,
localPosition: event.localPosition, localPosition: event.localPosition,
...@@ -329,11 +418,27 @@ class LongPressGestureRecognizer extends PrimaryPointerGestureRecognizer { ...@@ -329,11 +418,27 @@ class LongPressGestureRecognizer extends PrimaryPointerGestureRecognizer {
); );
_velocityTracker = null; _velocityTracker = null;
if (onLongPressEnd != null) switch (_initialButtons) {
case kPrimaryButton:
if (onLongPressEnd != null) {
invokeCallback<void>('onLongPressEnd', () => onLongPressEnd(details)); invokeCallback<void>('onLongPressEnd', () => onLongPressEnd(details));
if (onLongPressUp != null) }
if (onLongPressUp != null) {
invokeCallback<void>('onLongPressUp', onLongPressUp); invokeCallback<void>('onLongPressUp', onLongPressUp);
} }
break;
case kSecondaryButton:
if (onSecondaryLongPressEnd != null) {
invokeCallback<void>('onSecondaryLongPressEnd', () => onSecondaryLongPressEnd(details));
}
if (onSecondaryLongPressUp != null) {
invokeCallback<void>('onSecondaryLongPressUp', onSecondaryLongPressUp);
}
break;
default:
assert(false, 'Unhandled button $_initialButtons');
}
}
void _reset() { void _reset() {
_longPressAccepted = false; _longPressAccepted = false;
......
...@@ -369,7 +369,7 @@ class TapGestureRecognizer extends BaseTapGestureRecognizer { ...@@ -369,7 +369,7 @@ class TapGestureRecognizer extends BaseTapGestureRecognizer {
/// of a primary button. /// of a primary 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, immediately following [onTap]. /// or has previously won, immediately following [onTapUp].
/// ///
/// If this recognizer doesn't win the arena, [onTapCancel] is called instead. /// If this recognizer doesn't win the arena, [onTapCancel] is called instead.
/// ///
...@@ -396,6 +396,22 @@ class TapGestureRecognizer extends BaseTapGestureRecognizer { ...@@ -396,6 +396,22 @@ class TapGestureRecognizer extends BaseTapGestureRecognizer {
/// * [GestureDetector.onTapCancel], which exposes this callback. /// * [GestureDetector.onTapCancel], which exposes this callback.
GestureTapCancelCallback onTapCancel; GestureTapCancelCallback onTapCancel;
/// A pointer has stopped contacting the screen, which is recognized as a tap
/// of a secondary button.
///
/// This triggers on the up event, if the recognizer wins the arena with it or
/// has previously won, immediately following [onSecondaryTapUp].
///
/// If this recognizer doesn't win the arena, [onSecondaryTapCancel] is called
/// instead.
///
/// See also:
///
/// * [kSecondaryButton], the button this callback responds to.
/// * [onSecondaryTapUp], which has the same timing but with details.
/// * [GestureDetector.onSecondaryTap], which exposes this callback.
GestureTapCallback onSecondaryTap;
/// A pointer has contacted the screen at a particular location with a /// A pointer has contacted the screen at a particular location with a
/// secondary button, which might be the start of a secondary tap. /// secondary button, which might be the start of a secondary tap.
/// ///
...@@ -424,6 +440,8 @@ class TapGestureRecognizer extends BaseTapGestureRecognizer { ...@@ -424,6 +440,8 @@ class TapGestureRecognizer extends BaseTapGestureRecognizer {
/// ///
/// See also: /// See also:
/// ///
/// * [onSecondaryTap], a handler triggered right after this one that doesn't
/// 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.
/// * [TapUpDetails], which is passed as an argument to this callback. /// * [TapUpDetails], which is passed as an argument to this callback.
...@@ -456,7 +474,8 @@ class TapGestureRecognizer extends BaseTapGestureRecognizer { ...@@ -456,7 +474,8 @@ class TapGestureRecognizer extends BaseTapGestureRecognizer {
return false; return false;
break; break;
case kSecondaryButton: case kSecondaryButton:
if (onSecondaryTapDown == null && if (onSecondaryTap == null &&
onSecondaryTapDown == null &&
onSecondaryTapUp == null && onSecondaryTapUp == null &&
onSecondaryTapCancel == null) onSecondaryTapCancel == null)
return false; return false;
...@@ -482,8 +501,7 @@ class TapGestureRecognizer extends BaseTapGestureRecognizer { ...@@ -482,8 +501,7 @@ class TapGestureRecognizer extends BaseTapGestureRecognizer {
break; break;
case kSecondaryButton: case kSecondaryButton:
if (onSecondaryTapDown != null) if (onSecondaryTapDown != null)
invokeCallback<void>('onSecondaryTapDown', invokeCallback<void>('onSecondaryTapDown', () => onSecondaryTapDown(details));
() => onSecondaryTapDown(details));
break; break;
default: default:
} }
...@@ -505,8 +523,9 @@ class TapGestureRecognizer extends BaseTapGestureRecognizer { ...@@ -505,8 +523,9 @@ class TapGestureRecognizer extends BaseTapGestureRecognizer {
break; break;
case kSecondaryButton: case kSecondaryButton:
if (onSecondaryTapUp != null) if (onSecondaryTapUp != null)
invokeCallback<void>('onSecondaryTapUp', invokeCallback<void>('onSecondaryTapUp', () => onSecondaryTapUp(details));
() => onSecondaryTapUp(details)); if (onSecondaryTap != null)
invokeCallback<void>('onSecondaryTap', () => onSecondaryTap());
break; break;
default: default:
} }
...@@ -523,8 +542,7 @@ class TapGestureRecognizer extends BaseTapGestureRecognizer { ...@@ -523,8 +542,7 @@ class TapGestureRecognizer extends BaseTapGestureRecognizer {
break; break;
case kSecondaryButton: case kSecondaryButton:
if (onSecondaryTapCancel != null) if (onSecondaryTapCancel != null)
invokeCallback<void>('${note}onSecondaryTapCancel', invokeCallback<void>('${note}onSecondaryTapCancel', onSecondaryTapCancel);
onSecondaryTapCancel);
break; break;
default: default:
} }
......
...@@ -189,6 +189,7 @@ class GestureDetector extends StatelessWidget { ...@@ -189,6 +189,7 @@ class GestureDetector extends StatelessWidget {
this.onTapUp, this.onTapUp,
this.onTap, this.onTap,
this.onTapCancel, this.onTapCancel,
this.onSecondaryTap,
this.onSecondaryTapDown, this.onSecondaryTapDown,
this.onSecondaryTapUp, this.onSecondaryTapUp,
this.onSecondaryTapCancel, this.onSecondaryTapCancel,
...@@ -198,6 +199,11 @@ class GestureDetector extends StatelessWidget { ...@@ -198,6 +199,11 @@ class GestureDetector extends StatelessWidget {
this.onLongPressMoveUpdate, this.onLongPressMoveUpdate,
this.onLongPressUp, this.onLongPressUp,
this.onLongPressEnd, this.onLongPressEnd,
this.onSecondaryLongPress,
this.onSecondaryLongPressStart,
this.onSecondaryLongPressMoveUpdate,
this.onSecondaryLongPressUp,
this.onSecondaryLongPressEnd,
this.onVerticalDragDown, this.onVerticalDragDown,
this.onVerticalDragStart, this.onVerticalDragStart,
this.onVerticalDragUpdate, this.onVerticalDragUpdate,
...@@ -304,6 +310,18 @@ class GestureDetector extends StatelessWidget { ...@@ -304,6 +310,18 @@ class GestureDetector extends StatelessWidget {
/// * [kPrimaryButton], the button this callback responds to. /// * [kPrimaryButton], the button this callback responds to.
final GestureTapCancelCallback onTapCancel; final GestureTapCancelCallback onTapCancel;
/// A tap with a secondary button has occurred.
///
/// This triggers when the tap gesture wins. If the tap gesture did not win,
/// [onSecondaryTapCancel] is called instead.
///
/// See also:
///
/// * [kSecondaryButton], the button this callback responds to.
/// * [onSecondaryTapUp], which is called at the same time but includes details
/// regarding the pointer position.
final GestureTapCallback onSecondaryTap;
/// A pointer that might cause a tap with a secondary button has contacted the /// A pointer that might cause a tap with a secondary button has contacted the
/// screen at a particular location. /// screen at a particular location.
/// ///
...@@ -324,6 +342,8 @@ class GestureDetector extends StatelessWidget { ...@@ -324,6 +342,8 @@ class GestureDetector extends StatelessWidget {
/// ///
/// See also: /// See also:
/// ///
/// * [onSecondaryTap], a handler triggered right after this one that doesn't
/// pass any details about the tap.
/// * [kSecondaryButton], the button this callback responds to. /// * [kSecondaryButton], the button this callback responds to.
final GestureTapUpCallback onSecondaryTapUp; final GestureTapUpCallback onSecondaryTapUp;
...@@ -394,6 +414,59 @@ class GestureDetector extends StatelessWidget { ...@@ -394,6 +414,59 @@ class GestureDetector extends StatelessWidget {
/// details. /// details.
final GestureLongPressEndCallback onLongPressEnd; final GestureLongPressEndCallback onLongPressEnd;
/// Called when a long press gesture with a secondary 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.
///
/// See also:
///
/// * [kSecondaryButton], the button this callback responds to.
/// * [onSecondaryLongPressStart], which has the same timing but has gesture
/// details.
final GestureLongPressCallback onSecondaryLongPress;
/// Called when a long press gesture with a secondary 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.
///
/// See also:
///
/// * [kSecondaryButton], the button this callback responds to.
/// * [onSecondaryLongPress], which has the same timing but without the
/// gesture details.
final GestureLongPressStartCallback onSecondaryLongPressStart;
/// A pointer has been drag-moved after a long press with a secondary button.
///
/// See also:
///
/// * [kSecondaryButton], the button this callback responds to.
final GestureLongPressMoveUpdateCallback onSecondaryLongPressMoveUpdate;
/// A pointer that has triggered a long-press with a secondary button has
/// stopped contacting the screen.
///
/// See also:
///
/// * [kSecondaryButton], the button this callback responds to.
/// * [onSecondaryLongPressEnd], which has the same timing but has gesture
/// details.
final GestureLongPressUpCallback onSecondaryLongPressUp;
/// A pointer that has triggered a long-press with a secondary button has
/// stopped contacting the screen.
///
/// See also:
///
/// * [kSecondaryButton], the button this callback responds to.
/// * [onSecondaryLongPressUp], which has the same timing but without the
/// gesture details.
final GestureLongPressEndCallback onSecondaryLongPressEnd;
/// 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.
/// ///
...@@ -601,6 +674,7 @@ class GestureDetector extends StatelessWidget { ...@@ -601,6 +674,7 @@ class GestureDetector extends StatelessWidget {
onTapUp != null || onTapUp != null ||
onTap != null || onTap != null ||
onTapCancel != null || onTapCancel != null ||
onSecondaryTap != null ||
onSecondaryTapDown != null || onSecondaryTapDown != null ||
onSecondaryTapUp != null || onSecondaryTapUp != null ||
onSecondaryTapCancel != null onSecondaryTapCancel != null
...@@ -613,6 +687,7 @@ class GestureDetector extends StatelessWidget { ...@@ -613,6 +687,7 @@ class GestureDetector extends StatelessWidget {
..onTapUp = onTapUp ..onTapUp = onTapUp
..onTap = onTap ..onTap = onTap
..onTapCancel = onTapCancel ..onTapCancel = onTapCancel
..onSecondaryTap = onSecondaryTap
..onSecondaryTapDown = onSecondaryTapDown ..onSecondaryTapDown = onSecondaryTapDown
..onSecondaryTapUp = onSecondaryTapUp ..onSecondaryTapUp = onSecondaryTapUp
..onSecondaryTapCancel = onSecondaryTapCancel; ..onSecondaryTapCancel = onSecondaryTapCancel;
...@@ -647,6 +722,24 @@ class GestureDetector extends StatelessWidget { ...@@ -647,6 +722,24 @@ class GestureDetector extends StatelessWidget {
); );
} }
if (onSecondaryLongPress != null ||
onSecondaryLongPressUp != null ||
onSecondaryLongPressStart != null ||
onSecondaryLongPressMoveUpdate != null ||
onSecondaryLongPressEnd != null) {
gestures[LongPressGestureRecognizer] = GestureRecognizerFactoryWithHandlers<LongPressGestureRecognizer>(
() => LongPressGestureRecognizer(debugOwner: this),
(LongPressGestureRecognizer instance) {
instance
..onSecondaryLongPress = onSecondaryLongPress
..onSecondaryLongPressStart = onSecondaryLongPressStart
..onSecondaryLongPressMoveUpdate = onSecondaryLongPressMoveUpdate
..onSecondaryLongPressEnd =onSecondaryLongPressEnd
..onSecondaryLongPressUp = onSecondaryLongPressUp;
},
);
}
if (onVerticalDragDown != null || if (onVerticalDragDown != null ||
onVerticalDragStart != null || onVerticalDragStart != null ||
onVerticalDragUpdate != null || onVerticalDragUpdate != null ||
...@@ -1110,7 +1203,7 @@ abstract class SemanticsGestureDelegate { ...@@ -1110,7 +1203,7 @@ abstract class SemanticsGestureDelegate {
// For readers who come here to learn how to write custom semantics delegates: // For readers who come here to learn how to write custom semantics delegates:
// this is not a proper sample code. It has access to the detector state as well // this is not a proper sample code. It has access to the detector state as well
// as its private properties, which are inaccessible normally. It is designed // as its private properties, which are inaccessible normally. It is designed
// this way in order to work independenly in a [RawGestureRecognizer] to // this way in order to work independently in a [RawGestureRecognizer] to
// preserve existing behavior. // preserve existing behavior.
// //
// Instead, a normal delegate will store callbacks as properties, and use them // Instead, a normal delegate will store callbacks as properties, and use them
......
...@@ -125,6 +125,15 @@ void main() { ...@@ -125,6 +125,15 @@ void main() {
expect(didEndPan, isTrue); expect(didEndPan, isTrue);
}); });
group('Tap', () {
final ButtonVariant buttonVariant = ButtonVariant(
values: <int>[kPrimaryButton, kSecondaryButton],
descriptions: <int, String>{
kPrimaryButton: 'primary',
kSecondaryButton: 'secondary',
},
);
testWidgets('Translucent', (WidgetTester tester) async { testWidgets('Translucent', (WidgetTester tester) async {
bool didReceivePointerDown; bool didReceivePointerDown;
bool didTap; bool didTap;
...@@ -149,9 +158,12 @@ void main() { ...@@ -149,9 +158,12 @@ void main() {
width: 100.0, width: 100.0,
height: 100.0, height: 100.0,
child: GestureDetector( child: GestureDetector(
onTap: () { onTap: ButtonVariant.button == kPrimaryButton ? () {
didTap = true; didTap = true;
}, } : null,
onSecondaryTap: ButtonVariant.button == kSecondaryButton ? () {
didTap = true;
} : null,
behavior: behavior, behavior: behavior,
), ),
), ),
...@@ -164,57 +176,62 @@ void main() { ...@@ -164,57 +176,62 @@ void main() {
didReceivePointerDown = false; didReceivePointerDown = false;
didTap = false; didTap = false;
await pumpWidgetTree(null); await pumpWidgetTree(null);
await tester.tapAt(const Offset(10.0, 10.0)); await tester.tapAt(const Offset(10.0, 10.0), buttons: ButtonVariant.button);
expect(didReceivePointerDown, isTrue); expect(didReceivePointerDown, isTrue);
expect(didTap, isTrue); expect(didTap, isTrue);
didReceivePointerDown = false; didReceivePointerDown = false;
didTap = false; didTap = false;
await pumpWidgetTree(HitTestBehavior.deferToChild); await pumpWidgetTree(HitTestBehavior.deferToChild);
await tester.tapAt(const Offset(10.0, 10.0)); await tester.tapAt(const Offset(10.0, 10.0), buttons: ButtonVariant.button);
expect(didReceivePointerDown, isTrue); expect(didReceivePointerDown, isTrue);
expect(didTap, isFalse); expect(didTap, isFalse);
didReceivePointerDown = false; didReceivePointerDown = false;
didTap = false; didTap = false;
await pumpWidgetTree(HitTestBehavior.opaque); await pumpWidgetTree(HitTestBehavior.opaque);
await tester.tapAt(const Offset(10.0, 10.0)); await tester.tapAt(const Offset(10.0, 10.0), buttons: ButtonVariant.button);
expect(didReceivePointerDown, isFalse); expect(didReceivePointerDown, isFalse);
expect(didTap, isTrue); expect(didTap, isTrue);
didReceivePointerDown = false; didReceivePointerDown = false;
didTap = false; didTap = false;
await pumpWidgetTree(HitTestBehavior.translucent); await pumpWidgetTree(HitTestBehavior.translucent);
await tester.tapAt(const Offset(10.0, 10.0)); await tester.tapAt(const Offset(10.0, 10.0), buttons: ButtonVariant.button);
expect(didReceivePointerDown, isTrue); expect(didReceivePointerDown, isTrue);
expect(didTap, isTrue); expect(didTap, isTrue);
}, variant: buttonVariant);
});
testWidgets('Empty', (WidgetTester tester) async { testWidgets('Empty', (WidgetTester tester) async {
bool didTap = false; bool didTap = false;
await tester.pumpWidget( await tester.pumpWidget(
Center( Center(
child: GestureDetector( child: GestureDetector(
onTap: () { onTap: ButtonVariant.button == kPrimaryButton ? () {
didTap = true; didTap = true;
}, } : null,
onSecondaryTap: ButtonVariant.button == kSecondaryButton ? () {
didTap = true;
} : null,
), ),
), ),
); );
expect(didTap, isFalse); expect(didTap, isFalse);
await tester.tapAt(const Offset(10.0, 10.0)); await tester.tapAt(const Offset(10.0, 10.0), buttons: ButtonVariant.button);
expect(didTap, isTrue); expect(didTap, isTrue);
}); }, variant: buttonVariant);
testWidgets('Only container', (WidgetTester tester) async { testWidgets('Only container', (WidgetTester tester) async {
bool didTap = false; bool didTap = false;
await tester.pumpWidget( await tester.pumpWidget(
Center( Center(
child: GestureDetector( child: GestureDetector(
onTap: () { onTap: ButtonVariant.button == kPrimaryButton ? () {
didTap = true; didTap = true;
}, } : null,
onSecondaryTap: ButtonVariant.button == kSecondaryButton ? () {
didTap = true;
} : null,
child: Container(), child: Container(),
), ),
), ),
...@@ -222,7 +239,7 @@ void main() { ...@@ -222,7 +239,7 @@ void main() {
expect(didTap, isFalse); expect(didTap, isFalse);
await tester.tapAt(const Offset(10.0, 10.0)); await tester.tapAt(const Offset(10.0, 10.0));
expect(didTap, isFalse); expect(didTap, isFalse);
}); }, variant: buttonVariant);
testWidgets('cache render object', (WidgetTester tester) async { testWidgets('cache render object', (WidgetTester tester) async {
final GestureTapCallback inputCallback = () { }; final GestureTapCallback inputCallback = () { };
...@@ -230,7 +247,8 @@ void main() { ...@@ -230,7 +247,8 @@ void main() {
await tester.pumpWidget( await tester.pumpWidget(
Center( Center(
child: GestureDetector( child: GestureDetector(
onTap: inputCallback, onTap: ButtonVariant.button == kPrimaryButton ? inputCallback : null,
onSecondaryTap: ButtonVariant.button == kSecondaryButton ? inputCallback : null,
child: Container(), child: Container(),
), ),
), ),
...@@ -241,7 +259,8 @@ void main() { ...@@ -241,7 +259,8 @@ void main() {
await tester.pumpWidget( await tester.pumpWidget(
Center( Center(
child: GestureDetector( child: GestureDetector(
onTap: inputCallback, onTap: ButtonVariant.button == kPrimaryButton ? inputCallback : null,
onSecondaryTap: ButtonVariant.button == kSecondaryButton ? inputCallback : null,
child: Container(), child: Container(),
), ),
), ),
...@@ -250,7 +269,7 @@ void main() { ...@@ -250,7 +269,7 @@ void main() {
final RenderSemanticsGestureHandler renderObj2 = tester.renderObject(find.byType(GestureDetector)); final RenderSemanticsGestureHandler renderObj2 = tester.renderObject(find.byType(GestureDetector));
expect(renderObj1, same(renderObj2)); expect(renderObj1, same(renderObj2));
}); }, variant: buttonVariant);
testWidgets('Tap down occurs after kPressTimeout', (WidgetTester tester) async { testWidgets('Tap down occurs after kPressTimeout', (WidgetTester tester) async {
int tapDown = 0; int tapDown = 0;
...@@ -266,18 +285,30 @@ void main() { ...@@ -266,18 +285,30 @@ void main() {
height: 100.0, height: 100.0,
color: const Color(0xFF00FF00), color: const Color(0xFF00FF00),
child: GestureDetector( child: GestureDetector(
onTapDown: (TapDownDetails details) { onTapDown: ButtonVariant.button == kPrimaryButton ? (TapDownDetails details) {
tapDown += 1; tapDown += 1;
}, } : null,
onTap: () { onSecondaryTapDown: ButtonVariant.button == kSecondaryButton ? (TapDownDetails details) {
tapDown += 1;
} : null,
onTap: ButtonVariant.button == kPrimaryButton ? () {
tap += 1; tap += 1;
}, } : null,
onTapCancel: () { onSecondaryTap: ButtonVariant.button == kSecondaryButton ? () {
tap += 1;
} : null,
onTapCancel: ButtonVariant.button == kPrimaryButton ? () {
tapCancel += 1; tapCancel += 1;
}, } : null,
onLongPress: () { onSecondaryTapCancel: ButtonVariant.button == kSecondaryButton ? () {
tapCancel += 1;
} : null,
onLongPress: ButtonVariant.button == kPrimaryButton ? () {
longPress += 1; longPress += 1;
}, } : null,
onSecondaryLongPress: ButtonVariant.button == kSecondaryButton ? () {
longPress += 1;
} : null,
), ),
), ),
), ),
...@@ -286,7 +317,8 @@ void main() { ...@@ -286,7 +317,8 @@ void main() {
// Pointer is dragged from the center of the 800x100 gesture detector // Pointer is dragged from the center of the 800x100 gesture detector
// to a point (400,300) below it. This should never call onTap. // to a point (400,300) below it. This should never call onTap.
Future<void> dragOut(Duration timeout) async { Future<void> dragOut(Duration timeout) async {
final TestGesture gesture = await tester.startGesture(const Offset(400.0, 50.0)); final TestGesture gesture =
await tester.startGesture(const Offset(400.0, 50.0), buttons: ButtonVariant.button);
// If the timeout is less than kPressTimeout the recognizer will not // If the timeout is less than kPressTimeout the recognizer will not
// trigger any callbacks. If the timeout is greater than kLongPressTimeout // trigger any callbacks. If the timeout is greater than kLongPressTimeout
// then onTapDown, onLongPress, and onCancel will be called. // then onTapDown, onLongPress, and onCancel will be called.
...@@ -312,7 +344,7 @@ void main() { ...@@ -312,7 +344,7 @@ void main() {
expect(tapCancel, 2); expect(tapCancel, 2);
expect(tap, 0); expect(tap, 0);
expect(longPress, 1); expect(longPress, 1);
}); }, variant: buttonVariant);
testWidgets('Long Press Up Callback called after long press', (WidgetTester tester) async { testWidgets('Long Press Up Callback called after long press', (WidgetTester tester) async {
int longPressUp = 0; int longPressUp = 0;
...@@ -325,22 +357,26 @@ void main() { ...@@ -325,22 +357,26 @@ void main() {
height: 100.0, height: 100.0,
color: const Color(0xFF00FF00), color: const Color(0xFF00FF00),
child: GestureDetector( child: GestureDetector(
onLongPressUp: () { onLongPressUp: ButtonVariant.button == kPrimaryButton ? () {
longPressUp += 1; longPressUp += 1;
}, } : null,
onSecondaryLongPressUp: ButtonVariant.button == kSecondaryButton ? () {
longPressUp += 1;
} : null,
), ),
), ),
), ),
); );
Future<void> longPress(Duration timeout) async { Future<void> longPress(Duration timeout) async {
final TestGesture gesture = await tester.startGesture(const Offset(400.0, 50.0)); final TestGesture gesture = await tester.startGesture(const Offset(400.0, 50.0), buttons: ButtonVariant.button);
await tester.pump(timeout); await tester.pump(timeout);
await gesture.up(); await gesture.up();
} }
await longPress(kLongPressTimeout + const Duration(seconds: 1)); // To make sure the time for long press has occurred await longPress(kLongPressTimeout + const Duration(seconds: 1)); // To make sure the time for long press has occurred
expect(longPressUp, 1); expect(longPressUp, 1);
}, variant: buttonVariant);
}); });
testWidgets('Force Press Callback called after force press', (WidgetTester tester) async { testWidgets('Force Press Callback called after force press', (WidgetTester tester) async {
...@@ -707,3 +743,36 @@ class _EmptySemanticsGestureDelegate extends SemanticsGestureDelegate { ...@@ -707,3 +743,36 @@ class _EmptySemanticsGestureDelegate extends SemanticsGestureDelegate {
void assignSemantics(RenderSemanticsGestureHandler renderObject) { void assignSemantics(RenderSemanticsGestureHandler renderObject) {
} }
} }
/// A [TestVariant] that runs tests multiple times with different buttons.
class ButtonVariant extends TestVariant<int> {
const ButtonVariant({
@required this.values,
@required this.descriptions,
}) : assert(values.length != 0); // ignore: prefer_is_empty
@override
final List<int> values;
final Map<int, String> descriptions;
static int button;
@override
String describeValue(int value) {
assert(descriptions.containsKey(value), 'Unknown button');
return descriptions[value];
}
@override
Future<int> setUp(int value) async {
final int oldValue = button;
button = value;
return oldValue;
}
@override
Future<void> tearDown(int value, int memento) async {
button = memento;
}
}
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