recognizer.dart 24.9 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 'pointer_router.dart';
18
import 'team.dart';
19

20
export 'pointer_router.dart' show PointerRouter;
21

22 23 24 25
/// 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.
26
typedef RecognizerCallback<T> = T Function();
27

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

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

44
/// The base class that all gesture recognizers inherit from.
45 46 47 48
///
/// 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.
49 50 51
///
/// See also:
///
52 53
///  * [GestureDetector], the widget that is used to detect built-in gestures.
///  * [RawGestureDetector], the widget that is used to detect custom gestures.
54 55
///  * [debugPrintRecognizerCallbacksTrace], a flag that can be set to help
///    debug issues with gesture recognizers.
56
abstract class GestureRecognizer extends GestureArenaMember with DiagnosticableTreeMixin {
57 58 59 60
  /// Initializes the gesture recognizer.
  ///
  /// The argument is optional and is only used for debug purposes (e.g. in the
  /// [toString] serialization).
61
  ///
62 63 64
  /// {@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,
65 66
  /// the recognizer will accept pointer events from all device kinds.
  /// {@endtemplate}
67 68 69 70 71 72 73 74 75 76
  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 };
77 78 79 80 81

  /// 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.
82
  final Object? debugOwner;
83

84 85 86 87 88
  /// 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;
89 90 91 92

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

94 95
  /// Registers a new pointer that might be relevant to this gesture
  /// detector.
Florian Loitsch's avatar
Florian Loitsch committed
96
  ///
97 98 99 100 101 102 103
  /// 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
104
  /// the global gesture arena manager (see [GestureArenaManager]) to track
105
  /// that pointer.
106 107 108 109
  ///
  /// This method is called for each and all pointers being added. In
  /// most cases, you want to override [addAllowedPointer] instead.
  void addPointer(PointerDownEvent event) {
110
    _pointerToKind[event.pointer] = event.kind;
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 139 140 141
    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.
142
    return _supportedDevices == null || _supportedDevices!.contains(event.kind);
143 144 145 146 147 148 149 150 151
  }

  /// 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));
152
    return _pointerToKind[pointer]!;
153
  }
154

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

164 165
  /// Returns a very short pretty description of the gesture that the
  /// recognizer looks for, like 'tap' or 'horizontal drag'.
166
  String get debugDescription;
167

168 169
  /// Invoke a callback provided by the application, catching and logging any
  /// exceptions.
170 171
  ///
  /// The `name` argument is ignored except when reporting exceptions.
172 173 174 175 176
  ///
  /// 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.
177
  @protected
178
  @pragma('vm:notify-debugger-on-exception')
179
  T? invokeCallback<T>(String name, RecognizerCallback<T> callback, { String Function()? debugReport }) {
180
    assert(callback != null);
181
    T? result;
182
    try {
183 184
      assert(() {
        if (debugPrintRecognizerCallbacksTrace) {
185
          final String? report = debugReport != null ? debugReport() : null;
186 187
          // The 19 in the line below is the width of the prefix used by
          // _debugLogDiagnostic in arena.dart.
188
          final String prefix = debugPrintGestureArenaDiagnostics ? '${' ' * 19}❙ ' : '';
189 190 191
          debugPrint('$prefix$this calling $name callback.${ report?.isNotEmpty == true ? " $report" : "" }');
        }
        return true;
192
      }());
193 194
      result = callback();
    } catch (exception, stack) {
195
      InformationCollector? collector;
196 197 198 199 200 201 202
      assert(() {
        collector = () sync* {
          yield StringProperty('Handler', name);
          yield DiagnosticsProperty<GestureRecognizer>('Recognizer', this, style: DiagnosticsTreeStyle.errorProperty);
        };
        return true;
      }());
203
      FlutterError.reportError(FlutterErrorDetails(
204 205 206
        exception: exception,
        stack: stack,
        library: 'gesture',
207
        context: ErrorDescription('while handling a gesture'),
208
        informationCollector: collector,
209 210 211 212
      ));
    }
    return result;
  }
213 214

  @override
215 216
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
217
    properties.add(DiagnosticsProperty<Object>('debugOwner', debugOwner, defaultValue: null));
218
  }
219 220
}

221 222 223 224 225 226 227 228
/// 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.
229
abstract class OneSequenceGestureRecognizer extends GestureRecognizer {
230
  /// Initialize the object.
231
  ///
232
  /// {@macro flutter.gestures.GestureRecognizer.supportedDevices}
233
  OneSequenceGestureRecognizer({
234
    Object? debugOwner,
235 236 237 238
    @Deprecated(
      'Migrate to supportedDevices. '
      'This feature was deprecated after v2.3.0-1.0.pre.',
    )
239
    PointerDeviceKind? kind,
240 241 242 243 244 245
    Set<PointerDeviceKind>? supportedDevices,
  }) : super(
         debugOwner: debugOwner,
         kind: kind,
         supportedDevices: supportedDevices,
       );
246

247
  final Map<int, GestureArenaEntry> _entries = <int, GestureArenaEntry>{};
248
  final Set<int> _trackedPointers = HashSet<int>();
249

250
  @override
251 252 253 254 255 256 257
  @protected
  void addAllowedPointer(PointerDownEvent event) {
    startTrackingPointer(event.pointer, event.transform);
  }

