drag.dart 15.2 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
import 'package:flutter/foundation.dart';

7 8 9
import 'arena.dart';
import 'recognizer.dart';
import 'constants.dart';
10
import 'events.dart';
11
import 'velocity_tracker.dart';
12

13
enum _DragState {
14 15
  ready,
  possible,
16
  accepted,
17 18
}

19 20 21 22 23 24 25 26
/// Details object for callbacks that use [GestureDragDownCallback].
///
/// See also:
///
/// * [DragGestureRecognizer.onDown], which uses [GestureDragDownCallback].
/// * [DragStartDetails], the details for [GestureDragStartCallback].
/// * [DragUpdateDetails], the details for [GestureDragUpdateCallback].
/// * [DragEndDetails], the details for [GestureDragEndCallback].
27 28 29 30 31 32 33
class DragDownDetails {
  /// Creates details for a [GestureDragDownCallback].
  ///
  /// The [globalPosition] argument must not be null.
  DragDownDetails({ this.globalPosition: Point.origin }) {
    assert(globalPosition != null);
  }
34

35
  /// The global position at which the pointer contacted the screen.
36 37
  ///
  /// Defaults to the origin if not specified in the constructor.
38
  final Point globalPosition;
39 40 41

  @override
  String toString() => '$runtimeType($globalPosition)';
42
}
43

44 45 46 47
/// Signature for when a pointer has contacted the screen and might begin to
/// move.
///
/// The `details` object provides the position of the touch.
48 49
///
/// See [DragGestureRecognizer.onDown].
50 51
typedef void GestureDragDownCallback(DragDownDetails details);

52 53 54 55 56 57 58 59
/// Details object for callbacks that use [GestureDragStartCallback].
///
/// See also:
///
/// * [DragGestureRecognizer.onStart], which uses [GestureDragStartCallback].
/// * [DragDownDetails], the details for [GestureDragDownCallback].
/// * [DragUpdateDetails], the details for [GestureDragUpdateCallback].
/// * [DragEndDetails], the details for [GestureDragEndCallback].
60 61 62 63 64 65 66 67 68
class DragStartDetails {
  /// Creates details for a [GestureDragStartCallback].
  ///
  /// The [globalPosition] argument must not be null.
  DragStartDetails({ this.globalPosition: Point.origin }) {
    assert(globalPosition != null);
  }

  /// The global position at which the pointer contacted the screen.
69 70
  ///
  /// Defaults to the origin if not specified in the constructor.
71
  final Point globalPosition;
72 73 74

  @override
  String toString() => '$runtimeType($globalPosition)';
75
}
76 77

/// Signature for when a pointer has contacted the screen and has begun to move.
78
///
79 80 81
/// The `details` object provides the position of the touch when it first
/// touched the surface.
///
82
/// See [DragGestureRecognizer.onStart].
83 84
typedef void GestureDragStartCallback(DragStartDetails details);

85 86 87 88 89 90 91 92
/// Details object for callbacks that use [GestureDragUpdateCallback].
///
/// See also:
///
/// * [DragGestureRecognizer.onUpdate], which uses [GestureDragUpdateCallback].
/// * [DragDownDetails], the details for [GestureDragDownCallback].
/// * [DragStartDetails], the details for [GestureDragStartCallback].
/// * [DragEndDetails], the details for [GestureDragEndCallback].
93 94 95 96 97 98 99
class DragUpdateDetails {
  /// Creates details for a [DragUpdateDetails].
  ///
  /// The [delta] argument must not be null.
  ///
  /// If [primaryDelta] is non-null, then its value must match one of the
  /// coordinates of [delta] and the other coordinate must be zero.
100 101
  ///
  /// The [globalPosition] argument must be provided and must not be null.
102 103
  DragUpdateDetails({
    this.delta: Offset.zero,
104
    this.primaryDelta,
105
    @required this.globalPosition
106 107 108 109 110 111 112 113 114 115 116
  }) {
    assert(primaryDelta == null
        || (primaryDelta == delta.dx && delta.dy == 0.0)
        || (primaryDelta == delta.dy && delta.dx == 0.0));
  }

  /// The amount the pointer has moved since the previous update.
  ///
  /// If the [GestureDragUpdateCallback] is for a one-dimensional drag (e.g.,
  /// a horizontal or vertical drag), then this offset contains only the delta
  /// in that direction (i.e., the coordinate in the other direction is zero).
117 118
  ///
  /// Defaults to zero if not specified in the constructor.
119 120 121 122 123 124
  final Offset delta;

