Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draggable feedback positioning #145647

Merged
merged 13 commits into from
May 1, 2024
11 changes: 7 additions & 4 deletions packages/flutter/lib/src/widgets/drag_target.dart
Original file line number Diff line number Diff line change
Expand Up @@ -831,6 +831,7 @@ class _DragAvatar<T extends Object> extends Drag {
final List<_DragTargetState<Object>> _enteredTargets = <_DragTargetState<Object>>[];
Offset _position;
Offset? _lastOffset;
late Offset _overlayOffset;
OverlayEntry? _entry;

@override
Expand All @@ -856,6 +857,10 @@ class _DragAvatar<T extends Object> extends Drag {

void updateDrag(Offset globalPosition) {
_lastOffset = globalPosition - dragStartPoint;
final RenderBox box = overlayState.context.findRenderObject()! as RenderBox;
final Offset overlaySpaceOffset = box.globalToLocal(globalPosition);
_overlayOffset = overlaySpaceOffset - dragStartPoint;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems weird to me that we're subtracting dragStartPoint both from a point in global space (_lastOffset = globalPosition - dragStartPoint;) and from a point in local/overlay space (_overlayOffset = overlaySpaceOffset - dragStartPoint;). Wouldn't it be in the wrong coordinate space in one of those operations?

I guess based on your test and your video demo it works, though, so maybe I'm misunderstanding what dragStartPoint is.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had the same question at first. This transformation is definitely correct for the purposes of _overlayOffset and I've added another test to verify that. However, it might be incorrect for _lastOffset, which we purposefully haven't touched. We wanted this PR to focus on the visual feedback only, since we thought that would be least likely to break existing Flutter code. Adjusting _lastOffset_ can be done in another PR, where we could then make sure all Draggable callbacks use the Overlay space instead of global coordinates as they do now.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok sounds good. I agree that it looks like dragStartPoint is not in the same coordinate space as globalPosition, but _overlayOffset should be right.


_entry!.markNeedsBuild();
final HitTestResult result = HitTestResult();
WidgetsBinding.instance.hitTestInView(result, globalPosition + feedbackOffset, viewId);
Expand Down Expand Up @@ -946,11 +951,9 @@ class _DragAvatar<T extends Object> extends Drag {
}

Widget _build(BuildContext context) {
final RenderBox box = overlayState.context.findRenderObject()! as RenderBox;
final Offset overlayTopLeft = box.localToGlobal(Offset.zero);
return Positioned(
left: _lastOffset!.dx - overlayTopLeft.dx,
top: _lastOffset!.dy - overlayTopLeft.dy,
left: _overlayOffset.dx,
top: _overlayOffset.dy,
child: ExcludeSemantics(
excluding: ignoringFeedbackSemantics,
child: IgnorePointer(
Expand Down
68 changes: 68 additions & 0 deletions packages/flutter/test/widgets/draggable_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -968,6 +968,10 @@ void main() {
await gesture.moveTo(thirdLocation);
await tester.pump();
expect(tester.getTopLeft(find.text('N')), thirdLocation);

// Finish gesture to release resources.
await gesture.up();
await tester.pump();
});

testWidgets('Horizontal axis draggable moves horizontally', (WidgetTester tester) async {
Expand All @@ -983,6 +987,10 @@ void main() {
await gesture.moveTo(thirdLocation);
await tester.pump();
expect(tester.getTopLeft(find.text('H')), thirdLocation);

// Finish gesture to release resources.
await gesture.up();
await tester.pump();
});

testWidgets('Horizontal axis draggable does not move vertically', (WidgetTester tester) async {
Expand All @@ -1001,6 +1009,10 @@ void main() {
await gesture.moveTo(thirdDragLocation);
await tester.pump();
expect(tester.getTopLeft(find.text('H')), thirdWidgetLocation);

// Finish gesture to release resources.
await gesture.up();
await tester.pump();
});

testWidgets('Vertical axis draggable moves vertically', (WidgetTester tester) async {
Expand All @@ -1016,6 +1028,10 @@ void main() {
await gesture.moveTo(thirdLocation);
await tester.pump();
expect(tester.getTopLeft(find.text('V')), thirdLocation);

// Finish gesture to release resources.
await gesture.up();
await tester.pump();
});

testWidgets('Vertical axis draggable does not move horizontally', (WidgetTester tester) async {
Expand All @@ -1034,6 +1050,10 @@ void main() {
await gesture.moveTo(thirdDragLocation);
await tester.pump();
expect(tester.getTopLeft(find.text('V')), thirdWidgetLocation);

// Finish gesture to release resources.
await gesture.up();
await tester.pump();
});
});

Expand Down Expand Up @@ -1667,6 +1687,10 @@ void main() {
expect(find.text('Dragging'), findsOneWidget);
expect(find.text('Target'), findsOneWidget);
expect(find.text('Rejected'), findsNothing);

// Finish gesture to release resources.
await gesture.up();
await tester.pump();
});


Expand Down Expand Up @@ -3102,6 +3126,10 @@ void main() {
),
findsNothing,
);

// Finish gesture to release resources.
await gesture.up();
await tester.pump();
});

// Regression test for https://github.com/flutter/flutter/issues/72483
Expand Down Expand Up @@ -3414,6 +3442,34 @@ void main() {
await tester.pumpAndSettle();
});

testWidgets('Drag and drop - feedback matches pointer in scaled MaterialApp', (WidgetTester tester) async {
await tester.pumpWidget(Transform.scale(
scale: 0.5,
timcreatedit marked this conversation as resolved.
Show resolved Hide resolved
child: const MaterialApp(
home: Scaffold(
body: Draggable<int>(
data: 42,
feedback: Text('Feedback'),
child: Text('Source'),
),
),
),
));

final Offset location = tester.getTopLeft(find.text('Source'));
final TestGesture gesture = await tester.startGesture(location);
final Offset secondLocation = location + const Offset(100, 100);
await gesture.moveTo(secondLocation);
await tester.pump();
final Offset appTopLeft = tester.getTopLeft(find.byType(MaterialApp));
expect(tester.getTopLeft(find.text('Source')), appTopLeft);
expect(tester.getTopLeft(find.text('Feedback')), secondLocation);

// Finish gesture to release resources.
await gesture.up();
await tester.pumpAndSettle();
});

testWidgets('configurable Draggable hit test behavior', (WidgetTester tester) async {
const HitTestBehavior hitTestBehavior = HitTestBehavior.deferToChild;

Expand Down Expand Up @@ -3492,6 +3548,10 @@ void main() {

await tester.tap(find.text('Draggable'));
expect(onTap, true);

// Finish gesture to release resources.
await gesture.up();
await tester.pump();
});

testWidgets('configurable feedback ignore pointer behavior - LongPressDraggable', (WidgetTester tester) async {
Expand Down Expand Up @@ -3523,6 +3583,10 @@ void main() {

await tester.tap(find.text('Draggable'));
expect(onTap, true);

// Finish gesture to release resources.
await gesture.up();
await tester.pump();
});

testWidgets('configurable DragTarget hit test behavior', (WidgetTester tester) async {
Expand Down Expand Up @@ -3730,6 +3794,10 @@ Future<void> _testChildAnchorFeedbackPosition({ required WidgetTester tester, do
final Offset sourceTopLeft = tester.getTopLeft(find.text('Source'));
final Offset dragOffset = secondLocation - firstLocation;
expect(feedbackTopLeft, equals(sourceTopLeft + dragOffset));

// Finish gesture to release resources.
await gesture.up();
await tester.pump();
}

class DragTargetData { }
Expand Down