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;
/// 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
......
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