Merge pull request #1790 from Hixie/tap-drag

Refactor MultiDragPointerState to support delays
This commit is contained in:
Ian Hickson 2016-02-11 16:54:51 -08:00
commit 41b7fbf06b
2 changed files with 157 additions and 15 deletions

View File

@ -63,15 +63,14 @@ abstract class MultiDragPointerState {
void checkForResolutionAfterMove() { } void checkForResolutionAfterMove() { }
/// Called when the gesture was accepted. /// Called when the gesture was accepted.
void accepted(Drag client) { ///
assert(_arenaEntry != null); /// Either immediately or at some future point before the gesture is disposed,
assert(_client == null); /// call starter(), passing it initialPosition, to start the drag.
_client = client; void accepted(GestureMultiDragStartCallback starter);
_client.move(pendingDelta);
_pendingDelta = null;
}
/// Called when the gesture was rejected. /// Called when the gesture was rejected.
///
/// [dispose()] will be called immediately following this.
void rejected() { void rejected() {
assert(_arenaEntry != null); assert(_arenaEntry != null);
assert(_client == null); assert(_client == null);
@ -80,6 +79,16 @@ abstract class MultiDragPointerState {
_arenaEntry = null; _arenaEntry = null;
} }
void _startDrag(Drag client) {
assert(_arenaEntry != null);
assert(_client == null);
assert(client != null);
assert(pendingDelta != null);
_client = client;
_client.move(pendingDelta);
_pendingDelta = null;
}
void _up() { void _up() {
assert(_arenaEntry != null); assert(_arenaEntry != null);
if (_client != null) { if (_client != null) {
@ -106,7 +115,9 @@ abstract class MultiDragPointerState {
_arenaEntry = null; _arenaEntry = null;
} }
void dispose() { } void dispose() {
assert(() { _pendingDelta = null; return true; });
}
} }
abstract class MultiDragGestureRecognizer<T extends MultiDragPointerState> extends GestureRecognizer { abstract class MultiDragGestureRecognizer<T extends MultiDragPointerState> extends GestureRecognizer {
@ -168,14 +179,23 @@ abstract class MultiDragGestureRecognizer<T extends MultiDragPointerState> exten
assert(_pointers != null); assert(_pointers != null);
T state = _pointers[pointer]; T state = _pointers[pointer];
assert(state != null); assert(state != null);
state.accepted((Point initialPosition) => _startDrag(initialPosition, pointer));
}
Drag _startDrag(Point initialPosition, int pointer) {
assert(_pointers != null);
T state = _pointers[pointer];
assert(state != null);
assert(state._pendingDelta != null);
Drag drag; Drag drag;
if (onStart != null) if (onStart != null)
drag = onStart(state.initialPosition); drag = onStart(initialPosition);
if (drag != null) { if (drag != null) {
state.accepted(drag); state._startDrag(drag);
} else { } else {
_removeState(pointer); _removeState(pointer);
} }
return drag;
} }
void rejectGesture(int pointer) { void rejectGesture(int pointer) {
@ -214,6 +234,10 @@ class _ImmediatePointerState extends MultiDragPointerState {
if (pendingDelta.distance > kTouchSlop) if (pendingDelta.distance > kTouchSlop)
resolve(GestureDisposition.accepted); resolve(GestureDisposition.accepted);
} }
void accepted(GestureMultiDragStartCallback starter) {
starter(initialPosition);
}
} }
class ImmediateMultiDragGestureRecognizer extends MultiDragGestureRecognizer<_ImmediatePointerState> { class ImmediateMultiDragGestureRecognizer extends MultiDragGestureRecognizer<_ImmediatePointerState> {
@ -235,19 +259,28 @@ class _DelayedPointerState extends MultiDragPointerState {
} }
Timer _timer; Timer _timer;
GestureMultiDragStartCallback _starter;
void _delayPassed() { void _delayPassed() {
assert(_timer != null); assert(_timer != null);
assert(pendingDelta != null); assert(pendingDelta != null);
assert(pendingDelta.distance <= kTouchSlop); assert(pendingDelta.distance <= kTouchSlop);
resolve(GestureDisposition.accepted);
_timer = null; _timer = null;
if (_starter != null) {
_starter(initialPosition);
_starter = null;
} else {
resolve(GestureDisposition.accepted);
}
assert(_starter == null);
} }
void accepted(Drag client) { void accepted(GestureMultiDragStartCallback starter) {
_timer?.cancel(); assert(_starter == null);
_timer = null; if (_timer == null)
super.accepted(client); starter(initialPosition);
else
_starter = starter;
} }
void checkForResolutionAfterMove() { void checkForResolutionAfterMove() {

View File

@ -169,7 +169,9 @@ void main() {
expect(events, equals(<String>['tap', 'tap', 'drop'])); expect(events, equals(<String>['tap', 'tap', 'drop']));
events.clear(); events.clear();
}); });
});
test('Drag and drop - tapping button', () {
testWidgets((WidgetTester tester) { testWidgets((WidgetTester tester) {
TestPointer pointer = new TestPointer(7); TestPointer pointer = new TestPointer(7);
@ -230,6 +232,113 @@ void main() {
events.clear(); events.clear();
}); });
});
test('Drag and drop - long press draggable, short press', () {
testWidgets((WidgetTester tester) {
TestPointer pointer = new TestPointer(7);
List<String> events = <String>[];
Point firstLocation, secondLocation;
tester.pumpWidget(new MaterialApp(
routes: <String, RouteBuilder>{
'/': (RouteArguments args) { return new Column(
children: <Widget>[
new LongPressDraggable(
data: 1,
child: new Text('Source'),
feedback: new Text('Dragging')
),
new DragTarget(
builder: (context, data, rejects) {
return new Text('Target');
},
onAccept: (data) {
events.add('drop');
}
),
]);
},
}
));
expect(events, isEmpty);
expect(tester.findText('Source'), isNotNull);
expect(tester.findText('Target'), isNotNull);
expect(events, isEmpty);
tester.tap(tester.findText('Source'));
expect(events, isEmpty);
firstLocation = tester.getCenter(tester.findText('Source'));
tester.dispatchEvent(pointer.down(firstLocation), firstLocation);
tester.pump();
secondLocation = tester.getCenter(tester.findText('Target'));
tester.dispatchEvent(pointer.move(secondLocation), firstLocation);
tester.pump();
expect(events, isEmpty);
tester.dispatchEvent(pointer.up(), firstLocation);
tester.pump();
expect(events, isEmpty);
}); });
});
test('Drag and drop - long press draggable, long press', () {
testWidgets((WidgetTester tester) {
TestPointer pointer = new TestPointer(7);
List<String> events = <String>[];
Point firstLocation, secondLocation;
tester.pumpWidget(new MaterialApp(
routes: <String, RouteBuilder>{
'/': (RouteArguments args) { return new Column(
children: <Widget>[
new Draggable(
data: 1,
child: new Text('Source'),
feedback: new Text('Dragging')
),
new DragTarget(
builder: (context, data, rejects) {
return new Text('Target');
},
onAccept: (data) {
events.add('drop');
}
),
]);
},
}
));
expect(events, isEmpty);
expect(tester.findText('Source'), isNotNull);
expect(tester.findText('Target'), isNotNull);
expect(events, isEmpty);
tester.tap(tester.findText('Source'));
expect(events, isEmpty);
firstLocation = tester.getCenter(tester.findText('Source'));
tester.dispatchEvent(pointer.down(firstLocation), firstLocation);
tester.pump();
tester.pump(const Duration(seconds: 20));
secondLocation = tester.getCenter(tester.findText('Target'));
tester.dispatchEvent(pointer.move(secondLocation), firstLocation);
tester.pump();
expect(events, isEmpty);
tester.dispatchEvent(pointer.up(), firstLocation);
tester.pump();
expect(events, equals(<String>['drop']));
});
});
} }