blob: 34c2fbfc2385a4dcfc948cd1734a877380d9ac68 [file] [log] [blame]
// 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 'dart:math' as math;
import 'box.dart';
import 'object.dart';
/// Parent data for use with [RenderListBody].
class ListBodyParentData extends ContainerBoxParentData<RenderBox> { }
typedef double _ChildSizingFunction(RenderBox child);
/// Displays its children sequentially along a given axis, forcing them to the
/// dimensions of the parent in the other axis.
///
/// This layout algorithm arranges its children linearly along the main axis
/// (either horizontally or vertically). In the cross axis, children are
/// stretched to match the box's cross-axis extent. In the main axis, children
/// are given unlimited space and the box expands its main axis to contain all
/// its children. Because [RenderListBody] boxes expand in the main axis, they
/// must be given unlimited space in the main axis, typically by being contained
/// in a viewport with a scrolling direction that matches the box's main axis.
class RenderListBody extends RenderBox
with ContainerRenderObjectMixin<RenderBox, ListBodyParentData>,
RenderBoxContainerDefaultsMixin<RenderBox, ListBodyParentData> {
/// Creates a render object that arranges its children sequentially along a
/// given axis.
///
/// By default, children are arranged along the vertical axis.
RenderListBody({
List<RenderBox> children,
AxisDirection axisDirection: AxisDirection.down,
}) : assert(axisDirection != null),
_axisDirection = axisDirection {
addAll(children);
}
@override
void setupParentData(RenderBox child) {
if (child.parentData is! ListBodyParentData)
child.parentData = new ListBodyParentData();
}
/// The direction in which the children are laid out.
///
/// For example, if the [axisDirection] is [AxisDirection.down], each child
/// will be laid out below the next, vertically.
AxisDirection get axisDirection => _axisDirection;
AxisDirection _axisDirection;
set axisDirection(AxisDirection value) {
assert(value != null);
if (_axisDirection == value)
return;
_axisDirection = value;
markNeedsLayout();
}
/// The axis (horizontal or vertical) corresponding to the current
/// [axisDirection].
Axis get mainAxis => axisDirectionToAxis(axisDirection);
@override
void performLayout() {
assert(() {
switch (mainAxis) {
case Axis.horizontal:
if (!constraints.hasBoundedWidth)
return true;
break;
case Axis.vertical:
if (!constraints.hasBoundedHeight)
return true;
break;
}
throw new FlutterError(
'RenderListBody must have unlimited space along its main axis.\n'
'RenderListBody does not clip or resize its children, so it must be '
'placed in a parent that does not constrain the main '
'axis. You probably want to put the RenderListBody inside a '
'RenderViewport with a matching main axis.'
);
}());
assert(() {
switch (mainAxis) {
case Axis.horizontal:
if (constraints.hasBoundedHeight)
return true;
break;
case Axis.vertical:
if (constraints.hasBoundedWidth)
return true;
break;
}
// TODO(ianh): Detect if we're actually nested blocks and say something
// more specific to the exact situation in that case, and don't mention
// nesting blocks in the negative case.
throw new FlutterError(
'RenderListBody must have a bounded constraint for its cross axis.\n'
'RenderListBody forces its children to expand to fit the RenderListBody\'s container, '
'so it must be placed in a parent that constrains the cross '
'axis to a finite dimension. If you are attempting to nest a RenderListBody with '
'one direction inside one of another direction, you will want to '
'wrap the inner one inside a box that fixes the dimension in that direction, '
'for example, a RenderIntrinsicWidth or RenderIntrinsicHeight object. '
'This is relatively expensive, however.' // (that's why we don't do it automatically)
);
}());
double mainAxisExtent = 0.0;
RenderBox child = firstChild;
switch (axisDirection) {
case AxisDirection.right:
final BoxConstraints innerConstraints = new BoxConstraints.tightFor(height: constraints.maxHeight);
while (child != null) {
child.layout(innerConstraints, parentUsesSize: true);
final ListBodyParentData childParentData = child.parentData;
childParentData.offset = new Offset(mainAxisExtent, 0.0);
mainAxisExtent += child.size.width;
assert(child.parentData == childParentData);
child = childParentData.nextSibling;
}
size = constraints.constrain(new Size(mainAxisExtent, constraints.maxHeight));
break;
case AxisDirection.left:
final BoxConstraints innerConstraints = new BoxConstraints.tightFor(height: constraints.maxHeight);
while (child != null) {
child.layout(innerConstraints, parentUsesSize: true);
final ListBodyParentData childParentData = child.parentData;
mainAxisExtent += child.size.width;
assert(child.parentData == childParentData);
child = childParentData.nextSibling;
}
double position = 0.0;
child = firstChild;
while (child != null) {
final ListBodyParentData childParentData = child.parentData;
position += child.size.width;
childParentData.offset = new Offset(mainAxisExtent - position, 0.0);
assert(child.parentData == childParentData);
child = childParentData.nextSibling;
}
size = constraints.constrain(new Size(mainAxisExtent, constraints.maxHeight));
break;
case AxisDirection.down:
final BoxConstraints innerConstraints = new BoxConstraints.tightFor(width: constraints.maxWidth);
while (child != null) {
child.layout(innerConstraints, parentUsesSize: true);
final ListBodyParentData childParentData = child.parentData;
childParentData.offset = new Offset(0.0, mainAxisExtent);
mainAxisExtent += child.size.height;
assert(child.parentData == childParentData);
child = childParentData.nextSibling;
}
size = constraints.constrain(new Size(constraints.maxWidth, mainAxisExtent));
break;
case AxisDirection.up:
final BoxConstraints innerConstraints = new BoxConstraints.tightFor(width: constraints.maxWidth);
while (child != null) {
child.layout(innerConstraints, parentUsesSize: true);
final ListBodyParentData childParentData = child.parentData;
mainAxisExtent += child.size.height;
assert(child.parentData == childParentData);
child = childParentData.nextSibling;
}
double position = 0.0;
child = firstChild;
while (child != null) {
final ListBodyParentData childParentData = child.parentData;
position += child.size.height;
childParentData.offset = new Offset(0.0, mainAxisExtent - position);
assert(child.parentData == childParentData);
child = childParentData.nextSibling;
}
size = constraints.constrain(new Size(constraints.maxWidth, mainAxisExtent));
break;
}
assert(size.isFinite);
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(new EnumProperty<AxisDirection>('axisDirection', axisDirection));
}
double _getIntrinsicCrossAxis(_ChildSizingFunction childSize) {
double extent = 0.0;
RenderBox child = firstChild;
while (child != null) {
extent = math.max(extent, childSize(child));
final ListBodyParentData childParentData = child.parentData;
child = childParentData.nextSibling;
}
return extent;
}
double _getIntrinsicMainAxis(_ChildSizingFunction childSize) {
double extent = 0.0;
RenderBox child = firstChild;
while (child != null) {
extent += childSize(child);
final ListBodyParentData childParentData = child.parentData;
child = childParentData.nextSibling;
}
return extent;
}
@override
double computeMinIntrinsicWidth(double height) {
assert(mainAxis != null);
switch (mainAxis) {
case Axis.horizontal:
return _getIntrinsicMainAxis((RenderBox child) => child.getMinIntrinsicWidth(height));
case Axis.vertical:
return _getIntrinsicCrossAxis((RenderBox child) => child.getMinIntrinsicWidth(height));
}
return null;
}
@override
double computeMaxIntrinsicWidth(double height) {
assert(mainAxis != null);
switch (mainAxis) {
case Axis.horizontal:
return _getIntrinsicMainAxis((RenderBox child) => child.getMaxIntrinsicWidth(height));
case Axis.vertical:
return _getIntrinsicCrossAxis((RenderBox child) => child.getMaxIntrinsicWidth(height));
}
return null;
}
@override
double computeMinIntrinsicHeight(double width) {
assert(mainAxis != null);
switch (mainAxis) {
case Axis.horizontal:
return _getIntrinsicMainAxis((RenderBox child) => child.getMinIntrinsicHeight(width));
case Axis.vertical:
return _getIntrinsicCrossAxis((RenderBox child) => child.getMinIntrinsicHeight(width));
}
return null;
}
@override
double computeMaxIntrinsicHeight(double width) {
assert(mainAxis != null);
switch (mainAxis) {
case Axis.horizontal:
return _getIntrinsicMainAxis((RenderBox child) => child.getMaxIntrinsicHeight(width));
case Axis.vertical:
return _getIntrinsicCrossAxis((RenderBox child) => child.getMaxIntrinsicHeight(width));
}
return null;
}
@override
double computeDistanceToActualBaseline(TextBaseline baseline) {
return defaultComputeDistanceToFirstActualBaseline(baseline);
}
@override
void paint(PaintingContext context, Offset offset) {
defaultPaint(context, offset);
}
@override
bool hitTestChildren(HitTestResult result, { Offset position }) {
return defaultHitTestChildren(result, position: position);
}
}