  /// The amount the pointer has moved along the primary axis since the previous
  /// update.
  ///
  /// If the [GestureDragUpdateCallback] is for a one-dimensional drag (e.g.,
125 126 127 128
  /// a horizontal or vertical drag), then this value contains the component of
  /// [delta] along the primary axis (e.g., horizontal or vertical,
  /// respectively). Otherwise, if the [GestureDragUpdateCallback] is for a
  /// two-dimensional drag (e.g., a pan), then this value is null.
129 130
  ///
  /// Defaults to null if not specified in the constructor.
131
  final double primaryDelta;
132

133
  /// The pointer's global position when it triggered this update.
134
  final Point globalPosition;
135 136 137

  @override
  String toString() => '$runtimeType($delta)';
138
}
139 140 141

/// Signature for when a pointer that is in contact with the screen and moving
/// has moved again.
142
///
143 144 145
/// The `details` object provides the position of the touch and the distance it
/// has travelled since the last update.
///
146
/// See [DragGestureRecognizer.onUpdate].
147 148
typedef void GestureDragUpdateCallback(DragUpdateDetails details);

149 150 151 152 153 154 155 156
/// Details object for callbacks that use [GestureDragEndCallback].
///
/// See also:
///
/// * [DragGestureRecognizer.onEnd], which uses [GestureDragEndCallback].
/// * [DragDownDetails], the details for [GestureDragDownCallback].
/// * [DragStartDetails], the details for [GestureDragStartCallback].
/// * [DragUpdateDetails], the details for [GestureDragUpdateCallback].
157 158 159 160
class DragEndDetails {
  /// Creates details for a [GestureDragEndCallback].
  ///
  /// The [velocity] argument must not be null.
161 162 163 164
  DragEndDetails({
    this.velocity: Velocity.zero,
    this.primaryVelocity,
  }) {
165
    assert(velocity != null);
166 167 168
    assert(primaryVelocity == null
        || primaryVelocity == velocity.pixelsPerSecond.dx
        || primaryVelocity == velocity.pixelsPerSecond.dy);
169 170 171
  }

  /// The velocity the pointer was moving when it stopped contacting the screen.
172 173
  ///
  /// Defaults to zero if not specified in the constructor.
174
  final Velocity velocity;
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189

  /// The velocity the pointer was moving along the primary axis when it stopped
  /// contacting the screen, in logical pixels per second.
  ///
  /// If the [GestureDragEndCallback] is for a one-dimensional drag (e.g., a
  /// horizontal or vertical drag), then this value contains the component of
  /// [velocity] along the primary axis (e.g., horizontal or vertical,
  /// respectively). Otherwise, if the [GestureDragEndCallback] is for a
  /// two-dimensional drag (e.g., a pan), then this value is null.
  ///
  /// Defaults to null if not specified in the constructor.
  final double primaryVelocity;

  @override
  String toString() => '$runtimeType($velocity)';
190
}
191 192

/// Signature for when a pointer that was previously in contact with the screen
193
/// and moving is no longer in contact with the screen.
194 195 196
///
/// The velocity at which the pointer was moving when it stopped contacting
/// the screen is available in the `details`.
197 198
///
/// See [DragGestureRecognizer.onEnd].
199
typedef void GestureDragEndCallback(DragEndDetails details);
200 201

/// Signature for when the pointer that previously triggered a
202
/// [GestureDragDownCallback] did not complete.
203 204
///
/// See [DragGestureRecognizer.onCancel].
205
typedef void GestureDragCancelCallback();
206

207
bool _isFlingGesture(Velocity velocity) {
208
  assert(velocity != null);
209
  final double speedSquared = velocity.pixelsPerSecond.distanceSquared;
Hans Muller's avatar
Hans Muller committed
210
  return speedSquared > kMinFlingVelocity * kMinFlingVelocity;
211 212
}

