tap.dart 25.6 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5

6 7
import 'package:flutter/foundation.dart';

8
import 'arena.dart';
9
import 'constants.dart';
10
import 'events.dart';
11
import 'recognizer.dart';
12

13 14 15 16 17 18 19 20
export 'dart:ui' show Offset, PointerDeviceKind;

export 'package:flutter/foundation.dart' show DiagnosticPropertiesBuilder;
export 'package:vector_math/vector_math_64.dart' show Matrix4;

export 'arena.dart' show GestureDisposition;
export 'events.dart' show PointerCancelEvent, PointerDownEvent, PointerEvent, PointerUpEvent;

21
/// Details for [GestureTapDownCallback], such as position.
22 23 24 25 26
///
/// See also:
///
///  * [GestureDetector.onTapDown], which receives this information.
///  * [TapGestureRecognizer], which passes this information to one of its callbacks.
27 28 29 30
class TapDownDetails {
  /// Creates details for a [GestureTapDownCallback].
  ///
  /// The [globalPosition] argument must not be null.
31 32
  TapDownDetails({
    this.globalPosition = Offset.zero,
33
    Offset? localPosition,
34
    this.kind,
35
  }) : localPosition = localPosition ?? globalPosition;
36 37

  /// The global position at which the pointer contacted the screen.
38
  final Offset globalPosition;
39 40

  /// The kind of the device that initiated the event.
41
  final PointerDeviceKind? kind;
42 43 44

  /// The local position at which the pointer contacted the screen.
  final Offset localPosition;
45 46
}

47
/// {@template flutter.gestures.tap.GestureTapDownCallback}
48 49 50 51 52
/// Signature for when a pointer that might cause a tap has contacted the
/// screen.
///
/// The position at which the pointer contacted the screen is available in the
/// `details`.
53
/// {@endtemplate}
54 55 56 57 58
///
/// See also:
///
///  * [GestureDetector.onTapDown], which matches this signature.
///  * [TapGestureRecognizer], which uses this signature in one of its callbacks.
59
typedef GestureTapDownCallback = void Function(TapDownDetails details);
60 61

/// Details for [GestureTapUpCallback], such as position.
62 63 64 65 66
///
/// See also:
///
///  * [GestureDetector.onTapUp], which receives this information.
///  * [TapGestureRecognizer], which passes this information to one of its callbacks.
67 68
class TapUpDetails {
  /// The [globalPosition] argument must not be null.
69
  TapUpDetails({
70
    required this.kind,
71
    this.globalPosition = Offset.zero,
72
    Offset? localPosition,
73
  }) : localPosition = localPosition ?? globalPosition;
74 75

  /// The global position at which the pointer contacted the screen.
76
  final Offset globalPosition;
77 78 79

  /// The local position at which the pointer contacted the screen.
  final Offset localPosition;
80 81 82

  /// The kind of the device that initiated the event.
  final PointerDeviceKind kind;
83
}
84

85
/// {@template flutter.gestures.tap.GestureTapUpCallback}
86
/// Signature for when a pointer that will trigger a tap has stopped contacting
87 88 89 90
/// the screen.
///
/// The position at which the pointer stopped contacting the screen is available
/// in the `details`.
91
/// {@endtemplate}
92 93 94 95 96
///
/// See also:
///
///  * [GestureDetector.onTapUp], which matches this signature.
///  * [TapGestureRecognizer], which uses this signature in one of its callbacks.
97
typedef GestureTapUpCallback = void Function(TapUpDetails details);
98 99

/// Signature for when a tap has occurred.
100 101 102 103 104
///
/// See also:
///
///  * [GestureDetector.onTap], which matches this signature.
///  * [TapGestureRecognizer], which uses this signature in one of its callbacks.
105
typedef GestureTapCallback = void Function();
106 107 108

/// Signature for when the pointer that previously triggered a
/// [GestureTapDownCallback] will not end up causing a tap.
109 110 111 112 113
///
/// See also:
///
///  * [GestureDetector.onTapCancel], which matches this signature.
///  * [TapGestureRecognizer], which uses this signature in one of its callbacks.
114
typedef GestureTapCancelCallback = void Function();
115

