Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 81 additions & 34 deletions sky/packages/sky/lib/gestures/scroll.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,66 +15,113 @@ 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);
}
}
stopTrackingIfPointerNoLongerDown(event);
}

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;
}
}
115 changes: 90 additions & 25 deletions sky/packages/sky/lib/widgets/gesture_detector.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}

Expand All @@ -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() {
Expand All @@ -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() {
Expand All @@ -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;
}
}

Expand All @@ -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;
}

Expand Down
10 changes: 4 additions & 6 deletions sky/packages/sky/lib/widgets/scrollable.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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;
Expand Down
8 changes: 4 additions & 4 deletions sky/unit/test/gestures/scroll_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};

Expand Down
2 changes: 1 addition & 1 deletion sky/unit/test/widget/pageable_list_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down