From 842e94e9f856a9e0f99eb486dc0dacdbbad7741d Mon Sep 17 00:00:00 2001 From: Collin Jackson Date: Tue, 15 Sep 2015 17:13:15 -0700 Subject: [PATCH] First pass at support for pinch gestures; panning issues (needs testing) Conflicts: sky/packages/sky/lib/gestures/drag.dart --- packages/flutter/lib/gestures/constants.dart | 2 + packages/flutter/lib/gestures/drag.dart | 4 +- packages/flutter/lib/gestures/pinch.dart | 139 ++++++++++++++++++ .../lib/src/widgets/gesture_detector.dart | 35 ++++- 4 files changed, 177 insertions(+), 3 deletions(-) create mode 100644 packages/flutter/lib/gestures/pinch.dart diff --git a/packages/flutter/lib/gestures/constants.dart b/packages/flutter/lib/gestures/constants.dart index 6f1f2e0f86..281ff59862 100644 --- a/packages/flutter/lib/gestures/constants.dart +++ b/packages/flutter/lib/gestures/constants.dart @@ -17,6 +17,8 @@ const double kEdgeSlop = 12.0; // Logical pixels 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/packages/flutter/lib/gestures/drag.dart b/packages/flutter/lib/gestures/drag.dart index df12580512..867e5d4e01 100644 --- a/packages/flutter/lib/gestures/drag.dart +++ b/packages/flutter/lib/gestures/drag.dart @@ -79,7 +79,7 @@ abstract class _DragGestureRecognizer extends GestureRecogniz 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 @@ class PanGestureRecognizer extends _DragGestureRecognizer { 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/packages/flutter/lib/gestures/pinch.dart b/packages/flutter/lib/gestures/pinch.dart new file mode 100644 index 0000000000..800cf4bff2 --- /dev/null +++ b/packages/flutter/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 _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(); + } + } + + 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/packages/flutter/lib/src/widgets/gesture_detector.dart b/packages/flutter/lib/src/widgets/gesture_detector.dart index 6ed6e458bb..423227b536 100644 --- a/packages/flutter/lib/src/widgets/gesture_detector.dart +++ b/packages/flutter/lib/src/widgets/gesture_detector.dart @@ -6,6 +6,7 @@ import 'dart:sky' as sky; 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 @@ class GestureDetector extends StatefulComponent { 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 @@ class GestureDetector extends StatefulComponent { GesturePanUpdateCallback onPanUpdate; GesturePanEndCallback onPanEnd; + GesturePinchStartCallback onPinchStart; + GesturePinchUpdateCallback onPinchUpdate; + GesturePinchEndCallback onPinchEnd; + void syncConstructorArguments(GestureDetector source) { child = source.child; onTap = source.onTap; @@ -61,6 +69,9 @@ class GestureDetector extends StatefulComponent { onPanStart = source.onPanStart; onPanUpdate = source.onPanUpdate; onPanEnd = source.onPanEnd; + onPinchStart = source.onPinchStart; + onPinchUpdate = source.onPinchUpdate; + onPinchEnd = source.onPinchEnd; _syncGestureListeners(); } @@ -108,6 +119,13 @@ class GestureDetector extends StatefulComponent { 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 @@ class GestureDetector extends StatefulComponent { _verticalDrag = _ensureDisposed(_verticalDrag); _horizontalDrag = _ensureDisposed(_horizontalDrag); _pan = _ensureDisposed(_pan); + _pinch = _ensureDisposed(_pinch); } void _syncGestureListeners() { @@ -130,6 +149,7 @@ class GestureDetector extends StatefulComponent { _syncVerticalDrag(); _syncHorizontalDrag(); _syncPan(); + _syncPinch(); } void _syncTap() { @@ -186,6 +206,17 @@ class GestureDetector extends StatefulComponent { } } + 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 @@ class GestureDetector extends StatefulComponent { _horizontalDrag.addPointer(event); if (_pan != null) _pan.addPointer(event); + if (_pinch != null) + _pinch.addPointer(event); return EventDisposition.processed; }