Commit a5ee6e11 authored by Andrew Wilson's avatar Andrew Wilson

Merge pull request #2919 from apwilson/drop_through

Allow Draggables to drop through DragTargets that don't accept them to one that does.
parents c9c84946 8ff19827
...@@ -391,7 +391,7 @@ class _DragAvatar<T> extends Drag { ...@@ -391,7 +391,7 @@ class _DragAvatar<T> extends Drag {
final _OnDragEnd onDragEnd; final _OnDragEnd onDragEnd;
_DragTargetState<T> _activeTarget; _DragTargetState<T> _activeTarget;
bool _activeTargetWillAcceptDrop = false; List<_DragTargetState<T>> _lastTargets = <_DragTargetState<T>>[];
Point _position; Point _position;
Offset _lastOffset; Offset _lastOffset;
OverlayEntry _entry; OverlayEntry _entry;
...@@ -418,32 +418,58 @@ class _DragAvatar<T> extends Drag { ...@@ -418,32 +418,58 @@ class _DragAvatar<T> extends Drag {
_entry.markNeedsBuild(); _entry.markNeedsBuild();
HitTestResult result = new HitTestResult(); HitTestResult result = new HitTestResult();
WidgetFlutterBinding.instance.hitTest(result, globalPosition + feedbackOffset); WidgetFlutterBinding.instance.hitTest(result, globalPosition + feedbackOffset);
_DragTargetState<T> target = _getDragTarget(result.path);
if (target == _activeTarget) List<_DragTargetState<T>> targets = _getDragTargets(result.path).toList();
bool listsMatch = false;
if (targets.length >= _lastTargets.length && _lastTargets.isNotEmpty) {
listsMatch = true;
Iterator<_DragTargetState<T>> iterator = targets.iterator;
for (int i = 0; i < _lastTargets.length; i += 1) {
iterator.moveNext();
if (iterator.current != _lastTargets[i]) {
listsMatch = false;
break;
}
}
}
// If everything's the same, bail early.
if (listsMatch)
return; return;
if (_activeTarget != null)
_activeTarget.didLeave(data); // Leave old targets.
_activeTarget = target; for (int i = 0; i < _lastTargets.length; i += 1)
_activeTargetWillAcceptDrop = _activeTarget != null && _activeTarget.didEnter(data); _lastTargets[i].didLeave(data);
_lastTargets.clear();
// Enter new targets.
_DragTargetState<T> newTarget = targets.firstWhere((_DragTargetState<T> target) {
_lastTargets.add(target);
return target.didEnter(data);
},
orElse: () => null
);
_activeTarget = newTarget;
} }
_DragTargetState<T> _getDragTarget(List<HitTestEntry> path) { Iterable<_DragTargetState<T>> _getDragTargets(List<HitTestEntry> path) sync* {
// Look for the RenderBox that corresponds to the hit target (the hit target // Look for the RenderBoxes that corresponds to the hit target (the hit target
// widget builds a RenderMetadata box for us for this purpose). // widgets build RenderMetadata boxes for us for this purpose).
for (HitTestEntry entry in path) { for (HitTestEntry entry in path) {
if (entry.target is RenderMetaData) { if (entry.target is RenderMetaData) {
RenderMetaData renderMetaData = entry.target; RenderMetaData renderMetaData = entry.target;
if (renderMetaData.metaData is _DragTargetState<T>) if (renderMetaData.metaData is _DragTargetState<T>)
return renderMetaData.metaData; yield renderMetaData.metaData;
} }
} }
return null;
} }
void finish(_DragEndKind endKind, [Velocity velocity]) { void finish(_DragEndKind endKind, [Velocity velocity]) {
bool wasAccepted = false; bool wasAccepted = false;
if (_activeTarget != null) { if (_activeTarget != null) {
if (endKind == _DragEndKind.dropped && _activeTargetWillAcceptDrop) { if (endKind == _DragEndKind.dropped && _activeTarget != null) {
_activeTarget.didDrop(data); _activeTarget.didDrop(data);
wasAccepted = true; wasAccepted = true;
} else { } else {
...@@ -451,7 +477,6 @@ class _DragAvatar<T> extends Drag { ...@@ -451,7 +477,6 @@ class _DragAvatar<T> extends Drag {
} }
} }
_activeTarget = null; _activeTarget = null;
_activeTargetWillAcceptDrop = false;
_entry.remove(); _entry.remove();
_entry = null; _entry = null;
// TODO(ianh): consider passing _entry as well so the client can perform an animation. // TODO(ianh): consider passing _entry as well so the client can perform an animation.
......
...@@ -753,4 +753,123 @@ void main() { ...@@ -753,4 +753,123 @@ void main() {
expect(onDraggableCanceledOffset, equals(new Offset(flingStart.x, flingStart.y) + new Offset(0.0, 100.0))); expect(onDraggableCanceledOffset, equals(new Offset(flingStart.x, flingStart.y) + new Offset(0.0, 100.0)));
}); });
}); });
test('Drag and drop - allow pass thru of unaccepted data test', () {
testWidgets((WidgetTester tester) {
List<int> acceptedInts = <int>[];
List<double> acceptedDoubles = <double>[];
tester.pumpWidget(new MaterialApp(
routes: <String, WidgetBuilder>{
'/': (BuildContext context) { return new Column(
children: <Widget>[
new Draggable<int>(
data: 1,
child: new Text('IntSource'),
feedback: new Text('IntDragging')
),
new Draggable<double>(
data: 1.0,
child: new Text('DoubleSource'),
feedback: new Text('DoubleDragging')
),
new Stack(children:[
new DragTarget<int>(
builder: (BuildContext context, List<int> data, List<dynamic> rejects) {
return new IgnorePointer(
child: new Container(
height: 100.0,
child: new Text('Target1')
)
);
},
onAccept: (int data) {
acceptedInts.add(data);
}
),
new DragTarget<double>(
builder: (BuildContext context, List<double> data, List<dynamic> rejects) {
return new IgnorePointer(
child: new Container(
height: 100.0,
child: new Text('Target2')
)
);
},
onAccept: (double data) {
acceptedDoubles.add(data);
}
),
])
]);
},
}
));
expect(acceptedInts, isEmpty);
expect(acceptedDoubles, isEmpty);
expect(tester.findText('IntSource'), isNotNull);
expect(tester.findText('IntDragging'), isNull);
expect(tester.findText('DoubleSource'), isNotNull);
expect(tester.findText('DoubleDragging'), isNull);
expect(tester.findText('Target1'), isNotNull);
expect(tester.findText('Target2'), isNotNull);
Point intLocation = tester.getCenter(tester.findText('IntSource'));
Point doubleLocation = tester.getCenter(tester.findText('DoubleSource'));
Point targetLocation = tester.getCenter(tester.findText('Target1'));
// Drag the double draggable.
TestGesture doubleGesture = tester.startGesture(doubleLocation, pointer: 7);
tester.pump();
expect(acceptedInts, isEmpty);
expect(acceptedDoubles, isEmpty);
expect(tester.findText('IntDragging'), isNull);
expect(tester.findText('DoubleDragging'), isNotNull);
doubleGesture.moveTo(targetLocation);
tester.pump();
expect(acceptedInts, isEmpty);
expect(acceptedDoubles, isEmpty);
expect(tester.findText('IntDragging'), isNull);
expect(tester.findText('DoubleDragging'), isNotNull);
doubleGesture.up();
tester.pump();
expect(acceptedInts, isEmpty);
expect(acceptedDoubles, equals(<double>[1.0]));
expect(tester.findText('IntDragging'), isNull);
expect(tester.findText('DoubleDragging'), isNull);
acceptedDoubles.clear();
// Drag the int draggable.
TestGesture intGesture = tester.startGesture(intLocation, pointer: 7);
tester.pump();
expect(acceptedInts, isEmpty);
expect(acceptedDoubles, isEmpty);
expect(tester.findText('IntDragging'), isNotNull);
expect(tester.findText('DoubleDragging'), isNull);
intGesture.moveTo(targetLocation);
tester.pump();
expect(acceptedInts, isEmpty);
expect(acceptedDoubles, isEmpty);
expect(tester.findText('IntDragging'), isNotNull);
expect(tester.findText('DoubleDragging'), isNull);
intGesture.up();
tester.pump();
expect(acceptedInts, equals(<int>[1]));
expect(acceptedDoubles, isEmpty);
expect(tester.findText('IntDragging'), isNull);
expect(tester.findText('DoubleDragging'), isNull);
});
});
} }
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