recognizer.dart 25.1 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 'dart:async';
7
import 'dart:collection';
8

9
import 'package:flutter/foundation.dart';
10
import 'package:vector_math/vector_math_64.dart';
11

12
import 'arena.dart';
13
import 'binding.dart';
14
import 'constants.dart';
15
import 'debug.dart';
16
import 'events.dart';
17
import 'gesture_settings.dart';
18
import 'pointer_router.dart';
19
import 'team.dart';
20

21
export 'pointer_router.dart' show PointerRouter;
22

23 24 25 26
/// Generic signature for callbacks passed to
/// [GestureRecognizer.invokeCallback]. This allows the
/// [GestureRecognizer.invokeCallback] mechanism to be generically used with
/// anonymous functions that return objects of particular types.
27
typedef RecognizerCallback<T> = T Function();
28

29 30 31 32
/// Configuration of offset passed to [DragStartDetails].
///
/// See also:
///
33 34
///  * [DragGestureRecognizer.dragStartBehavior], which gives an example for the
///  different behaviors.
35
enum DragStartBehavior {
36
  /// Set the initial offset at the position where the first down event was
37 38 39
  /// detected.
  down,

40 41
  /// Set the initial position at the position where this gesture recognizer
  /// won the arena.
42 43 44
  start,
}

45
/// The base class that all gesture recognizers inherit from.
46 47 48 49
///
/// Provides a basic API that can be used by classes that work with
/// gesture recognizers but don't care about the specific details of
/// the gestures recognizers themselves.
50 51 52
///
/// See also:
///
53 54
///  * [GestureDetector], the widget that is used to detect built-in gestures.
///  * [RawGestureDetector], the widget that is used to detect custom gestures.
55 56
///  * [debugPrintRecognizerCallbacksTrace], a flag that can be set to help
///    debug issues with gesture recognizers.
57
abstract class GestureRecognizer extends GestureArenaMember with DiagnosticableTreeMixin {
58 59 60 61
  /// Initializes the gesture recognizer.
  ///
  /// The argument is optional and is only used for debug purposes (e.g. in the
  /// [toString] serialization).
62
  ///
63 64 65
  /// {@template flutter.gestures.GestureRecognizer.supportedDevices}
  /// It's possible to limit this recognizer to a specific set of [PointerDeviceKind]s
  /// by providing the optional [supportedDevices] argument. If [supportedDevices] is null,
66 67
  /// the recognizer will accept pointer events from all device kinds.
  /// {@endtemplate}
68 69 70 71 72 73 74 75 76 77
  GestureRecognizer({
    this.debugOwner,
    @Deprecated(
      'Migrate to supportedDevices. '
      'This feature was deprecated after v2.3.0-1.0.pre.',
    )
    PointerDeviceKind? kind,
    Set<PointerDeviceKind>? supportedDevices,
  }) : assert(kind == null || supportedDevices == null),
       _supportedDevices = kind == null ? supportedDevices : <PointerDeviceKind>{ kind };
78 79 80 81 82

  /// The recognizer's owner.
  ///
  /// This is used in the [toString] serialization to report the object for which
  /// this gesture recognizer was created, to aid in debugging.
83
  final Object? debugOwner;
84

85 86 87 88
  /// Optional device specific configuration for device gestures that will
  /// take precedence over framework defaults.
  DeviceGestureSettings? gestureSettings;

89 90 91 92 93
  /// The kind of devices that are allowed to be recognized as provided by
  /// `supportedDevices` in the constructor, or the currently deprecated `kind`.
  /// These cannot both be set. If both are null, events from all device kinds will be
  /// tracked and recognized.
  final Set<PointerDeviceKind>? _supportedDevices;
94 95 96 97

