tap.dart 24.5 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 36
  }) : assert(globalPosition != null),
       localPosition = localPosition ?? globalPosition;
37 38

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

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

  /// The local position at which the pointer contacted the screen.
  final Offset localPosition;
46 47 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 54 55 56 57
///
/// See also:
///
///  * [GestureDetector.onTapDown], which matches this signature.
///  * [TapGestureRecognizer], which uses this signature in one of its callbacks.
58
typedef GestureTapDownCallback = void Function(TapDownDetails details);
59 60

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

/// Signature for when a pointer that will trigger a tap has stopped contacting
86 87 88 89
/// the screen.
///
/// The position at which the pointer stopped contacting the screen is available
/// in the `details`.
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
  BaseTapGestureRecognizer({ super.debugOwner, super.supportedDevices })
    : super(deadline: kPressTimeout);
150 151 152 153

  bool _sentTapDown = false;
  bool _wonArenaForPrimaryPointer = false;

154 155
  PointerDownEvent? _down;
  PointerUpEvent? _up;
156 157 158 159 160 161 162 163 164 165 166 167

  /// 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
168
  void handleTapDown({ required PointerDownEvent down });
169 170 171

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

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

  @override
  void addAllowedPointer(PointerDownEvent event) {
201
    assert(event != null);
202
    if (state == GestureRecognizerState.ready) {
203 204 205 206 207 208 209 210
      // 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);
211 212 213 214 215
      // `_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;
    }
216 217 218 219 220 221 222 223 224 225 226 227
    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
228
  void startTrackingPointer(int pointer, [Matrix4? transform]) {
229 230 231 232
    // 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);
233 234 235 236 237 238 239 240 241 242 243 244 245
  }

  @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();
246
    } else if (event.buttons != _down!.buttons) {
247
      resolve(GestureDisposition.rejected);
248
      stopTrackingPointer(primaryPointer!);
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
    }
  }

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

  void _checkDown() {
    if (_sentTapDown) {
      return;
    }
297
    handleTapDown(down: _down!);
298 299 300 301 302 303 304
    _sentTapDown = true;
  }

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

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

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

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

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

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

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

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

431 432 433 434 435 436 437 438 439 440 441 442 443 444
  /// 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.
445
  GestureTapCallback? onSecondaryTap;
446

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

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

485
  /// A pointer that previously triggered [onSecondaryTapDown] will not end up
486 487
  /// causing a tap.
  ///
488
  /// This triggers once the gesture loses the arena if [onSecondaryTapDown]
489
  /// has previously been triggered.
490
  ///
491
  /// If this recognizer wins the arena, [onSecondaryTapUp] is called instead.
492 493 494 495
  ///
  /// See also:
  ///
  ///  * [kSecondaryButton], the button this callback responds to.
Dan Field's avatar
Dan Field committed
496
  ///  * [onTapCancel], a similar callback but for a primary button.
497 498
  ///  * [onTertiaryTapCancel], a similar callback but for a tertiary button.
  ///  * [GestureDetector.onSecondaryTapCancel], which exposes this callback.
499
  GestureTapCancelCallback? onSecondaryTapCancel;
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 546 547 548 549 550 551 552
  /// 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;

553 554 555 556 557 558 559
  @override
  bool isPointerAllowed(PointerDownEvent event) {
    switch (event.buttons) {
      case kPrimaryButton:
        if (onTapDown == null &&
            onTap == null &&
            onTapUp == null &&
560
            onTapCancel == null) {
561
          return false;
562
        }
563 564
        break;
      case kSecondaryButton:
565 566
        if (onSecondaryTap == null &&
            onSecondaryTapDown == null &&
567
            onSecondaryTapUp == null &&
568
            onSecondaryTapCancel == null) {
569
          return false;
570
        }
571
        break;
572 573 574
      case kTertiaryButton:
        if (onTertiaryTapDown == null &&
            onTertiaryTapUp == null &&
575
            onTertiaryTapCancel == null) {
576
          return false;
577
        }
578
        break;
579 580 581 582 583 584
      default:
        return false;
    }
    return super.isPointerAllowed(event);
  }

585
  @protected
586
  @override
587
  void handleTapDown({required PointerDownEvent down}) {
588
    final TapDownDetails details = TapDownDetails(
589 590 591
      globalPosition: down.position,
      localPosition: down.localPosition,
      kind: getKindForPointer(down.pointer),
592
    );
593
    switch (down.buttons) {
594
      case kPrimaryButton:
595
        if (onTapDown != null) {
596
          invokeCallback<void>('onTapDown', () => onTapDown!(details));
597
        }
598 599
        break;
      case kSecondaryButton:
600
        if (onSecondaryTapDown != null) {
601
          invokeCallback<void>('onSecondaryTapDown', () => onSecondaryTapDown!(details));
602
        }
603
        break;
604
      case kTertiaryButton:
605
        if (onTertiaryTapDown != null) {
606
          invokeCallback<void>('onTertiaryTapDown', () => onTertiaryTapDown!(details));
607
        }
608
        break;
609
      default:
Hixie's avatar
Hixie committed
610 611 612
    }
  }

613 614
  @protected
  @override
615
  void handleTapUp({ required PointerDownEvent down, required PointerUpEvent up}) {
616
    final TapUpDetails details = TapUpDetails(
617
      kind: up.kind,
618 619
      globalPosition: up.position,
      localPosition: up.localPosition,
620
    );
621
    switch (down.buttons) {
622
      case kPrimaryButton:
623
        if (onTapUp != null) {
624
          invokeCallback<void>('onTapUp', () => onTapUp!(details));
625 626
        }
        if (onTap != null) {
627
          invokeCallback<void>('onTap', onTap!);
628
        }
629 630
        break;
      case kSecondaryButton:
631
        if (onSecondaryTapUp != null) {
632
          invokeCallback<void>('onSecondaryTapUp', () => onSecondaryTapUp!(details));
633 634
        }
        if (onSecondaryTap != null) {
635
          invokeCallback<void>('onSecondaryTap', () => onSecondaryTap!());
636
        }
637
        break;
638
      case kTertiaryButton:
639
        if (onTertiaryTapUp != null) {
640
          invokeCallback<void>('onTertiaryTapUp', () => onTertiaryTapUp!(details));
641
        }
642
        break;
643 644 645 646
      default:
    }
  }

647 648
  @protected
  @override
649
  void handleTapCancel({ required PointerDownEvent down, PointerCancelEvent? cancel, required String reason }) {
650
    final String note = reason == '' ? reason : '$reason ';
651
    switch (down.buttons) {
652
      case kPrimaryButton:
653
        if (onTapCancel != null) {
654
          invokeCallback<void>('${note}onTapCancel', onTapCancel!);
655
        }
656 657
        break;
      case kSecondaryButton:
658
        if (onSecondaryTapCancel != null) {
659
          invokeCallback<void>('${note}onSecondaryTapCancel', onSecondaryTapCancel!);
660
        }
661
        break;
662
      case kTertiaryButton:
663
        if (onTertiaryTapCancel != null) {
664
          invokeCallback<void>('${note}onTertiaryTapCancel', onTertiaryTapCancel!);
665
        }
666
        break;
667
      default:
668 669
    }
  }
670

671
  @override
672
  String get debugDescription => 'tap';
673
}