layout method
Implementation
@override
void layout(BoxConstraints constraints) {
final span = TuiTrace.begin(
'RenderColumn.layout',
tag: TraceTag.layout,
extra: 'children=${children.length}',
);
super.layout(constraints);
var width = 0.0;
var height = 0.0;
final isStretch = crossAxisAlignment == RenderCrossAxisAlignment.stretch;
// --- Two-pass flex layout (mirrors Flutter's RenderFlex) ---
final flexData = children.map(_flexDataFor).toList();
final totalFlex = flexData.fold<int>(0, (sum, f) => sum + f.flex);
assert(() {
final hasTightFlex = flexData.any(
(f) => f.flex > 0 && f.fit == RenderFlexFit.tight,
);
if (hasTightFlex && !constraints.hasBoundedHeight) {
throw AssertionError(
'RenderColumn has non-zero flex children with unbounded height constraints. '
'Use MainAxisSize.min, wrap in a bounded parent, or switch flex children to loose fit.',
);
}
return true;
}());
final gapTotal = children.length > 1
? gap * (children.length - 1).toDouble()
: 0.0;
// Pass 1: Layout non-flex children with loose cross constraints.
// Stretch is applied in a final pass once the resolved cross size
// is known.
var nonFlexHeight = 0.0;
for (var i = 0; i < children.length; i++) {
if (flexData[i].flex > 0) continue;
final childConstraints = BoxConstraints(
minWidth: 0,
maxWidth: constraints.maxWidth,
minHeight: 0,
maxHeight: constraints.hasBoundedHeight
? double.infinity
: constraints.maxHeight,
);
if (_shouldLayoutChild(children[i], childConstraints)) {
children[i].layout(childConstraints);
}
nonFlexHeight += children[i].size.height;
width = math.max(width, children[i].size.width);
}
// Pass 2: Distribute remaining space among flex children.
if (totalFlex > 0 && constraints.hasBoundedHeight) {
final available = math.max(
0.0,
constraints.maxHeight - nonFlexHeight - gapTotal,
);
for (var i = 0; i < children.length; i++) {
final data = flexData[i];
if (data.flex <= 0) continue;
final alloc = (available * data.flex) / totalFlex;
final allocInt = alloc.floorToDouble();
final childConstraints = data.fit == RenderFlexFit.tight
? BoxConstraints(
minWidth: 0,
maxWidth: constraints.maxWidth,
minHeight: allocInt,
maxHeight: allocInt,
)
: BoxConstraints(
minWidth: 0,
maxWidth: constraints.maxWidth,
minHeight: 0,
maxHeight: allocInt,
);
if (_shouldLayoutChild(children[i], childConstraints)) {
children[i].layout(childConstraints);
}
width = math.max(width, children[i].size.width);
}
} else {
// No flex or unbounded — layout flex children with loose constraints.
for (var i = 0; i < children.length; i++) {
if (flexData[i].flex <= 0) continue;
final childConstraints = BoxConstraints(
minWidth: 0,
maxWidth: constraints.maxWidth,
minHeight: 0,
maxHeight: constraints.hasBoundedHeight
? double.infinity
: constraints.maxHeight,
);
if (_shouldLayoutChild(children[i], childConstraints)) {
children[i].layout(childConstraints);
}
width = math.max(width, children[i].size.width);
}
}
// Resolve cross size: max(constraints.minWidth, maxChildWidth).
// This matches Flutter's RenderFlex cross-axis resolution.
final crossSize = math.max(constraints.minWidth, width);
// Pass 3 (stretch only): Re-layout children that need to match the
// resolved cross size.
if (isStretch) {
for (var i = 0; i < children.length; i++) {
final child = children[i];
if (child.size.width == crossSize) continue;
final prevConstraints = child.constraints;
final stretchConstraints = BoxConstraints(
minWidth: crossSize,
maxWidth: crossSize,
minHeight: prevConstraints.minHeight,
maxHeight: prevConstraints.maxHeight,
);
if (_shouldLayoutChild(child, stretchConstraints)) {
child.layout(stretchConstraints);
}
}
}
height =
children.fold<double>(0, (sum, c) => sum + c.size.height) + gapTotal;
final contentWidth = isStretch ? crossSize : width;
final contentHeight = height;
final resolvedHeight = mainAxisSize == RenderMainAxisSize.max
? (mainAxisExtent?.toDouble() ??
(constraints.hasBoundedHeight
? constraints.maxHeight
: contentHeight))
: contentHeight;
final resolvedWidth = crossAxisExtent?.toDouble() ?? contentWidth;
size = constraints.constrain(Size(resolvedWidth, resolvedHeight));
// Compute child offsets matching the paint layout logic.
_computeChildOffsets();
span.end(extra: 'size=${size.width.toInt()}x${size.height.toInt()}');
}