tap.dart 24.2 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
import 'package:flutter/foundation.dart';
7
import 'package:vector_math/vector_math_64.dart' show Matrix4;
8

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

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

  /// The global position at which the pointer contacted the screen.
32
  final Offset globalPosition;
33 34

  /// The kind of the device that initiated the event.
35
  final PointerDeviceKind? kind;
36 37 38

  /// The local position at which the pointer contacted the screen.
  final Offset localPosition;
39 40 41 42 43 44 45
}

/// 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`.
46 47 48 49 50
///
/// See also:
///
///  * [GestureDetector.onTapDown], which matches this signature.
///  * [TapGestureRecognizer], which uses this signature in one of its callbacks.
51
typedef GestureTapDownCallback = void Function(TapDownDetails details);
52 53

/// Details for [GestureTapUpCallback], such as position.
54 55 56 57 58
///
/// See also:
///
///  * [GestureDetector.onTapUp], which receives this information.
///  * [TapGestureRecognizer], which passes this information to one of its callbacks.
59 60
class TapUpDetails {
  /// The [globalPosition] argument must not be null.
61
  TapUpDetails({
62
    required this.kind,
63
    this.globalPosition = Offset.zero,
64
    Offset? localPosition,
65 66
  }) : assert(globalPosition != null),
       localPosition = localPosition ?? globalPosition;
67 68

  /// The global position at which the pointer contacted the screen.
69
  final Offset globalPosition;
70 71 72

  /// The local position at which the pointer contacted the screen.
  final Offset localPosition;
73 74 75

  /// The kind of the device that initiated the event.
  final PointerDeviceKind kind;
76
}
77 78

/// Signature for when a pointer that will trigger a tap has stopped contacting
79 80 81 82
/// the screen.
///
/// The position at which the pointer stopped contacting the screen is available
/// in the `details`.
83 84 85 86 87
///
/// See also:
///
///  * [GestureDetector.onTapUp], which matches this signature.
///  * [TapGestureRecognizer], which uses this signature in one of its callbacks.
88
typedef GestureTapUpCallback = void Function(TapUpDetails details);
89 90

/// Signature for when a tap has occurred.
91 92 93 94 95
///
/// See also:
///
///  * [GestureDetector.onTap], which matches this signature.
///  * [TapGestureRecognizer], which uses this signature in one of its callbacks.
96
typedef GestureTapCallback = void Function();
97 98 99

/// Signature for when the pointer that previously triggered a
/// [GestureTapDownCallback] will not end up causing a tap.
100 101 102 103 104
///
/// See also:
///
///  * [GestureDetector.onTapCancel], which matches this signature.
///  * [TapGestureRecognizer], which uses this signature in one of its callbacks.
105
typedef GestureTapCancelCallback = void Function();
106

107 108 109 110 111 112 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
/// 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.
139 140 141 142
  ///
  /// {@macro flutter.gestures.GestureRecognizer.supportedDevices}
  BaseTapGestureRecognizer({ Object? debugOwner, Set<PointerDeviceKind>? supportedDevices })
    : super(deadline: kPressTimeout , debugOwner: debugOwner, supportedDevices: supportedDevices);
143 144 145 146

  bool _sentTapDown = false;
  bool _wonArenaForPrimaryPointer = false;

147 148
  PointerDownEvent? _down;
  PointerUpEvent? _up;
149 150 151 152 153 154 155 156 157 158 159 160

  /// 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
161
  void handleTapDown({ required PointerDownEvent down });
162 163 164

  /// A pointer has stopped contacting the screen, which is recognized as a tap.
  ///
165
  /// This triggers on the up event if the recognizer wins the arena with it
166 167 168 169 170 171 172 173
  /// 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
174
  void handleTapUp({ required PointerDownEvent down, required PointerUpEvent up });
175 176 177 178

  /// A pointer that previously triggered [handleTapDown] will not end up
  /// causing a tap.
  ///
179
  /// This triggers once the gesture loses the arena if [handleTapDown] has
180 181 182 183 184 185 186 187 188 189
  /// 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
190
  void handleTapCancel({ required PointerDownEvent down, PointerCancelEvent? cancel, required String reason });