  /// Holds a mapping between pointer IDs and the kind of devices they are
  /// coming from.
  final Map<int, PointerDeviceKind> _pointerToKind = <int, PointerDeviceKind>{};
98

99 100
  /// Registers a new pointer that might be relevant to this gesture
  /// detector.
Florian Loitsch's avatar
Florian Loitsch committed
101
  ///
102 103 104 105 106 107 108
  /// The owner of this gesture recognizer calls addPointer() with the
  /// PointerDownEvent of each pointer that should be considered for
  /// this gesture.
  ///
  /// It's the GestureRecognizer's responsibility to then add itself
  /// to the global pointer router (see [PointerRouter]) to receive
  /// subsequent events for this pointer, and to add the pointer to
109
  /// the global gesture arena manager (see [GestureArenaManager]) to track
110
  /// that pointer.
111 112 113 114
  ///
  /// This method is called for each and all pointers being added. In
  /// most cases, you want to override [addAllowedPointer] instead.
  void addPointer(PointerDownEvent event) {
115
    _pointerToKind[event.pointer] = event.kind;
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
    if (isPointerAllowed(event)) {
      addAllowedPointer(event);
    } else {
      handleNonAllowedPointer(event);
    }
  }

  /// Registers a new pointer that's been checked to be allowed by this gesture
  /// recognizer.
  ///
  /// Subclasses of [GestureRecognizer] are supposed to override this method
  /// instead of [addPointer] because [addPointer] will be called for each
  /// pointer being added while [addAllowedPointer] is only called for pointers
  /// that are allowed by this recognizer.
  @protected
  void addAllowedPointer(PointerDownEvent event) { }

  /// Handles a pointer being added that's not allowed by this recognizer.
  ///
  /// Subclasses can override this method and reject the gesture.
  ///
  /// See:
  /// - [OneSequenceGestureRecognizer.handleNonAllowedPointer].
  @protected
  void handleNonAllowedPointer(PointerDownEvent event) { }

  /// Checks whether or not a pointer is allowed to be tracked by this recognizer.
  @protected
  bool isPointerAllowed(PointerDownEvent event) {
    // Currently, it only checks for device kind. But in the future we could check
    // for other things e.g. mouse button.
147
    return _supportedDevices == null || _supportedDevices!.contains(event.kind);
148 149 150 151 152 153 154 155 156
  }

  /// For a given pointer ID, returns the device kind associated with it.
  ///
  /// The pointer ID is expected to be a valid one i.e. an event was received
  /// with that pointer ID.
  @protected
  PointerDeviceKind getKindForPointer(int pointer) {
    assert(_pointerToKind.containsKey(pointer));
157
    return _pointerToKind[pointer]!;
158
  }
159

Florian Loitsch's avatar
Florian Loitsch committed
160 161
  /// Releases any resources used by the object.
  ///
162 163
  /// This method is called by the owner of this gesture recognizer
  /// when the object is no longer needed (e.g. when a gesture
164
  /// recognizer is being unregistered from a [GestureDetector], the
165
  /// GestureDetector widget calls this method).
166
  @mustCallSuper
167 168
  void dispose() { }

169 170
  /// Returns a very short pretty description of the gesture that the
  /// recognizer looks for, like 'tap' or 'horizontal drag'.
171
  String get debugDescription;
172

173 174
  /// Invoke a callback provided by the application, catching and logging any
  /// exceptions.
175 176
  ///
  /// The `name` argument is ignored except when reporting exceptions.
177 178 179 180 181
  ///
  /// The `debugReport` argument is optional and is used when
  /// [debugPrintRecognizerCallbacksTrace] is true. If specified, it must be a
  /// callback that returns a string describing useful debugging information,
  /// e.g. the arguments passed to the callback.
182
  @protected
183
  @pragma('vm:notify-debugger-on-exception')
184
  T? invokeCallback<T>(String name, RecognizerCallback<T> callback, { String Function()? debugReport }) {
185
    assert(callback != null);
186
    T? result;
187
    try {
188 189
      assert(() {
        if (debugPrintRecognizerCallbacksTrace) {
190
          final String? report = debugReport != null ? debugReport() : null;
191 192
          // The 19 in the line below is the width of the prefix used by
          // _debugLogDiagnostic in arena.dart.
193
          final String prefix = debugPrintGestureArenaDiagnostics ? '${' ' * 19}❙ ' : '';
194 195 196
          debugPrint('$prefix$this calling $name callback.${ report?.isNotEmpty == true ? " $report" : "" }');
        }
        return true;
197
      }());
198 199
      result = callback();
    } catch (exception, stack) {
200
      InformationCollector? collector;
201
      assert(() {
202 203 204 205
        collector = () => <DiagnosticsNode>[
          StringProperty('Handler', name),
          DiagnosticsProperty<GestureRecognizer>('Recognizer', this, style: DiagnosticsTreeStyle.errorProperty),
        ];
206 207
        return true;
      }());
208
      FlutterError.reportError(FlutterErrorDetails(
209 210 211
        exception: exception,
        stack: stack,
        library: 'gesture',
212
        context: ErrorDescription('while handling a gesture'),
213
        informationCollector: collector,
214 215 216 217
      ));
    }
    return result;
  }
218 219

