From b865b0eb2f6f2ec37fb79c324e7634fda2206133 Mon Sep 17 00:00:00 2001 From: Jason Simmons Date: Fri, 3 Nov 2017 09:47:46 -0700 Subject: [PATCH] Use TextPainter.preferredLineHeight to estimate line height for Cupertino selection handles (#12833) Fixes https://github.com/flutter/flutter/issues/12046 --- .../flutter/lib/src/rendering/editable.dart | 17 ++++++------ .../lib/src/widgets/text_selection.dart | 4 +-- .../test/material/text_field_test.dart | 26 +++++++++++++++++++ 3 files changed, 37 insertions(+), 10 deletions(-) diff --git a/packages/flutter/lib/src/rendering/editable.dart b/packages/flutter/lib/src/rendering/editable.dart index fb0826d48e..d8ae24bb55 100644 --- a/packages/flutter/lib/src/rendering/editable.dart +++ b/packages/flutter/lib/src/rendering/editable.dart @@ -399,7 +399,7 @@ class RenderEditable extends RenderBox { if (selection.isCollapsed) { // TODO(mpcomplete): This doesn't work well at an RTL/LTR boundary. final Offset caretOffset = _textPainter.getOffsetForCaret(selection.extent, _caretPrototype); - final Offset start = new Offset(0.0, _preferredLineHeight) + caretOffset + paintOffset; + final Offset start = new Offset(0.0, preferredLineHeight) + caretOffset + paintOffset; return [new TextSelectionPoint(start, null)]; } else { final List boxes = _textPainter.getBoxesForSelection(selection); @@ -441,7 +441,7 @@ class RenderEditable extends RenderBox { _layoutText(constraints.maxWidth); final Offset caretOffset = _textPainter.getOffsetForCaret(caretPosition, _caretPrototype); // This rect is the same as _caretPrototype but without the vertical padding. - return new Rect.fromLTWH(0.0, 0.0, _kCaretWidth, _preferredLineHeight).shift(caretOffset + _paintOffset); + return new Rect.fromLTWH(0.0, 0.0, _kCaretWidth, preferredLineHeight).shift(caretOffset + _paintOffset); } @override @@ -456,12 +456,13 @@ class RenderEditable extends RenderBox { return _textPainter.maxIntrinsicWidth; } - // This does not required the layout to be updated. - double get _preferredLineHeight => _textPainter.preferredLineHeight; + /// An estimate of the height of a line in the text. See [TextPainter.preferredLineHeight]. + /// This does not required the layout to be updated. + double get preferredLineHeight => _textPainter.preferredLineHeight; double _preferredHeight(double width) { if (maxLines != null) - return _preferredLineHeight * maxLines; + return preferredLineHeight * maxLines; if (width == double.INFINITY) { final String text = _textPainter.text.toPlainText(); int lines = 1; @@ -469,10 +470,10 @@ class RenderEditable extends RenderBox { if (text.codeUnitAt(index) == 0x0A) // count explicit line breaks lines += 1; } - return _preferredLineHeight * lines; + return preferredLineHeight * lines; } _layoutText(width); - return math.max(_preferredLineHeight, _textPainter.height); + return math.max(preferredLineHeight, _textPainter.height); } @override @@ -558,7 +559,7 @@ class RenderEditable extends RenderBox { @override void performLayout() { _layoutText(constraints.maxWidth); - _caretPrototype = new Rect.fromLTWH(0.0, _kCaretHeightOffset, _kCaretWidth, _preferredLineHeight - 2.0 * _kCaretHeightOffset); + _caretPrototype = new Rect.fromLTWH(0.0, _kCaretHeightOffset, _kCaretWidth, preferredLineHeight - 2.0 * _kCaretHeightOffset); _selectionRects = null; // We grab _textPainter.size here because assigning to `size` on the next // line will trigger us to validate our intrinsic sizes, which will change diff --git a/packages/flutter/lib/src/widgets/text_selection.dart b/packages/flutter/lib/src/widgets/text_selection.dart index ecd7cdecf4..de098f6d93 100644 --- a/packages/flutter/lib/src/widgets/text_selection.dart +++ b/packages/flutter/lib/src/widgets/text_selection.dart @@ -344,7 +344,7 @@ class TextSelectionOverlay implements TextSelectionDelegate { (endpoints.length == 1) ? endpoints[0].point.dx : (endpoints[0].point.dx + endpoints[1].point.dx) / 2.0, - endpoints[0].point.dy - renderObject.size.height, + endpoints[0].point.dy - renderObject.preferredLineHeight, ); final Rect editingRegion = new Rect.fromPoints( @@ -509,7 +509,7 @@ class _TextSelectionHandleOverlayState extends State<_TextSelectionHandleOverlay child: widget.selectionControls.buildHandle( context, type, - widget.renderObject.size.height / widget.renderObject.maxLines, + widget.renderObject.preferredLineHeight, ), ), ], diff --git a/packages/flutter/test/material/text_field_test.dart b/packages/flutter/test/material/text_field_test.dart index 6b536c3d91..cbe0f46ea6 100644 --- a/packages/flutter/test/material/text_field_test.dart +++ b/packages/flutter/test/material/text_field_test.dart @@ -1664,4 +1664,30 @@ void main() { expect(semantics, includesNodeWith(flags: [SemanticsFlags.isTextField])); }); + + testWidgets('Caret works when maxLines is null', (WidgetTester tester) async { + final TextEditingController controller = new TextEditingController(); + + await tester.pumpWidget( + overlay( + child: new TextField( + controller: controller, + maxLines: null, + ), + ) + ); + + final String testValue = 'x'; + await tester.enterText(find.byType(TextField), testValue); + await skipPastScrollingAnimation(tester); + expect(controller.selection.baseOffset, -1); + + // Tap the selection handle to bring up the "paste / select all" menu. + await tester.tapAt(textOffsetToPosition(tester, 0)); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 200)); // skip past the frame where the opacity is + + // Confirm that the selection was updated. + expect(controller.selection.baseOffset, 0); + }); }