213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228
/// 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]
229
abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer {
230
  /// A pointer has contacted the screen and might begin to move.
231 232 233
  ///
  /// The position of the pointer is provided in the callback's `details`
  /// argument, which is a [DragDownDetails] object.
234
  GestureDragDownCallback onDown;
235 236

  /// A pointer has contacted the screen and has begun to move.
237 238 239
  ///
  /// The position of the pointer is provided in the callback's `details`
  /// argument, which is a [DragStartDetails] object.
240
  GestureDragStartCallback onStart;
241 242

  /// A pointer that is in contact with the screen and moving has moved again.
243 244 245
  ///
  /// The distance travelled by the pointer since the last update is provided in
  /// the callback's `details` argument, which is a [DragUpdateDetails] object.
246
  GestureDragUpdateCallback onUpdate;
247 248 249 250

  /// 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.
251 252 253
  ///
  /// The velocity is provided in the callback's `details` argument, which is a
  /// [DragEndDetails] object.
254
  GestureDragEndCallback onEnd;
255

256
  /// The pointer that previously triggered [onDown] did not complete.
257
  GestureDragCancelCallback onCancel;
258

259
  _DragState _state = _DragState.ready;
Ian Hickson's avatar
Ian Hickson committed
260
  Point _initialPosition;
261
  Offset _pendingDragOffset;
262

263
  Offset _getDeltaForDetails(Offset delta);
264
  double _getPrimaryValueFromOffset(Offset value);
265
  bool get _hasSufficientPendingDragDeltaToAccept;
266

267
  Map<int, VelocityTracker> _velocityTrackers = new Map<int, VelocityTracker>();
268

269
  @override
Ian Hickson's avatar
Ian Hickson committed
270
  void addPointer(PointerEvent event) {
271
    startTrackingPointer(event.pointer);
272
    _velocityTrackers[event.pointer] = new VelocityTracker();
273 274
    if (_state == _DragState.ready) {
      _state = _DragState.possible;
275
      _initialPosition = event.position;
276
      _pendingDragOffset = Offset.zero;
277
      if (onDown != null)
278
        invokeCallback<Null>('onDown', () => onDown(new DragDownDetails(globalPosition: _initialPosition))); // ignore: STRONG_MODE_INVALID_CAST_FUNCTION_EXPR, https://github.com/dart-lang/sdk/issues/27504
279 280 281
    }
  }

282
  @override
Ian Hickson's avatar
Ian Hickson committed
283
  void handleEvent(PointerEvent event) {
284
    assert(_state != _DragState.ready);
Ian Hickson's avatar
Ian Hickson committed
285
    if (event is PointerMoveEvent) {
286
      VelocityTracker tracker = _velocityTrackers[event.pointer];
287
      assert(tracker != null);
Ian Hickson's avatar
Ian Hickson committed
288
      tracker.addPosition(event.timeStamp, event.position);
289
      Offset delta = event.delta;
290
      if (_state == _DragState.accepted) {
291
        if (onUpdate != null) {
292
          invokeCallback<Null>('onUpdate', () => onUpdate(new DragUpdateDetails( // ignore: STRONG_MODE_INVALID_CAST_FUNCTION_EXPR, https://github.com/dart-lang/sdk/issues/27504
293
            delta: _getDeltaForDetails(delta),
294 295
            primaryDelta: _getPrimaryValueFromOffset(delta),
            globalPosition: event.position,
296
          )));
297
        }
298
      } else {
299
        _pendingDragOffset += delta;
300
        if (_hasSufficientPendingDragDeltaToAccept)
301 302 303 304 305 306
          resolve(GestureDisposition.accepted);
      }
    }
    stopTrackingIfPointerNoLongerDown(event);
  }

307
  @override
308
  void acceptGesture(int pointer) {
309 310
    if (_state != _DragState.accepted) {
      _state = _DragState.accepted;
311 312
      Offset delta = _pendingDragOffset;
      _pendingDragOffset = Offset.zero;
313
      if (onStart != null) {
314
        invokeCallback<Null>('onStart', () => onStart(new DragStartDetails( // ignore: STRONG_MODE_INVALID_CAST_FUNCTION_EXPR, https://github.com/dart-lang/sdk/issues/27504
315 316 317
          globalPosition: _initialPosition,
        )));
      }
318
      if (delta != Offset.zero && onUpdate != null) {
319
        invokeCallback<Null>('onUpdate', () => onUpdate(new DragUpdateDetails( // ignore: STRONG_MODE_INVALID_CAST_FUNCTION_EXPR, https://github.com/dart-lang/sdk/issues/27504
320
          delta: _getDeltaForDetails(delta),
321 322
          primaryDelta: _getPrimaryValueFromOffset(delta),
          globalPosition: _initialPosition,
323
        )));
324
      }
325 326 327
    }
  }

328 329
  @override
  void rejectGesture(int pointer) {
330
    stopTrackingPointer(pointer);
331 332
  }

333
  @override
334
  void didStopTrackingLastPointer(int pointer) {
335
    if (_state == _DragState.possible) {
336
      resolve(GestureDisposition.rejected);
337
      _state = _DragState.ready;
338
      if (onCancel != null)
339
        invokeCallback<Null>('onCancel', onCancel); // ignore: STRONG_MODE_INVALID_CAST_FUNCTION_EXPR, https://github.com/dart-lang/sdk/issues/27504
340 341
      return;
    }
342 343
    bool wasAccepted = (_state == _DragState.accepted);
    _state = _DragState.ready;
344
    if (wasAccepted && onEnd != null) {
345
      VelocityTracker tracker = _velocityTrackers[pointer];
346 347
      assert(tracker != null);

348
      Velocity velocity = tracker.getVelocity();
Hans Muller's avatar
Hans Muller committed
349 350 351 352
      if (velocity != null && _isFlingGesture(velocity)) {
        final Offset pixelsPerSecond = velocity.pixelsPerSecond;
        if (pixelsPerSecond.distanceSquared > kMaxFlingVelocity * kMaxFlingVelocity)
          velocity = new Velocity(pixelsPerSecond: (pixelsPerSecond / pixelsPerSecond.distance) * kMaxFlingVelocity);
353
        invokeCallback<Null>('onEnd', () => onEnd(new DragEndDetails( // ignore: STRONG_MODE_INVALID_CAST_FUNCTION_EXPR, https://github.com/dart-lang/sdk/issues/27504
354 355 356
          velocity: velocity,
          primaryVelocity: _getPrimaryValueFromOffset(velocity.pixelsPerSecond),
        )));
Hans Muller's avatar
Hans Muller committed
357
      } else {
358
        invokeCallback<Null>('onEnd', () => onEnd(new DragEndDetails( // ignore: STRONG_MODE_INVALID_CAST_FUNCTION_EXPR, https://github.com/dart-lang/sdk/issues/27504
359 360 361
          velocity: Velocity.zero,
          primaryVelocity: 0.0,
        )));
Hans Muller's avatar
Hans Muller committed
362
      }
363
    }
364
    _velocityTrackers.clear();
365 366
  }

367
  @override
368
  void dispose() {
369
    _velocityTrackers.clear();
370
    super.dispose();
371
  }
372 373
}