116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
/// A base class for gesture recognizers that recognize taps.
///
/// Gesture recognizers take part in gesture arenas to enable potential gestures
/// to be disambiguated from each other. This process is managed by a
/// [GestureArenaManager].
///
/// A tap is defined as a sequence of events that starts with a down, followed
/// by optional moves, then ends with an up. All move events must contain the
/// same `buttons` as the down event, and must not be too far from the initial
/// position. The gesture is rejected on any violation, a cancel event, or
/// if any other recognizers wins the arena. It is accepted only when it is the
/// last member of the arena.
///
/// The [BaseTapGestureRecognizer] considers all the pointers involved in the
/// pointer event sequence as contributing to one gesture. For this reason,
/// extra pointer interactions during a tap sequence are not recognized as
/// additional taps. For example, down-1, down-2, up-1, up-2 produces only one
/// tap on up-1.
///
/// The [BaseTapGestureRecognizer] can not be directly used, since it does not
/// define which buttons to accept, or what to do when a tap happens. If you
/// want to build a custom tap recognizer, extend this class by overriding
/// [isPointerAllowed] and the handler methods.
///
/// See also:
///
///  * [TapGestureRecognizer], a ready-to-use tap recognizer that recognizes
///    taps of the primary button and taps of the secondary button.
///  * [ModalBarrier], a widget that uses a custom tap recognizer that accepts
///    any buttons.
abstract class BaseTapGestureRecognizer extends PrimaryPointerGestureRecognizer {
  /// Creates a tap gesture recognizer.
148 149
  ///
  /// {@macro flutter.gestures.GestureRecognizer.supportedDevices}
150 151 152 153 154
  BaseTapGestureRecognizer({
    super.debugOwner,
    super.supportedDevices,
    super.allowedButtonsFilter,
  })
155
    : super(deadline: kPressTimeout);
156 157 158 159

  bool _sentTapDown = false;
  bool _wonArenaForPrimaryPointer = false;

160 161
  PointerDownEvent? _down;
  PointerUpEvent? _up;
162 163 164 165 166 167 168 169 170 171 172 173

  /// A pointer has contacted the screen, which might be the start of a tap.
  ///
  /// This triggers after the down event, once a short timeout ([deadline]) has
  /// elapsed, or once the gesture has won the arena, whichever comes first.
  ///
  /// The parameter `down` is the down event of the primary pointer that started
  /// the tap sequence.
  ///
  /// If this recognizer doesn't win the arena, [handleTapCancel] is called next.
  /// Otherwise, [handleTapUp] is called next.
  @protected
174
  void handleTapDown({ required PointerDownEvent down });
175 176 177

  /// A pointer has stopped contacting the screen, which is recognized as a tap.
  ///
178
  /// This triggers on the up event if the recognizer wins the arena with it
179 180 181 182 183 184 185 186
  /// or has previously won.
  ///
  /// The parameter `down` is the down event of the primary pointer that started
  /// the tap sequence, and `up` is the up event that ended the tap sequence.
  ///
  /// If this recognizer doesn't win the arena, [handleTapCancel] is called
  /// instead.
  @protected
187
  void handleTapUp({ required PointerDownEvent down, required PointerUpEvent up });
188 189 190 191

  /// A pointer that previously triggered [handleTapDown] will not end up
  /// causing a tap.
  ///
192
  /// This triggers once the gesture loses the arena if [handleTapDown] has
193 194 195 196 197 198 199 200 201 202
  /// been previously triggered.
  ///
  /// The parameter `down` is the down event of the primary pointer that started
  /// the tap sequence; `cancel` is the cancel event, which might be null;
  /// `reason` is a short description of the cause if `cancel` is null, which
  /// can be "forced" if other gestures won the arena, or "spontaneous"
  /// otherwise.
  ///
  /// If this recognizer wins the arena, [handleTapUp] is called instead.
  @protected
203
  void handleTapCancel({ required PointerDownEvent down, PointerCancelEvent? cancel, required String reason });
204 205 206 207

