layout method

  1. @override
void layout(
  1. BoxConstraints constraints
)
override

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()}');
}