This reverts commit 8eecdbe823ea09dd723a8af34c7c275e5536d728.
This commit is contained in:
parent
a1fa1a3d26
commit
52c665d23f
@ -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
|
|||||||
|
|
||||||
@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
|
|||||||
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
|
|||||||
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 @@
|
|||||||
|
|
||||||
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 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
|||||||
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 {
|
|||||||
|
|
||||||
@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 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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;
|
||||||
|
|
||||||
|
final List<Widget> offstage;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_TheatreElement createElement() => _TheatreElement(this);
|
_TheatreElement createElement() => _TheatreElement(this);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_RenderTheatre createRenderObject(BuildContext context) {
|
_RenderTheatre createRenderObject(BuildContext context) => _RenderTheatre();
|
||||||
return _RenderTheatre(
|
|
||||||
skipCount: skipCount,
|
|
||||||
textDirection: Directionality.of(context),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void updateRenderObject(BuildContext context, _RenderTheatre renderObject) {
|
|
||||||
renderObject
|
|
||||||
..skipCount = skipCount
|
|
||||||
..textDirection = Directionality.of(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
|
||||||
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 {
|
|||||||
@override
|
@override
|
||||||
_RenderTheatre get renderObject => super.renderObject as _RenderTheatre;
|
_RenderTheatre get renderObject => super.renderObject as _RenderTheatre;
|
||||||
|
|
||||||
|
Element _onstage;
|
||||||
|
static final Object _onstageSlot = Object();
|
||||||
|
|
||||||
|
List<Element> _offstage;
|
||||||
|
final Set<Element> _forgottenOffstageChildren = HashSet<Element>();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void insertChildRenderObject(RenderBox child, dynamic slot) {
|
||||||
|
assert(renderObject.debugValidateChild(child));
|
||||||
|
if (slot == _onstageSlot) {
|
||||||
|
assert(child is RenderStack);
|
||||||
|
renderObject.child = child as RenderStack;
|
||||||
|
} else {
|
||||||
|
assert(slot == null || slot is Element);
|
||||||
|
renderObject.insert(child, after: slot?.renderObject as RenderBox);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void moveChildRenderObject(RenderBox child, dynamic slot) {
|
||||||
|
if (slot == _onstageSlot) {
|
||||||
|
renderObject.remove(child);
|
||||||
|
assert(child is RenderStack);
|
||||||
|
renderObject.child = child as RenderStack;
|
||||||
|
} else {
|
||||||
|
assert(slot == null || slot is Element);
|
||||||
|
if (renderObject.child == child) {
|
||||||
|
renderObject.child = null;
|
||||||
|
renderObject.insert(child, after: slot?.renderObject as RenderBox);
|
||||||
|
} else {
|
||||||
|
renderObject.move(child, after: slot?.renderObject as RenderBox);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void removeChildRenderObject(RenderBox child) {
|
||||||
|
if (renderObject.child == child) {
|
||||||
|
renderObject.child = null;
|
||||||
|
} else {
|
||||||
|
renderObject.remove(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void visitChildren(ElementVisitor visitor) {
|
||||||
|
if (_onstage != null)
|
||||||
|
visitor(_onstage);
|
||||||
|
for (final Element child in _offstage) {
|
||||||
|
if (!_forgottenOffstageChildren.contains(child))
|
||||||
|
visitor(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void debugVisitOnstageChildren(ElementVisitor visitor) {
|
void debugVisitOnstageChildren(ElementVisitor visitor) {
|
||||||
assert(children.length >= widget.skipCount);
|
if (_onstage != null)
|
||||||
children.skip(widget.skipCount).forEach(visitor);
|
visitor(_onstage);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool forgetChild(Element child) {
|
||||||
|
if (child == _onstage) {
|
||||||
|
_onstage = null;
|
||||||
|
} else {
|
||||||
|
assert(_offstage.contains(child));
|
||||||
|
assert(!_forgottenOffstageChildren.contains(child));
|
||||||
|
_forgottenOffstageChildren.add(child);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void mount(Element parent, dynamic newSlot) {
|
||||||
|
super.mount(parent, newSlot);
|
||||||
|
_onstage = updateChild(_onstage, widget.onstage, _onstageSlot);
|
||||||
|
_offstage = List<Element>(widget.offstage.length);
|
||||||
|
Element previousChild;
|
||||||
|
for (int i = 0; i < _offstage.length; i += 1) {
|
||||||
|
final Element newChild = inflateWidget(widget.offstage[i], previousChild);
|
||||||
|
_offstage[i] = newChild;
|
||||||
|
previousChild = newChild;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void update(_Theatre newWidget) {
|
||||||
|
super.update(newWidget);
|
||||||
|
assert(widget == newWidget);
|
||||||
|
_onstage = updateChild(_onstage, widget.onstage, _onstageSlot);
|
||||||
|
_offstage = updateChildren(_offstage, widget.offstage, forgottenChildren: _forgottenOffstageChildren);
|
||||||
|
_forgottenOffstageChildren.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _RenderTheatre extends RenderBox with ContainerRenderObjectMixin<RenderBox, StackParentData> {
|
// A render object which lays out and paints one subtree while keeping a list
|
||||||
_RenderTheatre({
|
// of other subtrees alive but not laid out or painted (the "zombie" children).
|
||||||
List<RenderBox> children,
|
//
|
||||||
@required TextDirection textDirection,
|
// The subtree that is laid out and painted must be a [RenderStack].
|
||||||
int skipCount = 0,
|
//
|
||||||
}) : assert(skipCount != null),
|
// This class uses [StackParentData] objects for its parent data so that the
|
||||||
assert(skipCount >= 0),
|
// children of its primary subtree's stack can be moved to this object's list
|
||||||
assert(textDirection != null),
|
// of zombie children without changing their parent data objects.
|
||||||
_textDirection = textDirection,
|
class _RenderTheatre extends RenderBox
|
||||||
_skipCount = skipCount {
|
with RenderObjectWithChildMixin<RenderStack>, RenderProxyBoxMixin<RenderStack>,
|
||||||
addAll(children);
|
ContainerRenderObjectMixin<RenderBox, StackParentData> {
|
||||||
}
|
|
||||||
|
|
||||||
bool _hasVisualOverflow = false;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void setupParentData(RenderBox child) {
|
void setupParentData(RenderObject child) {
|
||||||
if (child.parentData is! StackParentData)
|
if (child.parentData is! StackParentData)
|
||||||
child.parentData = StackParentData();
|
child.parentData = StackParentData();
|
||||||
}
|
}
|
||||||
|
|
||||||
Alignment _resolvedAlignment;
|
// Because both RenderObjectWithChildMixin and ContainerRenderObjectMixin
|
||||||
|
// define redepthChildren, visitChildren and debugDescribeChildren and don't
|
||||||
void _resolve() {
|
// call super, we have to define them again here to make sure the work of both
|
||||||
if (_resolvedAlignment != null)
|
// is done.
|
||||||
return;
|
//
|
||||||
_resolvedAlignment = AlignmentDirectional.topStart.resolve(textDirection);
|
// We chose to put ContainerRenderObjectMixin last in the inheritance chain so
|
||||||
}
|
// that we can call super to hit its more complex definitions of
|
||||||
|
// redepthChildren and visitChildren, and then duplicate the more trivial
|
||||||
void _markNeedResolution() {
|
// definition from RenderObjectWithChildMixin inline in our version here.
|
||||||
_resolvedAlignment = null;
|
//
|
||||||
markNeedsLayout();
|
// This code duplication is suboptimal.
|
||||||
}
|
// TODO(ianh): Replace this with a better solution once https://github.com/dart-lang/sdk/issues/27100 is fixed
|
||||||
|
//
|
||||||
TextDirection get textDirection => _textDirection;
|
// For debugDescribeChildren we just roll our own because otherwise the line
|
||||||
TextDirection _textDirection;
|
// drawings won't really work as well.
|
||||||
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 {
|
|
||||||
if (skipCount == super.childCount) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
RenderBox child = super.firstChild;
|
|
||||||
for (int toSkip = skipCount; toSkip > 0; toSkip--) {
|
|
||||||
final StackParentData childParentData = child.parentData as StackParentData;
|
|
||||||
child = childParentData.nextSibling;
|
|
||||||
assert(child != null);
|
|
||||||
}
|
|
||||||
return child;
|
|
||||||
}
|
|
||||||
|
|
||||||
RenderBox get _lastOnstageChild => skipCount == super.childCount ? null : lastChild;
|
|
||||||
|
|
||||||
int get _onstageChildCount => childCount - skipCount;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
double computeMinIntrinsicWidth(double height) {
|
void redepthChildren() {
|
||||||
return RenderStack.getIntrinsicDimension(_firstOnstageChild, (RenderBox child) => child.getMinIntrinsicWidth(height));
|
if (child != null)
|
||||||
|
redepthChild(child);
|
||||||
|
super.redepthChildren();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
double computeMaxIntrinsicWidth(double height) {
|
void visitChildren(RenderObjectVisitor visitor) {
|
||||||
return RenderStack.getIntrinsicDimension(_firstOnstageChild, (RenderBox child) => child.getMaxIntrinsicWidth(height));
|
if (child != null)
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
double computeMinIntrinsicHeight(double width) {
|
|
||||||
return RenderStack.getIntrinsicDimension(_firstOnstageChild, (RenderBox child) => child.getMinIntrinsicHeight(width));
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
double computeMaxIntrinsicHeight(double width) {
|
|
||||||
return RenderStack.getIntrinsicDimension(_firstOnstageChild, (RenderBox child) => child.getMaxIntrinsicHeight(width));
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
double computeDistanceToActualBaseline(TextBaseline baseline) {
|
|
||||||
assert(!debugNeedsLayout);
|
|
||||||
double result;
|
|
||||||
RenderBox child = _firstOnstageChild;
|
|
||||||
while (child != null) {
|
|
||||||
assert(!child.debugNeedsLayout);
|
|
||||||
final StackParentData childParentData = child.parentData as StackParentData;
|
|
||||||
double candidate = child.getDistanceToActualBaseline(baseline);
|
|
||||||
if (candidate != null) {
|
|
||||||
candidate += childParentData.offset.dy;
|
|
||||||
if (result != null) {
|
|
||||||
result = math.min(result, candidate);
|
|
||||||
} else {
|
|
||||||
result = candidate;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
child = childParentData.nextSibling;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool get sizedByParent => true;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void performResize() {
|
|
||||||
size = constraints.biggest;
|
|
||||||
assert(size.isFinite);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void performLayout() {
|
|
||||||
_hasVisualOverflow = false;
|
|
||||||
|
|
||||||
if (_onstageChildCount == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_resolve();
|
|
||||||
assert(_resolvedAlignment != null);
|
|
||||||
|
|
||||||
// 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
|
|
||||||
bool hitTestChildren(BoxHitTestResult result, { Offset position }) {
|
|
||||||
RenderBox child = _lastOnstageChild;
|
|
||||||
for (int i = 0; i < _onstageChildCount; i++) {
|
|
||||||
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
|
|
||||||
void paintStack(PaintingContext context, Offset offset) {
|
|
||||||
RenderBox child = _firstOnstageChild;
|
|
||||||
while (child != null) {
|
|
||||||
final StackParentData childParentData = child.parentData as StackParentData;
|
|
||||||
context.paintChild(child, childParentData.offset + offset);
|
|
||||||
child = childParentData.nextSibling;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void paint(PaintingContext context, Offset offset) {
|
|
||||||
if (_hasVisualOverflow) {
|
|
||||||
context.pushClipRect(needsCompositing, offset, Offset.zero & size, paintStack);
|
|
||||||
} else {
|
|
||||||
paintStack(context, offset);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void visitChildrenForSemantics(RenderObjectVisitor visitor) {
|
|
||||||
RenderBox child = _firstOnstageChild;
|
|
||||||
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;
|
if (firstChild != null) {
|
||||||
bool onstage = false;
|
RenderBox child = firstChild;
|
||||||
RenderBox child = firstChild;
|
|
||||||
final RenderBox firstOnstageChild = _firstOnstageChild;
|
|
||||||
while (child != null) {
|
|
||||||
if (child == firstOnstageChild) {
|
|
||||||
onstage = true;
|
|
||||||
count = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (onstage) {
|
int count = 1;
|
||||||
onstageChildren.add(
|
while (true) {
|
||||||
child.toDiagnosticsNode(
|
children.add(
|
||||||
name: 'onstage $count',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} 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) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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() {
|
|||||||
' 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() {
|
|||||||
// 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() {
|
|||||||
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> {
|
|||||||
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';
|
|||||||
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() {
|
|||||||
return Container();
|
return Container();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
OverlayEntry(
|
|
||||||
builder: (BuildContext context) => Container(),
|
|
||||||
)
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -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() {
|
|||||||
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() {
|
|||||||
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user