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
class TapDownDetails {
  /// Creates details for a [GestureTapDownCallback].
29 30
  TapDownDetails({
    this.globalPosition = Offset.zero,
31
    Offset? localPosition,
32
    this.kind,
33
  }) : localPosition = localPosition ?? globalPosition;
34 35

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

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

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

45
/// {@template flutter.gestures.tap.GestureTapDownCallback}
46 47 48 49 50
/// 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`.
51
/// {@endtemplate}
52 53 54 55 56
///
/// See also:
///
///  * [GestureDetector.onTapDown], which matches this signature.
///  * [TapGestureRecognizer], which uses this signature in one of its callbacks.
57
typedef GestureTapDownCallback = void Function(TapDownDetails details);
58 59

/// Details for [GestureTapUpCallback], such as position.
60 61 62 63 64
///
/// See also:
///
///  * [GestureDetector.onTapUp], which receives this information.
///  * [TapGestureRecognizer], which passes this information to one of its callbacks.
65
class TapUpDetails {
66
  /// Creates a [TapUpDetails] data object.
67
  TapUpDetails({
68
    required this.kind,
69
    this.globalPosition = Offset.zero,
70
    Offset? localPosition,
71
  }) : localPosition = localPosition ?? globalPosition;
72 73

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

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

  /// The kind of the device that initiated the event.
  final PointerDeviceKind kind;
81
}
82

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

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

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

114 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
/// 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.
146 147
  ///
  /// {@macro flutter.gestures.GestureRecognizer.supportedDevices}
148 149 150 151 152
  BaseTapGestureRecognizer({
    super.debugOwner,
    super.supportedDevices,
    super.allowedButtonsFilter,
  })
153
    : super(deadline: kPressTimeout);
154 155 156 157

  bool _sentTapDown = false;
  bool _wonArenaForPrimaryPointer = false;

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

  /// 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
172
  void handleTapDown({ required PointerDownEvent down });
173 174 175

  /// A pointer has stopped contacting the screen, which is recognized as a tap.
  ///
176
  /// This triggers on the up event if the recognizer wins the arena with it
177 178 179 180 181 182 183 184
  /// 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
185
  void handleTapUp({ required PointerDownEvent down, required PointerUpEvent up });
186 187 188 189

  /// A pointer that previously triggered [handleTapDown] will not end up
  /// causing a tap.
  ///
190
  /// This triggers once the gesture loses the arena if [handleTapDown] has
191 192 193 194 195 196 197 198 199 200
  /// 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
201
  void handleTapCancel({ required PointerDownEvent down, PointerCancelEvent? cancel, required String reason });
202 203 204 205

  @override
  void addAllowedPointer(PointerDownEvent event) {
    if (state == GestureRecognizerState.ready) {
206 207 208 209 210 211 212 213
      // 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);
214 215 216 217 218
      // `_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;
    }
219 220 221
    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,
222
      // in which case all pointers are ignored. The `_down` being null
223 224 225 226 227 228 229 230
      // 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
231
  void startTrackingPointer(int pointer, [Matrix4? transform]) {
232 233 234 235
    // 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);
236 237 238 239 240 241 242 243 244 245 246 247 248
  }

  @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();
249
    } else if (event.buttons != _down!.buttons) {
250
      resolve(GestureDisposition.rejected);
251
      stopTrackingPointer(primaryPointer!);
252 253 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
    }
  }

  @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);
289
      if (_sentTapDown) {
290
        _checkCancel(null, 'forced');
291
      }
292 293 294 295 296 297 298 299
      _reset();
    }
  }

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

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

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

  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'));
  }
}

338 339
/// Recognizes taps.
///
340 341
/// Gesture recognizers take part in gesture arenas to enable potential gestures
/// to be disambiguated from each other. This process is managed by a
342
/// [GestureArenaManager].
343
///
344 345 346
/// [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
347
/// taps. For example, down-1, down-2, up-1, up-2 produces only one tap on up-1.
348
///
349
/// [TapGestureRecognizer] competes on pointer events of [kPrimaryButton] only
350
/// when it has at least one non-null `onTap*` callback, on events of
351
/// [kSecondaryButton] only when it has at least one non-null `onSecondaryTap*`
352 353 354
/// 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.
355
///
356 357 358 359 360 361 362 363 364 365
/// {@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}
///
366 367
/// See also:
///
368
///  * [GestureDetector.onTap], which uses this recognizer.
369
///  * [MultiTapGestureRecognizer]
370
class TapGestureRecognizer extends BaseTapGestureRecognizer {
371
  /// Creates a tap gesture recognizer.
372 373
  ///
  /// {@macro flutter.gestures.GestureRecognizer.supportedDevices}
374 375 376 377 378
  TapGestureRecognizer({
    super.debugOwner,
    super.supportedDevices,
    super.allowedButtonsFilter,
  });
379

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

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

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

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

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

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

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

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

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

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

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

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

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

690
  @override
691
  String get debugDescription => 'tap';
692
}