Commit 51b1550d authored by Adam Barth's avatar Adam Barth

Delay win-by-default in gesture arena (#3552)

Wait until the end of the microtask to tell gesture recognizers that
they've won in the gesture arena. This lets recognizers dispose reject
themselves at arbitrary times without triggering gestures in awkward
call stacks.

Fixes #3183
parent c69f4396
......@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
/// Whether the gesture was accepted or rejected.
enum GestureDisposition {
/// This gesture was accepted as the interpretation of the user's input.
......@@ -130,12 +132,22 @@ class GestureArenaManager {
sweep(pointer);
}
void _resolveByDefault(int pointer, _GestureArena state) {
if (!_arenas.containsKey(pointer))
return; // Already resolved earlier.
assert(_arenas[pointer] == state);
assert(!state.isOpen);
final List<GestureArenaMember> members = state.members;
assert(members.length == 1);
_arenas.remove(pointer);
state.members.first.acceptGesture(pointer);
}
void _tryToResolveArena(int pointer, _GestureArena state) {
assert(_arenas[pointer] == state);
assert(!state.isOpen);
if (state.members.length == 1) {
_arenas.remove(pointer);
state.members.first.acceptGesture(pointer);
scheduleMicrotask(() => _resolveByDefault(pointer, state));
} else if (state.members.isEmpty) {
_arenas.remove(pointer);
} else if (state.eagerWinner != null) {
......
......@@ -8,10 +8,10 @@ import 'constants.dart';
import 'events.dart';
import 'velocity_tracker.dart';
enum DragState {
enum _DragState {
ready,
possible,
accepted
accepted,
}
typedef void GestureDragDownCallback(Point globalPosition);
......@@ -41,7 +41,7 @@ abstract class _DragGestureRecognizer<T extends dynamic> extends OneSequenceGest
GestureDragEndCallback onEnd;
GestureDragCancelCallback onCancel;
DragState _state = DragState.ready;
_DragState _state = _DragState.ready;
Point _initialPosition;
T _pendingDragDelta;
......@@ -55,8 +55,8 @@ abstract class _DragGestureRecognizer<T extends dynamic> extends OneSequenceGest
void addPointer(PointerEvent event) {
startTrackingPointer(event.pointer);
_velocityTrackers[event.pointer] = new VelocityTracker();
if (_state == DragState.ready) {
_state = DragState.possible;
if (_state == _DragState.ready) {
_state = _DragState.possible;
_initialPosition = event.position;
_pendingDragDelta = _initialPendingDragDelta;
if (onDown != null)
......@@ -66,13 +66,13 @@ abstract class _DragGestureRecognizer<T extends dynamic> extends OneSequenceGest
@override
void handleEvent(PointerEvent event) {
assert(_state != DragState.ready);
assert(_state != _DragState.ready);
if (event is PointerMoveEvent) {
VelocityTracker tracker = _velocityTrackers[event.pointer];
assert(tracker != null);
tracker.addPosition(event.timeStamp, event.position);
T delta = _getDragDelta(event);
if (_state == DragState.accepted) {
if (_state == _DragState.accepted) {
if (onUpdate != null)
onUpdate(delta);
} else {
......@@ -86,8 +86,8 @@ abstract class _DragGestureRecognizer<T extends dynamic> extends OneSequenceGest
@override
void acceptGesture(int pointer) {
if (_state != DragState.accepted) {
_state = DragState.accepted;
if (_state != _DragState.accepted) {
_state = _DragState.accepted;
T delta = _pendingDragDelta;
_pendingDragDelta = _initialPendingDragDelta;
if (onStart != null)
......@@ -104,15 +104,15 @@ abstract class _DragGestureRecognizer<T extends dynamic> extends OneSequenceGest
@override
void didStopTrackingLastPointer(int pointer) {
if (_state == DragState.possible) {
if (_state == _DragState.possible) {
resolve(GestureDisposition.rejected);
_state = DragState.ready;
_state = _DragState.ready;
if (onCancel != null)
onCancel();
return;
}
bool wasAccepted = (_state == DragState.accepted);
_state = DragState.ready;
bool wasAccepted = (_state == _DragState.accepted);
_state = _DragState.ready;
if (wasAccepted && onEnd != null) {
VelocityTracker tracker = _velocityTrackers[pointer];
assert(tracker != null);
......
......@@ -168,7 +168,8 @@ abstract class MultiDragGestureRecognizer<T extends MultiDragPointerState> exten
void acceptGesture(int pointer) {
assert(_pointers != null);
T state = _pointers[pointer];
assert(state != null);
if (state == null)
return; // We might already have canceled this drag if the up comes before the accept.
state.accepted((Point initialPosition) => _startDrag(initialPosition, pointer));
}
......
......@@ -57,7 +57,7 @@ abstract class GestureRecognizer extends GestureArenaMember {
/// which manages each pointer independently and can consider multiple
/// simultaneous touches to each result in a separate tap.
abstract class OneSequenceGestureRecognizer extends GestureRecognizer {
final List<GestureArenaEntry> _entries = <GestureArenaEntry>[];
final Map<int, GestureArenaEntry> _entries = <int, GestureArenaEntry>{};
final Set<int> _trackedPointers = new HashSet<int>();
void handleEvent(PointerEvent event);
......@@ -71,7 +71,7 @@ abstract class OneSequenceGestureRecognizer extends GestureRecognizer {
void didStopTrackingLastPointer(int pointer);
void resolve(GestureDisposition disposition) {
List<GestureArenaEntry> localEntries = new List<GestureArenaEntry>.from(_entries);
List<GestureArenaEntry> localEntries = new List<GestureArenaEntry>.from(_entries.values);
_entries.clear();
for (GestureArenaEntry entry in localEntries)
entry.resolve(disposition);
......@@ -89,7 +89,8 @@ abstract class OneSequenceGestureRecognizer extends GestureRecognizer {
void startTrackingPointer(int pointer) {
GestureBinding.instance.pointerRouter.addRoute(pointer, handleEvent);
_trackedPointers.add(pointer);
_entries.add(GestureBinding.instance.gestureArena.add(pointer, this));
assert(!_entries.containsValue(pointer));
_entries[pointer] = GestureBinding.instance.gestureArena.add(pointer, this);
}
void stopTrackingPointer(int pointer) {
......
......@@ -4,6 +4,8 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:test/test.dart';
import 'package:quiver/testing/async.dart';
class TestGestureFlutterBinding extends BindingBase with GestureBinding { }
......@@ -12,3 +14,28 @@ void ensureGestureBinding() {
new TestGestureFlutterBinding();
assert(GestureBinding.instance != null);
}
class GestureTester {
GestureTester._(this.async);
final FakeAsync async;
void closeArena(int pointer) {
GestureBinding.instance.gestureArena.close(pointer);
}
void route(PointerEvent event) {
GestureBinding.instance.pointerRouter.route(event);
async.flushMicrotasks();
}
}
typedef void GestureTest(GestureTester tester);
void testGesture(String description, GestureTest callback) {
test(description, () {
new FakeAsync().run((FakeAsync async) {
callback(new GestureTester._(async));
});
});
}
......@@ -2,7 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:quiver/testing/async.dart';
import 'package:flutter/gestures.dart';
import 'package:test/test.dart';
......@@ -21,7 +20,7 @@ const PointerUpEvent up = const PointerUpEvent(
void main() {
setUp(ensureGestureBinding);
test('Should recognize long press', () {
testGesture('Should recognize long press', (GestureTester tester) {
LongPressGestureRecognizer longPress = new LongPressGestureRecognizer();
bool longPressRecognized = false;
......@@ -29,22 +28,20 @@ void main() {
longPressRecognized = true;
};
new FakeAsync().run((FakeAsync async) {
longPress.addPointer(down);
GestureBinding.instance.gestureArena.close(5);
expect(longPressRecognized, isFalse);
GestureBinding.instance.pointerRouter.route(down);
expect(longPressRecognized, isFalse);
async.elapse(const Duration(milliseconds: 300));
expect(longPressRecognized, isFalse);
async.elapse(const Duration(milliseconds: 700));
expect(longPressRecognized, isTrue);
});
longPress.addPointer(down);
tester.closeArena(5);
expect(longPressRecognized, isFalse);
tester.route(down);
expect(longPressRecognized, isFalse);
tester.async.elapse(const Duration(milliseconds: 300));
expect(longPressRecognized, isFalse);
tester.async.elapse(const Duration(milliseconds: 700));
expect(longPressRecognized, isTrue);
longPress.dispose();
});
test('Up cancels long press', () {
testGesture('Up cancels long press', (GestureTester tester) {
LongPressGestureRecognizer longPress = new LongPressGestureRecognizer();
bool longPressRecognized = false;
......@@ -52,24 +49,22 @@ void main() {
longPressRecognized = true;
};
new FakeAsync().run((FakeAsync async) {
longPress.addPointer(down);
GestureBinding.instance.gestureArena.close(5);
expect(longPressRecognized, isFalse);
GestureBinding.instance.pointerRouter.route(down);
expect(longPressRecognized, isFalse);
async.elapse(const Duration(milliseconds: 300));
expect(longPressRecognized, isFalse);
GestureBinding.instance.pointerRouter.route(up);
expect(longPressRecognized, isFalse);
async.elapse(const Duration(seconds: 1));
expect(longPressRecognized, isFalse);
});
longPress.addPointer(down);
tester.closeArena(5);
expect(longPressRecognized, isFalse);
tester.route(down);
expect(longPressRecognized, isFalse);
tester.async.elapse(const Duration(milliseconds: 300));
expect(longPressRecognized, isFalse);
tester.route(up);
expect(longPressRecognized, isFalse);
tester.async.elapse(const Duration(seconds: 1));
expect(longPressRecognized, isFalse);
longPress.dispose();
});
test('Should recognize both tap down and long press', () {
testGesture('Should recognize both tap down and long press', (GestureTester tester) {
LongPressGestureRecognizer longPress = new LongPressGestureRecognizer();
TapGestureRecognizer tap = new TapGestureRecognizer();
......@@ -83,24 +78,63 @@ void main() {
longPressRecognized = true;
};
new FakeAsync().run((FakeAsync async) {
tap.addPointer(down);
longPress.addPointer(down);
GestureBinding.instance.gestureArena.close(5);
expect(tapDownRecognized, isFalse);
expect(longPressRecognized, isFalse);
GestureBinding.instance.pointerRouter.route(down);
expect(tapDownRecognized, isFalse);
expect(longPressRecognized, isFalse);
async.elapse(const Duration(milliseconds: 300));
expect(tapDownRecognized, isTrue);
expect(longPressRecognized, isFalse);
async.elapse(const Duration(milliseconds: 700));
expect(tapDownRecognized, isTrue);
expect(longPressRecognized, isTrue);
});
tap.addPointer(down);
longPress.addPointer(down);
tester.closeArena(5);
expect(tapDownRecognized, isFalse);
expect(longPressRecognized, isFalse);
tester.route(down);
expect(tapDownRecognized, isFalse);
expect(longPressRecognized, isFalse);
tester.async.elapse(const Duration(milliseconds: 300));
expect(tapDownRecognized, isTrue);
expect(longPressRecognized, isFalse);
tester.async.elapse(const Duration(milliseconds: 700));
expect(tapDownRecognized, isTrue);
expect(longPressRecognized, isTrue);
tap.dispose();
longPress.dispose();
});
testGesture('Drag start delayed by microtask', (GestureTester tester) {
LongPressGestureRecognizer longPress = new LongPressGestureRecognizer();
HorizontalDragGestureRecognizer drag = new HorizontalDragGestureRecognizer();
bool isDangerousStack = false;
bool dragStartRecognized = false;
drag.onStart = (Point globalPosition) {
expect(isDangerousStack, isFalse);
dragStartRecognized = true;
};
bool longPressRecognized = false;
longPress.onLongPress = () {
expect(isDangerousStack, isFalse);
longPressRecognized = true;
};
drag.addPointer(down);
longPress.addPointer(down);
tester.closeArena(5);
expect(dragStartRecognized, isFalse);
expect(longPressRecognized, isFalse);
tester.route(down);
expect(dragStartRecognized, isFalse);
expect(longPressRecognized, isFalse);
tester.async.elapse(const Duration(milliseconds: 300));
expect(dragStartRecognized, isFalse);
expect(longPressRecognized, isFalse);
isDangerousStack = true;
longPress.dispose();
isDangerousStack = false;
expect(dragStartRecognized, isFalse);
expect(longPressRecognized, isFalse);
tester.async.flushMicrotasks();
expect(dragStartRecognized, isTrue);
expect(longPressRecognized, isFalse);
drag.dispose();
});
}
......@@ -11,9 +11,7 @@ import 'gesture_tester.dart';
void main() {
setUp(ensureGestureBinding);
test('Should recognize scale gestures', () {
GestureArenaManager gestureArena = GestureBinding.instance.gestureArena;
PointerRouter pointerRouter = GestureBinding.instance.pointerRouter;
testGesture('Should recognize scale gestures', (GestureTester tester) {
ScaleGestureRecognizer scale = new ScaleGestureRecognizer();
TapGestureRecognizer tap = new TapGestureRecognizer();
......@@ -46,7 +44,7 @@ void main() {
scale.addPointer(down);
tap.addPointer(down);
gestureArena.close(1);
tester.closeArena(1);
expect(didStartScale, isFalse);
expect(updatedScale, isNull);
expect(updatedFocalPoint, isNull);
......@@ -54,14 +52,14 @@ void main() {
expect(didTap, isFalse);
// One-finger panning
pointerRouter.route(down);
tester.route(down);
expect(didStartScale, isFalse);
expect(updatedScale, isNull);
expect(updatedFocalPoint, isNull);
expect(didEndScale, isFalse);
expect(didTap, isFalse);
pointerRouter.route(pointer1.move(const Point(20.0, 30.0)));
tester.route(pointer1.move(const Point(20.0, 30.0)));
expect(didStartScale, isTrue);
didStartScale = false;
expect(updatedFocalPoint, const Point(20.0, 30.0));
......@@ -76,8 +74,8 @@ void main() {
PointerDownEvent down2 = pointer2.down(const Point(10.0, 20.0));
scale.addPointer(down2);
tap.addPointer(down2);
gestureArena.close(2);
pointerRouter.route(down2);
tester.closeArena(2);
tester.route(down2);
expect(didEndScale, isTrue);
didEndScale = false;
......@@ -86,7 +84,7 @@ void main() {
expect(didStartScale, isFalse);
// Zoom in
pointerRouter.route(pointer2.move(const Point(0.0, 10.0)));
tester.route(pointer2.move(const Point(0.0, 10.0)));
expect(didStartScale, isTrue);
didStartScale = false;
expect(updatedFocalPoint, const Point(10.0, 20.0));
......@@ -97,7 +95,7 @@ void main() {
expect(didTap, isFalse);
// Zoom out
pointerRouter.route(pointer2.move(const Point(15.0, 25.0)));
tester.route(pointer2.move(const Point(15.0, 25.0)));
expect(updatedFocalPoint, const Point(17.5, 27.5));
updatedFocalPoint = null;
expect(updatedScale, 0.5);
......@@ -109,8 +107,8 @@ void main() {
PointerDownEvent down3 = pointer3.down(const Point(25.0, 35.0));
scale.addPointer(down3);
tap.addPointer(down3);
gestureArena.close(3);
pointerRouter.route(down3);
tester.closeArena(3);
tester.route(down3);
expect(didEndScale, isTrue);
didEndScale = false;
......@@ -119,7 +117,7 @@ void main() {
expect(didStartScale, isFalse);
// Zoom in
pointerRouter.route(pointer3.move(const Point(55.0, 65.0)));
tester.route(pointer3.move(const Point(55.0, 65.0)));
expect(didStartScale, isTrue);
didStartScale = false;
expect(updatedFocalPoint, const Point(30.0, 40.0));
......@@ -130,9 +128,9 @@ void main() {
expect(didTap, isFalse);
// Return to original positions but with different fingers
pointerRouter.route(pointer1.move(const Point(25.0, 35.0)));
pointerRouter.route(pointer2.move(const Point(20.0, 30.0)));
pointerRouter.route(pointer3.move(const Point(15.0, 25.0)));
tester.route(pointer1.move(const Point(25.0, 35.0)));
tester.route(pointer2.move(const Point(20.0, 30.0)));
tester.route(pointer3.move(const Point(15.0, 25.0)));
expect(didStartScale, isFalse);
expect(updatedFocalPoint, const Point(20.0, 30.0));
updatedFocalPoint = null;
......@@ -141,7 +139,7 @@ void main() {
expect(didEndScale, isFalse);
expect(didTap, isFalse);
pointerRouter.route(pointer1.up());
tester.route(pointer1.up());
expect(didStartScale, isFalse);
expect(updatedFocalPoint, isNull);
expect(updatedScale, isNull);
......@@ -150,7 +148,7 @@ void main() {
expect(didTap, isFalse);
// Continue scaling with two fingers
pointerRouter.route(pointer3.move(const Point(10.0, 20.0)));
tester.route(pointer3.move(const Point(10.0, 20.0)));
expect(didStartScale, isTrue);
didStartScale = false;
expect(updatedFocalPoint, const Point(15.0, 25.0));
......@@ -158,7 +156,7 @@ void main() {
expect(updatedScale, 2.0);
updatedScale = null;
pointerRouter.route(pointer2.up());
tester.route(pointer2.up());
expect(didStartScale, isFalse);
expect(updatedFocalPoint, isNull);
expect(updatedScale, isNull);
......@@ -167,7 +165,7 @@ void main() {
expect(didTap, isFalse);
// Continue panning with one finger
pointerRouter.route(pointer3.move(const Point(0.0, 0.0)));
tester.route(pointer3.move(const Point(0.0, 0.0)));
expect(didStartScale, isTrue);
didStartScale = false;
expect(updatedFocalPoint, const Point(0.0, 0.0));
......@@ -176,7 +174,7 @@ void main() {
updatedScale = null;
// We are done
pointerRouter.route(pointer3.up());
tester.route(pointer3.up());
expect(didStartScale, isFalse);
expect(updatedFocalPoint, isNull);
expect(updatedScale, isNull);
......
......@@ -11,9 +11,7 @@ import 'gesture_tester.dart';
void main() {
setUp(ensureGestureBinding);
test('Should recognize pan', () {
GestureArenaManager gestureArena = GestureBinding.instance.gestureArena;
PointerRouter pointerRouter = GestureBinding.instance.pointerRouter;
testGesture('Should recognize pan', (GestureTester tester) {
PanGestureRecognizer pan = new PanGestureRecognizer();
TapGestureRecognizer tap = new TapGestureRecognizer();
......@@ -41,19 +39,19 @@ void main() {
PointerDownEvent down = pointer.down(const Point(10.0, 10.0));
pan.addPointer(down);
tap.addPointer(down);
gestureArena.close(5);
tester.closeArena(5);
expect(didStartPan, isFalse);
expect(updatedScrollDelta, isNull);
expect(didEndPan, isFalse);
expect(didTap, isFalse);
pointerRouter.route(down);
tester.route(down);
expect(didStartPan, isFalse);
expect(updatedScrollDelta, isNull);
expect(didEndPan, isFalse);
expect(didTap, isFalse);
pointerRouter.route(pointer.move(const Point(20.0, 20.0)));
tester.route(pointer.move(const Point(20.0, 20.0)));
expect(didStartPan, isTrue);
didStartPan = false;
expect(updatedScrollDelta, const Offset(10.0, 10.0));
......@@ -61,14 +59,14 @@ void main() {
expect(didEndPan, isFalse);
expect(didTap, isFalse);
pointerRouter.route(pointer.move(const Point(20.0, 25.0)));
tester.route(pointer.move(const Point(20.0, 25.0)));
expect(didStartPan, isFalse);
expect(updatedScrollDelta, const Offset(0.0, 5.0));
updatedScrollDelta = null;
expect(didEndPan, isFalse);
expect(didTap, isFalse);
pointerRouter.route(pointer.up());
tester.route(pointer.up());
expect(didStartPan, isFalse);
expect(updatedScrollDelta, isNull);
expect(didEndPan, isTrue);
......@@ -78,4 +76,57 @@ void main() {
pan.dispose();
tap.dispose();
});
testGesture('Should recognize drag', (GestureTester tester) {
HorizontalDragGestureRecognizer drag = new HorizontalDragGestureRecognizer();
bool didStartDrag = false;
drag.onStart = (_) {
didStartDrag = true;
};
double updatedDelta;
drag.onUpdate = (double delta) {
updatedDelta = delta;
};
bool didEndDrag = false;
drag.onEnd = (Velocity velocity) {
didEndDrag = true;
};
TestPointer pointer = new TestPointer(5);
PointerDownEvent down = pointer.down(const Point(10.0, 10.0));
drag.addPointer(down);
tester.closeArena(5);
expect(didStartDrag, isFalse);
expect(updatedDelta, isNull);
expect(didEndDrag, isFalse);
tester.route(down);
expect(didStartDrag, isTrue);
expect(updatedDelta, isNull);
expect(didEndDrag, isFalse);
tester.route(pointer.move(const Point(20.0, 25.0)));
expect(didStartDrag, isTrue);
didStartDrag = false;
expect(updatedDelta, 10.0);
updatedDelta = null;
expect(didEndDrag, isFalse);
tester.route(pointer.move(const Point(20.0, 25.0)));
expect(didStartDrag, isFalse);
expect(updatedDelta, 0.0);
updatedDelta = null;
expect(didEndDrag, isFalse);
tester.route(pointer.up());
expect(didStartDrag, isFalse);
expect(updatedDelta, isNull);
expect(didEndDrag, isTrue);
didEndDrag = false;
drag.dispose();
});
}
......@@ -3,7 +3,6 @@
// found in the LICENSE file.
import 'package:flutter/gestures.dart';
import 'package:quiver/testing/async.dart';
import 'package:test/test.dart';
import 'gesture_tester.dart';
......@@ -57,7 +56,7 @@ void main() {
position: const Point(25.0, 25.0)
);
test('Should recognize tap', () {
testGesture('Should recognize tap', (GestureTester tester) {
TapGestureRecognizer tap = new TapGestureRecognizer();
bool tapRecognized = false;
......@@ -66,12 +65,12 @@ void main() {
};
tap.addPointer(down1);
GestureBinding.instance.gestureArena.close(1);
tester.closeArena(1);
expect(tapRecognized, isFalse);
GestureBinding.instance.pointerRouter.route(down1);
tester.route(down1);
expect(tapRecognized, isFalse);
GestureBinding.instance.pointerRouter.route(up1);
tester.route(up1);
expect(tapRecognized, isTrue);
GestureBinding.instance.gestureArena.sweep(1);
expect(tapRecognized, isTrue);
......@@ -79,7 +78,7 @@ void main() {
tap.dispose();
});
test('No duplicate tap events', () {
testGesture('No duplicate tap events', (GestureTester tester) {
TapGestureRecognizer tap = new TapGestureRecognizer();
int tapsRecognized = 0;
......@@ -88,23 +87,23 @@ void main() {
};
tap.addPointer(down1);
GestureBinding.instance.gestureArena.close(1);
tester.closeArena(1);
expect(tapsRecognized, 0);
GestureBinding.instance.pointerRouter.route(down1);
tester.route(down1);
expect(tapsRecognized, 0);
GestureBinding.instance.pointerRouter.route(up1);
tester.route(up1);
expect(tapsRecognized, 1);
GestureBinding.instance.gestureArena.sweep(1);
expect(tapsRecognized, 1);
tap.addPointer(down1);
GestureBinding.instance.gestureArena.close(1);
tester.closeArena(1);
expect(tapsRecognized, 1);
GestureBinding.instance.pointerRouter.route(down1);
tester.route(down1);
expect(tapsRecognized, 1);
GestureBinding.instance.pointerRouter.route(up1);
tester.route(up1);
expect(tapsRecognized, 2);
GestureBinding.instance.gestureArena.sweep(1);
expect(tapsRecognized, 2);
......@@ -112,7 +111,7 @@ void main() {
tap.dispose();
});
test('Should not recognize two overlapping taps', () {
testGesture('Should not recognize two overlapping taps', (GestureTester tester) {
TapGestureRecognizer tap = new TapGestureRecognizer();
int tapsRecognized = 0;
......@@ -121,24 +120,24 @@ void main() {
};
tap.addPointer(down1);
GestureBinding.instance.gestureArena.close(1);
tester.closeArena(1);
expect(tapsRecognized, 0);
GestureBinding.instance.pointerRouter.route(down1);
tester.route(down1);
expect(tapsRecognized, 0);
tap.addPointer(down2);
GestureBinding.instance.gestureArena.close(2);
tester.closeArena(2);
expect(tapsRecognized, 0);
GestureBinding.instance.pointerRouter.route(down1);
tester.route(down1);
expect(tapsRecognized, 0);
GestureBinding.instance.pointerRouter.route(up1);
tester.route(up1);
expect(tapsRecognized, 1);
GestureBinding.instance.gestureArena.sweep(1);
expect(tapsRecognized, 1);
GestureBinding.instance.pointerRouter.route(up2);
tester.route(up2);
expect(tapsRecognized, 1);
GestureBinding.instance.gestureArena.sweep(2);
expect(tapsRecognized, 1);
......@@ -146,7 +145,7 @@ void main() {
tap.dispose();
});
test('Distance cancels tap', () {
testGesture('Distance cancels tap', (GestureTester tester) {
TapGestureRecognizer tap = new TapGestureRecognizer();
bool tapRecognized = false;
......@@ -159,17 +158,17 @@ void main() {
};
tap.addPointer(down3);
GestureBinding.instance.gestureArena.close(3);
tester.closeArena(3);
expect(tapRecognized, isFalse);
expect(tapCanceled, isFalse);
GestureBinding.instance.pointerRouter.route(down3);
tester.route(down3);
expect(tapRecognized, isFalse);
expect(tapCanceled, isFalse);
GestureBinding.instance.pointerRouter.route(move3);
tester.route(move3);
expect(tapRecognized, isFalse);
expect(tapCanceled, isTrue);
GestureBinding.instance.pointerRouter.route(up3);
tester.route(up3);
expect(tapRecognized, isFalse);
expect(tapCanceled, isTrue);
GestureBinding.instance.gestureArena.sweep(3);
......@@ -179,7 +178,7 @@ void main() {
tap.dispose();
});
test('Timeout does not cancel tap', () {
testGesture('Timeout does not cancel tap', (GestureTester tester) {
TapGestureRecognizer tap = new TapGestureRecognizer();
bool tapRecognized = false;
......@@ -187,25 +186,23 @@ void main() {
tapRecognized = true;
};
new FakeAsync().run((FakeAsync async) {
tap.addPointer(down1);
GestureBinding.instance.gestureArena.close(1);
expect(tapRecognized, isFalse);
GestureBinding.instance.pointerRouter.route(down1);
expect(tapRecognized, isFalse);
async.elapse(new Duration(milliseconds: 500));
expect(tapRecognized, isFalse);
GestureBinding.instance.pointerRouter.route(up1);
expect(tapRecognized, isTrue);
GestureBinding.instance.gestureArena.sweep(1);
expect(tapRecognized, isTrue);
});
tap.addPointer(down1);
tester.closeArena(1);
expect(tapRecognized, isFalse);
tester.route(down1);
expect(tapRecognized, isFalse);
tester.async.elapse(new Duration(milliseconds: 500));
expect(tapRecognized, isFalse);
tester.route(up1);
expect(tapRecognized, isTrue);
GestureBinding.instance.gestureArena.sweep(1);
expect(tapRecognized, isTrue);
tap.dispose();
});
test('Should yield to other arena members', () {
testGesture('Should yield to other arena members', (GestureTester tester) {
TapGestureRecognizer tap = new TapGestureRecognizer();
bool tapRecognized = false;
......@@ -217,12 +214,12 @@ void main() {
TestGestureArenaMember member = new TestGestureArenaMember();
GestureArenaEntry entry = GestureBinding.instance.gestureArena.add(1, member);
GestureBinding.instance.gestureArena.hold(1);
GestureBinding.instance.gestureArena.close(1);
tester.closeArena(1);
expect(tapRecognized, isFalse);
GestureBinding.instance.pointerRouter.route(down1);
tester.route(down1);
expect(tapRecognized, isFalse);
GestureBinding.instance.pointerRouter.route(up1);
tester.route(up1);
expect(tapRecognized, isFalse);
GestureBinding.instance.gestureArena.sweep(1);
expect(tapRecognized, isFalse);
......@@ -233,7 +230,7 @@ void main() {
tap.dispose();
});
test('Should trigger on release of held arena', () {
testGesture('Should trigger on release of held arena', (GestureTester tester) {
TapGestureRecognizer tap = new TapGestureRecognizer();
bool tapRecognized = false;
......@@ -245,17 +242,18 @@ void main() {
TestGestureArenaMember member = new TestGestureArenaMember();
GestureArenaEntry entry = GestureBinding.instance.gestureArena.add(1, member);
GestureBinding.instance.gestureArena.hold(1);
GestureBinding.instance.gestureArena.close(1);
tester.closeArena(1);
expect(tapRecognized, isFalse);
GestureBinding.instance.pointerRouter.route(down1);
tester.route(down1);
expect(tapRecognized, isFalse);
GestureBinding.instance.pointerRouter.route(up1);
tester.route(up1);
expect(tapRecognized, isFalse);
GestureBinding.instance.gestureArena.sweep(1);
expect(tapRecognized, isFalse);
entry.resolve(GestureDisposition.rejected);
tester.async.flushMicrotasks();
expect(tapRecognized, isTrue);
tap.dispose();
......
......@@ -164,10 +164,8 @@ class Instrumentation {
/// Dispatch a pointer down / pointer up sequence at the given
/// location.
void tapAt(Point location, { int pointer: 1 }) {
HitTestResult result = _hitTest(location);
TestPointer p = new TestPointer(pointer);
binding.dispatchEvent(p.down(location), result);
binding.dispatchEvent(p.up(), result);
startGesture(location, pointer: pointer)
..up();
}
/// Attempts a fling gesture starting from the center of the given
......@@ -210,14 +208,9 @@ class Instrumentation {
/// Attempts a drag gesture consisting of a pointer down, a move by
/// the given offset, and a pointer up.
void scrollAt(Point startLocation, Offset offset, { int pointer: 1 }) {
Point endLocation = startLocation + offset;
TestPointer p = new TestPointer(pointer);
// Events for the entire press-drag-release gesture are dispatched
// to the widgets "hit" by the pointer down event.
HitTestResult result = _hitTest(startLocation);
binding.dispatchEvent(p.down(startLocation), result);
binding.dispatchEvent(p.move(endLocation), result);
binding.dispatchEvent(p.up(), result);
startGesture(startLocation, pointer: pointer)
..moveBy(offset)
..up();
}
/// Begins a gesture at a particular point, and returns the
......
......@@ -3,6 +3,7 @@
// found in the LICENSE file.
import 'package:flutter/gestures.dart';
import 'package:quiver/testing/async.dart';
export 'dart:ui' show Point;
......@@ -139,11 +140,12 @@ class TestGesture {
return new TestGesture._(dispatcher, result, testPointer);
}
const TestGesture._(this._dispatcher, this._result, this._pointer);
TestGesture._(this._dispatcher, this._result, this._pointer);
final HitTestDispatcher _dispatcher;
final HitTestResult _result;
final TestPointer _pointer;
FakeAsync async;
/// Send a move event moving the pointer by the given offset.
void moveBy(Offset offset) {
......@@ -155,6 +157,7 @@ class TestGesture {
void moveTo(Point location) {
assert(_pointer._isDown);
_dispatcher.dispatchEvent(_pointer.move(location), _result);
async?.flushMicrotasks();
}
/// End the gesture by releasing the pointer.
......@@ -164,6 +167,7 @@ class TestGesture {
assert(_pointer._isDown);
_dispatcher.dispatchEvent(_pointer.up(), _result);
assert(!_pointer._isDown);
async?.flushMicrotasks();
}
/// End the gesture by canceling the pointer (as would happen if the
......@@ -175,5 +179,6 @@ class TestGesture {
assert(_pointer._isDown);
_dispatcher.dispatchEvent(_pointer.cancel(), _result);
assert(!_pointer._isDown);
async?.flushMicrotasks();
}
}
......@@ -143,7 +143,8 @@ class WidgetTester {
///
/// See [ElementTreeTester.tapAt] for details.
void tapAt(Point location, { int pointer: 1 }) {
elementTreeTester.tapAt(location, pointer: pointer);
startGesture(location, pointer: pointer)
..up();
}
/// Scrolls by dragging the center of a widget found by [finder] by [offset].
......@@ -155,7 +156,9 @@ class WidgetTester {
///
/// See [ElementTreeTester.scrollAt] for details.
void scrollAt(Point startLocation, Offset offset, { int pointer: 1 }) {
elementTreeTester.scrollAt(startLocation, offset, pointer: pointer);
startGesture(startLocation, pointer: pointer)
..moveBy(offset)
..up();
}
/// Attempts a fling gesture starting at the center of a widget found by
......@@ -172,12 +175,15 @@ class WidgetTester {
/// See [ElementTreeTester.flingFrom] for details.
void flingFrom(Point startLocation, Offset offset, double velocity, { int pointer: 1 }) {
elementTreeTester.flingFrom(startLocation, offset, velocity, pointer: pointer);
flushMicrotasks();
}
/// Begins a gesture at a particular point, and returns the
/// [TestGesture] object which you can use to continue the gesture.
TestGesture startGesture(Point downLocation, { int pointer: 1 }) {
return elementTreeTester.startGesture(downLocation, pointer: pointer);
TestGesture gesture = elementTreeTester.startGesture(downLocation, pointer: pointer)..async = async;
flushMicrotasks();
return gesture;
}
/// Returns the size of the element corresponding to the widget located by
......
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