diff --git a/packages/flutter/lib/src/rendering/editable.dart b/packages/flutter/lib/src/rendering/editable.dart index b62c2b3eaa..d266d0b6a1 100644 --- a/packages/flutter/lib/src/rendering/editable.dart +++ b/packages/flutter/lib/src/rendering/editable.dart @@ -161,16 +161,6 @@ class VerticalCaretMovementRun extends BidirectionalIterator { return _isValid; } - // Computes the vertical distance from the `from` line's bottom to the `to` - // lines top. - double _lineDistance(int from, int to) { - double lineHeight = 0; - for (int index = from + 1; index < to; index += 1) { - lineHeight += _lineMetrics[index].height; - } - return lineHeight; - } - final Map> _positionCache = >{}; MapEntry _getTextPositionForLine(int lineNumber) { @@ -181,11 +171,8 @@ class VerticalCaretMovementRun extends BidirectionalIterator { return cachedPosition; } assert(lineNumber != _currentLine); - final double distanceY = lineNumber > _currentLine - ? _lineMetrics[_currentLine].descent + _lineMetrics[lineNumber].ascent + _lineDistance(_currentLine, lineNumber) - : - _lineMetrics[_currentLine].ascent - _lineMetrics[lineNumber].descent - _lineDistance(lineNumber, _currentLine); - final Offset newOffset = _currentOffset.translate(0, distanceY); + final Offset newOffset = Offset(_currentOffset.dx, _lineMetrics[lineNumber].baseline); final TextPosition closestPosition = _editable._textPainter.getPositionForOffset(newOffset); final MapEntry position = MapEntry(newOffset, closestPosition); _positionCache[lineNumber] = position; @@ -2418,17 +2405,16 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin, // TODO(LongCatIsLooong): include line boundaries information in // ui.LineMetrics, then we can get rid of this. final Offset offset = _textPainter.getOffsetForCaret(startPosition, Rect.zero); - int line = 0; - double accumulatedHeight = 0; for (final ui.LineMetrics lineMetrics in metrics) { - if (accumulatedHeight + lineMetrics.height > offset.dy) { - return MapEntry(line, Offset(offset.dx, lineMetrics.baseline)); + if (lineMetrics.baseline + lineMetrics.descent > offset.dy) { + return MapEntry(lineMetrics.lineNumber, Offset(offset.dx, lineMetrics.baseline)); } - line += 1; - accumulatedHeight += lineMetrics.height; } assert(false, 'unable to find the line for $startPosition'); - return MapEntry(math.max(0, metrics.length - 1), Offset(offset.dx, accumulatedHeight)); + return MapEntry( + math.max(0, metrics.length - 1), + Offset(offset.dx, metrics.isNotEmpty ? metrics.last.baseline + metrics.last.descent : 0.0), + ); } /// Starts a [VerticalCaretMovementRun] at the given location in the text, for diff --git a/packages/flutter/test/widgets/editable_text_shortcuts_tests.dart b/packages/flutter/test/widgets/editable_text_shortcuts_tests.dart index 9b08d92450..6941c25a37 100644 --- a/packages/flutter/test/widgets/editable_text_shortcuts_tests.dart +++ b/packages/flutter/test/widgets/editable_text_shortcuts_tests.dart @@ -57,7 +57,12 @@ void main() { final TextEditingController controller = TextEditingController(text: testText); final FocusNode focusNode = FocusNode(); - Widget buildEditableText({ TextAlign textAlign = TextAlign.left, bool readOnly = false, bool obscured = false }) { + Widget buildEditableText({ + TextAlign textAlign = TextAlign.left, + bool readOnly = false, + bool obscured = false, + TextStyle style = const TextStyle(fontSize: 10.0), + }) { return MaterialApp( home: Align( alignment: Alignment.topLeft, @@ -69,7 +74,7 @@ void main() { showSelectionHandles: true, autofocus: true, focusNode: focusNode, - style: const TextStyle(fontSize: 10), + style: style, textScaleFactor: 1, // Avoid the cursor from taking up width. cursorWidth: 0, @@ -1593,6 +1598,32 @@ void main() { offset: 3, // Would have been 4 if the run wasn't interrupted. )); }, variant: TargetPlatformVariant.all()); + + testWidgets('long run with fractional text height', (WidgetTester tester) async { + controller.text = "${'źdźbło\n' * 49}źdźbło"; + controller.selection = const TextSelection.collapsed(offset: 2); + await tester.pumpWidget(buildEditableText(style: const TextStyle(fontSize: 13.0, height: 1.17))); + + for (int i = 1; i <= 49; i++) { + await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowDown)); + await tester.pump(); + expect( + controller.selection, + TextSelection.collapsed(offset: 2 + i * 7), + reason: 'line $i', + ); + } + + for (int i = 49; i >= 1; i--) { + await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowUp)); + await tester.pump(); + expect( + controller.selection, + TextSelection.collapsed(offset: 2 + (i - 1) * 7), + reason: 'line $i', + ); + } + }, variant: TargetPlatformVariant.all()); }); }); },