  @override
220 221
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
222
    properties.add(DiagnosticsProperty<Object>('debugOwner', debugOwner, defaultValue: null));
223
  }
224 225
}

226 227 228 229 230 231 232 233
/// Base class for gesture recognizers that can only recognize one
/// gesture at a time. For example, a single [TapGestureRecognizer]
/// can never recognize two taps happening simultaneously, even if
/// multiple pointers are placed on the same widget.
///
/// This is in contrast to, for instance, [MultiTapGestureRecognizer],
/// which manages each pointer independently and can consider multiple
/// simultaneous touches to each result in a separate tap.
234
abstract class OneSequenceGestureRecognizer extends GestureRecognizer {
235
  /// Initialize the object.
236
  ///
237
  /// {@macro flutter.gestures.GestureRecognizer.supportedDevices}
238
  OneSequenceGestureRecognizer({
239
    Object? debugOwner,
240 241 242 243
    @Deprecated(
      'Migrate to supportedDevices. '
      'This feature was deprecated after v2.3.0-1.0.pre.',
    )
244
    PointerDeviceKind? kind,
245 246 247 248 249 250
    Set<PointerDeviceKind>? supportedDevices,
  }) : super(
         debugOwner: debugOwner,
         kind: kind,
         supportedDevices: supportedDevices,
       );
251

252
  final Map<int, GestureArenaEntry> _entries = <int, GestureArenaEntry>{};
253
  final Set<int> _trackedPointers = HashSet<int>();
254

255
  @override
256 257 258 259 260 261 262
  @protected
  void addAllowedPointer(PointerDownEvent event) {
    startTrackingPointer(event.pointer, event.transform);
  }

  @override
  @protected
263 264 265 266
  void handleNonAllowedPointer(PointerDownEvent event) {
    resolve(GestureDisposition.rejected);
  }

267
  /// Called when a pointer event is routed to this recognizer.
268 269 270 271 272 273 274 275 276 277 278 279 280 281
  ///
  /// This will be called for every pointer event while the pointer is being
  /// tracked. Typically, this recognizer will start tracking the pointer in
  /// [addAllowedPointer], which means that [handleEvent] will be called
  /// starting with the [PointerDownEvent] that was passed to [addAllowedPointer].
  ///
  /// See also:
  ///
  ///  * [startTrackingPointer], which causes pointer events to be routed to
  ///    this recognizer.
  ///  * [stopTrackingPointer], which stops events from being routed to this
  ///    recognizer.
  ///  * [stopTrackingIfPointerNoLongerDown], which conditionally stops events
  ///    from being routed to this recognizer.
282
  @protected
Ian Hickson's avatar
Ian Hickson committed
283
  void handleEvent(PointerEvent event);
284 285

  @override
286
  void acceptGesture(int pointer) { }
287 288

