diff --git a/packages/flutter/lib/src/rendering/overflow.dart b/packages/flutter/lib/src/rendering/overflow.dart index d58eff2e286..3db1df97438 100644 --- a/packages/flutter/lib/src/rendering/overflow.dart +++ b/packages/flutter/lib/src/rendering/overflow.dart @@ -7,146 +7,6 @@ import 'object.dart'; export 'package:flutter/src/painting/box_painter.dart'; -/// A render object that imposes different constraints on its child than it gets -/// from its parent, possibly allowing the child to overflow the parent. -/// -/// A render overflow box proxies most functions in the render box protocol to -/// its child, except that when laying out its child, it passes constraints -/// based on the minWidth, maxWidth, minHeight, and maxHeight fields instead of -/// just passing the parent's constraints in. Specifically, it overrides any of -/// the equivalent fields on the constraints given by the parent with the -/// constraints given by these fields for each such field that is not null. It -/// then sizes itself based on the parent's constraints' maxWidth and maxHeight, -/// ignoring the child's dimensions. -/// -/// For example, if you wanted a box to always render 50 pixels high, regardless -/// of where it was rendered, you would wrap it in a RenderOverflow with -/// minHeight and maxHeight set to 50.0. Generally speaking, to avoid confusing -/// behaviour around hit testing, a RenderOverflowBox should usually be wrapped -/// in a RenderClipRect. -/// -/// The child is positioned at the top left of the box. To position a smaller -/// child inside a larger parent, use [RenderPositionedBox] and -/// [RenderConstrainedBox] rather than RenderOverflowBox. -class RenderOverflowBox extends RenderBox with RenderObjectWithChildMixin { - RenderOverflowBox({ - RenderBox child, - double minWidth, - double maxWidth, - double minHeight, - double maxHeight - }) : _minWidth = minWidth, _maxWidth = maxWidth, _minHeight = minHeight, _maxHeight = maxHeight { - this.child = child; - } - - /// The minimum width constraint to give the child. Set this to null (the - /// default) to use the constraint from the parent instead. - double get minWidth => _minWidth; - double _minWidth; - void set minWidth (double value) { - if (_minWidth == value) - return; - _minWidth = value; - markNeedsLayout(); - } - - /// The maximum width constraint to give the child. Set this to null (the - /// default) to use the constraint from the parent instead. - double get maxWidth => _maxWidth; - double _maxWidth; - void set maxWidth (double value) { - if (_maxWidth == value) - return; - _maxWidth = value; - markNeedsLayout(); - } - - /// The minimum height constraint to give the child. Set this to null (the - /// default) to use the constraint from the parent instead. - double get minHeight => _minHeight; - double _minHeight; - void set minHeight (double value) { - if (_minHeight == value) - return; - _minHeight = value; - markNeedsLayout(); - } - - /// The maximum height constraint to give the child. Set this to null (the - /// default) to use the constraint from the parent instead. - double get maxHeight => _maxHeight; - double _maxHeight; - void set maxHeight (double value) { - if (_maxHeight == value) - return; - _maxHeight = value; - markNeedsLayout(); - } - - BoxConstraints _getInnerConstraints(BoxConstraints constraints) { - return new BoxConstraints( - minWidth: _minWidth ?? constraints.minWidth, - maxWidth: _maxWidth ?? constraints.maxWidth, - minHeight: _minHeight ?? constraints.minHeight, - maxHeight: _maxHeight ?? constraints.maxHeight - ); - } - - double getMinIntrinsicWidth(BoxConstraints constraints) { - assert(constraints.isNormalized); - return constraints.constrainWidth(); - } - - double getMaxIntrinsicWidth(BoxConstraints constraints) { - assert(constraints.isNormalized); - return constraints.constrainWidth(); - } - - double getMinIntrinsicHeight(BoxConstraints constraints) { - assert(constraints.isNormalized); - return constraints.constrainHeight(); - } - - double getMaxIntrinsicHeight(BoxConstraints constraints) { - assert(constraints.isNormalized); - return constraints.constrainHeight(); - } - - double computeDistanceToActualBaseline(TextBaseline baseline) { - if (child != null) - return child.getDistanceToActualBaseline(baseline); - return super.computeDistanceToActualBaseline(baseline); - } - - bool get sizedByParent => true; - - void performResize() { - size = constraints.biggest; - } - - void performLayout() { - if (child != null) - child.layout(_getInnerConstraints(constraints)); - } - - bool hitTestChildren(HitTestResult result, { Point position }) { - return child?.hitTest(result, position: position) ?? false; - } - - void paint(PaintingContext context, Offset offset) { - if (child != null) - context.paintChild(child, offset); - } - - void debugDescribeSettings(List settings) { - super.debugDescribeSettings(settings); - settings.add('minWidth: ${minWidth ?? "use parent minWidth constraint"}'); - settings.add('maxWidth: ${maxWidth ?? "use parent maxWidth constraint"}'); - settings.add('minHeight: ${minHeight ?? "use parent minHeight constraint"}'); - settings.add('maxHeight: ${maxHeight ?? "use parent maxHeight constraint"}'); - } -} - /// A render box that's a specific size but passes its original constraints through to its child, which will probably overflow class RenderSizedOverflowBox extends RenderBox with RenderObjectWithChildMixin { RenderSizedOverflowBox({ diff --git a/packages/flutter/lib/src/rendering/shifted_box.dart b/packages/flutter/lib/src/rendering/shifted_box.dart index 619e862ebe7..e39b3e6a5af 100644 --- a/packages/flutter/lib/src/rendering/shifted_box.dart +++ b/packages/flutter/lib/src/rendering/shifted_box.dart @@ -356,6 +356,158 @@ class RenderPositionedBox extends RenderShiftedBox { } } +/// A render object that imposes different constraints on its child than it gets +/// from its parent, possibly allowing the child to overflow the parent. +/// +/// A render overflow box proxies most functions in the render box protocol to +/// its child, except that when laying out its child, it passes constraints +/// based on the minWidth, maxWidth, minHeight, and maxHeight fields instead of +/// just passing the parent's constraints in. Specifically, it overrides any of +/// the equivalent fields on the constraints given by the parent with the +/// constraints given by these fields for each such field that is not null. It +/// then sizes itself based on the parent's constraints' maxWidth and maxHeight, +/// ignoring the child's dimensions. +/// +/// For example, if you wanted a box to always render 50 pixels high, regardless +/// of where it was rendered, you would wrap it in a RenderOverflow with +/// minHeight and maxHeight set to 50.0. Generally speaking, to avoid confusing +/// behaviour around hit testing, a RenderOverflowBox should usually be wrapped +/// in a RenderClipRect. +/// +/// The child is positioned at the top left of the box. To position a smaller +/// child inside a larger parent, use [RenderPositionedBox] and +/// [RenderConstrainedBox] rather than RenderOverflowBox. +class RenderOverflowBox extends RenderShiftedBox { + RenderOverflowBox({ + RenderBox child, + double minWidth, + double maxWidth, + double minHeight, + double maxHeight, + FractionalOffset alignment: const FractionalOffset(0.5, 0.5) + }) : _minWidth = minWidth, + _maxWidth = maxWidth, + _minHeight = minHeight, + _maxHeight = maxHeight, + _alignment = alignment, + super(child); + + /// The minimum width constraint to give the child. Set this to null (the + /// default) to use the constraint from the parent instead. + double get minWidth => _minWidth; + double _minWidth; + void set minWidth (double value) { + if (_minWidth == value) + return; + _minWidth = value; + markNeedsLayout(); + } + + /// The maximum width constraint to give the child. Set this to null (the + /// default) to use the constraint from the parent instead. + double get maxWidth => _maxWidth; + double _maxWidth; + void set maxWidth (double value) { + if (_maxWidth == value) + return; + _maxWidth = value; + markNeedsLayout(); + } + + /// The minimum height constraint to give the child. Set this to null (the + /// default) to use the constraint from the parent instead. + double get minHeight => _minHeight; + double _minHeight; + void set minHeight (double value) { + if (_minHeight == value) + return; + _minHeight = value; + markNeedsLayout(); + } + + /// The maximum height constraint to give the child. Set this to null (the + /// default) to use the constraint from the parent instead. + double get maxHeight => _maxHeight; + double _maxHeight; + void set maxHeight (double value) { + if (_maxHeight == value) + return; + _maxHeight = value; + markNeedsLayout(); + } + + /// How to align the child. + /// + /// The x and y values of the alignment control the horizontal and vertical + /// alignment, respectively. An x value of 0.0 means that the left edge of + /// the child is aligned with the left edge of the parent whereas an x value + /// of 1.0 means that the right edge of the child is aligned with the right + /// edge of the parent. Other values interpolate (and extrapolate) linearly. + /// For example, a value of 0.5 means that the center of the child is aligned + /// with the center of the parent. + FractionalOffset get alignment => _alignment; + FractionalOffset _alignment; + void set alignment (FractionalOffset newAlignment) { + assert(newAlignment != null && newAlignment.dx != null && newAlignment.dy != null); + if (_alignment == newAlignment) + return; + _alignment = newAlignment; + markNeedsLayout(); + } + + BoxConstraints _getInnerConstraints(BoxConstraints constraints) { + return new BoxConstraints( + minWidth: _minWidth ?? constraints.minWidth, + maxWidth: _maxWidth ?? constraints.maxWidth, + minHeight: _minHeight ?? constraints.minHeight, + maxHeight: _maxHeight ?? constraints.maxHeight + ); + } + + double getMinIntrinsicWidth(BoxConstraints constraints) { + assert(constraints.isNormalized); + return constraints.minWidth; + } + + double getMaxIntrinsicWidth(BoxConstraints constraints) { + assert(constraints.isNormalized); + return constraints.minWidth; + } + + double getMinIntrinsicHeight(BoxConstraints constraints) { + assert(constraints.isNormalized); + return constraints.minHeight; + } + + double getMaxIntrinsicHeight(BoxConstraints constraints) { + assert(constraints.isNormalized); + return constraints.minHeight; + } + + bool get sizedByParent => true; + + void performResize() { + size = constraints.biggest; + } + + void performLayout() { + if (child != null) { + child.layout(_getInnerConstraints(constraints), parentUsesSize: true); + final BoxParentData childParentData = child.parentData; + childParentData.offset = _alignment.alongOffset(size - child.size); + } + } + + void debugDescribeSettings(List settings) { + super.debugDescribeSettings(settings); + settings.add('minWidth: ${minWidth ?? "use parent minWidth constraint"}'); + settings.add('maxWidth: ${maxWidth ?? "use parent maxWidth constraint"}'); + settings.add('minHeight: ${minHeight ?? "use parent minHeight constraint"}'); + settings.add('maxHeight: ${maxHeight ?? "use parent maxHeight constraint"}'); + settings.add('alignment: $alignment'); + } +} + /// A delegate for computing the layout of a render object with a single child. class OneChildLayoutDelegate { /// Returns the size of this object given the incoming constraints. diff --git a/packages/flutter/lib/src/widgets/basic.dart b/packages/flutter/lib/src/widgets/basic.dart index 41cc8b06916..06be46988f5 100644 --- a/packages/flutter/lib/src/widgets/basic.dart +++ b/packages/flutter/lib/src/widgets/basic.dart @@ -597,8 +597,15 @@ class FractionallySizedBox extends OneChildRenderObjectWidget { /// /// See [RenderOverflowBox] for details. class OverflowBox extends OneChildRenderObjectWidget { - OverflowBox({ Key key, this.minWidth, this.maxWidth, this.minHeight, this.maxHeight, Widget child }) - : super(key: key, child: child); + OverflowBox({ + Key key, + this.minWidth, + this.maxWidth, + this.minHeight, + this.maxHeight, + this.alignment: const FractionalOffset(0.5, 0.5), + Widget child + }) : super(key: key, child: child); /// The minimum width constraint to give the child. Set this to null (the /// default) to use the constraint from the parent instead. @@ -616,18 +623,32 @@ class OverflowBox extends OneChildRenderObjectWidget { /// default) to use the constraint from the parent instead. final double maxHeight; + /// How to align the child. + /// + /// The x and y values of the alignment control the horizontal and vertical + /// alignment, respectively. An x value of 0.0 means that the left edge of + /// the child is aligned with the left edge of the parent whereas an x value + /// of 1.0 means that the right edge of the child is aligned with the right + /// edge of the parent. Other values interpolate (and extrapolate) linearly. + /// For example, a value of 0.5 means that the center of the child is aligned + /// with the center of the parent. + final FractionalOffset alignment; + RenderOverflowBox createRenderObject() => new RenderOverflowBox( minWidth: minWidth, maxWidth: maxWidth, minHeight: minHeight, - maxHeight: maxHeight + maxHeight: maxHeight, + alignment: alignment ); void updateRenderObject(RenderOverflowBox renderObject, OverflowBox oldWidget) { - renderObject.minWidth = minWidth; - renderObject.maxWidth = maxWidth; - renderObject.minHeight = minHeight; - renderObject.maxHeight = maxHeight; + renderObject + ..minWidth = minWidth + ..maxWidth = maxWidth + ..minHeight = minHeight + ..maxHeight = maxHeight + ..alignment = alignment; } } @@ -2047,7 +2068,7 @@ class Semantics extends OneChildRenderObjectWidget { /// If 'container' is true, this Widget will introduce a new node in /// the semantics tree. Otherwise, the semantics will be merged with /// the semantics of any ancestors. - /// + /// /// The 'container' flag is implicitly set to true on the immediate /// semantics-providing descendants of a node where multiple /// children have semantics or have descendants providing semantics. @@ -2080,7 +2101,7 @@ class Semantics extends OneChildRenderObjectWidget { super.debugFillDescription(description); description.add('container: $container'); if (checked != null); - description.add('checked: $checked'); + description.add('checked: $checked'); if (label != null); description.add('label: "$label"'); } diff --git a/packages/flutter/test/rendering/overflow_test.dart b/packages/flutter/test/rendering/overflow_test.dart index d9f7c794e73..d8fbacdd903 100644 --- a/packages/flutter/test/rendering/overflow_test.dart +++ b/packages/flutter/test/rendering/overflow_test.dart @@ -30,7 +30,8 @@ void main() { child: new RenderCustomPaint( child: child = new RenderOverflowBox( child: text = new RenderParagraph(new PlainTextSpan('Hello World')), - maxHeight: height1 / 2.0 + maxHeight: height1 / 2.0, + alignment: const FractionalOffset(0.0, 0.0) ), painter: new TestCallbackPainter( onPaint: () { diff --git a/packages/flutter/test/widget/fractionally_sized_box_test.dart b/packages/flutter/test/widget/fractionally_sized_box_test.dart index ad3055d3c01..623c2d34fe4 100644 --- a/packages/flutter/test/widget/fractionally_sized_box_test.dart +++ b/packages/flutter/test/widget/fractionally_sized_box_test.dart @@ -17,6 +17,7 @@ void main() { maxWidth: 100.0, minHeight: 0.0, maxHeight: 100.0, + alignment: const FractionalOffset(0.0, 0.0), child: new Center( child: new FractionallySizedBox( width: 0.5, diff --git a/packages/flutter/test/widget/overflow_box_test.dart b/packages/flutter/test/widget/overflow_box_test.dart new file mode 100644 index 00000000000..7d1cc4fc150 --- /dev/null +++ b/packages/flutter/test/widget/overflow_box_test.dart @@ -0,0 +1,35 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/widgets.dart'; +import 'package:test/test.dart'; + +void main() { + test('OverflowBox control test', () { + testWidgets((WidgetTester tester) { + GlobalKey inner = new GlobalKey(); + tester.pumpWidget(new Align( + alignment: const FractionalOffset(1.0, 1.0), + child: new SizedBox( + width: 10.0, + height: 20.0, + child: new OverflowBox( + minWidth: 0.0, + maxWidth: 100.0, + minHeight: 0.0, + maxHeight: 50.0, + child: new Container( + key: inner + ) + ) + ) + )); + RenderBox box = inner.currentContext.findRenderObject(); + expect(box.localToGlobal(Point.origin), equals(const Point(745.0, 565.0))); + expect(box.size, equals(const Size(100.0, 50.0))); + }); + }); +}