recognizer.dart 6.11 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 'arena.dart';
10
import 'binding.dart';
11
import 'constants.dart';
12
import 'events.dart';
13
import 'pointer_router.dart';
14

15
export 'pointer_router.dart' show PointerRouter;
16

17 18 19 20 21
/// 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.
22
abstract class GestureRecognizer extends GestureArenaMember {
23

24 25
  /// Registers a new pointer that might be relevant to this gesture
  /// detector.
Florian Loitsch's avatar
Florian Loitsch committed
26
  ///
27 28 29 30 31 32 33
  /// 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
34
  /// the global gesture arena manager (see [GestureArenaManager]) to track
35
  /// that pointer.
Ian Hickson's avatar
Ian Hickson committed
36
  void addPointer(PointerDownEvent event);
37

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

46 47 48
  /// Returns a very short pretty description of the gesture that the
  /// recognizer looks for, like 'tap' or 'horizontal drag'.
  String toStringShort() => toString();
49 50
}

51 52 53 54 55 56 57 58
/// 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.
59
abstract class OneSequenceGestureRecognizer extends GestureRecognizer {
60
  final Map<int, GestureArenaEntry> _entries = <int, GestureArenaEntry>{};
61
  final Set<int> _trackedPointers = new HashSet<int>();
62

Ian Hickson's avatar
Ian Hickson committed
63
  void handleEvent(PointerEvent event);
64 65

  @override
66
  void acceptGesture(int pointer) { }
67 68

  @override
69
  void rejectGesture(int pointer) { }
70

71
  void didStopTrackingLastPointer(int pointer);
72 73

  void resolve(GestureDisposition disposition) {
74
    List<GestureArenaEntry> localEntries = new List<GestureArenaEntry>.from(_entries.values);
75 76 77 78 79
    _entries.clear();
    for (GestureArenaEntry entry in localEntries)
      entry.resolve(disposition);
  }

80
  @override
81 82 83
  void dispose() {
    resolve(GestureDisposition.rejected);
    for (int pointer in _trackedPointers)
84
      GestureBinding.instance.pointerRouter.removeRoute(pointer, handleEvent);
85 86 87 88 89
    _trackedPointers.clear();
    assert(_entries.isEmpty);
  }

  void startTrackingPointer(int pointer) {
90
    GestureBinding.instance.pointerRouter.addRoute(pointer, handleEvent);
91
    _trackedPointers.add(pointer);
92 93
    assert(!_entries.containsValue(pointer));
    _entries[pointer] = GestureBinding.instance.gestureArena.add(pointer, this);
94 95 96
  }

  void stopTrackingPointer(int pointer) {
97
    GestureBinding.instance.pointerRouter.removeRoute(pointer, handleEvent);
98 99
    _trackedPointers.remove(pointer);
    if (_trackedPointers.isEmpty)
100
      didStopTrackingLastPointer(pointer);
101 102
  }

103 104 105 106 107
  void ensureNotTrackingPointer(int pointer) {
    if (_trackedPointers.contains(pointer))
      stopTrackingPointer(pointer);
  }

Ian Hickson's avatar
Ian Hickson committed
108 109
  void stopTrackingIfPointerNoLongerDown(PointerEvent event) {
    if (event is PointerUpEvent || event is PointerCancelEvent)
110 111 112 113 114 115 116 117 118 119 120
      stopTrackingPointer(event.pointer);
  }

}

enum GestureRecognizerState {
  ready,
  possible,
  defunct
}

121
abstract class PrimaryPointerGestureRecognizer extends OneSequenceGestureRecognizer {
122
  PrimaryPointerGestureRecognizer({ this.deadline });
123 124 125 126 127

  final Duration deadline;

  GestureRecognizerState state = GestureRecognizerState.ready;
  int primaryPointer;
Hixie's avatar
Hixie committed
128
  Point initialPosition;
129 130
  Timer _timer;

131
  @override
Ian Hickson's avatar
Ian Hickson committed
132
  void addPointer(PointerDownEvent event) {
133 134 135 136
    startTrackingPointer(event.pointer);
    if (state == GestureRecognizerState.ready) {
      state = GestureRecognizerState.possible;
      primaryPointer = event.pointer;
Hixie's avatar
Hixie committed
137
      initialPosition = event.position;
138 139 140 141 142
      if (deadline != null)
        _timer = new Timer(deadline, didExceedDeadline);
    }
  }

143
  @override
Ian Hickson's avatar
Ian Hickson committed
144
  void handleEvent(PointerEvent event) {
145 146 147
    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
148
      if (event is PointerMoveEvent && _getDistance(event) > kTouchSlop) {
149
        resolve(GestureDisposition.rejected);
Ian Hickson's avatar
Ian Hickson committed
150
        stopTrackingPointer(primaryPointer);
151
      } else {
152
        handlePrimaryPointer(event);
153
      }
154 155 156 157 158
    }
    stopTrackingIfPointerNoLongerDown(event);
  }

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

Florian Loitsch's avatar
Florian Loitsch committed
161
  /// Override to be notified when [deadline] is exceeded.
162 163 164 165 166 167
  ///
  /// You must override this function if you supply a [deadline].
  void didExceedDeadline() {
    assert(deadline == null);
  }

168
  @override
169
  void rejectGesture(int pointer) {
170 171
    if (pointer == primaryPointer) {
      _stopTimer();
172
      state = GestureRecognizerState.defunct;
173
    }
174 175
  }

176
  @override
177
  void didStopTrackingLastPointer(int pointer) {
178
    _stopTimer();
179 180 181
    state = GestureRecognizerState.ready;
  }

182
  @override
183 184 185 186 187 188 189 190 191 192 193 194
  void dispose() {
    _stopTimer();
    super.dispose();
  }

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

Ian Hickson's avatar
Ian Hickson committed
195
  double _getDistance(PointerEvent event) {
Hixie's avatar
Hixie committed
196
    Offset offset = event.position - initialPosition;
197 198 199 200
    return offset.distance;
  }

}