  @override
  @protected
258 259 260 261
  void handleNonAllowedPointer(PointerDownEvent event) {
    resolve(GestureDisposition.rejected);
  }

262
  /// Called when a pointer event is routed to this recognizer.
263 264 265 266 267 268 269 270 271 272 273 274 275 276
  ///
  /// 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.
277
  @protected
Ian Hickson's avatar
Ian Hickson committed
278
  void handleEvent(PointerEvent event);
279 280

  @override
281
  void acceptGesture(int pointer) { }
282 283

  @override
284
  void rejectGesture(int pointer) { }
285

286
  /// Called when the number of pointers this recognizer is tracking changes from one to zero.
287 288 289
  ///
  /// The given pointer ID is the ID of the last pointer this recognizer was
  /// tracking.
290
  @protected
291
  void didStopTrackingLastPointer(int pointer);
292

293 294
  /// Resolves this recognizer's participation in each gesture arena with the
  /// given disposition.
295 296
  @protected
  @mustCallSuper
297
  void resolve(GestureDisposition disposition) {
298
    final List<GestureArenaEntry> localEntries = List<GestureArenaEntry>.from(_entries.values);
299
    _entries.clear();
300
    for (final GestureArenaEntry entry in localEntries)
301 302 303
      entry.resolve(disposition);
  }

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

316
  @override
317 318
  void dispose() {
    resolve(GestureDisposition.rejected);
319
    for (final int pointer in _trackedPointers)
320
      GestureBinding.instance!.pointerRouter.removeRoute(pointer, handleEvent);
321 322
    _trackedPointers.clear();
    assert(_entries.isEmpty);
323
    super.dispose();
324 325
  }

326 327 328 329 330 331 332 333 334 335
  /// 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.
336 337
  GestureArenaTeam? get team => _team;
  GestureArenaTeam? _team;
338
  /// The [team] can only be set once.
339
  set team(GestureArenaTeam? value) {
340
    assert(value != null);
341 342 343 344 345 346 347 348
    assert(_entries.isEmpty);
    assert(_trackedPointers.isEmpty);
    assert(_team == null);
    _team = value;
  }

  GestureArenaEntry _addPointerToArena(int pointer) {
    if (_team != null)
349 350
      return _team!.add(pointer, this);
    return GestureBinding.instance!.gestureArena.add(pointer, this);
351 352
  }

353 354
  /// Causes events related to the given pointer ID to be routed to this recognizer.
  ///
355 356 357 358 359
  /// 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.
360 361
  ///
  /// Use [stopTrackingPointer] to remove the route added by this function.
362 363 364 365 366
  ///
  /// 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].
367
  @protected
368 369
  void startTrackingPointer(int pointer, [Matrix4? transform]) {
    GestureBinding.instance!.pointerRouter.addRoute(pointer, handleEvent, transform);
370
    _trackedPointers.add(pointer);
371
    assert(!_entries.containsValue(pointer));
372
    _entries[pointer] = _addPointerToArena(pointer);
373 374
  }

375 376 377 378 379 380
  /// 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.
381
  @protected
382
  void stopTrackingPointer(int pointer) {
383
    if (_trackedPointers.contains(pointer)) {
384
      GestureBinding.instance!.pointerRouter.removeRoute(pointer, handleEvent);
385 386 387 388
      _trackedPointers.remove(pointer);
      if (_trackedPointers.isEmpty)
        didStopTrackingLastPointer(pointer);
    }
389 390
  }

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

400 401
/// The possible states of a [PrimaryPointerGestureRecognizer].
///
402
/// The recognizer advances from [ready] to [possible] when it starts tracking a
403 404 405 406 407 408 409 410 411
/// 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].
///
412 413
/// Once the recognizer has stopped tracking any remaining pointers, the
/// recognizer returns to [ready].
414
enum GestureRecognizerState {
415
  /// The recognizer is ready to start recognizing a gesture.
416
  ready,
417

418
  /// The sequence of pointer events seen thus far is consistent with the
419 420
  /// gesture the recognizer is attempting to recognize but the gesture has not
  /// been accepted definitively.
421
  possible,
422

423
  /// Further pointer events cannot cause this recognizer to recognize the
424 425 426
  /// gesture until the recognizer returns to the [ready] state (typically when
  /// all the pointers the recognizer is tracking are removed from the screen).
  defunct,
427 428
}

429
/// A base class for gesture recognizers that track a single primary pointer.
430
///
431 432 433 434 435 436
/// 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.
437
abstract class PrimaryPointerGestureRecognizer extends OneSequenceGestureRecognizer {
438
  /// Initializes the [deadline] field during construction of subclasses.
439
  ///
440
  /// {@macro flutter.gestures.GestureRecognizer.supportedDevices}
441 442
  PrimaryPointerGestureRecognizer({
    this.deadline,
443 444
    this.preAcceptSlopTolerance = kTouchSlop,
    this.postAcceptSlopTolerance = kTouchSlop,
445
    Object? debugOwner,
446 447 448 449
    @Deprecated(
      'Migrate to supportedDevices. '
      'This feature was deprecated after v2.3.0-1.0.pre.',
    )
450
    PointerDeviceKind? kind,
451
    Set<PointerDeviceKind>? supportedDevices,
452 453 454 455 456 457 458 459
  }) : assert(
         preAcceptSlopTolerance == null || preAcceptSlopTolerance >= 0,
         'The preAcceptSlopTolerance must be positive or null',
       ),
       assert(
         postAcceptSlopTolerance == null || postAcceptSlopTolerance >= 0,
         'The postAcceptSlopTolerance must be positive or null',
       ),
460 461 462 463 464
       super(
         debugOwner: debugOwner,
         kind: kind,
         supportedDevices: supportedDevices,
       );
465

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

473 474 475 476 477 478 479
  /// 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.
480
  final double? preAcceptSlopTolerance;
481 482 483 484 485 486 487 488 489

