Unverified Commit 52c665d2 authored by Maurice Parrish's avatar Maurice Parrish Committed by GitHub

Revert "Do not rebuild Routes when a new opaque Route is pushed on top (#48900)" (#49366)

This reverts commit 8eecdbe8.
parent a1fa1a3d
...@@ -425,8 +425,7 @@ class RenderStack extends RenderBox ...@@ -425,8 +425,7 @@ class RenderStack extends RenderBox
} }
} }
/// Helper function for calculating the intrinsics metrics of a Stack. double _getIntrinsicDimension(double mainChildSizeGetter(RenderBox child)) {
static double getIntrinsicDimension(RenderBox firstChild, double mainChildSizeGetter(RenderBox child)) {
double extent = 0.0; double extent = 0.0;
RenderBox child = firstChild; RenderBox child = firstChild;
while (child != null) { while (child != null) {
...@@ -441,22 +440,22 @@ class RenderStack extends RenderBox ...@@ -441,22 +440,22 @@ class RenderStack extends RenderBox
@override @override
double computeMinIntrinsicWidth(double height) { double computeMinIntrinsicWidth(double height) {
return getIntrinsicDimension(firstChild, (RenderBox child) => child.getMinIntrinsicWidth(height)); return _getIntrinsicDimension((RenderBox child) => child.getMinIntrinsicWidth(height));
} }
@override @override
double computeMaxIntrinsicWidth(double height) { double computeMaxIntrinsicWidth(double height) {
return getIntrinsicDimension(firstChild, (RenderBox child) => child.getMaxIntrinsicWidth(height)); return _getIntrinsicDimension((RenderBox child) => child.getMaxIntrinsicWidth(height));
} }
@override @override
double computeMinIntrinsicHeight(double width) { double computeMinIntrinsicHeight(double width) {
return getIntrinsicDimension(firstChild, (RenderBox child) => child.getMinIntrinsicHeight(width)); return _getIntrinsicDimension((RenderBox child) => child.getMinIntrinsicHeight(width));
} }
@override @override
double computeMaxIntrinsicHeight(double width) { double computeMaxIntrinsicHeight(double width) {
return getIntrinsicDimension(firstChild, (RenderBox child) => child.getMaxIntrinsicHeight(width)); return _getIntrinsicDimension((RenderBox child) => child.getMaxIntrinsicHeight(width));
} }
@override @override
...@@ -464,57 +463,6 @@ class RenderStack extends RenderBox ...@@ -464,57 +463,6 @@ class RenderStack extends RenderBox
return defaultComputeDistanceToHighestActualBaseline(baseline); 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 @override
void performLayout() { void performLayout() {
_resolve(); _resolve();
...@@ -579,7 +527,45 @@ class RenderStack extends RenderBox ...@@ -579,7 +527,45 @@ class RenderStack extends RenderBox
if (!childParentData.isPositioned) { if (!childParentData.isPositioned) {
childParentData.offset = _resolvedAlignment.alongOffset(size - child.size as Offset); childParentData.offset = _resolvedAlignment.alongOffset(size - child.size as Offset);
} else { } else {
_hasVisualOverflow = layoutPositionedChild(child, childParentData, size, _resolvedAlignment) || _hasVisualOverflow; 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);
} }
assert(child.parentData == childParentData); assert(child.parentData == childParentData);
......
...@@ -4,13 +4,13 @@ ...@@ -4,13 +4,13 @@
import 'dart:async'; import 'dart:async';
import 'dart:collection'; import 'dart:collection';
import 'dart:math' as math;
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter/scheduler.dart'; import 'package:flutter/scheduler.dart';
import 'basic.dart'; import 'basic.dart';
import 'debug.dart';
import 'framework.dart'; import 'framework.dart';
import 'ticker_provider.dart'; import 'ticker_provider.dart';
...@@ -115,7 +115,7 @@ class OverlayEntry { ...@@ -115,7 +115,7 @@ class OverlayEntry {
} }
OverlayState _overlay; OverlayState _overlay;
final GlobalKey<_OverlayEntryWidgetState> _key = GlobalKey<_OverlayEntryWidgetState>(); final GlobalKey<_OverlayEntryState> _key = GlobalKey<_OverlayEntryState>();
/// Remove this entry from the overlay. /// Remove this entry from the overlay.
/// ///
...@@ -152,30 +152,21 @@ class OverlayEntry { ...@@ -152,30 +152,21 @@ class OverlayEntry {
String toString() => '${describeIdentity(this)}(opaque: $opaque; maintainState: $maintainState)'; String toString() => '${describeIdentity(this)}(opaque: $opaque; maintainState: $maintainState)';
} }
class _OverlayEntryWidget extends StatefulWidget { class _OverlayEntry extends StatefulWidget {
const _OverlayEntryWidget({ _OverlayEntry(this.entry)
@required Key key, : assert(entry != null),
@required this.entry, super(key: entry._key);
this.tickerEnabled = true,
}) : assert(key != null),
assert(entry != null),
assert(tickerEnabled != null),
super(key: key);
final OverlayEntry entry; final OverlayEntry entry;
final bool tickerEnabled;
@override @override
_OverlayEntryWidgetState createState() => _OverlayEntryWidgetState(); _OverlayEntryState createState() => _OverlayEntryState();
} }
class _OverlayEntryWidgetState extends State<_OverlayEntryWidget> { class _OverlayEntryState extends State<_OverlayEntry> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return TickerMode( return widget.entry.builder(context);
enabled: widget.tickerEnabled,
child: widget.entry.builder(context),
);
} }
void _markNeedsBuild() { void _markNeedsBuild() {
...@@ -461,32 +452,28 @@ class OverlayState extends State<Overlay> with TickerProviderStateMixin { ...@@ -461,32 +452,28 @@ class OverlayState extends State<Overlay> with TickerProviderStateMixin {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// This list is filled backwards and then reversed below before // These lists are filled backwards. For the offstage children that
// it is added to the tree. // does not matter since they aren't rendered, but for the onstage
final List<Widget> children = <Widget>[]; // children we reverse the list below before adding it to the tree.
final List<Widget> onstageChildren = <Widget>[];
final List<Widget> offstageChildren = <Widget>[];
bool onstage = true; bool onstage = true;
int onstageCount = 0;
for (int i = _entries.length - 1; i >= 0; i -= 1) { for (int i = _entries.length - 1; i >= 0; i -= 1) {
final OverlayEntry entry = _entries[i]; final OverlayEntry entry = _entries[i];
if (onstage) { if (onstage) {
onstageCount += 1; onstageChildren.add(_OverlayEntry(entry));
children.add(_OverlayEntryWidget(
key: entry._key,
entry: entry,
));
if (entry.opaque) if (entry.opaque)
onstage = false; onstage = false;
} else if (entry.maintainState) { } else if (entry.maintainState) {
children.add(_OverlayEntryWidget( offstageChildren.add(TickerMode(enabled: false, child: _OverlayEntry(entry)));
key: entry._key,
entry: entry,
tickerEnabled: false,
));
} }
} }
return _Theatre( return _Theatre(
skipCount: children.length - onstageCount, onstage: Stack(
children: children.reversed.toList(growable: false), fit: StackFit.expand,
children: onstageChildren.reversed.toList(growable: false),
),
offstage: offstageChildren,
); );
} }
...@@ -499,50 +486,36 @@ class OverlayState extends State<Overlay> with TickerProviderStateMixin { ...@@ -499,50 +486,36 @@ class OverlayState extends State<Overlay> with TickerProviderStateMixin {
} }
} }
/// Special version of a [Stack], that doesn't layout and render the first /// A widget that has one [onstage] child which is visible, and one or more
/// [skipCount] children. /// [offstage] widgets which are kept alive, and are built, but are not laid out
/// or painted.
/// ///
/// The first [skipCount] children are considered "offstage". /// The onstage widget must be a [Stack].
class _Theatre extends MultiChildRenderObjectWidget { ///
/// For convenience, it is legal to use [Positioned] widgets around the offstage
/// widgets.
class _Theatre extends RenderObjectWidget {
_Theatre({ _Theatre({
Key key, this.onstage,
this.skipCount = 0, @required this.offstage,
List<Widget> children = const <Widget>[], }) : assert(offstage != null),
}) : assert(skipCount != null), assert(!offstage.any((Widget child) => child == null));
assert(skipCount >= 0),
assert(children != null),
assert(children.length >= skipCount),
super(key: key, children: children);
final int skipCount; final Stack onstage;
@override
_TheatreElement createElement() => _TheatreElement(this);
@override final List<Widget> offstage;
_RenderTheatre createRenderObject(BuildContext context) {
return _RenderTheatre(
skipCount: skipCount,
textDirection: Directionality.of(context),
);
}
@override @override
void updateRenderObject(BuildContext context, _RenderTheatre renderObject) { _TheatreElement createElement() => _TheatreElement(this);
renderObject
..skipCount = skipCount
..textDirection = Directionality.of(context);
}
@override @override
void debugFillProperties(DiagnosticPropertiesBuilder properties) { _RenderTheatre createRenderObject(BuildContext context) => _RenderTheatre();
super.debugFillProperties(properties);
properties.add(IntProperty('skipCount', skipCount));
}
} }
class _TheatreElement extends MultiChildRenderObjectElement { class _TheatreElement extends RenderObjectElement {
_TheatreElement(_Theatre widget) : super(widget); _TheatreElement(_Theatre widget)
: assert(!debugChildrenHaveDuplicateKeys(widget, widget.offstage)),
super(widget);
@override @override
_Theatre get widget => super.widget as _Theatre; _Theatre get widget => super.widget as _Theatre;
...@@ -550,268 +523,186 @@ class _TheatreElement extends MultiChildRenderObjectElement { ...@@ -550,268 +523,186 @@ class _TheatreElement extends MultiChildRenderObjectElement {
@override @override
_RenderTheatre get renderObject => super.renderObject as _RenderTheatre; _RenderTheatre get renderObject => super.renderObject as _RenderTheatre;
@override Element _onstage;
void debugVisitOnstageChildren(ElementVisitor visitor) { static final Object _onstageSlot = Object();
assert(children.length >= widget.skipCount);
children.skip(widget.skipCount).forEach(visitor);
}
}
class _RenderTheatre extends RenderBox with ContainerRenderObjectMixin<RenderBox, StackParentData> {
_RenderTheatre({
List<RenderBox> children,
@required TextDirection textDirection,
int skipCount = 0,
}) : assert(skipCount != null),
assert(skipCount >= 0),
assert(textDirection != null),
_textDirection = textDirection,
_skipCount = skipCount {
addAll(children);
}
bool _hasVisualOverflow = false; List<Element> _offstage;
final Set<Element> _forgottenOffstageChildren = HashSet<Element>();
@override @override
void setupParentData(RenderBox child) { void insertChildRenderObject(RenderBox child, dynamic slot) {
if (child.parentData is! StackParentData) assert(renderObject.debugValidateChild(child));
child.parentData = StackParentData(); if (slot == _onstageSlot) {
} assert(child is RenderStack);
renderObject.child = child as RenderStack;
Alignment _resolvedAlignment; } else {
assert(slot == null || slot is Element);
void _resolve() { renderObject.insert(child, after: slot?.renderObject as RenderBox);
if (_resolvedAlignment != null)
return;
_resolvedAlignment = AlignmentDirectional.topStart.resolve(textDirection);
}
void _markNeedResolution() {
_resolvedAlignment = null;
markNeedsLayout();
}
TextDirection get textDirection => _textDirection;
TextDirection _textDirection;
set textDirection(TextDirection value) {
if (_textDirection == value)
return;
_textDirection = value;
_markNeedResolution();
}
int get skipCount => _skipCount;
int _skipCount;
set skipCount(int value) {
assert(value != null);
if (_skipCount != value) {
_skipCount = value;
markNeedsLayout();
} }
} }
RenderBox get _firstOnstageChild { @override
if (skipCount == super.childCount) { void moveChildRenderObject(RenderBox child, dynamic slot) {
return null; if (slot == _onstageSlot) {
} renderObject.remove(child);
RenderBox child = super.firstChild; assert(child is RenderStack);
for (int toSkip = skipCount; toSkip > 0; toSkip--) { renderObject.child = child as RenderStack;
final StackParentData childParentData = child.parentData as StackParentData; } else {
child = childParentData.nextSibling; assert(slot == null || slot is Element);
assert(child != null); if (renderObject.child == child) {
renderObject.child = null;
renderObject.insert(child, after: slot?.renderObject as RenderBox);
} else {
renderObject.move(child, after: slot?.renderObject as RenderBox);
}
} }
return child;
} }
RenderBox get _lastOnstageChild => skipCount == super.childCount ? null : lastChild;
int get _onstageChildCount => childCount - skipCount;
@override @override
double computeMinIntrinsicWidth(double height) { void removeChildRenderObject(RenderBox child) {
return RenderStack.getIntrinsicDimension(_firstOnstageChild, (RenderBox child) => child.getMinIntrinsicWidth(height)); if (renderObject.child == child) {
renderObject.child = null;
} else {
renderObject.remove(child);
}
} }
@override @override
double computeMaxIntrinsicWidth(double height) { void visitChildren(ElementVisitor visitor) {
return RenderStack.getIntrinsicDimension(_firstOnstageChild, (RenderBox child) => child.getMaxIntrinsicWidth(height)); if (_onstage != null)
visitor(_onstage);
for (final Element child in _offstage) {
if (!_forgottenOffstageChildren.contains(child))
visitor(child);
}
} }
@override @override
double computeMinIntrinsicHeight(double width) { void debugVisitOnstageChildren(ElementVisitor visitor) {
return RenderStack.getIntrinsicDimension(_firstOnstageChild, (RenderBox child) => child.getMinIntrinsicHeight(width)); if (_onstage != null)
visitor(_onstage);
} }
@override @override
double computeMaxIntrinsicHeight(double width) { bool forgetChild(Element child) {
return RenderStack.getIntrinsicDimension(_firstOnstageChild, (RenderBox child) => child.getMaxIntrinsicHeight(width)); if (child == _onstage) {
_onstage = null;
} else {
assert(_offstage.contains(child));
assert(!_forgottenOffstageChildren.contains(child));
_forgottenOffstageChildren.add(child);
}
return true;
} }
@override @override
double computeDistanceToActualBaseline(TextBaseline baseline) { void mount(Element parent, dynamic newSlot) {
assert(!debugNeedsLayout); super.mount(parent, newSlot);
double result; _onstage = updateChild(_onstage, widget.onstage, _onstageSlot);
RenderBox child = _firstOnstageChild; _offstage = List<Element>(widget.offstage.length);
while (child != null) { Element previousChild;
assert(!child.debugNeedsLayout); for (int i = 0; i < _offstage.length; i += 1) {
final StackParentData childParentData = child.parentData as StackParentData; final Element newChild = inflateWidget(widget.offstage[i], previousChild);
double candidate = child.getDistanceToActualBaseline(baseline); _offstage[i] = newChild;
if (candidate != null) { previousChild = newChild;
candidate += childParentData.offset.dy;
if (result != null) {
result = math.min(result, candidate);
} else {
result = candidate;
}
}
child = childParentData.nextSibling;
} }
return result;
} }
@override @override
bool get sizedByParent => true; void update(_Theatre newWidget) {
super.update(newWidget);
@override assert(widget == newWidget);
void performResize() { _onstage = updateChild(_onstage, widget.onstage, _onstageSlot);
size = constraints.biggest; _offstage = updateChildren(_offstage, widget.offstage, forgottenChildren: _forgottenOffstageChildren);
assert(size.isFinite); _forgottenOffstageChildren.clear();
} }
}
@override // A render object which lays out and paints one subtree while keeping a list
void performLayout() { // of other subtrees alive but not laid out or painted (the "zombie" children).
_hasVisualOverflow = false; //
// The subtree that is laid out and painted must be a [RenderStack].
if (_onstageChildCount == 0) { //
return; // This class uses [StackParentData] objects for its parent data so that the
} // children of its primary subtree's stack can be moved to this object's list
// of zombie children without changing their parent data objects.
_resolve(); class _RenderTheatre extends RenderBox
assert(_resolvedAlignment != null); with RenderObjectWithChildMixin<RenderStack>, RenderProxyBoxMixin<RenderStack>,
ContainerRenderObjectMixin<RenderBox, StackParentData> {
// Same BoxConstraints as used by RenderStack for StackFit.expand.
final BoxConstraints nonPositionedConstraints = BoxConstraints.tight(constraints.biggest);
RenderBox child = _firstOnstageChild;
while (child != null) {
final StackParentData childParentData = child.parentData as StackParentData;
if (!childParentData.isPositioned) {
child.layout(nonPositionedConstraints, parentUsesSize: true);
childParentData.offset = _resolvedAlignment.alongOffset(size - child.size as Offset);
} else {
_hasVisualOverflow = RenderStack.layoutPositionedChild(child, childParentData, size, _resolvedAlignment) || _hasVisualOverflow;
}
assert(child.parentData == childParentData);
child = childParentData.nextSibling;
}
}
@override @override
bool hitTestChildren(BoxHitTestResult result, { Offset position }) { void setupParentData(RenderObject child) {
RenderBox child = _lastOnstageChild; if (child.parentData is! StackParentData)
for (int i = 0; i < _onstageChildCount; i++) { child.parentData = StackParentData();
assert(child != null);
final StackParentData childParentData = child.parentData as StackParentData;
final bool isHit = result.addWithPaintOffset(
offset: childParentData.offset,
position: position,
hitTest: (BoxHitTestResult result, Offset transformed) {
assert(transformed == position - childParentData.offset);
return child.hitTest(result, position: transformed);
},
);
if (isHit)
return true;
child = childParentData.previousSibling;
}
return false;
} }
@protected // Because both RenderObjectWithChildMixin and ContainerRenderObjectMixin
void paintStack(PaintingContext context, Offset offset) { // define redepthChildren, visitChildren and debugDescribeChildren and don't
RenderBox child = _firstOnstageChild; // call super, we have to define them again here to make sure the work of both
while (child != null) { // is done.
final StackParentData childParentData = child.parentData as StackParentData; //
context.paintChild(child, childParentData.offset + offset); // We chose to put ContainerRenderObjectMixin last in the inheritance chain so
child = childParentData.nextSibling; // that we can call super to hit its more complex definitions of
} // redepthChildren and visitChildren, and then duplicate the more trivial
} // definition from RenderObjectWithChildMixin inline in our version here.
//
// This code duplication is suboptimal.
// TODO(ianh): Replace this with a better solution once https://github.com/dart-lang/sdk/issues/27100 is fixed
//
// For debugDescribeChildren we just roll our own because otherwise the line
// drawings won't really work as well.
@override @override
void paint(PaintingContext context, Offset offset) { void redepthChildren() {
if (_hasVisualOverflow) { if (child != null)
context.pushClipRect(needsCompositing, offset, Offset.zero & size, paintStack); redepthChild(child);
} else { super.redepthChildren();
paintStack(context, offset);
}
} }
@override @override
void visitChildrenForSemantics(RenderObjectVisitor visitor) { void visitChildren(RenderObjectVisitor visitor) {
RenderBox child = _firstOnstageChild; if (child != null)
while (child != null) {
visitor(child); visitor(child);
final StackParentData childParentData = child.parentData as StackParentData; super.visitChildren(visitor);
child = childParentData.nextSibling;
}
}
@override
Rect describeApproximatePaintClip(RenderObject child) => _hasVisualOverflow ? Offset.zero & size : null;
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(IntProperty('skipCount', skipCount));
properties.add(EnumProperty<TextDirection>('textDirection', textDirection));
} }
@override @override
List<DiagnosticsNode> debugDescribeChildren() { List<DiagnosticsNode> debugDescribeChildren() {
final List<DiagnosticsNode> offstageChildren = <DiagnosticsNode>[]; final List<DiagnosticsNode> children = <DiagnosticsNode>[
final List<DiagnosticsNode> onstageChildren = <DiagnosticsNode>[]; if (child != null) child.toDiagnosticsNode(name: 'onstage'),
];
int count = 1;
bool onstage = false;
RenderBox child = firstChild;
final RenderBox firstOnstageChild = _firstOnstageChild;
while (child != null) {
if (child == firstOnstageChild) {
onstage = true;
count = 1;
}
if (onstage) { if (firstChild != null) {
onstageChildren.add( RenderBox child = firstChild;
child.toDiagnosticsNode(
name: 'onstage $count', int count = 1;
), while (true) {
); children.add(
} else {
offstageChildren.add(
child.toDiagnosticsNode( child.toDiagnosticsNode(
name: 'offstage $count', name: 'offstage $count',
style: DiagnosticsTreeStyle.offstage, style: DiagnosticsTreeStyle.offstage,
), ),
); );
if (child == lastChild)
break;
final StackParentData childParentData = child.parentData as StackParentData;
child = childParentData.nextSibling;
count += 1;
} }
} else {
final StackParentData childParentData = child.parentData as StackParentData; children.add(
child = childParentData.nextSibling;
count += 1;
}
return <DiagnosticsNode>[
...onstageChildren,
if (offstageChildren.isNotEmpty)
...offstageChildren
else
DiagnosticsNode.message( DiagnosticsNode.message(
'no offstage children', 'no offstage children',
style: DiagnosticsTreeStyle.offstage, style: DiagnosticsTreeStyle.offstage,
), ),
]; );
}
return children;
}
@override
void visitChildrenForSemantics(RenderObjectVisitor visitor) {
if (child != null)
visitor(child);
} }
} }
...@@ -72,9 +72,12 @@ CupertinoPageScaffold scaffoldForNavBar(Widget navBar) { ...@@ -72,9 +72,12 @@ CupertinoPageScaffold scaffoldForNavBar(Widget navBar) {
} }
Finder flying(WidgetTester tester, Finder finder) { Finder flying(WidgetTester tester, Finder finder) {
final ContainerRenderObjectMixin<RenderBox, StackParentData> theater = tester.renderObject(find.byType(Overlay)); final RenderObjectWithChildMixin<RenderStack> theater =
tester.renderObject(find.byType(Overlay));
final RenderStack theaterStack = theater.child;
final Finder lastOverlayFinder = find.byElementPredicate((Element element) { final Finder lastOverlayFinder = find.byElementPredicate((Element element) {
return element is RenderObjectElement && element.renderObject == theater.lastChild; return element is RenderObjectElement &&
element.renderObject == theaterStack.lastChild;
}); });
assert( assert(
......
...@@ -132,8 +132,8 @@ void main() { ...@@ -132,8 +132,8 @@ void main() {
' Offstage\n' ' Offstage\n'
' _ModalScopeStatus\n' ' _ModalScopeStatus\n'
' _ModalScope<dynamic>-[LabeledGlobalKey<_ModalScopeState<dynamic>>#969b7]\n' ' _ModalScope<dynamic>-[LabeledGlobalKey<_ModalScopeState<dynamic>>#969b7]\n'
' TickerMode\n' ' _OverlayEntry-[LabeledGlobalKey<_OverlayEntryState>#7a3ae]\n'
' _OverlayEntryWidget-[LabeledGlobalKey<_OverlayEntryWidgetState>#545d0]\n' ' Stack\n'
' _Theatre\n' ' _Theatre\n'
' Overlay-[LabeledGlobalKey<OverlayState>#31a52]\n' ' Overlay-[LabeledGlobalKey<OverlayState>#31a52]\n'
' _FocusMarker\n' ' _FocusMarker\n'
......
...@@ -529,13 +529,12 @@ void main() { ...@@ -529,13 +529,12 @@ void main() {
// which will change depending on where the test is run. // which will change depending on where the test is run.
expect(lines.length, greaterThan(7)); expect(lines.length, greaterThan(7));
expect( expect(
lines.take(9).join('\n'), lines.take(8).join('\n'),
equalsIgnoringHashCodes( equalsIgnoringHashCodes(
'══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞════════════════════════\n' '══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞════════════════════════\n'
'The following assertion was thrown building Stepper(dirty,\n' 'The following assertion was thrown building Stepper(dirty,\n'
'dependencies: [TickerMode,\n' 'dependencies: [_LocalizationsScope-[GlobalKey#00000]], state:\n'
'_LocalizationsScope-[GlobalKey#6b31b]], state:\n' '_StepperState#00000):\n'
'_StepperState#1bf00):\n'
'Steppers must not be nested.\n' 'Steppers must not be nested.\n'
'The material specification advises that one should avoid\n' 'The material specification advises that one should avoid\n'
'embedding steppers within steppers.\n' 'embedding steppers within steppers.\n'
......
...@@ -1186,33 +1186,6 @@ void main() { ...@@ -1186,33 +1186,6 @@ void main() {
expect(find.byKey(const ValueKey<String>('/A/B')), findsNothing); // popped expect(find.byKey(const ValueKey<String>('/A/B')), findsNothing); // popped
expect(find.byKey(const ValueKey<String>('/C')), findsOneWidget); 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> { class NoAnimationPageRoute extends PageRouteBuilder<void> {
...@@ -1226,20 +1199,3 @@ class NoAnimationPageRoute extends PageRouteBuilder<void> { ...@@ -1226,20 +1199,3 @@ class NoAnimationPageRoute extends PageRouteBuilder<void> {
return super.createAnimationController()..value = 1.0; 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();
}
}
...@@ -6,8 +6,6 @@ import 'package:flutter/foundation.dart'; ...@@ -6,8 +6,6 @@ import 'package:flutter/foundation.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'semantics_tester.dart';
void main() { void main() {
testWidgets('OverflowEntries context contains Overlay', (WidgetTester tester) async { testWidgets('OverflowEntries context contains Overlay', (WidgetTester tester) async {
final GlobalKey overlayKey = GlobalKey(); final GlobalKey overlayKey = GlobalKey();
...@@ -27,9 +25,6 @@ void main() { ...@@ -27,9 +25,6 @@ void main() {
return Container(); return Container();
}, },
), ),
OverlayEntry(
builder: (BuildContext context) => Container(),
)
], ],
), ),
), ),
...@@ -41,42 +36,36 @@ void main() { ...@@ -41,42 +36,36 @@ void main() {
expect( expect(
theater.toStringDeep(minLevel: DiagnosticLevel.info), theater.toStringDeep(minLevel: DiagnosticLevel.info),
equalsIgnoringHashCodes( equalsIgnoringHashCodes(
'_RenderTheatre#744c9\n' '_RenderTheatre#f5cf2\n'
' │ parentData: <none>\n' ' │ parentData: <none>\n'
' │ constraints: BoxConstraints(w=800.0, h=600.0)\n' ' │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
' │ size: Size(800.0, 600.0)\n' ' │ size: Size(800.0, 600.0)\n'
' │ skipCount: 0\n' ' │\n'
' │ textDirection: ltr\n' ' ├─onstage: RenderStack#39819\n'
' │\n' ' ╎ │ parentData: not positioned; offset=Offset(0.0, 0.0) (can use\n'
' ├─onstage 1: RenderLimitedBox#bb803\n' ' ╎ │ size)\n'
' │ │ parentData: not positioned; offset=Offset(0.0, 0.0) (can use\n' ' ╎ │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
' │ │ size)\n' ' ╎ │ size: Size(800.0, 600.0)\n'
' │ │ constraints: BoxConstraints(w=800.0, h=600.0)\n' ' ╎ │ alignment: AlignmentDirectional.topStart\n'
' │ │ size: Size(800.0, 600.0)\n' ' ╎ │ textDirection: ltr\n'
' │ │ maxWidth: 0.0\n' ' ╎ │ fit: expand\n'
' │ │ maxHeight: 0.0\n' ' ╎ │ overflow: clip\n'
' │ │\n' ' ╎ │\n'
' │ └─child: RenderConstrainedBox#62707\n' ' ╎ └─child 1: RenderLimitedBox#d1448\n'
' │ parentData: <none> (can use size)\n' ' ╎ │ parentData: not positioned; offset=Offset(0.0, 0.0) (can use\n'
' │ constraints: BoxConstraints(w=800.0, h=600.0)\n' ' ╎ │ size)\n'
' │ size: Size(800.0, 600.0)\n' ' ╎ │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
' │ additionalConstraints: BoxConstraints(biggest)\n' ' ╎ │ size: Size(800.0, 600.0)\n'
' │\n' ' ╎ │ maxWidth: 0.0\n'
' ├─onstage 2: RenderLimitedBox#af5f1\n' ' ╎ │ maxHeight: 0.0\n'
' ╎ │ parentData: not positioned; offset=Offset(0.0, 0.0) (can use\n' ' ╎ │\n'
' ╎ │ size)\n' ' ╎ └─child: RenderConstrainedBox#e8b87\n'
' ╎ │ constraints: BoxConstraints(w=800.0, h=600.0)\n' ' ╎ parentData: <none> (can use size)\n'
' ╎ │ size: Size(800.0, 600.0)\n' ' ╎ constraints: BoxConstraints(w=800.0, h=600.0)\n'
' ╎ │ maxWidth: 0.0\n' ' ╎ size: Size(800.0, 600.0)\n'
' ╎ │ maxHeight: 0.0\n' ' ╎ additionalConstraints: BoxConstraints(biggest)\n'
' ╎ │\n' ' ╎\n'
' ╎ └─child: RenderConstrainedBox#69c48\n' ' └╌no offstage children\n'
' ╎ parentData: <none> (can use size)\n'
' ╎ constraints: BoxConstraints(w=800.0, h=600.0)\n'
' ╎ size: Size(800.0, 600.0)\n'
' ╎ additionalConstraints: BoxConstraints(biggest)\n'
' ╎\n'
' └╌no offstage children\n'
), ),
); );
}); });
...@@ -114,52 +103,60 @@ void main() { ...@@ -114,52 +103,60 @@ void main() {
expect( expect(
theater.toStringDeep(minLevel: DiagnosticLevel.info), theater.toStringDeep(minLevel: DiagnosticLevel.info),
equalsIgnoringHashCodes( equalsIgnoringHashCodes(
'_RenderTheatre#385b3\n' '_RenderTheatre#b22a8\n'
' │ parentData: <none>\n' ' │ parentData: <none>\n'
' │ constraints: BoxConstraints(w=800.0, h=600.0)\n' ' │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
' │ size: Size(800.0, 600.0)\n' ' │ size: Size(800.0, 600.0)\n'
' │ skipCount: 2\n'
' │ textDirection: ltr\n'
' │\n' ' │\n'
' ├─onstage 1: RenderLimitedBox#0a77a\n' ' ├─onstage: RenderStack#eab87\n'
' ╎ │ parentData: not positioned; offset=Offset(0.0, 0.0) (can use\n' ' ╎ │ parentData: not positioned; offset=Offset(0.0, 0.0) (can use\n'
' ╎ │ size)\n' ' ╎ │ size)\n'
' ╎ │ constraints: BoxConstraints(w=800.0, h=600.0)\n' ' ╎ │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
' ╎ │ size: Size(800.0, 600.0)\n' ' ╎ │ size: Size(800.0, 600.0)\n'
' ╎ │ maxWidth: 0.0\n' ' ╎ │ alignment: AlignmentDirectional.topStart\n'
' ╎ │ maxHeight: 0.0\n' ' ╎ │ textDirection: ltr\n'
' ╎ │ fit: expand\n'
' ╎ │ overflow: clip\n'
' ╎ │\n' ' ╎ │\n'
' ╎ └─child: RenderConstrainedBox#21f3a\n' ' ╎ └─child 1: RenderLimitedBox#ca15b\n'
' ╎ parentData: <none> (can use size)\n' ' ╎ │ parentData: not positioned; offset=Offset(0.0, 0.0) (can use\n'
' ╎ constraints: BoxConstraints(w=800.0, h=600.0)\n' ' ╎ │ size)\n'
' ╎ size: Size(800.0, 600.0)\n' ' ╎ │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
' ╎ additionalConstraints: BoxConstraints(biggest)\n' ' ╎ │ size: Size(800.0, 600.0)\n'
' ╎ │ maxWidth: 0.0\n'
' ╎ │ maxHeight: 0.0\n'
' ╎ │\n'
' ╎ └─child: RenderConstrainedBox#dffe5\n'
' ╎ parentData: <none> (can use size)\n'
' ╎ constraints: BoxConstraints(w=800.0, h=600.0)\n'
' ╎ size: Size(800.0, 600.0)\n'
' ╎ additionalConstraints: BoxConstraints(biggest)\n'
' ╎\n' ' ╎\n'
' ╎╌offstage 1: RenderLimitedBox#62c8c NEEDS-LAYOUT NEEDS-PAINT\n' ' ╎╌offstage 1: RenderLimitedBox#b6f09 NEEDS-LAYOUT NEEDS-PAINT\n'
' ╎ │ parentData: not positioned; offset=Offset(0.0, 0.0)\n' ' ╎ │ parentData: not positioned; offset=Offset(0.0, 0.0)\n'
' ╎ │ constraints: MISSING\n' ' ╎ │ constraints: MISSING\n'
' ╎ │ size: MISSING\n' ' ╎ │ size: MISSING\n'
' ╎ │ maxWidth: 0.0\n' ' ╎ │ maxWidth: 0.0\n'
' ╎ │ maxHeight: 0.0\n' ' ╎ │ maxHeight: 0.0\n'
' ╎ │\n' ' ╎ │\n'
' ╎ └─child: RenderConstrainedBox#425fa NEEDS-LAYOUT NEEDS-PAINT\n' ' ╎ └─child: RenderConstrainedBox#5a057 NEEDS-LAYOUT NEEDS-PAINT\n'
' ╎ parentData: <none>\n' ' ╎ parentData: <none>\n'
' ╎ constraints: MISSING\n' ' ╎ constraints: MISSING\n'
' ╎ size: MISSING\n' ' ╎ size: MISSING\n'
' ╎ additionalConstraints: BoxConstraints(biggest)\n' ' ╎ additionalConstraints: BoxConstraints(biggest)\n'
' ╎\n' ' ╎\n'
' └╌offstage 2: RenderLimitedBox#03ae2 NEEDS-LAYOUT NEEDS-PAINT\n' ' └╌offstage 2: RenderLimitedBox#f689e NEEDS-LAYOUT NEEDS-PAINT\n'
' │ parentData: not positioned; offset=Offset(0.0, 0.0)\n' ' │ parentData: not positioned; offset=Offset(0.0, 0.0)\n'
' │ constraints: MISSING\n' ' │ constraints: MISSING\n'
' │ size: MISSING\n' ' │ size: MISSING\n'
' │ maxWidth: 0.0\n' ' │ maxWidth: 0.0\n'
' │ maxHeight: 0.0\n' ' │ maxHeight: 0.0\n'
' │\n' ' │\n'
' └─child: RenderConstrainedBox#b4d48 NEEDS-LAYOUT NEEDS-PAINT\n' ' └─child: RenderConstrainedBox#c15f0 NEEDS-LAYOUT NEEDS-PAINT\n'
' parentData: <none>\n' ' parentData: <none>\n'
' constraints: MISSING\n' ' constraints: MISSING\n'
' size: MISSING\n' ' size: MISSING\n'
' additionalConstraints: BoxConstraints(biggest)\n', ' additionalConstraints: BoxConstraints(biggest)\n'
), ),
); );
}); });
...@@ -701,261 +698,4 @@ void main() { ...@@ -701,261 +698,4 @@ void main() {
expect(find.byKey(root), findsNothing); expect(find.byKey(root), findsNothing);
expect(find.byKey(top), findsOneWidget); expect(find.byKey(top), findsOneWidget);
}); });
testWidgets('OverlayEntries do not rebuild when opaqueness changes', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/45797.
final GlobalKey<OverlayState> overlayKey = GlobalKey<OverlayState>();
final Key bottom = UniqueKey();
final Key middle = UniqueKey();
final Key top = UniqueKey();
final Widget bottomWidget = StatefulTestWidget(key: bottom);
final Widget middleWidget = StatefulTestWidget(key: middle);
final Widget topWidget = StatefulTestWidget(key: top);
final OverlayEntry bottomEntry = OverlayEntry(
maintainState: true,
builder: (BuildContext context) {
return bottomWidget;
},
);
final OverlayEntry middleEntry = OverlayEntry(
maintainState: true,
builder: (BuildContext context) {
return middleWidget;
},
);
final OverlayEntry topEntry = OverlayEntry(
maintainState: true,
builder: (BuildContext context) {
return topWidget;
},
);
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: Overlay(
key: overlayKey,
initialEntries: <OverlayEntry>[
bottomEntry,
middleEntry,
topEntry,
],
),
),
);
// All widgets are onstage.
expect(tester.state<StatefulTestState>(find.byKey(bottom)).rebuildCount, 1);
expect(tester.state<StatefulTestState>(find.byKey(middle)).rebuildCount, 1);
expect(tester.state<StatefulTestState>(find.byKey(top)).rebuildCount, 1);
middleEntry.opaque = true;
await tester.pump();
// Bottom widget is offstage and did not rebuild.
expect(find.byKey(bottom), findsNothing);
expect(tester.state<StatefulTestState>(find.byKey(bottom, skipOffstage: false)).rebuildCount, 1);
expect(tester.state<StatefulTestState>(find.byKey(middle)).rebuildCount, 1);
expect(tester.state<StatefulTestState>(find.byKey(top)).rebuildCount, 1);
});
testWidgets('OverlayEntries do not rebuild when opaque entry is added', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/45797.
final GlobalKey<OverlayState> overlayKey = GlobalKey<OverlayState>();
final Key bottom = UniqueKey();
final Key middle = UniqueKey();
final Key top = UniqueKey();
final Widget bottomWidget = StatefulTestWidget(key: bottom);
final Widget middleWidget = StatefulTestWidget(key: middle);
final Widget topWidget = StatefulTestWidget(key: top);
final OverlayEntry bottomEntry = OverlayEntry(
maintainState: true,
builder: (BuildContext context) {
return bottomWidget;
},
);
final OverlayEntry middleEntry = OverlayEntry(
opaque: true,
maintainState: true,
builder: (BuildContext context) {
return middleWidget;
},
);
final OverlayEntry topEntry = OverlayEntry(
maintainState: true,
builder: (BuildContext context) {
return topWidget;
},
);
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: Overlay(
key: overlayKey,
initialEntries: <OverlayEntry>[
bottomEntry,
topEntry,
],
),
),
);
// Both widgets are onstage.
expect(tester.state<StatefulTestState>(find.byKey(bottom)).rebuildCount, 1);
expect(tester.state<StatefulTestState>(find.byKey(top)).rebuildCount, 1);
overlayKey.currentState.rearrange(<OverlayEntry>[
bottomEntry, middleEntry, topEntry,
]);
await tester.pump();
// Bottom widget is offstage and did not rebuild.
expect(find.byKey(bottom), findsNothing);
expect(tester.state<StatefulTestState>(find.byKey(bottom, skipOffstage: false)).rebuildCount, 1);
expect(tester.state<StatefulTestState>(find.byKey(middle)).rebuildCount, 1);
expect(tester.state<StatefulTestState>(find.byKey(top)).rebuildCount, 1);
});
testWidgets('entries below opaque entries are ignored for hit testing', (WidgetTester tester) async {
final GlobalKey<OverlayState> overlayKey = GlobalKey<OverlayState>();
int bottomTapCount = 0;
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: Overlay(
key: overlayKey,
initialEntries: <OverlayEntry>[
OverlayEntry(
maintainState: true,
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
bottomTapCount++;
},
);
},
),
],
),
),
);
expect(bottomTapCount, 0);
await tester.tap(find.byKey(overlayKey));
expect(bottomTapCount, 1);
overlayKey.currentState.insert(OverlayEntry(
maintainState: true,
opaque: true,
builder: (BuildContext context) {
return Container();
},
));
await tester.pump();
// Bottom is offstage and does not receive tap events.
expect(find.byType(GestureDetector), findsNothing);
expect(find.byType(GestureDetector, skipOffstage: false), findsOneWidget);
await tester.tap(find.byKey(overlayKey));
expect(bottomTapCount, 1);
int topTapCount = 0;
overlayKey.currentState.insert(OverlayEntry(
maintainState: true,
opaque: true,
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
topTapCount++;
},
);
},
));
await tester.pump();
expect(topTapCount, 0);
await tester.tap(find.byKey(overlayKey));
expect(topTapCount, 1);
expect(bottomTapCount, 1);
});
testWidgets('Semantics of entries below opaque entries are ignored', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
final GlobalKey<OverlayState> overlayKey = GlobalKey<OverlayState>();
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: Overlay(
key: overlayKey,
initialEntries: <OverlayEntry>[
OverlayEntry(
maintainState: true,
builder: (BuildContext context) {
return const Text('bottom');
},
),
OverlayEntry(
maintainState: true,
opaque: true,
builder: (BuildContext context) {
return const Text('top');
},
),
],
),
),
);
expect(find.text('bottom'), findsNothing);
expect(find.text('bottom', skipOffstage: false), findsOneWidget);
expect(find.text('top'), findsOneWidget);
expect(semantics, includesNodeWith(label: 'top'));
expect(semantics, isNot(includesNodeWith(label: 'bottom')));
semantics.dispose();
});
testWidgets('Can used Positioned within OverlayEntry', (WidgetTester tester) async {
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: Overlay(
initialEntries: <OverlayEntry>[
OverlayEntry(
builder: (BuildContext context) {
return const Positioned(
left: 145,
top: 123,
child: Text('positioned child'),
);
},
),
],
),
),
);
expect(tester.getTopLeft(find.text('positioned child')), const Offset(145, 123));
});
}
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