Skip to content

Commit

Permalink
Send text direction in selection rects (flutter#117436)
Browse files Browse the repository at this point in the history
  • Loading branch information
moffatman committed Jan 4, 2023
1 parent e599e5c commit c53501d
Show file tree
Hide file tree
Showing 4 changed files with 43 additions and 13 deletions.
11 changes: 8 additions & 3 deletions packages/flutter/lib/src/rendering/editable.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<Rect> getBoxesForSelection(TextSelection selection) {
List<TextBox> 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
Expand Down
21 changes: 18 additions & 3 deletions packages/flutter/lib/src/services/text_input.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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)) {
Expand All @@ -1230,7 +1237,8 @@ class SelectionRect {
}
return other is SelectionRect
&& other.position == position
&& other.bounds == bounds;
&& other.bounds == bounds
&& other.direction == direction;
}

@override
Expand Down Expand Up @@ -2321,7 +2329,14 @@ class _PlatformTextInputControl with TextInputControl {
_channel.invokeMethod<void>(
'TextInput.setSelectionRects',
selectionRects.map((SelectionRect rect) {
return <num>[rect.bounds.left, rect.bounds.top, rect.bounds.width, rect.bounds.height, rect.position];
return <num>[
rect.bounds.left,
rect.bounds.top,
rect.bounds.width,
rect.bounds.height,
rect.position,
rect.direction.index,
];
}).toList(),
);
}
Expand Down
12 changes: 6 additions & 6 deletions packages/flutter/lib/src/widgets/editable_text.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3279,15 +3279,15 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
if (selection.isCollapsed) {
rectToReveal = targetOffset.rect;
} else {
final List<Rect> selectionBoxes = renderEditable.getBoxesForSelection(selection);
final List<TextBox> 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.
if (selectionBoxes.isEmpty) {
rectToReveal = targetOffset.rect;
} else {
rectToReveal = selection.baseOffset < selection.extentOffset ?
selectionBoxes.last : selectionBoxes.first;
selectionBoxes.last.toRect() : selectionBoxes.first.toRect();
}
}

Expand Down Expand Up @@ -3590,20 +3590,20 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
final CharacterRange characterRange = CharacterRange(plainText);
while (characterRange.moveNext()) {
final int graphemeEnd = graphemeStart + characterRange.current.length;
final List<Rect> boxes = renderEditable.getBoxesForSelection(
final List<TextBox> 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
// RenderEditable, regardless of its clipBehavior.
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;
Expand Down
12 changes: 11 additions & 1 deletion packages/flutter/test/services/text_input_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -906,10 +906,20 @@ void main() {
expect(fakeTextChannel.outgoingCalls.length, 6);
expect(fakeTextChannel.outgoingCalls.last.method, 'TextInput.setEditableSizeAndTransform');

connection.setSelectionRects(const <SelectionRect>[SelectionRect(position: 0, bounds: Rect.zero)]);
connection.setSelectionRects(const <SelectionRect>[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<List<List<num>>>());
final List<List<num>> sentList = fakeTextChannel.outgoingCalls.last.arguments as List<List<num>>;
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(
Expand Down

0 comments on commit c53501d

Please sign in to comment.