recognizer.dart 9.41 KB
Newer Older
1 2 3 4 5
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:async';
6
import 'dart:collection';
Hixie's avatar
Hixie committed
7
import 'dart:ui' show Point, Offset;
8

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

12
import 'arena.dart';
13
import 'binding.dart';
14
import 'constants.dart';
15
import 'events.dart';
16
import 'pointer_router.dart';
17

18
export 'pointer_router.dart' show PointerRouter;
19

20 21
typedef T RecognizerCallback<T>();

22 23 24 25 26
/// The base class that all GestureRecognizers should inherit from.
///
/// 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.
27
abstract class GestureRecognizer extends GestureArenaMember {
28 29
  /// Registers a new pointer that might be relevant to this gesture
  /// detector.
Florian Loitsch's avatar
Florian Loitsch committed
30
  ///
31 32 33 34 35 36 37
  /// 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
38
  /// the global gesture arena manager (see [GestureArenaManager]) to track
39
  /// that pointer.
Ian Hickson's avatar
Ian Hickson committed
40
  void addPointer(PointerDownEvent event);
41

Florian Loitsch's avatar
Florian Loitsch committed
42 43
  /// Releases any resources used by the object.
  ///
44 45
  /// This method is called by the owner of this gesture recognizer
  /// when the object is no longer needed (e.g. when a gesture
46
  /// recognizer is being unregistered from a [GestureDetector], the
47
  /// GestureDetector widget calls this method).
48
  @mustCallSuper
49 50
  void dispose() { }

51 52 53
  /// Returns a very short pretty description of the gesture that the
  /// recognizer looks for, like 'tap' or 'horizontal drag'.
  String toStringShort() => toString();
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75

  /// Invoke a callback provided by the application and log any exceptions.
  @protected
  dynamic/*=T*/ invokeCallback/*<T>*/(String name, RecognizerCallback<dynamic/*=T*/> callback) {
    dynamic/*=T*/ result;
    try {
      result = callback();
    } catch (exception, stack) {
      FlutterError.reportError(new FlutterErrorDetails(
        exception: exception,
        stack: stack,
        library: 'gesture',
        context: 'while handling a gesture',
        informationCollector: (StringBuffer information) {
          information.writeln('Handler: $name');
          information.writeln('Recognizer:');
          information.writeln('  $this');
        }
      ));
    }
    return result;
  }
76 77
}

78 79 80 81 82 83 84 85
/// 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.
86
abstract class OneSequenceGestureRecognizer extends GestureRecognizer {
87
  final Map<int, GestureArenaEntry> _entries = <int, GestureArenaEntry>{};
88
  final Set<int> _trackedPointers = new HashSet<int>();
89

90 91
  /// Called when a pointer event is routed to this recognizer.
  @protected
Ian Hickson's avatar
Ian Hickson committed
92
  void handleEvent(PointerEvent event);
93 94

  @override
95
  void acceptGesture(int pointer) { }
96 97

  @override
98
  void rejectGesture(int pointer) { }
99

100
  /// Called when the number of pointers this recognizer is tracking changes from one to zero.
101 102 103
  ///
  /// The given pointer ID is the ID of the last pointer this recognizer was
  /// tracking.
104
  @protected
105
  void didStopTrackingLastPointer(int pointer);
106

107
  /// Resolves this recognizer's participation in each gesture arena with the given disposition.
108 109
  @protected
  @mustCallSuper
110
  void resolve(GestureDisposition disposition) {
111
    List<GestureArenaEntry> localEntries = new List<GestureArenaEntry>.from(_entries.values);
112 113 114 115 116
    _entries.clear();
    for (GestureArenaEntry entry in localEntries)
      entry.resolve(disposition);
  }

117
  @override
118 119 120
  void dispose() {
    resolve(GestureDisposition.rejected);
    for (int pointer in _trackedPointers)
121
      GestureBinding.instance.pointerRouter.removeRoute(pointer, handleEvent);
122 123
    _trackedPointers.clear();
    assert(_entries.isEmpty);
124
    super.dispose();
125 126
  }

127 128 129 130 131
  /// Causes events related to the given pointer ID to be routed to this recognizer.
  ///
  /// The pointer events are delivered to [handleEvent].
  ///
  /// Use [stopTrackingPointer] to remove the route added by this function.
132
  @protected
133
  void startTrackingPointer(int pointer) {
134
    GestureBinding.instance.pointerRouter.addRoute(pointer, handleEvent);
135
    _trackedPointers.add(pointer);
136 137
    assert(!_entries.containsValue(pointer));
    _entries[pointer] = GestureBinding.instance.gestureArena.add(pointer, this);
138 139
  }

140 141 142 143 144 145
  /// 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.
146
  @protected
147
  void stopTrackingPointer(int pointer) {
148 149 150 151 152 153
    if (_trackedPointers.contains(pointer)) {
      GestureBinding.instance.pointerRouter.removeRoute(pointer, handleEvent);
      _trackedPointers.remove(pointer);
      if (_trackedPointers.isEmpty)
        didStopTrackingLastPointer(pointer);
    }
154 155
  }

156 157
  /// Stops tracking the pointer associated with the given event if the event is
  /// a [PointerUpEvent] or a [PointerCancelEvent] event.
158
  @protected
Ian Hickson's avatar
Ian Hickson committed
159 160
  void stopTrackingIfPointerNoLongerDown(PointerEvent event) {
    if (event is PointerUpEvent || event is PointerCancelEvent)
161 162 163 164
      stopTrackingPointer(event.pointer);
  }
}

