Commit 596eb033 authored by Ian Hickson's avatar Ian Hickson Committed by GitHub

Loosen the constraints for Stack non-positioned children. (#9581)

Also:

 * Add three explicit sizing modes to Stack for non-positioned
   children: loose, expand, and passthrough. (All three are used.)

 * Fix a bug whereby layers would try to paint in the same frame as
   they were removed from layout (but not detached).

 * Fix a bug whereby Offstage wasn't properly marking the parent dirty
   when changing its sizedByParent flag.

 * Explicitly make Overlay expand non-positioned children.

 * Explicitly have InputDecoration pass through the constraints from
   its Row to its Stack children.
parent 24b40d87
...@@ -428,7 +428,10 @@ class InputDecorator extends StatelessWidget { ...@@ -428,7 +428,10 @@ class InputDecorator extends StatelessWidget {
)); ));
} }
final Widget stack = new Stack(children: stackChildren); final Widget stack = new Stack(
sizing: StackFit.passthrough,
children: stackChildren
);
if (decoration.icon != null) { if (decoration.icon != null) {
assert(!isCollapsed); assert(!isCollapsed);
......
...@@ -1638,6 +1638,9 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget { ...@@ -1638,6 +1638,9 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
/// `super.markNeedsLayout()`, in the normal case, or call /// `super.markNeedsLayout()`, in the normal case, or call
/// [markParentNeedsLayout], in the case where the parent neds to be laid out /// [markParentNeedsLayout], in the case where the parent neds to be laid out
/// as well as the child. /// as well as the child.
///
/// If [sizedByParent] has changed, called
/// [markNeedsLayoutForSizedByParentChange] instead of [markNeedsLayout].
void markNeedsLayout() { void markNeedsLayout() {
assert(_debugCanPerformMutations); assert(_debugCanPerformMutations);
if (_needsLayout) { if (_needsLayout) {
...@@ -1664,9 +1667,10 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget { ...@@ -1664,9 +1667,10 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
/// Mark this render object's layout information as dirty, and then defer to /// Mark this render object's layout information as dirty, and then defer to
/// the parent. /// the parent.
/// ///
/// This function should only be called from [markNeedsLayout] implementations /// This function should only be called from [markNeedsLayout] or
/// of subclasses that introduce more reasons for deferring the handling of /// [markNeedsLayoutForSizedByParentChange] implementations of subclasses that
/// dirty layout to the parent. See [markNeedsLayout] for details. /// introduce more reasons for deferring the handling of dirty layout to the
/// parent. See [markNeedsLayout] for details.
/// ///
/// Only call this if [parent] is not null. /// Only call this if [parent] is not null.
@protected @protected
...@@ -1681,6 +1685,18 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget { ...@@ -1681,6 +1685,18 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
assert(parent == this.parent); assert(parent == this.parent);
} }
/// Mark this render object's layout information as dirty (like
/// [markNeedsLayout]), and additionally also handle any necessary work to
/// handle the case where [sizedByParent] has changed value.
///
/// This should be called whenever [sizedByParent] might have changed.
///
/// Only call this if [parent] is not null.
void markNeedsLayoutForSizedByParentChange() {
markNeedsLayout();
markParentNeedsLayout();
}
void _cleanRelayoutBoundary() { void _cleanRelayoutBoundary() {
if (_relayoutBoundary != this) { if (_relayoutBoundary != this) {
_relayoutBoundary = null; _relayoutBoundary = null;
...@@ -1878,6 +1894,10 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget { ...@@ -1878,6 +1894,10 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
/// Returning false is always correct, but returning true can be more /// Returning false is always correct, but returning true can be more
/// efficient when computing the size of this render object because we don't /// efficient when computing the size of this render object because we don't
/// need to recompute the size if the constraints don't change. /// need to recompute the size if the constraints don't change.
///
/// Typically, subclasses will always return the same value. If the value can
/// change, then, when it does change, the subclass should make sure to call
/// [markNeedsLayoutForSizedByParentChange].
@protected @protected
bool get sizedByParent => false; bool get sizedByParent => false;
...@@ -2224,21 +2244,18 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget { ...@@ -2224,21 +2244,18 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
'disallowed.' 'disallowed.'
); );
} }
if (_needsLayout) { return true;
throw new FlutterError( });
'Tried to paint a RenderObject before it was laid out.\n' // If we still need layout, then that means that we were skipped in the
'The following RenderObject was marked as dirty for layout at the ' // layout phase and therefore don't need painting. We might not know that
'time that it was painted:\n' // yet (that is, our layer might not have been detached yet), because the
' ${toStringShallow("\n ")}\n' // same node that skipped us in layout is above us in the tree (obviously)
'A RenderObject that is still dirty for layout cannot be painted ' // and therefore may not have had a chance to paint yet (since the tree
'because it does not know its own geometry yet.\n' // paints in reverse order). In particular this will happen if they are have
'Maybe one of the ancestors of this RenderObject was skipped ' // a different layer, because there's a repaint boundary between us.
'during the layout phase, but not skipped during the paint phase. ' if (_needsLayout)
'If the ancestor in question is below the nearest relayout boundary, ' return;
'but is not below the nearest repaint boundary, that could cause ' assert(() {
'this error.'
);
}
if (_needsCompositingBitsUpdate) { if (_needsCompositingBitsUpdate) {
throw new FlutterError( throw new FlutterError(
'Tried to paint a RenderObject before its compositing bits were ' 'Tried to paint a RenderObject before its compositing bits were '
...@@ -2651,7 +2668,7 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget { ...@@ -2651,7 +2668,7 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
void debugFillDescription(List<String> description) { void debugFillDescription(List<String> description) {
if (debugCreator != null) if (debugCreator != null)
description.add('creator: $debugCreator'); description.add('creator: $debugCreator');
description.add('parentData: $parentData'); description.add('parentData: $parentData${ _debugCanParentUseSize ? " (can use size)" : ""}');
description.add('constraints: $constraints'); description.add('constraints: $constraints');
if (_layer != null) // don't access it via the "layer" getter since that's only valid when we don't need paint if (_layer != null) // don't access it via the "layer" getter since that's only valid when we don't need paint
description.add('layer: $_layer'); description.add('layer: $_layer');
......
...@@ -2407,7 +2407,7 @@ class RenderOffstage extends RenderProxyBox { ...@@ -2407,7 +2407,7 @@ class RenderOffstage extends RenderProxyBox {
if (value == _offstage) if (value == _offstage)
return; return;
_offstage = value; _offstage = value;
markNeedsLayout(); markNeedsLayoutForSizedByParentChange();
} }
@override @override
......
...@@ -200,6 +200,48 @@ class StackParentData extends ContainerBoxParentDataMixin<RenderBox> { ...@@ -200,6 +200,48 @@ class StackParentData extends ContainerBoxParentDataMixin<RenderBox> {
} }
} }
/// How to size the non-positioned children of a [Stack].
///
/// This enum is used with [Stack.sizing] and [RenderStack.sizing] to control
/// how the [BoxConstraints] passed from the stack's parent to the stack's child
/// are adjusted.
///
/// See also:
///
/// * [Stack], the widget that uses this.
/// * [RenderStack], the render object that implements the stack algorithm.
enum StackFit {
/// The constraints passed to the stack from its parent are loosened.
///
/// For example, if the stack has constraints that force it to 350x600, then
/// this would allow the non-positioned children of the stack to have any
/// width from zero to 350 and any height from zero to 600.
///
/// See also:
///
/// * [Center], which loosens the constraints passed to its child and then
/// centers the child in itself.
/// * [BoxConstraints.loosen], which implements the loosening of box
/// constraints.
loose,
/// The constraints passed to the stack from its parent are tightened to the
/// biggest size allowed.
///
/// For example, if the stack has loose constraints with a width in the range
/// 10 to 100 and a height in the range 0 to 600, then the non-positioned
/// children of the stack would all be sized as 100 pixels wide and 600 high.
expand,
/// The constraints passed to the stack from its parent are passed unmodified
/// to the non-positioned children.
///
/// For example, if a [Stack] is an [Expanded] child of a [Row], the
/// horizontal constraints will be tight and the vertical constraints will be
/// loose.
passthrough,
}
/// Whether overflowing children should be clipped, or their overflow be /// Whether overflowing children should be clipped, or their overflow be
/// visible. /// visible.
enum Overflow { enum Overflow {
...@@ -255,10 +297,14 @@ class RenderStack extends RenderBox ...@@ -255,10 +297,14 @@ class RenderStack extends RenderBox
/// top left corners. /// top left corners.
RenderStack({ RenderStack({
List<RenderBox> children, List<RenderBox> children,
FractionalOffset alignment: FractionalOffset.topLeft, FractionalOffset alignment: FractionalOffset.center,
StackFit sizing: StackFit.loose,
Overflow overflow: Overflow.clip Overflow overflow: Overflow.clip
}) : _alignment = alignment, }) : _alignment = alignment,
_sizing = sizing,
_overflow = overflow { _overflow = overflow {
assert(alignment != null);
assert(sizing != null);
assert(overflow != null); assert(overflow != null);
addAll(children); addAll(children);
} }
...@@ -271,20 +317,6 @@ class RenderStack extends RenderBox ...@@ -271,20 +317,6 @@ class RenderStack extends RenderBox
child.parentData = new StackParentData(); child.parentData = new StackParentData();
} }
/// Whether overflowing children should be clipped. See [Overflow].
///
/// Some children in a stack might overflow its box. When this flag is set to
/// [Overflow.clipped], children cannot paint outside of the stack's box.
Overflow get overflow => _overflow;
Overflow _overflow;
set overflow(Overflow value) {
assert(value != null);
if (_overflow != value) {
_overflow = value;
markNeedsPaint();
}
}
/// How to align the non-positioned children in the stack. /// How to align the non-positioned children in the stack.
/// ///
/// The non-positioned children are placed relative to each other such that /// The non-positioned children are placed relative to each other such that
...@@ -294,12 +326,42 @@ class RenderStack extends RenderBox ...@@ -294,12 +326,42 @@ class RenderStack extends RenderBox
FractionalOffset get alignment => _alignment; FractionalOffset get alignment => _alignment;
FractionalOffset _alignment; FractionalOffset _alignment;
set alignment(FractionalOffset value) { set alignment(FractionalOffset value) {
assert(value != null);
if (_alignment != value) { if (_alignment != value) {
_alignment = value; _alignment = value;
markNeedsLayout(); markNeedsLayout();
} }
} }
/// How to size the non-positioned children in the stack.
///
/// The constraints passed into the [RenderStack] from its parent are either
/// loosened ([StackFit.loose]) or tightened to their biggest size
/// ([StackFit.expand]).
StackFit get sizing => _sizing;
StackFit _sizing;
set sizing(StackFit value) {
assert(value != null);
if (_sizing != value) {
_sizing = value;
markNeedsLayout();
}
}
/// Whether overflowing children should be clipped. See [Overflow].
///
/// Some children in a stack might overflow its box. When this flag is set to
/// [Overflow.clipped], children cannot paint outside of the stack's box.
Overflow get overflow => _overflow;
Overflow _overflow;
set overflow(Overflow value) {
assert(value != null);
if (_overflow != value) {
_overflow = value;
markNeedsPaint();
}
}
double _getIntrinsicDimension(double mainChildSizeGetter(RenderBox child)) { double _getIntrinsicDimension(double mainChildSizeGetter(RenderBox child)) {
double extent = 0.0; double extent = 0.0;
RenderBox child = firstChild; RenderBox child = firstChild;
...@@ -343,8 +405,23 @@ class RenderStack extends RenderBox ...@@ -343,8 +405,23 @@ class RenderStack extends RenderBox
_hasVisualOverflow = false; _hasVisualOverflow = false;
bool hasNonPositionedChildren = false; bool hasNonPositionedChildren = false;
double width = 0.0; double width = constraints.minWidth;
double height = 0.0; double height = constraints.minHeight;
BoxConstraints nonPositionedConstraints;
assert(sizing != null);
switch (sizing) {
case StackFit.loose:
nonPositionedConstraints = constraints.loosen();
break;
case StackFit.expand:
nonPositionedConstraints = new BoxConstraints.tight(constraints.biggest);
break;
case StackFit.passthrough:
nonPositionedConstraints = constraints;
break;
}
assert(nonPositionedConstraints != null);
RenderBox child = firstChild; RenderBox child = firstChild;
while (child != null) { while (child != null) {
...@@ -353,8 +430,7 @@ class RenderStack extends RenderBox ...@@ -353,8 +430,7 @@ class RenderStack extends RenderBox
if (!childParentData.isPositioned) { if (!childParentData.isPositioned) {
hasNonPositionedChildren = true; hasNonPositionedChildren = true;
child.layout(constraints, parentUsesSize: true); child.layout(nonPositionedConstraints, parentUsesSize: true);
childParentData.offset = Offset.zero;
final Size childSize = child.size; final Size childSize = child.size;
width = math.max(width, childSize.width); width = math.max(width, childSize.width);
......
...@@ -43,6 +43,7 @@ export 'package:flutter/rendering.dart' show ...@@ -43,6 +43,7 @@ export 'package:flutter/rendering.dart' show
RelativeRect, RelativeRect,
ShaderCallback, ShaderCallback,
SingleChildLayoutDelegate, SingleChildLayoutDelegate,
StackFit,
TextOverflow, TextOverflow,
ValueChanged, ValueChanged,
ValueGetter, ValueGetter,
...@@ -1647,6 +1648,7 @@ class Stack extends MultiChildRenderObjectWidget { ...@@ -1647,6 +1648,7 @@ class Stack extends MultiChildRenderObjectWidget {
Stack({ Stack({
Key key, Key key,
this.alignment: FractionalOffset.topLeft, this.alignment: FractionalOffset.topLeft,
this.sizing: StackFit.loose,
this.overflow: Overflow.clip, this.overflow: Overflow.clip,
List<Widget> children: const <Widget>[], List<Widget> children: const <Widget>[],
}) : super(key: key, children: children); }) : super(key: key, children: children);
...@@ -1659,6 +1661,13 @@ class Stack extends MultiChildRenderObjectWidget { ...@@ -1659,6 +1661,13 @@ class Stack extends MultiChildRenderObjectWidget {
/// each non-positioned child will be located at the same global coordinate. /// each non-positioned child will be located at the same global coordinate.
final FractionalOffset alignment; final FractionalOffset alignment;
/// How to size the non-positioned children in the stack.
///
/// The constraints passed into the [Stack] from its parent are either
/// loosened ([StackFit.loose]) or tightened to their biggest size
/// ([StackFit.expand]).
final StackFit sizing;
/// Whether overflowing children should be clipped. See [Overflow]. /// Whether overflowing children should be clipped. See [Overflow].
/// ///
/// Some children in a stack might overflow its box. When this flag is set to /// Some children in a stack might overflow its box. When this flag is set to
...@@ -1669,7 +1678,8 @@ class Stack extends MultiChildRenderObjectWidget { ...@@ -1669,7 +1678,8 @@ class Stack extends MultiChildRenderObjectWidget {
RenderStack createRenderObject(BuildContext context) { RenderStack createRenderObject(BuildContext context) {
return new RenderStack( return new RenderStack(
alignment: alignment, alignment: alignment,
overflow: overflow sizing: sizing,
overflow: overflow,
); );
} }
...@@ -1677,6 +1687,7 @@ class Stack extends MultiChildRenderObjectWidget { ...@@ -1677,6 +1687,7 @@ class Stack extends MultiChildRenderObjectWidget {
void updateRenderObject(BuildContext context, RenderStack renderObject) { void updateRenderObject(BuildContext context, RenderStack renderObject) {
renderObject renderObject
..alignment = alignment ..alignment = alignment
..sizing = sizing
..overflow = overflow; ..overflow = overflow;
} }
} }
...@@ -1696,9 +1707,10 @@ class IndexedStack extends Stack { ...@@ -1696,9 +1707,10 @@ class IndexedStack extends Stack {
IndexedStack({ IndexedStack({
Key key, Key key,
FractionalOffset alignment: FractionalOffset.topLeft, FractionalOffset alignment: FractionalOffset.topLeft,
StackFit sizing: StackFit.loose,
this.index: 0, this.index: 0,
List<Widget> children: const <Widget>[], List<Widget> children: const <Widget>[],
}) : super(key: key, alignment: alignment, children: children); }) : super(key: key, alignment: alignment, sizing: sizing, children: children);
/// The index of the child to show. /// The index of the child to show.
final int index; final int index;
......
...@@ -198,9 +198,8 @@ class Overlay extends StatefulWidget { ...@@ -198,9 +198,8 @@ class Overlay extends StatefulWidget {
/// The initial entries will be inserted into the overlay when its associated /// The initial entries will be inserted into the overlay when its associated
/// [OverlayState] is initialized. /// [OverlayState] is initialized.
/// ///
/// Rather than creating an overlay, consider using the overlay that has /// Rather than creating an overlay, consider using the overlay that is
/// already been created by the [WidgetsApp] or the [MaterialApp] for this /// created by the [WidgetsApp] or the [MaterialApp] for the application.
/// application.
const Overlay({ const Overlay({
Key key, Key key,
this.initialEntries: const <OverlayEntry>[] this.initialEntries: const <OverlayEntry>[]
...@@ -362,7 +361,10 @@ class OverlayState extends State<Overlay> with TickerProviderStateMixin { ...@@ -362,7 +361,10 @@ class OverlayState extends State<Overlay> with TickerProviderStateMixin {
} }
} }
return new _Theatre( return new _Theatre(
onstage: new Stack(children: onstageChildren.reversed.toList(growable: false)), onstage: new Stack(
sizing: StackFit.expand,
children: onstageChildren.reversed.toList(growable: false),
),
offstage: offstageChildren, offstage: offstageChildren,
); );
} }
......
...@@ -6,7 +6,7 @@ import 'package:flutter_test/flutter_test.dart'; ...@@ -6,7 +6,7 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
void main() { void main() {
testWidgets('InputDecorator always expands', (WidgetTester tester) async { testWidgets('InputDecorator always expands horizontally', (WidgetTester tester) async {
final Key key = new UniqueKey(); final Key key = new UniqueKey();
await tester.pumpWidget(new Material( await tester.pumpWidget(new Material(
......
...@@ -45,4 +45,6 @@ void main() { ...@@ -45,4 +45,6 @@ void main() {
expect(green.size.width, equals(100.0)); expect(green.size.width, equals(100.0));
expect(green.size.height, equals(100.0)); expect(green.size.height, equals(100.0));
}); });
// More tests in ../widgets/stack_test.dart
} }
...@@ -76,7 +76,7 @@ void main() { ...@@ -76,7 +76,7 @@ void main() {
await tester.pump(const Duration(seconds: 1)); // end transition await tester.pump(const Duration(seconds: 1)); // end transition
expect(find.byKey(const ValueKey<String>('barrier')), findsNothing, expect(find.byKey(const ValueKey<String>('barrier')), findsNothing,
reason: 'because the barrier was dismissed'); reason: 'The route should have been dismissed by tapping the barrier.');
}); });
} }
......
...@@ -22,25 +22,27 @@ void main() { ...@@ -22,25 +22,27 @@ void main() {
// //
await tester.pumpWidget( await tester.pumpWidget(
new Stack( new Stack(
sizing: StackFit.expand,
children: <Widget>[ children: <Widget>[
const Semantics( const Semantics(
label: 'L1' label: 'L1',
), ),
new Semantics( new Semantics(
label: 'L2', label: 'L2',
child: new Stack( child: new Stack(
sizing: StackFit.expand,
children: <Widget>[ children: <Widget>[
const Semantics( const Semantics(
checked: true checked: true,
), ),
const Semantics( const Semantics(
checked: false checked: false,
) ),
] ],
) ),
) ),
] ],
) ),
); );
expect(semantics, hasSemantics( expect(semantics, hasSemantics(
...@@ -76,23 +78,25 @@ void main() { ...@@ -76,23 +78,25 @@ void main() {
// //
await tester.pumpWidget( await tester.pumpWidget(
new Stack( new Stack(
sizing: StackFit.expand,
children: <Widget>[ children: <Widget>[
const Semantics( const Semantics(
label: 'L1' label: 'L1',
), ),
new Semantics( new Semantics(
label: 'L2', label: 'L2',
child: new Stack( child: new Stack(
sizing: StackFit.expand,
children: <Widget>[ children: <Widget>[
const Semantics( const Semantics(
checked: true checked: true,
), ),
const Semantics() const Semantics(),
] ],
) ),
) ),
] ],
) ),
); );
expect(semantics, hasSemantics( expect(semantics, hasSemantics(
...@@ -118,21 +122,23 @@ void main() { ...@@ -118,21 +122,23 @@ void main() {
// //
await tester.pumpWidget( await tester.pumpWidget(
new Stack( new Stack(
sizing: StackFit.expand,
children: <Widget>[ children: <Widget>[
const Semantics(), const Semantics(),
new Semantics( new Semantics(
label: 'L2', label: 'L2',
child: new Stack( child: new Stack(
sizing: StackFit.expand,
children: <Widget>[ children: <Widget>[
const Semantics( const Semantics(
checked: true checked: true,
), ),
const Semantics() const Semantics(),
] ],
) ),
) ),
] ],
) ),
); );
expect(semantics, hasSemantics( expect(semantics, hasSemantics(
......
...@@ -14,16 +14,17 @@ void main() { ...@@ -14,16 +14,17 @@ void main() {
await tester.pumpWidget( await tester.pumpWidget(
new Stack( new Stack(
sizing: StackFit.expand,
children: <Widget>[ children: <Widget>[
const Semantics( const Semantics(
// this tests that empty nodes disappear // this tests that empty nodes disappear
), ),
const Semantics( const Semantics(
// this tests whether you can have a container with no other semantics // this tests whether you can have a container with no other semantics
container: true container: true,
), ),
const Semantics( const Semantics(
label: 'label' // (force a fork) label: 'label', // (force a fork)
), ),
] ]
) )
......
...@@ -19,6 +19,7 @@ void main() { ...@@ -19,6 +19,7 @@ void main() {
label = '1'; label = '1';
await tester.pumpWidget( await tester.pumpWidget(
new Stack( new Stack(
sizing: StackFit.expand,
children: <Widget>[ children: <Widget>[
new MergeSemantics( new MergeSemantics(
child: new Semantics( child: new Semantics(
...@@ -26,19 +27,20 @@ void main() { ...@@ -26,19 +27,20 @@ void main() {
container: true, container: true,
child: new Semantics( child: new Semantics(
container: true, container: true,
label: label label: label,
) )
) )
), ),
new MergeSemantics( new MergeSemantics(
child: new Stack( child: new Stack(
sizing: StackFit.expand,
children: <Widget>[ children: <Widget>[
const Semantics( const Semantics(
checked: true checked: true,
), ),
new Semantics( new Semantics(
label: label label: label,
) ),
] ]
) )
), ),
...@@ -69,6 +71,7 @@ void main() { ...@@ -69,6 +71,7 @@ void main() {
label = '2'; label = '2';
await tester.pumpWidget( await tester.pumpWidget(
new Stack( new Stack(
sizing: StackFit.expand,
children: <Widget>[ children: <Widget>[
new MergeSemantics( new MergeSemantics(
child: new Semantics( child: new Semantics(
...@@ -76,18 +79,19 @@ void main() { ...@@ -76,18 +79,19 @@ void main() {
container: true, container: true,
child: new Semantics( child: new Semantics(
container: true, container: true,
label: label label: label,
) )
) )
), ),
new MergeSemantics( new MergeSemantics(
child: new Stack( child: new Stack(
sizing: StackFit.expand,
children: <Widget>[ children: <Widget>[
const Semantics( const Semantics(
checked: true checked: true,
), ),
new Semantics( new Semantics(
label: label label: label,
) )
] ]
) )
......
...@@ -44,6 +44,8 @@ class TestSemantics { ...@@ -44,6 +44,8 @@ class TestSemantics {
final String label; final String label;
/// The bounding box for this node in its coordinate system. /// The bounding box for this node in its coordinate system.
///
/// Defaults to filling the screen.
final Rect rect; final Rect rect;
/// The transform from this node's coordinate system to its parent's coordinate system. /// The transform from this node's coordinate system to its parent's coordinate system.
...@@ -62,7 +64,7 @@ class TestSemantics { ...@@ -62,7 +64,7 @@ class TestSemantics {
actions: actions, actions: actions,
label: label, label: label,
rect: rect, rect: rect,
transform: transform transform: transform,
); );
} }
......
...@@ -343,4 +343,70 @@ void main() { ...@@ -343,4 +343,70 @@ void main() {
box.paint(context, Offset.zero); box.paint(context, Offset.zero);
expect(context.invocations.first.memberName, equals(#paintChild)); expect(context.invocations.first.memberName, equals(#paintChild));
}); });
testWidgets('Stack sizing: default', (WidgetTester tester) async {
final List<String> logs = <String>[];
await tester.pumpWidget(
new Center(
child: new ConstrainedBox(
constraints: const BoxConstraints(
minWidth: 2.0,
maxWidth: 3.0,
minHeight: 5.0,
maxHeight: 7.0,
),
child: new Stack(
children: <Widget>[
new LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
logs.add(constraints.toString());
return const Placeholder();
},
),
],
),
),
),
);
expect(logs, <String>['BoxConstraints(0.0<=w<=3.0, 0.0<=h<=7.0)']);
});
testWidgets('Stack sizing: explicit', (WidgetTester tester) async {
final List<String> logs = <String>[];
Widget buildStack(StackFit sizing) {
return new Center(
child: new ConstrainedBox(
constraints: const BoxConstraints(
minWidth: 2.0,
maxWidth: 3.0,
minHeight: 5.0,
maxHeight: 7.0,
),
child: new Stack(
sizing: sizing,
children: <Widget>[
new LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
logs.add(constraints.toString());
return const Placeholder();
},
),
],
),
),
);
}
await tester.pumpWidget(buildStack(StackFit.loose));
logs.add('=1=');
await tester.pumpWidget(buildStack(StackFit.expand));
logs.add('=2=');
await tester.pumpWidget(buildStack(StackFit.passthrough));
expect(logs, <String>[
'BoxConstraints(0.0<=w<=3.0, 0.0<=h<=7.0)',
'=1=',
'BoxConstraints(w=3.0, h=7.0)',
'=2=',
'BoxConstraints(2.0<=w<=3.0, 5.0<=h<=7.0)'
]);
});
} }
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