Add onSecondaryTap to gesture recognizer and gesture detector. (#55494)
This commit is contained in:
@@ -30,7 +30,7 @@ const int kPrimaryButton = 0x01;
|
||||
/// It is equivalent to:
|
||||
///
|
||||
/// * [kPrimaryStylusButton]: The stylus contacts the screen.
|
||||
/// * [kSecondaryMouseButton]: The primary mouse button.
|
||||
/// * [kSecondaryMouseButton]: The secondary mouse button.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
|
||||
@@ -141,9 +141,9 @@ class LongPressEndDetails {
|
||||
/// moved, triggering [onLongPressMoveUpdate] callbacks, unless the
|
||||
/// [postAcceptSlopTolerance] constructor argument is specified.
|
||||
///
|
||||
/// [LongPressGestureRecognizer] competes on pointer events of [kPrimaryButton]
|
||||
/// only when it has at least one non-null callback. If it has no callbacks, it
|
||||
/// is a no-op.
|
||||
/// [LongPressGestureRecognizer] may compete on pointer events of
|
||||
/// [kPrimaryButton] and/or [kSecondaryButton] if at least one corresponding
|
||||
/// callback is non-null. If it has no callbacks, it is a no-op.
|
||||
class LongPressGestureRecognizer extends PrimaryPointerGestureRecognizer {
|
||||
/// Creates a long-press gesture recognizer.
|
||||
///
|
||||
@@ -225,6 +225,57 @@ class LongPressGestureRecognizer extends PrimaryPointerGestureRecognizer {
|
||||
/// callback.
|
||||
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;
|
||||
|
||||
@override
|
||||
@@ -238,6 +289,14 @@ class LongPressGestureRecognizer extends PrimaryPointerGestureRecognizer {
|
||||
onLongPressUp == null)
|
||||
return false;
|
||||
break;
|
||||
case kSecondaryButton:
|
||||
if (onSecondaryLongPressStart == null &&
|
||||
onSecondaryLongPress == null &&
|
||||
onSecondaryLongPressMoveUpdate == null &&
|
||||
onSecondaryLongPressEnd == null &&
|
||||
onSecondaryLongPressUp == null)
|
||||
return false;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -291,37 +350,67 @@ class LongPressGestureRecognizer extends PrimaryPointerGestureRecognizer {
|
||||
}
|
||||
|
||||
void _checkLongPressStart() {
|
||||
assert(_initialButtons == kPrimaryButton);
|
||||
if (onLongPressStart != null) {
|
||||
final LongPressStartDetails details = LongPressStartDetails(
|
||||
globalPosition: _longPressOrigin.global,
|
||||
localPosition: _longPressOrigin.local,
|
||||
);
|
||||
invokeCallback<void>('onLongPressStart',
|
||||
() => onLongPressStart(details));
|
||||
switch (_initialButtons) {
|
||||
case kPrimaryButton:
|
||||
if (onLongPressStart != null) {
|
||||
final LongPressStartDetails details = LongPressStartDetails(
|
||||
globalPosition: _longPressOrigin.global,
|
||||
localPosition: _longPressOrigin.local,
|
||||
);
|
||||
invokeCallback<void>('onLongPressStart', () => onLongPressStart(details));
|
||||
}
|
||||
if (onLongPress != null) {
|
||||
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');
|
||||
}
|
||||
if (onLongPress != null)
|
||||
invokeCallback<void>('onLongPress', onLongPress);
|
||||
}
|
||||
|
||||
void _checkLongPressMoveUpdate(PointerEvent event) {
|
||||
assert(_initialButtons == kPrimaryButton);
|
||||
final LongPressMoveUpdateDetails details = LongPressMoveUpdateDetails(
|
||||
globalPosition: event.position,
|
||||
localPosition: event.localPosition,
|
||||
offsetFromOrigin: event.position - _longPressOrigin.global,
|
||||
localOffsetFromOrigin: event.localPosition - _longPressOrigin.local,
|
||||
);
|
||||
if (onLongPressMoveUpdate != null)
|
||||
invokeCallback<void>('onLongPressMoveUpdate',
|
||||
() => onLongPressMoveUpdate(details));
|
||||
switch (_initialButtons) {
|
||||
case kPrimaryButton:
|
||||
if (onLongPressMoveUpdate != null) {
|
||||
invokeCallback<void>('onLongPressMoveUpdate',
|
||||
() => onLongPressMoveUpdate(details));
|
||||
}
|
||||
break;
|
||||
case kSecondaryButton:
|
||||
if (onSecondaryLongPressMoveUpdate != null) {
|
||||
invokeCallback<void>('onSecondaryLongPressMoveUpdate',
|
||||
() => onSecondaryLongPressMoveUpdate(details));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
assert(false, 'Unhandled button $_initialButtons');
|
||||
}
|
||||
}
|
||||
|
||||
void _checkLongPressEnd(PointerEvent event) {
|
||||
assert(_initialButtons == kPrimaryButton);
|
||||
|
||||
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(
|
||||
globalPosition: event.position,
|
||||
localPosition: event.localPosition,
|
||||
@@ -329,10 +418,26 @@ class LongPressGestureRecognizer extends PrimaryPointerGestureRecognizer {
|
||||
);
|
||||
|
||||
_velocityTracker = null;
|
||||
if (onLongPressEnd != null)
|
||||
invokeCallback<void>('onLongPressEnd', () => onLongPressEnd(details));
|
||||
if (onLongPressUp != null)
|
||||
invokeCallback<void>('onLongPressUp', onLongPressUp);
|
||||
switch (_initialButtons) {
|
||||
case kPrimaryButton:
|
||||
if (onLongPressEnd != null) {
|
||||
invokeCallback<void>('onLongPressEnd', () => onLongPressEnd(details));
|
||||
}
|
||||
if (onLongPressUp != null) {
|
||||
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() {
|
||||
|
||||
@@ -369,7 +369,7 @@ class TapGestureRecognizer extends BaseTapGestureRecognizer {
|
||||
/// of a primary button.
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
@@ -396,6 +396,22 @@ class TapGestureRecognizer extends BaseTapGestureRecognizer {
|
||||
/// * [GestureDetector.onTapCancel], which exposes this callback.
|
||||
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
|
||||
/// secondary button, which might be the start of a secondary tap.
|
||||
///
|
||||
@@ -424,6 +440,8 @@ class TapGestureRecognizer extends BaseTapGestureRecognizer {
|
||||
///
|
||||
/// 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.
|
||||
/// * [onTapUp], a similar callback but for a primary button.
|
||||
/// * [TapUpDetails], which is passed as an argument to this callback.
|
||||
@@ -456,7 +474,8 @@ class TapGestureRecognizer extends BaseTapGestureRecognizer {
|
||||
return false;
|
||||
break;
|
||||
case kSecondaryButton:
|
||||
if (onSecondaryTapDown == null &&
|
||||
if (onSecondaryTap == null &&
|
||||
onSecondaryTapDown == null &&
|
||||
onSecondaryTapUp == null &&
|
||||
onSecondaryTapCancel == null)
|
||||
return false;
|
||||
@@ -482,8 +501,7 @@ class TapGestureRecognizer extends BaseTapGestureRecognizer {
|
||||
break;
|
||||
case kSecondaryButton:
|
||||
if (onSecondaryTapDown != null)
|
||||
invokeCallback<void>('onSecondaryTapDown',
|
||||
() => onSecondaryTapDown(details));
|
||||
invokeCallback<void>('onSecondaryTapDown', () => onSecondaryTapDown(details));
|
||||
break;
|
||||
default:
|
||||
}
|
||||
@@ -505,8 +523,9 @@ class TapGestureRecognizer extends BaseTapGestureRecognizer {
|
||||
break;
|
||||
case kSecondaryButton:
|
||||
if (onSecondaryTapUp != null)
|
||||
invokeCallback<void>('onSecondaryTapUp',
|
||||
() => onSecondaryTapUp(details));
|
||||
invokeCallback<void>('onSecondaryTapUp', () => onSecondaryTapUp(details));
|
||||
if (onSecondaryTap != null)
|
||||
invokeCallback<void>('onSecondaryTap', () => onSecondaryTap());
|
||||
break;
|
||||
default:
|
||||
}
|
||||
@@ -523,8 +542,7 @@ class TapGestureRecognizer extends BaseTapGestureRecognizer {
|
||||
break;
|
||||
case kSecondaryButton:
|
||||
if (onSecondaryTapCancel != null)
|
||||
invokeCallback<void>('${note}onSecondaryTapCancel',
|
||||
onSecondaryTapCancel);
|
||||
invokeCallback<void>('${note}onSecondaryTapCancel', onSecondaryTapCancel);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
|
||||
@@ -189,6 +189,7 @@ class GestureDetector extends StatelessWidget {
|
||||
this.onTapUp,
|
||||
this.onTap,
|
||||
this.onTapCancel,
|
||||
this.onSecondaryTap,
|
||||
this.onSecondaryTapDown,
|
||||
this.onSecondaryTapUp,
|
||||
this.onSecondaryTapCancel,
|
||||
@@ -198,6 +199,11 @@ class GestureDetector extends StatelessWidget {
|
||||
this.onLongPressMoveUpdate,
|
||||
this.onLongPressUp,
|
||||
this.onLongPressEnd,
|
||||
this.onSecondaryLongPress,
|
||||
this.onSecondaryLongPressStart,
|
||||
this.onSecondaryLongPressMoveUpdate,
|
||||
this.onSecondaryLongPressUp,
|
||||
this.onSecondaryLongPressEnd,
|
||||
this.onVerticalDragDown,
|
||||
this.onVerticalDragStart,
|
||||
this.onVerticalDragUpdate,
|
||||
@@ -304,6 +310,18 @@ class GestureDetector extends StatelessWidget {
|
||||
/// * [kPrimaryButton], the button this callback responds to.
|
||||
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
|
||||
/// screen at a particular location.
|
||||
///
|
||||
@@ -324,6 +342,8 @@ class GestureDetector extends StatelessWidget {
|
||||
///
|
||||
/// 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.
|
||||
final GestureTapUpCallback onSecondaryTapUp;
|
||||
|
||||
@@ -394,6 +414,59 @@ class GestureDetector extends StatelessWidget {
|
||||
/// details.
|
||||
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
|
||||
/// to move vertically.
|
||||
///
|
||||
@@ -601,6 +674,7 @@ class GestureDetector extends StatelessWidget {
|
||||
onTapUp != null ||
|
||||
onTap != null ||
|
||||
onTapCancel != null ||
|
||||
onSecondaryTap != null ||
|
||||
onSecondaryTapDown != null ||
|
||||
onSecondaryTapUp != null ||
|
||||
onSecondaryTapCancel != null
|
||||
@@ -613,6 +687,7 @@ class GestureDetector extends StatelessWidget {
|
||||
..onTapUp = onTapUp
|
||||
..onTap = onTap
|
||||
..onTapCancel = onTapCancel
|
||||
..onSecondaryTap = onSecondaryTap
|
||||
..onSecondaryTapDown = onSecondaryTapDown
|
||||
..onSecondaryTapUp = onSecondaryTapUp
|
||||
..onSecondaryTapCancel = onSecondaryTapCancel;
|
||||
@@ -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 ||
|
||||
onVerticalDragStart != null ||
|
||||
onVerticalDragUpdate != null ||
|
||||
@@ -1110,7 +1203,7 @@ abstract class SemanticsGestureDelegate {
|
||||
// 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
|
||||
// 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.
|
||||
//
|
||||
// Instead, a normal delegate will store callbacks as properties, and use them
|
||||
|
||||
@@ -125,222 +125,258 @@ void main() {
|
||||
expect(didEndPan, isTrue);
|
||||
});
|
||||
|
||||
testWidgets('Translucent', (WidgetTester tester) async {
|
||||
bool didReceivePointerDown;
|
||||
bool didTap;
|
||||
group('Tap', () {
|
||||
final ButtonVariant buttonVariant = ButtonVariant(
|
||||
values: <int>[kPrimaryButton, kSecondaryButton],
|
||||
descriptions: <int, String>{
|
||||
kPrimaryButton: 'primary',
|
||||
kSecondaryButton: 'secondary',
|
||||
},
|
||||
);
|
||||
|
||||
Future<void> pumpWidgetTree(HitTestBehavior behavior) {
|
||||
return tester.pumpWidget(
|
||||
Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: Stack(
|
||||
children: <Widget>[
|
||||
Listener(
|
||||
onPointerDown: (_) {
|
||||
didReceivePointerDown = true;
|
||||
},
|
||||
child: Container(
|
||||
testWidgets('Translucent', (WidgetTester tester) async {
|
||||
bool didReceivePointerDown;
|
||||
bool didTap;
|
||||
|
||||
Future<void> pumpWidgetTree(HitTestBehavior behavior) {
|
||||
return tester.pumpWidget(
|
||||
Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: Stack(
|
||||
children: <Widget>[
|
||||
Listener(
|
||||
onPointerDown: (_) {
|
||||
didReceivePointerDown = true;
|
||||
},
|
||||
child: Container(
|
||||
width: 100.0,
|
||||
height: 100.0,
|
||||
color: const Color(0xFF00FF00),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: 100.0,
|
||||
height: 100.0,
|
||||
color: const Color(0xFF00FF00),
|
||||
child: GestureDetector(
|
||||
onTap: ButtonVariant.button == kPrimaryButton ? () {
|
||||
didTap = true;
|
||||
} : null,
|
||||
onSecondaryTap: ButtonVariant.button == kSecondaryButton ? () {
|
||||
didTap = true;
|
||||
} : null,
|
||||
behavior: behavior,
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: 100.0,
|
||||
height: 100.0,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
didTap = true;
|
||||
},
|
||||
behavior: behavior,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
didReceivePointerDown = false;
|
||||
didTap = false;
|
||||
await pumpWidgetTree(null);
|
||||
await tester.tapAt(const Offset(10.0, 10.0), buttons: ButtonVariant.button);
|
||||
expect(didReceivePointerDown, isTrue);
|
||||
expect(didTap, isTrue);
|
||||
|
||||
didReceivePointerDown = false;
|
||||
didTap = false;
|
||||
await pumpWidgetTree(HitTestBehavior.deferToChild);
|
||||
await tester.tapAt(const Offset(10.0, 10.0), buttons: ButtonVariant.button);
|
||||
expect(didReceivePointerDown, isTrue);
|
||||
expect(didTap, isFalse);
|
||||
|
||||
didReceivePointerDown = false;
|
||||
didTap = false;
|
||||
await pumpWidgetTree(HitTestBehavior.opaque);
|
||||
await tester.tapAt(const Offset(10.0, 10.0), buttons: ButtonVariant.button);
|
||||
expect(didReceivePointerDown, isFalse);
|
||||
expect(didTap, isTrue);
|
||||
|
||||
didReceivePointerDown = false;
|
||||
didTap = false;
|
||||
await pumpWidgetTree(HitTestBehavior.translucent);
|
||||
await tester.tapAt(const Offset(10.0, 10.0), buttons: ButtonVariant.button);
|
||||
expect(didReceivePointerDown, isTrue);
|
||||
expect(didTap, isTrue);
|
||||
}, variant: buttonVariant);
|
||||
|
||||
testWidgets('Empty', (WidgetTester tester) async {
|
||||
bool didTap = false;
|
||||
await tester.pumpWidget(
|
||||
Center(
|
||||
child: GestureDetector(
|
||||
onTap: ButtonVariant.button == kPrimaryButton ? () {
|
||||
didTap = true;
|
||||
} : null,
|
||||
onSecondaryTap: ButtonVariant.button == kSecondaryButton ? () {
|
||||
didTap = true;
|
||||
} : null,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
expect(didTap, isFalse);
|
||||
await tester.tapAt(const Offset(10.0, 10.0), buttons: ButtonVariant.button);
|
||||
expect(didTap, isTrue);
|
||||
}, variant: buttonVariant);
|
||||
|
||||
didReceivePointerDown = false;
|
||||
didTap = false;
|
||||
await pumpWidgetTree(null);
|
||||
await tester.tapAt(const Offset(10.0, 10.0));
|
||||
expect(didReceivePointerDown, isTrue);
|
||||
expect(didTap, isTrue);
|
||||
|
||||
didReceivePointerDown = false;
|
||||
didTap = false;
|
||||
await pumpWidgetTree(HitTestBehavior.deferToChild);
|
||||
await tester.tapAt(const Offset(10.0, 10.0));
|
||||
expect(didReceivePointerDown, isTrue);
|
||||
expect(didTap, isFalse);
|
||||
|
||||
didReceivePointerDown = false;
|
||||
didTap = false;
|
||||
await pumpWidgetTree(HitTestBehavior.opaque);
|
||||
await tester.tapAt(const Offset(10.0, 10.0));
|
||||
expect(didReceivePointerDown, isFalse);
|
||||
expect(didTap, isTrue);
|
||||
|
||||
didReceivePointerDown = false;
|
||||
didTap = false;
|
||||
await pumpWidgetTree(HitTestBehavior.translucent);
|
||||
await tester.tapAt(const Offset(10.0, 10.0));
|
||||
expect(didReceivePointerDown, isTrue);
|
||||
expect(didTap, isTrue);
|
||||
|
||||
});
|
||||
|
||||
testWidgets('Empty', (WidgetTester tester) async {
|
||||
bool didTap = false;
|
||||
await tester.pumpWidget(
|
||||
Center(
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
didTap = true;
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
expect(didTap, isFalse);
|
||||
await tester.tapAt(const Offset(10.0, 10.0));
|
||||
expect(didTap, isTrue);
|
||||
});
|
||||
|
||||
testWidgets('Only container', (WidgetTester tester) async {
|
||||
bool didTap = false;
|
||||
await tester.pumpWidget(
|
||||
Center(
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
didTap = true;
|
||||
},
|
||||
child: Container(),
|
||||
),
|
||||
),
|
||||
);
|
||||
expect(didTap, isFalse);
|
||||
await tester.tapAt(const Offset(10.0, 10.0));
|
||||
expect(didTap, isFalse);
|
||||
});
|
||||
|
||||
testWidgets('cache render object', (WidgetTester tester) async {
|
||||
final GestureTapCallback inputCallback = () { };
|
||||
|
||||
await tester.pumpWidget(
|
||||
Center(
|
||||
child: GestureDetector(
|
||||
onTap: inputCallback,
|
||||
child: Container(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final RenderSemanticsGestureHandler renderObj1 = tester.renderObject(find.byType(GestureDetector));
|
||||
|
||||
await tester.pumpWidget(
|
||||
Center(
|
||||
child: GestureDetector(
|
||||
onTap: inputCallback,
|
||||
child: Container(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final RenderSemanticsGestureHandler renderObj2 = tester.renderObject(find.byType(GestureDetector));
|
||||
|
||||
expect(renderObj1, same(renderObj2));
|
||||
});
|
||||
|
||||
testWidgets('Tap down occurs after kPressTimeout', (WidgetTester tester) async {
|
||||
int tapDown = 0;
|
||||
int tap = 0;
|
||||
int tapCancel = 0;
|
||||
int longPress = 0;
|
||||
|
||||
await tester.pumpWidget(
|
||||
Container(
|
||||
alignment: Alignment.topLeft,
|
||||
child: Container(
|
||||
alignment: Alignment.center,
|
||||
height: 100.0,
|
||||
color: const Color(0xFF00FF00),
|
||||
testWidgets('Only container', (WidgetTester tester) async {
|
||||
bool didTap = false;
|
||||
await tester.pumpWidget(
|
||||
Center(
|
||||
child: GestureDetector(
|
||||
onTapDown: (TapDownDetails details) {
|
||||
tapDown += 1;
|
||||
},
|
||||
onTap: () {
|
||||
tap += 1;
|
||||
},
|
||||
onTapCancel: () {
|
||||
tapCancel += 1;
|
||||
},
|
||||
onLongPress: () {
|
||||
longPress += 1;
|
||||
},
|
||||
onTap: ButtonVariant.button == kPrimaryButton ? () {
|
||||
didTap = true;
|
||||
} : null,
|
||||
onSecondaryTap: ButtonVariant.button == kSecondaryButton ? () {
|
||||
didTap = true;
|
||||
} : null,
|
||||
child: Container(),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
);
|
||||
expect(didTap, isFalse);
|
||||
await tester.tapAt(const Offset(10.0, 10.0));
|
||||
expect(didTap, isFalse);
|
||||
}, variant: buttonVariant);
|
||||
|
||||
// Pointer is dragged from the center of the 800x100 gesture detector
|
||||
// to a point (400,300) below it. This should never call onTap.
|
||||
Future<void> dragOut(Duration timeout) async {
|
||||
final TestGesture gesture = await tester.startGesture(const Offset(400.0, 50.0));
|
||||
// If the timeout is less than kPressTimeout the recognizer will not
|
||||
// trigger any callbacks. If the timeout is greater than kLongPressTimeout
|
||||
// then onTapDown, onLongPress, and onCancel will be called.
|
||||
await tester.pump(timeout);
|
||||
await gesture.moveTo(const Offset(400.0, 300.0));
|
||||
await gesture.up();
|
||||
}
|
||||
testWidgets('cache render object', (WidgetTester tester) async {
|
||||
final GestureTapCallback inputCallback = () { };
|
||||
|
||||
await dragOut(kPressTimeout * 0.5); // generates nothing
|
||||
expect(tapDown, 0);
|
||||
expect(tapCancel, 0);
|
||||
expect(tap, 0);
|
||||
expect(longPress, 0);
|
||||
|
||||
await dragOut(kPressTimeout); // generates tapDown, tapCancel
|
||||
expect(tapDown, 1);
|
||||
expect(tapCancel, 1);
|
||||
expect(tap, 0);
|
||||
expect(longPress, 0);
|
||||
|
||||
await dragOut(kLongPressTimeout); // generates tapDown, longPress, tapCancel
|
||||
expect(tapDown, 2);
|
||||
expect(tapCancel, 2);
|
||||
expect(tap, 0);
|
||||
expect(longPress, 1);
|
||||
});
|
||||
|
||||
testWidgets('Long Press Up Callback called after long press', (WidgetTester tester) async {
|
||||
int longPressUp = 0;
|
||||
|
||||
await tester.pumpWidget(
|
||||
Container(
|
||||
alignment: Alignment.topLeft,
|
||||
child: Container(
|
||||
alignment: Alignment.center,
|
||||
height: 100.0,
|
||||
color: const Color(0xFF00FF00),
|
||||
await tester.pumpWidget(
|
||||
Center(
|
||||
child: GestureDetector(
|
||||
onLongPressUp: () {
|
||||
longPressUp += 1;
|
||||
},
|
||||
onTap: ButtonVariant.button == kPrimaryButton ? inputCallback : null,
|
||||
onSecondaryTap: ButtonVariant.button == kSecondaryButton ? inputCallback : null,
|
||||
child: Container(),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
);
|
||||
|
||||
Future<void> longPress(Duration timeout) async {
|
||||
final TestGesture gesture = await tester.startGesture(const Offset(400.0, 50.0));
|
||||
await tester.pump(timeout);
|
||||
await gesture.up();
|
||||
}
|
||||
final RenderSemanticsGestureHandler renderObj1 = tester.renderObject(find.byType(GestureDetector));
|
||||
|
||||
await longPress(kLongPressTimeout + const Duration(seconds: 1)); // To make sure the time for long press has occurred
|
||||
expect(longPressUp, 1);
|
||||
await tester.pumpWidget(
|
||||
Center(
|
||||
child: GestureDetector(
|
||||
onTap: ButtonVariant.button == kPrimaryButton ? inputCallback : null,
|
||||
onSecondaryTap: ButtonVariant.button == kSecondaryButton ? inputCallback : null,
|
||||
child: Container(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final RenderSemanticsGestureHandler renderObj2 = tester.renderObject(find.byType(GestureDetector));
|
||||
|
||||
expect(renderObj1, same(renderObj2));
|
||||
}, variant: buttonVariant);
|
||||
|
||||
testWidgets('Tap down occurs after kPressTimeout', (WidgetTester tester) async {
|
||||
int tapDown = 0;
|
||||
int tap = 0;
|
||||
int tapCancel = 0;
|
||||
int longPress = 0;
|
||||
|
||||
await tester.pumpWidget(
|
||||
Container(
|
||||
alignment: Alignment.topLeft,
|
||||
child: Container(
|
||||
alignment: Alignment.center,
|
||||
height: 100.0,
|
||||
color: const Color(0xFF00FF00),
|
||||
child: GestureDetector(
|
||||
onTapDown: ButtonVariant.button == kPrimaryButton ? (TapDownDetails details) {
|
||||
tapDown += 1;
|
||||
} : null,
|
||||
onSecondaryTapDown: ButtonVariant.button == kSecondaryButton ? (TapDownDetails details) {
|
||||
tapDown += 1;
|
||||
} : null,
|
||||
onTap: ButtonVariant.button == kPrimaryButton ? () {
|
||||
tap += 1;
|
||||
} : null,
|
||||
onSecondaryTap: ButtonVariant.button == kSecondaryButton ? () {
|
||||
tap += 1;
|
||||
} : null,
|
||||
onTapCancel: ButtonVariant.button == kPrimaryButton ? () {
|
||||
tapCancel += 1;
|
||||
} : null,
|
||||
onSecondaryTapCancel: ButtonVariant.button == kSecondaryButton ? () {
|
||||
tapCancel += 1;
|
||||
} : null,
|
||||
onLongPress: ButtonVariant.button == kPrimaryButton ? () {
|
||||
longPress += 1;
|
||||
} : null,
|
||||
onSecondaryLongPress: ButtonVariant.button == kSecondaryButton ? () {
|
||||
longPress += 1;
|
||||
} : null,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Pointer is dragged from the center of the 800x100 gesture detector
|
||||
// to a point (400,300) below it. This should never call onTap.
|
||||
Future<void> dragOut(Duration timeout) async {
|
||||
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
|
||||
// trigger any callbacks. If the timeout is greater than kLongPressTimeout
|
||||
// then onTapDown, onLongPress, and onCancel will be called.
|
||||
await tester.pump(timeout);
|
||||
await gesture.moveTo(const Offset(400.0, 300.0));
|
||||
await gesture.up();
|
||||
}
|
||||
|
||||
await dragOut(kPressTimeout * 0.5); // generates nothing
|
||||
expect(tapDown, 0);
|
||||
expect(tapCancel, 0);
|
||||
expect(tap, 0);
|
||||
expect(longPress, 0);
|
||||
|
||||
await dragOut(kPressTimeout); // generates tapDown, tapCancel
|
||||
expect(tapDown, 1);
|
||||
expect(tapCancel, 1);
|
||||
expect(tap, 0);
|
||||
expect(longPress, 0);
|
||||
|
||||
await dragOut(kLongPressTimeout); // generates tapDown, longPress, tapCancel
|
||||
expect(tapDown, 2);
|
||||
expect(tapCancel, 2);
|
||||
expect(tap, 0);
|
||||
expect(longPress, 1);
|
||||
}, variant: buttonVariant);
|
||||
|
||||
testWidgets('Long Press Up Callback called after long press', (WidgetTester tester) async {
|
||||
int longPressUp = 0;
|
||||
|
||||
await tester.pumpWidget(
|
||||
Container(
|
||||
alignment: Alignment.topLeft,
|
||||
child: Container(
|
||||
alignment: Alignment.center,
|
||||
height: 100.0,
|
||||
color: const Color(0xFF00FF00),
|
||||
child: GestureDetector(
|
||||
onLongPressUp: ButtonVariant.button == kPrimaryButton ? () {
|
||||
longPressUp += 1;
|
||||
} : null,
|
||||
onSecondaryLongPressUp: ButtonVariant.button == kSecondaryButton ? () {
|
||||
longPressUp += 1;
|
||||
} : null,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
Future<void> longPress(Duration timeout) async {
|
||||
final TestGesture gesture = await tester.startGesture(const Offset(400.0, 50.0), buttons: ButtonVariant.button);
|
||||
await tester.pump(timeout);
|
||||
await gesture.up();
|
||||
}
|
||||
|
||||
await longPress(kLongPressTimeout + const Duration(seconds: 1)); // To make sure the time for long press has occurred
|
||||
expect(longPressUp, 1);
|
||||
}, variant: buttonVariant);
|
||||
});
|
||||
|
||||
testWidgets('Force Press Callback called after force press', (WidgetTester tester) async {
|
||||
@@ -707,3 +743,36 @@ class _EmptySemanticsGestureDelegate extends SemanticsGestureDelegate {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user