165 166 167 168 169 170 171
/// The possible states of a [PrimaryPointerGestureRecognizer].
///
/// The recognizer advances from [ready] to [possible] when starts tracking a
/// primary pointer. When the primary pointer is resolve (either accepted or
/// or rejected), the recognizers advances to [defunct]. Once the recognizer
/// has stopped tracking any remaining pointers, the recognizer returns to
/// [ready].
172
enum GestureRecognizerState {
173
  /// The recognizer is ready to start recognizing a gesture.
174
  ready,
175

176
  /// The sequence of pointer events seen thus far is consistent with the
177 178
  /// gesture the recognizer is attempting to recognize but the gesture has not
  /// been accepted definitively.
179
  possible,
180

181
  /// Further pointer events cannot cause this recognizer to recognize the
182 183 184
  /// gesture until the recognizer returns to the [ready] state (typically when
  /// all the pointers the recognizer is tracking are removed from the screen).
  defunct,
185 186
}

187
/// A base class for gesture recognizers that track a single primary pointer.
188
abstract class PrimaryPointerGestureRecognizer extends OneSequenceGestureRecognizer {
189
  /// Initializes the [deadline] field during construction of subclasses.
190
  PrimaryPointerGestureRecognizer({ this.deadline });
191

192 193
  /// If non-null, the recognizer will call [didExceedDeadline] after this
  /// amount of time has elapsed since starting to track the primary pointer.
194 195
  final Duration deadline;

196 197 198
  /// The current state of the recognizer.
  ///
  /// See [GestureRecognizerState] for a description of the states.
199
  GestureRecognizerState state = GestureRecognizerState.ready;
200 201

  /// The ID of the primary pointer this recognizer is tracking.
202
  int primaryPointer;
203 204

  /// The global location at which the primary pointer contacted the screen.
Hixie's avatar
Hixie committed
205
  Point initialPosition;
206

207 208
  Timer _timer;

209
  @override
Ian Hickson's avatar
Ian Hickson committed
210
  void addPointer(PointerDownEvent event) {
211 212 213 214
    startTrackingPointer(event.pointer);
    if (state == GestureRecognizerState.ready) {
      state = GestureRecognizerState.possible;
      primaryPointer = event.pointer;
Hixie's avatar
Hixie committed
215
      initialPosition = event.position;
216 217 218 219 220
      if (deadline != null)
        _timer = new Timer(deadline, didExceedDeadline);
    }
  }

221
  @override
Ian Hickson's avatar
Ian Hickson committed
222
  void handleEvent(PointerEvent event) {
223 224 225
    assert(state != GestureRecognizerState.ready);
    if (state == GestureRecognizerState.possible && event.pointer == primaryPointer) {
      // TODO(abarth): Maybe factor the slop handling out into a separate class?
Ian Hickson's avatar
Ian Hickson committed
226
      if (event is PointerMoveEvent && _getDistance(event) > kTouchSlop) {
227
        resolve(GestureDisposition.rejected);
Ian Hickson's avatar
Ian Hickson committed
228
        stopTrackingPointer(primaryPointer);
229
      } else {
230
        handlePrimaryPointer(event);
231
      }
232 233 234 235 236
    }
    stopTrackingIfPointerNoLongerDown(event);
  }

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

Florian Loitsch's avatar
Florian Loitsch committed
240
  /// Override to be notified when [deadline] is exceeded.
241
  ///
242
  /// You must override this method if you supply a [deadline].
243
  @protected
244 245 246 247
  void didExceedDeadline() {
    assert(deadline == null);
  }

248
  @override
249
  void rejectGesture(int pointer) {
250 251
    if (pointer == primaryPointer) {
      _stopTimer();
252
      state = GestureRecognizerState.defunct;
253
    }
254 255
  }

256
  @override
257
  void didStopTrackingLastPointer(int pointer) {
258
    _stopTimer();
259 260 261
    state = GestureRecognizerState.ready;
  }

262
  @override
263 264 265 266 267 268 269 270 271 272 273 274
  void dispose() {
    _stopTimer();
    super.dispose();
  }

  void _stopTimer() {
    if (_timer != null) {
      _timer.cancel();
      _timer = null;
    }
  }

Ian Hickson's avatar
Ian Hickson committed
275
  double _getDistance(PointerEvent event) {
Hixie's avatar
Hixie committed
276
    Offset offset = event.position - initialPosition;
277 278 279 280
    return offset.distance;
  }

}