Commit 81b3bdd4 authored by Adam Barth's avatar Adam Barth

Merge pull request #930 from abarth/scroll

Add scroll gestures and use them in Scrollable
parents 9047830c e99ff362
......@@ -9,6 +9,8 @@ import 'package:sky/base/pointer_router.dart';
import 'package:sky/gestures/arena.dart';
import 'package:sky/gestures/constants.dart';
export 'package:sky/base/pointer_router.dart' show PointerRouter;
abstract class GestureRecognizer extends GestureArenaMember {
GestureRecognizer({ PointerRouter router }) : _router = router;
......@@ -21,8 +23,8 @@ abstract class GestureRecognizer extends GestureArenaMember {
void addPointer(sky.PointerEvent event);
void handleEvent(sky.PointerEvent event);
void acceptGesture(int key);
void rejectGesture(int key);
void acceptGesture(int pointer) { }
void rejectGesture(int pointer) { }
void didStopTrackingLastPointer();
void resolve(GestureDisposition disposition) {
......@@ -115,9 +117,6 @@ abstract class PrimaryPointerGestureRecognizer extends GestureRecognizer {
assert(deadline == null);
}
void acceptGesture(int pointer) {
}
void rejectGesture(int pointer) {
_stopTimer();
if (pointer == primaryPointer)
......
// 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:sky' as sky;
import 'package:sky/gestures/arena.dart';
import 'package:sky/gestures/recognizer.dart';
import 'package:sky/gestures/constants.dart';
enum ScrollState {
ready,
possible,
accepted
}
typedef void GestureScrollStartCallback(sky.Offset scrollDelta);
typedef void GestureScrollUpdateCallback(sky.Offset scrollDelta);
typedef void GestureScrollEndCallback();
sky.Offset _getScrollOffset(sky.PointerEvent event) {
// Notice that we negate dy because scroll offsets go in the opposite direction.
return new sky.Offset(event.dx, -event.dy);
}
class ScrollGestureRecognizer extends GestureRecognizer {
ScrollGestureRecognizer({ PointerRouter router, this.onScrollStart, this.onScrollUpdate, this.onScrollEnd })
: super(router: router);
GestureScrollStartCallback onScrollStart;
GestureScrollUpdateCallback onScrollUpdate;
GestureScrollEndCallback onScrollEnd;
ScrollState state = ScrollState.ready;
sky.Offset pendingScrollOffset;
void addPointer(sky.PointerEvent event) {
startTrackingPointer(event.pointer);
if (state == ScrollState.ready) {
state = ScrollState.possible;
pendingScrollOffset = sky.Offset.zero;
}
}
void handleEvent(sky.PointerEvent event) {
assert(state != ScrollState.ready);
if (event.type == 'pointermove') {
sky.Offset offset = _getScrollOffset(event);
if (state == ScrollState.accepted) {
onScrollUpdate(offset);
} else {
pendingScrollOffset += offset;
if (pendingScrollOffset.distance > kTouchSlop)
resolve(GestureDisposition.accepted);
}
}
stopTrackingIfPointerNoLongerDown(event);
}
void acceptGesture(int pointer) {
if (state != ScrollState.accepted) {
state = ScrollState.accepted;
sky.Offset offset = pendingScrollOffset;
pendingScrollOffset = null;
onScrollStart(offset);
}
}
void didStopTrackingLastPointer() {
bool wasAccepted = (state == ScrollState.accepted);
state = ScrollState.ready;
if (wasAccepted)
onScrollEnd();
}
}
......@@ -4,7 +4,6 @@
import 'dart:sky' as sky;
import 'package:sky/base/pointer_router.dart';
import 'package:sky/gestures/arena.dart';
import 'package:sky/gestures/constants.dart';
import 'package:sky/gestures/recognizer.dart';
......
......@@ -4,7 +4,6 @@
import 'dart:sky' as sky;
import 'package:sky/base/pointer_router.dart';
import 'package:sky/gestures/arena.dart';
import 'package:sky/gestures/recognizer.dart';
......
......@@ -4,9 +4,9 @@
import 'dart:sky' as sky;
import 'package:sky/base/pointer_router.dart';
import 'package:sky/gestures/long_press.dart';
import 'package:sky/gestures/recognizer.dart';
import 'package:sky/gestures/scroll.dart';
import 'package:sky/gestures/show_press.dart';
import 'package:sky/gestures/tap.dart';
import 'package:sky/rendering/sky_binding.dart';
......@@ -18,19 +18,28 @@ class GestureDetector extends StatefulComponent {
this.child,
this.onTap,
this.onShowPress,
this.onLongPress
this.onLongPress,
this.onScrollStart,
this.onScrollUpdate,
this.onScrollEnd
}) : super(key: key);
Widget child;
GestureTapListener onTap;
GestureShowPressListener onShowPress;
GestureLongPressListener onLongPress;
GestureScrollStartCallback onScrollStart;
GestureScrollUpdateCallback onScrollUpdate;
GestureScrollEndCallback onScrollEnd;
void syncConstructorArguments(GestureDetector source) {
child = source.child;
onTap = source.onTap;
onShowPress = source.onShowPress;
onLongPress = source.onLongPress;
onScrollStart = source.onScrollStart;
onScrollUpdate = source.onScrollUpdate;
onScrollEnd = source.onScrollEnd;
_syncGestureListeners();
}
......@@ -57,6 +66,13 @@ class GestureDetector extends StatefulComponent {
return _longPress;
}
ScrollGestureRecognizer _scroll;
ScrollGestureRecognizer _ensureScroll() {
if (_scroll == null)
_scroll = new ScrollGestureRecognizer(router: _router);
return _scroll;
}
void didMount() {
super.didMount();
_syncGestureListeners();
......@@ -67,12 +83,14 @@ class GestureDetector extends StatefulComponent {
_tap = _ensureDisposed(_tap);
_showPress = _ensureDisposed(_showPress);
_longPress = _ensureDisposed(_longPress);
_scroll = _ensureDisposed(_scroll);
}
void _syncGestureListeners() {
_syncTap();
_syncShowPress();
_syncLongPress();
_syncScroll();
}
void _syncTap() {
......@@ -96,6 +114,17 @@ class GestureDetector extends StatefulComponent {
_ensureLongPress().onLongPress = onLongPress;
}
void _syncScroll() {
if (onScrollStart == null && onScrollUpdate == null && onScrollEnd == null) {
_scroll = _ensureDisposed(_scroll);
} else {
_ensureScroll()
..onScrollStart = onScrollStart
..onScrollUpdate = onScrollUpdate
..onScrollEnd = onScrollEnd;
}
}
GestureRecognizer _ensureDisposed(GestureRecognizer recognizer) {
if (recognizer != null)
recognizer.dispose();
......@@ -109,6 +138,8 @@ class GestureDetector extends StatefulComponent {
_showPress.addPointer(event);
if (_longPress != null)
_longPress.addPointer(event);
if (_scroll != null)
_scroll.addPointer(event);
return EventDisposition.processed;
}
......
......@@ -8,8 +8,8 @@ import 'dart:sky' as sky;
import 'package:newton/newton.dart';
import 'package:sky/animation/animated_simulation.dart';
import 'package:sky/animation/animation_performance.dart';
import 'package:sky/animation/animated_value.dart';
import 'package:sky/animation/animation_performance.dart';
import 'package:sky/animation/curves.dart';
import 'package:sky/animation/scroll_behavior.dart';
import 'package:sky/gestures/constants.dart';
......@@ -17,6 +17,7 @@ import 'package:sky/rendering/box.dart';
import 'package:sky/rendering/viewport.dart';
import 'package:sky/widgets/basic.dart';
import 'package:sky/widgets/framework.dart';
import 'package:sky/widgets/gesture_detector.dart';
import 'package:sky/widgets/mixed_viewport.dart';
import 'package:sky/widgets/scrollable.dart';
......@@ -83,15 +84,17 @@ abstract class Scrollable extends StatefulComponent {
Widget buildContent();
Widget build() {
return new Listener(
child: buildContent(),
onPointerDown: _handlePointerDown,
onPointerUp: _handlePointerUpOrCancel,
onPointerCancel: _handlePointerUpOrCancel,
onGestureFlingStart: _handleFlingStart,
onGestureFlingCancel: _handleFlingCancel,
onGestureScrollUpdate: _handleScrollUpdate,
onWheel: _handleWheel
return new GestureDetector(
onScrollStart: _handleScrollOffset,
onScrollUpdate: _handleScrollOffset,
onScrollEnd: _maybeSettleScrollOffset,
child: new Listener(
child: buildContent(),
onPointerDown: _handlePointerDown,
onGestureFlingStart: _handleFlingStart,
onGestureFlingCancel: _handleFlingCancel,
onWheel: _handleWheel
)
);
}
......@@ -171,9 +174,8 @@ abstract class Scrollable extends StatefulComponent {
return EventDisposition.processed;
}
EventDisposition _handleScrollUpdate(sky.GestureEvent event) {
scrollBy(scrollDirection == ScrollDirection.horizontal ? event.dx : -event.dy);
return EventDisposition.processed;
void _handleScrollOffset(Offset offset) {
scrollBy(scrollDirection == ScrollDirection.horizontal ? offset.dx : offset.dy);
}
EventDisposition _handleFlingStart(sky.GestureEvent event) {
......@@ -187,11 +189,6 @@ abstract class Scrollable extends StatefulComponent {
settleScrollOffset();
}
EventDisposition _handlePointerUpOrCancel(_) {
_maybeSettleScrollOffset();
return EventDisposition.processed;
}
EventDisposition _handleFlingCancel(sky.GestureEvent event) {
_maybeSettleScrollOffset();
return EventDisposition.processed;
......
import 'dart:sky' as sky;
import 'package:sky/base/pointer_router.dart';
import 'package:sky/gestures/scroll.dart';
import 'package:test/test.dart';
import '../engine/mock_events.dart';
TestPointerEvent down = new TestPointerEvent(
pointer: 5,
type: 'pointerdown',
x: 10.0,
y: 10.0
);
TestPointerEvent move1 = new TestPointerEvent(
pointer: 5,
type: 'pointermove',
x: 20.0,
y: 20.0,
dx: 10.0,
dy: 10.0
);
TestPointerEvent move2 = new TestPointerEvent(
pointer: 5,
type: 'pointermove',
x: 20.0,
y: 25.0,
dx: 0.0,
dy: 5.0
);
TestPointerEvent up = new TestPointerEvent(
pointer: 5,
type: 'pointerup',
x: 20.0,
y: 25.0
);
void main() {
test('Should recognize scroll', () {
PointerRouter router = new PointerRouter();
ScrollGestureRecognizer scroll = new ScrollGestureRecognizer(router: router);
sky.Offset startOffset;
scroll.onScrollStart = (sky.Offset offset) {
startOffset = offset;
};
sky.Offset updateOffset;
scroll.onScrollUpdate = (sky.Offset offset) {
updateOffset = offset;
};
bool didEndScroll = false;
scroll.onScrollEnd = () {
didEndScroll = true;
};
scroll.addPointer(down);
expect(startOffset, isNull);
expect(updateOffset, isNull);
expect(didEndScroll, isFalse);
router.handleEvent(down, null);
expect(startOffset, isNull);
expect(updateOffset, isNull);
expect(didEndScroll, isFalse);
router.handleEvent(move1, null);
expect(startOffset, new sky.Offset(10.0, -10.0));
startOffset = null;
expect(updateOffset, isNull);
expect(didEndScroll, isFalse);
router.handleEvent(move2, null);
expect(startOffset, isNull);
expect(updateOffset, new sky.Offset(0.0, -5.0));
updateOffset = null;
expect(didEndScroll, isFalse);
router.handleEvent(up, null);
expect(startOffset, isNull);
expect(updateOffset, isNull);
expect(didEndScroll, isTrue);
didEndScroll = false;
scroll.dispose();
});
}
......@@ -83,26 +83,39 @@ class WidgetTester {
return box.localToGlobal(box.size.center(Point.origin));
}
HitTestResult _hitTest(Point location) => SkyBinding.instance.hitTest(location);
EventDisposition _dispatchEvent(sky.Event event, HitTestResult result) {
return SkyBinding.instance.dispatchEvent(event, result);
}
void tap(Widget widget) {
Point location = getCenter(widget);
dispatchEvent(new TestPointerEvent(type: 'pointerdown', x: location.x, y: location.y), location);
dispatchEvent(new TestPointerEvent(type: 'pointerup', x: location.x, y: location.y), location);
HitTestResult result = _hitTest(location);
_dispatchEvent(new TestPointerEvent(type: 'pointerdown', x: location.x, y: location.y), result);
_dispatchEvent(new TestPointerEvent(type: 'pointerup', x: location.x, y: location.y), result);
}
void scroll(Widget widget, Offset offset) {
dispatchEvent(new TestGestureEvent(type: 'gesturescrollstart'), getCenter(widget));
dispatchEvent(new TestGestureEvent(
type: 'gesturescrollupdate',
dx: offset.dx,
dy: offset.dy), getCenter(widget));
// pointerup to trigger scroll settling in Scrollable<T>
dispatchEvent(new TestPointerEvent(
type: 'pointerup', down: false, primary: true), getCenter(widget));
Point startLocation = getCenter(widget);
HitTestResult result = _hitTest(startLocation);
_dispatchEvent(new TestPointerEvent(type: 'pointerdown', x: startLocation.x, y: startLocation.y), result);
Point endLocation = startLocation + offset;
_dispatchEvent(
new TestPointerEvent(
type: 'pointermove',
x: endLocation.x,
y: endLocation.y,
dx: offset.dx,
dy: offset.dy
),
result
);
_dispatchEvent(new TestPointerEvent(type: 'pointerup', x: endLocation.x, y: endLocation.y), result);
}
void dispatchEvent(sky.Event event, Point position) {
HitTestResult result = SkyBinding.instance.hitTest(position);
SkyBinding.instance.dispatchEvent(event, result);
void dispatchEvent(sky.Event event, Point location) {
_dispatchEvent(event, _hitTest(location));
}
void pumpFrame(WidgetBuilder builder, [double frameTimeMs = 0.0]) {
......
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