Chromium Code Reviews| Index: sky/sdk/lib/rendering/box.dart |
| diff --git a/sky/sdk/lib/rendering/box.dart b/sky/sdk/lib/rendering/box.dart |
| index 515f58c9f43be093f415609a5759963c1bfda79a..08189169a0fe197401633cbeb9014e19c14012fb 100644 |
| --- a/sky/sdk/lib/rendering/box.dart |
| +++ b/sky/sdk/lib/rendering/box.dart |
| @@ -291,28 +291,62 @@ abstract class RenderBox extends RenderObject { |
| return constraints.constrainHeight(0.0); |
| } |
| - // getDistanceToBaseline() should return the distance from the |
| + |
| + Map<TextBaseline, double> _cachedBaselines; |
| + RenderObject _baselineSubtreeRoot; |
| + // getDistanceToBaseline() returns the distance from the |
| // y-coordinate of the position of the box to the y-coordinate of |
| // the first given baseline in the box's contents. This is used by |
| // certain layout models to align adjacent boxes on a common |
| // baseline, regardless of padding, font size differences, etc. If |
| - // there is no baseline, then it should return the distance from the |
| + // there is no baseline, then it returns the distance from the |
| // y-coordinate of the position of the box to the y-coordinate of |
| - // the bottom of the box, i.e., the height of the box. |
| - // Only call this after layout has been performed. |
| + // the bottom of the box, i.e., the height of the box. Only call |
| + // this after layout has been performed. You are only allowed to |
| + // call this from the parent of this node, and only during |
| + // performLayout(). |
| double getDistanceToBaseline(TextBaseline baseline) { |
| - assert(!needsLayout); |
| - double result = getDistanceToActualBaseline(baseline); |
| + RenderObject client = parent; |
| + assert(client == RenderObject.debugActiveLayout); |
| + assert(client.debugDoingThisLayout); |
| + if (_baselineSubtreeRoot != null) { |
| + client = _baselineSubtreeRoot; |
| + assert(client is! RenderBox || (client as RenderBox)._baselineSubtreeRoot == null); // TODO(ianh): Remove cast once the analyzer is cleverer |
| + } else { |
| + if (client is RenderBox && client._baselineSubtreeRoot != null) |
| + client = (client as RenderBox)._baselineSubtreeRoot; // TODO(ianh): Remove cast once the analyzer is cleverer |
| + } |
| + double result = getDistanceToActualBaseline(baseline, client); |
| if (result == null) |
| return size.height; |
| return result; |
| } |
| - // getDistanceToActualBaseline() should return the distance from the |
| - // y-coordinate of the position of the box to the y-coordinate of |
| - // the first given baseline in the box's contents, if any, or null |
| - // otherwise. |
| - double getDistanceToActualBaseline(TextBaseline baseline) { |
| + // getDistanceToActualBaseline() must only be called from |
| + // getDistanceToBaseline() and computeDistanceToActualBaseline(). |
| + // Do not call it directly from outside those two methods. |
| + double getDistanceToActualBaseline(TextBaseline baseline, RenderObject client) { |
| assert(!needsLayout); |
| + assert(client != this); |
| + _baselineSubtreeRoot = client; |
| + if (_cachedBaselines == null) |
| + _cachedBaselines = new Map<TextBaseline, double>(); |
| + if (!_cachedBaselines.containsKey(baseline)) |
| + _cachedBaselines[baseline] = computeDistanceToActualBaseline(baseline, client); |
|
abarth-chromium
2015/07/01 17:29:53
_cachedBaselines.putIfAbsent(...)
|
| + return _cachedBaselines[baseline]; |
| + } |
| + // computeDistanceToActualBaseline() should return the distance from |
| + // the y-coordinate of the position of the box to the y-coordinate |
| + // of the first given baseline in the box's contents, if any, or |
| + // null otherwise. This is the method that you should override in |
| + // subclasses. If you defer to a child, you should call the child's |
| + // getDistanceToActualBaseline(), passing it the same client. This |
| + // method should not be called directly. Use getDistanceToBaseline() |
| + // if you need to know the baseline of a child from performLayout(). |
| + // If you need the baseline during paint, cache it during |
| + // performLayout(). Use getDistanceToActualBaseline() if you are |
| + // implementing computeDistanceToActualBaseline() and need to defer |
| + // to a child. |
| + double computeDistanceToActualBaseline(TextBaseline baseline, RenderObject client) { |
| return null; |
| } |
| @@ -326,6 +360,52 @@ abstract class RenderBox extends RenderObject { |
| print("${this.runtimeType} does not meet its constraints. Constraints: $constraints, size: $_size"); |
| return result; |
| } |
| + |
| + void cleanParentDependencies() { |
| + _baselineSubtreeRoot = null; |
| + if (_cachedBaselines != null) |
| + _cachedBaselines.clear(); |
| + super.cleanParentDependencies(); |
| + } |
| + bool debugAncestorsAlreadyClearedBaselineCache() { |
| + if (_baselineSubtreeRoot == null) |
| + return true; // we haven't yet been asked for a baseline even once, so there's nothing for us to do |
| + RenderBox node = this; |
| + while (node != _baselineSubtreeRoot) { |
| + assert(node._baselineSubtreeRoot == _baselineSubtreeRoot); |
| + if (node._cachedBaselines != null && node._cachedBaselines.isNotEmpty) |
| + return false; |
| + assert(node.parent != null); |
| + if (node is! RenderBox) { |
| + assert(node is RenderObject); |
| + break; |
| + } |
| + node = node.parent as RenderBox; |
| + } |
| + if (node is RenderBox) { |
| + assert(node._baselineSubtreeRoot == null); |
| + if (node._cachedBaselines != null && node._cachedBaselines.isNotEmpty) |
| + return false; |
| + } |
| + return true; |
| + } |
| + void markNeedsLayout() { |
| + assert(!RenderObject.debugDoingLayout); |
| + assert(!RenderObject.debugDoingPaint); |
| + if (_cachedBaselines != null && _cachedBaselines.isNotEmpty) { |
| + _cachedBaselines.clear(); |
| + if (_baselineSubtreeRoot != null) { |
| + assert(_baselineSubtreeRoot != this); |
| + final parent = this.parent; // TODO(ianh): Remove this once the analyzer is cleverer |
| + assert(parent is RenderObject); |
| + parent.markNeedsLayout(); |
| + assert(parent == this.parent); // TODO(ianh): Remove this once the analyzer is cleverer |
| + } |
| + } else { |
| + assert(debugAncestorsAlreadyClearedBaselineCache()); |
| + } |
| + super.markNeedsLayout(); |
| + } |
| void performResize() { |
| // default behaviour for subclasses that have sizedByParent = true |
| size = constraints.constrain(Size.zero); |
| @@ -410,10 +490,10 @@ class RenderProxyBox extends RenderBox with RenderObjectWithChildMixin<RenderBox |
| return super.getMaxIntrinsicHeight(constraints); |
| } |
| - double getDistanceToActualBaseline(TextBaseline baseline) { |
| + double computeDistanceToActualBaseline(TextBaseline baseline, RenderObject client) { |
| if (child != null) |
| - return child.getDistanceToActualBaseline(baseline); |
| - return super.getDistanceToActualBaseline(baseline); |
| + return child.getDistanceToActualBaseline(baseline, client); |
| + return super.computeDistanceToActualBaseline(baseline, client); |
| } |
| void performLayout() { |
| @@ -494,6 +574,15 @@ class RenderConstrainedBox extends RenderProxyBox { |
| } |
| class RenderShrinkWrapWidth extends RenderProxyBox { |
| + |
| + // This class will attempt to size its child to the child's maximum |
| + // intrinsic width, snapped to a multiple of the stepWidth, if one |
| + // is provided, and given the provided constraints; and will then |
| + // adopt the child's resulting dimensions. |
| + |
| + // Note: laying out this class is relatively expensive. Avoid using |
| + // it where possible. |
| + |
| RenderShrinkWrapWidth({ |
| double stepWidth, |
| double stepHeight, |
| @@ -525,16 +614,15 @@ class RenderShrinkWrapWidth extends RenderProxyBox { |
| } |
| BoxConstraints _getInnerConstraints(BoxConstraints constraints) { |
| + if (constraints.hasTightWidth) |
| + return constraints; |
| double width = child.getMaxIntrinsicWidth(constraints); |
| assert(width == constraints.constrainWidth(width)); |
| - return constraints.applyWidth(width); |
| + return constraints.applyWidth(applyStep(width, _stepWidth)); |
| } |
| double getMinIntrinsicWidth(BoxConstraints constraints) { |
| - if (child == null) |
| - return constraints.constrainWidth(0.0); |
| - double childResult = child.getMinIntrinsicWidth(constraints); |
| - return constraints.constrainWidth(applyStep(childResult, _stepWidth)); |
| + return getMaxIntrinsicWidth(constraints); |
| } |
| double getMaxIntrinsicWidth(BoxConstraints constraints) { |
| @@ -560,12 +648,18 @@ class RenderShrinkWrapWidth extends RenderProxyBox { |
| void performLayout() { |
| if (child != null) { |
| - child.layout(_getInnerConstraints(constraints), parentUsesSize: true); |
| - size = new Size(applyStep(child.size.width, _stepWidth), applyStep(child.size.height, _stepHeight)); |
| + BoxConstraints childConstraints = _getInnerConstraints(constraints); |
| + if (_stepHeight != null) |
| + childConstraints.applyHeight(getMaxIntrinsicHeight(childConstraints)); |
| + child.layout(childConstraints, parentUsesSize: true); |
| + size = child.size; |
| } else { |
| performResize(); |
| } |
| } |
| + |
| + String debugDescribeSettings(String prefix) => '${super.debugDescribeSettings(prefix)}${prefix}stepWidth: ${stepWidth}\n${prefix}stepHeight: ${stepHeight}\n'; |
| + |
| } |
| class RenderOpacity extends RenderProxyBox { |
| @@ -719,25 +813,49 @@ abstract class RenderShiftedBox extends RenderBox with RenderObjectWithChildMixi |
| this.child = child; |
| } |
| - void paint(PaintingCanvas canvas, Offset offset) { |
| + double getMinIntrinsicWidth(BoxConstraints constraints) { |
| if (child != null) |
| - canvas.paintChild(child, child.parentData.position + offset); |
| + return child.getMinIntrinsicWidth(constraints); |
| + return super.getMinIntrinsicWidth(constraints); |
| + } |
| + |
| + double getMaxIntrinsicWidth(BoxConstraints constraints) { |
| + if (child != null) |
| + return child.getMaxIntrinsicWidth(constraints); |
| + return super.getMaxIntrinsicWidth(constraints); |
| + } |
| + |
| + double getMinIntrinsicHeight(BoxConstraints constraints) { |
| + if (child != null) |
| + return child.getMinIntrinsicHeight(constraints); |
| + return super.getMinIntrinsicHeight(constraints); |
| + } |
| + |
| + double getMaxIntrinsicHeight(BoxConstraints constraints) { |
| + if (child != null) |
| + return child.getMaxIntrinsicHeight(constraints); |
| + return super.getMaxIntrinsicHeight(constraints); |
| } |
| - double getDistanceToActualBaseline(TextBaseline baseline) { |
| + double computeDistanceToActualBaseline(TextBaseline baseline, RenderObject client) { |
| double result; |
| if (child != null) { |
| assert(!needsLayout); |
| - result = child.getDistanceToActualBaseline(baseline); |
| + result = child.getDistanceToActualBaseline(baseline, client); |
| assert(child.parentData is BoxParentData); |
| if (result != null) |
| result += child.parentData.position.y; |
| } else { |
| - result = super.getDistanceToActualBaseline(baseline); |
| + result = super.computeDistanceToActualBaseline(baseline, client); |
| } |
| return result; |
| } |
| + void paint(PaintingCanvas canvas, Offset offset) { |
| + if (child != null) |
| + canvas.paintChild(child, child.parentData.position + offset); |
| + } |
| + |
| void hitTestChildren(HitTestResult result, { Point position }) { |
| if (child != null) { |
| assert(child.parentData is BoxParentData); |
| @@ -820,6 +938,12 @@ class RenderPadding extends RenderShiftedBox { |
| class RenderPositionedBox extends RenderShiftedBox { |
| + // This box aligns a child box within itself. It's only useful for |
| + // children that don't always size to fit their parent. For example, |
| + // to align a box at the bottom right, you would pass this box a |
| + // tight constraint that is bigger than the child's natural size, |
| + // with horizontal and vertical set to 1.0. |
| + |
| RenderPositionedBox({ |
| RenderBox child, |
| double horizontal: 0.5, |
| @@ -851,28 +975,52 @@ class RenderPositionedBox extends RenderShiftedBox { |
| markNeedsLayout(); |
| } |
| - double getMinIntrinsicWidth(BoxConstraints constraints) { |
| - if (child != null) |
| - return child.getMinIntrinsicWidth(constraints); |
| - return super.getMinIntrinsicWidth(constraints); |
| + void performLayout() { |
| + if (child != null) { |
| + child.layout(constraints.loosen(), parentUsesSize: true); |
| + size = constraints.constrain(child.size); |
| + assert(child.parentData is BoxParentData); |
| + Offset delta = size - child.size; |
| + child.parentData.position = (delta.scale(horizontal, vertical)).toPoint(); |
| + } else { |
| + performResize(); |
| + } |
| } |
| - double getMaxIntrinsicWidth(BoxConstraints constraints) { |
| - if (child != null) |
| - return child.getMaxIntrinsicWidth(constraints); |
| - return super.getMaxIntrinsicWidth(constraints); |
| + String debugDescribeSettings(String prefix) => '${super.debugDescribeSettings(prefix)}${prefix}horizontal: ${horizontal}\n${prefix}vertical: ${vertical}\n'; |
| +} |
| + |
| +class RenderBaseline extends RenderShiftedBox { |
| + |
| + RenderBaseline({ |
| + RenderBox child, |
| + double baseline, |
| + TextBaseline baselineType |
| + }) : _baseline = baseline, |
| + _baselineType = baselineType, |
| + super(child) { |
| + assert(baseline != null); |
| + assert(baselineType != null); |
| } |
| - double getMinIntrinsicHeight(BoxConstraints constraints) { |
| - if (child != null) |
| - return child.getMinIntrinsicHeight(constraints); |
| - return super.getMinIntrinsicHeight(constraints); |
| + double _baseline; |
| + double get baseline => _baseline; |
| + void set baseline (double value) { |
| + assert(value != null); |
| + if (_baseline == value) |
| + return; |
| + _baseline = value; |
| + markNeedsLayout(); |
| } |
| - double getMaxIntrinsicHeight(BoxConstraints constraints) { |
| - if (child != null) |
| - return child.getMaxIntrinsicHeight(constraints); |
| - return super.getMaxIntrinsicHeight(constraints); |
| + TextBaseline _baselineType; |
| + TextBaseline get baselineType => _baselineType; |
| + void set baselineType (TextBaseline value) { |
| + assert(value != null); |
| + if (_baselineType == value) |
| + return; |
| + _baselineType = value; |
| + markNeedsLayout(); |
| } |
| void performLayout() { |
| @@ -880,14 +1028,14 @@ class RenderPositionedBox extends RenderShiftedBox { |
| child.layout(constraints.loosen(), parentUsesSize: true); |
| size = constraints.constrain(child.size); |
| assert(child.parentData is BoxParentData); |
| - Offset delta = size - child.size; |
| - child.parentData.position = (delta.scale(horizontal, vertical)).toPoint(); |
| + double delta = baseline - child.getDistanceToBaseline(baselineType); |
| + child.parentData.position = new Point(0.0, delta); |
| } else { |
| performResize(); |
| } |
| } |
| - String debugDescribeSettings(String prefix) => '${super.debugDescribeSettings(prefix)}${prefix}horizontal: ${horizontal}\n${prefix}vertical: ${vertical}\n'; |
| + String debugDescribeSettings(String prefix) => '${super.debugDescribeSettings(prefix)}${prefix}baseline: ${baseline}\nbaselineType: ${baselineType}'; |
| } |
| class RenderImage extends RenderBox { |
| @@ -1255,12 +1403,12 @@ abstract class RenderBoxContainerDefaultsMixin<ChildType extends RenderBox, Pare |
| // This class, by convention, doesn't override any members of the superclass. |
| // It only provides helper functions that subclasses can call. |
| - double defaultGetDistanceToFirstActualBaseline(TextBaseline baseline) { |
| + double defaultComputeDistanceToFirstActualBaseline(TextBaseline baseline, RenderObject client) { |
| assert(!needsLayout); |
| RenderBox child = firstChild; |
| while (child != null) { |
| assert(child.parentData is ParentDataType); |
| - double result = child.getDistanceToActualBaseline(baseline); |
| + double result = child.getDistanceToActualBaseline(baseline, client); |
| if (result != null) |
| return result + child.parentData.position.y; |
| child = child.parentData.nextSibling; |
| @@ -1268,13 +1416,13 @@ abstract class RenderBoxContainerDefaultsMixin<ChildType extends RenderBox, Pare |
| return null; |
| } |
| - double defaultGetDistanceToHighestActualBaseline(TextBaseline baseline) { |
| + double defaultComputeDistanceToHighestActualBaseline(TextBaseline baseline, RenderObject client) { |
| assert(!needsLayout); |
| double result; |
| RenderBox child = firstChild; |
| while (child != null) { |
| assert(child.parentData is ParentDataType); |
| - double candidate = child.getDistanceToActualBaseline(baseline); |
| + double candidate = child.getDistanceToActualBaseline(baseline, client); |
| if (candidate != null) { |
| candidate += child.parentData.position.x; |
| if (result != null) |