Unverified Commit bc5ea0a0 authored by Michael Goderbauer's avatar Michael Goderbauer Committed by GitHub

Reland "Do not rebuild Routes when a new opaque Route is pushed on top" (#49376)

parent c341d4b7
......@@ -425,7 +425,8 @@ class RenderStack extends RenderBox
}
}
double _getIntrinsicDimension(double mainChildSizeGetter(RenderBox child)) {
/// Helper function for calculating the intrinsics metrics of a Stack.
static double getIntrinsicDimension(RenderBox firstChild, double mainChildSizeGetter(RenderBox child)) {
double extent = 0.0;
RenderBox child = firstChild;
while (child != null) {
......@@ -440,22 +441,22 @@ class RenderStack extends RenderBox
@override
double computeMinIntrinsicWidth(double height) {
return _getIntrinsicDimension((RenderBox child) => child.getMinIntrinsicWidth(height));
return getIntrinsicDimension(firstChild, (RenderBox child) => child.getMinIntrinsicWidth(height));
}
@override
double computeMaxIntrinsicWidth(double height) {
return _getIntrinsicDimension((RenderBox child) => child.getMaxIntrinsicWidth(height));
return getIntrinsicDimension(firstChild, (RenderBox child) => child.getMaxIntrinsicWidth(height));
}
@override
double computeMinIntrinsicHeight(double width) {
return _getIntrinsicDimension((RenderBox child) => child.getMinIntrinsicHeight(width));
return getIntrinsicDimension(firstChild, (RenderBox child) => child.getMinIntrinsicHeight(width));
}
@override
double computeMaxIntrinsicHeight(double width) {
return _getIntrinsicDimension((RenderBox child) => child.getMaxIntrinsicHeight(width));
return getIntrinsicDimension(firstChild, (RenderBox child) => child.getMaxIntrinsicHeight(width));
}
@override
......@@ -463,6 +464,57 @@ class RenderStack extends RenderBox
return defaultComputeDistanceToHighestActualBaseline(baseline);
}
/// Lays out the positioned `child` according to `alignment` within a Stack of `size`.
///
/// Returns true when the child has visual overflow.
static bool layoutPositionedChild(RenderBox child, StackParentData childParentData, Size size, Alignment alignment) {
assert(childParentData.isPositioned);
assert(child.parentData == childParentData);
bool hasVisualOverflow = false;
BoxConstraints childConstraints = const BoxConstraints();
if (childParentData.left != null && childParentData.right != null)
childConstraints = childConstraints.tighten(width: size.width - childParentData.right - childParentData.left);
else if (childParentData.width != null)
childConstraints = childConstraints.tighten(width: childParentData.width);
if (childParentData.top != null && childParentData.bottom != null)
childConstraints = childConstraints.tighten(height: size.height - childParentData.bottom - childParentData.top);
else if (childParentData.height != null)
childConstraints = childConstraints.tighten(height: childParentData.height);
child.layout(childConstraints, parentUsesSize: true);
double x;
if (childParentData.left != null) {
x = childParentData.left;
} else if (childParentData.right != null) {
x = size.width - childParentData.right - child.size.width;
} else {
x = alignment.alongOffset(size - child.size as Offset).dx;
}
if (x < 0.0 || x + child.size.width > size.width)
hasVisualOverflow = true;
double y;
if (childParentData.top != null) {
y = childParentData.top;
} else if (childParentData.bottom != null) {
y = size.height - childParentData.bottom - child.size.height;
} else {
y = alignment.alongOffset(size - child.size as Offset).dy;
}
if (y < 0.0 || y + child.size.height > size.height)
hasVisualOverflow = true;
childParentData.offset = Offset(x, y);
return hasVisualOverflow;
}
@override
void performLayout() {
_resolve();
......@@ -527,45 +579,7 @@ class RenderStack extends RenderBox
if (!childParentData.isPositioned) {
childParentData.offset = _resolvedAlignment.alongOffset(size - child.size as Offset);
} else {
BoxConstraints childConstraints = const BoxConstraints();
if (childParentData.left != null && childParentData.right != null)
childConstraints = childConstraints.tighten(width: size.width - childParentData.right - childParentData.left);
else if (childParentData.width != null)
childConstraints = childConstraints.tighten(width: childParentData.width);
if (childParentData.top != null && childParentData.bottom != null)
childConstraints = childConstraints.tighten(height: size.height - childParentData.bottom - childParentData.top);
else if (childParentData.height != null)
childConstraints = childConstraints.tighten(height: childParentData.height);
child.layout(childConstraints, parentUsesSize: true);
double x;
if (childParentData.left != null) {
x = childParentData.left;
} else if (childParentData.right != null) {
x = size.width - childParentData.right - child.size.width;
} else {
x = _resolvedAlignment.alongOffset(size - child.size as Offset).dx;
}
if (x < 0.0 || x + child.size.width > size.width)
_hasVisualOverflow = true;
double y;
if (childParentData.top != null) {
y = childParentData.top;
} else if (childParentData.bottom != null) {
y = size.height - childParentData.bottom - child.size.height;
} else {
y = _resolvedAlignment.alongOffset(size - child.size as Offset).dy;
}
if (y < 0.0 || y + child.size.height > size.height)
_hasVisualOverflow = true;
childParentData.offset = Offset(x, y);
_hasVisualOverflow = layoutPositionedChild(child, childParentData, size, _resolvedAlignment) || _hasVisualOverflow;
}
assert(child.parentData == childParentData);
......
......@@ -72,12 +72,9 @@ CupertinoPageScaffold scaffoldForNavBar(Widget navBar) {
}
Finder flying(WidgetTester tester, Finder finder) {
final RenderObjectWithChildMixin<RenderStack> theater =
tester.renderObject(find.byType(Overlay));
final RenderStack theaterStack = theater.child;
final ContainerRenderObjectMixin<RenderBox, StackParentData> theater = tester.renderObject(find.byType(Overlay));
final Finder lastOverlayFinder = find.byElementPredicate((Element element) {
return element is RenderObjectElement &&
element.renderObject == theaterStack.lastChild;
return element is RenderObjectElement && element.renderObject == theater.lastChild;
});
assert(
......
......@@ -132,8 +132,8 @@ void main() {
' Offstage\n'
' _ModalScopeStatus\n'
' _ModalScope<dynamic>-[LabeledGlobalKey<_ModalScopeState<dynamic>>#969b7]\n'
' _OverlayEntry-[LabeledGlobalKey<_OverlayEntryState>#7a3ae]\n'
' Stack\n'
' TickerMode\n'
' _OverlayEntryWidget-[LabeledGlobalKey<_OverlayEntryWidgetState>#545d0]\n'
' _Theatre\n'
' Overlay-[LabeledGlobalKey<OverlayState>#31a52]\n'
' _FocusMarker\n'
......
......@@ -524,23 +524,27 @@ void main() {
expect(errorDetails, isNotNull);
expect(errorDetails.stack, isNotNull);
// Check the ErrorDetails without the stack trace
final List<String> lines = errorDetails.toString().split('\n');
final String fullErrorMessage = errorDetails.toString();
final List<String> lines = fullErrorMessage.split('\n');
// The lines in the middle of the error message contain the stack trace
// which will change depending on where the test is run.
expect(lines.length, greaterThan(7));
expect(
lines.take(8).join('\n'),
equalsIgnoringHashCodes(
'══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞════════════════════════\n'
'The following assertion was thrown building Stepper(dirty,\n'
'dependencies: [_LocalizationsScope-[GlobalKey#00000]], state:\n'
'_StepperState#00000):\n'
'Steppers must not be nested.\n'
'The material specification advises that one should avoid\n'
'embedding steppers within steppers.\n'
'https://material.io/archive/guidelines/components/steppers.html#steppers-usage'
),
);
final String errorMessage = lines.takeWhile(
(String line) => line != '',
).join('\n');
expect(errorMessage.length, lessThan(fullErrorMessage.length));
expect(errorMessage, startsWith(
'══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞════════════════════════\n'
'The following assertion was thrown building Stepper('
));
// The description string of the stepper looks slightly different depending
// on the platform and is omitted here.
expect(errorMessage, endsWith(
'):\n'
'Steppers must not be nested.\n'
'The material specification advises that one should avoid\n'
'embedding steppers within steppers.\n'
'https://material.io/archive/guidelines/components/steppers.html#steppers-usage'
));
});
///https://github.com/flutter/flutter/issues/16920
......
......@@ -1465,6 +1465,33 @@ void main() {
expect(find.byKey(const ValueKey<String>('/A/B')), findsNothing); // popped
expect(find.byKey(const ValueKey<String>('/C')), findsOneWidget);
});
testWidgets('Pushing opaque Route does not rebuild routes below', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/45797.
final GlobalKey<NavigatorState> navigator = GlobalKey<NavigatorState>();
final Key bottomRoute = UniqueKey();
final Key topRoute = UniqueKey();
await tester.pumpWidget(
MaterialApp(
navigatorKey: navigator,
routes: <String, WidgetBuilder>{
'/' : (BuildContext context) => StatefulTestWidget(key: bottomRoute),
'/a': (BuildContext context) => StatefulTestWidget(key: topRoute),
},
),
);
expect(tester.state<StatefulTestState>(find.byKey(bottomRoute)).rebuildCount, 1);
navigator.currentState.pushNamed('/a');
await tester.pumpAndSettle();
// Bottom route is offstage and did not rebuild.
expect(find.byKey(bottomRoute), findsNothing);
expect(tester.state<StatefulTestState>(find.byKey(bottomRoute, skipOffstage: false)).rebuildCount, 1);
expect(tester.state<StatefulTestState>(find.byKey(topRoute)).rebuildCount, 1);
});
}
class NoAnimationPageRoute extends PageRouteBuilder<void> {
......@@ -1478,3 +1505,20 @@ class NoAnimationPageRoute extends PageRouteBuilder<void> {
return super.createAnimationController()..value = 1.0;
}
}
class StatefulTestWidget extends StatefulWidget {
const StatefulTestWidget({Key key}) : super(key: key);
@override
State<StatefulTestWidget> createState() => StatefulTestState();
}
class StatefulTestState extends State<StatefulTestWidget> {
int rebuildCount = 0;
@override
Widget build(BuildContext context) {
rebuildCount += 1;
return Container();
}
}
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