drag.dart 8.56 KB
Newer Older
1 2 3 4
// 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.

5 6 7
import 'arena.dart';
import 'recognizer.dart';
import 'constants.dart';
8
import 'events.dart';
9
import 'velocity_tracker.dart';
10

11
enum _DragState {
12 13
  ready,
  possible,
14
  accepted,
15 16
}

17
/// Signature for when a pointer has contacted the screen and might begin to move.
18
typedef void GestureDragDownCallback(Point globalPosition);
19 20

/// Signature for when a pointer has contacted the screen and has begun to move.
Ian Hickson's avatar
Ian Hickson committed
21
typedef void GestureDragStartCallback(Point globalPosition);
22 23 24 25

/// Signature for when a pointer that is in contact with the screen and moving
/// in a direction (e.g., vertically or horizontally) has moved in that
/// direction.
26
typedef void GestureDragUpdateCallback(double delta);
27 28 29 30 31

/// Signature for when a pointer that was previously in contact with the screen
/// and moving in a direction (e.g., vertically or horizontally) is no longer in
/// contact with the screen and was moving at a specific velocity when it
/// stopped contacting the screen.
32
typedef void GestureDragEndCallback(Velocity velocity);
33 34 35

/// Signature for when the pointer that previously triggered a
/// [GestureDragDownCallback] did not complete.
36
typedef void GestureDragCancelCallback();
37

38
/// Signature for when a pointer has contacted the screen and might begin to move.
39
typedef void GesturePanDownCallback(Point globalPosition);
40 41

/// Signature for when a pointer has contacted the screen and has begun to move.
Ian Hickson's avatar
Ian Hickson committed
42
typedef void GesturePanStartCallback(Point globalPosition);
43 44 45

/// Signature for when a pointer that is in contact with the screen and moving
/// has moved again.
Ian Hickson's avatar
Ian Hickson committed
46
typedef void GesturePanUpdateCallback(Offset delta);
47 48 49 50

/// Signature for when a pointer that was previously in contact with the screen
/// and moving is no longer in contact with the screen and was moving at a
/// specific velocity when it stopped contacting the screen.
51
typedef void GesturePanEndCallback(Velocity velocity);
52 53 54

/// Signature for when the pointer that previously triggered a
/// [GesturePanDownCallback] did not complete.
55
typedef void GesturePanCancelCallback();
56

57 58 59 60 61
/// Signature for when a pointer that is in contact with the screen and moving
/// has moved again. For one-dimensional drags (e.g., horizontal or vertical),
/// T is `double`, as in [GestureDragUpdateCallback]. For two-dimensional drags
/// (e.g., pans), T is `Offset`, as in GesturePanUpdateCallback.
typedef void GesturePolymorphicUpdateCallback<T>(T delta);
62

63
bool _isFlingGesture(Velocity velocity) {
64
  assert(velocity != null);
65
  final double speedSquared = velocity.pixelsPerSecond.distanceSquared;
Hans Muller's avatar
Hans Muller committed
66
  return speedSquared > kMinFlingVelocity * kMinFlingVelocity;
67 68
}

69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
/// Recognizes movement.
///
/// In contrast to [MultiDragGestureRecognizer], [DragGestureRecognizer]
/// recognizes a single gesture sequence for all the pointers it watches, which
/// means that the recognizer has at most one drag sequence active at any given
/// time regardless of how many pointers are in contact with the screen.
///
/// [DragGestureRecognizer] is not intended to be used directly. Instead,
/// consider using one of its subclasses to recognize specific types for drag
/// gestures.
///
/// See also:
///
///  * [HorizontalDragGestureRecognizer]
///  * [VerticalDragGestureRecognizer]
///  * [PanGestureRecognizer]
// Having T extend dynamic makes it possible to use the += operator on
// _pendingDragDelta without causing an analysis error.
abstract class DragGestureRecognizer<T extends dynamic> extends OneSequenceGestureRecognizer {
88
  /// A pointer has contacted the screen and might begin to move.
89
  GestureDragDownCallback onDown;
90 91

  /// A pointer has contacted the screen and has begun to move.
92
  GestureDragStartCallback onStart;
93 94

  /// A pointer that is in contact with the screen and moving has moved again.
95
  GesturePolymorphicUpdateCallback<T> onUpdate;
96 97 98 99

