Commit b40864c9 authored by Adam Barth's avatar Adam Barth

Disambiguate horizontal and vertical scrolling

We now have separate gestures for horizontal, vertical, and pan scrolling.
parent a5d9c0cb
...@@ -15,43 +15,48 @@ enum ScrollState { ...@@ -15,43 +15,48 @@ enum ScrollState {
} }
typedef void GestureScrollStartCallback(); typedef void GestureScrollStartCallback();
typedef void GestureScrollUpdateCallback(sky.Offset scrollDelta); typedef void GestureScrollUpdateCallback(double scrollDelta);
typedef void GestureScrollEndCallback(); typedef void GestureScrollEndCallback();
sky.Offset _getScrollOffset(sky.PointerEvent event) { typedef void GesturePanStartCallback();
// Notice that we negate dy because scroll offsets go in the opposite direction. typedef void GesturePanUpdateCallback(sky.Offset scrollDelta);
return new sky.Offset(event.dx, -event.dy); typedef void GesturePanEndCallback();
}
class ScrollGestureRecognizer extends GestureRecognizer { typedef void _GesturePolymorphicUpdateCallback<T>(T scrollDelta);
ScrollGestureRecognizer({ PointerRouter router, this.onScrollStart, this.onScrollUpdate, this.onScrollEnd })
abstract class _ScrollGestureRecognizer<T extends dynamic> extends GestureRecognizer {
_ScrollGestureRecognizer({ PointerRouter router, this.onStart, this.onUpdate, this.onEnd })
: super(router: router); : super(router: router);
GestureScrollStartCallback onScrollStart; GestureScrollStartCallback onStart;
GestureScrollUpdateCallback onScrollUpdate; _GesturePolymorphicUpdateCallback<T> onUpdate;
GestureScrollEndCallback onScrollEnd; GestureScrollEndCallback onEnd;
ScrollState _state = ScrollState.ready;
T _pendingScrollDelta;
ScrollState state = ScrollState.ready; T get _initialPendingScrollDelta;
sky.Offset pendingScrollOffset; T _getScrollDelta(sky.PointerEvent event);
bool get _hasSufficientPendingScrollDeltaToAccept;
void addPointer(sky.PointerEvent event) { void addPointer(sky.PointerEvent event) {
startTrackingPointer(event.pointer); startTrackingPointer(event.pointer);
if (state == ScrollState.ready) { if (_state == ScrollState.ready) {
state = ScrollState.possible; _state = ScrollState.possible;
pendingScrollOffset = sky.Offset.zero; _pendingScrollDelta = _initialPendingScrollDelta;
} }
} }
void handleEvent(sky.PointerEvent event) { void handleEvent(sky.PointerEvent event) {
assert(state != ScrollState.ready); assert(_state != ScrollState.ready);
if (event.type == 'pointermove') { if (event.type == 'pointermove') {
sky.Offset offset = _getScrollOffset(event); T delta = _getScrollDelta(event);
if (state == ScrollState.accepted) { if (_state == ScrollState.accepted) {
if (onScrollUpdate != null) if (onUpdate != null)
onScrollUpdate(offset); onUpdate(delta);
} else { } else {
pendingScrollOffset += offset; _pendingScrollDelta += delta;
if (pendingScrollOffset.distance > kTouchSlop) if (_hasSufficientPendingScrollDeltaToAccept)
resolve(GestureDisposition.accepted); resolve(GestureDisposition.accepted);
} }
} }
...@@ -59,22 +64,64 @@ class ScrollGestureRecognizer extends GestureRecognizer { ...@@ -59,22 +64,64 @@ class ScrollGestureRecognizer extends GestureRecognizer {
} }
void acceptGesture(int pointer) { void acceptGesture(int pointer) {
if (state != ScrollState.accepted) { if (_state != ScrollState.accepted) {
state = ScrollState.accepted; _state = ScrollState.accepted;
sky.Offset offset = pendingScrollOffset; T delta = _pendingScrollDelta;
pendingScrollOffset = null; _pendingScrollDelta = null;
if (onScrollStart != null) if (onStart != null)
onScrollStart(); onStart();
if (offset != sky.Offset.zero && onScrollUpdate != null) if (delta != _initialPendingScrollDelta && onUpdate != null)
onScrollUpdate(offset); onUpdate(delta);
} }
} }
void didStopTrackingLastPointer() { void didStopTrackingLastPointer() {
bool wasAccepted = (state == ScrollState.accepted); bool wasAccepted = (_state == ScrollState.accepted);
state = ScrollState.ready; _state = ScrollState.ready;
if (wasAccepted && onScrollEnd != null) if (wasAccepted && onEnd != null)
onScrollEnd(); onEnd();
} }
}
class VerticalScrollGestureRecognizer extends _ScrollGestureRecognizer<double> {
VerticalScrollGestureRecognizer({
PointerRouter router,
GestureScrollStartCallback onStart,
GestureScrollUpdateCallback onUpdate,
GestureScrollEndCallback onEnd
}) : super(router: router, onStart: onStart, onUpdate: onUpdate, onEnd: onEnd);
double get _initialPendingScrollDelta => 0.0;
// Notice that we negate dy because scroll offsets go in the opposite direction.
double _getScrollDelta(sky.PointerEvent event) => -event.dy;
bool get _hasSufficientPendingScrollDeltaToAccept => _pendingScrollDelta.abs() > kTouchSlop;
}
class HorizontalScrollGestureRecognizer extends _ScrollGestureRecognizer<double> {
HorizontalScrollGestureRecognizer({
PointerRouter router,
GestureScrollStartCallback onStart,
GestureScrollUpdateCallback onUpdate,
GestureScrollEndCallback onEnd
}) : super(router: router, onStart: onStart, onUpdate: onUpdate, onEnd: onEnd);
double get _initialPendingScrollDelta => 0.0;
double _getScrollDelta(sky.PointerEvent event) => -event.dx;
bool get _hasSufficientPendingScrollDeltaToAccept => _pendingScrollDelta.abs() > kTouchSlop;
}
class PanGestureRecognizer extends _ScrollGestureRecognizer<sky.Offset> {
PanGestureRecognizer({
PointerRouter router,
GesturePanStartCallback onStart,
GesturePanUpdateCallback onUpdate,
GesturePanEndCallback onEnd
}) : super(router: router, onStart: onStart, onUpdate: onUpdate, onEnd: onEnd);
sky.Offset get _initialPendingScrollDelta => sky.Offset.zero;
// Notice that we negate dy because scroll offsets go in the opposite direction.
sky.Offset _getScrollDelta(sky.PointerEvent event) => new sky.Offset(event.dx, -event.dy);
bool get _hasSufficientPendingScrollDeltaToAccept {
return _pendingScrollDelta.dx.abs() > kTouchSlop || _pendingScrollDelta.dy.abs() > kTouchSlop;
}
} }
...@@ -19,27 +19,48 @@ class GestureDetector extends StatefulComponent { ...@@ -19,27 +19,48 @@ class GestureDetector extends StatefulComponent {
this.onTap, this.onTap,
this.onShowPress, this.onShowPress,
this.onLongPress, this.onLongPress,
this.onScrollStart, this.onVerticalScrollStart,
this.onScrollUpdate, this.onVerticalScrollUpdate,
this.onScrollEnd this.onVerticalScrollEnd,
this.onHorizontalScrollStart,
this.onHorizontalScrollUpdate,
this.onHorizontalScrollEnd,
this.onPanStart,
this.onPanUpdate,
this.onPanEnd
}) : 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; GestureScrollStartCallback onVerticalScrollStart;
GestureScrollEndCallback onScrollEnd; GestureScrollUpdateCallback onVerticalScrollUpdate;
GestureScrollEndCallback onVerticalScrollEnd;
GestureScrollStartCallback onHorizontalScrollStart;
GestureScrollUpdateCallback onHorizontalScrollUpdate;
GestureScrollEndCallback onHorizontalScrollEnd;
GesturePanStartCallback onPanStart;
GesturePanUpdateCallback onPanUpdate;
GesturePanEndCallback onPanEnd;
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; onVerticalScrollStart = onVerticalScrollStart;
onScrollUpdate = source.onScrollUpdate; onVerticalScrollUpdate = onVerticalScrollUpdate;
onScrollEnd = source.onScrollEnd; onVerticalScrollEnd = onVerticalScrollEnd;
onHorizontalScrollStart = onHorizontalScrollStart;
onHorizontalScrollUpdate = onHorizontalScrollUpdate;
onHorizontalScrollEnd = onHorizontalScrollEnd;
onPanStart = source.onPanStart;
onPanUpdate = source.onPanUpdate;
onPanEnd = source.onPanEnd;
_syncGestureListeners(); _syncGestureListeners();
} }
...@@ -66,11 +87,25 @@ class GestureDetector extends StatefulComponent { ...@@ -66,11 +87,25 @@ class GestureDetector extends StatefulComponent {
return _longPress; return _longPress;
} }
ScrollGestureRecognizer _scroll; VerticalScrollGestureRecognizer _verticalScroll;
ScrollGestureRecognizer _ensureScroll() { VerticalScrollGestureRecognizer _ensureVerticalScroll() {
if (_scroll == null) if (_verticalScroll == null)
_scroll = new ScrollGestureRecognizer(router: _router); _verticalScroll = new VerticalScrollGestureRecognizer(router: _router);
return _scroll; return _verticalScroll;
}
HorizontalScrollGestureRecognizer _horizontalScroll;
HorizontalScrollGestureRecognizer _ensureHorizontalScroll() {
if (_horizontalScroll == null)
_horizontalScroll = new HorizontalScrollGestureRecognizer(router: _router);
return _horizontalScroll;
}
PanGestureRecognizer _pan;
PanGestureRecognizer _ensurePan() {
if (_pan == null)
_pan = new PanGestureRecognizer(router: _router);
return _pan;
} }
void didMount() { void didMount() {
...@@ -83,14 +118,18 @@ class GestureDetector extends StatefulComponent { ...@@ -83,14 +118,18 @@ 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); _verticalScroll = _ensureDisposed(_verticalScroll);
_horizontalScroll = _ensureDisposed(_horizontalScroll);
_pan = _ensureDisposed(_pan);
} }
void _syncGestureListeners() { void _syncGestureListeners() {
_syncTap(); _syncTap();
_syncShowPress(); _syncShowPress();
_syncLongPress(); _syncLongPress();
_syncScroll(); _syncVerticalScroll();
_syncHorizontalScroll();
_syncPan();
} }
void _syncTap() { void _syncTap() {
...@@ -114,14 +153,36 @@ class GestureDetector extends StatefulComponent { ...@@ -114,14 +153,36 @@ class GestureDetector extends StatefulComponent {
_ensureLongPress().onLongPress = onLongPress; _ensureLongPress().onLongPress = onLongPress;
} }
void _syncScroll() { void _syncVerticalScroll() {
if (onScrollStart == null && onScrollUpdate == null && onScrollEnd == null) { if (onVerticalScrollStart == null && onVerticalScrollUpdate == null && onVerticalScrollEnd == null) {
_scroll = _ensureDisposed(_scroll); _verticalScroll = _ensureDisposed(_verticalScroll);
} else {
_ensureVerticalScroll()
..onStart = onVerticalScrollStart
..onUpdate = onVerticalScrollUpdate
..onEnd = onVerticalScrollEnd;
}
}
void _syncHorizontalScroll() {
if (onHorizontalScrollStart == null && onHorizontalScrollUpdate == null && onHorizontalScrollEnd == null) {
_horizontalScroll = _ensureDisposed(_horizontalScroll);
} else {
_ensureHorizontalScroll()
..onStart = onHorizontalScrollStart
..onUpdate = onHorizontalScrollUpdate
..onEnd = onHorizontalScrollEnd;
}
}
void _syncPan() {
if (onPanStart == null && onPanUpdate == null && onPanEnd == null) {
_pan = _ensureDisposed(_pan);
} else { } else {
_ensureScroll() _ensurePan()
..onScrollStart = onScrollStart ..onStart = onPanStart
..onScrollUpdate = onScrollUpdate ..onUpdate = onPanUpdate
..onScrollEnd = onScrollEnd; ..onEnd = onPanEnd;
} }
} }
...@@ -138,8 +199,12 @@ class GestureDetector extends StatefulComponent { ...@@ -138,8 +199,12 @@ 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) if (_verticalScroll != null)
_scroll.addPointer(event); _verticalScroll.addPointer(event);
if (_horizontalScroll != null)
_horizontalScroll.addPointer(event);
if (_pan != null)
_pan.addPointer(event);
return EventDisposition.processed; return EventDisposition.processed;
} }
......
...@@ -84,8 +84,10 @@ abstract class Scrollable extends StatefulComponent { ...@@ -84,8 +84,10 @@ abstract class Scrollable extends StatefulComponent {
Widget build() { Widget build() {
return new GestureDetector( return new GestureDetector(
onScrollUpdate: _handleScrollUpdate, onVerticalScrollUpdate: scrollDirection == ScrollDirection.vertical ? scrollBy : null,
onScrollEnd: _maybeSettleScrollOffset, onVerticalScrollEnd: scrollDirection == ScrollDirection.vertical ? _maybeSettleScrollOffset : null,
onHorizontalScrollUpdate: scrollDirection == ScrollDirection.horizontal ? scrollBy : null,
onHorizontalScrollEnd: scrollDirection == ScrollDirection.horizontal ? _maybeSettleScrollOffset : null,
child: new Listener( child: new Listener(
child: buildContent(), child: buildContent(),
onPointerDown: _handlePointerDown, onPointerDown: _handlePointerDown,
...@@ -172,10 +174,6 @@ abstract class Scrollable extends StatefulComponent { ...@@ -172,10 +174,6 @@ abstract class Scrollable extends StatefulComponent {
return EventDisposition.processed; return EventDisposition.processed;
} }
void _handleScrollUpdate(Offset offset) {
scrollBy(scrollDirection == ScrollDirection.horizontal ? offset.dx : offset.dy);
}
EventDisposition _handleFlingStart(sky.GestureEvent event) { EventDisposition _handleFlingStart(sky.GestureEvent event) {
_startToEndAnimation(velocity: _eventVelocity(event)); _startToEndAnimation(velocity: _eventVelocity(event));
return EventDisposition.processed; return EventDisposition.processed;
......
...@@ -41,20 +41,20 @@ TestPointerEvent up = new TestPointerEvent( ...@@ -41,20 +41,20 @@ TestPointerEvent up = new TestPointerEvent(
void main() { void main() {
test('Should recognize scroll', () { test('Should recognize scroll', () {
PointerRouter router = new PointerRouter(); PointerRouter router = new PointerRouter();
ScrollGestureRecognizer scroll = new ScrollGestureRecognizer(router: router); PanGestureRecognizer scroll = new PanGestureRecognizer(router: router);
bool didStartScroll = false; bool didStartScroll = false;
scroll.onScrollStart = () { scroll.onStart = () {
didStartScroll = true; didStartScroll = true;
}; };
sky.Offset updateOffset; sky.Offset updateOffset;
scroll.onScrollUpdate = (sky.Offset offset) { scroll.onUpdate = (sky.Offset offset) {
updateOffset = offset; updateOffset = offset;
}; };
bool didEndScroll = false; bool didEndScroll = false;
scroll.onScrollEnd = () { scroll.onEnd = () {
didEndScroll = true; didEndScroll = true;
}; };
......
...@@ -43,7 +43,7 @@ void main() { ...@@ -43,7 +43,7 @@ void main() {
expect(currentPage, isNull); expect(currentPage, isNull);
new FakeAsync().run((async) { new FakeAsync().run((async) {
tester.scroll(tester.findText('1'), new Offset(300.0, 0.0)); tester.scroll(tester.findText('1'), new Offset(-300.0, 0.0));
// One frame to start the animation, a second to complete it. // One frame to start the animation, a second to complete it.
tester.pumpFrame(builder); tester.pumpFrame(builder);
tester.pumpFrame(builder, 5000.0); tester.pumpFrame(builder, 5000.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