Unverified Commit a5658485 authored by Jason Simmons's avatar Jason Simmons Committed by GitHub

Ignore text selection boxes when assembling semantics for placeholder runs (#70487)

parent 5f56db0a
......@@ -902,38 +902,11 @@ class RenderParagraph extends RenderBox
RenderBox? child = firstChild;
final Queue<SemanticsNode> newChildCache = Queue<SemanticsNode>();
for (final InlineSpanSemanticsInformation info in _combineSemanticsInfo()) {
final TextDirection initialDirection = currentDirection;
final TextSelection selection = TextSelection(
baseOffset: start,
extentOffset: start + info.text.length,
);
final List<ui.TextBox> rects = getBoxesForSelection(selection);
start += info.text.length;
if (rects.isEmpty) {
continue;
}
Rect rect = rects.first.toRect();
currentDirection = rects.first.direction;
for (final ui.TextBox textBox in rects.skip(1)) {
rect = rect.expandToInclude(textBox.toRect());
currentDirection = textBox.direction;
}
// Any of the text boxes may have had infinite dimensions.
// We shouldn't pass infinite dimensions up to the bridges.
rect = Rect.fromLTWH(
math.max(0.0, rect.left),
math.max(0.0, rect.top),
math.min(rect.width, constraints.maxWidth),
math.min(rect.height, constraints.maxHeight),
);
// round the current rectangle to make this API testable and add some
// padding so that the accessibility rects do not overlap with the text.
currentRect = Rect.fromLTRB(
rect.left.floorToDouble() - 4.0,
rect.top.floorToDouble() - 4.0,
rect.right.ceilToDouble() + 4.0,
rect.bottom.ceilToDouble() + 4.0,
);
if (info.isPlaceholder) {
// A placeholder span may have 0 to multple semantics nodes, we need
......@@ -954,6 +927,33 @@ class RenderParagraph extends RenderBox
child = childAfter(child!);
placeholderIndex += 1;
} else {
final TextDirection initialDirection = currentDirection;
final List<ui.TextBox> rects = getBoxesForSelection(selection);
if (rects.isEmpty) {
continue;
}
Rect rect = rects.first.toRect();
currentDirection = rects.first.direction;
for (final ui.TextBox textBox in rects.skip(1)) {
rect = rect.expandToInclude(textBox.toRect());
currentDirection = textBox.direction;
}
// Any of the text boxes may have had infinite dimensions.
// We shouldn't pass infinite dimensions up to the bridges.
rect = Rect.fromLTWH(
math.max(0.0, rect.left),
math.max(0.0, rect.top),
math.min(rect.width, constraints.maxWidth),
math.min(rect.height, constraints.maxHeight),
);
// round the current rectangle to make this API testable and add some
// padding so that the accessibility rects do not overlap with the text.
currentRect = Rect.fromLTRB(
rect.left.floorToDouble() - 4.0,
rect.top.floorToDouble() - 4.0,
rect.right.ceilToDouble() + 4.0,
rect.bottom.ceilToDouble() + 4.0,
);
final SemanticsConfiguration configuration = SemanticsConfiguration()
..sortKey = OrdinalSortKey(ordinal++)
..textDirection = initialDirection
......
......@@ -37,6 +37,26 @@ class RenderParagraphWithEmptySelectionBoxList extends RenderParagraph {
}
}
// A subclass of RenderParagraph that returns an empty list in getBoxesForSelection
// for a selection representing a WidgetSpan.
// This is intended to simulate how SkParagraph's implementation of Paragraph.getBoxesForRange
// can return an empty list for a WidgetSpan with empty dimensions.
class RenderParagraphWithEmptyBoxListForWidgetSpan extends RenderParagraph {
RenderParagraphWithEmptyBoxListForWidgetSpan(
InlineSpan text, {
required List<RenderBox> children,
required TextDirection textDirection,
}) : super(text, children: children, textDirection: textDirection);
@override
List<ui.TextBox> getBoxesForSelection(TextSelection selection) {
if (text.getSpanForPosition(selection.base) is WidgetSpan) {
return <ui.TextBox>[];
}
return super.getBoxesForSelection(selection);
}
}
void main() {
test('getOffsetForCaret control test', () {
final RenderParagraph paragraph = RenderParagraph(
......@@ -588,4 +608,25 @@ void main() {
paragraph.assembleSemanticsNode(node, SemanticsConfiguration(), <SemanticsNode>[]);
expect(node.childrenCount, 2);
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/61020
test('assembleSemanticsNode handles empty WidgetSpans that do not yield selection boxes', () {
final TextSpan text = TextSpan(text: '', children: <InlineSpan>[
TextSpan(text: 'A', recognizer: TapGestureRecognizer()..onTap = () {}),
const WidgetSpan(child: SizedBox(width: 0, height: 0)),
TextSpan(text: 'C', recognizer: TapGestureRecognizer()..onTap = () {}),
]);
final List<RenderBox> renderBoxes = <RenderBox>[
RenderParagraph(const TextSpan(text: 'b'), textDirection: TextDirection.ltr),
];
final RenderParagraph paragraph = RenderParagraphWithEmptyBoxListForWidgetSpan(
text,
children: renderBoxes,
textDirection: TextDirection.ltr,
);
layout(paragraph);
final SemanticsNode node = SemanticsNode();
paragraph.assembleSemanticsNode(node, SemanticsConfiguration(), <SemanticsNode>[]);
expect(node.childrenCount, 2);
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/61020
}
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