Commit d602589d authored by Collin Jackson's avatar Collin Jackson

Merge pull request #1181 from collinjackson/pinch

First pass at support for pinch gestures; panning issues (needs testing)
parents f55a6ad1 109a496e
// 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 'package:sky/rendering.dart';
import 'package:sky/theme/colors.dart' as colors;
import 'package:sky/widgets.dart';
class ScaleApp extends App {
Point _startingFocalPoint;
Offset _previousOffset;
Offset _offset;
double _previousZoom;
double _zoom;
void initState() {
_offset = Offset.zero;
_zoom = 1.0;
}
void _handleScaleStart(Point focalPoint) {
setState(() {
_startingFocalPoint = focalPoint;
_previousOffset = _offset;
_previousZoom = _zoom;
});
}
void _handleScaleUpdate(double scale, Point focalPoint) {
setState(() {
_zoom = _previousZoom * scale;
_offset = _previousOffset + (focalPoint - _startingFocalPoint) / _zoom;
});
}
void callback(PaintingCanvas canvas, Size size) {
Point center = size.center(Point.origin) + _offset * _zoom;
double radius = size.width / 2.0 * _zoom;
Gradient gradient = new RadialGradient(
center: center, radius: radius,
colors: [colors.Blue[200], colors.Blue[800]]
);
Paint paint = new Paint()
..shader = gradient.createShader();
canvas.drawCircle(center, radius, paint);
}
Widget build() {
return new Theme(
data: new ThemeData.dark(),
child: new Scaffold(
toolbar: new ToolBar(
center: new Text('Scale Demo')),
body: new Material(
type: MaterialType.canvas,
child: new GestureDetector(
onScaleStart: _handleScaleStart,
onScaleUpdate: _handleScaleUpdate,
child: new CustomPaint(callback: callback)
)
)
)
);
}
}
void main() => runApp(new ScaleApp());
......@@ -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 kScaleSlop = 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
......
......@@ -79,7 +79,7 @@ abstract class _DragGestureRecognizer<T extends dynamic> 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)
......@@ -100,7 +100,6 @@ abstract class _DragGestureRecognizer<T extends dynamic> extends GestureRecogniz
sky.Offset velocity = sky.Offset.zero;
if (_isFlingGesture(gestureVelocity))
velocity = new sky.Offset(gestureVelocity.x, gestureVelocity.y);
resolve(GestureDisposition.accepted);
onEnd(velocity);
}
_velocityTracker.reset();
......@@ -149,6 +148,6 @@ class PanGestureRecognizer extends _DragGestureRecognizer<sky.Offset> {
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;
}
}
// 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 ScaleState {
ready,
possible,
accepted,
started
}
typedef void GestureScaleStartCallback(sky.Point focalPoint);
typedef void GestureScaleUpdateCallback(double scale, sky.Point focalPoint);
typedef void GestureScaleEndCallback();
class ScaleGestureRecognizer extends GestureRecognizer {
ScaleGestureRecognizer({ PointerRouter router, this.onStart, this.onUpdate, this.onEnd })
: super(router: router);
GestureScaleStartCallback onStart;
GestureScaleUpdateCallback onUpdate;
GestureScaleEndCallback onEnd;
ScaleState _state = ScaleState.ready;
double _initialSpan;
double _currentSpan;
Map<int, sky.Point> _pointerLocations;
double get _scaleFactor => _initialSpan > 0.0 ? _currentSpan / _initialSpan : 1.0;
void addPointer(sky.PointerEvent event) {
startTrackingPointer(event.pointer);
if (_state == ScaleState.ready) {
_state = ScaleState.possible;
_initialSpan = 0.0;
_currentSpan = 0.0;
_pointerLocations = new Map<int, sky.Point>();
}
}
void handleEvent(sky.PointerEvent event) {
assert(_state != ScaleState.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;
}
_update(configChanged);
stopTrackingIfPointerNoLongerDown(event);
}
void _update(bool configChanged) {
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 == ScaleState.started) {
if (onEnd != null)
onEnd();
_state = ScaleState.accepted;
}
}
if (_state == ScaleState.ready)
_state = ScaleState.possible;
if (_state == ScaleState.possible &&
(_currentSpan - _initialSpan).abs() > kScaleSlop) {
resolve(GestureDisposition.accepted);
}
if (_state == ScaleState.accepted && !configChanged) {
_state = ScaleState.started;
if (onStart != null)
onStart(focalPoint);
}
if (_state == ScaleState.started && onUpdate != null)
onUpdate(_scaleFactor, focalPoint);
}
void acceptGesture(int pointer) {
if (_state != ScaleState.accepted) {
_state = ScaleState.accepted;
_update(false);
}
}
void didStopTrackingLastPointer(int pointer) {
switch(_state) {
case ScaleState.possible:
resolve(GestureDisposition.rejected);
break;
case ScaleState.ready:
assert(false); // We should have not seen a pointer yet
break;
case ScaleState.accepted:
break;
case ScaleState.started:
assert(false); // We should be in the accepted state when user is done
break;
}
_state = ScaleState.ready;
}
void dispose() {
super.dispose();
}
}
......@@ -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/scale.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.onScaleStart,
this.onScaleUpdate,
this.onScaleEnd
}) : super(key: key);
Widget child;
......@@ -47,6 +51,10 @@ class GestureDetector extends StatefulComponent {
GesturePanUpdateCallback onPanUpdate;
GesturePanEndCallback onPanEnd;
GestureScaleStartCallback onScaleStart;
GestureScaleUpdateCallback onScaleUpdate;
GestureScaleEndCallback onScaleEnd;
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;
onScaleStart = source.onScaleStart;
onScaleUpdate = source.onScaleUpdate;
onScaleEnd = source.onScaleEnd;
_syncGestureListeners();
}
......@@ -103,11 +114,20 @@ class GestureDetector extends StatefulComponent {
PanGestureRecognizer _pan;
PanGestureRecognizer _ensurePan() {
assert(_scale == null); // Scale is a superset of pan; just use scale
if (_pan == null)
_pan = new PanGestureRecognizer(router: _router);
return _pan;
}
ScaleGestureRecognizer _scale;
ScaleGestureRecognizer _ensureScale() {
assert(_pan == null); // Scale is a superset of pan; just use scale
if (_scale == null)
_scale = new ScaleGestureRecognizer(router: _router);
return _scale;
}
void didMount() {
super.didMount();
_syncGestureListeners();
......@@ -121,6 +141,7 @@ class GestureDetector extends StatefulComponent {
_verticalDrag = _ensureDisposed(_verticalDrag);
_horizontalDrag = _ensureDisposed(_horizontalDrag);
_pan = _ensureDisposed(_pan);
_scale = _ensureDisposed(_scale);
}
void _syncGestureListeners() {
......@@ -130,6 +151,7 @@ class GestureDetector extends StatefulComponent {
_syncVerticalDrag();
_syncHorizontalDrag();
_syncPan();
_syncScale();
}
void _syncTap() {
......@@ -186,6 +208,17 @@ class GestureDetector extends StatefulComponent {
}
}
void _syncScale() {
if (onScaleStart == null && onScaleUpdate == null && onScaleEnd == null) {
_scale = _ensureDisposed(_pan);
} else {
_ensureScale()
..onStart = onScaleStart
..onUpdate = onScaleUpdate
..onEnd = onScaleEnd;
}
}
GestureRecognizer _ensureDisposed(GestureRecognizer recognizer) {
recognizer?.dispose();
return null;
......@@ -204,6 +237,8 @@ class GestureDetector extends StatefulComponent {
_horizontalDrag.addPointer(event);
if (_pan != null)
_pan.addPointer(event);
if (_scale != null)
_scale.addPointer(event);
return EventDisposition.processed;
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment