performPositioning method
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;
}