Commit 7a4bdc7f authored by Ian Hickson's avatar Ian Hickson

Merge pull request #870 from Hixie/RenderBlockViewport

Generalise RenderBlockViewport so that it can be used by a Widget that knows its children's dimensions.
parents b1468cb2 04a8fe8e
...@@ -13,6 +13,7 @@ class BlockParentData extends BoxParentData with ContainerParentDataMixin<Render ...@@ -13,6 +13,7 @@ class BlockParentData extends BoxParentData with ContainerParentDataMixin<Render
enum BlockDirection { horizontal, vertical } enum BlockDirection { horizontal, vertical }
typedef double _ChildSizingFunction(RenderBox child, BoxConstraints constraints); typedef double _ChildSizingFunction(RenderBox child, BoxConstraints constraints);
typedef double _Constrainer(double value);
abstract class RenderBlockBase extends RenderBox with ContainerRenderObjectMixin<RenderBox, BlockParentData>, abstract class RenderBlockBase extends RenderBox with ContainerRenderObjectMixin<RenderBox, BlockParentData>,
RenderBoxContainerDefaultsMixin<RenderBox, BlockParentData> { RenderBoxContainerDefaultsMixin<RenderBox, BlockParentData> {
...@@ -41,10 +42,10 @@ abstract class RenderBlockBase extends RenderBox with ContainerRenderObjectMixin ...@@ -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) { BoxConstraints _getInnerConstraints(BoxConstraints constraints) {
if (_isVertical) if (isVertical)
return new BoxConstraints.tightFor(width: constraints.constrainWidth(constraints.maxWidth)); return new BoxConstraints.tightFor(width: constraints.constrainWidth(constraints.maxWidth));
return new BoxConstraints.tightFor(height: constraints.constrainHeight(constraints.maxHeight)); return new BoxConstraints.tightFor(height: constraints.constrainHeight(constraints.maxHeight));
} }
...@@ -54,7 +55,7 @@ abstract class RenderBlockBase extends RenderBox with ContainerRenderObjectMixin ...@@ -54,7 +55,7 @@ abstract class RenderBlockBase extends RenderBox with ContainerRenderObjectMixin
if (child == null) if (child == null)
return 0.0; return 0.0;
BoxParentData parentData = child.parentData; BoxParentData parentData = child.parentData;
return _isVertical ? return isVertical ?
parentData.position.y + child.size.height : parentData.position.y + child.size.height :
parentData.position.x + child.size.width; parentData.position.x + child.size.width;
} }
...@@ -66,11 +67,11 @@ abstract class RenderBlockBase extends RenderBox with ContainerRenderObjectMixin ...@@ -66,11 +67,11 @@ abstract class RenderBlockBase extends RenderBox with ContainerRenderObjectMixin
while (child != null) { while (child != null) {
child.layout(innerConstraints, parentUsesSize: true); child.layout(innerConstraints, parentUsesSize: true);
assert(child.parentData is BlockParentData); assert(child.parentData is BlockParentData);
child.parentData.position = _isVertical ? new Point(0.0, position) : new Point(position, 0.0); child.parentData.position = isVertical ? new Point(0.0, position) : new Point(position, 0.0);
position += _isVertical ? child.size.height : child.size.width; position += isVertical ? child.size.height : child.size.width;
child = child.parentData.nextSibling; child = child.parentData.nextSibling;
} }
size = _isVertical ? size = isVertical ?
constraints.constrain(new Size(constraints.maxWidth, _mainAxisExtent)) : constraints.constrain(new Size(constraints.maxWidth, _mainAxisExtent)) :
constraints.constrain(new Size(_mainAxisExtent, constraints.maxHeight)); constraints.constrain(new Size(_mainAxisExtent, constraints.maxHeight));
assert(!size.isInfinite); assert(!size.isInfinite);
...@@ -88,7 +89,7 @@ class RenderBlock extends RenderBlockBase { ...@@ -88,7 +89,7 @@ class RenderBlock extends RenderBlockBase {
double _getIntrinsicCrossAxis(BoxConstraints constraints, _ChildSizingFunction childSize) { double _getIntrinsicCrossAxis(BoxConstraints constraints, _ChildSizingFunction childSize) {
double extent = 0.0; double extent = 0.0;
BoxConstraints innerConstraints = _isVertical ? constraints.widthConstraints() : constraints.heightConstraints(); BoxConstraints innerConstraints = isVertical ? constraints.widthConstraints() : constraints.heightConstraints();
RenderBox child = firstChild; RenderBox child = firstChild;
while (child != null) { while (child != null) {
extent = math.max(extent, childSize(child, innerConstraints)); extent = math.max(extent, childSize(child, innerConstraints));
...@@ -103,11 +104,11 @@ class RenderBlock extends RenderBlockBase { ...@@ -103,11 +104,11 @@ class RenderBlock extends RenderBlockBase {
BoxConstraints innerConstraints = _getInnerConstraints(constraints); BoxConstraints innerConstraints = _getInnerConstraints(constraints);
RenderBox child = firstChild; RenderBox child = firstChild;
while (child != null) { while (child != null) {
double childExtent = _isVertical ? double childExtent = isVertical ?
child.getMinIntrinsicHeight(innerConstraints) : child.getMinIntrinsicHeight(innerConstraints) :
child.getMinIntrinsicWidth(innerConstraints); child.getMinIntrinsicWidth(innerConstraints);
assert(() { assert(() {
if (_isVertical) if (isVertical)
return childExtent == child.getMaxIntrinsicHeight(innerConstraints); return childExtent == child.getMaxIntrinsicHeight(innerConstraints);
return childExtent == child.getMaxIntrinsicWidth(innerConstraints); return childExtent == child.getMaxIntrinsicWidth(innerConstraints);
}); });
...@@ -119,7 +120,7 @@ class RenderBlock extends RenderBlockBase { ...@@ -119,7 +120,7 @@ class RenderBlock extends RenderBlockBase {
} }
double getMinIntrinsicWidth(BoxConstraints constraints) { double getMinIntrinsicWidth(BoxConstraints constraints) {
if (_isVertical) { if (isVertical) {
return _getIntrinsicCrossAxis(constraints, return _getIntrinsicCrossAxis(constraints,
(c, innerConstraints) => c.getMinIntrinsicWidth(innerConstraints)); (c, innerConstraints) => c.getMinIntrinsicWidth(innerConstraints));
} }
...@@ -127,7 +128,7 @@ class RenderBlock extends RenderBlockBase { ...@@ -127,7 +128,7 @@ class RenderBlock extends RenderBlockBase {
} }
double getMaxIntrinsicWidth(BoxConstraints constraints) { double getMaxIntrinsicWidth(BoxConstraints constraints) {
if (_isVertical) { if (isVertical) {
return _getIntrinsicCrossAxis(constraints, return _getIntrinsicCrossAxis(constraints,
(c, innerConstraints) => c.getMaxIntrinsicWidth(innerConstraints)); (c, innerConstraints) => c.getMaxIntrinsicWidth(innerConstraints));
} }
...@@ -135,14 +136,14 @@ class RenderBlock extends RenderBlockBase { ...@@ -135,14 +136,14 @@ class RenderBlock extends RenderBlockBase {
} }
double getMinIntrinsicHeight(BoxConstraints constraints) { double getMinIntrinsicHeight(BoxConstraints constraints) {
if (_isVertical) if (isVertical)
return _getIntrinsicMainAxis(constraints); return _getIntrinsicMainAxis(constraints);
return _getIntrinsicCrossAxis(constraints, return _getIntrinsicCrossAxis(constraints,
(c, innerConstraints) => c.getMinIntrinsicWidth(innerConstraints)); (c, innerConstraints) => c.getMinIntrinsicWidth(innerConstraints));
} }
double getMaxIntrinsicHeight(BoxConstraints constraints) { double getMaxIntrinsicHeight(BoxConstraints constraints) {
if (_isVertical) if (isVertical)
return _getIntrinsicMainAxis(constraints); return _getIntrinsicMainAxis(constraints);
return _getIntrinsicCrossAxis(constraints, return _getIntrinsicCrossAxis(constraints,
(c, innerConstraints) => c.getMaxIntrinsicWidth(innerConstraints)); (c, innerConstraints) => c.getMaxIntrinsicWidth(innerConstraints));
...@@ -153,7 +154,7 @@ class RenderBlock extends RenderBlockBase { ...@@ -153,7 +154,7 @@ class RenderBlock extends RenderBlockBase {
} }
void performLayout() { 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 ' + '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); 'the block\'s main direction. You probably want to put the RenderBlock inside a RenderViewport.' is String);
super.performLayout(); super.performLayout();
...@@ -171,14 +172,32 @@ class RenderBlock extends RenderBlockBase { ...@@ -171,14 +172,32 @@ class RenderBlock extends RenderBlockBase {
class RenderBlockViewport 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({ RenderBlockViewport({
LayoutCallback callback, LayoutCallback callback,
DimensionCallback totalExtentCallback,
DimensionCallback maxCrossAxisDimensionCallback,
DimensionCallback minCrossAxisDimensionCallback,
double startOffset: 0.0,
List<RenderBox> children, List<RenderBox> children,
double startOffset: 0.0 BlockDirection direction: BlockDirection.vertical
}) : _callback = callback, _startOffset = startOffset, super(children: children); }) : _callback = callback,
_totalExtentCallback = totalExtentCallback,
_maxCrossAxisDimensionCallback = maxCrossAxisDimensionCallback,
_minCrossAxisDimensionCallback = minCrossAxisDimensionCallback,
_startOffset = startOffset,
super(children: children, direction: direction);
bool _inCallback = false; bool _inCallback = false;
// Called during layout. Mutate the child list appropriately.
LayoutCallback _callback; LayoutCallback _callback;
LayoutCallback get callback => _callback; LayoutCallback get callback => _callback;
void set callback(LayoutCallback value) { void set callback(LayoutCallback value) {
...@@ -189,6 +208,44 @@ class RenderBlockViewport extends RenderBlockBase { ...@@ -189,6 +208,44 @@ class RenderBlockViewport extends RenderBlockBase {
markNeedsLayout(); 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 // you can set this from within the callback if necessary
double _startOffset; double _startOffset;
double get startOffset => _startOffset; double get startOffset => _startOffset;
...@@ -200,30 +257,50 @@ class RenderBlockViewport extends RenderBlockBase { ...@@ -200,30 +257,50 @@ class RenderBlockViewport extends RenderBlockBase {
markNeedsPaint(); markNeedsPaint();
} }
double _noIntrinsicDimensions() { double _getIntrinsicDimension(BoxConstraints constraints, DimensionCallback intrinsicCallback, _Constrainer constrainer) {
assert(() { assert(!_inCallback);
'RenderBlockViewport does not support returning intrinsic dimensions. ' + double result;
'Calculating the intrinsic dimensions would require walking the entire child list, ' + if (intrinsicCallback == null) {
'which defeats the entire point of having a lazily-built list of children.'; assert(() {
return false; 'RenderBlockViewport does not support returning intrinsic dimensions if the relevant callbacks have not been specified.';
}); return false;
return 0.0; });
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) { double getMinIntrinsicWidth(BoxConstraints constraints) {
return _noIntrinsicDimensions(); if (isVertical)
return _getIntrinsicDimension(constraints, minCrossAxisDimensionCallback, constraints.constrainWidth);
return constraints.constrainWidth(0.0);
} }
double getMaxIntrinsicWidth(BoxConstraints constraints) { double getMaxIntrinsicWidth(BoxConstraints constraints) {
return _noIntrinsicDimensions(); if (isVertical)
return _getIntrinsicDimension(constraints, maxCrossAxisDimensionCallback, constraints.constrainWidth);
return _getIntrinsicDimension(constraints, totalExtentCallback, constraints.constrainWidth);
} }
double getMinIntrinsicHeight(BoxConstraints constraints) { double getMinIntrinsicHeight(BoxConstraints constraints) {
return _noIntrinsicDimensions(); if (!isVertical)
return _getIntrinsicDimension(constraints, minCrossAxisDimensionCallback, constraints.constrainHeight);
return constraints.constrainHeight(0.0);
} }
double getMaxIntrinsicHeight(BoxConstraints constraints) { 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 // We don't override computeDistanceToActualBaseline(), because we
......
...@@ -292,6 +292,7 @@ abstract class Constraints { ...@@ -292,6 +292,7 @@ abstract class Constraints {
typedef void RenderObjectVisitor(RenderObject child); typedef void RenderObjectVisitor(RenderObject child);
typedef void LayoutCallback(Constraints constraints); typedef void LayoutCallback(Constraints constraints);
typedef double DimensionCallback(Constraints constraints);
abstract class RenderObject extends AbstractNode implements HitTestTarget { abstract class RenderObject extends AbstractNode implements HitTestTarget {
......
...@@ -117,13 +117,29 @@ class MixedViewport extends RenderObjectWrapper { ...@@ -117,13 +117,29 @@ class MixedViewport extends RenderObjectWrapper {
assert(renderObject == this.renderObject); // TODO(ianh): Remove this once the analyzer is cleverer 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() { void didMount() {
renderObject.callback = layout; renderObject.callback = layout;
renderObject.totalExtentCallback = _noIntrinsicDimensions;
renderObject.maxCrossAxisDimensionCallback = _noIntrinsicDimensions;
renderObject.minCrossAxisDimensionCallback = _noIntrinsicDimensions;
super.didMount(); super.didMount();
} }
void didUnmount() { void didUnmount() {
renderObject.callback = null; renderObject.callback = null;
renderObject.totalExtentCallback = null;
renderObject.maxCrossAxisDimensionCallback = null;
renderObject.minCrossAxisDimensionCallback = null;
super.didUnmount(); super.didUnmount();
} }
......
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