Skip to content

Commit 1e255b1

Browse files
authored
Fix scroll offset when caret larger than viewport (flutter#93248)
Fixing a calculation when jumping to an offset with a large caret as compared to the viewport.
1 parent 2a149bc commit 1e255b1

File tree

2 files changed

+103
-5
lines changed

2 files changed

+103
-5
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2151,7 +2151,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
21512151
if (!_isMultiline) {
21522152
additionalOffset = rect.width >= editableSize.width
21532153
// Center `rect` if it's oversized.
2154-
? editableSize.width / 2 - rect.center.dx
2154+
? rect.center.dx - editableSize.width / 2
21552155
// Valid additional offsets range from (rect.right - size.width)
21562156
// to (rect.left). Pick the closest one if out of range.
21572157
: 0.0.clamp(rect.right - editableSize.width, rect.left);
@@ -2167,7 +2167,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
21672167
);
21682168

21692169
additionalOffset = expandedRect.height >= editableSize.height
2170-
? editableSize.height / 2 - expandedRect.center.dy
2170+
? expandedRect.center.dy - editableSize.height / 2
21712171
: 0.0.clamp(expandedRect.bottom - editableSize.height, expandedRect.top);
21722172
unitOffset = const Offset(0, 1);
21732173
}

packages/flutter/test/widgets/editable_text_test.dart

Lines changed: 101 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5812,12 +5812,111 @@ void main() {
58125812
state.bringIntoView(TextPosition(offset: controller.text.length));
58135813

58145814
await tester.pumpAndSettle();
5815-
// The SingleChildScrollView is scrolled instead of the EditableText to
5816-
// reveal the caret.
5815+
// The SingleChildScrollView is scrolled instead of the EditableText to reveal the caret.
58175816
expect(outerController.offset, outerController.position.maxScrollExtent);
58185817
expect(editableScrollController.offset, 0);
58195818
});
58205819

5820+
testWidgets('bringIntoView centers the viewport on caret when the caret is wider than the viewport', (WidgetTester tester) async {
5821+
const String text = 'to coz ze ze Szwecji';
5822+
final TextEditingController controller = TextEditingController(text: text);
5823+
5824+
await tester.pumpWidget(MaterialApp(
5825+
home: Align(
5826+
alignment: Alignment.topLeft,
5827+
child: SizedBox(
5828+
width: 32.0,
5829+
height: 100.0,
5830+
child: EditableText(
5831+
showSelectionHandles: true,
5832+
controller: controller,
5833+
focusNode: FocusNode(),
5834+
style: const TextStyle(fontFamily: 'Ahem', fontSize: 48.0, height: 1.0),
5835+
cursorColor: Colors.blue,
5836+
cursorWidth: 48.0,
5837+
backgroundCursorColor: Colors.grey,
5838+
selectionControls: materialTextSelectionControls,
5839+
keyboardType: TextInputType.text,
5840+
),
5841+
),
5842+
),
5843+
));
5844+
5845+
final EditableTextState state = tester.state<EditableTextState>(find.byType(EditableText));
5846+
final RenderEditable renderEditable = state.renderEditable;
5847+
final Scrollable scrollable = tester.widget<Scrollable>(find.byType(Scrollable));
5848+
5849+
expect(scrollable.controller!.position.viewportDimension, equals(32.0));
5850+
expect(scrollable.controller!.offset, 0.0);
5851+
expect(renderEditable.maxScrollExtent, equals(977.0));
5852+
5853+
state.bringIntoView(const TextPosition(offset: 2));
5854+
await tester.pumpAndSettle();
5855+
expect(scrollable.controller!.offset, 2 * 48.0 + 48.0 / 2 - 32.0 / 2);
5856+
5857+
state.bringIntoView(const TextPosition(offset: 5));
5858+
await tester.pumpAndSettle();
5859+
expect(scrollable.controller!.offset, 5 * 48.0 + 48.0 / 2 - 32.0 / 2);
5860+
5861+
state.bringIntoView(const TextPosition(offset: 7));
5862+
await tester.pumpAndSettle();
5863+
expect(scrollable.controller!.offset, 7 * 48.0 + 48.0 / 2 - 32.0 / 2);
5864+
5865+
state.bringIntoView(const TextPosition(offset: 9));
5866+
await tester.pumpAndSettle();
5867+
expect(scrollable.controller!.offset, 9 * 48.0 + 48.0 / 2 - 32.0 / 2);
5868+
});
5869+
5870+
testWidgets('bringIntoView centers the viewport on caret when the caret is taller than the viewport', (WidgetTester tester) async {
5871+
const String text = 'to\ncoz\nze\nze\nSzwecji';
5872+
final TextEditingController controller = TextEditingController(text: text);
5873+
5874+
await tester.pumpWidget(MaterialApp(
5875+
home: Align(
5876+
alignment: Alignment.topLeft,
5877+
child: SizedBox(
5878+
width: 500.0,
5879+
height: 32.0,
5880+
child: EditableText(
5881+
showSelectionHandles: true,
5882+
maxLines: null,
5883+
controller: controller,
5884+
focusNode: FocusNode(),
5885+
style: const TextStyle(fontFamily: 'Ahem', fontSize: 48.0, height: 1.0),
5886+
cursorColor: Colors.blue,
5887+
backgroundCursorColor: Colors.grey,
5888+
selectionControls: materialTextSelectionControls,
5889+
keyboardType: TextInputType.text,
5890+
),
5891+
),
5892+
),
5893+
));
5894+
5895+
final EditableTextState state = tester.state<EditableTextState>(find.byType(EditableText));
5896+
final RenderEditable renderEditable = state.renderEditable;
5897+
final Scrollable scrollable = tester.widget<Scrollable>(find.byType(Scrollable));
5898+
5899+
expect(scrollable.controller!.position.viewportDimension, equals(32.0));
5900+
expect(scrollable.controller!.offset, 0.0);
5901+
expect(renderEditable.maxScrollExtent, equals(208.0));
5902+
5903+
state.bringIntoView(const TextPosition(offset: 3));
5904+
await tester.pumpAndSettle();
5905+
expect(scrollable.controller!.offset, 48.0 + 48.0 / 2 - 32.0 / 2);
5906+
5907+
state.bringIntoView(const TextPosition(offset: 7));
5908+
await tester.pumpAndSettle();
5909+
expect(scrollable.controller!.offset, 2 * 48.0 + 48.0 / 2 - 32.0 / 2);
5910+
5911+
state.bringIntoView(const TextPosition(offset: 10));
5912+
await tester.pumpAndSettle();
5913+
expect(scrollable.controller!.offset, 3 * 48.0 + 48.0 / 2 - 32.0 / 2);
5914+
5915+
state.bringIntoView(const TextPosition(offset: 13));
5916+
await tester.pumpAndSettle();
5917+
expect(scrollable.controller!.offset, 4 * 48.0 + 48.0 / 2 - 32.0 / 2);
5918+
});
5919+
58215920
testWidgets('bringIntoView does nothing if the physics prohibits implicit scrolling', (WidgetTester tester) async {
58225921
final TextEditingController controller = TextEditingController(text: testText * 20);
58235922
final ScrollController scrollController = ScrollController();
@@ -5844,7 +5943,6 @@ void main() {
58445943
));
58455944
}
58465945

5847-
58485946
await buildWithPhysics();
58495947
expect(scrollController.offset, 0);
58505948

0 commit comments

Comments
 (0)