First pass at support for pinch gestures; panning issues (needs testing)
Conflicts:
sky/packages/sky/lib/gestures/drag.dart
diff --git a/sky/packages/sky/lib/gestures/constants.dart b/sky/packages/sky/lib/gestures/constants.dart
index 6f1f2e0..281ff59 100644
--- a/sky/packages/sky/lib/gestures/constants.dart
+++ b/sky/packages/sky/lib/gestures/constants.dart
@@ -17,6 +17,8 @@
const double kTouchSlop = 8.0; // Logical pixels
const double kDoubleTapTouchSlop = kTouchSlop; // Logical pixels
const double kPagingTouchSlop = kTouchSlop * 2.0; // Logical pixels
+const double kPanSlop = kTouchSlop * 2.0; // Logical pixels
+const double kPinchSlop = kTouchSlop; // Logical pixels
const double kDoubleTapSlop = 100.0; // Logical pixels
const double kWindowTouchSlop = 16.0; // Logical pixels
const double kMinFlingVelocity = 50.0; // Logical pixels / second
diff --git a/sky/packages/sky/lib/gestures/drag.dart b/sky/packages/sky/lib/gestures/drag.dart
index df12580..867e5d4 100644
--- a/sky/packages/sky/lib/gestures/drag.dart
+++ b/sky/packages/sky/lib/gestures/drag.dart
@@ -79,7 +79,7 @@
if (_state != DragState.accepted) {
_state = DragState.accepted;
T delta = _pendingDragDelta;
- _pendingDragDelta = null;
+ _pendingDragDelta = _initialPendingDragDelta;
if (onStart != null)
onStart();
if (delta != _initialPendingDragDelta && onUpdate != null)
@@ -149,6 +149,6 @@
sky.Offset get _initialPendingDragDelta => sky.Offset.zero;
sky.Offset _getDragDelta(sky.PointerEvent event) => new sky.Offset(event.dx, event.dy);
bool get _hasSufficientPendingDragDeltaToAccept {
- return _pendingDragDelta.dx.abs() > kTouchSlop || _pendingDragDelta.dy.abs() > kTouchSlop;
+ return _pendingDragDelta.distance > kPanSlop;
}
}
diff --git a/sky/packages/sky/lib/gestures/pinch.dart b/sky/packages/sky/lib/gestures/pinch.dart
new file mode 100644
index 0000000..800cf4b
--- /dev/null
+++ b/sky/packages/sky/lib/gestures/pinch.dart
@@ -0,0 +1,139 @@
+// 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 'dart:math' as math;
+import 'dart:sky' as sky;
+
+import 'package:sky/gestures/arena.dart';
+import 'package:sky/gestures/recognizer.dart';
+import 'package:sky/gestures/constants.dart';
+
+enum PinchState {
+ ready,
+ possible,
+ started,
+ ended
+}
+
+typedef void GesturePinchStartCallback();
+typedef void GesturePinchUpdateCallback(double scale);
+typedef void GesturePinchEndCallback();
+
+class PinchGestureRecognizer extends GestureRecognizer {
+ PinchGestureRecognizer({ PointerRouter router, this.onStart, this.onUpdate, this.onEnd })
+ : super(router: router);
+
+ GesturePinchStartCallback onStart;
+ GesturePinchUpdateCallback onUpdate;
+ GesturePinchEndCallback onEnd;
+
+ PinchState _state = PinchState.ready;
+
+ double _initialSpan;
+ double _currentSpan;
+ Map<int, sky.Point> _pointerLocations;
+
+ double get _scaleFactor => _initialSpan > 0 ? _currentSpan / _initialSpan : 1.0;
+
+ void addPointer(sky.PointerEvent event) {
+ startTrackingPointer(event.pointer);
+ if (_state == PinchState.ready) {
+ _state = PinchState.possible;
+ _initialSpan = 0.0;
+ _currentSpan = 0.0;
+ _pointerLocations = new Map<int, sky.Point>();
+ }
+ }
+
+ void handleEvent(sky.PointerEvent event) {
+ assert(_state != PinchState.ready);
+ bool configChanged = false;
+ switch(event.type) {
+ case 'pointerup':
+ configChanged = true;
+ _pointerLocations.remove(event.pointer);
+ break;
+ case 'pointerdown':
+ configChanged = true;
+ _pointerLocations[event.pointer] = new sky.Point(event.x, event.y);
+ break;
+ case 'pointermove':
+ _pointerLocations[event.pointer] = new sky.Point(event.x, event.y);
+ break;
+ }
+
+ int count = _pointerLocations.keys.length;
+
+ // Compute the focal point
+ sky.Point focalPoint = sky.Point.origin;
+ for (int pointer in _pointerLocations.keys)
+ focalPoint += _pointerLocations[pointer].toOffset();
+ focalPoint = new sky.Point(focalPoint.x / count, focalPoint.y / count);
+
+ // 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;
+ if (_state == PinchState.started) {
+ _state = PinchState.ended;
+ if (onEnd != null)
+ onEnd();
+ }
+ }
+
+ if (_state == PinchState.ready)
+ _state = PinchState.possible;
+
+ if (_state == PinchState.possible &&
+ (_currentSpan - _initialSpan).abs() > kPinchSlop) {
+ resolve(GestureDisposition.accepted);
+ }
+
+ if (_state == PinchState.ended && _currentSpan != _initialSpan) {
+ _state = PinchState.started;
+ if (onStart != null)
+ onStart();
+ }
+
+ if (_state == PinchState.started && onUpdate != null)
+ onUpdate(_scaleFactor);
+
+ stopTrackingIfPointerNoLongerDown(event);
+ }
+
+ void acceptGesture(int pointer) {
+ if (_state != PinchState.started) {
+ _state = PinchState.started;
+ if (onStart != null)
+ onStart();
+ if (onUpdate != null)
+ onUpdate(_scaleFactor);
+ }
+ }
+
+ void didStopTrackingLastPointer(int pointer) {
+ switch(_state) {
+ case PinchState.possible:
+ resolve(GestureDisposition.rejected);
+ break;
+ case PinchState.ready:
+ assert(false);
+ break;
+ case PinchState.started:
+ assert(false);
+ break;
+ case PinchState.ended:
+ break;
+ }
+ _state = PinchState.ready;
+ }
+
+ void dispose() {
+ super.dispose();
+ }
+}
diff --git a/sky/packages/sky/lib/src/widgets/gesture_detector.dart b/sky/packages/sky/lib/src/widgets/gesture_detector.dart
index 6ed6e45..423227b 100644
--- a/sky/packages/sky/lib/src/widgets/gesture_detector.dart
+++ b/sky/packages/sky/lib/src/widgets/gesture_detector.dart
@@ -6,6 +6,7 @@
import 'package:sky/gestures/drag.dart';
import 'package:sky/gestures/long_press.dart';
+import 'package:sky/gestures/pinch.dart';
import 'package:sky/gestures/recognizer.dart';
import 'package:sky/gestures/show_press.dart';
import 'package:sky/gestures/tap.dart';
@@ -27,7 +28,10 @@
this.onHorizontalDragEnd,
this.onPanStart,
this.onPanUpdate,
- this.onPanEnd
+ this.onPanEnd,
+ this.onPinchStart,
+ this.onPinchUpdate,
+ this.onPinchEnd
}) : super(key: key);
Widget child;
@@ -47,6 +51,10 @@
GesturePanUpdateCallback onPanUpdate;
GesturePanEndCallback onPanEnd;
+ GesturePinchStartCallback onPinchStart;
+ GesturePinchUpdateCallback onPinchUpdate;
+ GesturePinchEndCallback onPinchEnd;
+
void syncConstructorArguments(GestureDetector source) {
child = source.child;
onTap = source.onTap;
@@ -61,6 +69,9 @@
onPanStart = source.onPanStart;
onPanUpdate = source.onPanUpdate;
onPanEnd = source.onPanEnd;
+ onPinchStart = source.onPinchStart;
+ onPinchUpdate = source.onPinchUpdate;
+ onPinchEnd = source.onPinchEnd;
_syncGestureListeners();
}
@@ -108,6 +119,13 @@
return _pan;
}
+ PinchGestureRecognizer _pinch;
+ PinchGestureRecognizer _ensurePinch() {
+ if (_pinch == null)
+ _pinch = new PinchGestureRecognizer(router: _router);
+ return _pinch;
+ }
+
void didMount() {
super.didMount();
_syncGestureListeners();
@@ -121,6 +139,7 @@
_verticalDrag = _ensureDisposed(_verticalDrag);
_horizontalDrag = _ensureDisposed(_horizontalDrag);
_pan = _ensureDisposed(_pan);
+ _pinch = _ensureDisposed(_pinch);
}
void _syncGestureListeners() {
@@ -130,6 +149,7 @@
_syncVerticalDrag();
_syncHorizontalDrag();
_syncPan();
+ _syncPinch();
}
void _syncTap() {
@@ -186,6 +206,17 @@
}
}
+ void _syncPinch() {
+ if (onPinchStart == null && onPinchUpdate == null && onPinchEnd == null) {
+ _pinch = _ensureDisposed(_pan);
+ } else {
+ _ensurePinch()
+ ..onStart = onPinchStart
+ ..onUpdate = onPinchUpdate
+ ..onEnd = onPinchEnd;
+ }
+ }
+
GestureRecognizer _ensureDisposed(GestureRecognizer recognizer) {
recognizer?.dispose();
return null;
@@ -204,6 +235,8 @@
_horizontalDrag.addPointer(event);
if (_pan != null)
_pan.addPointer(event);
+ if (_pinch != null)
+ _pinch.addPointer(event);
return EventDisposition.processed;
}