From c53501d83562bc983e1e8ff31e1e8eaa474f2049 Mon Sep 17 00:00:00 2001 From: Callum Moffat Date: Tue, 3 Jan 2023 23:00:08 -0500 Subject: [PATCH] Send text direction in selection rects (#117436) --- .../flutter/lib/src/rendering/editable.dart | 11 +++++++--- .../flutter/lib/src/services/text_input.dart | 21 ++++++++++++++++--- .../lib/src/widgets/editable_text.dart | 12 +++++------ .../test/services/text_input_test.dart | 12 ++++++++++- 4 files changed, 43 insertions(+), 13 deletions(-) diff --git a/packages/flutter/lib/src/rendering/editable.dart b/packages/flutter/lib/src/rendering/editable.dart index d6fad8c988b2..e99b0825338c 100644 --- a/packages/flutter/lib/src/rendering/editable.dart +++ b/packages/flutter/lib/src/rendering/editable.dart @@ -1316,11 +1316,16 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin, /// Returns a list of rects that bound the given selection. /// /// See [TextPainter.getBoxesForSelection] for more details. - List getBoxesForSelection(TextSelection selection) { + List getBoxesForSelection(TextSelection selection) { _computeTextMetricsIfNeeded(); return _textPainter.getBoxesForSelection(selection) - .map((TextBox textBox) => textBox.toRect().shift(_paintOffset)) - .toList(); + .map((TextBox textBox) => TextBox.fromLTRBD( + textBox.left + _paintOffset.dx, + textBox.top + _paintOffset.dy, + textBox.right + _paintOffset.dx, + textBox.bottom + _paintOffset.dy, + textBox.direction + )).toList(); } @override diff --git a/packages/flutter/lib/src/services/text_input.dart b/packages/flutter/lib/src/services/text_input.dart index b2ccef4320b6..3506da6a1b00 100644 --- a/packages/flutter/lib/src/services/text_input.dart +++ b/packages/flutter/lib/src/services/text_input.dart @@ -1211,7 +1211,11 @@ abstract class ScribbleClient { class SelectionRect { /// Constructor for creating a [SelectionRect] from a text [position] and /// [bounds]. - const SelectionRect({required this.position, required this.bounds}); + const SelectionRect({ + required this.position, + required this.bounds, + this.direction = TextDirection.ltr, + }); /// The position of this selection rect within the text String. final int position; @@ -1220,6 +1224,9 @@ class SelectionRect { /// currently focused [RenderEditable]'s coordinate space. final Rect bounds; + /// The direction text flows within this selection rect. + final TextDirection direction; + @override bool operator ==(Object other) { if (identical(this, other)) { @@ -1230,7 +1237,8 @@ class SelectionRect { } return other is SelectionRect && other.position == position - && other.bounds == bounds; + && other.bounds == bounds + && other.direction == direction; } @override @@ -2321,7 +2329,14 @@ class _PlatformTextInputControl with TextInputControl { _channel.invokeMethod( 'TextInput.setSelectionRects', selectionRects.map((SelectionRect rect) { - return [rect.bounds.left, rect.bounds.top, rect.bounds.width, rect.bounds.height, rect.position]; + return [ + rect.bounds.left, + rect.bounds.top, + rect.bounds.width, + rect.bounds.height, + rect.position, + rect.direction.index, + ]; }).toList(), ); } diff --git a/packages/flutter/lib/src/widgets/editable_text.dart b/packages/flutter/lib/src/widgets/editable_text.dart index f5698a1a67d4..1d83b559e6e6 100644 --- a/packages/flutter/lib/src/widgets/editable_text.dart +++ b/packages/flutter/lib/src/widgets/editable_text.dart @@ -3279,7 +3279,7 @@ class EditableTextState extends State with AutomaticKeepAliveClien if (selection.isCollapsed) { rectToReveal = targetOffset.rect; } else { - final List selectionBoxes = renderEditable.getBoxesForSelection(selection); + final List selectionBoxes = renderEditable.getBoxesForSelection(selection); // selectionBoxes may be empty if, for example, the selection does not // encompass a full character, like if it only contained part of an // extended grapheme cluster. @@ -3287,7 +3287,7 @@ class EditableTextState extends State with AutomaticKeepAliveClien rectToReveal = targetOffset.rect; } else { rectToReveal = selection.baseOffset < selection.extentOffset ? - selectionBoxes.last : selectionBoxes.first; + selectionBoxes.last.toRect() : selectionBoxes.first.toRect(); } } @@ -3590,11 +3590,11 @@ class EditableTextState extends State with AutomaticKeepAliveClien final CharacterRange characterRange = CharacterRange(plainText); while (characterRange.moveNext()) { final int graphemeEnd = graphemeStart + characterRange.current.length; - final List boxes = renderEditable.getBoxesForSelection( + final List boxes = renderEditable.getBoxesForSelection( TextSelection(baseOffset: graphemeStart, extentOffset: graphemeEnd), ); - final Rect? box = boxes.isEmpty ? null : boxes.first; + final TextBox? box = boxes.isEmpty ? null : boxes.first; if (box != null) { final Rect paintBounds = renderEditable.paintBounds; // Stop early when characters are already below the bottom edge of the @@ -3602,8 +3602,8 @@ class EditableTextState extends State with AutomaticKeepAliveClien if (paintBounds.bottom <= box.top) { break; } - if (paintBounds.contains(box.topLeft) || paintBounds.contains(box.bottomRight)) { - rects.add(SelectionRect(position: graphemeStart, bounds: box)); + if (paintBounds.contains(Offset(box.left, box.top)) || paintBounds.contains(Offset(box.right, box.bottom))) { + rects.add(SelectionRect(position: graphemeStart, bounds: box.toRect(), direction: box.direction)); } } graphemeStart = graphemeEnd; diff --git a/packages/flutter/test/services/text_input_test.dart b/packages/flutter/test/services/text_input_test.dart index a1be567feb1f..254e7b714452 100644 --- a/packages/flutter/test/services/text_input_test.dart +++ b/packages/flutter/test/services/text_input_test.dart @@ -906,10 +906,20 @@ void main() { expect(fakeTextChannel.outgoingCalls.length, 6); expect(fakeTextChannel.outgoingCalls.last.method, 'TextInput.setEditableSizeAndTransform'); - connection.setSelectionRects(const [SelectionRect(position: 0, bounds: Rect.zero)]); + connection.setSelectionRects(const [SelectionRect(position: 1, bounds: Rect.fromLTWH(2, 3, 4, 5), direction: TextDirection.rtl)]); expectedMethodCalls.add('setSelectionRects'); expect(control.methodCalls, expectedMethodCalls); expect(fakeTextChannel.outgoingCalls.length, 7); + expect(fakeTextChannel.outgoingCalls.last.arguments, const TypeMatcher>>()); + final List> sentList = fakeTextChannel.outgoingCalls.last.arguments as List>; + expect(sentList.length, 1); + expect(sentList[0].length, 6); + expect(sentList[0][0], 2); // left + expect(sentList[0][1], 3); // top + expect(sentList[0][2], 4); // width + expect(sentList[0][3], 5); // height + expect(sentList[0][4], 1); // position + expect(sentList[0][5], TextDirection.rtl.index); // direction expect(fakeTextChannel.outgoingCalls.last.method, 'TextInput.setSelectionRects'); connection.setStyle(