  @override
  void addAllowedPointer(PointerDownEvent event) {
    if (state == GestureRecognizerState.ready) {
208 209 210 211 212 213 214 215
      // If there is no result in the previous gesture arena,
      // we ignore them and prepare to accept a new pointer.
      if (_down != null && _up != null) {
        assert(_down!.pointer == _up!.pointer);
        _reset();
      }

      assert(_down == null && _up == null);
216 217 218 219 220
      // `_down` must be assigned in this method instead of `handlePrimaryPointer`,
      // because `acceptGesture` might be called before `handlePrimaryPointer`,
      // which relies on `_down` to call `handleTapDown`.
      _down = event;
    }
221 222 223
    if (_down != null) {
      // This happens when this tap gesture has been rejected while the pointer
      // is down (i.e. due to movement), when another allowed pointer is added,
224
      // in which case all pointers are ignored. The `_down` being null
225 226 227 228 229 230 231 232
      // means that _reset() has been called, since it is always set at the
      // first allowed down event and will not be cleared except for reset(),
      super.addAllowedPointer(event);
    }
  }

  @override
  @protected
233
  void startTrackingPointer(int pointer, [Matrix4? transform]) {
234 235 236 237
    // The recognizer should never track any pointers when `_down` is null,
    // because calling `_checkDown` in this state will throw exception.
    assert(_down != null);
    super.startTrackingPointer(pointer, transform);
238 239 240 241 242 243 244 245 246 247 248 249 250
  }

  @override
  void handlePrimaryPointer(PointerEvent event) {
    if (event is PointerUpEvent) {
      _up = event;
      _checkUp();
    } else if (event is PointerCancelEvent) {
      resolve(GestureDisposition.rejected);
      if (_sentTapDown) {
        _checkCancel(event, '');
      }
      _reset();
251
    } else if (event.buttons != _down!.buttons) {
252
      resolve(GestureDisposition.rejected);
253
      stopTrackingPointer(primaryPointer!);
254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290
    }
  }

  @override
  void resolve(GestureDisposition disposition) {
    if (_wonArenaForPrimaryPointer && disposition == GestureDisposition.rejected) {
      // This can happen if the gesture has been canceled. For example, when
      // the pointer has exceeded the touch slop, the buttons have been changed,
      // or if the recognizer is disposed.
      assert(_sentTapDown);
      _checkCancel(null, 'spontaneous');
      _reset();
    }
    super.resolve(disposition);
  }

  @override
  void didExceedDeadline() {
    _checkDown();
  }

  @override
  void acceptGesture(int pointer) {
    super.acceptGesture(pointer);
    if (pointer == primaryPointer) {
      _checkDown();
      _wonArenaForPrimaryPointer = true;
      _checkUp();
    }
  }

  @override
  void rejectGesture(int pointer) {
    super.rejectGesture(pointer);
    if (pointer == primaryPointer) {
      // Another gesture won the arena.
      assert(state != GestureRecognizerState.possible);
291
      if (_sentTapDown) {
292
        _checkCancel(null, 'forced');
293
      }
294 295 296 297 298 299 300 301
      _reset();
    }
  }

  void _checkDown() {
    if (_sentTapDown) {
      return;
    }
302
    handleTapDown(down: _down!);
303 304 305 306 307 308 309
    _sentTapDown = true;
  }

  void _checkUp() {
    if (!_wonArenaForPrimaryPointer || _up == null) {
      return;
    }
310
    assert(_up!.pointer == _down!.pointer);
311
    handleTapUp(down: _down!, up: _up!);
312 313 314
    _reset();
  }

315 316
  void _checkCancel(PointerCancelEvent? event, String note) {
    handleTapCancel(down: _down!, cancel: event, reason: note);
317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339
  }

  void _reset() {
    _sentTapDown = false;
    _wonArenaForPrimaryPointer = false;
    _up = null;
    _down = null;
  }

