Skip to content

Commit 213bf37

Browse files
authored
Text handle drag swap on Apple platforms (#105069)
Dragging the base text selection handle on Apple makes it the extent.
1 parent d8783ff commit 213bf37

File tree

2 files changed

+99
-19
lines changed

2 files changed

+99
-19
lines changed

packages/flutter/lib/src/widgets/text_selection.dart

Lines changed: 50 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -459,13 +459,31 @@ class TextSelectionOverlay {
459459
return;
460460
}
461461

462-
final TextSelection newSelection = TextSelection(
463-
baseOffset: _selection.baseOffset,
464-
extentOffset: position.offset,
465-
);
466-
467-
if (newSelection.baseOffset >= newSelection.extentOffset) {
468-
return; // Don't allow order swapping.
462+
final TextSelection newSelection;
463+
switch (defaultTargetPlatform) {
464+
// On Apple platforms, dragging the base handle makes it the extent.
465+
case TargetPlatform.iOS:
466+
case TargetPlatform.macOS:
467+
newSelection = TextSelection(
468+
extentOffset: position.offset,
469+
baseOffset: _selection.start,
470+
);
471+
if (position.offset <= _selection.start) {
472+
return; // Don't allow order swapping.
473+
}
474+
break;
475+
case TargetPlatform.android:
476+
case TargetPlatform.fuchsia:
477+
case TargetPlatform.linux:
478+
case TargetPlatform.windows:
479+
newSelection = TextSelection(
480+
baseOffset: _selection.baseOffset,
481+
extentOffset: position.offset,
482+
);
483+
if (newSelection.baseOffset >= newSelection.extentOffset) {
484+
return; // Don't allow order swapping.
485+
}
486+
break;
469487
}
470488

471489
_handleSelectionHandleChanged(newSelection, isEnd: true);
@@ -489,13 +507,31 @@ class TextSelectionOverlay {
489507
return;
490508
}
491509

492-
final TextSelection newSelection = TextSelection(
493-
baseOffset: position.offset,
494-
extentOffset: _selection.extentOffset,
495-
);
496-
497-
if (newSelection.baseOffset >= newSelection.extentOffset) {
498-
return; // Don't allow order swapping.
510+
final TextSelection newSelection;
511+
switch (defaultTargetPlatform) {
512+
// On Apple platforms, dragging the base handle makes it the extent.
513+
case TargetPlatform.iOS:
514+
case TargetPlatform.macOS:
515+
newSelection = TextSelection(
516+
extentOffset: position.offset,
517+
baseOffset: _selection.end,
518+
);
519+
if (newSelection.extentOffset >= _selection.end) {
520+
return; // Don't allow order swapping.
521+
}
522+
break;
523+
case TargetPlatform.android:
524+
case TargetPlatform.fuchsia:
525+
case TargetPlatform.linux:
526+
case TargetPlatform.windows:
527+
newSelection = TextSelection(
528+
baseOffset: position.offset,
529+
extentOffset: _selection.extentOffset,
530+
);
531+
if (newSelection.baseOffset >= newSelection.extentOffset) {
532+
return; // Don't allow order swapping.
533+
}
534+
break;
499535
}
500536

501537
_handleSelectionHandleChanged(newSelection, isEnd: false);
@@ -1116,7 +1152,6 @@ class _SelectionHandleOverlay extends StatefulWidget {
11161152
}
11171153

11181154
class _SelectionHandleOverlayState extends State<_SelectionHandleOverlay> with SingleTickerProviderStateMixin {
1119-
11201155
late AnimationController _controller;
11211156
Animation<double> get _opacity => _controller.view;
11221157

packages/flutter/test/material/text_field_test.dart

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2077,7 +2077,7 @@ void main() {
20772077
expect(selection.extentOffset, 7);
20782078

20792079
final RenderEditable renderEditable = findRenderEditable(tester);
2080-
final List<TextSelectionPoint> endpoints = globalize(
2080+
List<TextSelectionPoint> endpoints = globalize(
20812081
renderEditable.getEndpointsForSelection(selection),
20822082
renderEditable,
20832083
);
@@ -2100,6 +2100,36 @@ void main() {
21002100

21012101
// Drag the left handle 2 letters to the left.
21022102
handlePos = endpoints[0].point + const Offset(-1.0, 1.0);
2103+
newHandlePos = textOffsetToPosition(tester, 2);
2104+
gesture = await tester.startGesture(handlePos, pointer: 7);
2105+
await tester.pump();
2106+
await gesture.moveTo(newHandlePos);
2107+
await tester.pump();
2108+
await gesture.up();
2109+
await tester.pump();
2110+
2111+
switch (defaultTargetPlatform) {
2112+
// On Apple platforms, dragging the base handle makes it the extent.
2113+
case TargetPlatform.iOS:
2114+
case TargetPlatform.macOS:
2115+
expect(controller.selection.baseOffset, 11);
2116+
expect(controller.selection.extentOffset, 2);
2117+
break;
2118+
case TargetPlatform.android:
2119+
case TargetPlatform.fuchsia:
2120+
case TargetPlatform.linux:
2121+
case TargetPlatform.windows:
2122+
expect(controller.selection.baseOffset, 2);
2123+
expect(controller.selection.extentOffset, 11);
2124+
break;
2125+
}
2126+
2127+
// Drag the left handle 2 letters to the left again.
2128+
endpoints = globalize(
2129+
renderEditable.getEndpointsForSelection(controller.selection),
2130+
renderEditable,
2131+
);
2132+
handlePos = endpoints[0].point + const Offset(-1.0, 1.0);
21032133
newHandlePos = textOffsetToPosition(tester, 0);
21042134
gesture = await tester.startGesture(handlePos, pointer: 7);
21052135
await tester.pump();
@@ -2108,9 +2138,24 @@ void main() {
21082138
await gesture.up();
21092139
await tester.pump();
21102140

2111-
expect(controller.selection.baseOffset, 0);
2112-
expect(controller.selection.extentOffset, 11);
2113-
});
2141+
switch (defaultTargetPlatform) {
2142+
case TargetPlatform.iOS:
2143+
case TargetPlatform.macOS:
2144+
// The left handle was already the extent, and it remains so.
2145+
expect(controller.selection.baseOffset, 11);
2146+
expect(controller.selection.extentOffset, 0);
2147+
break;
2148+
case TargetPlatform.android:
2149+
case TargetPlatform.fuchsia:
2150+
case TargetPlatform.linux:
2151+
case TargetPlatform.windows:
2152+
expect(controller.selection.baseOffset, 0);
2153+
expect(controller.selection.extentOffset, 11);
2154+
break;
2155+
}
2156+
},
2157+
variant: TargetPlatformVariant.all(),
2158+
);
21142159

21152160
testWidgets('Cannot drag one handle past the other', (WidgetTester tester) async {
21162161
final TextEditingController controller = TextEditingController();

0 commit comments

Comments
 (0)