  @override
289
  void rejectGesture(int pointer) { }
290

291
  /// Called when the number of pointers this recognizer is tracking changes from one to zero.
292 293 294
  ///
  /// The given pointer ID is the ID of the last pointer this recognizer was
  /// tracking.
295
  @protected
296
  void didStopTrackingLastPointer(int pointer);
297

298 299
  /// Resolves this recognizer's participation in each gesture arena with the
  /// given disposition.
300 301
  @protected
  @mustCallSuper
302
  void resolve(GestureDisposition disposition) {
303
    final List<GestureArenaEntry> localEntries = List<GestureArenaEntry>.of(_entries.values);
304
    _entries.clear();
305
    for (final GestureArenaEntry entry in localEntries)
306 307 308
      entry.resolve(disposition);
  }

309 310 311 312 313
  /// Resolves this recognizer's participation in the given gesture arena with
  /// the given disposition.
  @protected
  @mustCallSuper
  void resolvePointer(int pointer, GestureDisposition disposition) {
314
    final GestureArenaEntry? entry = _entries[pointer];
315
    if (entry != null) {
316
      _entries.remove(pointer);
317
      entry.resolve(disposition);
318 319 320
    }
  }

321
  @override
322 323
  void dispose() {
    resolve(GestureDisposition.rejected);
324
    for (final int pointer in _trackedPointers)
325
      GestureBinding.instance!.pointerRouter.removeRoute(pointer, handleEvent);
326 327
    _trackedPointers.clear();
    assert(_entries.isEmpty);
328
    super.dispose();
329 330
  }

331 332 333 334 335 336 337 338 339 340
  /// The team that this recognizer belongs to, if any.
  ///
  /// If [team] is null, this recognizer competes directly in the
  /// [GestureArenaManager] to recognize a sequence of pointer events as a
  /// gesture. If [team] is non-null, this recognizer competes in the arena in
  /// a group with other recognizers on the same team.
  ///
  /// A recognizer can be assigned to a team only when it is not participating
  /// in the arena. For example, a common time to assign a recognizer to a team
  /// is shortly after creating the recognizer.
341 342
  GestureArenaTeam? get team => _team;
  GestureArenaTeam? _team;
343
  /// The [team] can only be set once.
344
  set team(GestureArenaTeam? value) {
345
    assert(value != null);
346 347 348 349 350 351 352 353
    assert(_entries.isEmpty);
    assert(_trackedPointers.isEmpty);
    assert(_team == null);
    _team = value;
  }

  GestureArenaEntry _addPointerToArena(int pointer) {
    if (_team != null)
354
      return _team!.add(pointer, this);
355
    return GestureBinding.instance!.gestureArena.add(pointer, this);
356 357
  }

358 359
  /// Causes events related to the given pointer ID to be routed to this recognizer.
  ///
360 361 362 363 364
  /// The pointer events are transformed according to `transform` and then delivered
  /// to [handleEvent]. The value for the `transform` argument is usually obtained
  /// from [PointerDownEvent.transform] to transform the events from the global
  /// coordinate space into the coordinate space of the event receiver. It may be
  /// null if no transformation is necessary.
365 366
  ///
  /// Use [stopTrackingPointer] to remove the route added by this function.
367 368 369 370 371
  ///
  /// This method also adds this recognizer (or its [team] if it's non-null) to
  /// the gesture arena for the specified pointer.
  ///
  /// This is called by [OneSequenceGestureRecognizer.addAllowedPointer].
372
  @protected
373
  void startTrackingPointer(int pointer, [Matrix4? transform]) {
374
    GestureBinding.instance!.pointerRouter.addRoute(pointer, handleEvent, transform);
375
    _trackedPointers.add(pointer);
376
    assert(!_entries.containsValue(pointer));
377
    _entries[pointer] = _addPointerToArena(pointer);
378 379
  }

380 381 382 383 384 385
  /// Stops events related to the given pointer ID from being routed to this recognizer.
  ///
  /// If this function reduces the number of tracked pointers to zero, it will
  /// call [didStopTrackingLastPointer] synchronously.
  ///
  /// Use [startTrackingPointer] to add the routes in the first place.
386
  @protected
387
  void stopTrackingPointer(int pointer) {
388
    if (_trackedPointers.contains(pointer)) {
389
      GestureBinding.instance!.pointerRouter.removeRoute(pointer, handleEvent);
390 391 392 393
      _trackedPointers.remove(pointer);
      if (_trackedPointers.isEmpty)
        didStopTrackingLastPointer(pointer);
    }
394 395
  }

396 397
  /// Stops tracking the pointer associated with the given event if the event is
  /// a [PointerUpEvent] or a [PointerCancelEvent] event.
398
  @protected
Ian Hickson's avatar
Ian Hickson committed
399 400
  void stopTrackingIfPointerNoLongerDown(PointerEvent event) {
    if (event is PointerUpEvent || event is PointerCancelEvent)
401 402 403 404
      stopTrackingPointer(event.pointer);
  }
}