  @override
  String get debugDescription => 'base tap';

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(FlagProperty('wonArenaForPrimaryPointer', value: _wonArenaForPrimaryPointer, ifTrue: 'won arena'));
    properties.add(DiagnosticsProperty<Offset>('finalPosition', _up?.position, defaultValue: null));
    properties.add(DiagnosticsProperty<Offset>('finalLocalPosition', _up?.localPosition, defaultValue: _up?.position));
    properties.add(DiagnosticsProperty<int>('button', _down?.buttons, defaultValue: null));
    properties.add(FlagProperty('sentTapDown', value: _sentTapDown, ifTrue: 'sent tap down'));
  }
}

340 341
/// Recognizes taps.
///
342 343
/// Gesture recognizers take part in gesture arenas to enable potential gestures
/// to be disambiguated from each other. This process is managed by a
344
/// [GestureArenaManager].
345
///
346 347 348
/// [TapGestureRecognizer] considers all the pointers involved in the pointer
/// event sequence as contributing to one gesture. For this reason, extra
/// pointer interactions during a tap sequence are not recognized as additional
349
/// taps. For example, down-1, down-2, up-1, up-2 produces only one tap on up-1.
350
///
351
/// [TapGestureRecognizer] competes on pointer events of [kPrimaryButton] only
352
/// when it has at least one non-null `onTap*` callback, on events of
353
/// [kSecondaryButton] only when it has at least one non-null `onSecondaryTap*`
354 355 356
/// callback, and on events of [kTertiaryButton] only when it has at least
/// one non-null `onTertiaryTap*` callback. If it has no callbacks, it is a
/// no-op.
357
///
358 359 360 361 362 363 364 365 366 367
/// {@template flutter.gestures.tap.TapGestureRecognizer.allowedButtonsFilter}
/// The [allowedButtonsFilter] argument only gives this recognizer the
/// ability to limit the buttons it accepts. It does not provide the
/// ability to recognize any buttons beyond the ones it already accepts:
/// kPrimaryButton, kSecondaryButton or kTertiaryButton. Therefore, a
/// combined value of `kPrimaryButton & kSecondaryButton` would be ignored,
/// but `kPrimaryButton | kSecondaryButton` would be allowed, as long as
/// only one of them is selected at a time.
/// {@endtemplate}
///
368 369
/// See also:
///
370
///  * [GestureDetector.onTap], which uses this recognizer.
371
///  * [MultiTapGestureRecognizer]
372
class TapGestureRecognizer extends BaseTapGestureRecognizer {
373
  /// Creates a tap gesture recognizer.
374 375
  ///
  /// {@macro flutter.gestures.GestureRecognizer.supportedDevices}
376 377 378 379 380
  TapGestureRecognizer({
    super.debugOwner,
    super.supportedDevices,
    super.allowedButtonsFilter,
  });
381

382
  /// {@template flutter.gestures.tap.TapGestureRecognizer.onTapDown}
383 384
  /// A pointer has contacted the screen at a particular location with a primary
  /// button, which might be the start of a tap.
385
  /// {@endtemplate}
386
  ///
387 388
  /// This triggers after the down event, once a short timeout ([deadline]) has
  /// elapsed, or once the gestures has won the arena, whichever comes first.
389
  ///
390
  /// If this recognizer doesn't win the arena, [onTapCancel] is called next.
391 392 393 394
  /// Otherwise, [onTapUp] is called next.
  ///
  /// See also:
  ///
395 396
  ///  * [kPrimaryButton], the button this callback responds to.
  ///  * [onSecondaryTapDown], a similar callback but for a secondary button.
397
  ///  * [onTertiaryTapDown], a similar callback but for a tertiary button.
398
  ///  * [TapDownDetails], which is passed as an argument to this callback.
399
  ///  * [GestureDetector.onTapDown], which exposes this callback.
400
  GestureTapDownCallback? onTapDown;
401

402
  /// {@template flutter.gestures.tap.TapGestureRecognizer.onTapUp}
403 404
  /// A pointer has stopped contacting the screen at a particular location,
  /// which is recognized as a tap of a primary button.
405
  /// {@endtemplate}
406
  ///
407 408
  /// This triggers on the up event, if the recognizer wins the arena with it
  /// or has previously won, immediately followed by [onTap].
409
  ///
410
  /// If this recognizer doesn't win the arena, [onTapCancel] is called instead.
411 412 413
  ///
  /// See also:
  ///
414 415
  ///  * [kPrimaryButton], the button this callback responds to.
  ///  * [onSecondaryTapUp], a similar callback but for a secondary button.
416
  ///  * [onTertiaryTapUp], a similar callback but for a tertiary button.
417
  ///  * [TapUpDetails], which is passed as an argument to this callback.
418
  ///  * [GestureDetector.onTapUp], which exposes this callback.
419
  GestureTapUpCallback? onTapUp;
420

421 422
  /// A pointer has stopped contacting the screen, which is recognized as a tap
  /// of a primary button.
423
  ///
424
  /// This triggers on the up event, if the recognizer wins the arena with it
425
  /// or has previously won, immediately following [onTapUp].
426
  ///
427
  /// If this recognizer doesn't win the arena, [onTapCancel] is called instead.
428 429 430
  ///
  /// See also:
  ///
431
  ///  * [kPrimaryButton], the button this callback responds to.
432
  ///  * [onSecondaryTap], a similar callback but for a secondary button.
433
  ///  * [onTapUp], which has the same timing but with details.
434
  ///  * [GestureDetector.onTap], which exposes this callback.
435
  GestureTapCallback? onTap;
436

437
  /// {@template flutter.gestures.tap.TapGestureRecognizer.onTapCancel}
438
  /// A pointer that previously triggered [onTapDown] will not end up causing
439
  /// a tap.
440
  /// {@endtemplate}
441
  ///
442
  /// This triggers once the gesture loses the arena if [onTapDown] has
443
  /// previously been triggered.
444
  ///
445 446
  /// If this recognizer wins the arena, [onTapUp] and [onTap] are called
  /// instead.
447 448 449
  ///
  /// See also:
  ///
450 451
  ///  * [kPrimaryButton], the button this callback responds to.
  ///  * [onSecondaryTapCancel], a similar callback but for a secondary button.
452
  ///  * [onTertiaryTapCancel], a similar callback but for a tertiary button.
453
  ///  * [GestureDetector.onTapCancel], which exposes this callback.
454
  GestureTapCancelCallback? onTapCancel;
455

456
  /// {@template flutter.gestures.tap.TapGestureRecognizer.onSecondaryTap}
457 458
  /// A pointer has stopped contacting the screen, which is recognized as a tap
  /// of a secondary button.
459
  /// {@endtemplate}
460 461 462 463 464 465 466 467 468 469 470 471
  ///
  /// 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.
472
  GestureTapCallback? onSecondaryTap;
473

474
  /// {@template flutter.gestures.tap.TapGestureRecognizer.onSecondaryTapDown}
475 476
  /// A pointer has contacted the screen at a particular location with a
  /// secondary button, which might be the start of a secondary tap.
477
  /// {@endtemplate}
478
  ///
479 480
  /// This triggers after the down event, once a short timeout ([deadline]) has
  /// elapsed, or once the gestures has won the arena, whichever comes first.
481
  ///
482 483
  /// If this recognizer doesn't win the arena, [onSecondaryTapCancel] is called
  /// next. Otherwise, [onSecondaryTapUp] is called next.
484 485 486 487
  ///
  /// See also:
  ///
  ///  * [kSecondaryButton], the button this callback responds to.
Dan Field's avatar
Dan Field committed
488
  ///  * [onTapDown], a similar callback but for a primary button.
489
  ///  * [onTertiaryTapDown], a similar callback but for a tertiary button.
490 491
  ///  * [TapDownDetails], which is passed as an argument to this callback.
  ///  * [GestureDetector.onSecondaryTapDown], which exposes this callback.
492
  GestureTapDownCallback? onSecondaryTapDown;
493

494
  /// {@template flutter.gestures.tap.TapGestureRecognizer.onSecondaryTapUp}
495 496
  /// A pointer has stopped contacting the screen at a particular location,
  /// which is recognized as a tap of a secondary button.
497
  /// {@endtemplate}
498
  ///
499
  /// This triggers on the up event if the recognizer wins the arena with it
500
  /// or has previously won.
501
  ///
502
  /// If this recognizer doesn't win the arena, [onSecondaryTapCancel] is called
503 504 505 506
  /// instead.
  ///
  /// See also:
  ///
507 508
  ///  * [onSecondaryTap], a handler triggered right after this one that doesn't
  ///    pass any details about the tap.
509
  ///  * [kSecondaryButton], the button this callback responds to.
Dan Field's avatar
Dan Field committed
510
  ///  * [onTapUp], a similar callback but for a primary button.
511
  ///  * [onTertiaryTapUp], a similar callback but for a tertiary button.
512 513
  ///  * [TapUpDetails], which is passed as an argument to this callback.
  ///  * [GestureDetector.onSecondaryTapUp], which exposes this callback.
514
  GestureTapUpCallback? onSecondaryTapUp;
515

516
  /// {@template flutter.gestures.tap.TapGestureRecognizer.onSecondaryTapCancel}
517
  /// A pointer that previously triggered [onSecondaryTapDown] will not end up
518
  /// causing a tap.
519
  /// {@endtemplate}
520
  ///
521
  /// This triggers once the gesture loses the arena if [onSecondaryTapDown]
522
  /// has previously been triggered.
523
  ///
524
  /// If this recognizer wins the arena, [onSecondaryTapUp] is called instead.
525 526 527 528
  ///
  /// See also:
  ///
  ///  * [kSecondaryButton], the button this callback responds to.
Dan Field's avatar
Dan Field committed
529
  ///  * [onTapCancel], a similar callback but for a primary button.
530 531
  ///  * [onTertiaryTapCancel], a similar callback but for a tertiary button.
  ///  * [GestureDetector.onSecondaryTapCancel], which exposes this callback.
532
  GestureTapCancelCallback? onSecondaryTapCancel;
533

534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585
  /// A pointer has contacted the screen at a particular location with a
  /// tertiary button, which might be the start of a tertiary tap.
  ///
  /// This triggers after the down event, once a short timeout ([deadline]) has
  /// elapsed, or once the gestures has won the arena, whichever comes first.
  ///
  /// If this recognizer doesn't win the arena, [onTertiaryTapCancel] is called
  /// next. Otherwise, [onTertiaryTapUp] is called next.
  ///
  /// See also:
  ///
  ///  * [kTertiaryButton], the button this callback responds to.
  ///  * [onTapDown], a similar callback but for a primary button.
  ///  * [onSecondaryTapDown], a similar callback but for a secondary button.
  ///  * [TapDownDetails], which is passed as an argument to this callback.
  ///  * [GestureDetector.onTertiaryTapDown], which exposes this callback.
  GestureTapDownCallback? onTertiaryTapDown;

