assembleSemanticsNode method Null safety
- SemanticsNode node,
- SemanticsConfiguration config,
- Iterable<
SemanticsNode> children
override
Assemble the SemanticsNode for this RenderObject.
If describeSemanticsConfiguration sets
SemanticsConfiguration.isSemanticBoundary to true, this method is called
with the node
created for this RenderObject, the config
to be
applied to that node and the children
SemanticsNodes that descendants
of this RenderObject have generated.
By default, the method will annotate node
with config
and add the
children
to it.
Subclasses can override this method to add additional SemanticsNodes to the tree. If new SemanticsNodes are instantiated in this method they must be disposed in clearSemantics.
Implementation
@override
void assembleSemanticsNode(SemanticsNode node, SemanticsConfiguration config, Iterable<SemanticsNode> children) {
assert(_semanticsInfo != null && _semanticsInfo!.isNotEmpty);
final List<SemanticsNode> newChildren = <SemanticsNode>[];
TextDirection currentDirection = textDirection;
Rect currentRect;
double ordinal = 0.0;
int start = 0;
int placeholderIndex = 0;
int childIndex = 0;
RenderBox? child = firstChild;
final LinkedHashMap<Key, SemanticsNode> newChildCache = LinkedHashMap<Key, SemanticsNode>();
_cachedCombinedSemanticsInfos ??= combineSemanticsInfo(_semanticsInfo!);
for (final InlineSpanSemanticsInformation info in _cachedCombinedSemanticsInfos!) {
final TextSelection selection = TextSelection(
baseOffset: start,
extentOffset: start + info.text.length,
);
start += info.text.length;
if (info.isPlaceholder) {
// A placeholder span may have 0 to multiple semantics nodes, we need
// to annotate all of the semantics nodes belong to this span.
while (children.length > childIndex &&
children.elementAt(childIndex).isTagged(PlaceholderSpanIndexSemanticsTag(placeholderIndex))) {
final SemanticsNode childNode = children.elementAt(childIndex);
final TextParentData parentData = child!.parentData! as TextParentData;
assert(parentData.scale != null || parentData.offset == Offset.zero);
// parentData.scale may be null if the render object is truncated.
if (parentData.scale != null) {
childNode.rect = Rect.fromLTWH(
childNode.rect.left,
childNode.rect.top,
childNode.rect.width * parentData.scale!,
childNode.rect.height * parentData.scale!,
);
newChildren.add(childNode);
}
childIndex += 1;
}
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
..attributedLabel = AttributedString(info.semanticsLabel ?? info.text, attributes: info.stringAttributes);
final GestureRecognizer? recognizer = info.recognizer;
if (recognizer != null) {
if (recognizer is TapGestureRecognizer) {
if (recognizer.onTap != null) {
configuration.onTap = recognizer.onTap;
configuration.isLink = true;
}
} else if (recognizer is DoubleTapGestureRecognizer) {
if (recognizer.onDoubleTap != null) {
configuration.onTap = recognizer.onDoubleTap;
configuration.isLink = true;
}
} else if (recognizer is LongPressGestureRecognizer) {
if (recognizer.onLongPress != null) {
configuration.onLongPress = recognizer.onLongPress;
}
} else {
assert(false, '${recognizer.runtimeType} is not supported.');
}
}
if (node.parentPaintClipRect != null) {
final Rect paintRect = node.parentPaintClipRect!.intersect(currentRect);
configuration.isHidden = paintRect.isEmpty && !currentRect.isEmpty;
}
late final SemanticsNode newChild;
if (_cachedChildNodes?.isNotEmpty ?? false) {
newChild = _cachedChildNodes!.remove(_cachedChildNodes!.keys.first)!;
} else {
final UniqueKey key = UniqueKey();
newChild = SemanticsNode(
key: key,
showOnScreen: _createShowOnScreenFor(key),
);
}
newChild
..updateWith(config: configuration)
..rect = currentRect;
newChildCache[newChild.key!] = newChild;
newChildren.add(newChild);
}
}
// Makes sure we annotated all of the semantics children.
assert(childIndex == children.length);
assert(child == null);
_cachedChildNodes = newChildCache;
node.updateWith(config: config, childrenInInversePaintOrder: newChildren);
}