performLayout method

  1. @override
void performLayout()
override

Do the work of computing the layout for this render object.

Do not call this function directly: call layout instead. This function is called by layout when there is actually work to be done by this render object during layout. The layout constraints provided by your parent are available via the constraints getter.

If sizedByParent is true, then this function should not actually change the dimensions of this render object. Instead, that work should be done by performResize. If sizedByParent is false, then this function should both change the dimensions of this render object and instruct its children to layout.

In implementing this function, you must call layout on each of your children, passing true for parentUsesSize if your layout information is dependent on your child's layout information. Passing true for parentUsesSize ensures that this render object will undergo layout if the child undergoes layout. Otherwise, the child can change its layout information without informing this render object.

Some special RenderObject subclasses (such as the one used by OverlayPortal.overlayChildLayoutBuilder) call applyPaintTransform in their performLayout implementation. To ensure such RenderObjects get the up-to-date paint transform, RenderObject subclasses should typically update the paint transform (as reported by applyPaintTransform) in this method instead of paint.

Implementation

@override
void performLayout() {
  renderStyle.computeContentBoxLogicalWidth();
  renderStyle.computeContentBoxLogicalHeight();

  // Do NOT call super.performLayout() (RenderProxyBoxMixin) because it would
  // pass our tight ListView constraints to the child, forcing it to expand.
  // Instead, lay out the child with its own CSS-derived constraints.
  final RenderBox? c = child;
  if (c == null) {
    size = constraints.constrain(Size.zero);
    initOverflowLayout(Rect.fromLTRB(0, 0, size.width, size.height), Rect.fromLTRB(0, 0, size.width, size.height));
    return;
  }

  BoxConstraints childConstraints;
  if (c is RenderBoxModel) {
    // Compute constraints from the child's CSS, isolating it from wrapper tightness.
    childConstraints = c.getConstraints();
    // Deflate wrapper padding/border aren’t relevant here; the child’s own
    // CSS logic already accounts for its padding/border.
  } else if (c is RenderTextBox) {
    // Text nodes inside wrappers should measure themselves with a sensible bound.
    final double maxW = constraints.hasBoundedWidth ? constraints.maxWidth : double.infinity;
    final double maxH = constraints.hasBoundedHeight ? constraints.maxHeight : double.infinity;
    childConstraints = BoxConstraints(minWidth: 0, maxWidth: maxW, minHeight: 0, maxHeight: maxH);
  } else {
    // Fallback: provide loose, unbounded constraints so inner WebF render boxes
    // can compute their own CSS-based constraints without being forced to expand.
    childConstraints = const BoxConstraints(
      minWidth: 0,
      maxWidth: double.infinity,
      minHeight: 0,
      maxHeight: double.infinity,
    );
  }

  // Intersect child's CSS-derived constraints with the wrapper's incoming constraints.
  // This allows outer layout (e.g., flex) to enforce a definite inline/cross size.
  // Without this, the wrapper would ignore tight sizes from its parent and the
  // inner WebF render boxes would keep unbounded widths.
  BoxConstraints _intersect(BoxConstraints a, BoxConstraints b) {
    double minW = math.max(a.minWidth, b.minWidth);
    double minH = math.max(a.minHeight, b.minHeight);
    double maxW = a.maxWidth;
    double maxH = a.maxHeight;
    if (b.hasBoundedWidth) {
      maxW = math.min(a.maxWidth, b.maxWidth);
    }
    if (b.hasBoundedHeight) {
      maxH = math.min(a.maxHeight, b.maxHeight);
    }
    if (minW > maxW) minW = maxW;
    if (minH > maxH) minH = maxH;
    return BoxConstraints(minWidth: minW, maxWidth: maxW, minHeight: minH, maxHeight: maxH);
  }

  childConstraints = _intersect(childConstraints, constraints);

  c.layout(childConstraints, parentUsesSize: true);

  if (c is RenderBoxModel) {
    // For list-like widget containers (ListView children), sibling margin
    // collapsing must be handled within each wrapped item since the parent
    // is a Flutter RenderObject that doesn't implement CSS collapsing.
    // Use sibling-oriented collapsed margins so the inter-item spacing equals
    // the CSS collapsed result between previous bottom and current top.
    final double childMarginTop = renderStyle.collapsedMarginTopForSibling;
    final double childMarginBottom = renderStyle.collapsedMarginBottomForSibling;
    final double childMarginLeft = renderStyle.marginLeft.computedValue;
    final double childMarginRight = renderStyle.marginRight.computedValue;

    // Scrollable size of the child’s content box
    final Size contentScrollable = c.renderStyle.isSelfScrollingContainer() ? c.size : c.scrollableSize;

    // Decide sizing based on list axis. In a horizontal list (unbounded width),
    // widen by left+right margins so gaps appear between items. In a vertical
    // list (unbounded height), increase height by top+bottom margins.
    final bool isHorizontalList = constraints.hasBoundedHeight && !constraints.hasBoundedWidth;
    final bool isVerticalList = constraints.hasBoundedWidth && !constraints.hasBoundedHeight;

    double wrapperWidth;
    double wrapperHeight;
    if (isHorizontalList) {
      wrapperWidth = contentScrollable.width + childMarginLeft + childMarginRight;
      // Height is tight from the viewport; still offset child by vertical margins below.
      wrapperHeight = constraints.hasBoundedHeight ? constraints.maxHeight : (contentScrollable.height + childMarginTop + childMarginBottom);
    } else if (isVerticalList) {
      wrapperWidth = constraints.hasBoundedWidth ? constraints.maxWidth : (contentScrollable.width + childMarginLeft + childMarginRight);
      wrapperHeight = contentScrollable.height + childMarginTop + childMarginBottom;
    } else {
      // Fallback (no tightness info): include both margins conservatively.
      wrapperWidth = contentScrollable.width + childMarginLeft + childMarginRight;
      wrapperHeight = contentScrollable.height + childMarginTop + childMarginBottom;
    }

    size = constraints.constrain(Size(wrapperWidth, wrapperHeight));

    if (renderStyle.isSelfPositioned() || renderStyle.isSelfStickyPosition()) {
      CSSPositionedLayout.applyPositionedChildOffset(this, c);
    } else {
      // Offset the child within the wrapper by its margins
      final Offset relativeOffset = Offset(childMarginLeft, childMarginTop);
      CSSPositionedLayout.applyRelativeOffset(relativeOffset, c);
    }
  } else {
    // Non-RenderBoxModel child: just adopt its size vertically with margins=0.
    size = constraints.constrain(c.size);
  }

  calculateBaseline();
  initOverflowLayout(Rect.fromLTRB(0, 0, size.width, size.height), Rect.fromLTRB(0, 0, size.width, size.height));
}