191 192 193

  @override
  void addAllowedPointer(PointerDownEvent event) {
194
    assert(event != null);
195
    if (state == GestureRecognizerState.ready) {
196 197 198 199 200 201 202 203
      // 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);
204 205 206 207 208
      // `_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;
    }
209 210 211 212 213 214 215 216 217 218 219 220
    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,
      // in which case all pointers are simply ignored. The `_down` being null
      // 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
221
  void startTrackingPointer(int pointer, [Matrix4? transform]) {
222 223 224 225
    // 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);
226 227 228 229 230 231 232 233 234 235 236 237 238
  }

  @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();
239
    } else if (event.buttons != _down!.buttons) {
240
      resolve(GestureDisposition.rejected);
241
      stopTrackingPointer(primaryPointer!);
242 243 244 245 246 247 248 249 250 251 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);
      if (_sentTapDown)
        _checkCancel(null, 'forced');
      _reset();
    }
  }

  void _checkDown() {
    if (_sentTapDown) {
      return;
    }
289
    handleTapDown(down: _down!);
290 291 292 293 294 295 296
    _sentTapDown = true;
  }

  void _checkUp() {
    if (!_wonArenaForPrimaryPointer || _up == null) {
      return;
    }
297
    assert(_up!.pointer == _down!.pointer);
298
    handleTapUp(down: _down!, up: _up!);
299 300 301
    _reset();
  }

302 303
  void _checkCancel(PointerCancelEvent? event, String note) {
    handleTapCancel(down: _down!, cancel: event, reason: note);
304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326
  }

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