405 406
/// The possible states of a [PrimaryPointerGestureRecognizer].
///
407
/// The recognizer advances from [ready] to [possible] when it starts tracking a
408 409 410 411 412 413 414 415 416
/// primary pointer. Where it advances from there depends on how the gesture is
/// resolved for that pointer:
///
///  * If the primary pointer is resolved by the gesture winning the arena, the
///    recognizer stays in the [possible] state as long as it continues to track
///    a pointer.
///  * If the primary pointer is resolved by the gesture being rejected and
///    losing the arena, the recognizer's state advances to [defunct].
///
417 418
/// Once the recognizer has stopped tracking any remaining pointers, the
/// recognizer returns to [ready].
419
enum GestureRecognizerState {
420
  /// The recognizer is ready to start recognizing a gesture.
421
  ready,
422

423
  /// The sequence of pointer events seen thus far is consistent with the
424 425
  /// gesture the recognizer is attempting to recognize but the gesture has not
  /// been accepted definitively.
426
  possible,
427

428
  /// Further pointer events cannot cause this recognizer to recognize the
429 430 431
  /// gesture until the recognizer returns to the [ready] state (typically when
  /// all the pointers the recognizer is tracking are removed from the screen).
  defunct,
432 433
}

434
/// A base class for gesture recognizers that track a single primary pointer.
435
///
436 437 438 439 440 441
/// Gestures based on this class will stop tracking the gesture if the primary
/// pointer travels beyond [preAcceptSlopTolerance] or [postAcceptSlopTolerance]
/// pixels from the original contact point of the gesture.
///
/// If the [preAcceptSlopTolerance] was breached before the gesture was accepted
/// in the gesture arena, the gesture will be rejected.
442
abstract class PrimaryPointerGestureRecognizer extends OneSequenceGestureRecognizer {
443
  /// Initializes the [deadline] field during construction of subclasses.
444
  ///
445
  /// {@macro flutter.gestures.GestureRecognizer.supportedDevices}
446 447
  PrimaryPointerGestureRecognizer({
    this.deadline,
448 449
    this.preAcceptSlopTolerance = kTouchSlop,
    this.postAcceptSlopTolerance = kTouchSlop,
450
    Object? debugOwner,
451 452 453 454
    @Deprecated(
      'Migrate to supportedDevices. '
      'This feature was deprecated after v2.3.0-1.0.pre.',
    )
455
    PointerDeviceKind? kind,
456
    Set<PointerDeviceKind>? supportedDevices,
457 458 459 460 461 462 463 464
  }) : assert(
         preAcceptSlopTolerance == null || preAcceptSlopTolerance >= 0,
         'The preAcceptSlopTolerance must be positive or null',
       ),
       assert(
         postAcceptSlopTolerance == null || postAcceptSlopTolerance >= 0,
         'The postAcceptSlopTolerance must be positive or null',
       ),
465 466 467 468 469
       super(
         debugOwner: debugOwner,
         kind: kind,
         supportedDevices: supportedDevices,
       );
470

471 472
  /// If non-null, the recognizer will call [didExceedDeadline] after this
  /// amount of time has elapsed since starting to track the primary pointer.
473 474 475
  ///
  /// The [didExceedDeadline] will not be called if the primary pointer is
  /// accepted, rejected, or all pointers are up or canceled before [deadline].
476
  final Duration? deadline;
477

478 479 480 481 482 483 484
  /// The maximum distance in logical pixels the gesture is allowed to drift
  /// from the initial touch down position before the gesture is accepted.
  ///
  /// Drifting past the allowed slop amount causes the gesture to be rejected.
  ///
  /// Can be null to indicate that the gesture can drift for any distance.
  /// Defaults to 18 logical pixels.
485
  final double? preAcceptSlopTolerance;
486 487 488 489 490 491 492 493 494

