// Copyright 2015 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'box.dart'; import 'object.dart'; // For OneChildLayoutDelegate and RenderCustomOneChildLayoutBox, see shifted_box.dart class MultiChildLayoutParentData extends ContainerBoxParentDataMixin<RenderBox> { /// An object representing the identity of this child. Object id; void merge(MultiChildLayoutParentData other) { if (other.id != null) id = other.id; super.merge(other); } String toString() => '${super.toString()}; id=$id'; } /// A delegate that controls the layout of multiple children. abstract class MultiChildLayoutDelegate { Map<Object, RenderBox> _idToChild; Set<RenderBox> _debugChildrenNeedingLayout; /// Returns the size of this object given the incoming constraints. /// The size cannot reflect the instrinsic sizes of the children. /// If this layout has a fixed width or height the returned size /// can reflect that. Size getSize(BoxConstraints constraints) => constraints.biggest; /// True if a non-null LayoutChild was provided for the specified id. bool isChild(Object childId) => _idToChild[childId] != null; /// Ask the child to update its layout within the limits specified by /// the constraints parameter. The child's size is returned. Size layoutChild(Object childId, BoxConstraints constraints) { final RenderBox child = _idToChild[childId]; assert(child != null); assert(() { 'A MultiChildLayoutDelegate cannot layout the same child more than once.'; return _debugChildrenNeedingLayout.remove(child); }); assert(constraints.isNormalized); child.layout(constraints, parentUsesSize: true); return child.size; } /// Specify the child's origin relative to this origin. void positionChild(Object childId, Offset offset) { final RenderBox child = _idToChild[childId]; assert(child != null); final MultiChildLayoutParentData childParentData = child.parentData; childParentData.offset = offset; } void _callPerformLayout(Size size, BoxConstraints constraints, RenderBox firstChild) { final Map<Object, RenderBox> previousIdToChild = _idToChild; Set<RenderBox> debugPreviousChildrenNeedingLayout; assert(() { debugPreviousChildrenNeedingLayout = _debugChildrenNeedingLayout; _debugChildrenNeedingLayout = new Set<RenderBox>(); return true; }); try { _idToChild = new Map<Object, RenderBox>(); RenderBox child = firstChild; while (child != null) { final MultiChildLayoutParentData childParentData = child.parentData; assert(childParentData.id != null); _idToChild[childParentData.id] = child; assert(() { _debugChildrenNeedingLayout.add(child); return true; }); child = childParentData.nextSibling; } performLayout(size, constraints); assert(() { 'A MultiChildLayoutDelegate needs to call layoutChild on every child.'; return _debugChildrenNeedingLayout.isEmpty; }); } finally { _idToChild = previousIdToChild; assert(() { _debugChildrenNeedingLayout = debugPreviousChildrenNeedingLayout; return true; }); } } /// Override this method to return true when the children need to be laid out. bool shouldRelayout(MultiChildLayoutDelegate oldDelegate); /// Layout and position all children given this widget's size and the specified /// constraints. This method must apply layoutChild() to each child. It should /// specify the final position of each child with positionChild(). void performLayout(Size size, BoxConstraints constraints); } /// Defers the layout of multiple children to a delegate. /// /// The delegate can determine the layout constraints for each child and can /// decide where to position each child. The delegate can also determine the /// size of the parent, but the size of the parent cannot depend on the sizes of /// the children. class RenderCustomMultiChildLayoutBox extends RenderBox with ContainerRenderObjectMixin<RenderBox, MultiChildLayoutParentData>, RenderBoxContainerDefaultsMixin<RenderBox, MultiChildLayoutParentData> { RenderCustomMultiChildLayoutBox({ List<RenderBox> children, MultiChildLayoutDelegate delegate }) : _delegate = delegate { assert(delegate != null); addAll(children); } void setupParentData(RenderBox child) { if (child.parentData is! MultiChildLayoutParentData) child.parentData = new MultiChildLayoutParentData(); } /// The delegate that controls the layout of the children. MultiChildLayoutDelegate get delegate => _delegate; MultiChildLayoutDelegate _delegate; void set delegate (MultiChildLayoutDelegate newDelegate) { assert(newDelegate != null); if (_delegate == newDelegate) return; if (newDelegate.runtimeType != _delegate.runtimeType || newDelegate.shouldRelayout(_delegate)) markNeedsLayout(); _delegate = newDelegate; } Size _getSize(BoxConstraints constraints) { assert(constraints.isNormalized); 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() { delegate._callPerformLayout(size, constraints, firstChild); } void paint(PaintingContext context, Offset offset) { defaultPaint(context, offset); } bool hitTestChildren(HitTestResult result, { Point position }) { return defaultHitTestChildren(result, position: position); } }