layout method
Implementation
@override
void layout(BoxConstraints constraints) {
final span = TuiTrace.begin(
'RenderRow.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) ---
//
// Pass 1: Layout non-flex children to determine how much space is left
// for flex children. Use loose cross constraints initially; stretch
// is applied in a final pass once the resolved cross size is known.
final flexData = children.map(_flexDataFor).toList();
final totalFlex = flexData.fold<int>(0, (sum, f) => sum + f.flex);
final gapTotal = children.length > 1
? gap * (children.length - 1).toDouble()
: 0.0;
var nonFlexWidth = 0.0;
for (var i = 0; i < children.length; i++) {
if (flexData[i].flex > 0) continue;
final childConstraints = BoxConstraints(
minWidth: 0,
maxWidth: constraints.hasBoundedWidth
? double.infinity
: constraints.maxWidth,
minHeight: 0,
maxHeight: constraints.maxHeight,
);
if (_shouldLayoutChild(children[i], childConstraints)) {
children[i].layout(childConstraints);
}
nonFlexWidth += children[i].size.width;
height = math.max(height, children[i].size.height);
}
// Pass 2: Distribute remaining space among flex children.
if (totalFlex > 0 && constraints.hasBoundedWidth) {
final available = math.max(
0.0,
constraints.maxWidth - nonFlexWidth - 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: allocInt,
maxWidth: allocInt,
minHeight: 0,
maxHeight: constraints.maxHeight,
)
: BoxConstraints(
minWidth: 0,
maxWidth: allocInt,
minHeight: 0,
maxHeight: constraints.maxHeight,
);
if (_shouldLayoutChild(children[i], childConstraints)) {
children[i].layout(childConstraints);
}
height = math.max(height, children[i].size.height);
}
} 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.hasBoundedWidth
? double.infinity
: constraints.maxWidth,
minHeight: 0,
maxHeight: constraints.maxHeight,
);
if (_shouldLayoutChild(children[i], childConstraints)) {
children[i].layout(childConstraints);
}
height = math.max(height, children[i].size.height);
}
}
// Resolve cross size: max(constraints.minHeight, maxChildHeight).
// This matches Flutter's RenderFlex cross-axis resolution.
final crossSize = math.max(constraints.minHeight, height);
// Pass 3 (stretch only): Re-layout children that need to match the
// resolved cross size. This is necessary because the initial passes
// used loose cross constraints to discover the natural cross extent.
if (isStretch) {
for (var i = 0; i < children.length; i++) {
final child = children[i];
if (child.size.height == crossSize) continue;
final prevConstraints = child.constraints;
final stretchConstraints = BoxConstraints(
minWidth: prevConstraints.minWidth,
maxWidth: prevConstraints.maxWidth,
minHeight: crossSize,
maxHeight: crossSize,
);
if (_shouldLayoutChild(child, stretchConstraints)) {
child.layout(stretchConstraints);
}
}
}
width = children.fold<double>(0, (sum, c) => sum + c.size.width) + gapTotal;
final contentWidth = width;
final contentHeight = isStretch ? crossSize : height;
final resolvedWidth = mainAxisSize == RenderMainAxisSize.max
? (mainAxisExtent?.toDouble() ??
(constraints.hasBoundedWidth
? constraints.maxWidth
: contentWidth))
: contentWidth;
final resolvedHeight = crossAxisExtent?.toDouble() ?? contentHeight;
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()}');
}