diff --git a/packages/flutter/lib/src/rendering/proxy_box.dart b/packages/flutter/lib/src/rendering/proxy_box.dart index dbba8781e4..8f3d50fdcb 100644 --- a/packages/flutter/lib/src/rendering/proxy_box.dart +++ b/packages/flutter/lib/src/rendering/proxy_box.dart @@ -145,6 +145,98 @@ class RenderConstrainedBox extends RenderProxyBox { String debugDescribeSettings(String prefix) => '${super.debugDescribeSettings(prefix)}${prefix}additionalConstraints: ${additionalConstraints}\n'; } +/// A render object that, for both width and height, imposes a tight constraint +/// on its child that is a multiple (typically less than 1.0) of the maximum +/// constraint it received from its parent on that axis. If the factor for a +/// given axis is null, then the constraints from the parent are just passed +/// through instead. +/// +/// It then tries to size itself the size of its child. +class RenderFractionallySizedBox extends RenderProxyBox { + RenderFractionallySizedBox({ + RenderBox child, + double widthFactor, + double heightFactor + }) : _widthFactor = widthFactor, _heightFactor = heightFactor, super(child) { + assert(_widthFactor == null || _widthFactor > 0.0); + assert(_heightFactor == null || _heightFactor > 0.0); + } + + /// The multiple to apply to the incoming maximum width constraint to use as + /// the tight width constraint for the child, or null to pass through the + /// constraints given by the parent. + double get widthFactor => _widthFactor; + double _widthFactor; + void set widthFactor (double value) { + assert(value == null || value > 0.0); + if (_widthFactor == value) + return; + _widthFactor = value; + markNeedsLayout(); + } + + /// The multiple to apply to the incoming maximum height constraint to use as + /// the tight height constraint for the child, or null to pass through the + /// constraints given by the parent. + double get heightFactor => _heightFactor; + double _heightFactor; + void set heightFactor (double value) { + assert(value == null || value > 0.0); + if (_heightFactor == value) + return; + _heightFactor = value; + markNeedsLayout(); + } + + BoxConstraints _computeChildConstraints(BoxConstraints constraints) { + return new BoxConstraints( + minWidth: _widthFactor == null ? constraints.minWidth : constraints.maxWidth * _widthFactor, + maxWidth: _widthFactor == null ? constraints.maxWidth : constraints.maxWidth * _widthFactor, + minHeight: _heightFactor == null ? constraints.minHeight : constraints.maxHeight * _heightFactor, + maxHeight: _heightFactor == null ? constraints.maxHeight : constraints.maxHeight * _heightFactor + ); + } + + double getMinIntrinsicWidth(BoxConstraints constraints) { + if (child != null) + return child.getMinIntrinsicWidth(_computeChildConstraints(constraints)); + return _computeChildConstraints(constraints).constrainWidth(0.0); + } + + double getMaxIntrinsicWidth(BoxConstraints constraints) { + if (child != null) + return child.getMaxIntrinsicWidth(_computeChildConstraints(constraints)); + return _computeChildConstraints(constraints).constrainWidth(0.0); + } + + double getMinIntrinsicHeight(BoxConstraints constraints) { + if (child != null) + return child.getMinIntrinsicHeight(_computeChildConstraints(constraints)); + return _computeChildConstraints(constraints).constrainHeight(0.0); + } + + double getMaxIntrinsicHeight(BoxConstraints constraints) { + if (child != null) + return child.getMaxIntrinsicHeight(_computeChildConstraints(constraints)); + return _computeChildConstraints(constraints).constrainHeight(0.0); + } + + void performLayout() { + if (child != null) { + child.layout(_computeChildConstraints(constraints), parentUsesSize: true); + size = child.size; + } else { + size = _computeChildConstraints(constraints).constrain(Size.zero); + } + } + + String debugDescribeSettings(String prefix) { + return '${super.debugDescribeSettings(prefix)}' + + '${prefix}widthFactor: ${_widthFactor ?? "pass-through"}\n' + + '${prefix}heightFactor: ${_heightFactor ?? "pass-through"}\n'; + } +} + /// A render object that imposes different constraints on its child than it gets /// from its parent, possibly allowing the child to overflow the parent. /// diff --git a/packages/flutter/lib/src/widgets/basic.dart b/packages/flutter/lib/src/widgets/basic.dart index 45caafc4b4..66bc316745 100644 --- a/packages/flutter/lib/src/widgets/basic.dart +++ b/packages/flutter/lib/src/widgets/basic.dart @@ -244,6 +244,24 @@ class ConstrainedBox extends OneChildRenderObjectWidget { } } +class FractionallySizedBox extends OneChildRenderObjectWidget { + FractionallySizedBox({ Key key, this.width, this.height, Widget child }) + : super(key: key, child: child); + + final double width; + final double height; + + RenderFractionallySizedBox createRenderObject() => new RenderFractionallySizedBox( + widthFactor: width, + heightFactor: height + ); + + void updateRenderObject(RenderFractionallySizedBox renderObject, SizedBox oldWidget) { + renderObject.widthFactor = width; + renderObject.heightFactor = height; + } +} + class OverflowBox extends OneChildRenderObjectWidget { OverflowBox({ Key key, this.minWidth, this.maxWidth, this.minHeight, this.maxHeight, Widget child }) : super(key: key, child: child); diff --git a/packages/unit/test/widget/fractionally_sized_box_test.dart b/packages/unit/test/widget/fractionally_sized_box_test.dart new file mode 100644 index 0000000000..4343f5a950 --- /dev/null +++ b/packages/unit/test/widget/fractionally_sized_box_test.dart @@ -0,0 +1,35 @@ +import 'package:sky/widgets.dart'; +import 'package:test/test.dart'; + +import 'widget_tester.dart'; + +void main() { + test('FractionallySizedBox', () { + testWidgets((WidgetTester tester) { + Size detectedSize; + GlobalKey inner = new GlobalKey(); + tester.pumpWidget(new OverflowBox( + minWidth: 0.0, + maxWidth: 100.0, + minHeight: 0.0, + maxHeight: 100.0, + child: new Center( + child: new FractionallySizedBox( + width: 0.5, + height: 0.25, + child: new SizeObserver( + callback: (Size size) { + detectedSize = size; + }, + child: new Container( + key: inner + ) + ) + ) + ) + )); + expect(detectedSize, equals(const Size(50.0, 25.0))); + expect(inner.currentContext.findRenderObject().localToGlobal(Point.origin), equals(const Point(25.0, 37.5))); + }); + }); +}