diff --git a/packages/flutter/lib/rendering/block.dart b/packages/flutter/lib/rendering/block.dart index 7214c533d7..27d6af50d2 100644 --- a/packages/flutter/lib/rendering/block.dart +++ b/packages/flutter/lib/rendering/block.dart @@ -13,6 +13,7 @@ class BlockParentData extends BoxParentData with ContainerParentDataMixin, RenderBoxContainerDefaultsMixin { @@ -41,10 +42,10 @@ abstract class RenderBlockBase extends RenderBox with ContainerRenderObjectMixin } } - bool get _isVertical => _direction == BlockDirection.vertical; + bool get isVertical => _direction == BlockDirection.vertical; BoxConstraints _getInnerConstraints(BoxConstraints constraints) { - if (_isVertical) + if (isVertical) return new BoxConstraints.tightFor(width: constraints.constrainWidth(constraints.maxWidth)); return new BoxConstraints.tightFor(height: constraints.constrainHeight(constraints.maxHeight)); } @@ -54,7 +55,7 @@ abstract class RenderBlockBase extends RenderBox with ContainerRenderObjectMixin if (child == null) return 0.0; BoxParentData parentData = child.parentData; - return _isVertical ? + return isVertical ? parentData.position.y + child.size.height : parentData.position.x + child.size.width; } @@ -66,11 +67,11 @@ abstract class RenderBlockBase extends RenderBox with ContainerRenderObjectMixin while (child != null) { child.layout(innerConstraints, parentUsesSize: true); assert(child.parentData is BlockParentData); - child.parentData.position = _isVertical ? new Point(0.0, position) : new Point(position, 0.0); - position += _isVertical ? child.size.height : child.size.width; + child.parentData.position = isVertical ? new Point(0.0, position) : new Point(position, 0.0); + position += isVertical ? child.size.height : child.size.width; child = child.parentData.nextSibling; } - size = _isVertical ? + size = isVertical ? constraints.constrain(new Size(constraints.maxWidth, _mainAxisExtent)) : constraints.constrain(new Size(_mainAxisExtent, constraints.maxHeight)); assert(!size.isInfinite); @@ -87,7 +88,7 @@ class RenderBlock extends RenderBlockBase { double _getIntrinsicCrossAxis(BoxConstraints constraints, _ChildSizingFunction childSize) { double extent = 0.0; - BoxConstraints innerConstraints = _isVertical ? constraints.widthConstraints() : constraints.heightConstraints(); + BoxConstraints innerConstraints = isVertical ? constraints.widthConstraints() : constraints.heightConstraints(); RenderBox child = firstChild; while (child != null) { extent = math.max(extent, childSize(child, innerConstraints)); @@ -102,11 +103,11 @@ class RenderBlock extends RenderBlockBase { BoxConstraints innerConstraints = _getInnerConstraints(constraints); RenderBox child = firstChild; while (child != null) { - double childExtent = _isVertical ? + double childExtent = isVertical ? child.getMinIntrinsicHeight(innerConstraints) : child.getMinIntrinsicWidth(innerConstraints); assert(() { - if (_isVertical) + if (isVertical) return childExtent == child.getMaxIntrinsicHeight(innerConstraints); return childExtent == child.getMaxIntrinsicWidth(innerConstraints); }); @@ -118,7 +119,7 @@ class RenderBlock extends RenderBlockBase { } double getMinIntrinsicWidth(BoxConstraints constraints) { - if (_isVertical) { + if (isVertical) { return _getIntrinsicCrossAxis(constraints, (c, innerConstraints) => c.getMinIntrinsicWidth(innerConstraints)); } @@ -126,7 +127,7 @@ class RenderBlock extends RenderBlockBase { } double getMaxIntrinsicWidth(BoxConstraints constraints) { - if (_isVertical) { + if (isVertical) { return _getIntrinsicCrossAxis(constraints, (c, innerConstraints) => c.getMaxIntrinsicWidth(innerConstraints)); } @@ -134,14 +135,14 @@ class RenderBlock extends RenderBlockBase { } double getMinIntrinsicHeight(BoxConstraints constraints) { - if (_isVertical) + if (isVertical) return _getIntrinsicMainAxis(constraints); return _getIntrinsicCrossAxis(constraints, (c, innerConstraints) => c.getMinIntrinsicWidth(innerConstraints)); } double getMaxIntrinsicHeight(BoxConstraints constraints) { - if (_isVertical) + if (isVertical) return _getIntrinsicMainAxis(constraints); return _getIntrinsicCrossAxis(constraints, (c, innerConstraints) => c.getMaxIntrinsicWidth(innerConstraints)); @@ -152,7 +153,7 @@ class RenderBlock extends RenderBlockBase { } void performLayout() { - assert((_isVertical ? constraints.maxHeight >= double.INFINITY : constraints.maxWidth >= double.INFINITY) && + assert((isVertical ? constraints.maxHeight >= double.INFINITY : constraints.maxWidth >= double.INFINITY) && 'RenderBlock does not clip or resize its children, so it must be placed in a parent that does not constrain ' + 'the block\'s main direction. You probably want to put the RenderBlock inside a RenderViewport.' is String); super.performLayout(); @@ -170,14 +171,32 @@ class RenderBlock extends RenderBlockBase { class RenderBlockViewport extends RenderBlockBase { + // This class invokes a callbacks for layout and intrinsic + // dimensions. The main callback (constructor argument and property + // called "callback") is expected to modify the element's child + // list. The regular block layout algorithm is then applied to the + // children. The intrinsic dimension callbacks are called to + // determine intrinsic dimensions; if no value can be returned, they + // should not be set or, if set, should return null. + RenderBlockViewport({ LayoutCallback callback, + DimensionCallback totalExtentCallback, + DimensionCallback maxCrossAxisDimensionCallback, + DimensionCallback minCrossAxisDimensionCallback, + double startOffset: 0.0, List children, - double startOffset: 0.0 - }) : _callback = callback, _startOffset = startOffset, super(children: children); + BlockDirection direction: BlockDirection.vertical + }) : _callback = callback, + _totalExtentCallback = totalExtentCallback, + _maxCrossAxisDimensionCallback = maxCrossAxisDimensionCallback, + _minCrossAxisDimensionCallback = minCrossAxisDimensionCallback, + _startOffset = startOffset, + super(children: children, direction: direction); bool _inCallback = false; + // Called during layout. Mutate the child list appropriately. LayoutCallback _callback; LayoutCallback get callback => _callback; void set callback(LayoutCallback value) { @@ -188,6 +207,44 @@ class RenderBlockViewport extends RenderBlockBase { markNeedsLayout(); } + // Return the sum of the extent of all the children that could be included by the callback in one go. + // The extent is the dimension in the direction given by the 'direction' property. + DimensionCallback _totalExtentCallback; + DimensionCallback get totalExtentCallback => _totalExtentCallback; + void set totalExtentCallback(DimensionCallback value) { + assert(!_inCallback); + if (value == _totalExtentCallback) + return; + _totalExtentCallback = value; + markNeedsLayout(); + } + + // Return the minimum dimension across all the children that could + // be included in one go, in the direction orthogonal to that given + // by the 'direction' property. + DimensionCallback _minCrossAxisDimensionCallback; + DimensionCallback get minCrossAxisDimensionCallback => _minCrossAxisDimensionCallback; + void set minCrossAxisDimensionCallback(DimensionCallback value) { + assert(!_inCallback); + if (value == _minCrossAxisDimensionCallback) + return; + _minCrossAxisDimensionCallback = value; + markNeedsLayout(); + } + + // Return the maximum dimension across all the children that could + // be included in one go, in the direction orthogonal to that given + // by the 'direction' property. + DimensionCallback _maxCrossAxisDimensionCallback; + DimensionCallback get maxCrossAxisDimensionCallback => _maxCrossAxisDimensionCallback; + void set maxCrossAxisDimensionCallback(DimensionCallback value) { + assert(!_inCallback); + if (value == _maxCrossAxisDimensionCallback) + return; + _maxCrossAxisDimensionCallback = value; + markNeedsLayout(); + } + // you can set this from within the callback if necessary double _startOffset; double get startOffset => _startOffset; @@ -199,30 +256,50 @@ class RenderBlockViewport extends RenderBlockBase { markNeedsPaint(); } - double _noIntrinsicDimensions() { - assert(() { - 'RenderBlockViewport does not support returning intrinsic dimensions. ' + - 'Calculating the intrinsic dimensions would require walking the entire child list, ' + - 'which defeats the entire point of having a lazily-built list of children.'; - return false; - }); - return 0.0; + double _getIntrinsicDimension(BoxConstraints constraints, DimensionCallback intrinsicCallback, _Constrainer constrainer) { + assert(!_inCallback); + double result; + if (intrinsicCallback == null) { + assert(() { + 'RenderBlockViewport does not support returning intrinsic dimensions if the relevant callbacks have not been specified.'; + return false; + }); + return constrainer(0.0); + } + try { + _inCallback = true; + result = intrinsicCallback(constraints); + if (result == null) + result = constrainer(0.0); + assert(constrainer(result) == result); + } finally { + _inCallback = false; + } + return result; } double getMinIntrinsicWidth(BoxConstraints constraints) { - return _noIntrinsicDimensions(); + if (isVertical) + return _getIntrinsicDimension(constraints, minCrossAxisDimensionCallback, constraints.constrainWidth); + return constraints.constrainWidth(0.0); } double getMaxIntrinsicWidth(BoxConstraints constraints) { - return _noIntrinsicDimensions(); + if (isVertical) + return _getIntrinsicDimension(constraints, maxCrossAxisDimensionCallback, constraints.constrainWidth); + return _getIntrinsicDimension(constraints, totalExtentCallback, constraints.constrainWidth); } double getMinIntrinsicHeight(BoxConstraints constraints) { - return _noIntrinsicDimensions(); + if (!isVertical) + return _getIntrinsicDimension(constraints, minCrossAxisDimensionCallback, constraints.constrainHeight); + return constraints.constrainHeight(0.0); } double getMaxIntrinsicHeight(BoxConstraints constraints) { - return _noIntrinsicDimensions(); + if (!isVertical) + return _getIntrinsicDimension(constraints, maxCrossAxisDimensionCallback, constraints.constrainHeight); + return _getIntrinsicDimension(constraints, totalExtentCallback, constraints.constrainHeight); } // We don't override computeDistanceToActualBaseline(), because we diff --git a/packages/flutter/lib/rendering/object.dart b/packages/flutter/lib/rendering/object.dart index c444440d34..bd468fa22d 100644 --- a/packages/flutter/lib/rendering/object.dart +++ b/packages/flutter/lib/rendering/object.dart @@ -292,6 +292,7 @@ abstract class Constraints { typedef void RenderObjectVisitor(RenderObject child); typedef void LayoutCallback(Constraints constraints); +typedef double DimensionCallback(Constraints constraints); abstract class RenderObject extends AbstractNode implements HitTestTarget { diff --git a/packages/flutter/lib/widgets/mixed_viewport.dart b/packages/flutter/lib/widgets/mixed_viewport.dart index c8e58c486a..4e1a3615bb 100644 --- a/packages/flutter/lib/widgets/mixed_viewport.dart +++ b/packages/flutter/lib/widgets/mixed_viewport.dart @@ -117,13 +117,29 @@ class MixedViewport extends RenderObjectWrapper { assert(renderObject == this.renderObject); // TODO(ianh): Remove this once the analyzer is cleverer } + double _noIntrinsicDimensions(BoxConstraints constraints) { + assert(() { + 'MixedViewport does not support returning intrinsic dimensions. ' + + 'Calculating the intrinsic dimensions would require walking the entire child list, ' + + 'which defeats the entire point of having a lazily-built list of children.'; + return false; + }); + return null; + } + void didMount() { renderObject.callback = layout; + renderObject.totalExtentCallback = _noIntrinsicDimensions; + renderObject.maxCrossAxisDimensionCallback = _noIntrinsicDimensions; + renderObject.minCrossAxisDimensionCallback = _noIntrinsicDimensions; super.didMount(); } void didUnmount() { renderObject.callback = null; + renderObject.totalExtentCallback = null; + renderObject.maxCrossAxisDimensionCallback = null; + renderObject.minCrossAxisDimensionCallback = null; super.didUnmount(); }