getConstraints method

BoxConstraints getConstraints()

Implementation

BoxConstraints getConstraints() {
  CSSDisplay? effectiveDisplay = renderStyle.effectiveDisplay;
  bool isDisplayInline = effectiveDisplay == CSSDisplay.inline;

  double? minWidth = renderStyle.minWidth.isAuto ? null : renderStyle.minWidth.computedValue;
  double? maxWidth = renderStyle.maxWidth.isNone ? null : renderStyle.maxWidth.computedValue;
  double? minHeight = renderStyle.minHeight.isAuto ? null : renderStyle.minHeight.computedValue;
  double? maxHeight = renderStyle.maxHeight.isNone ? null : renderStyle.maxHeight.computedValue;

  // Need to calculated logic content size on every layout.
  renderStyle.computeContentBoxLogicalWidth();
  renderStyle.computeContentBoxLogicalHeight();

  // Width should be not smaller than border and padding in horizontal direction
  // when box-sizing is border-box which is only supported.
  double minConstraintWidth = renderStyle.effectiveBorderLeftWidth.computedValue +
      renderStyle.effectiveBorderRightWidth.computedValue +
      renderStyle.paddingLeft.computedValue +
      renderStyle.paddingRight.computedValue;

  double? parentBoxContentConstraintsWidth;
  if (renderStyle.isParentRenderBoxModel() &&
      (renderStyle.isSelfRenderLayoutBox() || renderStyle.isSelfRenderWidget())) {
    RenderBoxModel parentRenderBoxModel = (renderStyle.getParentRenderStyle()!.attachedRenderBoxModel!);

    // Inline-block shrink-to-fit: when the parent is inline-block with auto width,
    // do not bound block children by the parent's finite content width. This allows
    // the child to compute its own natural width and lets the parent shrink-wrap.
    bool parentIsInlineBlockAutoWidth = parentRenderBoxModel.renderStyle.effectiveDisplay == CSSDisplay.inlineBlock &&
        parentRenderBoxModel.renderStyle.width.isAuto;

    if (parentIsInlineBlockAutoWidth) {
        parentBoxContentConstraintsWidth = double.infinity;
    } else {
      // Prefer the actual layout parent's content constraints if available,
      // because CSS parent may not yet have computed contentConstraints during IFC layout.
      BoxConstraints? candidate;
      if (parent is RenderBoxModel) {
        candidate = (parent as RenderBoxModel).contentConstraints;
      }
      candidate ??= parentRenderBoxModel.contentConstraints;

      if (candidate != null) {
        // When using CSS parent constraints, deflate with the current element's margins
        // to match previous behavior; otherwise use the raw layout parent's constraints.
        if (identical(candidate, parentRenderBoxModel.contentConstraints)) {
          parentBoxContentConstraintsWidth =
              parentRenderBoxModel.renderStyle.deflateMarginConstraints(candidate).maxWidth;
        } else {
          parentBoxContentConstraintsWidth = candidate.maxWidth;
        }

        // When inner minimal content size are larger that parent's constraints,
        // still use parent constraints but ensure minConstraintWidth is properly handled later
        if (parentBoxContentConstraintsWidth < minConstraintWidth) {
          // Keep parentBoxContentConstraintsWidth; resolution happens below.
        }
      }
    }

    // Flex context adjustments
    if (renderStyle.isParentRenderFlexLayout()) {
      final RenderFlexLayout flexParent =
          renderStyle.getParentRenderStyle()!.attachedRenderBoxModel! as RenderFlexLayout;
      // FlexItems with flex:none won't inherit parent box's constraints
      if (flexParent.isFlexNone(this)) {
        parentBoxContentConstraintsWidth = null;
      }

      // In a column-direction flex container, a flex item with auto cross-size (width)
      // that is not stretched in the cross axis should not inherit the container's
      // bounded width during its own constraint computation. Let it shrink-to-fit
      // its contents instead (so percentage paddings resolve against the item's
      // own width rather than the container width).
      final bool isColumn = !CSSFlex.isHorizontalFlexDirection(flexParent.renderStyle.flexDirection);
      if (isColumn) {
        // Determine if this flex item would be stretched in the cross axis.
        final AlignSelf self = renderStyle.alignSelf;
        final bool parentStretch = flexParent.renderStyle.alignItems == AlignItems.stretch;
        final bool shouldStretch = self == AlignSelf.auto ? parentStretch : self == AlignSelf.stretch;
        final bool crossAuto = renderStyle.width.isAuto;

        if (!shouldStretch && crossAuto) {
          // Do not adopt the parent's bounded width; use intrinsic sizing.
          parentBoxContentConstraintsWidth = null;
        }
      }
    }
    // For absolutely/fixed positioned elements inside a flex container, avoid collapsing
    // their intrinsic width to 0 solely because the flex item has a content constraint
    // width of 0 (e.g., auto-width flex item whose only child is out-of-flow).
    // In such cases, the containing block still exists, but the abspos box should size
    // from its own content rather than the flex item's zero content width.
    final bool isAbsOrFixed = renderStyle.position == CSSPositionType.absolute ||
        renderStyle.position == CSSPositionType.fixed;
    if (isAbsOrFixed &&
        renderStyle.width.isAuto &&
        renderStyle.isParentRenderFlexLayout() &&
        parentBoxContentConstraintsWidth != null &&
        parentBoxContentConstraintsWidth == 0) {
      parentBoxContentConstraintsWidth = null;
    }
  } else if (isDisplayInline && parent is RenderFlowLayout) {
    // For inline elements inside a flow layout, check if we should inherit parent's constraints
    RenderFlowLayout parentFlow = parent as RenderFlowLayout;

    // Skip constraint inheritance if parent is a flex item with flex: none (flex-grow: 0, flex-shrink: 0)
    if (parentFlow.renderStyle.isParentRenderFlexLayout()) {
      RenderFlexLayout flexParent =
          parentFlow.renderStyle.getParentRenderStyle()!.attachedRenderBoxModel as RenderFlexLayout;
      if (flexParent.isFlexNone(parentFlow)) {
        // Don't inherit constraints for flex: none items
        parentBoxContentConstraintsWidth = null;
      } else {
        double parentContentWidth = parentFlow.renderStyle.contentMaxConstraintsWidth;
        if (parentContentWidth != double.infinity) {
          parentBoxContentConstraintsWidth = parentContentWidth;
        }
      }
    } else {
      // Not in a flex context, inherit parent's content width constraint normally
      double parentContentWidth = parentFlow.renderStyle.contentMaxConstraintsWidth;
      if (parentContentWidth != double.infinity) {
        parentBoxContentConstraintsWidth = parentContentWidth;
      }
    }
  }

  // CSS abspos width for non-replaced boxes with width:auto follows the
  // shrink-to-fit algorithm in CSS 2.1 ยง10.3.7 / CSS Position 3, except when
  // both left and right are non-auto (in which case width is solved directly
  // from the insets equation).
  //
  // Approximation: when width:auto and not (left & right both non-auto),
  // do not clamp the positioned box by the parent's content max-width.
  // Let the box measure to its intrinsic content width (subject to any
  // explicit min/max-width), which matches shrink-to-fit in the common
  // case where content width <= available width.
  final bool _absOrFixedForWidth = renderStyle.position == CSSPositionType.absolute ||
      renderStyle.position == CSSPositionType.fixed;
  final bool _widthAutoForAbs = renderStyle.width.isAuto;
  final bool _bothLRNonAuto = renderStyle.left.isNotAuto && renderStyle.right.isNotAuto;
  if (_absOrFixedForWidth &&
      !_bothLRNonAuto &&
      _widthAutoForAbs &&
      !renderStyle.isSelfRenderReplaced() &&
      renderStyle.borderBoxLogicalWidth == null &&
      parentBoxContentConstraintsWidth != null) {
    parentBoxContentConstraintsWidth = null;
  }

  double maxConstraintWidth =
      renderStyle.borderBoxLogicalWidth ?? parentBoxContentConstraintsWidth ?? double.infinity;

  // For absolutely/fixed positioned non-replaced elements with both left and right specified
  // and width:auto, compute the used border-box width from the containing block at layout time.
  // This handles cases where style-tree logical widths are unavailable (e.g., parent inline-block
  // with auto width) but the containing block has been measured. See:
  // https://www.w3.org/TR/css-position-3/#abs-non-replaced-width
  final bool isAbsOrFixed = renderStyle.position == CSSPositionType.absolute ||
      renderStyle.position == CSSPositionType.fixed;
  if (maxConstraintWidth == double.infinity &&
      isAbsOrFixed &&
      !renderStyle.isSelfRenderReplaced() &&
      renderStyle.width.isAuto &&
      renderStyle.left.isNotAuto &&
      renderStyle.right.isNotAuto &&
      parent is RenderBoxModel) {
    final RenderBoxModel cb = parent as RenderBoxModel;
    double? parentPaddingBoxWidth;
    // Use the parent's content constraints (plus padding) as the containing block width.
    // Avoid using parent.size or style-tree logical widths here to prevent feedback loops
    // in flex/inline-block shrink-to-fit scenarios.
    final BoxConstraints? pcc = cb.contentConstraints;
    if (pcc != null && pcc.maxWidth.isFinite) {
      parentPaddingBoxWidth = pcc.maxWidth +
          cb.renderStyle.paddingLeft.computedValue +
          cb.renderStyle.paddingRight.computedValue;
    }
    if (parentPaddingBoxWidth != null && parentPaddingBoxWidth.isFinite) {
      // Solve the horizontal insets equation for the child border-box width.
      double solvedBorderBoxWidth = parentPaddingBoxWidth -
          renderStyle.left.computedValue -
          renderStyle.right.computedValue -
          renderStyle.marginLeft.computedValue -
          renderStyle.marginRight.computedValue;
      // Guard against negative sizes.
      solvedBorderBoxWidth = math.max(0, solvedBorderBoxWidth);
      // Use a tight width so empty positioned boxes still fill the available space.
      minConstraintWidth = solvedBorderBoxWidth;
      maxConstraintWidth = solvedBorderBoxWidth;
    }
  }
  // Height should be not smaller than border and padding in vertical direction
  // when box-sizing is border-box which is only supported.
  double minConstraintHeight = renderStyle.effectiveBorderTopWidth.computedValue +
      renderStyle.effectiveBorderBottomWidth.computedValue +
      renderStyle.paddingTop.computedValue +
      renderStyle.paddingBottom.computedValue;
  double maxConstraintHeight = renderStyle.borderBoxLogicalHeight ?? double.infinity;

  // // Apply maxHeight constraint if specified
  // if (maxHeight != null && maxHeight < maxConstraintHeight) {
  //   maxConstraintHeight = maxHeight;
  // }

  if (parent is RenderFlexLayout) {
    double? flexBasis = renderStyle.flexBasis == CSSLengthValue.auto ? null : renderStyle.flexBasis?.computedValue;
    RenderBoxModel? parentRenderBoxModel = parent as RenderBoxModel?;
    // In flex layout, flex basis takes priority over width/height if set.
    // Flex-basis cannot be smaller than its content size which happens can not be known
    // in constraints apply stage, so flex-basis acts as min-width in constraints apply stage.
    if (flexBasis != null) {
      if (CSSFlex.isHorizontalFlexDirection(parentRenderBoxModel!.renderStyle.flexDirection)) {
        minWidth = minWidth != null ? math.max(flexBasis, minWidth) : flexBasis;
      } else {
        minHeight = minHeight != null ? math.max(flexBasis, minHeight) : flexBasis;
      }
    }
  }

  // Apply min/max width constraints for all display types
  if (minWidth != null && !isDisplayInline) {
    minConstraintWidth = minConstraintWidth < minWidth ? minWidth : minConstraintWidth;
    maxConstraintWidth = maxConstraintWidth < minWidth ? minWidth : maxConstraintWidth;
  }

  // Apply maxWidth constraint for all elements (including inline)
  if (maxWidth != null) {
    // Ensure maxConstraintWidth respects maxWidth, but don't reduce minConstraintWidth below border+padding
    maxConstraintWidth = maxConstraintWidth > maxWidth ? maxWidth : maxConstraintWidth;
    // Only reduce minConstraintWidth if maxWidth is larger than border+padding requirements
    double borderPadding = renderStyle.effectiveBorderLeftWidth.computedValue +
        renderStyle.effectiveBorderRightWidth.computedValue +
        renderStyle.paddingLeft.computedValue +
        renderStyle.paddingRight.computedValue;
    if (maxWidth >= borderPadding) {
      minConstraintWidth = minConstraintWidth > maxWidth ? maxWidth : minConstraintWidth;
    }
  }

  // Apply min/max height constraints when display is not inline
  if (!isDisplayInline) {
    if (minHeight != null) {
      minConstraintHeight = minConstraintHeight < minHeight ? minHeight : minConstraintHeight;
      maxConstraintHeight = maxConstraintHeight < minHeight ? minHeight : maxConstraintHeight;
    }
    if (maxHeight != null) {
      minConstraintHeight = minConstraintHeight > maxHeight ? maxHeight : minConstraintHeight;
      maxConstraintHeight = maxConstraintHeight > maxHeight ? maxHeight : maxConstraintHeight;
    }
  }

  // Normalize constraints to satisfy Flutter's requirement: min <= max.
  // This can happen when the computed minimum width from border/padding
  // exceeds the available max width from the parent (e.g., inline-block
  // with large horizontal padding inside a narrow container). In such
  // cases, cap the minimum to the maximum so constraints are valid.
  if (minConstraintWidth > maxConstraintWidth) {
    minConstraintWidth = maxConstraintWidth;
  }
  if (minConstraintHeight > maxConstraintHeight) {
    minConstraintHeight = maxConstraintHeight;
  }

  BoxConstraints constraints = BoxConstraints(
    minWidth: minConstraintWidth,
    maxWidth: maxConstraintWidth,
    minHeight: minConstraintHeight,
    maxHeight: maxConstraintHeight,
  );

  return constraints;
}