scale.dart 5.14 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

10
/// The possible states of a [ScaleGestureRecognizer].
11
enum ScaleState {
12
  /// The recognizer is ready to start recognizing a gesture.
13
  ready,
14

15
  /// The sequence of pointer events seen thus far is consistent with a scale
16
  /// gesture but the gesture has not been accepted definitively.
17
  possible,
18

19
  /// The sequence of pointer events seen thus far has been accepted
20
  /// definitively as a scale gesture.
21
  accepted,
22

23
  /// The sequence of pointer events seen thus far has been accepted
24 25 26
  /// definitively as a scale gesture and the pointers established a focal point
  /// and initial scale.
  started,
27 28
}

29 30
/// Signature for when the pointers in contact with the screen have established
/// a focal point and initial scale of 1.0.
Ian Hickson's avatar
Ian Hickson committed
31
typedef void GestureScaleStartCallback(Point focalPoint);
32 33 34

/// Signature for when the pointers in contact with the screen have indicated a
/// new focal point and/or scale.
Ian Hickson's avatar
Ian Hickson committed
35
typedef void GestureScaleUpdateCallback(double scale, Point focalPoint);
36 37

/// Signature for when the pointers are no longer in contact with the screen.
38
typedef void GestureScaleEndCallback();
39

40 41 42 43 44 45 46
/// Recognizes a scale gesture.
///
/// [ScaleGestureRecognizer] tracks the pointers in contact with the screen and
/// calculates their focal point and indiciated scale. When a focal pointer is
/// established, the recognizer calls [onStart]. As the focal point and scale
/// change, the recognizer calls [onUpdate]. When the pointers are no longer in
/// contact with the screen, the recognizer calls [onEnd].
47
class ScaleGestureRecognizer extends OneSequenceGestureRecognizer {
48 49
  /// The pointers in contact with the screen have established a focal point and
  /// initial scale of 1.0.
50
  GestureScaleStartCallback onStart;
51 52 53

  /// The pointers in contact with the screen have indicated a new focal point
  /// and/or scale.
54
  GestureScaleUpdateCallback onUpdate;
55 56

  /// The pointers are no longer in contact with the screen.
57
  GestureScaleEndCallback onEnd;
58

59
  ScaleState _state = ScaleState.ready;
60 61 62

  double _initialSpan;
  double _currentSpan;
Ian Hickson's avatar
Ian Hickson committed
63
  Map<int, Point> _pointerLocations;
64

65
  double get _scaleFactor => _initialSpan > 0.0 ? _currentSpan / _initialSpan : 1.0;
66

67
  @override
Ian Hickson's avatar
Ian Hickson committed
68
  void addPointer(PointerEvent event) {
69
    startTrackingPointer(event.pointer);
70 71
    if (_state == ScaleState.ready) {
      _state = ScaleState.possible;
72 73
      _initialSpan = 0.0;
      _currentSpan = 0.0;
Ian Hickson's avatar
Ian Hickson committed
74
      _pointerLocations = new Map<int, Point>();
75 76 77
    }
  }

78
  @override
Ian Hickson's avatar
Ian Hickson committed
79
  void handleEvent(PointerEvent event) {
80
    assert(_state != ScaleState.ready);
81
    bool configChanged = false;
Ian Hickson's avatar
Ian Hickson committed
82 83 84 85 86 87 88 89
    if (event is PointerMoveEvent) {
      _pointerLocations[event.pointer] = event.position;
    } else if (event is PointerDownEvent) {
      configChanged = true;
      _pointerLocations[event.pointer] = event.position;
    } else if (event is PointerUpEvent) {
      configChanged = true;
      _pointerLocations.remove(event.pointer);
90 91
    }

92 93 94 95 96 97
    _update(configChanged);

    stopTrackingIfPointerNoLongerDown(event);
  }

  void _update(bool configChanged) {
98 99 100
    int count = _pointerLocations.keys.length;

    // Compute the focal point
Ian Hickson's avatar
Ian Hickson committed
101
    Point focalPoint = Point.origin;
102 103
    for (int pointer in _pointerLocations.keys)
      focalPoint += _pointerLocations[pointer].toOffset();
Ian Hickson's avatar
Ian Hickson committed
104
    focalPoint = new Point(focalPoint.x / count, focalPoint.y / count);
105 106 107 108 109 110 111 112 113

    // Span is the average deviation from focal point
    double totalDeviation = 0.0;
    for (int pointer in _pointerLocations.keys)
      totalDeviation += (focalPoint - _pointerLocations[pointer]).distance;
    _currentSpan = count > 0 ? totalDeviation / count : 0.0;

    if (configChanged) {
      _initialSpan = _currentSpan;
114
      if (_state == ScaleState.started) {
115 116
        if (onEnd != null)
          onEnd();
117
        _state = ScaleState.accepted;
118 119 120
      }
    }

121 122
    if (_state == ScaleState.ready)
      _state = ScaleState.possible;
123

124 125
    if (_state == ScaleState.possible &&
        (_currentSpan - _initialSpan).abs() > kScaleSlop) {
126 127 128
      resolve(GestureDisposition.accepted);
    }

129 130
    if (_state == ScaleState.accepted && !configChanged) {
      _state = ScaleState.started;
131
      if (onStart != null)
132
        onStart(focalPoint);
133 134
    }

135 136
    if (_state == ScaleState.started && onUpdate != null)
      onUpdate(_scaleFactor, focalPoint);
137 138
  }

139
  @override
140
  void acceptGesture(int pointer) {
141 142 143
    if (_state != ScaleState.accepted) {
      _state = ScaleState.accepted;
      _update(false);
144 145 146
    }
  }

147
  @override
148 149
  void didStopTrackingLastPointer(int pointer) {
    switch(_state) {
150
      case ScaleState.possible:
151 152
        resolve(GestureDisposition.rejected);
        break;
153 154
      case ScaleState.ready:
        assert(false);  // We should have not seen a pointer yet
155
        break;
156
      case ScaleState.accepted:
157
        break;
158 159
      case ScaleState.started:
        assert(false);  // We should be in the accepted state when user is done
160 161
        break;
    }
162
    _state = ScaleState.ready;
163
  }
164

165
  @override
166
  String toStringShort() => 'scale';
167
}