performPositioning method

  1. @override
LayoutRect performPositioning(
  1. LayoutSize viewportSize,
  2. LayoutSize contentSize
)
override

Calculates the positioning rectangle for content within the viewport.

This method determines how the flex content should be positioned within the available viewport space, taking into account scrolling offsets and content size. It handles the final positioning phase of flex layout.

The returned rectangle defines the bounds of the visible content area and is used for scrolling calculations and viewport management.

Implementation

@override
LayoutRect performPositioning(
  LayoutSize viewportSize,
  LayoutSize contentSize,
) {
  LayoutRect bounds = LayoutRect.zero;

  double viewportMainSize = switch (layout.direction.axis) {
    LayoutAxis.horizontal => viewportSize.width,
    LayoutAxis.vertical => viewportSize.height,
  };
  double viewportCrossSize = switch (layout.direction.axis) {
    LayoutAxis.horizontal => viewportSize.height,
    LayoutAxis.vertical => viewportSize.width,
  };
  double contentMainSize = switch (layout.direction.axis) {
    LayoutAxis.horizontal => contentSize.width,
    LayoutAxis.vertical => contentSize.height,
  };
  double contentCrossSize = switch (layout.direction.axis) {
    LayoutAxis.horizontal => contentSize.height,
    LayoutAxis.vertical => contentSize.width,
  };

  // layout absolute children
  ChildLayout? child = parent.firstLayoutChild;
  // note: absolute children don't use content size ([size])
  // instead it uses the constraints, so padding or any spacing
  // does not affect the absolute children
  while (child != null) {
    final data = child.layoutData;
    if (data.behavior != LayoutBehavior.absolute) {
      child = child.nextSibling;
      continue;
    }

    double? topOffset;
    double? leftOffset;
    double? rightOffset;
    double? bottomOffset;

    if (data.top != null) {
      topOffset = data.top!.computePosition(
        parent: parent,
        child: child,
        direction: LayoutAxis.vertical,
      );
    }
    if (data.left != null) {
      leftOffset = data.left!.computePosition(
        parent: parent,
        child: child,
        direction: LayoutAxis.horizontal,
      );
    }
    if (data.right != null) {
      rightOffset = data.right!.computePosition(
        parent: parent,
        child: child,
        direction: LayoutAxis.horizontal,
      );
    }
    if (data.bottom != null) {
      bottomOffset = data.bottom!.computePosition(
        parent: parent,
        child: child,
        direction: LayoutAxis.vertical,
      );
    }

    double? maxWidth;
    double? maxHeight;
    double? minWidth;
    double? minHeight;

    double? availableWidth;
    double? availableHeight;

    double? aspectRatio = data.aspectRatio;

    if (topOffset != null && bottomOffset != null) {
      availableHeight = viewportSize.height - topOffset - bottomOffset;
    }
    if (leftOffset != null && rightOffset != null) {
      availableWidth = viewportSize.width - leftOffset - rightOffset;
    }

    if (data.maxWidth != null) {
      maxWidth = data.maxWidth!.computeSize(
        parent: parent,
        child: child,
        layoutHandle: this,
        axis: LayoutAxis.horizontal,
        contentSize: availableWidth != null && availableHeight != null
            ? LayoutSize(availableWidth, availableHeight)
            : viewportSize,
        viewportSize: viewportSize,
      );
    }
    if (data.maxHeight != null) {
      maxHeight = data.maxHeight!.computeSize(
        parent: parent,
        child: child,
        layoutHandle: this,
        axis: LayoutAxis.vertical,
        contentSize: availableWidth != null && availableHeight != null
            ? LayoutSize(availableWidth, availableHeight)
            : viewportSize,
        viewportSize: viewportSize,
      );
    }
    if (data.minWidth != null) {
      minWidth = data.minWidth!.computeSize(
        parent: parent,
        child: child,
        layoutHandle: this,
        axis: LayoutAxis.horizontal,
        contentSize: availableWidth != null && availableHeight != null
            ? LayoutSize(availableWidth, availableHeight)
            : viewportSize,
        viewportSize: viewportSize,
      );
    }
    if (data.minHeight != null) {
      minHeight = data.minHeight!.computeSize(
        parent: parent,
        child: child,
        layoutHandle: this,
        axis: LayoutAxis.vertical,
        contentSize: availableWidth != null && availableHeight != null
            ? LayoutSize(availableWidth, availableHeight)
            : viewportSize,
        viewportSize: viewportSize,
      );
    }

    double? preferredWidth;
    double? preferredHeight;
    if (data.width != null) {
      preferredWidth = data.width!.computeSize(
        parent: parent,
        child: child,
        layoutHandle: this,
        axis: LayoutAxis.horizontal,
        contentSize: availableWidth != null && availableHeight != null
            ? LayoutSize(availableWidth, availableHeight)
            : viewportSize,
        viewportSize: viewportSize,
      );
      preferredWidth = _clampNullableDouble(
        preferredWidth,
        minWidth,
        maxWidth,
      );
    } else if (availableWidth != null) {
      preferredWidth = _clampNullableDouble(
        availableWidth,
        minWidth,
        maxWidth,
      );
    }
    if (data.height != null) {
      preferredHeight = data.height!.computeSize(
        parent: parent,
        child: child,
        layoutHandle: this,
        axis: LayoutAxis.vertical,
        contentSize: availableWidth != null && availableHeight != null
            ? LayoutSize(availableWidth, availableHeight)
            : viewportSize,
        viewportSize: viewportSize,
      );
      preferredHeight = _clampNullableDouble(
        preferredHeight,
        minHeight,
        maxHeight,
      );
    } else if (availableHeight != null) {
      preferredHeight = _clampNullableDouble(
        availableHeight,
        minHeight,
        maxHeight,
      );
    }

    // handle aspect ratio
    if (aspectRatio != null) {
      if (preferredWidth != null && preferredHeight == null) {
        preferredHeight = preferredWidth / aspectRatio;
        preferredHeight = _clampNullableDouble(
          preferredHeight,
          minHeight,
          maxHeight,
        );
      } else if (preferredHeight != null && preferredWidth == null) {
        preferredWidth = preferredHeight * aspectRatio;
        preferredWidth = _clampNullableDouble(
          preferredWidth,
          minWidth,
          maxWidth,
        );
      }
    }

    preferredWidth = _clampNullableDouble(
      preferredWidth,
      0.0,
      double.infinity,
    );
    preferredHeight = _clampNullableDouble(
      preferredHeight,
      0.0,
      double.infinity,
    );

    LayoutOffset offset;
    LayoutSize size = LayoutSize(
      preferredWidth ?? 0.0,
      preferredHeight ?? 0.0,
    );

    switch (parent.textDirection) {
      case LayoutTextDirection.ltr:
        offset = LayoutOffset(
          leftOffset ??
              (rightOffset != null
                  ? viewportSize.width - rightOffset - size.width
                  : 0.0),
          topOffset ??
              (bottomOffset != null
                  ? viewportSize.height - bottomOffset - size.height
                  : 0.0),
        );
      case LayoutTextDirection.rtl:
        offset = LayoutOffset(
          rightOffset != null
              ? viewportSize.width - rightOffset - size.width
              : (leftOffset ?? 0.0),
          topOffset ??
              (bottomOffset != null
                  ? viewportSize.height - bottomOffset - size.height
                  : 0.0),
        );
    }

    child.layout(offset, size, OverflowBounds.zero);

    LayoutRect childBounds = offset & size;
    bounds = bounds.expandToInclude(childBounds);

    child = child.nextSibling;
  }

  // positions non-absolute children
  bool reverseWrap = layout.wrap == FlexWrap.wrapReverse;

  bool reverseMain = false;
  bool reverseCross = false;
  if (layout.direction.reverse) {
    reverseMain = !reverseMain;
  }
  // ALREADY HANDLED BY THE ALIGNMENT
  // if (layout.direction.axis == LayoutAxis.horizontal &&
  //     parent.textDirection == LayoutTextDirection.rtl) {
  //   reverseMain = !reverseMain;
  // }
  if (reverseWrap) {
    reverseCross = !reverseCross;
  }
  // ALREADY HANDLED BY THE ALIGNMENT
  // if (layout.direction.axis == LayoutAxis.vertical &&
  //     parent.textDirection == LayoutTextDirection.rtl) {
  //   reverseCross = !reverseCross;
  // }

  double crossContentOffset = reverseCross
      ? contentCrossSize - cache.crossEndSpacing
      : cache.crossStartSpacing;
  final alignItemsNeedsBaseline = layout.alignItems.needsBaseline(
    parent: parent,
    axis: switch (layout.direction.axis) {
      LayoutAxis.horizontal => LayoutAxis.vertical,
      LayoutAxis.vertical => LayoutAxis.horizontal,
    },
  );
  bool isWrapped = cache.lineCount > 1;
  if (isWrapped) {
    double alignContent = layout.alignContent.align(
      parent: parent,
      axis: switch (layout.direction.axis) {
        LayoutAxis.horizontal => LayoutAxis.vertical,
        LayoutAxis.vertical => LayoutAxis.horizontal,
      },
      viewportSize: viewportCrossSize,
      contentSize: contentCrossSize,
      // align content does not need baseline
      maxBaseline: 0.0,
      childBaseline: 0.0,
    );
    crossContentOffset += alignContent;
  }
  FlexLineLayoutCache? line = cache.firstLine;
  while (line != null) {
    double mainLineOffset = reverseMain
        ? contentMainSize - line.mainSpacingEnd
        : line.mainSpacingStart;

    final justifyContent = layout.justifyContent.align(
      parent: parent,
      axis: layout.direction.axis,
      viewportSize: viewportMainSize,
      contentSize: line.mainSize,
      // justify content does not need baseline
      maxBaseline: 0.0,
      childBaseline: 0.0,
    );

    mainLineOffset += justifyContent;

    if (reverseCross) {
      // subtract right away
      crossContentOffset -= line.crossSize;
      if (line != cache.firstLine) {
        crossContentOffset -= cache.crossSpacing;
      }
    }

    ChildLayout? child = line.firstChild;
    int childIndex = 0;
    while (child != null && child != line.lastChild) {
      // handled separately
      if (child.layoutData.behavior == LayoutBehavior.absolute) {
        child = child.nextSibling;
        continue;
      }

      final childCache = child.layoutCache as FlexChildLayoutCache;

      if (reverseMain) {
        mainLineOffset -= childCache.mainFlexSize ?? 0.0;
      } else {
        if (childIndex > 0) {
          mainLineOffset += line.mainSpacing;
        }
      }

      double mainOffset = mainLineOffset;
      double crossOffset = crossContentOffset;

      bool? alignSelfNeedsBaseline = childCache.alignSelfNeedsBaseline;
      // note:
      // alignSelf - acts as override to alignItems
      // alignItems - aligns all items in the line
      // alignContent - aligns all lines in the container
      double? alignSelf = child.layoutData.alignSelf?.align(
        parent: parent,
        axis: switch (layout.direction.axis) {
          LayoutAxis.horizontal => LayoutAxis.vertical,
          LayoutAxis.vertical => LayoutAxis.horizontal,
        },
        viewportSize: isWrapped
            ? line.crossSize
            : max(contentCrossSize, viewportCrossSize),
        contentSize: childCache.crossSize ?? 0.0,
        // align self does not need baseline
        maxBaseline: line.biggestBaseline,
        childBaseline: alignSelfNeedsBaseline == true
            ? childCache.baseline! // should be non-null here
            // because we computed it earlier
            : 0.0,
      );

      alignSelf ??= layout.alignItems.align(
        parent: parent,
        axis: switch (layout.direction.axis) {
          LayoutAxis.horizontal => LayoutAxis.vertical,
          LayoutAxis.vertical => LayoutAxis.horizontal,
        },
        viewportSize: isWrapped
            ? line.crossSize
            : max(contentCrossSize, viewportCrossSize),
        contentSize: childCache.crossSize ?? 0.0,
        maxBaseline: line.biggestBaseline,
        childBaseline: alignItemsNeedsBaseline
            ? childCache.baseline! // should be non-null here
            // because we computed it earlier
            : 0.0,
      );

      if (childCache.crossSize == null) {
        double? adjustCrossSize;

        if (isWrapped) {
          adjustCrossSize = child.layoutData.alignSelf?.adjustSize(
            parent: parent,
            axis: switch (layout.direction.axis) {
              LayoutAxis.horizontal => LayoutAxis.vertical,
              LayoutAxis.vertical => LayoutAxis.horizontal,
            },
            viewportSize: max(contentCrossSize, viewportCrossSize),
            contentSize: line.crossSize,
          );

          adjustCrossSize ??= layout.alignItems.adjustSize(
            parent: parent,
            axis: switch (layout.direction.axis) {
              LayoutAxis.horizontal => LayoutAxis.vertical,
              LayoutAxis.vertical => LayoutAxis.horizontal,
            },
            viewportSize: max(contentCrossSize, viewportCrossSize),
            contentSize: line.crossSize,
          );
        } else {
          adjustCrossSize = child.layoutData.alignSelf?.adjustSize(
            parent: parent,
            axis: switch (layout.direction.axis) {
              LayoutAxis.horizontal => LayoutAxis.vertical,
              LayoutAxis.vertical => LayoutAxis.horizontal,
            },
            viewportSize: max(contentCrossSize, viewportCrossSize),
            contentSize: contentCrossSize,
          );
          adjustCrossSize ??= layout.alignItems.adjustSize(
            parent: parent,
            axis: switch (layout.direction.axis) {
              LayoutAxis.horizontal => LayoutAxis.vertical,
              LayoutAxis.vertical => LayoutAxis.horizontal,
            },
            viewportSize: max(contentCrossSize, viewportCrossSize),
            contentSize: contentCrossSize,
          );
        }

        if (adjustCrossSize != null) {
          childCache.crossSize = adjustCrossSize;
        }
      }

      double dx = switch (layout.direction.axis) {
        LayoutAxis.horizontal => mainOffset,
        LayoutAxis.vertical => crossOffset + alignSelf,
      };
      double dy = switch (layout.direction.axis) {
        LayoutAxis.horizontal => crossOffset + alignSelf,
        LayoutAxis.vertical => mainOffset,
      };

      // apply scroll offset
      dx -= parent.scrollOffsetX;
      dy -= parent.scrollOffsetY;

      // apply sticky adjustment
      LayoutRect contentRect = LayoutRect.fromLTWH(
        dx,
        dy,
        childCache.mainFlexSize ?? 0.0,
        childCache.crossSize ?? 0.0,
      );
      // the bounds rely on the top/left/right/bottom position defined
      // in the layout data, otherwise it uses the parent content bounds
      // note: these bounds are relative to the viewport size ([constraints])
      double? topBound;
      double? leftBound;
      double? rightBound;
      double? bottomBound;
      if (child.layoutData.top != null) {
        topBound =
            child.layoutData.top!.computePosition(
              parent: parent,
              child: child,
              direction: LayoutAxis.vertical,
            ) +
            cache.paddingTop;
      } else {
        topBound = double.negativeInfinity;
      }
      if (child.layoutData.left != null) {
        leftBound =
            child.layoutData.left!.computePosition(
              parent: parent,
              child: child,
              direction: LayoutAxis.horizontal,
            ) +
            cache.paddingLeft;
      } else {
        leftBound = double.negativeInfinity;
      }
      if (child.layoutData.right != null) {
        rightBound =
            viewportSize.width -
            child.layoutData.right!.computePosition(
              parent: parent,
              child: child,
              direction: LayoutAxis.horizontal,
            ) -
            cache.paddingRight;
      } else {
        rightBound = double.infinity;
      }
      if (child.layoutData.bottom != null) {
        bottomBound =
            viewportSize.height -
            child.layoutData.bottom!.computePosition(
              parent: parent,
              child: child,
              direction: LayoutAxis.vertical,
            ) -
            cache.paddingBottom;
      } else {
        bottomBound = double.infinity;
      }
      LayoutRect limitBounds = LayoutRect.fromLTRB(
        leftBound,
        topBound,
        rightBound,
        bottomBound,
      );
      double overflowTop = limitBounds.top - contentRect.top;
      double overflowLeft = limitBounds.left - contentRect.left;
      double overflowRight = contentRect.right - limitBounds.right;
      double overflowBottom = contentRect.bottom - limitBounds.bottom;
      LayoutOffset? revealOffset = LayoutOffset(dx, dy);
      LayoutOffset? boundOffset = _limitRectToBounds(
        contentRect,
        limitBounds,
      );
      if (boundOffset != null) {
        dx = boundOffset.dx;
        dy = boundOffset.dy;
      } else {
        revealOffset = null;
        final currentBounds = line.bounds;
        if (currentBounds != null) {
          // store the bounds for the line
          line.bounds = currentBounds.expandToInclude(contentRect);
        } else {
          line.bounds = contentRect;
        }
      }

      LayoutOffset offset = LayoutOffset(dx, dy);
      LayoutSize size = switch (layout.direction.axis) {
        LayoutAxis.horizontal => LayoutSize(
          childCache.mainFlexSize ?? 0.0,
          childCache.crossSize ?? 0.0,
        ),
        LayoutAxis.vertical => LayoutSize(
          childCache.crossSize ?? 0.0,
          childCache.mainFlexSize ?? 0.0,
        ),
      };
      child.layout(
        offset,
        size,
        OverflowBounds(
          top: _validateOverflowBounds(overflowTop),
          left: _validateOverflowBounds(overflowLeft),
          right: _validateOverflowBounds(overflowRight),
          bottom: _validateOverflowBounds(overflowBottom),
        ),
        revealOffset: revealOffset,
      );

      LayoutRect childBounds = offset & size;
      bounds = bounds.expandToInclude(childBounds);

      if (!reverseMain) {
        mainLineOffset += childCache.mainFlexSize ?? 0.0;
      } else {
        mainLineOffset -= line.mainSpacing;
      }

      child = child.nextSibling;
      childIndex++;
    }

    if (!reverseCross) {
      crossContentOffset += line.crossSize + cache.crossSpacing;
    }

    line = line.nextLine;
  }
  return bounds;
}