  /// The maximum distance in logical pixels the gesture is allowed to drift
  /// after the gesture has been accepted.
  ///
  /// Drifting past the allowed slop amount causes the gesture to stop tracking
  /// and signaling subsequent callbacks.
  ///
  /// Can be null to indicate that the gesture can drift for any distance.
  /// Defaults to 18 logical pixels.
495
  final double? postAcceptSlopTolerance;
496

497 498 499
  /// The current state of the recognizer.
  ///
  /// See [GestureRecognizerState] for a description of the states.
500 501
  GestureRecognizerState get state => _state;
  GestureRecognizerState _state = GestureRecognizerState.ready;
502 503

  /// The ID of the primary pointer this recognizer is tracking.
504 505 506 507 508 509 510 511 512 513
  ///
  /// If this recognizer is no longer tracking any pointers, this field holds
  /// the ID of the primary pointer this recognizer was most recently tracking.
  /// This enables the recognizer to know which pointer it was most recently
  /// tracking when [acceptGesture] or [rejectGesture] is called (which may be
  /// called after the recognizer is no longer tracking a pointer if, e.g.
  /// [GestureArenaManager.hold] has been called, or if there are other
  /// recognizers keeping the arena open).
  int? get primaryPointer => _primaryPointer;
  int? _primaryPointer;
514

515
  /// The location at which the primary pointer contacted the screen.
516 517 518 519 520
  ///
  /// This will only be non-null while this recognizer is tracking at least
  /// one pointer.
  OffsetPair? get initialPosition => _initialPosition;
  OffsetPair? _initialPosition;
521

522 523 524
  // Whether this pointer is accepted by winning the arena or as defined by
  // a subclass calling acceptGesture.
  bool _gestureAccepted = false;
525
  Timer? _timer;
526

527
  @override
528
  void addAllowedPointer(PointerDownEvent event) {
529
    super.addAllowedPointer(event);
530
    if (state == GestureRecognizerState.ready) {
531 532 533
      _state = GestureRecognizerState.possible;
      _primaryPointer = event.pointer;
      _initialPosition = OffsetPair(local: event.localPosition, global: event.position);
534
      if (deadline != null)
535
        _timer = Timer(deadline!, () => didExceedDeadlineWithEvent(event));
536 537 538
    }
  }

539 540 541 542 543 544 545
  @override
  void handleNonAllowedPointer(PointerDownEvent event) {
    if (!_gestureAccepted) {
      super.handleNonAllowedPointer(event);
    }
  }

546
  @override
Ian Hickson's avatar
Ian Hickson committed
547
  void handleEvent(PointerEvent event) {
548
    assert(state != GestureRecognizerState.ready);
xster's avatar
xster committed
549
    if (state == GestureRecognizerState.possible && event.pointer == primaryPointer) {
550 551 552
      final bool isPreAcceptSlopPastTolerance =
          !_gestureAccepted &&
          preAcceptSlopTolerance != null &&
553
          _getGlobalDistance(event) > preAcceptSlopTolerance!;
554 555 556
      final bool isPostAcceptSlopPastTolerance =
          _gestureAccepted &&
          postAcceptSlopTolerance != null &&
557
          _getGlobalDistance(event) > postAcceptSlopTolerance!;
558 559

      if (event is PointerMoveEvent && (isPreAcceptSlopPastTolerance || isPostAcceptSlopPastTolerance)) {
560
        resolve(GestureDisposition.rejected);
561
        stopTrackingPointer(primaryPointer!);
562
      } else {
563
        handlePrimaryPointer(event);
564
      }
565 566 567 568 569
    }
    stopTrackingIfPointerNoLongerDown(event);
  }

