Commit e99ff362 authored by Adam Barth's avatar Adam Barth

Add scroll gestures and use them in Scrollable

This patch replaces the scroll gestures used by Scrollable with ones detected
by the ScrollGestureDetector.
parent 12e3897f
...@@ -9,6 +9,8 @@ import 'package:sky/base/pointer_router.dart'; ...@@ -9,6 +9,8 @@ import 'package:sky/base/pointer_router.dart';
import 'package:sky/gestures/arena.dart'; import 'package:sky/gestures/arena.dart';
import 'package:sky/gestures/constants.dart'; import 'package:sky/gestures/constants.dart';
export 'package:sky/base/pointer_router.dart' show PointerRouter;
abstract class GestureRecognizer extends GestureArenaMember { abstract class GestureRecognizer extends GestureArenaMember {
GestureRecognizer({ PointerRouter router }) : _router = router; GestureRecognizer({ PointerRouter router }) : _router = router;
...@@ -21,8 +23,8 @@ abstract class GestureRecognizer extends GestureArenaMember { ...@@ -21,8 +23,8 @@ abstract class GestureRecognizer extends GestureArenaMember {
void addPointer(sky.PointerEvent event); void addPointer(sky.PointerEvent event);
void handleEvent(sky.PointerEvent event); void handleEvent(sky.PointerEvent event);
void acceptGesture(int key); void acceptGesture(int pointer) { }
void rejectGesture(int key); void rejectGesture(int pointer) { }
void didStopTrackingLastPointer(); void didStopTrackingLastPointer();
void resolve(GestureDisposition disposition) { void resolve(GestureDisposition disposition) {
...@@ -115,9 +117,6 @@ abstract class PrimaryPointerGestureRecognizer extends GestureRecognizer { ...@@ -115,9 +117,6 @@ abstract class PrimaryPointerGestureRecognizer extends GestureRecognizer {
assert(deadline == null); assert(deadline == null);
} }
void acceptGesture(int pointer) {
}
void rejectGesture(int pointer) { void rejectGesture(int pointer) {
_stopTimer(); _stopTimer();
if (pointer == primaryPointer) 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 @@ ...@@ -4,7 +4,6 @@
import 'dart:sky' as sky; import 'dart:sky' as sky;
import 'package:sky/base/pointer_router.dart';
import 'package:sky/gestures/arena.dart'; import 'package:sky/gestures/arena.dart';
import 'package:sky/gestures/constants.dart'; import 'package:sky/gestures/constants.dart';
import 'package:sky/gestures/recognizer.dart'; import 'package:sky/gestures/recognizer.dart';
......
...@@ -4,7 +4,6 @@ ...@@ -4,7 +4,6 @@
import 'dart:sky' as sky; import 'dart:sky' as sky;
import 'package:sky/base/pointer_router.dart';
import 'package:sky/gestures/arena.dart'; import 'package:sky/gestures/arena.dart';
import 'package:sky/gestures/recognizer.dart'; import 'package:sky/gestures/recognizer.dart';
......
...@@ -4,9 +4,9 @@ ...@@ -4,9 +4,9 @@
import 'dart:sky' as sky; import 'dart:sky' as sky;
import 'package:sky/base/pointer_router.dart';
import 'package:sky/gestures/long_press.dart'; import 'package:sky/gestures/long_press.dart';
import 'package:sky/gestures/recognizer.dart'; import 'package:sky/gestures/recognizer.dart';
import 'package:sky/gestures/scroll.dart';
import 'package:sky/gestures/show_press.dart'; import 'package:sky/gestures/show_press.dart';
import 'package:sky/gestures/tap.dart'; import 'package:sky/gestures/tap.dart';
import 'package:sky/rendering/sky_binding.dart'; import 'package:sky/rendering/sky_binding.dart';
...@@ -18,19 +18,28 @@ class GestureDetector extends StatefulComponent { ...@@ -18,19 +18,28 @@ class GestureDetector extends StatefulComponent {
this.child, this.child,
this.onTap, this.onTap,
this.onShowPress, this.onShowPress,
this.onLongPress this.onLongPress,
this.onScrollStart,
this.onScrollUpdate,
this.onScrollEnd
}) : super(key: key); }) : super(key: key);
Widget child; Widget child;
GestureTapListener onTap; GestureTapListener onTap;
GestureShowPressListener onShowPress; GestureShowPressListener onShowPress;
GestureLongPressListener onLongPress; GestureLongPressListener onLongPress;
GestureScrollStartCallback onScrollStart;
GestureScrollUpdateCallback onScrollUpdate;
GestureScrollEndCallback onScrollEnd;
void syncConstructorArguments(GestureDetector source) { void syncConstructorArguments(GestureDetector source) {
child = source.child; child = source.child;
onTap = source.onTap; onTap = source.onTap;
onShowPress = source.onShowPress; onShowPress = source.onShowPress;
onLongPress = source.onLongPress; onLongPress = source.onLongPress;
onScrollStart = source.onScrollStart;
onScrollUpdate = source.onScrollUpdate;
onScrollEnd = source.onScrollEnd;
_syncGestureListeners(); _syncGestureListeners();
} }
...@@ -57,6 +66,13 @@ class GestureDetector extends StatefulComponent { ...@@ -57,6 +66,13 @@ class GestureDetector extends StatefulComponent {
return _longPress; return _longPress;
} }
ScrollGestureRecognizer _scroll;
ScrollGestureRecognizer _ensureScroll() {
if (_scroll == null)
_scroll = new ScrollGestureRecognizer(router: _router);
return _scroll;
}
void didMount() { void didMount() {
super.didMount(); super.didMount();
_syncGestureListeners(); _syncGestureListeners();
...@@ -67,12 +83,14 @@ class GestureDetector extends StatefulComponent { ...@@ -67,12 +83,14 @@ class GestureDetector extends StatefulComponent {
_tap = _ensureDisposed(_tap); _tap = _ensureDisposed(_tap);
_showPress = _ensureDisposed(_showPress); _showPress = _ensureDisposed(_showPress);
_longPress = _ensureDisposed(_longPress); _longPress = _ensureDisposed(_longPress);
_scroll = _ensureDisposed(_scroll);
} }
void _syncGestureListeners() { void _syncGestureListeners() {
_syncTap(); _syncTap();
_syncShowPress(); _syncShowPress();
_syncLongPress(); _syncLongPress();
_syncScroll();
} }
void _syncTap() { void _syncTap() {
...@@ -96,6 +114,17 @@ class GestureDetector extends StatefulComponent { ...@@ -96,6 +114,17 @@ class GestureDetector extends StatefulComponent {
_ensureLongPress().onLongPress = onLongPress; _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) { GestureRecognizer _ensureDisposed(GestureRecognizer recognizer) {
if (recognizer != null) if (recognizer != null)
recognizer.dispose(); recognizer.dispose();
...@@ -109,6 +138,8 @@ class GestureDetector extends StatefulComponent { ...@@ -109,6 +138,8 @@ class GestureDetector extends StatefulComponent {
_showPress.addPointer(event); _showPress.addPointer(event);
if (_longPress != null) if (_longPress != null)
_longPress.addPointer(event); _longPress.addPointer(event);
if (_scroll != null)
_scroll.addPointer(event);
return EventDisposition.processed; return EventDisposition.processed;
} }
......
...@@ -8,8 +8,8 @@ import 'dart:sky' as sky; ...@@ -8,8 +8,8 @@ import 'dart:sky' as sky;
import 'package:newton/newton.dart'; import 'package:newton/newton.dart';
import 'package:sky/animation/animated_simulation.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/animated_value.dart';
import 'package:sky/animation/animation_performance.dart';
import 'package:sky/animation/curves.dart'; import 'package:sky/animation/curves.dart';
import 'package:sky/animation/scroll_behavior.dart'; import 'package:sky/animation/scroll_behavior.dart';
import 'package:sky/gestures/constants.dart'; import 'package:sky/gestures/constants.dart';
...@@ -17,6 +17,7 @@ import 'package:sky/rendering/box.dart'; ...@@ -17,6 +17,7 @@ import 'package:sky/rendering/box.dart';
import 'package:sky/rendering/viewport.dart'; import 'package:sky/rendering/viewport.dart';
import 'package:sky/widgets/basic.dart'; import 'package:sky/widgets/basic.dart';
import 'package:sky/widgets/framework.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/mixed_viewport.dart';
import 'package:sky/widgets/scrollable.dart'; import 'package:sky/widgets/scrollable.dart';
...@@ -83,15 +84,17 @@ abstract class Scrollable extends StatefulComponent { ...@@ -83,15 +84,17 @@ abstract class Scrollable extends StatefulComponent {
Widget buildContent(); Widget buildContent();
Widget build() { Widget build() {
return new Listener( return new GestureDetector(
child: buildContent(), onScrollStart: _handleScrollOffset,
onPointerDown: _handlePointerDown, onScrollUpdate: _handleScrollOffset,
onPointerUp: _handlePointerUpOrCancel, onScrollEnd: _maybeSettleScrollOffset,
onPointerCancel: _handlePointerUpOrCancel, child: new Listener(
onGestureFlingStart: _handleFlingStart, child: buildContent(),
onGestureFlingCancel: _handleFlingCancel, onPointerDown: _handlePointerDown,
onGestureScrollUpdate: _handleScrollUpdate, onGestureFlingStart: _handleFlingStart,
onWheel: _handleWheel onGestureFlingCancel: _handleFlingCancel,
onWheel: _handleWheel
)
); );
} }
...@@ -171,9 +174,8 @@ abstract class Scrollable extends StatefulComponent { ...@@ -171,9 +174,8 @@ abstract class Scrollable extends StatefulComponent {
return EventDisposition.processed; return EventDisposition.processed;
} }
EventDisposition _handleScrollUpdate(sky.GestureEvent event) { void _handleScrollOffset(Offset offset) {
scrollBy(scrollDirection == ScrollDirection.horizontal ? event.dx : -event.dy); scrollBy(scrollDirection == ScrollDirection.horizontal ? offset.dx : offset.dy);
return EventDisposition.processed;
} }
EventDisposition _handleFlingStart(sky.GestureEvent event) { EventDisposition _handleFlingStart(sky.GestureEvent event) {
...@@ -187,11 +189,6 @@ abstract class Scrollable extends StatefulComponent { ...@@ -187,11 +189,6 @@ abstract class Scrollable extends StatefulComponent {
settleScrollOffset(); settleScrollOffset();
} }
EventDisposition _handlePointerUpOrCancel(_) {
_maybeSettleScrollOffset();
return EventDisposition.processed;
}
EventDisposition _handleFlingCancel(sky.GestureEvent event) { EventDisposition _handleFlingCancel(sky.GestureEvent event) {
_maybeSettleScrollOffset(); _maybeSettleScrollOffset();
return EventDisposition.processed; 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 { ...@@ -83,26 +83,39 @@ class WidgetTester {
return box.localToGlobal(box.size.center(Point.origin)); 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) { void tap(Widget widget) {
Point location = getCenter(widget); Point location = getCenter(widget);
dispatchEvent(new TestPointerEvent(type: 'pointerdown', x: location.x, y: location.y), location); HitTestResult result = _hitTest(location);
dispatchEvent(new TestPointerEvent(type: 'pointerup', x: location.x, y: location.y), 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) { void scroll(Widget widget, Offset offset) {
dispatchEvent(new TestGestureEvent(type: 'gesturescrollstart'), getCenter(widget)); Point startLocation = getCenter(widget);
dispatchEvent(new TestGestureEvent( HitTestResult result = _hitTest(startLocation);
type: 'gesturescrollupdate', _dispatchEvent(new TestPointerEvent(type: 'pointerdown', x: startLocation.x, y: startLocation.y), result);
dx: offset.dx, Point endLocation = startLocation + offset;
dy: offset.dy), getCenter(widget)); _dispatchEvent(
// pointerup to trigger scroll settling in Scrollable<T> new TestPointerEvent(
dispatchEvent(new TestPointerEvent( type: 'pointermove',
type: 'pointerup', down: false, primary: true), getCenter(widget)); 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) { void dispatchEvent(sky.Event event, Point location) {
HitTestResult result = SkyBinding.instance.hitTest(position); _dispatchEvent(event, _hitTest(location));
SkyBinding.instance.dispatchEvent(event, result);
} }
void pumpFrame(WidgetBuilder builder, [double frameTimeMs = 0.0]) { 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