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() {
  beforeLayout();

  if (child != null) {
    BoxConstraints childConstraints = contentConstraints!;

    double? width;
    double? height;
    // Only use computed values if they are finite; unresolved percentages compute to infinity.
    if (renderStyle.width.isNotAuto) {
      final double w = renderStyle.width.computedValue;
      if (w.isFinite) width = w;
    }
    if (renderStyle.height.isNotAuto) {
      final double h = renderStyle.height.computedValue;
      if (h.isFinite) height = h;
    }

    // Clamp specified sizes to the available constraints first (includes max-width/min-width from style).
    if (width != null) {
      width = width!.clamp(childConstraints.minWidth, childConstraints.maxWidth);
    }
    if (height != null) {
      height = height!.clamp(childConstraints.minHeight, childConstraints.maxHeight);
    }

    // Apply aspect-ratio and constraint-driven sizing when only one or neither dimension is specified.
    final double? ratio = renderStyle.aspectRatio;
    if (width != null && height != null) {
      childConstraints = childConstraints.tighten(width: width, height: height);
    } else if (width != null) {
      final double? h = ratio != null ? (width! / ratio) : null;
      final double? clampedH = h != null ? h.clamp(childConstraints.minHeight, childConstraints.maxHeight) : null;
      childConstraints = childConstraints.tighten(width: width, height: clampedH);
    } else if (height != null) {
      final double? w = ratio != null ? (height! * ratio) : null;
      final double? clampedW = w != null ? w.clamp(childConstraints.minWidth, childConstraints.maxWidth) : null;
      childConstraints = childConstraints.tighten(width: clampedW, height: height);
    } else if (ratio != null) {
      // Both width and height are auto; resolve via aspect-ratio and constraints.
      final double loW = childConstraints.minWidth.isFinite ? childConstraints.minWidth : 0.0;
      final double hiW = childConstraints.maxWidth.isFinite ? childConstraints.maxWidth : double.infinity;
      final double loH = childConstraints.minHeight.isFinite ? childConstraints.minHeight : 0.0;
      final double hiH = childConstraints.maxHeight.isFinite ? childConstraints.maxHeight : double.infinity;

      final bool hasWidthDef = loW > 0 || hiW.isFinite;
      final bool hasHeightDef = loH > 0 || hiH.isFinite;

      double? usedW;
      double? usedH;

      if (hasWidthDef || !hasHeightDef) {
        // Prefer width-driven when width has a definite constraint (e.g., min-width) or height does not.
        double w0 = renderStyle.intrinsicWidth.isFinite && renderStyle.intrinsicWidth > 0
            ? renderStyle.intrinsicWidth
            : 0.0;
        // Satisfy min/max width constraints; if intrinsic is below min, take min.
        w0 = w0.clamp(loW, hiW);
        if (w0 <= 0 && loW > 0) w0 = loW;
        // Derive height from ratio and clamp.
        double hFromW = w0 / ratio;
        double h1 = hFromW.clamp(loH, hiH);
        // If clamping height changed the ratio, recompute width to preserve ratio within constraints.
        double w1 = (h1 * ratio).clamp(loW, hiW);
        usedW = w1;
        usedH = h1;
      } else {
        // Height-driven when only height has a definite constraint.
        double h0 = renderStyle.intrinsicHeight.isFinite && renderStyle.intrinsicHeight > 0
            ? renderStyle.intrinsicHeight
            : 0.0;
        h0 = h0.clamp(loH, hiH);
        if (h0 <= 0 && loH > 0) h0 = loH;
        double wFromH = h0 * ratio;
        double w1 = wFromH.clamp(loW, hiW);
        double h1 = (w1 / ratio).clamp(loH, hiH);
        usedW = w1;
        usedH = h1;
      }

      if (usedW != null && usedH != null && usedW > 0 && usedH > 0) {
        childConstraints = childConstraints.tighten(width: usedW, height: usedH);
      }
    }

    // Avoid passing totally unconstrained constraints to child render box.
    // Historically we clamped both axes to the viewport which caused <img> with unknown
    // intrinsic height to temporarily take the full viewport height (e.g., 640px) before
    // the image loaded, breaking baseline alignment in flex containers.
    //
    // Fix: Only clamp the max-width to the viewport when unbounded. Keep max-height
    // unbounded so replaced elements without a resolved height/aspect-ratio don't
    // inherit the viewport height during initial layout. This lets them size to 0
    // (or to their intrinsic defaults once available) instead of stretching cross-axis.
    if (childConstraints.maxWidth == double.infinity || childConstraints.maxHeight == double.infinity) {
      final viewport = renderStyle.target.ownerDocument.viewport!.viewportSize;

      final double resolvedMaxW = childConstraints.maxWidth.isFinite ? childConstraints.maxWidth : viewport.width;
      // Preserve unbounded maxHeight to avoid using viewport height as a fallback.
      final double resolvedMaxH = childConstraints.maxHeight.isFinite ? childConstraints.maxHeight : double.infinity;

      double resolvedMinW = childConstraints.minWidth;
      double resolvedMinH = childConstraints.minHeight;
      if (!resolvedMinW.isFinite || resolvedMinW > resolvedMaxW) resolvedMinW = 0;
      // When maxHeight is unbounded, ensure minHeight is not greater than it and remains finite.
      if (!resolvedMinH.isFinite || (!resolvedMaxH.isFinite && resolvedMinH > 0) || (resolvedMaxH.isFinite && resolvedMinH > resolvedMaxH)) {
        resolvedMinH = 0;
      }

      childConstraints = BoxConstraints(
        minWidth: resolvedMinW,
        maxWidth: resolvedMaxW,
        minHeight: resolvedMinH,
        maxHeight: resolvedMaxH,
      );
    }

    child!.layout(childConstraints, parentUsesSize: true);

    Size childSize = child!.size;

    setMaxScrollableSize(childSize);
    size = getBoxSize(childSize);

    minContentWidth = renderStyle.intrinsicWidth;
    minContentHeight = renderStyle.intrinsicHeight;

    // Cache CSS baselines for replaced elements (inline-level):
    // CSS baseline for replaced inline elements is the bottom border edge
    // (i.e., the border-box bottom). Margins are handled by the formatting
    // context and must NOT be included here.
    try {
      final double baseline = (boxSize?.height ?? size.height);
      setCssBaselines(first: baseline, last: baseline);
    } catch (_) {
      // Safeguard: never let baseline caching break layout.
    }

    didLayout();
  } else {
    performResize();
    dispatchResize(contentSize, boxSize ?? Size.zero);
  }
  initOverflowLayout(Rect.fromLTRB(0, 0, size.width, size.height), Rect.fromLTRB(0, 0, size.width, size.height));
}