  /// Override to provide behavior for the primary pointer when the gesture is still possible.
570
  @protected
Ian Hickson's avatar
Ian Hickson committed
571
  void handlePrimaryPointer(PointerEvent event);
572

Florian Loitsch's avatar
Florian Loitsch committed
573
  /// Override to be notified when [deadline] is exceeded.
574
  ///
575
  /// You must override this method or [didExceedDeadlineWithEvent] if you
576 577
  /// supply a [deadline]. Subclasses that override this method must _not_
  /// call `super.didExceedDeadline()`.
578
  @protected
579 580 581 582
  void didExceedDeadline() {
    assert(deadline == null);
  }

583 584 585 586
  /// Same as [didExceedDeadline] but receives the [event] that initiated the
  /// gesture.
  ///
  /// You must override this method or [didExceedDeadline] if you supply a
587 588
  /// [deadline]. Subclasses that override this method must _not_ call
  /// `super.didExceedDeadlineWithEvent(event)`.
589 590 591 592 593
  @protected
  void didExceedDeadlineWithEvent(PointerDownEvent event) {
    didExceedDeadline();
  }

594 595
  @override
  void acceptGesture(int pointer) {
596 597 598 599
    if (pointer == primaryPointer) {
      _stopTimer();
      _gestureAccepted = true;
    }
600 601
  }

602 603
  @override
  void rejectGesture(int pointer) {
xster's avatar
xster committed
604
    if (pointer == primaryPointer && state == GestureRecognizerState.possible) {
605
      _stopTimer();
606
      _state = GestureRecognizerState.defunct;
607
    }
608 609
  }

610
  @override
611
  void didStopTrackingLastPointer(int pointer) {
612
    assert(state != GestureRecognizerState.ready);
613
    _stopTimer();
614 615 616
    _state = GestureRecognizerState.ready;
    _initialPosition = null;
    _gestureAccepted = false;
617 618
  }

619
  @override
620 621 622 623 624 625 626
  void dispose() {
    _stopTimer();
    super.dispose();
  }

  void _stopTimer() {
    if (_timer != null) {
627
      _timer!.cancel();
628 629 630 631
      _timer = null;
    }
  }

632
  double _getGlobalDistance(PointerEvent event) {
633
    final Offset offset = event.position - initialPosition!.global;
634 635 636
    return offset.distance;
  }

637
  @override
638 639
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
640
    properties.add(EnumProperty<GestureRecognizerState>('state', state));
641
  }
642
}
643 644 645 646 647 648

/// A container for a [local] and [global] [Offset] pair.
///
/// Usually, the [global] [Offset] is in the coordinate space of the screen
/// after conversion to logical pixels and the [local] offset is the same
/// [Offset], but transformed to a local coordinate space.
649
@immutable
650 651 652
class OffsetPair {
  /// Creates a [OffsetPair] combining a [local] and [global] [Offset].
  const OffsetPair({
653 654
    required this.local,
    required this.global,
655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695
  });

  /// Creates a [OffsetPair] from [PointerEvent.localPosition] and
  /// [PointerEvent.position].
  factory OffsetPair.fromEventPosition(PointerEvent event) {
    return OffsetPair(local: event.localPosition, global: event.position);
  }

  /// Creates a [OffsetPair] from [PointerEvent.localDelta] and
  /// [PointerEvent.delta].
  factory OffsetPair.fromEventDelta(PointerEvent event) {
    return OffsetPair(local: event.localDelta, global: event.delta);
  }

  /// A [OffsetPair] where both [Offset]s are [Offset.zero].
  static const OffsetPair zero = OffsetPair(local: Offset.zero, global: Offset.zero);

  /// The [Offset] in the local coordinate space.
  final Offset local;

  /// The [Offset] in the global coordinate space after conversion to logical
  /// pixels.
  final Offset global;

  /// Adds the `other.global` to [global] and `other.local` to [local].
  OffsetPair operator+(OffsetPair other) {
    return OffsetPair(
      local: local + other.local,
      global: global + other.global,
    );
  }

  /// Subtracts the `other.global` from [global] and `other.local` from [local].
  OffsetPair operator-(OffsetPair other) {
    return OffsetPair(
      local: local - other.local,
      global: global - other.global,
    );
  }

  @override
696
  String toString() => '${objectRuntimeType(this, 'OffsetPair')}(local: $local, global: $global)';
697
}