  /// A pointer has stopped contacting the screen at a particular location,
  /// which is recognized as a tap of a tertiary button.
  ///
  /// This triggers on the up event if the recognizer wins the arena with it
  /// or has previously won.
  ///
  /// If this recognizer doesn't win the arena, [onTertiaryTapCancel] is called
  /// instead.
  ///
  /// See also:
  ///
  ///  * [kTertiaryButton], the button this callback responds to.
  ///  * [onTapUp], a similar callback but for a primary button.
  ///  * [onSecondaryTapUp], a similar callback but for a secondary button.
  ///  * [TapUpDetails], which is passed as an argument to this callback.
  ///  * [GestureDetector.onTertiaryTapUp], which exposes this callback.
  GestureTapUpCallback? onTertiaryTapUp;

  /// A pointer that previously triggered [onTertiaryTapDown] will not end up
  /// causing a tap.
  ///
  /// This triggers once the gesture loses the arena if [onTertiaryTapDown]
  /// has previously been triggered.
  ///
  /// If this recognizer wins the arena, [onTertiaryTapUp] is called instead.
  ///
  /// See also:
  ///
  ///  * [kSecondaryButton], the button this callback responds to.
  ///  * [onTapCancel], a similar callback but for a primary button.
  ///  * [onSecondaryTapCancel], a similar callback but for a secondary button.
  ///  * [GestureDetector.onTertiaryTapCancel], which exposes this callback.
  GestureTapCancelCallback? onTertiaryTapCancel;

586 587 588 589 590 591 592
  @override
  bool isPointerAllowed(PointerDownEvent event) {
    switch (event.buttons) {
      case kPrimaryButton:
        if (onTapDown == null &&
            onTap == null &&
            onTapUp == null &&
593
            onTapCancel == null) {
594
          return false;
595
        }
596
      case kSecondaryButton:
597 598
        if (onSecondaryTap == null &&
            onSecondaryTapDown == null &&
599
            onSecondaryTapUp == null &&
600
            onSecondaryTapCancel == null) {
601
          return false;
602
        }
603 604 605
      case kTertiaryButton:
        if (onTertiaryTapDown == null &&
            onTertiaryTapUp == null &&
606
            onTertiaryTapCancel == null) {
607
          return false;
608
        }
609 610 611 612 613 614
      default:
        return false;
    }
    return super.isPointerAllowed(event);
  }

615
  @protected
616
  @override
617
  void handleTapDown({required PointerDownEvent down}) {
618
    final TapDownDetails details = TapDownDetails(
619 620 621
      globalPosition: down.position,
      localPosition: down.localPosition,
      kind: getKindForPointer(down.pointer),
622
    );
623
    switch (down.buttons) {
624
      case kPrimaryButton:
625
        if (onTapDown != null) {
626
          invokeCallback<void>('onTapDown', () => onTapDown!(details));
627
        }
628
      case kSecondaryButton:
629
        if (onSecondaryTapDown != null) {
630
          invokeCallback<void>('onSecondaryTapDown', () => onSecondaryTapDown!(details));
631
        }
632
      case kTertiaryButton:
633
        if (onTertiaryTapDown != null) {
634
          invokeCallback<void>('onTertiaryTapDown', () => onTertiaryTapDown!(details));
635
        }
636
      default:
Hixie's avatar
Hixie committed
637 638 639
    }
  }

640 641
  @protected
  @override
642
  void handleTapUp({ required PointerDownEvent down, required PointerUpEvent up}) {
643
    final TapUpDetails details = TapUpDetails(
644
      kind: up.kind,
645 646
      globalPosition: up.position,
      localPosition: up.localPosition,
647
    );
648
    switch (down.buttons) {
649
      case kPrimaryButton:
650
        if (onTapUp != null) {
651
          invokeCallback<void>('onTapUp', () => onTapUp!(details));
652 653
        }
        if (onTap != null) {
654
          invokeCallback<void>('onTap', onTap!);
655
        }
656
      case kSecondaryButton:
657
        if (onSecondaryTapUp != null) {
658
          invokeCallback<void>('onSecondaryTapUp', () => onSecondaryTapUp!(details));
659 660
        }
        if (onSecondaryTap != null) {
661
          invokeCallback<void>('onSecondaryTap', () => onSecondaryTap!());
662
        }
663
      case kTertiaryButton:
664
        if (onTertiaryTapUp != null) {
665
          invokeCallback<void>('onTertiaryTapUp', () => onTertiaryTapUp!(details));
666
        }
667 668 669 670
      default:
    }
  }

671 672
  @protected
  @override
673
  void handleTapCancel({ required PointerDownEvent down, PointerCancelEvent? cancel, required String reason }) {
674
    final String note = reason == '' ? reason : '$reason ';
675
    switch (down.buttons) {
676
      case kPrimaryButton:
677
        if (onTapCancel != null) {
678
          invokeCallback<void>('${note}onTapCancel', onTapCancel!);
679
        }
680
      case kSecondaryButton:
681
        if (onSecondaryTapCancel != null) {
682
          invokeCallback<void>('${note}onSecondaryTapCancel', onSecondaryTapCancel!);
683
        }
684
      case kTertiaryButton:
685
        if (onTertiaryTapCancel != null) {
686
          invokeCallback<void>('${note}onTertiaryTapCancel', onTertiaryTapCancel!);
687
        }
688
      default:
689 690
    }
  }
691

692
  @override
693
  String get debugDescription => 'tap';
694
}