Unverified Commit af129b61 authored by LongCatIsLooong's avatar LongCatIsLooong Committed by GitHub

`OverlayPortal.overlayChild` contributes semantics to `OverlayPortal` instead...

`OverlayPortal.overlayChild` contributes semantics to `OverlayPortal` instead of `Overlay` (#134921)

Fixes https://github.com/flutter/flutter/issues/134456
parent 6dc5d2fd
......@@ -901,17 +901,20 @@ class _TooltipOverlay extends StatelessWidget {
constraints: BoxConstraints(minHeight: height),
child: DefaultTextStyle(
style: Theme.of(context).textTheme.bodyMedium!,
child: Container(
decoration: decoration,
padding: padding,
margin: margin,
child: Center(
widthFactor: 1.0,
heightFactor: 1.0,
child: Text.rich(
richMessage,
style: textStyle,
textAlign: textAlign,
child: Semantics(
container: true,
child: Container(
decoration: decoration,
padding: padding,
margin: margin,
child: Center(
widthFactor: 1.0,
heightFactor: 1.0,
child: Text.rich(
richMessage,
style: textStyle,
textAlign: textAlign,
),
),
),
),
......
......@@ -923,12 +923,26 @@ class _TheaterParentData extends StackParentData {
// children that are created by an OverlayPortal.
OverlayEntry? overlayEntry;
/// A [OverlayPortal] makes its overlay child a render child of an ancestor
/// [Overlay]. Currently, to make sure the overlay child is painted after its
/// [OverlayPortal], and before the next [OverlayEntry] (which could be
/// something that should obstruct the overlay child, such as a [ModalRoute])
/// in the host [Overlay], the paint order of each overlay child is managed by
/// the [OverlayEntry] that hosts its [OverlayPortal].
///
/// The following methods are exposed to allow easy access to the overlay
/// children's render objects whose order is managed by [overlayEntry], in the
/// right order.
// _overlayStateMounted is set to null in _OverlayEntryWidgetState's dispose
// method. This property is only accessed during layout, paint and hit-test so
// the `value!` should be safe.
Iterator<RenderBox>? get paintOrderIterator => overlayEntry?._overlayEntryStateNotifier?.value!._paintOrderIterable.iterator;
Iterator<RenderBox>? get hitTestOrderIterator => overlayEntry?._overlayEntryStateNotifier?.value!._hitTestOrderIterable.iterator;
void visitChildrenOfOverlayEntry(RenderObjectVisitor visitor) => overlayEntry?._overlayEntryStateNotifier?.value!._paintOrderIterable.forEach(visitor);
// A convenience method for traversing `paintOrderIterator` with a
// [RenderObjectVisitor].
void visitOverlayPortalChildrenOnOverlayEntry(RenderObjectVisitor visitor) => overlayEntry?._overlayEntryStateNotifier?.value!._paintOrderIterable.forEach(visitor);
}
class _RenderTheater extends RenderBox with ContainerRenderObjectMixin<RenderBox, StackParentData>, _RenderTheaterMixin {
......@@ -978,7 +992,7 @@ class _RenderTheater extends RenderBox with ContainerRenderObjectMixin<RenderBox
RenderBox? child = firstChild;
while (child != null) {
final _TheaterParentData childParentData = child.parentData! as _TheaterParentData;
childParentData.visitChildrenOfOverlayEntry(_detachChild);
childParentData.visitOverlayPortalChildrenOnOverlayEntry(_detachChild);
child = childParentData.nextSibling;
}
}
......@@ -1197,7 +1211,7 @@ class _RenderTheater extends RenderBox with ContainerRenderObjectMixin<RenderBox
while (child != null) {
visitor(child);
final _TheaterParentData childParentData = child.parentData! as _TheaterParentData;
childParentData.visitChildrenOfOverlayEntry(visitor);
childParentData.visitOverlayPortalChildrenOnOverlayEntry(visitor);
child = childParentData.nextSibling;
}
}
......@@ -1208,7 +1222,6 @@ class _RenderTheater extends RenderBox with ContainerRenderObjectMixin<RenderBox
while (child != null) {
visitor(child);
final _TheaterParentData childParentData = child.parentData! as _TheaterParentData;
childParentData.visitChildrenOfOverlayEntry(visitor);
child = childParentData.nextSibling;
}
}
......@@ -1264,7 +1277,7 @@ class _RenderTheater extends RenderBox with ContainerRenderObjectMixin<RenderBox
}
int subcount = 1;
childParentData.visitChildrenOfOverlayEntry((RenderObject renderObject) {
childParentData.visitOverlayPortalChildrenOnOverlayEntry((RenderObject renderObject) {
final RenderBox child = renderObject as RenderBox;
if (onstage) {
onstageChildren.add(
......@@ -1468,6 +1481,17 @@ class OverlayPortalController {
/// [OverlayPortalController.show] was called. The last [OverlayPortal] to have
/// called `show` gets to paint its overlay child in the foreground.
///
/// ### Semantics
///
/// The semantics subtree generated by the overlay child is considered attached
/// to [OverlayPortal] instead of the target [Overlay]. An [OverlayPortal]'s
/// semantics subtree can be dropped from the semantics tree due to invisibility
/// while the overlay child is still visible (for example, when the
/// [OverlayPortal] is completely invisible in a [ListView] but kept alive by
/// a [KeepAlive] widget). When this happens the semantics subtree generated by
/// the overlay child is also dropped, even if the overlay child is still visible
/// on screen.
///
/// {@template flutter.widgets.overlayPortalVsOverlayEntry}
/// ### Differences between [OverlayPortal] and [OverlayEntry]
///
......@@ -2028,8 +2052,9 @@ class _DeferredLayout extends SingleChildRenderObjectWidget {
}
}
// A `RenderProxyBox` that defers its layout until its `_layoutSurrogate` is
// laid out.
// A `RenderProxyBox` that defers its layout until its `_layoutSurrogate` (which
// is not necessarily an ancestor of this RenderBox, but shares at least one
// `_RenderTheater` ancestor with this RenderBox) is laid out.
//
// This `RenderObject` must be a child of a `_RenderTheater`. It guarantees that:
//
......@@ -2200,4 +2225,13 @@ class _RenderLayoutSurrogateProxyBox extends RenderProxyBox {
// walk.
_deferredLayoutChild?.layoutByLayoutSurrogate();
}
@override
void visitChildrenForSemantics(RenderObjectVisitor visitor) {
super.visitChildrenForSemantics(visitor);
final _RenderDeferredLayoutBox? deferredChild = _deferredLayoutChild;
if (deferredChild != null) {
visitor(deferredChild);
}
}
}
......@@ -3374,29 +3374,29 @@ void main() {
label: 'ABC',
rect: const Rect.fromLTRB(0.0, 0.0, 88.0, 48.0),
),
],
),
],
),
TestSemantics(
id: 6,
rect: const Rect.fromLTRB(0.0, 0.0, 120.0, 64.0),
children: <TestSemantics>[
TestSemantics(
id: 7,
rect: const Rect.fromLTRB(0.0, 0.0, 120.0, 48.0),
flags: <SemanticsFlag>[SemanticsFlag.hasImplicitScrolling],
children: <TestSemantics>[
TestSemantics(
id: 8,
label: 'Item 0',
rect: const Rect.fromLTRB(0.0, 0.0, 120.0, 48.0),
flags: <SemanticsFlag>[
SemanticsFlag.hasEnabledState,
SemanticsFlag.isEnabled,
SemanticsFlag.isFocusable,
id: 6,
rect: const Rect.fromLTRB(0.0, 0.0, 120.0, 64.0),
children: <TestSemantics>[
TestSemantics(
id: 7,
rect: const Rect.fromLTRB(0.0, 0.0, 120.0, 48.0),
flags: <SemanticsFlag>[SemanticsFlag.hasImplicitScrolling],
children: <TestSemantics>[
TestSemantics(
id: 8,
label: 'Item 0',
rect: const Rect.fromLTRB(0.0, 0.0, 120.0, 48.0),
flags: <SemanticsFlag>[
SemanticsFlag.hasEnabledState,
SemanticsFlag.isEnabled,
SemanticsFlag.isFocusable,
],
actions: <SemanticsAction>[SemanticsAction.tap],
),
],
),
],
actions: <SemanticsAction>[SemanticsAction.tap],
),
],
),
......
......@@ -1480,6 +1480,68 @@ void main() {
semantics.dispose();
});
testWidgets('Tooltip semantics does not merge into child', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
final GlobalKey<TooltipState> tooltipKey = GlobalKey<TooltipState>();
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: Overlay(
initialEntries: <OverlayEntry>[
OverlayEntry(
builder: (BuildContext context) {
return ListView(
children: <Widget>[
const Text('before'),
Tooltip(
key: tooltipKey,
showDuration: const Duration(seconds: 50),
message: 'B',
child: const Text('child'),
),
const Text('after'),
],
);
},
),
],
),
),
);
tooltipKey.currentState?.ensureTooltipVisible();
// Starts the animation.
await tester.pump();
// Make sure the fade in animation has started and the tooltip isn't transparent.
await tester.pump(const Duration(seconds: 2));
expect(semantics, hasSemantics(
TestSemantics.root(
children: <TestSemantics>[
TestSemantics(
children: <TestSemantics>[
TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.hasImplicitScrolling],
children: <TestSemantics>[
TestSemantics(label: 'before'),
TestSemantics(label: 'child', tooltip: 'B', children: <TestSemantics>[TestSemantics(label: 'B')]),
TestSemantics(label: 'after'),
],
),
],
),
],
),
ignoreId: true,
ignoreRect: true,
ignoreTransform: true,
));
semantics.dispose();
});
testWidgetsWithLeakTracking('Tooltip text scales with textScaleFactor', (WidgetTester tester) async {
Widget buildApp(String text, { required double textScaleFactor }) {
return Theme(
......
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