374 375 376
/// Recognizes movement in the vertical direction.
///
/// Used for vertical scrolling.
377 378 379 380
///
/// See also:
///
///  * [VerticalMultiDragGestureRecognizer]
381
class VerticalDragGestureRecognizer extends DragGestureRecognizer {
382
  @override
383
  bool get _hasSufficientPendingDragDeltaToAccept => _pendingDragOffset.dy.abs() > kTouchSlop;
384 385

  @override
386
  Offset _getDeltaForDetails(Offset delta) => new Offset(0.0, delta.dy);
387 388

  @override
389
  double _getPrimaryValueFromOffset(Offset value) => value.dy;
390

391
  @override
392
  String toStringShort() => 'vertical drag';
393 394
}

395 396 397
/// Recognizes movement in the horizontal direction.
///
/// Used for horizontal scrolling.
398 399 400 401
///
/// See also:
///
///  * [HorizontalMultiDragGestureRecognizer]
402
class HorizontalDragGestureRecognizer extends DragGestureRecognizer {
403
  @override
404
  bool get _hasSufficientPendingDragDeltaToAccept => _pendingDragOffset.dx.abs() > kTouchSlop;
405 406

  @override
407
  Offset _getDeltaForDetails(Offset delta) => new Offset(delta.dx, 0.0);
408 409

  @override
410
  double _getPrimaryValueFromOffset(Offset value) => value.dx;
411

412
  @override
413
  String toStringShort() => 'horizontal drag';
414 415
}

416
/// Recognizes movement both horizontally and vertically.
417 418 419 420 421
///
/// See also:
///
///  * [ImmediateMultiDragGestureRecognizer]
///  * [DelayedMultiDragGestureRecognizer]
422
class PanGestureRecognizer extends DragGestureRecognizer {
423
  @override
424 425 426
  bool get _hasSufficientPendingDragDeltaToAccept {
    return _pendingDragOffset.distance > kPanSlop;
  }
427 428

  @override
429
  Offset _getDeltaForDetails(Offset delta) => delta;
430 431

  @override
432
  double _getPrimaryValueFromOffset(Offset value) => null;
433

434
  @override
435
  String toStringShort() => 'pan';
436
}