Skip to content

Commit 3f02d4b

Browse files
authored
Tweak to floating-cursor-end behaviour (#119893)
* Tweak to floating-cursor-end behaviour * Simplify
1 parent 51b05ac commit 3f02d4b

File tree

2 files changed

+87
-10
lines changed

2 files changed

+87
-10
lines changed

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2915,9 +2915,9 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
29152915
final Offset finalPosition = renderEditable.getLocalRectForCaret(_lastTextPosition!).centerLeft - _floatingCursorOffset;
29162916
if (_floatingCursorResetController!.isCompleted) {
29172917
renderEditable.setFloatingCursor(FloatingCursorDragState.End, finalPosition, _lastTextPosition!);
2918-
// Only change if new position is out of current selection range, as the
2919-
// selection may have been modified using the iOS keyboard selection gesture.
2920-
if (_lastTextPosition!.offset < renderEditable.selection!.start || _lastTextPosition!.offset >= renderEditable.selection!.end) {
2918+
// Only change if the current selection range is collapsed, to prevent
2919+
// overwriting the result of the iOS keyboard selection gesture.
2920+
if (renderEditable.selection!.isCollapsed) {
29212921
// The cause is technically the force cursor, but the cause is listed as tap as the desired functionality is the same.
29222922
_handleSelectionChanged(TextSelection.fromPosition(_lastTextPosition!), SelectionChangedCause.forcePress);
29232923
}

packages/flutter/test/widgets/editable_text_test.dart

Lines changed: 84 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12016,8 +12016,8 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async
1201612016
EditableText.debugDeterministicCursor = true;
1201712017
final FocusNode focusNode = FocusNode();
1201812018
final GlobalKey key = GlobalKey();
12019-
// Set it up so that there will be word-wrap.
12020-
final TextEditingController controller = TextEditingController(text: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ');
12019+
12020+
final TextEditingController controller = TextEditingController(text: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ\n1234567890');
1202112021
controller.selection = const TextSelection.collapsed(offset: 0);
1202212022
await tester.pumpWidget(
1202312023
MaterialApp(
@@ -12030,6 +12030,7 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async
1203012030
cursorColor: Colors.blue,
1203112031
backgroundCursorColor: Colors.grey,
1203212032
cursorOpacityAnimates: true,
12033+
maxLines: 2,
1203312034
),
1203412035
),
1203512036
);
@@ -12040,7 +12041,7 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async
1204012041
state.updateFloatingCursor(RawFloatingCursorPoint(state: FloatingCursorDragState.Start, offset: Offset.zero));
1204112042
await tester.pump();
1204212043

12043-
// The floating cursor should be drawn at the start of the line.
12044+
// The cursor should be drawn at the start of the line.
1204412045
expect(key.currentContext!.findRenderObject(), paints..rrect(
1204512046
rrect: RRect.fromRectAndRadius(
1204612047
const Rect.fromLTWH(0.5, 1, 3, 12),
@@ -12051,7 +12052,7 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async
1205112052
state.updateFloatingCursor(RawFloatingCursorPoint(state: FloatingCursorDragState.Update, offset: const Offset(50, 0)));
1205212053
await tester.pump();
1205312054

12054-
// The floating cursor should be drawn somewhere in the middle of the line
12055+
// The cursor should be drawn somewhere in the middle of the line
1205512056
expect(key.currentContext!.findRenderObject(), paints..rrect(
1205612057
rrect: RRect.fromRectAndRadius(
1205712058
const Rect.fromLTWH(50.5, 1, 3, 12),
@@ -12069,7 +12070,7 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async
1206912070
state.updateFloatingCursor(RawFloatingCursorPoint(state: FloatingCursorDragState.Start, offset: Offset.zero));
1207012071
await tester.pump();
1207112072

12072-
// The floating cursor should be drawn near to the previous position.
12073+
// The cursor should be drawn near to the previous position.
1207312074
// It's different because it's snapped to exactly between characters.
1207412075
expect(key.currentContext!.findRenderObject(), paints..rrect(
1207512076
rrect: RRect.fromRectAndRadius(
@@ -12081,7 +12082,7 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async
1208112082
state.updateFloatingCursor(RawFloatingCursorPoint(state: FloatingCursorDragState.Update, offset: const Offset(-56, 0)));
1208212083
await tester.pump();
1208312084

12084-
// The floating cursor should be drawn at the start of the line.
12085+
// The cursor should be drawn at the start of the line.
1208512086
expect(key.currentContext!.findRenderObject(), paints..rrect(
1208612087
rrect: RRect.fromRectAndRadius(
1208712088
const Rect.fromLTWH(0.5, 1, 3, 12),
@@ -12096,11 +12097,87 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async
1209612097
state.updateFloatingCursor(RawFloatingCursorPoint(state: FloatingCursorDragState.End, offset: Offset.zero));
1209712098
await tester.pump();
1209812099

12099-
// Selection should not be updated as the new position is within the selection range.
12100+
// Selection should not be changed since it wasn't previously collapsed.
1210012101
expect(controller.selection.isCollapsed, false);
1210112102
expect(controller.selection.baseOffset, 0);
1210212103
expect(controller.selection.extentOffset, 4);
1210312104

12105+
// Now test using keyboard selection in a forwards direction.
12106+
controller.selection = const TextSelection.collapsed(offset: 0);
12107+
await tester.pump();
12108+
state.updateFloatingCursor(RawFloatingCursorPoint(state: FloatingCursorDragState.Start, offset: Offset.zero));
12109+
await tester.pump();
12110+
12111+
// The cursor should be drawn in the same (start) position.
12112+
expect(key.currentContext!.findRenderObject(), paints..rrect(
12113+
rrect: RRect.fromRectAndRadius(
12114+
const Rect.fromLTWH(0.5, 1, 3, 12),
12115+
const Radius.circular(1)
12116+
)
12117+
));
12118+
12119+
state.updateFloatingCursor(RawFloatingCursorPoint(state: FloatingCursorDragState.Update, offset: const Offset(56, 0)));
12120+
await tester.pump();
12121+
12122+
// The cursor should be drawn somewhere in the middle of the line.
12123+
expect(key.currentContext!.findRenderObject(), paints..rrect(
12124+
rrect: RRect.fromRectAndRadius(
12125+
const Rect.fromLTWH(56.5, 1, 3, 12),
12126+
const Radius.circular(1)
12127+
)
12128+
));
12129+
12130+
// Simulate UIKit setting the selection using keyboard selection.
12131+
controller.selection = const TextSelection(baseOffset: 0, extentOffset: 4);
12132+
await tester.pump();
12133+
12134+
state.updateFloatingCursor(RawFloatingCursorPoint(state: FloatingCursorDragState.End, offset: Offset.zero));
12135+
await tester.pump();
12136+
12137+
// Selection should not be changed since it wasn't previously collapsed.
12138+
expect(controller.selection.isCollapsed, false);
12139+
expect(controller.selection.baseOffset, 0);
12140+
expect(controller.selection.extentOffset, 4);
12141+
12142+
// Test that the affinity is updated in case the floating cursor ends at the same offset.
12143+
12144+
// Put the selection at the beginning of the second line.
12145+
controller.selection = const TextSelection.collapsed(offset: 27);
12146+
await tester.pump();
12147+
12148+
// Now test using keyboard selection in a forwards direction.
12149+
state.updateFloatingCursor(RawFloatingCursorPoint(state: FloatingCursorDragState.Start, offset: Offset.zero));
12150+
await tester.pump();
12151+
12152+
// The cursor should be drawn at the start of the second line.
12153+
expect(key.currentContext!.findRenderObject(), paints..rrect(
12154+
rrect: RRect.fromRectAndRadius(
12155+
const Rect.fromLTWH(0.5, 15, 3, 12),
12156+
const Radius.circular(1)
12157+
)
12158+
));
12159+
12160+
// Move the cursor to the end of the first line.
12161+
12162+
state.updateFloatingCursor(RawFloatingCursorPoint(state: FloatingCursorDragState.Update, offset: const Offset(9999, -14)));
12163+
await tester.pump();
12164+
12165+
// The cursor should be drawn at the end of the first line.
12166+
expect(key.currentContext!.findRenderObject(), paints..rrect(
12167+
rrect: RRect.fromRectAndRadius(
12168+
const Rect.fromLTWH(800.5, 1, 3, 12),
12169+
const Radius.circular(1)
12170+
)
12171+
));
12172+
12173+
state.updateFloatingCursor(RawFloatingCursorPoint(state: FloatingCursorDragState.End, offset: Offset.zero));
12174+
await tester.pump();
12175+
12176+
// Selection should be changed as it was previously collapsed.
12177+
expect(controller.selection.isCollapsed, true);
12178+
expect(controller.selection.baseOffset, 27);
12179+
expect(controller.selection.extentOffset, 27);
12180+
1210412181
EditableText.debugDeterministicCursor = false;
1210512182
});
1210612183

0 commit comments

Comments
 (0)