Skip to content

Commit af129b6

Browse files
OverlayPortal.overlayChild contributes semantics to OverlayPortal instead of Overlay (flutter#134921)
Fixes flutter#134456
1 parent 6dc5d2f commit af129b6

File tree

6 files changed

+544
-141
lines changed

6 files changed

+544
-141
lines changed

packages/flutter/lib/src/material/tooltip.dart

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -901,17 +901,20 @@ class _TooltipOverlay extends StatelessWidget {
901901
constraints: BoxConstraints(minHeight: height),
902902
child: DefaultTextStyle(
903903
style: Theme.of(context).textTheme.bodyMedium!,
904-
child: Container(
905-
decoration: decoration,
906-
padding: padding,
907-
margin: margin,
908-
child: Center(
909-
widthFactor: 1.0,
910-
heightFactor: 1.0,
911-
child: Text.rich(
912-
richMessage,
913-
style: textStyle,
914-
textAlign: textAlign,
904+
child: Semantics(
905+
container: true,
906+
child: Container(
907+
decoration: decoration,
908+
padding: padding,
909+
margin: margin,
910+
child: Center(
911+
widthFactor: 1.0,
912+
heightFactor: 1.0,
913+
child: Text.rich(
914+
richMessage,
915+
style: textStyle,
916+
textAlign: textAlign,
917+
),
915918
),
916919
),
917920
),

packages/flutter/lib/src/rendering/object.dart

Lines changed: 118 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -3531,7 +3531,7 @@ abstract class RenderObject with DiagnosticableTreeMixin implements HitTestTarge
35313531
// node, thus marking this semantics boundary dirty is not enough, it needs
35323532
// to find the first parent semantics boundary that does not have any
35333533
// possible sibling node.
3534-
while (node.parent is RenderObject && (mayProduceSiblingNodes || !isEffectiveSemanticsBoundary)) {
3534+
while (node.parent != null && (mayProduceSiblingNodes || !isEffectiveSemanticsBoundary)) {
35353535
if (node != this && node._needsSemanticsUpdate) {
35363536
break;
35373537
}
@@ -3565,7 +3565,7 @@ abstract class RenderObject with DiagnosticableTreeMixin implements HitTestTarge
35653565
if (!node._needsSemanticsUpdate) {
35663566
node._needsSemanticsUpdate = true;
35673567
if (owner != null) {
3568-
assert(node._semanticsConfiguration.isSemanticBoundary || node.parent is! RenderObject);
3568+
assert(node._semanticsConfiguration.isSemanticBoundary || node.parent == null);
35693569
owner!._nodesNeedingSemantics.add(node);
35703570
owner!.requestVisualUpdate();
35713571
}
@@ -3574,7 +3574,7 @@ abstract class RenderObject with DiagnosticableTreeMixin implements HitTestTarge
35743574

35753575
/// Updates the semantic information of the render object.
35763576
void _updateSemantics() {
3577-
assert(_semanticsConfiguration.isSemanticBoundary || parent is! RenderObject);
3577+
assert(_semanticsConfiguration.isSemanticBoundary || parent == null);
35783578
if (_needsLayout) {
35793579
// There's not enough information in this subtree to compute semantics.
35803580
// The subtree is probably being kept alive by a viewport but not laid out.
@@ -3625,8 +3625,8 @@ abstract class RenderObject with DiagnosticableTreeMixin implements HitTestTarge
36253625
final bool blockChildInteractions = blockUserActions || config.isBlockingUserActions;
36263626
final bool childrenMergeIntoParent = mergeIntoParent || config.isMergingSemanticsOfDescendants;
36273627
final List<SemanticsConfiguration> childConfigurations = <SemanticsConfiguration>[];
3628-
final bool explicitChildNode = config.explicitChildNodes || parent is! RenderObject;
3629-
final bool hasChildConfigurationsDelegate = config.childConfigurationsDelegate != null;
3628+
final bool explicitChildNode = config.explicitChildNodes || parent == null;
3629+
final ChildSemanticsConfigurationsDelegate? childConfigurationsDelegate = config.childConfigurationsDelegate;
36303630
final Map<SemanticsConfiguration, _InterestingSemanticsFragment> configToFragment = <SemanticsConfiguration, _InterestingSemanticsFragment>{};
36313631
final List<_InterestingSemanticsFragment> mergeUpFragments = <_InterestingSemanticsFragment>[];
36323632
final List<List<_InterestingSemanticsFragment>> siblingMergeFragmentGroups = <List<_InterestingSemanticsFragment>>[];
@@ -3650,7 +3650,7 @@ abstract class RenderObject with DiagnosticableTreeMixin implements HitTestTarge
36503650
if (hasTags) {
36513651
fragment.addTags(config.tagsForChildren!);
36523652
}
3653-
if (hasChildConfigurationsDelegate && fragment.config != null) {
3653+
if (childConfigurationsDelegate != null && fragment.config != null) {
36543654
// This fragment need to go through delegate to determine whether it
36553655
// merge up or not.
36563656
childConfigurations.add(fragment.config!);
@@ -3674,14 +3674,14 @@ abstract class RenderObject with DiagnosticableTreeMixin implements HitTestTarge
36743674
}
36753675
});
36763676

3677-
assert(hasChildConfigurationsDelegate || configToFragment.isEmpty);
3677+
assert(childConfigurationsDelegate != null || configToFragment.isEmpty);
36783678

36793679
if (explicitChildNode) {
36803680
for (final _InterestingSemanticsFragment fragment in mergeUpFragments) {
36813681
fragment.markAsExplicit();
36823682
}
3683-
} else if (hasChildConfigurationsDelegate) {
3684-
final ChildSemanticsConfigurationsResult result = config.childConfigurationsDelegate!(childConfigurations);
3683+
} else if (childConfigurationsDelegate != null) {
3684+
final ChildSemanticsConfigurationsResult result = childConfigurationsDelegate(childConfigurations);
36853685
mergeUpFragments.addAll(
36863686
result.mergeUp.map<_InterestingSemanticsFragment>((SemanticsConfiguration config) {
36873687
final _InterestingSemanticsFragment? fragment = configToFragment[config];
@@ -3706,7 +3706,7 @@ abstract class RenderObject with DiagnosticableTreeMixin implements HitTestTarge
37063706
_needsSemanticsUpdate = false;
37073707

37083708
final _SemanticsFragment result;
3709-
if (parent is! RenderObject) {
3709+
if (parent == null) {
37103710
assert(!config.hasBeenAnnotated);
37113711
assert(!mergeIntoParent);
37123712
assert(siblingMergeFragmentGroups.isEmpty);
@@ -4781,26 +4781,14 @@ class _SwitchableSemanticsFragment extends _InterestingSemanticsFragment {
47814781
parentPaintClipRect: parentPaintClipRect,
47824782
)!;
47834783
final Rect fragmentRect = MatrixUtils.transformRect(geometry.transform, geometry.rect);
4784-
if (rect == null) {
4785-
rect = fragmentRect;
4786-
} else {
4787-
rect = rect.expandToInclude(fragmentRect);
4788-
}
4784+
rect = rect?.expandToInclude(fragmentRect) ?? fragmentRect;
47894785
if (geometry.semanticsClipRect != null) {
47904786
final Rect rect = MatrixUtils.transformRect(geometry.transform, geometry.semanticsClipRect!);
4791-
if (semanticsClipRect == null) {
4792-
semanticsClipRect = rect;
4793-
} else {
4794-
semanticsClipRect = semanticsClipRect.intersect(rect);
4795-
}
4787+
semanticsClipRect = semanticsClipRect?.intersect(rect) ?? rect;
47964788
}
47974789
if (geometry.paintClipRect != null) {
47984790
final Rect rect = MatrixUtils.transformRect(geometry.transform, geometry.paintClipRect!);
4799-
if (paintClipRect == null) {
4800-
paintClipRect = rect;
4801-
} else {
4802-
paintClipRect = paintClipRect.intersect(rect);
4803-
}
4791+
paintClipRect = paintClipRect?.intersect(rect) ?? rect;
48044792
}
48054793
if (switchableFragment._tagsForChildren != null) {
48064794
tags.addAll(switchableFragment._tagsForChildren!);
@@ -4891,8 +4879,7 @@ class _SwitchableSemanticsFragment extends _InterestingSemanticsFragment {
48914879
return; // Drop the node, it's not going to be visible.
48924880
}
48934881

4894-
owner._semantics ??= SemanticsNode(showOnScreen: owner.showOnScreen);
4895-
final SemanticsNode node = owner._semantics!
4882+
final SemanticsNode node = (owner._semantics ??= SemanticsNode(showOnScreen: owner.showOnScreen))
48964883
..isMergedIntoParent = _mergeIntoParent
48974884
..tags = _tagsForChildren;
48984885

@@ -5070,24 +5057,45 @@ class _SemanticsGeometry {
50705057
_transform = Matrix4.identity();
50715058
_semanticsClipRect = parentSemanticsClipRect;
50725059
_paintClipRect = parentPaintClipRect;
5060+
50735061
for (int index = ancestors.length-1; index > 0; index -= 1) {
5074-
final RenderObject parent = ancestors[index];
5075-
final RenderObject child = ancestors[index-1];
5076-
final Rect? parentSemanticsClipRect = parent.describeSemanticsClip(child);
5077-
if (parentSemanticsClipRect != null) {
5078-
_semanticsClipRect = parentSemanticsClipRect;
5079-
_paintClipRect = _intersectRects(_paintClipRect, parent.describeApproximatePaintClip(child));
5062+
final RenderObject semanticsParent = ancestors[index];
5063+
final RenderObject semanticsChild = ancestors[index-1];
5064+
_applyIntermediatePaintTransforms(semanticsParent, semanticsChild, _transform);
5065+
5066+
if (identical(semanticsParent, semanticsChild.parent)) {
5067+
// The easier and more common case: semanticsParent is directly
5068+
// responsible for painting (and potentially clipping) the semanticsChild
5069+
// RenderObject.
5070+
_computeClipRect(semanticsParent, semanticsChild, _semanticsClipRect, _paintClipRect);
50805071
} else {
5081-
_semanticsClipRect = _intersectRects(_semanticsClipRect, parent.describeApproximatePaintClip(child));
5072+
// Otherwise we have to find the closest ancestor RenderObject that
5073+
// has up-to-date semantics geometry and compute the clip rects from there.
5074+
//
5075+
// Currently it can only happen when the subtree contains an OverlayPortal.
5076+
final List<RenderObject> clipPath = <RenderObject>[semanticsChild];
5077+
5078+
RenderObject? ancestor = semanticsChild.parent;
5079+
while (ancestor != null && ancestor._semantics == null) {
5080+
clipPath.add(ancestor);
5081+
ancestor = ancestor.parent;
5082+
}
5083+
_paintClipRect = ancestor?._semantics?.parentPaintClipRect;
5084+
_semanticsClipRect = ancestor?._semantics?.parentSemanticsClipRect;
5085+
if (ancestor != null) {
5086+
assert(ancestor._semantics != null);
5087+
assert(!ancestor._needsSemanticsUpdate);
5088+
RenderObject parent = ancestor;
5089+
for (int i = clipPath.length - 1; i >= 0; i -= 1) {
5090+
_computeClipRect(parent, clipPath[i], _semanticsClipRect, _paintClipRect);
5091+
parent = clipPath[i];
5092+
}
5093+
}
50825094
}
5083-
_temporaryTransformHolder.setIdentity(); // clears data from previous call(s)
5084-
_applyIntermediatePaintTransforms(parent, child, _transform, _temporaryTransformHolder);
5085-
_semanticsClipRect = _transformRect(_semanticsClipRect, _temporaryTransformHolder);
5086-
_paintClipRect = _transformRect(_paintClipRect, _temporaryTransformHolder);
50875095
}
50885096

50895097
final RenderObject owner = ancestors.first;
5090-
_rect = _semanticsClipRect == null ? owner.semanticBounds : _semanticsClipRect!.intersect(owner.semanticBounds);
5098+
_rect = _semanticsClipRect?.intersect(owner.semanticBounds) ?? owner.semanticBounds;
50915099
if (_paintClipRect != null) {
50925100
final Rect paintRect = _paintClipRect!.intersect(_rect);
50935101
_markAsHidden = paintRect.isEmpty && !_rect.isEmpty;
@@ -5097,15 +5105,6 @@ class _SemanticsGeometry {
50975105
}
50985106
}
50995107

5100-
// A matrix used to store transient transform data.
5101-
//
5102-
// Reusing this matrix avoids allocating a new matrix every time a temporary
5103-
// matrix is needed.
5104-
//
5105-
// This instance should never be returned to the caller. Otherwise, the data
5106-
// stored in it will be overwritten unpredictably by subsequent reuses.
5107-
static final Matrix4 _temporaryTransformHolder = Matrix4.zero();
5108-
51095108
/// From parent to child coordinate system.
51105109
static Rect? _transformRect(Rect? rect, Matrix4 transform) {
51115110
if (rect == null) {
@@ -5117,36 +5116,88 @@ class _SemanticsGeometry {
51175116
return MatrixUtils.inverseTransformRect(transform, rect);
51185117
}
51195118

5120-
// Calls applyPaintTransform on all of the render objects between [child] and
5121-
// [ancestor]. This method handles cases where the immediate semantic parent
5122-
// is not the immediate render object parent of the child.
5119+
// Computes the paint transform from `childFragmentOwner` to
5120+
// `parentFragmentOwner` and applies the paint transform to `transform` in
5121+
// place.
51235122
//
5124-
// It will mutate both transform and clipRectTransform.
5123+
// The `parentFragmentOwner` and `childFragmentOwner` [RenderObject]s must be
5124+
// in the same render tree (so they have a common ancestor).
51255125
static void _applyIntermediatePaintTransforms(
5126-
RenderObject ancestor,
5127-
RenderObject child,
5126+
RenderObject parentFragmentOwner,
5127+
RenderObject childFragmentOwner,
51285128
Matrix4 transform,
5129-
Matrix4 clipRectTransform,
51305129
) {
5131-
assert(clipRectTransform.isIdentity());
5132-
RenderObject intermediateParent = child.parent!;
5133-
while (intermediateParent != ancestor) {
5134-
intermediateParent.applyPaintTransform(child, transform);
5135-
intermediateParent = intermediateParent.parent!;
5136-
child = child.parent!;
5130+
Matrix4? parentToCommonAncestorTransform;
5131+
RenderObject from = childFragmentOwner;
5132+
RenderObject to = parentFragmentOwner;
5133+
5134+
while (!identical(from, to)) {
5135+
final int fromDepth = from.depth;
5136+
final int toDepth = to.depth;
5137+
5138+
if (fromDepth >= toDepth) {
5139+
assert(from.parent != null, '$parentFragmentOwner and $childFragmentOwner are not in the same render tree.');
5140+
final RenderObject fromParent = from.parent!;
5141+
fromParent.applyPaintTransform(from, transform);
5142+
from = fromParent;
5143+
}
5144+
if (fromDepth <= toDepth) {
5145+
assert(to.parent != null, '$parentFragmentOwner and $childFragmentOwner are not in the same render tree.');
5146+
final RenderObject toParent = to.parent!;
5147+
toParent.applyPaintTransform(to, parentToCommonAncestorTransform ??= Matrix4.identity());
5148+
to = toParent;
5149+
}
5150+
}
5151+
5152+
if (parentToCommonAncestorTransform != null) {
5153+
if (parentToCommonAncestorTransform.invert() != 0) {
5154+
transform.multiply(parentToCommonAncestorTransform);
5155+
} else {
5156+
transform.setZero();
5157+
}
51375158
}
5138-
ancestor.applyPaintTransform(child, transform);
5139-
ancestor.applyPaintTransform(child, clipRectTransform);
51405159
}
51415160

5142-
static Rect? _intersectRects(Rect? a, Rect? b) {
5143-
if (a == null) {
5144-
return b;
5161+
// A matrix used to store transient transform data.
5162+
//
5163+
// Reusing this matrix avoids allocating a new matrix every time a temporary
5164+
// matrix is needed.
5165+
//
5166+
// This instance should never be returned to the caller. Otherwise, the data
5167+
// stored in it will be overwritten unpredictably by subsequent reuses.
5168+
static final Matrix4 _temporaryTransformHolder = Matrix4.zero();
5169+
5170+
// Computes the semantics and painting clip rects for the given child and
5171+
// assigns the rects to _semanticsClipRect and _paintClipRect respectively.
5172+
//
5173+
// The caller must guarantee that child.parent == parent. The resulting rects
5174+
// are in `child`'s coordinate system.
5175+
void _computeClipRect(RenderObject parent, RenderObject child, Rect? parentSemanticsClipRect, Rect? parentPaintClipRect) {
5176+
assert(identical(child.parent, parent));
5177+
// Computes the paint transform from child to parent. The _transformRect
5178+
// method will compute the inverse.
5179+
_temporaryTransformHolder.setIdentity(); // clears data from previous call(s)
5180+
parent.applyPaintTransform(child, _temporaryTransformHolder);
5181+
5182+
final Rect? additionalPaintClip = parent.describeApproximatePaintClip(child);
5183+
_paintClipRect = _transformRect(
5184+
_intersectRects(additionalPaintClip, parentPaintClipRect),
5185+
_temporaryTransformHolder,
5186+
);
5187+
5188+
if (_paintClipRect == null) {
5189+
_semanticsClipRect = null;
5190+
} else {
5191+
final Rect? semanticsClip = parent.describeSemanticsClip(child) ?? _intersectRects(parentSemanticsClipRect, additionalPaintClip);
5192+
_semanticsClipRect = _transformRect(semanticsClip, _temporaryTransformHolder);
51455193
}
5194+
}
5195+
5196+
static Rect? _intersectRects(Rect? a, Rect? b) {
51465197
if (b == null) {
51475198
return a;
51485199
}
5149-
return a.intersect(b);
5200+
return a?.intersect(b) ?? b;
51505201
}
51515202

51525203
/// Whether the [SemanticsNode] annotated with the geometric information tracked

0 commit comments

Comments
 (0)