From 6b487e4eccbd4403a38bab5b3c39b0fae22b5fab Mon Sep 17 00:00:00 2001 From: Hixie Date: Thu, 11 Feb 2016 16:47:27 -0800 Subject: [PATCH] Refactor MultiDragPointerState to support delays ...even after winning the arena. --- .../flutter/lib/src/gestures/multidrag.dart | 63 +++++++--- .../flutter/test/widget/draggable_test.dart | 109 ++++++++++++++++++ 2 files changed, 157 insertions(+), 15 deletions(-) diff --git a/packages/flutter/lib/src/gestures/multidrag.dart b/packages/flutter/lib/src/gestures/multidrag.dart index 2c11fbfc37..eeb929707c 100644 --- a/packages/flutter/lib/src/gestures/multidrag.dart +++ b/packages/flutter/lib/src/gestures/multidrag.dart @@ -63,15 +63,14 @@ abstract class MultiDragPointerState { void checkForResolutionAfterMove() { } /// Called when the gesture was accepted. - void accepted(Drag client) { - assert(_arenaEntry != null); - assert(_client == null); - _client = client; - _client.move(pendingDelta); - _pendingDelta = null; - } + /// + /// Either immediately or at some future point before the gesture is disposed, + /// call starter(), passing it initialPosition, to start the drag. + void accepted(GestureMultiDragStartCallback starter); /// Called when the gesture was rejected. + /// + /// [dispose()] will be called immediately following this. void rejected() { assert(_arenaEntry != null); assert(_client == null); @@ -80,6 +79,16 @@ abstract class MultiDragPointerState { _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() { assert(_arenaEntry != null); if (_client != null) { @@ -106,7 +115,9 @@ abstract class MultiDragPointerState { _arenaEntry = null; } - void dispose() { } + void dispose() { + assert(() { _pendingDelta = null; return true; }); + } } abstract class MultiDragGestureRecognizer extends GestureRecognizer { @@ -168,14 +179,23 @@ abstract class MultiDragGestureRecognizer exten assert(_pointers != null); T state = _pointers[pointer]; 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; if (onStart != null) - drag = onStart(state.initialPosition); + drag = onStart(initialPosition); if (drag != null) { - state.accepted(drag); + state._startDrag(drag); } else { _removeState(pointer); } + return drag; } void rejectGesture(int pointer) { @@ -214,6 +234,10 @@ class _ImmediatePointerState extends MultiDragPointerState { if (pendingDelta.distance > kTouchSlop) resolve(GestureDisposition.accepted); } + + void accepted(GestureMultiDragStartCallback starter) { + starter(initialPosition); + } } class ImmediateMultiDragGestureRecognizer extends MultiDragGestureRecognizer<_ImmediatePointerState> { @@ -235,19 +259,28 @@ class _DelayedPointerState extends MultiDragPointerState { } Timer _timer; + GestureMultiDragStartCallback _starter; void _delayPassed() { assert(_timer != null); assert(pendingDelta != null); assert(pendingDelta.distance <= kTouchSlop); - resolve(GestureDisposition.accepted); _timer = null; + if (_starter != null) { + _starter(initialPosition); + _starter = null; + } else { + resolve(GestureDisposition.accepted); + } + assert(_starter == null); } - void accepted(Drag client) { - _timer?.cancel(); - _timer = null; - super.accepted(client); + void accepted(GestureMultiDragStartCallback starter) { + assert(_starter == null); + if (_timer == null) + starter(initialPosition); + else + _starter = starter; } void checkForResolutionAfterMove() { diff --git a/packages/flutter/test/widget/draggable_test.dart b/packages/flutter/test/widget/draggable_test.dart index e8cb08856b..f7b473f4db 100644 --- a/packages/flutter/test/widget/draggable_test.dart +++ b/packages/flutter/test/widget/draggable_test.dart @@ -169,7 +169,9 @@ void main() { expect(events, equals(['tap', 'tap', 'drop'])); events.clear(); }); + }); + test('Drag and drop - tapping button', () { testWidgets((WidgetTester tester) { TestPointer pointer = new TestPointer(7); @@ -230,6 +232,113 @@ void main() { events.clear(); }); + }); + test('Drag and drop - long press draggable, short press', () { + testWidgets((WidgetTester tester) { + TestPointer pointer = new TestPointer(7); + + List events = []; + Point firstLocation, secondLocation; + + tester.pumpWidget(new MaterialApp( + routes: { + '/': (RouteArguments args) { return new Column( + children: [ + 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 events = []; + Point firstLocation, secondLocation; + + tester.pumpWidget(new MaterialApp( + routes: { + '/': (RouteArguments args) { return new Column( + children: [ + 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(['drop'])); + + }); }); }