327 328
/// Recognizes taps.
///
329 330
/// Gesture recognizers take part in gesture arenas to enable potential gestures
/// to be disambiguated from each other. This process is managed by a
331
/// [GestureArenaManager].
332
///
333 334 335
/// [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
336
/// taps. For example, down-1, down-2, up-1, up-2 produces only one tap on up-1.
337
///
338
/// [TapGestureRecognizer] competes on pointer events of [kPrimaryButton] only
339
/// when it has at least one non-null `onTap*` callback, on events of
340
/// [kSecondaryButton] only when it has at least one non-null `onSecondaryTap*`
341 342 343
/// 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.
344
///
345 346
/// See also:
///
347
///  * [GestureDetector.onTap], which uses this recognizer.
348
///  * [MultiTapGestureRecognizer]
349
class TapGestureRecognizer extends BaseTapGestureRecognizer {
350
  /// Creates a tap gesture recognizer.
351 352 353 354
  ///
  /// {@macro flutter.gestures.GestureRecognizer.supportedDevices}
  TapGestureRecognizer({ Object? debugOwner, Set<PointerDeviceKind>? supportedDevices })
    : super(debugOwner: debugOwner, supportedDevices: supportedDevices);
355

356 357
  /// A pointer has contacted the screen at a particular location with a primary
  /// button, which might be the start of a tap.
358
  ///
359 360
  /// This triggers after the down event, once a short timeout ([deadline]) has
  /// elapsed, or once the gestures has won the arena, whichever comes first.
361
  ///
362
  /// If this recognizer doesn't win the arena, [onTapCancel] is called next.
363 364 365 366
  /// Otherwise, [onTapUp] is called next.
  ///
  /// See also:
  ///
367 368
  ///  * [kPrimaryButton], the button this callback responds to.
  ///  * [onSecondaryTapDown], a similar callback but for a secondary button.
369
  ///  * [onTertiaryTapDown], a similar callback but for a tertiary button.
370
  ///  * [TapDownDetails], which is passed as an argument to this callback.
371
  ///  * [GestureDetector.onTapDown], which exposes this callback.
372
  GestureTapDownCallback? onTapDown;
373

374 375
  /// A pointer has stopped contacting the screen at a particular location,
  /// which is recognized as a tap of a primary button.
376
  ///
377 378
  /// This triggers on the up event, if the recognizer wins the arena with it
  /// or has previously won, immediately followed by [onTap].
379
  ///
380
  /// If this recognizer doesn't win the arena, [onTapCancel] is called instead.
381 382 383
  ///
  /// See also:
  ///
384 385
  ///  * [kPrimaryButton], the button this callback responds to.
  ///  * [onSecondaryTapUp], a similar callback but for a secondary button.
386
  ///  * [onTertiaryTapUp], a similar callback but for a tertiary button.
387
  ///  * [TapUpDetails], which is passed as an argument to this callback.
388
  ///  * [GestureDetector.onTapUp], which exposes this callback.
389
  GestureTapUpCallback? onTapUp;
390

391 392
  /// A pointer has stopped contacting the screen, which is recognized as a tap
  /// of a primary button.
393
  ///
394
  /// This triggers on the up event, if the recognizer wins the arena with it
395
  /// or has previously won, immediately following [onTapUp].
396
  ///
397
  /// If this recognizer doesn't win the arena, [onTapCancel] is called instead.
398 399 400
  ///
  /// See also:
  ///
401
  ///  * [kPrimaryButton], the button this callback responds to.
402
  ///  * [onSecondaryTap], a similar callback but for a secondary button.
403
  ///  * [onTapUp], which has the same timing but with details.
404
  ///  * [GestureDetector.onTap], which exposes this callback.
405
  GestureTapCallback? onTap;
406

407
  /// A pointer that previously triggered [onTapDown] will not end up causing
408
  /// a tap.
409
  ///
410
  /// This triggers once the gesture loses the arena if [onTapDown] has
411
  /// previously been triggered.
412
  ///
413 414
  /// If this recognizer wins the arena, [onTapUp] and [onTap] are called
  /// instead.
415 416 417
  ///
  /// See also:
  ///
418 419
  ///  * [kPrimaryButton], the button this callback responds to.
  ///  * [onSecondaryTapCancel], a similar callback but for a secondary button.
420
  ///  * [onTertiaryTapCancel], a similar callback but for a tertiary button.
421
  ///  * [GestureDetector.onTapCancel], which exposes this callback.
422
  GestureTapCancelCallback? onTapCancel;
423

424 425 426 427 428 429 430 431 432 433 434 435 436 437
  /// 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.
438
  GestureTapCallback? onSecondaryTap;
439

440 441
  /// A pointer has contacted the screen at a particular location with a
  /// secondary button, which might be the start of a secondary tap.
442
  ///
443 444
  /// This triggers after the down event, once a short timeout ([deadline]) has
  /// elapsed, or once the gestures has won the arena, whichever comes first.
445
  ///
446 447
  /// If this recognizer doesn't win the arena, [onSecondaryTapCancel] is called
  /// next. Otherwise, [onSecondaryTapUp] is called next.
448 449 450 451
  ///
  /// See also:
  ///
  ///  * [kSecondaryButton], the button this callback responds to.
Dan Field's avatar
Dan Field committed
452
  ///  * [onTapDown], a similar callback but for a primary button.
453
  ///  * [onTertiaryTapDown], a similar callback but for a tertiary button.
454 455
  ///  * [TapDownDetails], which is passed as an argument to this callback.
  ///  * [GestureDetector.onSecondaryTapDown], which exposes this callback.
456
  GestureTapDownCallback? onSecondaryTapDown;
457

458 459
  /// A pointer has stopped contacting the screen at a particular location,
  /// which is recognized as a tap of a secondary button.
460
  ///
461
  /// This triggers on the up event if the recognizer wins the arena with it
462
  /// or has previously won.
463
  ///
464
  /// If this recognizer doesn't win the arena, [onSecondaryTapCancel] is called
465 466 467 468
  /// instead.
  ///
  /// See also:
  ///
469 470
  ///  * [onSecondaryTap], a handler triggered right after this one that doesn't
  ///    pass any details about the tap.
471
  ///  * [kSecondaryButton], the button this callback responds to.
Dan Field's avatar
Dan Field committed
472
  ///  * [onTapUp], a similar callback but for a primary button.
473
  ///  * [onTertiaryTapUp], a similar callback but for a tertiary button.
474 475
  ///  * [TapUpDetails], which is passed as an argument to this callback.
  ///  * [GestureDetector.onSecondaryTapUp], which exposes this callback.
476
  GestureTapUpCallback? onSecondaryTapUp;
477

478
  /// A pointer that previously triggered [onSecondaryTapDown] will not end up
479 480
  /// causing a tap.
  ///
481
  /// This triggers once the gesture loses the arena if [onSecondaryTapDown]
482
  /// has previously been triggered.
483
  ///
484
  /// If this recognizer wins the arena, [onSecondaryTapUp] is called instead.
485 486 487 488
  ///
  /// See also:
  ///
  ///  * [kSecondaryButton], the button this callback responds to.
Dan Field's avatar
Dan Field committed
489
  ///  * [onTapCancel], a similar callback but for a primary button.
490 491
  ///  * [onTertiaryTapCancel], a similar callback but for a tertiary button.
  ///  * [GestureDetector.onSecondaryTapCancel], which exposes this callback.
492
  GestureTapCancelCallback? onSecondaryTapCancel;
493

494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545
  /// 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;

546 547 548 549 550 551 552 553 554 555 556
  @override
  bool isPointerAllowed(PointerDownEvent event) {
    switch (event.buttons) {
      case kPrimaryButton:
        if (onTapDown == null &&
            onTap == null &&
            onTapUp == null &&
            onTapCancel == null)
          return false;
        break;
      case kSecondaryButton:
557 558
        if (onSecondaryTap == null &&
            onSecondaryTapDown == null &&
559 560 561 562
            onSecondaryTapUp == null &&
            onSecondaryTapCancel == null)
          return false;
        break;
563 564 565 566 567 568
      case kTertiaryButton:
        if (onTertiaryTapDown == null &&
            onTertiaryTapUp == null &&
            onTertiaryTapCancel == null)
          return false;
        break;
569 570 571 572 573 574
      default:
        return false;
    }
    return super.isPointerAllowed(event);
  }

575
  @protected
576
  @override
577
  void handleTapDown({required PointerDownEvent down}) {
578
    final TapDownDetails details = TapDownDetails(
579 580 581
      globalPosition: down.position,
      localPosition: down.localPosition,
      kind: getKindForPointer(down.pointer),
582
    );
583
    switch (down.buttons) {
584 585
      case kPrimaryButton:
        if (onTapDown != null)
586
          invokeCallback<void>('onTapDown', () => onTapDown!(details));
587 588 589
        break;
      case kSecondaryButton:
        if (onSecondaryTapDown != null)
590
          invokeCallback<void>('onSecondaryTapDown', () => onSecondaryTapDown!(details));
591
        break;
592 593 594 595
      case kTertiaryButton:
        if (onTertiaryTapDown != null)
          invokeCallback<void>('onTertiaryTapDown', () => onTertiaryTapDown!(details));
        break;
596
      default:
Hixie's avatar
Hixie committed
597 598 599
    }
  }

600 601
  @protected
  @override
602
  void handleTapUp({ required PointerDownEvent down, required PointerUpEvent up}) {
603
    final TapUpDetails details = TapUpDetails(
604
      kind: up.kind,
605 606
      globalPosition: up.position,
      localPosition: up.localPosition,
607
    );
608
    switch (down.buttons) {
609 610
      case kPrimaryButton:
        if (onTapUp != null)
611
          invokeCallback<void>('onTapUp', () => onTapUp!(details));
612
        if (onTap != null)
613
          invokeCallback<void>('onTap', onTap!);
614 615 616
        break;
      case kSecondaryButton:
        if (onSecondaryTapUp != null)
617
          invokeCallback<void>('onSecondaryTapUp', () => onSecondaryTapUp!(details));
618
        if (onSecondaryTap != null)
619
          invokeCallback<void>('onSecondaryTap', () => onSecondaryTap!());
620
        break;
621 622 623 624
      case kTertiaryButton:
        if (onTertiaryTapUp != null)
          invokeCallback<void>('onTertiaryTapUp', () => onTertiaryTapUp!(details));
        break;
625 626 627 628
      default:
    }
  }

629 630
  @protected
  @override
631
  void handleTapCancel({ required PointerDownEvent down, PointerCancelEvent? cancel, required String reason }) {
632
    final String note = reason == '' ? reason : '$reason ';
633
    switch (down.buttons) {
634 635
      case kPrimaryButton:
        if (onTapCancel != null)
636
          invokeCallback<void>('${note}onTapCancel', onTapCancel!);
637 638 639
        break;
      case kSecondaryButton:
        if (onSecondaryTapCancel != null)
640
          invokeCallback<void>('${note}onSecondaryTapCancel', onSecondaryTapCancel!);
641
        break;
642 643 644 645
      case kTertiaryButton:
        if (onTertiaryTapCancel != null)
          invokeCallback<void>('${note}onTertiaryTapCancel', onTertiaryTapCancel!);
        break;
646
      default:
647 648
    }
  }
649

650
  @override
651
  String get debugDescription => 'tap';
652
}