monodrag.dart 11.9 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
// 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 'arena.dart';
import 'constants.dart';
import 'drag_details.dart';
import 'events.dart';
import 'recognizer.dart';
import 'velocity_tracker.dart';

enum _DragState {
  ready,
  possible,
  accepted,
}

/// Signature for when a pointer that was previously in contact with the screen
/// and moving is no longer in contact with the screen.
///
/// The velocity at which the pointer was moving when it stopped contacting
/// the screen is available in the `details`.
///
/// See [DragGestureRecognizer.onEnd].
25
typedef GestureDragEndCallback = void Function(DragEndDetails details);
26 27 28 29 30

/// Signature for when the pointer that previously triggered a
/// [GestureDragDownCallback] did not complete.
///
/// See [DragGestureRecognizer.onCancel].
31
typedef GestureDragCancelCallback = void Function();
32 33 34 35 36 37 38 39 40 41 42 43 44 45

/// 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:
///
46 47 48
///  * [HorizontalDragGestureRecognizer], for left and right drags.
///  * [VerticalDragGestureRecognizer], for up and down drags.
///  * [PanGestureRecognizer], for drags that are not locked to a single axis.
49
abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer {
50 51 52
  /// Initialize the object.
  DragGestureRecognizer({ Object debugOwner }) : super(debugOwner: debugOwner);

53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
  /// A pointer has contacted the screen and might begin to move.
  ///
  /// The position of the pointer is provided in the callback's `details`
  /// argument, which is a [DragDownDetails] object.
  GestureDragDownCallback onDown;

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

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

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

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

  /// The minimum distance an input pointer drag must have moved to
  /// to be considered a fling gesture.
  ///
  /// This value is typically compared with the distance traveled along the
  /// scrolling axis. If null then [kTouchSlop] is used.
  double minFlingDistance;

  /// The minimum velocity for an input pointer drag to be considered fling.
  ///
  /// This value is typically compared with the magnitude of fling gesture's
  /// velocity along the scrolling axis. If null then [kMinFlingVelocity]
  /// is used.
  double minFlingVelocity;

  /// Fling velocity magnitudes will be clamped to this value.
  ///
  /// If null then [kMaxFlingVelocity] is used.
  double maxFlingVelocity;

  _DragState _state = _DragState.ready;
  Offset _initialPosition;
  Offset _pendingDragOffset;
104
  Duration _lastPendingEventTimestamp;
105 106 107 108 109 110 111 112 113 114 115

  bool _isFlingGesture(VelocityEstimate estimate);
  Offset _getDeltaForDetails(Offset delta);
  double _getPrimaryValueFromOffset(Offset value);
  bool get _hasSufficientPendingDragDeltaToAccept;

  final Map<int, VelocityTracker> _velocityTrackers = <int, VelocityTracker>{};

  @override
  void addPointer(PointerEvent event) {
    startTrackingPointer(event.pointer);
116
    _velocityTrackers[event.pointer] = VelocityTracker();
117 118 119 120
    if (_state == _DragState.ready) {
      _state = _DragState.possible;
      _initialPosition = event.position;
      _pendingDragOffset = Offset.zero;
121
      _lastPendingEventTimestamp = event.timeStamp;
122
      if (onDown != null)
123
        invokeCallback<void>('onDown', () => onDown(DragDownDetails(globalPosition: _initialPosition)));
124 125
    } else if (_state == _DragState.accepted) {
      resolve(GestureDisposition.accepted);
126 127 128 129 130 131
    }
  }

  @override
  void handleEvent(PointerEvent event) {
    assert(_state != _DragState.ready);
132 133
    if (!event.synthesized
        && (event is PointerDownEvent || event is PointerMoveEvent)) {
134 135
      final VelocityTracker tracker = _velocityTrackers[event.pointer];
      assert(tracker != null);
136 137 138 139
      tracker.addPosition(event.timeStamp, event.position);
    }

    if (event is PointerMoveEvent) {
140 141 142
      final Offset delta = event.delta;
      if (_state == _DragState.accepted) {
        if (onUpdate != null) {
143
          invokeCallback<void>('onUpdate', () => onUpdate(DragUpdateDetails(
144
            sourceTimeStamp: event.timeStamp,
145 146 147 148 149 150 151
            delta: _getDeltaForDetails(delta),
            primaryDelta: _getPrimaryValueFromOffset(delta),
            globalPosition: event.position,
          )));
        }
      } else {
        _pendingDragOffset += delta;
152
        _lastPendingEventTimestamp = event.timeStamp;
153 154 155 156 157 158 159 160 161 162 163 164
        if (_hasSufficientPendingDragDeltaToAccept)
          resolve(GestureDisposition.accepted);
      }
    }
    stopTrackingIfPointerNoLongerDown(event);
  }

  @override
  void acceptGesture(int pointer) {
    if (_state != _DragState.accepted) {
      _state = _DragState.accepted;
      final Offset delta = _pendingDragOffset;
165
      final Duration timestamp = _lastPendingEventTimestamp;
166
      _pendingDragOffset = Offset.zero;
167
      _lastPendingEventTimestamp = null;
168
      if (onStart != null) {
169
        invokeCallback<void>('onStart', () => onStart(DragStartDetails(
170
          sourceTimeStamp: timestamp,
171 172 173 174
          globalPosition: _initialPosition,
        )));
      }
      if (delta != Offset.zero && onUpdate != null) {
jslavitz's avatar
jslavitz committed
175
        final Offset deltaForDetails = _getDeltaForDetails(delta);
176
        invokeCallback<void>('onUpdate', () => onUpdate(DragUpdateDetails(
177
          sourceTimeStamp: timestamp,
jslavitz's avatar
jslavitz committed
178
          delta: deltaForDetails,
179
          primaryDelta: _getPrimaryValueFromOffset(delta),
jslavitz's avatar
jslavitz committed
180
          globalPosition: _initialPosition + deltaForDetails,
181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196
        )));
      }
    }
  }

  @override
  void rejectGesture(int pointer) {
    stopTrackingPointer(pointer);
  }

  @override
  void didStopTrackingLastPointer(int pointer) {
    if (_state == _DragState.possible) {
      resolve(GestureDisposition.rejected);
      _state = _DragState.ready;
      if (onCancel != null)
197
        invokeCallback<void>('onCancel', onCancel);
198 199
      return;
    }
200
    final bool wasAccepted = _state == _DragState.accepted;
201 202 203 204 205 206 207
    _state = _DragState.ready;
    if (wasAccepted && onEnd != null) {
      final VelocityTracker tracker = _velocityTrackers[pointer];
      assert(tracker != null);

      final VelocityEstimate estimate = tracker.getVelocityEstimate();
      if (estimate != null && _isFlingGesture(estimate)) {
208
        final Velocity velocity = Velocity(pixelsPerSecond: estimate.pixelsPerSecond)
209
          .clampMagnitude(minFlingVelocity ?? kMinFlingVelocity, maxFlingVelocity ?? kMaxFlingVelocity);
210
        invokeCallback<void>('onEnd', () => onEnd(DragEndDetails(
211 212
          velocity: velocity,
          primaryVelocity: _getPrimaryValueFromOffset(velocity.pixelsPerSecond),
213 214 215
        )), debugReport: () {
          return '$estimate; fling at $velocity.';
        });
216
      } else {
217
        invokeCallback<void>('onEnd', () => onEnd(DragEndDetails(
218 219
          velocity: Velocity.zero,
          primaryVelocity: 0.0,
220 221 222 223 224
        )), debugReport: () {
          if (estimate == null)
            return 'Could not estimate velocity.';
          return '$estimate; judged to not be a fling.';
        });
225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242
      }
    }
    _velocityTrackers.clear();
  }

  @override
  void dispose() {
    _velocityTrackers.clear();
    super.dispose();
  }
}

