scale.dart 3.74 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
enum ScaleState {
11 12
  ready,
  possible,
13 14
  accepted,
  started
15 16
}

Ian Hickson's avatar
Ian Hickson committed
17 18
typedef void GestureScaleStartCallback(Point focalPoint);
typedef void GestureScaleUpdateCallback(double scale, Point focalPoint);
19
typedef void GestureScaleEndCallback();
20

21
class ScaleGestureRecognizer extends OneSequenceGestureRecognizer {
Ian Hickson's avatar
Ian Hickson committed
22 23 24 25 26 27 28 29 30 31
  ScaleGestureRecognizer({
    PointerRouter router,
    GestureArena gestureArena,
    this.onStart,
    this.onUpdate,
    this.onEnd
  }) : super(
    router: router,
    gestureArena: gestureArena
  );
32

33 34 35
  GestureScaleStartCallback onStart;
  GestureScaleUpdateCallback onUpdate;
  GestureScaleEndCallback onEnd;
36

37
  ScaleState _state = ScaleState.ready;
38 39 40

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

43
  double get _scaleFactor => _initialSpan > 0.0 ? _currentSpan / _initialSpan : 1.0;
44

Ian Hickson's avatar
Ian Hickson committed
45
  void addPointer(PointerEvent event) {
46
    startTrackingPointer(event.pointer);
47 48
    if (_state == ScaleState.ready) {
      _state = ScaleState.possible;
49 50
      _initialSpan = 0.0;
      _currentSpan = 0.0;
Ian Hickson's avatar
Ian Hickson committed
51
      _pointerLocations = new Map<int, Point>();
52 53 54
    }
  }

Ian Hickson's avatar
Ian Hickson committed
55
  void handleEvent(PointerEvent event) {
56
    assert(_state != ScaleState.ready);
57
    bool configChanged = false;
Ian Hickson's avatar
Ian Hickson committed
58 59 60 61 62 63 64 65
    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);
66 67
    }

68 69 70 71 72 73
    _update(configChanged);

    stopTrackingIfPointerNoLongerDown(event);
  }

  void _update(bool configChanged) {
74 75 76
    int count = _pointerLocations.keys.length;

    // Compute the focal point
Ian Hickson's avatar
Ian Hickson committed
77
    Point focalPoint = Point.origin;
78 79
    for (int pointer in _pointerLocations.keys)
      focalPoint += _pointerLocations[pointer].toOffset();
Ian Hickson's avatar
Ian Hickson committed
80
    focalPoint = new Point(focalPoint.x / count, focalPoint.y / count);
81 82 83 84 85 86 87 88 89

    // 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;
90
      if (_state == ScaleState.started) {
91 92
        if (onEnd != null)
          onEnd();
93
        _state = ScaleState.accepted;
94 95 96
      }
    }

97 98
    if (_state == ScaleState.ready)
      _state = ScaleState.possible;
99

100 101
    if (_state == ScaleState.possible &&
        (_currentSpan - _initialSpan).abs() > kScaleSlop) {
102 103 104
      resolve(GestureDisposition.accepted);
    }

105 106
    if (_state == ScaleState.accepted && !configChanged) {
      _state = ScaleState.started;
107
      if (onStart != null)
108
        onStart(focalPoint);
109 110
    }

111 112
    if (_state == ScaleState.started && onUpdate != null)
      onUpdate(_scaleFactor, focalPoint);
113 114 115
  }

  void acceptGesture(int pointer) {
116 117 118
    if (_state != ScaleState.accepted) {
      _state = ScaleState.accepted;
      _update(false);
119 120 121 122 123
    }
  }

  void didStopTrackingLastPointer(int pointer) {
    switch(_state) {
124
      case ScaleState.possible:
125 126
        resolve(GestureDisposition.rejected);
        break;
127 128
      case ScaleState.ready:
        assert(false);  // We should have not seen a pointer yet
129
        break;
130
      case ScaleState.accepted:
131
        break;
132 133
      case ScaleState.started:
        assert(false);  // We should be in the accepted state when user is done
134 135
        break;
    }
136
    _state = ScaleState.ready;
137 138
  }
}