  /// A pointer that was previously in contact with the screen and moving is no
  /// longer in contact with the screen and was moving at a specific velocity
  /// when it stopped contacting the screen.
100
  GestureDragEndCallback onEnd;
101

102
  /// The pointer that previously triggered [onDown] did not complete.
103
  GestureDragCancelCallback onCancel;
104

105
  _DragState _state = _DragState.ready;
Ian Hickson's avatar
Ian Hickson committed
106
  Point _initialPosition;
107
  T _pendingDragDelta;
108

109
  T get _initialPendingDragDelta;
Ian Hickson's avatar
Ian Hickson committed
110
  T _getDragDelta(PointerEvent event);
111
  bool get _hasSufficientPendingDragDeltaToAccept;
112

113
  Map<int, VelocityTracker> _velocityTrackers = new Map<int, VelocityTracker>();
114

115
  @override
Ian Hickson's avatar
Ian Hickson committed
116
  void addPointer(PointerEvent event) {
117
    startTrackingPointer(event.pointer);
118
    _velocityTrackers[event.pointer] = new VelocityTracker();
119 120
    if (_state == _DragState.ready) {
      _state = _DragState.possible;
121
      _initialPosition = event.position;
122
      _pendingDragDelta = _initialPendingDragDelta;
123 124
      if (onDown != null)
        onDown(_initialPosition);
125 126 127
    }
  }

128
  @override
Ian Hickson's avatar
Ian Hickson committed
129
  void handleEvent(PointerEvent event) {
130
    assert(_state != _DragState.ready);
Ian Hickson's avatar
Ian Hickson committed
131
    if (event is PointerMoveEvent) {
132
      VelocityTracker tracker = _velocityTrackers[event.pointer];
133
      assert(tracker != null);
Ian Hickson's avatar
Ian Hickson committed
134
      tracker.addPosition(event.timeStamp, event.position);
135
      T delta = _getDragDelta(event);
136
      if (_state == _DragState.accepted) {
137 138
        if (onUpdate != null)
          onUpdate(delta);
139
      } else {
140 141
        _pendingDragDelta += delta;
        if (_hasSufficientPendingDragDeltaToAccept)
142 143 144 145 146 147
          resolve(GestureDisposition.accepted);
      }
    }
    stopTrackingIfPointerNoLongerDown(event);
  }

148
  @override
149
  void acceptGesture(int pointer) {
150 151
    if (_state != _DragState.accepted) {
      _state = _DragState.accepted;
152
      T delta = _pendingDragDelta;
153
      _pendingDragDelta = _initialPendingDragDelta;
154
      if (onStart != null)
155
        onStart(_initialPosition);
156
      if (delta != _initialPendingDragDelta && onUpdate != null)
157
        onUpdate(delta);
158 159 160
    }
  }

161 162 163 164 165
  @override
  void rejectGesture(int pointer) {
    ensureNotTrackingPointer(pointer);
  }

166
  @override
167
  void didStopTrackingLastPointer(int pointer) {
168
    if (_state == _DragState.possible) {
169
      resolve(GestureDisposition.rejected);
170
      _state = _DragState.ready;
171 172
      if (onCancel != null)
        onCancel();
173 174
      return;
    }
175 176
    bool wasAccepted = (_state == _DragState.accepted);
    _state = _DragState.ready;
177
    if (wasAccepted && onEnd != null) {
178
      VelocityTracker tracker = _velocityTrackers[pointer];
179 180
      assert(tracker != null);

181
      Velocity velocity = tracker.getVelocity();
Hans Muller's avatar
Hans Muller committed
182 183 184 185
      if (velocity != null && _isFlingGesture(velocity)) {
        final Offset pixelsPerSecond = velocity.pixelsPerSecond;
        if (pixelsPerSecond.distanceSquared > kMaxFlingVelocity * kMaxFlingVelocity)
          velocity = new Velocity(pixelsPerSecond: (pixelsPerSecond / pixelsPerSecond.distance) * kMaxFlingVelocity);
Ian Hickson's avatar
Ian Hickson committed
186
        onEnd(velocity);
Hans Muller's avatar
Hans Muller committed
187
      } else {
188
        onEnd(Velocity.zero);
Hans Muller's avatar
Hans Muller committed
189
      }
190
    }
191
    _velocityTrackers.clear();
192 193
  }

194
  @override
195
  void dispose() {
196
    _velocityTrackers.clear();
197
    super.dispose();
198
  }
199 200
}

201 202 203
/// Recognizes movement in the vertical direction.
///
/// Used for vertical scrolling.
204 205 206 207
///
/// See also:
///
///  * [VerticalMultiDragGestureRecognizer]
208
class VerticalDragGestureRecognizer extends DragGestureRecognizer<double> {
209
  @override
210
  double get _initialPendingDragDelta => 0.0;
211 212

  @override
Ian Hickson's avatar
Ian Hickson committed
213
  double _getDragDelta(PointerEvent event) => event.delta.dy;
214 215

  @override
216
  bool get _hasSufficientPendingDragDeltaToAccept => _pendingDragDelta.abs() > kTouchSlop;
217

218
  @override
219
  String toStringShort() => 'vertical drag';
220 221
}

222 223 224
/// Recognizes movement in the horizontal direction.
///
/// Used for horizontal scrolling.
225 226 227 228
///
/// See also:
///
///  * [HorizontalMultiDragGestureRecognizer]
229
class HorizontalDragGestureRecognizer extends DragGestureRecognizer<double> {
230
  @override
231
  double get _initialPendingDragDelta => 0.0;
232 233

  @override
Ian Hickson's avatar
Ian Hickson committed
234
  double _getDragDelta(PointerEvent event) => event.delta.dx;
235 236

  @override
237
  bool get _hasSufficientPendingDragDeltaToAccept => _pendingDragDelta.abs() > kTouchSlop;
238

239
  @override
240
  String toStringShort() => 'horizontal drag';
241 242
}

243
/// Recognizes movement both horizontally and vertically.
244 245 246 247 248
///
/// See also:
///
///  * [ImmediateMultiDragGestureRecognizer]
///  * [DelayedMultiDragGestureRecognizer]
249
class PanGestureRecognizer extends DragGestureRecognizer<Offset> {
250
  @override
Ian Hickson's avatar
Ian Hickson committed
251
  Offset get _initialPendingDragDelta => Offset.zero;
252 253

  @override
Ian Hickson's avatar
Ian Hickson committed
254
  Offset _getDragDelta(PointerEvent event) => event.delta;
255 256

  @override
257
  bool get _hasSufficientPendingDragDeltaToAccept {
258
    return _pendingDragDelta.distance > kPanSlop;
259
  }
260

261
  @override
262
  String toStringShort() => 'pan';
263
}