Fix inline text span semantics (#34434)
This commit is contained in:
parent
300e8f842b
commit
71b9d461c8
@ -2,6 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:math' as math;
|
||||
import 'dart:ui' as ui show Gradient, Shader, TextBox, PlaceholderAlignment;
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
@ -766,12 +767,23 @@ class RenderParagraph extends RenderBox
|
||||
final TextDirection initialDirection = currentDirection;
|
||||
final TextSelection selection = TextSelection(baseOffset: start, extentOffset: end);
|
||||
final List<ui.TextBox> rects = getBoxesForSelection(selection);
|
||||
Rect rect;
|
||||
for (ui.TextBox textBox in rects) {
|
||||
rect ??= textBox.toRect();
|
||||
if (rects.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
Rect rect = rects.first.toRect();
|
||||
currentDirection = rects.first.direction;
|
||||
for (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.
|
||||
// TODO(jonahwilliams): implement this for all text accessibility rects.
|
||||
@ -798,12 +810,18 @@ class RenderParagraph extends RenderBox
|
||||
if (current != start) {
|
||||
final SemanticsNode node = SemanticsNode();
|
||||
final SemanticsConfiguration configuration = buildSemanticsConfig(current, start);
|
||||
if (configuration == null) {
|
||||
continue;
|
||||
}
|
||||
node.updateWith(config: configuration);
|
||||
node.rect = currentRect;
|
||||
newChildren.add(node);
|
||||
}
|
||||
final dynamic inlineElement = _inlineSemanticsElements[j];
|
||||
final SemanticsConfiguration configuration = buildSemanticsConfig(start, end);
|
||||
if (configuration == null) {
|
||||
continue;
|
||||
}
|
||||
if (inlineElement != null) {
|
||||
// Add semantics for this recognizer.
|
||||
final SemanticsNode node = SemanticsNode();
|
||||
@ -842,9 +860,11 @@ class RenderParagraph extends RenderBox
|
||||
if (current < rawLabel.length) {
|
||||
final SemanticsNode node = SemanticsNode();
|
||||
final SemanticsConfiguration configuration = buildSemanticsConfig(current, rawLabel.length);
|
||||
node.updateWith(config: configuration);
|
||||
node.rect = currentRect;
|
||||
newChildren.add(node);
|
||||
if (configuration != null) {
|
||||
node.updateWith(config: configuration);
|
||||
node.rect = currentRect;
|
||||
newChildren.add(node);
|
||||
}
|
||||
}
|
||||
node.updateWith(config: config, childrenInInversePaintOrder: newChildren);
|
||||
}
|
||||
|
@ -1162,6 +1162,7 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
|
||||
Rect _rect = Rect.zero;
|
||||
set rect(Rect value) {
|
||||
assert(value != null);
|
||||
assert(value.isFinite, '$this (with $owner) tried to set a non-finite rect.');
|
||||
if (_rect != value) {
|
||||
_rect = value;
|
||||
_markDirty();
|
||||
@ -2178,6 +2179,7 @@ class _BoxEdge implements Comparable<_BoxEdge> {
|
||||
@required this.node,
|
||||
}) : assert(isLeadingEdge != null),
|
||||
assert(offset != null),
|
||||
assert(offset.isFinite),
|
||||
assert(node != null);
|
||||
|
||||
/// True if the edge comes before the seconds edge along the traversal
|
||||
@ -2384,6 +2386,7 @@ Offset _pointInParentCoordinates(SemanticsNode node, Offset point) {
|
||||
List<SemanticsNode> _childrenInDefaultOrder(List<SemanticsNode> children, TextDirection textDirection) {
|
||||
final List<_BoxEdge> edges = <_BoxEdge>[];
|
||||
for (SemanticsNode child in children) {
|
||||
assert(child.rect.isFinite);
|
||||
// Using a small delta to shrink child rects removes overlapping cases.
|
||||
final Rect childRect = child.rect.deflate(0.1);
|
||||
edges.add(_BoxEdge(
|
||||
|
@ -185,6 +185,45 @@ void main() {
|
||||
semantics.dispose();
|
||||
});
|
||||
|
||||
testWidgets('recognizers split semantic node when TextSpan overflows', (WidgetTester tester) async {
|
||||
final SemanticsTester semantics = SemanticsTester(tester);
|
||||
const TextStyle textStyle = TextStyle(fontFamily: 'Ahem');
|
||||
await tester.pumpWidget(
|
||||
SizedBox(
|
||||
height: 10,
|
||||
child: Text.rich(
|
||||
TextSpan(
|
||||
children: <TextSpan>[
|
||||
const TextSpan(text: '\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n'),
|
||||
TextSpan(text: 'world', recognizer: TapGestureRecognizer()..onTap = () { }),
|
||||
],
|
||||
style: textStyle,
|
||||
),
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
),
|
||||
);
|
||||
final TestSemantics expectedSemantics = TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
TestSemantics.rootChild(
|
||||
children: <TestSemantics>[
|
||||
TestSemantics(
|
||||
label: '\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
TestSemantics(
|
||||
label: 'world',
|
||||
textDirection: TextDirection.ltr,
|
||||
actions: <SemanticsAction>[SemanticsAction.tap]
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
expect(semantics, hasSemantics(expectedSemantics, ignoreTransform: true, ignoreId: true, ignoreRect: true));
|
||||
semantics.dispose();
|
||||
});
|
||||
|
||||
testWidgets('recognizers split semantic nodes with text span labels', (WidgetTester tester) async {
|
||||
final SemanticsTester semantics = SemanticsTester(tester);
|
||||
const TextStyle textStyle = TextStyle(fontFamily: 'Ahem');
|
||||
|
Loading…
x
Reference in New Issue
Block a user