Fix scroll offset when caret larger than viewport (#93248)

Fixing a calculation when jumping to an offset with a large caret as compared to the viewport.
This commit is contained in:
Tomasz Gucio 2021-11-22 22:24:10 +01:00 committed by GitHub
parent 2a149bc4cd
commit 1e255b137b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 103 additions and 5 deletions

View file

@ -2151,7 +2151,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
if (!_isMultiline) {
additionalOffset = rect.width >= editableSize.width
// Center `rect` if it's oversized.
? editableSize.width / 2 - rect.center.dx
? rect.center.dx - editableSize.width / 2
// Valid additional offsets range from (rect.right - size.width)
// to (rect.left). Pick the closest one if out of range.
: 0.0.clamp(rect.right - editableSize.width, rect.left);
@ -2167,7 +2167,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
);
additionalOffset = expandedRect.height >= editableSize.height
? editableSize.height / 2 - expandedRect.center.dy
? expandedRect.center.dy - editableSize.height / 2
: 0.0.clamp(expandedRect.bottom - editableSize.height, expandedRect.top);
unitOffset = const Offset(0, 1);
}

View file

@ -5812,12 +5812,111 @@ void main() {
state.bringIntoView(TextPosition(offset: controller.text.length));
await tester.pumpAndSettle();
// The SingleChildScrollView is scrolled instead of the EditableText to
// reveal the caret.
// The SingleChildScrollView is scrolled instead of the EditableText to reveal the caret.
expect(outerController.offset, outerController.position.maxScrollExtent);
expect(editableScrollController.offset, 0);
});
testWidgets('bringIntoView centers the viewport on caret when the caret is wider than the viewport', (WidgetTester tester) async {
const String text = 'to coz ze ze Szwecji';
final TextEditingController controller = TextEditingController(text: text);
await tester.pumpWidget(MaterialApp(
home: Align(
alignment: Alignment.topLeft,
child: SizedBox(
width: 32.0,
height: 100.0,
child: EditableText(
showSelectionHandles: true,
controller: controller,
focusNode: FocusNode(),
style: const TextStyle(fontFamily: 'Ahem', fontSize: 48.0, height: 1.0),
cursorColor: Colors.blue,
cursorWidth: 48.0,
backgroundCursorColor: Colors.grey,
selectionControls: materialTextSelectionControls,
keyboardType: TextInputType.text,
),
),
),
));
final EditableTextState state = tester.state<EditableTextState>(find.byType(EditableText));
final RenderEditable renderEditable = state.renderEditable;
final Scrollable scrollable = tester.widget<Scrollable>(find.byType(Scrollable));
expect(scrollable.controller!.position.viewportDimension, equals(32.0));
expect(scrollable.controller!.offset, 0.0);
expect(renderEditable.maxScrollExtent, equals(977.0));
state.bringIntoView(const TextPosition(offset: 2));
await tester.pumpAndSettle();
expect(scrollable.controller!.offset, 2 * 48.0 + 48.0 / 2 - 32.0 / 2);
state.bringIntoView(const TextPosition(offset: 5));
await tester.pumpAndSettle();
expect(scrollable.controller!.offset, 5 * 48.0 + 48.0 / 2 - 32.0 / 2);
state.bringIntoView(const TextPosition(offset: 7));
await tester.pumpAndSettle();
expect(scrollable.controller!.offset, 7 * 48.0 + 48.0 / 2 - 32.0 / 2);
state.bringIntoView(const TextPosition(offset: 9));
await tester.pumpAndSettle();
expect(scrollable.controller!.offset, 9 * 48.0 + 48.0 / 2 - 32.0 / 2);
});
testWidgets('bringIntoView centers the viewport on caret when the caret is taller than the viewport', (WidgetTester tester) async {
const String text = 'to\ncoz\nze\nze\nSzwecji';
final TextEditingController controller = TextEditingController(text: text);
await tester.pumpWidget(MaterialApp(
home: Align(
alignment: Alignment.topLeft,
child: SizedBox(
width: 500.0,
height: 32.0,
child: EditableText(
showSelectionHandles: true,
maxLines: null,
controller: controller,
focusNode: FocusNode(),
style: const TextStyle(fontFamily: 'Ahem', fontSize: 48.0, height: 1.0),
cursorColor: Colors.blue,
backgroundCursorColor: Colors.grey,
selectionControls: materialTextSelectionControls,
keyboardType: TextInputType.text,
),
),
),
));
final EditableTextState state = tester.state<EditableTextState>(find.byType(EditableText));
final RenderEditable renderEditable = state.renderEditable;
final Scrollable scrollable = tester.widget<Scrollable>(find.byType(Scrollable));
expect(scrollable.controller!.position.viewportDimension, equals(32.0));
expect(scrollable.controller!.offset, 0.0);
expect(renderEditable.maxScrollExtent, equals(208.0));
state.bringIntoView(const TextPosition(offset: 3));
await tester.pumpAndSettle();
expect(scrollable.controller!.offset, 48.0 + 48.0 / 2 - 32.0 / 2);
state.bringIntoView(const TextPosition(offset: 7));
await tester.pumpAndSettle();
expect(scrollable.controller!.offset, 2 * 48.0 + 48.0 / 2 - 32.0 / 2);
state.bringIntoView(const TextPosition(offset: 10));
await tester.pumpAndSettle();
expect(scrollable.controller!.offset, 3 * 48.0 + 48.0 / 2 - 32.0 / 2);
state.bringIntoView(const TextPosition(offset: 13));
await tester.pumpAndSettle();
expect(scrollable.controller!.offset, 4 * 48.0 + 48.0 / 2 - 32.0 / 2);
});
testWidgets('bringIntoView does nothing if the physics prohibits implicit scrolling', (WidgetTester tester) async {
final TextEditingController controller = TextEditingController(text: testText * 20);
final ScrollController scrollController = ScrollController();
@ -5844,7 +5943,6 @@ void main() {
));
}
await buildWithPhysics();
expect(scrollController.offset, 0);