  /// 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.
490
  final double? postAcceptSlopTolerance;
491

492 493 494
  /// The current state of the recognizer.
  ///
  /// See [GestureRecognizerState] for a description of the states.
495 496
  GestureRecognizerState get state => _state;
  GestureRecognizerState _state = GestureRecognizerState.ready;
497 498

  /// The ID of the primary pointer this recognizer is tracking.
499 500 501 502 503 504 505 506 507 508
  ///
  /// 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;
509

510
  /// The location at which the primary pointer contacted the screen.
511 512 513 514 515
  ///
  /// This will only be non-null while this recognizer is tracking at least
  /// one pointer.
  OffsetPair? get initialPosition => _initialPosition;
  OffsetPair? _initialPosition;
516

517 518 519
  // Whether this pointer is accepted by winning the arena or as defined by
  // a subclass calling acceptGesture.
  bool _gestureAccepted = false;
520
  Timer? _timer;
521

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

534 535 536 537 538 539 540
  @override
  void handleNonAllowedPointer(PointerDownEvent event) {
    if (!_gestureAccepted) {
      super.handleNonAllowedPointer(event);
    }
  }

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

      if (event is PointerMoveEvent && (isPreAcceptSlopPastTolerance || isPostAcceptSlopPastTolerance)) {
555
        resolve(GestureDisposition.rejected);
556
        stopTrackingPointer(primaryPointer!);
557
      } else {
558
        handlePrimaryPointer(event);
559
      }
560 561 562 563 564
    }
    stopTrackingIfPointerNoLongerDown(event);
  }

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

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

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

589 590
  @override
  void acceptGesture(int pointer) {
591 592 593 594
    if (pointer == primaryPointer) {
      _stopTimer();
      _gestureAccepted = true;
    }
595 596
  }

597 598
  @override
  void rejectGesture(int pointer) {
xster's avatar
xster committed
599
    if (pointer == primaryPointer && state == GestureRecognizerState.possible) {
600
      _stopTimer();
601
      _state = GestureRecognizerState.defunct;
602
    }
603 604
  }

605
  @override
606
  void didStopTrackingLastPointer(int pointer) {
607
    assert(state != GestureRecognizerState.ready);
608
    _stopTimer();
609 610 611
    _state = GestureRecognizerState.ready;
    _initialPosition = null;
    _gestureAccepted = false;
612 613
  }

614
  @override
615 616 617 618 619 620 621
  void dispose() {
    _stopTimer();
    super.dispose();
  }

  void _stopTimer() {
    if (_timer != null) {
622
      _timer!.cancel();
623 624 625 626
      _timer = null;
    }
  }

627
  double _getGlobalDistance(PointerEvent event) {
628
    final Offset offset = event.position - initialPosition!.global;
629 630 631
    return offset.distance;
  }

632
  @override
633 634
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
635
    properties.add(EnumProperty<GestureRecognizerState>('state', state));
636
  }
637
}
638 639 640 641 642 643

/// 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.
644
@immutable
645 646 647
class OffsetPair {
  /// Creates a [OffsetPair] combining a [local] and [global] [Offset].
  const OffsetPair({
648 649
    required this.local,
    required this.global,
650 651 652 653 654 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
  });

  /// 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
691
  String toString() => '${objectRuntimeType(this, 'OffsetPair')}(local: $local, global: $global)';
692
}