/// Recognizes movement in the vertical direction.
///
/// Used for vertical scrolling.
///
/// See also:
///
243 244 245 246
///  * [HorizontalDragGestureRecognizer], for a similar recognizer but for
///    horizontal movement.
///  * [MultiDragGestureRecognizer], for a family of gesture recognizers that
///    track each touch point independently.
247
class VerticalDragGestureRecognizer extends DragGestureRecognizer {
248 249 250
  /// Create a gesture recognizer for interactions in the vertical axis.
  VerticalDragGestureRecognizer({ Object debugOwner }) : super(debugOwner: debugOwner);

251 252 253 254 255 256 257 258 259 260 261
  @override
  bool _isFlingGesture(VelocityEstimate estimate) {
    final double minVelocity = minFlingVelocity ?? kMinFlingVelocity;
    final double minDistance = minFlingDistance ?? kTouchSlop;
    return estimate.pixelsPerSecond.dy.abs() > minVelocity && estimate.offset.dy.abs() > minDistance;
  }

  @override
  bool get _hasSufficientPendingDragDeltaToAccept => _pendingDragOffset.dy.abs() > kTouchSlop;

  @override
262
  Offset _getDeltaForDetails(Offset delta) => Offset(0.0, delta.dy);
263 264 265 266 267

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

