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 {
}
typedef void GestureScrollStartCallback();
typedef void GestureScrollUpdateCallback(sky.Offset scrollDelta);
typedef void GestureScrollUpdateCallback(double 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);
}
typedef void GesturePanStartCallback();
typedef void GesturePanUpdateCallback(sky.Offset scrollDelta);
typedef void GesturePanEndCallback();
class ScrollGestureRecognizer extends GestureRecognizer {
ScrollGestureRecognizer({ PointerRouter router, this.onScrollStart, this.onScrollUpdate, this.onScrollEnd })
typedef void _GesturePolymorphicUpdateCallback<T>(T scrollDelta);
abstract class _ScrollGestureRecognizer<T extends dynamic> extends GestureRecognizer {
_ScrollGestureRecognizer({ PointerRouter router, this.onStart, this.onUpdate, this.onEnd })
: super(router: router);
GestureScrollStartCallback onScrollStart;
GestureScrollUpdateCallback onScrollUpdate;
GestureScrollEndCallback onScrollEnd;
GestureScrollStartCallback onStart;
_GesturePolymorphicUpdateCallback<T> onUpdate;
GestureScrollEndCallback onEnd;
ScrollState _state = ScrollState.ready;
T _pendingScrollDelta;
ScrollState state = ScrollState.ready;
sky.Offset pendingScrollOffset;
T get _initialPendingScrollDelta;
T _getScrollDelta(sky.PointerEvent event);
bool get _hasSufficientPendingScrollDeltaToAccept;
void addPointer(sky.PointerEvent event) {
startTrackingPointer(event.pointer);
if (state == ScrollState.ready) {
state = ScrollState.possible;
pendingScrollOffset = sky.Offset.zero;
if (_state == ScrollState.ready) {
_state = ScrollState.possible;
_pendingScrollDelta = _initialPendingScrollDelta;
}
}
void handleEvent(sky.PointerEvent event) {
assert(state != ScrollState.ready);
assert(_state != ScrollState.ready);
if (event.type == 'pointermove') {
sky.Offset offset = _getScrollOffset(event);
if (state == ScrollState.accepted) {
if (onScrollUpdate != null)
onScrollUpdate(offset);
T delta = _getScrollDelta(event);
if (_state == ScrollState.accepted) {
if (onUpdate != null)
onUpdate(delta);
} else {
pendingScrollOffset += offset;
if (pendingScrollOffset.distance > kTouchSlop)
_pendingScrollDelta += delta;
if (_hasSufficientPendingScrollDeltaToAccept)
resolve(GestureDisposition.accepted);
}
}
......@@ -59,22 +64,64 @@ class ScrollGestureRecognizer extends GestureRecognizer {
}
void acceptGesture(int pointer) {
if (state != ScrollState.accepted) {
state = ScrollState.accepted;
sky.Offset offset = pendingScrollOffset;
pendingScrollOffset = null;
if (onScrollStart != null)
onScrollStart();
if (offset != sky.Offset.zero && onScrollUpdate != null)
onScrollUpdate(offset);
if (_state != ScrollState.accepted) {
_state = ScrollState.accepted;
T delta = _pendingScrollDelta;
_pendingScrollDelta = null;
if (onStart != null)
onStart();
if (delta != _initialPendingScrollDelta && onUpdate != null)
onUpdate(delta);
}
}
void didStopTrackingLastPointer() {
bool wasAccepted = (state == ScrollState.accepted);
state = ScrollState.ready;
if (wasAccepted && onScrollEnd != null)
onScrollEnd();
bool wasAccepted = (_state == ScrollState.accepted);
_state = ScrollState.ready;
if (wasAccepted && onEnd != null)
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 {
this.onTap,
this.onShowPress,
this.onLongPress,
this.onScrollStart,
this.onScrollUpdate,
this.onScrollEnd
this.onVerticalScrollStart,
this.onVerticalScrollUpdate,
this.onVerticalScrollEnd,
this.onHorizontalScrollStart,
this.onHorizontalScrollUpdate,
this.onHorizontalScrollEnd,
this.onPanStart,
this.onPanUpdate,
this.onPanEnd
}) : super(key: key);
Widget child;
GestureTapListener onTap;
GestureShowPressListener onShowPress;
GestureLongPressListener onLongPress;
GestureScrollStartCallback onScrollStart;
GestureScrollUpdateCallback onScrollUpdate;
GestureScrollEndCallback onScrollEnd;
GestureScrollStartCallback onVerticalScrollStart;
GestureScrollUpdateCallback onVerticalScrollUpdate;
GestureScrollEndCallback onVerticalScrollEnd;
GestureScrollStartCallback onHorizontalScrollStart;
GestureScrollUpdateCallback onHorizontalScrollUpdate;
GestureScrollEndCallback onHorizontalScrollEnd;
GesturePanStartCallback onPanStart;
GesturePanUpdateCallback onPanUpdate;
GesturePanEndCallback onPanEnd;
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;
onVerticalScrollStart = onVerticalScrollStart;
onVerticalScrollUpdate = onVerticalScrollUpdate;
onVerticalScrollEnd = onVerticalScrollEnd;
onHorizontalScrollStart = onHorizontalScrollStart;
onHorizontalScrollUpdate = onHorizontalScrollUpdate;
onHorizontalScrollEnd = onHorizontalScrollEnd;
onPanStart = source.onPanStart;
onPanUpdate = source.onPanUpdate;
onPanEnd = source.onPanEnd;
_syncGestureListeners();
}
......@@ -66,11 +87,25 @@ class GestureDetector extends StatefulComponent {
return _longPress;
}
ScrollGestureRecognizer _scroll;
ScrollGestureRecognizer _ensureScroll() {
if (_scroll == null)
_scroll = new ScrollGestureRecognizer(router: _router);
return _scroll;
VerticalScrollGestureRecognizer _verticalScroll;
VerticalScrollGestureRecognizer _ensureVerticalScroll() {
if (_verticalScroll == null)
_verticalScroll = new VerticalScrollGestureRecognizer(router: _router);
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() {
......@@ -83,14 +118,18 @@ class GestureDetector extends StatefulComponent {
_tap = _ensureDisposed(_tap);
_showPress = _ensureDisposed(_showPress);
_longPress = _ensureDisposed(_longPress);
_scroll = _ensureDisposed(_scroll);
_verticalScroll = _ensureDisposed(_verticalScroll);
_horizontalScroll = _ensureDisposed(_horizontalScroll);
_pan = _ensureDisposed(_pan);
}
void _syncGestureListeners() {
_syncTap();
_syncShowPress();
_syncLongPress();
_syncScroll();
_syncVerticalScroll();
_syncHorizontalScroll();
_syncPan();
}
void _syncTap() {
......@@ -114,14 +153,36 @@ class GestureDetector extends StatefulComponent {
_ensureLongPress().onLongPress = onLongPress;
}
void _syncScroll() {
if (onScrollStart == null && onScrollUpdate == null && onScrollEnd == null) {
_scroll = _ensureDisposed(_scroll);
void _syncVerticalScroll() {
if (onVerticalScrollStart == null && onVerticalScrollUpdate == null && onVerticalScrollEnd == null) {
_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 {
_ensureScroll()
..onScrollStart = onScrollStart
..onScrollUpdate = onScrollUpdate
..onScrollEnd = onScrollEnd;
_ensurePan()
..onStart = onPanStart
..onUpdate = onPanUpdate
..onEnd = onPanEnd;
}
}
......@@ -138,8 +199,12 @@ class GestureDetector extends StatefulComponent {
_showPress.addPointer(event);
if (_longPress != null)
_longPress.addPointer(event);
if (_scroll != null)
_scroll.addPointer(event);
if (_verticalScroll != null)
_verticalScroll.addPointer(event);
if (_horizontalScroll != null)
_horizontalScroll.addPointer(event);
if (_pan != null)
_pan.addPointer(event);
return EventDisposition.processed;
}
......
......@@ -84,8 +84,10 @@ abstract class Scrollable extends StatefulComponent {
Widget build() {
return new GestureDetector(
onScrollUpdate: _handleScrollUpdate,
onScrollEnd: _maybeSettleScrollOffset,
onVerticalScrollUpdate: scrollDirection == ScrollDirection.vertical ? scrollBy : null,
onVerticalScrollEnd: scrollDirection == ScrollDirection.vertical ? _maybeSettleScrollOffset : null,
onHorizontalScrollUpdate: scrollDirection == ScrollDirection.horizontal ? scrollBy : null,
onHorizontalScrollEnd: scrollDirection == ScrollDirection.horizontal ? _maybeSettleScrollOffset : null,
child: new Listener(
child: buildContent(),
onPointerDown: _handlePointerDown,
......@@ -172,10 +174,6 @@ abstract class Scrollable extends StatefulComponent {
return EventDisposition.processed;
}
void _handleScrollUpdate(Offset offset) {
scrollBy(scrollDirection == ScrollDirection.horizontal ? offset.dx : offset.dy);
}
EventDisposition _handleFlingStart(sky.GestureEvent event) {
_startToEndAnimation(velocity: _eventVelocity(event));
return EventDisposition.processed;
......
......@@ -41,20 +41,20 @@ TestPointerEvent up = new TestPointerEvent(
void main() {
test('Should recognize scroll', () {
PointerRouter router = new PointerRouter();
ScrollGestureRecognizer scroll = new ScrollGestureRecognizer(router: router);
PanGestureRecognizer scroll = new PanGestureRecognizer(router: router);
bool didStartScroll = false;
scroll.onScrollStart = () {
scroll.onStart = () {
didStartScroll = true;
};
sky.Offset updateOffset;
scroll.onScrollUpdate = (sky.Offset offset) {
scroll.onUpdate = (sky.Offset offset) {
updateOffset = offset;
};
bool didEndScroll = false;
scroll.onScrollEnd = () {
scroll.onEnd = () {
didEndScroll = true;
};
......
......@@ -43,7 +43,7 @@ void main() {
expect(currentPage, isNull);
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.
tester.pumpFrame(builder);
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