diff --git a/sky/packages/sky/lib/src/rendering/shifted_box.dart b/sky/packages/sky/lib/src/rendering/shifted_box.dart index 581fbbdbec299d8ced16ccb240a83179cc035b2b..bd7a3a682cf236c491c22e1bcd321b535711a612 100644 --- a/sky/packages/sky/lib/src/rendering/shifted_box.dart +++ b/sky/packages/sky/lib/src/rendering/shifted_box.dart @@ -205,6 +205,71 @@ class RenderPositionedBox extends RenderShiftedBox { String debugDescribeSettings(String prefix) => '${super.debugDescribeSettings(prefix)}${prefix}alignment: $alignment\n'; } +/// A delegate for computing the layout of a render object with a single child. +class OneChildLayoutDelegate { + /// Returns the size of this object given the incomming constraints. + Size getSize(BoxConstraints constraints) => constraints.biggest; + + /// Returns the box constraints for the child given the incomming constraints. + BoxConstraints getConstraintsForChild(BoxConstraints constraints) => constraints; + + /// Returns the position where the child should be placed given the size of this object and the size of the child. + Point getPositionForChild(Size size, Size childSize) => Point.origin; +} + +class RenderCustomOneChildLayoutBox extends RenderShiftedBox { + RenderCustomOneChildLayoutBox({ + RenderBox child, + OneChildLayoutDelegate delegate + }) : _delegate = delegate, super(child) { + assert(delegate != null); + } + + OneChildLayoutDelegate get delegate => _delegate; + OneChildLayoutDelegate _delegate; + void set delegate (OneChildLayoutDelegate newDelegate) { + assert(newDelegate != null); + if (_delegate == newDelegate) + return; + _delegate = newDelegate; + markNeedsLayout(); + } + + Size _getSize(BoxConstraints constraints) { + return constraints.constrain(_delegate.getSize(constraints)); + } + + double getMinIntrinsicWidth(BoxConstraints constraints) { + return _getSize(constraints).width; + } + + double getMaxIntrinsicWidth(BoxConstraints constraints) { + return _getSize(constraints).width; + } + + double getMinIntrinsicHeight(BoxConstraints constraints) { + return _getSize(constraints).height; + } + + double getMaxIntrinsicHeight(BoxConstraints constraints) { + return _getSize(constraints).height; + } + + bool get sizedByParent => true; + + void performResize() { + size = _getSize(constraints); + } + + void performLayout() { + if (child != null) { + child.layout(delegate.getConstraintsForChild(constraints), parentUsesSize: true); + final BoxParentData childParentData = child.parentData; + childParentData.position = delegate.getPositionForChild(size, child.size); + } + } +} + class RenderBaseline extends RenderShiftedBox { RenderBaseline({ diff --git a/sky/packages/sky/lib/src/widgets/basic.dart b/sky/packages/sky/lib/src/widgets/basic.dart index ff8ffa08ca2bf433557f4f64931ae21b598136f5..41d08cf8f7f87bb79158f77a3a47553513ffcd43 100644 --- a/sky/packages/sky/lib/src/widgets/basic.dart +++ b/sky/packages/sky/lib/src/widgets/basic.dart @@ -35,6 +35,7 @@ export 'package:flutter/rendering.dart' show LinearGradient, Matrix4, Offset, + OneChildLayoutDelegate, Paint, Path, PlainTextSpan, @@ -139,13 +140,21 @@ class CustomPaint extends OneChildRenderObjectWidget { assert(onPaint != null); } + /// This widget repaints whenver you supply a new onPaint callback. + /// + /// If you use an anonymous closure for the onPaint callback, you'll trigger + /// a repaint every time you build this widget, which might not be what you + /// intend. Instead, consider passing a reference to a member function, which + /// has a more stable identity. final CustomPaintCallback onPaint; - final Object token; // set this to be repainted automatically when the token changes + + /// This widget repaints whenever you supply a new token. + final Object token; RenderCustomPaint createRenderObject() => new RenderCustomPaint(onPaint: onPaint); void updateRenderObject(RenderCustomPaint renderObject, CustomPaint oldWidget) { - if (oldWidget != null && oldWidget.token != token) + if (oldWidget.token != token) renderObject.markNeedsPaint(); renderObject.onPaint = onPaint; } @@ -243,6 +252,35 @@ class Center extends Align { : super(key: key, shrinkWrap: shrinkWrap, child: child); } +class CustomOneChildLayout extends OneChildRenderObjectWidget { + CustomOneChildLayout({ + Key key, + this.delegate, + this.token, + Widget child + }) : super(key: key, child: child) { + assert(delegate != null); + } + + /// A long-lived delegate that controls the layout of this widget. + /// + /// Whenever the delegate changes, we need to recompute the layout of this + /// widget, which means you might not want to create a new delegate instance + /// every time you build this widget. Instead, consider using a long-lived + /// deletate (perhaps held in a component's state) that you re-use every time + /// you build this widget. + final OneChildLayoutDelegate delegate; + final Object token; + + RenderCustomOneChildLayoutBox createRenderObject() => new RenderCustomOneChildLayoutBox(delegate: delegate); + + void updateRenderObject(RenderCustomOneChildLayoutBox renderObject, CustomOneChildLayout oldWidget) { + if (oldWidget.token != token) + renderObject.markNeedsLayout(); + renderObject.delegate = delegate; + } +} + class SizedBox extends OneChildRenderObjectWidget { SizedBox({ Key key, this.width, this.height, Widget child }) : super(key: key, child: child); diff --git a/sky/unit/test/widget/custom_one_child_layout_test.dart b/sky/unit/test/widget/custom_one_child_layout_test.dart new file mode 100644 index 0000000000000000000000000000000000000000..6010414d47e99c3d8174617b423e2e528a4e10f0 --- /dev/null +++ b/sky/unit/test/widget/custom_one_child_layout_test.dart @@ -0,0 +1,59 @@ +import 'package:flutter/widgets.dart'; +import 'package:test/test.dart'; + +import 'widget_tester.dart'; + +class TestOneChildLayoutDelegate extends OneChildLayoutDelegate { + BoxConstraints constraintsFromGetSize; + BoxConstraints constraintsFromGetConstraintsForChild; + Size sizeFromGetPositionForChild; + Size childSizeFromGetPositionForChild; + + Size getSize(BoxConstraints constraints) { + constraintsFromGetSize = constraints; + return new Size(200.0, 300.0); + } + + BoxConstraints getConstraintsForChild(BoxConstraints constraints) { + constraintsFromGetConstraintsForChild = constraints; + return new BoxConstraints( + minWidth: 100.0, + maxWidth: 150.0, + minHeight: 200.0, + maxHeight: 400.0 + ); + } + + Point getPositionForChild(Size size, Size childSize) { + sizeFromGetPositionForChild = size; + childSizeFromGetPositionForChild = childSize; + return Point.origin; + } +} + +void main() { + test('Control test for CustomOneChildLayout', () { + testWidgets((WidgetTester tester) { + TestOneChildLayoutDelegate delegate = new TestOneChildLayoutDelegate(); + tester.pumpWidget(new Center( + child: new CustomOneChildLayout(delegate: delegate, child: new Container()) + )); + + expect(delegate.constraintsFromGetSize.minWidth, 0.0); + expect(delegate.constraintsFromGetSize.maxWidth, 800.0); + expect(delegate.constraintsFromGetSize.minHeight, 0.0); + expect(delegate.constraintsFromGetSize.maxHeight, 600.0); + + expect(delegate.constraintsFromGetConstraintsForChild.minWidth, 0.0); + expect(delegate.constraintsFromGetConstraintsForChild.maxWidth, 800.0); + expect(delegate.constraintsFromGetConstraintsForChild.minHeight, 0.0); + expect(delegate.constraintsFromGetConstraintsForChild.maxHeight, 600.0); + + expect(delegate.sizeFromGetPositionForChild.width, 200.0); + expect(delegate.sizeFromGetPositionForChild.height, 300.0); + + expect(delegate.childSizeFromGetPositionForChild.width, 150.0); + expect(delegate.childSizeFromGetPositionForChild.height, 400.0); + }); + }); +}