  @override
268
  String get debugDescription => 'vertical drag';
269 270 271 272 273 274 275 276
}

/// Recognizes movement in the horizontal direction.
///
/// Used for horizontal scrolling.
///
/// See also:
///
277 278 279 280
///  * [VerticalDragGestureRecognizer], for a similar recognizer but for
///    vertical movement.
///  * [MultiDragGestureRecognizer], for a family of gesture recognizers that
///    track each touch point independently.
281
class HorizontalDragGestureRecognizer extends DragGestureRecognizer {
282 283 284
  /// Create a gesture recognizer for interactions in the horizontal axis.
  HorizontalDragGestureRecognizer({ Object debugOwner }) : super(debugOwner: debugOwner);

285 286 287 288 289 290 291 292 293 294 295
  @override
  bool _isFlingGesture(VelocityEstimate estimate) {
    final double minVelocity = minFlingVelocity ?? kMinFlingVelocity;
    final double minDistance = minFlingDistance ?? kTouchSlop;
    return estimate.pixelsPerSecond.dx.abs() > minVelocity && estimate.offset.dx.abs() > minDistance;
  }

  @override
  bool get _hasSufficientPendingDragDeltaToAccept => _pendingDragOffset.dx.abs() > kTouchSlop;

  @override
296
  Offset _getDeltaForDetails(Offset delta) => Offset(delta.dx, 0.0);
297 298 299 300 301

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

  @override
302
  String get debugDescription => 'horizontal drag';
303 304 305 306 307 308
}

/// Recognizes movement both horizontally and vertically.
///
/// See also:
///
309 310 311 312 313
///  * [ImmediateMultiDragGestureRecognizer], for a similar recognizer that
///    tracks each touch point independently.
///  * [DelayedMultiDragGestureRecognizer], for a similar recognizer that
///    tracks each touch point independently, but that doesn't start until
///    some time has passed.
314
class PanGestureRecognizer extends DragGestureRecognizer {
315 316 317
  /// Create a gesture recognizer for tracking movement on a plane.
  PanGestureRecognizer({ Object debugOwner }) : super(debugOwner: debugOwner);

318 319 320 321
  @override
  bool _isFlingGesture(VelocityEstimate estimate) {
    final double minVelocity = minFlingVelocity ?? kMinFlingVelocity;
    final double minDistance = minFlingDistance ?? kTouchSlop;
322 323
    return estimate.pixelsPerSecond.distanceSquared > minVelocity * minVelocity
        && estimate.offset.distanceSquared > minDistance * minDistance;
324 325 326 327 328 329 330 331 332 333 334 335 336 337
  }

  @override
  bool get _hasSufficientPendingDragDeltaToAccept {
    return _pendingDragOffset.distance > kPanSlop;
  }

  @override
  Offset _getDeltaForDetails(Offset delta) => delta;

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

  @override
338
  String get debugDescription => 'pan';
339
}