Fix text selection when user is dragging in the opposite direction (#29395)
diff --git a/packages/flutter/lib/src/rendering/editable.dart b/packages/flutter/lib/src/rendering/editable.dart index 2485239..4a11875 100644 --- a/packages/flutter/lib/src/rendering/editable.dart +++ b/packages/flutter/lib/src/rendering/editable.dart
@@ -1377,10 +1377,18 @@ final TextPosition toPosition = to == null ? null : _textPainter.getPositionForOffset(globalToLocal(to - _paintOffset)); + + int baseOffset = fromPosition.offset; + int extentOffset = fromPosition.offset; + if (toPosition != null) { + baseOffset = math.min(fromPosition.offset, toPosition.offset); + extentOffset = math.max(fromPosition.offset, toPosition.offset); + } + onSelectionChanged( TextSelection( - baseOffset: fromPosition.offset, - extentOffset: toPosition?.offset ?? fromPosition.offset, + baseOffset: baseOffset, + extentOffset: extentOffset, affinity: fromPosition.affinity, ), this,
diff --git a/packages/flutter/test/material/text_field_test.dart b/packages/flutter/test/material/text_field_test.dart index 1e38ea3..10d765a 100644 --- a/packages/flutter/test/material/text_field_test.dart +++ b/packages/flutter/test/material/text_field_test.dart
@@ -645,6 +645,38 @@ expect(controller.selection.extentOffset, testValue.indexOf('g')); }); + testWidgets('Dragging in opposite direction also works', (WidgetTester tester) async { + final TextEditingController controller = TextEditingController(); + + await tester.pumpWidget( + MaterialApp( + home: Material( + child: TextField( + dragStartBehavior: DragStartBehavior.down, + controller: controller, + ), + ), + ), + ); + + const String testValue = 'abc def ghi'; + await tester.enterText(find.byType(TextField), testValue); + await skipPastScrollingAnimation(tester); + + final Offset ePos = textOffsetToPosition(tester, testValue.indexOf('e')); + final Offset gPos = textOffsetToPosition(tester, testValue.indexOf('g')); + + final TestGesture gesture = await tester.startGesture(gPos, kind: PointerDeviceKind.mouse); + await tester.pump(); + await gesture.moveTo(ePos); + await tester.pump(); + await gesture.up(); + await tester.pumpAndSettle(); + + expect(controller.selection.baseOffset, testValue.indexOf('e')); + expect(controller.selection.extentOffset, testValue.indexOf('g')); + }); + testWidgets('Slow mouse dragging also selects text', (WidgetTester tester) async { final TextEditingController controller = TextEditingController();
diff --git a/packages/flutter/test/rendering/editable_test.dart b/packages/flutter/test/rendering/editable_test.dart index f54ba7a..a797bdb 100644 --- a/packages/flutter/test/rendering/editable_test.dart +++ b/packages/flutter/test/rendering/editable_test.dart
@@ -335,4 +335,36 @@ expect(currentSelection.baseOffset, 5); expect(currentSelection.extentOffset, 9); }); + + test('selects correct place when offsets are flipped', () { + final TextSelectionDelegate delegate = FakeEditableTextState(); + final ViewportOffset viewportOffset = ViewportOffset.zero(); + TextSelection currentSelection; + final RenderEditable editable = RenderEditable( + backgroundCursorColor: Colors.grey, + selectionColor: Colors.black, + textDirection: TextDirection.ltr, + cursorColor: Colors.red, + offset: viewportOffset, + textSelectionDelegate: delegate, + onSelectionChanged: (TextSelection selection, RenderEditable renderObject, SelectionChangedCause cause) { + currentSelection = selection; + }, + text: const TextSpan( + text: 'abc def ghi', + style: TextStyle( + height: 1.0, fontSize: 10.0, fontFamily: 'Ahem', + ), + ), + ); + + layout(editable); + + editable.selectPositionAt(from: const Offset(30, 2), to: const Offset(10, 2), cause: SelectionChangedCause.drag); + pumpFrame(); + + expect(currentSelection.isCollapsed, isFalse); + expect(currentSelection.baseOffset, 1); + expect(currentSelection.extentOffset, 3); + }); }