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) {
if (onLongPressStart != null) { case kPrimaryButton:
final LongPressStartDetails details = LongPressStartDetails( if (onLongPressStart != null) {
globalPosition: _longPressOrigin.global, final LongPressStartDetails details = LongPressStartDetails(
localPosition: _longPressOrigin.local, globalPosition: _longPressOrigin.global,
); localPosition: _longPressOrigin.local,
invokeCallback<void>('onLongPressStart', );
() => onLongPressStart(details)); 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) { 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) {
invokeCallback<void>('onLongPressMoveUpdate', case kPrimaryButton:
() => onLongPressMoveUpdate(details)); 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) { 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,10 +418,26 @@ class LongPressGestureRecognizer extends PrimaryPointerGestureRecognizer { ...@@ -329,10 +418,26 @@ class LongPressGestureRecognizer extends PrimaryPointerGestureRecognizer {
); );
_velocityTracker = null; _velocityTracker = null;
if (onLongPressEnd != null) switch (_initialButtons) {
invokeCallback<void>('onLongPressEnd', () => onLongPressEnd(details)); case kPrimaryButton:
if (onLongPressUp != null) if (onLongPressEnd != null) {
invokeCallback<void>('onLongPressUp', onLongPressUp); 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() { void _reset() {
......
...@@ -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
......
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