Unverified Commit 105026cd authored by Justin McCandless's avatar Justin McCandless Committed by GitHub

Fix descenders cutoff in TextField (#26309)

* Include caret gaps in position calculation to avoid descender cutoff

* Calculate caret offset instead of exposing it

* Test that textfields scroll vertically by full line height

* Only add offset to end of caret to avoid going above 0.0

* Fix and test horizontal vs. vertical scrolling

* CR: docs and top caret offset logic

* Take max instead of assuming caretRect.top is always a full line down

* Always subtract caretOffset from caretTop since caretRect.top can be negative
parent c20db668
...@@ -752,8 +752,21 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien ...@@ -752,8 +752,21 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
// Calculate the new scroll offset so the cursor remains visible. // Calculate the new scroll offset so the cursor remains visible.
double _getScrollOffsetForCaret(Rect caretRect) { double _getScrollOffsetForCaret(Rect caretRect) {
final double caretStart = _isMultiline ? caretRect.top : caretRect.left; double caretStart;
final double caretEnd = _isMultiline ? caretRect.bottom : caretRect.right; double caretEnd;
if (_isMultiline) {
// The caret is vertically centered within the line. Expand the caret's
// height so that it spans the line because we're going to ensure that the entire
// expanded caret is scrolled into view.
final double lineHeight = renderEditable.preferredLineHeight;
final double caretOffset = (lineHeight - caretRect.height) / 2;
caretStart = caretRect.top - caretOffset;
caretEnd = caretRect.bottom + caretOffset;
} else {
caretStart = caretRect.left;
caretEnd = caretRect.right;
}
double scrollOffset = _scrollController.offset; double scrollOffset = _scrollController.offset;
final double viewportExtent = _scrollController.position.viewportDimension; final double viewportExtent = _scrollController.position.viewportDimension;
if (caretStart < 0.0) // cursor before start of bounds if (caretStart < 0.0) // cursor before start of bounds
......
...@@ -1722,7 +1722,42 @@ void main() { ...@@ -1722,7 +1722,42 @@ void main() {
await skipPastScrollingAnimation(tester); await skipPastScrollingAnimation(tester);
scrollableState = tester.firstState(find.byType(Scrollable)); scrollableState = tester.firstState(find.byType(Scrollable));
expect(scrollableState.position.pixels, isNot(equals(0.0))); // For a horizontal input, scrolls to the exact position of the caret.
expect(scrollableState.position.pixels, equals(222.0));
});
testWidgets('Multiline text field scrolls the caret into view', (WidgetTester tester) async {
final TextEditingController controller = TextEditingController();
await tester.pumpWidget(
overlay(
child: Container(
child: TextField(
controller: controller,
maxLines: 6,
),
),
),
);
const String tallText = 'a\nb\nc\nd\ne\nf\ng'; // One line over max
await tester.enterText(find.byType(TextField), tallText);
await skipPastScrollingAnimation(tester);
ScrollableState scrollableState = tester.firstState(find.byType(Scrollable));
expect(scrollableState.position.pixels, equals(0.0));
// Move the caret to the end of the text and check that the text field
// scrolls to make the caret visible.
controller.selection = const TextSelection.collapsed(offset: tallText.length);
await tester.pump();
await skipPastScrollingAnimation(tester);
// Should have scrolled down exactly one line height (7 lines of text in 6
// line text field).
final double lineHeight = findRenderEditable(tester).preferredLineHeight;
scrollableState = tester.firstState(find.byType(Scrollable));
expect(scrollableState.position.pixels, equals(lineHeight));
}); });
testWidgets('haptic feedback', (WidgetTester tester) async { testWidgets('haptic feedback', (WidgetTester tester) async {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment