diff --git a/yoga/Yoga.cpp b/yoga/Yoga.cpp index 3e6cb6eca6..1a1ba7e1aa 100644 --- a/yoga/Yoga.cpp +++ b/yoga/Yoga.cpp @@ -5,25 +5,17 @@ * LICENSE file in the root directory of this source tree. */ -#include -#include -#include -#include -#include -#include - #include #include -#include -#include -#include +#include +#include +#include #include #include #include #include #include -#include using namespace facebook; using namespace facebook::yoga; @@ -981,25 +973,6 @@ YG_NODE_LAYOUT_RESOLVED_PROPERTY_IMPL(float, Margin, margin) YG_NODE_LAYOUT_RESOLVED_PROPERTY_IMPL(float, Border, border) YG_NODE_LAYOUT_RESOLVED_PROPERTY_IMPL(float, Padding, padding) -std::atomic gCurrentGenerationCount(0); - -bool YGLayoutNodeInternal( - yoga::Node* const node, - const float availableWidth, - const float availableHeight, - const YGDirection ownerDirection, - const YGMeasureMode widthMeasureMode, - const YGMeasureMode heightMeasureMode, - const float ownerWidth, - const float ownerHeight, - const bool performLayout, - const LayoutPassReason reason, - const YGConfigRef config, - LayoutData& layoutMarkerData, - void* const layoutContext, - const uint32_t depth, - const uint32_t generationCount); - #ifdef DEBUG YOGA_EXPORT void YGNodePrint( const YGNodeRef nodeRef, @@ -1011,3080 +984,16 @@ YOGA_EXPORT void YGNodePrint( } #endif -static const std::array pos = {{ - YGEdgeTop, - YGEdgeBottom, - YGEdgeLeft, - YGEdgeRight, -}}; - -static const std::array dim = { - {YGDimensionHeight, YGDimensionHeight, YGDimensionWidth, YGDimensionWidth}}; - -static inline float YGNodePaddingAndBorderForAxis( - const yoga::Node* const node, - const YGFlexDirection axis, - const float widthSize) { - return (node->getLeadingPaddingAndBorder(axis, widthSize) + - node->getTrailingPaddingAndBorder(axis, widthSize)) - .unwrap(); -} - -static inline YGAlign YGNodeAlignItem( - const yoga::Node* node, - const yoga::Node* child) { - const YGAlign align = child->getStyle().alignSelf() == YGAlignAuto - ? node->getStyle().alignItems() - : child->getStyle().alignSelf(); - if (align == YGAlignBaseline && isColumn(node->getStyle().flexDirection())) { - return YGAlignFlexStart; - } - return align; -} - -static float YGBaseline(yoga::Node* node, void* layoutContext) { - if (node->hasBaselineFunc()) { - - Event::publish(node); - - const float baseline = node->baseline( - node->getLayout().measuredDimensions[YGDimensionWidth], - node->getLayout().measuredDimensions[YGDimensionHeight], - layoutContext); - - Event::publish(node); - - yoga::assertFatalWithNode( - node, - !yoga::isUndefined(baseline), - "Expect custom baseline function to not return NaN"); - return baseline; - } - - yoga::Node* baselineChild = nullptr; - const uint32_t childCount = YGNodeGetChildCount(node); - for (uint32_t i = 0; i < childCount; i++) { - auto child = node->getChild(i); - if (child->getLineIndex() > 0) { - break; - } - if (child->getStyle().positionType() == YGPositionTypeAbsolute) { - continue; - } - if (YGNodeAlignItem(node, child) == YGAlignBaseline || - child->isReferenceBaseline()) { - baselineChild = child; - break; - } - - if (baselineChild == nullptr) { - baselineChild = child; - } - } - - if (baselineChild == nullptr) { - return node->getLayout().measuredDimensions[YGDimensionHeight]; - } - - const float baseline = YGBaseline(baselineChild, layoutContext); - return baseline + baselineChild->getLayout().position[YGEdgeTop]; -} - -static bool YGIsBaselineLayout(const yoga::Node* node) { - if (isColumn(node->getStyle().flexDirection())) { - return false; - } - if (node->getStyle().alignItems() == YGAlignBaseline) { - return true; - } - const uint32_t childCount = YGNodeGetChildCount(node); - for (uint32_t i = 0; i < childCount; i++) { - auto child = node->getChild(i); - if (child->getStyle().positionType() != YGPositionTypeAbsolute && - child->getStyle().alignSelf() == YGAlignBaseline) { - return true; - } - } - - return false; -} - -static inline float YGNodeDimWithMargin( - const yoga::Node* const node, - const YGFlexDirection axis, - const float widthSize) { - return node->getLayout().measuredDimensions[dim[axis]] + - (node->getLeadingMargin(axis, widthSize) + - node->getTrailingMargin(axis, widthSize)) - .unwrap(); -} - -static inline bool YGNodeIsStyleDimDefined( - const yoga::Node* const node, - const YGFlexDirection axis, - const float ownerSize) { - bool isUndefined = - yoga::isUndefined(node->getResolvedDimension(dim[axis]).value); - return !( - node->getResolvedDimension(dim[axis]).unit == YGUnitAuto || - node->getResolvedDimension(dim[axis]).unit == YGUnitUndefined || - (node->getResolvedDimension(dim[axis]).unit == YGUnitPoint && - !isUndefined && node->getResolvedDimension(dim[axis]).value < 0.0f) || - (node->getResolvedDimension(dim[axis]).unit == YGUnitPercent && - !isUndefined && - (node->getResolvedDimension(dim[axis]).value < 0.0f || - yoga::isUndefined(ownerSize)))); -} - -static inline bool YGNodeIsLayoutDimDefined( - const yoga::Node* const node, - const YGFlexDirection axis) { - const float value = node->getLayout().measuredDimensions[dim[axis]]; - return !yoga::isUndefined(value) && value >= 0.0f; -} - -static FloatOptional YGNodeBoundAxisWithinMinAndMax( - const yoga::Node* const node, - const YGFlexDirection axis, - const FloatOptional value, - const float axisSize) { - FloatOptional min; - FloatOptional max; - - if (isColumn(axis)) { - min = yoga::resolveValue( - node->getStyle().minDimensions()[YGDimensionHeight], axisSize); - max = yoga::resolveValue( - node->getStyle().maxDimensions()[YGDimensionHeight], axisSize); - } else if (isRow(axis)) { - min = yoga::resolveValue( - node->getStyle().minDimensions()[YGDimensionWidth], axisSize); - max = yoga::resolveValue( - node->getStyle().maxDimensions()[YGDimensionWidth], axisSize); - } - - if (max >= FloatOptional{0} && value > max) { - return max; - } - - if (min >= FloatOptional{0} && value < min) { - return min; - } - - return value; -} - -// Like YGNodeBoundAxisWithinMinAndMax but also ensures that the value doesn't -// go below the padding and border amount. -static inline float YGNodeBoundAxis( - const yoga::Node* const node, - const YGFlexDirection axis, - const float value, - const float axisSize, - const float widthSize) { - return yoga::maxOrDefined( - YGNodeBoundAxisWithinMinAndMax(node, axis, FloatOptional{value}, axisSize) - .unwrap(), - YGNodePaddingAndBorderForAxis(node, axis, widthSize)); -} - -static void YGNodeSetChildTrailingPosition( - const yoga::Node* const node, - yoga::Node* const child, - const YGFlexDirection axis) { - const float size = child->getLayout().measuredDimensions[dim[axis]]; - child->setLayoutPosition( - node->getLayout().measuredDimensions[dim[axis]] - size - - child->getLayout().position[pos[axis]], - trailingEdge(axis)); -} - -static void YGConstrainMaxSizeForMode( - const yoga::Node* const node, - const enum YGFlexDirection axis, - const float ownerAxisSize, - const float ownerWidth, - YGMeasureMode* mode, - float* size) { - const FloatOptional maxSize = - yoga::resolveValue( - node->getStyle().maxDimensions()[dim[axis]], ownerAxisSize) + - FloatOptional(node->getMarginForAxis(axis, ownerWidth)); - switch (*mode) { - case YGMeasureModeExactly: - case YGMeasureModeAtMost: - *size = (maxSize.isUndefined() || *size < maxSize.unwrap()) - ? *size - : maxSize.unwrap(); - break; - case YGMeasureModeUndefined: - if (!maxSize.isUndefined()) { - *mode = YGMeasureModeAtMost; - *size = maxSize.unwrap(); - } - break; - } -} - -static void YGNodeComputeFlexBasisForChild( - const yoga::Node* const node, - yoga::Node* const child, - const float width, - const YGMeasureMode widthMode, - const float height, - const float ownerWidth, - const float ownerHeight, - const YGMeasureMode heightMode, - const YGDirection direction, - const YGConfigRef config, - LayoutData& layoutMarkerData, - void* const layoutContext, - const uint32_t depth, - const uint32_t generationCount) { - const YGFlexDirection mainAxis = - resolveDirection(node->getStyle().flexDirection(), direction); - const bool isMainAxisRow = isRow(mainAxis); - const float mainAxisSize = isMainAxisRow ? width : height; - const float mainAxisownerSize = isMainAxisRow ? ownerWidth : ownerHeight; - - float childWidth; - float childHeight; - YGMeasureMode childWidthMeasureMode; - YGMeasureMode childHeightMeasureMode; - - const FloatOptional resolvedFlexBasis = - yoga::resolveValue(child->resolveFlexBasisPtr(), mainAxisownerSize); - const bool isRowStyleDimDefined = - YGNodeIsStyleDimDefined(child, YGFlexDirectionRow, ownerWidth); - const bool isColumnStyleDimDefined = - YGNodeIsStyleDimDefined(child, YGFlexDirectionColumn, ownerHeight); - - if (!resolvedFlexBasis.isUndefined() && !yoga::isUndefined(mainAxisSize)) { - if (child->getLayout().computedFlexBasis.isUndefined() || - (child->getConfig()->isExperimentalFeatureEnabled( - YGExperimentalFeatureWebFlexBasis) && - child->getLayout().computedFlexBasisGeneration != generationCount)) { - const FloatOptional paddingAndBorder = FloatOptional( - YGNodePaddingAndBorderForAxis(child, mainAxis, ownerWidth)); - child->setLayoutComputedFlexBasis( - yoga::maxOrDefined(resolvedFlexBasis, paddingAndBorder)); - } - } else if (isMainAxisRow && isRowStyleDimDefined) { - // The width is definite, so use that as the flex basis. - const FloatOptional paddingAndBorder = FloatOptional( - YGNodePaddingAndBorderForAxis(child, YGFlexDirectionRow, ownerWidth)); - - child->setLayoutComputedFlexBasis(yoga::maxOrDefined( - yoga::resolveValue( - child->getResolvedDimensions()[YGDimensionWidth], ownerWidth), - paddingAndBorder)); - } else if (!isMainAxisRow && isColumnStyleDimDefined) { - // The height is definite, so use that as the flex basis. - const FloatOptional paddingAndBorder = - FloatOptional(YGNodePaddingAndBorderForAxis( - child, YGFlexDirectionColumn, ownerWidth)); - child->setLayoutComputedFlexBasis(yoga::maxOrDefined( - yoga::resolveValue( - child->getResolvedDimensions()[YGDimensionHeight], ownerHeight), - paddingAndBorder)); - } else { - // Compute the flex basis and hypothetical main size (i.e. the clamped flex - // basis). - childWidth = YGUndefined; - childHeight = YGUndefined; - childWidthMeasureMode = YGMeasureModeUndefined; - childHeightMeasureMode = YGMeasureModeUndefined; - - auto marginRow = - child->getMarginForAxis(YGFlexDirectionRow, ownerWidth).unwrap(); - auto marginColumn = - child->getMarginForAxis(YGFlexDirectionColumn, ownerWidth).unwrap(); - - if (isRowStyleDimDefined) { - childWidth = - yoga::resolveValue( - child->getResolvedDimensions()[YGDimensionWidth], ownerWidth) - .unwrap() + - marginRow; - childWidthMeasureMode = YGMeasureModeExactly; - } - if (isColumnStyleDimDefined) { - childHeight = - yoga::resolveValue( - child->getResolvedDimensions()[YGDimensionHeight], ownerHeight) - .unwrap() + - marginColumn; - childHeightMeasureMode = YGMeasureModeExactly; - } - - // The W3C spec doesn't say anything about the 'overflow' property, but all - // major browsers appear to implement the following logic. - if ((!isMainAxisRow && node->getStyle().overflow() == YGOverflowScroll) || - node->getStyle().overflow() != YGOverflowScroll) { - if (yoga::isUndefined(childWidth) && !yoga::isUndefined(width)) { - childWidth = width; - childWidthMeasureMode = YGMeasureModeAtMost; - } - } - - if ((isMainAxisRow && node->getStyle().overflow() == YGOverflowScroll) || - node->getStyle().overflow() != YGOverflowScroll) { - if (yoga::isUndefined(childHeight) && !yoga::isUndefined(height)) { - childHeight = height; - childHeightMeasureMode = YGMeasureModeAtMost; - } - } - - const auto& childStyle = child->getStyle(); - if (!childStyle.aspectRatio().isUndefined()) { - if (!isMainAxisRow && childWidthMeasureMode == YGMeasureModeExactly) { - childHeight = marginColumn + - (childWidth - marginRow) / childStyle.aspectRatio().unwrap(); - childHeightMeasureMode = YGMeasureModeExactly; - } else if ( - isMainAxisRow && childHeightMeasureMode == YGMeasureModeExactly) { - childWidth = marginRow + - (childHeight - marginColumn) * childStyle.aspectRatio().unwrap(); - childWidthMeasureMode = YGMeasureModeExactly; - } - } - - // If child has no defined size in the cross axis and is set to stretch, set - // the cross axis to be measured exactly with the available inner width - - const bool hasExactWidth = - !yoga::isUndefined(width) && widthMode == YGMeasureModeExactly; - const bool childWidthStretch = - YGNodeAlignItem(node, child) == YGAlignStretch && - childWidthMeasureMode != YGMeasureModeExactly; - if (!isMainAxisRow && !isRowStyleDimDefined && hasExactWidth && - childWidthStretch) { - childWidth = width; - childWidthMeasureMode = YGMeasureModeExactly; - if (!childStyle.aspectRatio().isUndefined()) { - childHeight = - (childWidth - marginRow) / childStyle.aspectRatio().unwrap(); - childHeightMeasureMode = YGMeasureModeExactly; - } - } - - const bool hasExactHeight = - !yoga::isUndefined(height) && heightMode == YGMeasureModeExactly; - const bool childHeightStretch = - YGNodeAlignItem(node, child) == YGAlignStretch && - childHeightMeasureMode != YGMeasureModeExactly; - if (isMainAxisRow && !isColumnStyleDimDefined && hasExactHeight && - childHeightStretch) { - childHeight = height; - childHeightMeasureMode = YGMeasureModeExactly; - - if (!childStyle.aspectRatio().isUndefined()) { - childWidth = - (childHeight - marginColumn) * childStyle.aspectRatio().unwrap(); - childWidthMeasureMode = YGMeasureModeExactly; - } - } - - YGConstrainMaxSizeForMode( - child, - YGFlexDirectionRow, - ownerWidth, - ownerWidth, - &childWidthMeasureMode, - &childWidth); - YGConstrainMaxSizeForMode( - child, - YGFlexDirectionColumn, - ownerHeight, - ownerWidth, - &childHeightMeasureMode, - &childHeight); - - // Measure the child - YGLayoutNodeInternal( - child, - childWidth, - childHeight, - direction, - childWidthMeasureMode, - childHeightMeasureMode, - ownerWidth, - ownerHeight, - false, - LayoutPassReason::kMeasureChild, - config, - layoutMarkerData, - layoutContext, - depth, - generationCount); - - child->setLayoutComputedFlexBasis(FloatOptional(yoga::maxOrDefined( - child->getLayout().measuredDimensions[dim[mainAxis]], - YGNodePaddingAndBorderForAxis(child, mainAxis, ownerWidth)))); - } - child->setLayoutComputedFlexBasisGeneration(generationCount); -} - -static void YGNodeAbsoluteLayoutChild( - const yoga::Node* const node, - yoga::Node* const child, - const float width, - const YGMeasureMode widthMode, - const float height, - const YGDirection direction, - const YGConfigRef config, - LayoutData& layoutMarkerData, - void* const layoutContext, - const uint32_t depth, - const uint32_t generationCount) { - const YGFlexDirection mainAxis = - resolveDirection(node->getStyle().flexDirection(), direction); - const YGFlexDirection crossAxis = resolveCrossDirection(mainAxis, direction); - const bool isMainAxisRow = isRow(mainAxis); - - float childWidth = YGUndefined; - float childHeight = YGUndefined; - YGMeasureMode childWidthMeasureMode = YGMeasureModeUndefined; - YGMeasureMode childHeightMeasureMode = YGMeasureModeUndefined; - - auto marginRow = child->getMarginForAxis(YGFlexDirectionRow, width).unwrap(); - auto marginColumn = - child->getMarginForAxis(YGFlexDirectionColumn, width).unwrap(); - - if (YGNodeIsStyleDimDefined(child, YGFlexDirectionRow, width)) { - childWidth = yoga::resolveValue( - child->getResolvedDimensions()[YGDimensionWidth], width) - .unwrap() + - marginRow; - } else { - // If the child doesn't have a specified width, compute the width based on - // the left/right offsets if they're defined. - if (child->isLeadingPositionDefined(YGFlexDirectionRow) && - child->isTrailingPosDefined(YGFlexDirectionRow)) { - childWidth = node->getLayout().measuredDimensions[YGDimensionWidth] - - (node->getLeadingBorder(YGFlexDirectionRow) + - node->getTrailingBorder(YGFlexDirectionRow)) - - (child->getLeadingPosition(YGFlexDirectionRow, width) + - child->getTrailingPosition(YGFlexDirectionRow, width)) - .unwrap(); - childWidth = - YGNodeBoundAxis(child, YGFlexDirectionRow, childWidth, width, width); - } - } - - if (YGNodeIsStyleDimDefined(child, YGFlexDirectionColumn, height)) { - childHeight = yoga::resolveValue( - child->getResolvedDimensions()[YGDimensionHeight], height) - .unwrap() + - marginColumn; - } else { - // If the child doesn't have a specified height, compute the height based on - // the top/bottom offsets if they're defined. - if (child->isLeadingPositionDefined(YGFlexDirectionColumn) && - child->isTrailingPosDefined(YGFlexDirectionColumn)) { - childHeight = node->getLayout().measuredDimensions[YGDimensionHeight] - - (node->getLeadingBorder(YGFlexDirectionColumn) + - node->getTrailingBorder(YGFlexDirectionColumn)) - - (child->getLeadingPosition(YGFlexDirectionColumn, height) + - child->getTrailingPosition(YGFlexDirectionColumn, height)) - .unwrap(); - childHeight = YGNodeBoundAxis( - child, YGFlexDirectionColumn, childHeight, height, width); - } - } - - // Exactly one dimension needs to be defined for us to be able to do aspect - // ratio calculation. One dimension being the anchor and the other being - // flexible. - const auto& childStyle = child->getStyle(); - if (yoga::isUndefined(childWidth) ^ yoga::isUndefined(childHeight)) { - if (!childStyle.aspectRatio().isUndefined()) { - if (yoga::isUndefined(childWidth)) { - childWidth = marginRow + - (childHeight - marginColumn) * childStyle.aspectRatio().unwrap(); - } else if (yoga::isUndefined(childHeight)) { - childHeight = marginColumn + - (childWidth - marginRow) / childStyle.aspectRatio().unwrap(); - } - } - } - - // If we're still missing one or the other dimension, measure the content. - if (yoga::isUndefined(childWidth) || yoga::isUndefined(childHeight)) { - childWidthMeasureMode = yoga::isUndefined(childWidth) - ? YGMeasureModeUndefined - : YGMeasureModeExactly; - childHeightMeasureMode = yoga::isUndefined(childHeight) - ? YGMeasureModeUndefined - : YGMeasureModeExactly; - - // If the size of the owner is defined then try to constrain the absolute - // child to that size as well. This allows text within the absolute child to - // wrap to the size of its owner. This is the same behavior as many browsers - // implement. - if (!isMainAxisRow && yoga::isUndefined(childWidth) && - widthMode != YGMeasureModeUndefined && !yoga::isUndefined(width) && - width > 0) { - childWidth = width; - childWidthMeasureMode = YGMeasureModeAtMost; - } - - YGLayoutNodeInternal( - child, - childWidth, - childHeight, - direction, - childWidthMeasureMode, - childHeightMeasureMode, - childWidth, - childHeight, - false, - LayoutPassReason::kAbsMeasureChild, - config, - layoutMarkerData, - layoutContext, - depth, - generationCount); - childWidth = child->getLayout().measuredDimensions[YGDimensionWidth] + - child->getMarginForAxis(YGFlexDirectionRow, width).unwrap(); - childHeight = child->getLayout().measuredDimensions[YGDimensionHeight] + - child->getMarginForAxis(YGFlexDirectionColumn, width).unwrap(); - } - - YGLayoutNodeInternal( - child, - childWidth, - childHeight, - direction, - YGMeasureModeExactly, - YGMeasureModeExactly, - childWidth, - childHeight, - true, - LayoutPassReason::kAbsLayout, - config, - layoutMarkerData, - layoutContext, - depth, - generationCount); - - if (child->isTrailingPosDefined(mainAxis) && - !child->isLeadingPositionDefined(mainAxis)) { - child->setLayoutPosition( - node->getLayout().measuredDimensions[dim[mainAxis]] - - child->getLayout().measuredDimensions[dim[mainAxis]] - - node->getTrailingBorder(mainAxis) - - child->getTrailingMargin(mainAxis, isMainAxisRow ? width : height) - .unwrap() - - child->getTrailingPosition(mainAxis, isMainAxisRow ? width : height) - .unwrap(), - leadingEdge(mainAxis)); - } else if ( - !child->isLeadingPositionDefined(mainAxis) && - node->getStyle().justifyContent() == YGJustifyCenter) { - child->setLayoutPosition( - (node->getLayout().measuredDimensions[dim[mainAxis]] - - child->getLayout().measuredDimensions[dim[mainAxis]]) / - 2.0f, - leadingEdge(mainAxis)); - } else if ( - !child->isLeadingPositionDefined(mainAxis) && - node->getStyle().justifyContent() == YGJustifyFlexEnd) { - child->setLayoutPosition( - (node->getLayout().measuredDimensions[dim[mainAxis]] - - child->getLayout().measuredDimensions[dim[mainAxis]]), - leadingEdge(mainAxis)); - } else if ( - node->getConfig()->isExperimentalFeatureEnabled( - YGExperimentalFeatureAbsolutePercentageAgainstPaddingEdge) && - child->isLeadingPositionDefined(mainAxis)) { - child->setLayoutPosition( - child->getLeadingPosition( - mainAxis, node->getLayout().measuredDimensions[dim[mainAxis]]) - .unwrap() + - node->getLeadingBorder(mainAxis) + - child - ->getLeadingMargin( - mainAxis, - node->getLayout().measuredDimensions[dim[mainAxis]]) - .unwrap(), - leadingEdge(mainAxis)); - } - - if (child->isTrailingPosDefined(crossAxis) && - !child->isLeadingPositionDefined(crossAxis)) { - child->setLayoutPosition( - node->getLayout().measuredDimensions[dim[crossAxis]] - - child->getLayout().measuredDimensions[dim[crossAxis]] - - node->getTrailingBorder(crossAxis) - - child->getTrailingMargin(crossAxis, isMainAxisRow ? height : width) - .unwrap() - - child - ->getTrailingPosition(crossAxis, isMainAxisRow ? height : width) - .unwrap(), - leadingEdge(crossAxis)); - - } else if ( - !child->isLeadingPositionDefined(crossAxis) && - YGNodeAlignItem(node, child) == YGAlignCenter) { - child->setLayoutPosition( - (node->getLayout().measuredDimensions[dim[crossAxis]] - - child->getLayout().measuredDimensions[dim[crossAxis]]) / - 2.0f, - leadingEdge(crossAxis)); - } else if ( - !child->isLeadingPositionDefined(crossAxis) && - ((YGNodeAlignItem(node, child) == YGAlignFlexEnd) ^ - (node->getStyle().flexWrap() == YGWrapWrapReverse))) { - child->setLayoutPosition( - (node->getLayout().measuredDimensions[dim[crossAxis]] - - child->getLayout().measuredDimensions[dim[crossAxis]]), - leadingEdge(crossAxis)); - } else if ( - node->getConfig()->isExperimentalFeatureEnabled( - YGExperimentalFeatureAbsolutePercentageAgainstPaddingEdge) && - child->isLeadingPositionDefined(crossAxis)) { - child->setLayoutPosition( - child->getLeadingPosition( - crossAxis, - node->getLayout().measuredDimensions[dim[crossAxis]]) - .unwrap() + - node->getLeadingBorder(crossAxis) + - child - ->getLeadingMargin( - crossAxis, - node->getLayout().measuredDimensions[dim[crossAxis]]) - .unwrap(), - leadingEdge(crossAxis)); - } -} - -static void YGNodeWithMeasureFuncSetMeasuredDimensions( - yoga::Node* const node, - float availableWidth, - float availableHeight, - const YGMeasureMode widthMeasureMode, - const YGMeasureMode heightMeasureMode, - const float ownerWidth, - const float ownerHeight, - LayoutData& layoutMarkerData, - void* const layoutContext, - const LayoutPassReason reason) { - yoga::assertFatalWithNode( - node, - node->hasMeasureFunc(), - "Expected node to have custom measure function"); - - if (widthMeasureMode == YGMeasureModeUndefined) { - availableWidth = YGUndefined; - } - if (heightMeasureMode == YGMeasureModeUndefined) { - availableHeight = YGUndefined; - } - - const auto& padding = node->getLayout().padding; - const auto& border = node->getLayout().border; - const float paddingAndBorderAxisRow = padding[YGEdgeLeft] + - padding[YGEdgeRight] + border[YGEdgeLeft] + border[YGEdgeRight]; - const float paddingAndBorderAxisColumn = padding[YGEdgeTop] + - padding[YGEdgeBottom] + border[YGEdgeTop] + border[YGEdgeBottom]; - - // We want to make sure we don't call measure with negative size - const float innerWidth = yoga::isUndefined(availableWidth) - ? availableWidth - : yoga::maxOrDefined(0, availableWidth - paddingAndBorderAxisRow); - const float innerHeight = yoga::isUndefined(availableHeight) - ? availableHeight - : yoga::maxOrDefined(0, availableHeight - paddingAndBorderAxisColumn); - - if (widthMeasureMode == YGMeasureModeExactly && - heightMeasureMode == YGMeasureModeExactly) { - // Don't bother sizing the text if both dimensions are already defined. - node->setLayoutMeasuredDimension( - YGNodeBoundAxis( - node, YGFlexDirectionRow, availableWidth, ownerWidth, ownerWidth), - YGDimensionWidth); - node->setLayoutMeasuredDimension( - YGNodeBoundAxis( - node, - YGFlexDirectionColumn, - availableHeight, - ownerHeight, - ownerWidth), - YGDimensionHeight); - } else { - Event::publish(node); - - // Measure the text under the current constraints. - const YGSize measuredSize = node->measure( - innerWidth, - widthMeasureMode, - innerHeight, - heightMeasureMode, - layoutContext); - - layoutMarkerData.measureCallbacks += 1; - layoutMarkerData.measureCallbackReasonsCount[static_cast(reason)] += - 1; - - Event::publish( - node, - {layoutContext, - innerWidth, - widthMeasureMode, - innerHeight, - heightMeasureMode, - measuredSize.width, - measuredSize.height, - reason}); - - node->setLayoutMeasuredDimension( - YGNodeBoundAxis( - node, - YGFlexDirectionRow, - (widthMeasureMode == YGMeasureModeUndefined || - widthMeasureMode == YGMeasureModeAtMost) - ? measuredSize.width + paddingAndBorderAxisRow - : availableWidth, - ownerWidth, - ownerWidth), - YGDimensionWidth); - - node->setLayoutMeasuredDimension( - YGNodeBoundAxis( - node, - YGFlexDirectionColumn, - (heightMeasureMode == YGMeasureModeUndefined || - heightMeasureMode == YGMeasureModeAtMost) - ? measuredSize.height + paddingAndBorderAxisColumn - : availableHeight, - ownerHeight, - ownerWidth), - YGDimensionHeight); - } -} - -// For nodes with no children, use the available values if they were provided, -// or the minimum size as indicated by the padding and border sizes. -static void YGNodeEmptyContainerSetMeasuredDimensions( - yoga::Node* const node, - const float availableWidth, - const float availableHeight, - const YGMeasureMode widthMeasureMode, - const YGMeasureMode heightMeasureMode, - const float ownerWidth, - const float ownerHeight) { - const auto& padding = node->getLayout().padding; - const auto& border = node->getLayout().border; - - float width = availableWidth; - if (widthMeasureMode == YGMeasureModeUndefined || - widthMeasureMode == YGMeasureModeAtMost) { - width = padding[YGEdgeLeft] + padding[YGEdgeRight] + border[YGEdgeLeft] + - border[YGEdgeRight]; - } - node->setLayoutMeasuredDimension( - YGNodeBoundAxis(node, YGFlexDirectionRow, width, ownerWidth, ownerWidth), - YGDimensionWidth); - - float height = availableHeight; - if (heightMeasureMode == YGMeasureModeUndefined || - heightMeasureMode == YGMeasureModeAtMost) { - height = padding[YGEdgeTop] + padding[YGEdgeBottom] + border[YGEdgeTop] + - border[YGEdgeBottom]; - } - node->setLayoutMeasuredDimension( - YGNodeBoundAxis( - node, YGFlexDirectionColumn, height, ownerHeight, ownerWidth), - YGDimensionHeight); -} - -static bool YGNodeFixedSizeSetMeasuredDimensions( - yoga::Node* const node, - const float availableWidth, - const float availableHeight, - const YGMeasureMode widthMeasureMode, - const YGMeasureMode heightMeasureMode, - const float ownerWidth, - const float ownerHeight) { - if ((!yoga::isUndefined(availableWidth) && - widthMeasureMode == YGMeasureModeAtMost && availableWidth <= 0.0f) || - (!yoga::isUndefined(availableHeight) && - heightMeasureMode == YGMeasureModeAtMost && availableHeight <= 0.0f) || - (widthMeasureMode == YGMeasureModeExactly && - heightMeasureMode == YGMeasureModeExactly)) { - node->setLayoutMeasuredDimension( - YGNodeBoundAxis( - node, - YGFlexDirectionRow, - yoga::isUndefined(availableWidth) || - (widthMeasureMode == YGMeasureModeAtMost && - availableWidth < 0.0f) - ? 0.0f - : availableWidth, - ownerWidth, - ownerWidth), - YGDimensionWidth); - - node->setLayoutMeasuredDimension( - YGNodeBoundAxis( - node, - YGFlexDirectionColumn, - yoga::isUndefined(availableHeight) || - (heightMeasureMode == YGMeasureModeAtMost && - availableHeight < 0.0f) - ? 0.0f - : availableHeight, - ownerHeight, - ownerWidth), - YGDimensionHeight); - return true; - } - - return false; -} - -static void YGZeroOutLayoutRecursively( - yoga::Node* const node, - void* layoutContext) { - node->getLayout() = {}; - node->setLayoutDimension(0, 0); - node->setLayoutDimension(0, 1); - node->setHasNewLayout(true); - - node->iterChildrenAfterCloningIfNeeded( - YGZeroOutLayoutRecursively, layoutContext); -} - -static float YGNodeCalculateAvailableInnerDim( - const yoga::Node* const node, - const YGDimension dimension, - const float availableDim, - const float paddingAndBorder, - const float ownerDim) { - float availableInnerDim = availableDim - paddingAndBorder; - // Max dimension overrides predefined dimension value; Min dimension in turn - // overrides both of the above - if (!yoga::isUndefined(availableInnerDim)) { - // We want to make sure our available height does not violate min and max - // constraints - const FloatOptional minDimensionOptional = yoga::resolveValue( - node->getStyle().minDimensions()[dimension], ownerDim); - const float minInnerDim = minDimensionOptional.isUndefined() - ? 0.0f - : minDimensionOptional.unwrap() - paddingAndBorder; - - const FloatOptional maxDimensionOptional = yoga::resolveValue( - node->getStyle().maxDimensions()[dimension], ownerDim); - - const float maxInnerDim = maxDimensionOptional.isUndefined() - ? FLT_MAX - : maxDimensionOptional.unwrap() - paddingAndBorder; - availableInnerDim = yoga::maxOrDefined( - yoga::minOrDefined(availableInnerDim, maxInnerDim), minInnerDim); - } - - return availableInnerDim; -} - -static float YGNodeComputeFlexBasisForChildren( - yoga::Node* const node, - const float availableInnerWidth, - const float availableInnerHeight, - YGMeasureMode widthMeasureMode, - YGMeasureMode heightMeasureMode, - YGDirection direction, - YGFlexDirection mainAxis, - const YGConfigRef config, - bool performLayout, - LayoutData& layoutMarkerData, - void* const layoutContext, - const uint32_t depth, - const uint32_t generationCount) { - float totalOuterFlexBasis = 0.0f; - YGNodeRef singleFlexChild = nullptr; - const auto& children = node->getChildren(); - YGMeasureMode measureModeMainDim = - isRow(mainAxis) ? widthMeasureMode : heightMeasureMode; - // If there is only one child with flexGrow + flexShrink it means we can set - // the computedFlexBasis to 0 instead of measuring and shrinking / flexing the - // child to exactly match the remaining space - if (measureModeMainDim == YGMeasureModeExactly) { - for (auto child : children) { - if (child->isNodeFlexible()) { - if (singleFlexChild != nullptr || - yoga::inexactEquals(child->resolveFlexGrow(), 0.0f) || - yoga::inexactEquals(child->resolveFlexShrink(), 0.0f)) { - // There is already a flexible child, or this flexible child doesn't - // have flexGrow and flexShrink, abort - singleFlexChild = nullptr; - break; - } else { - singleFlexChild = child; - } - } - } - } - - for (auto child : children) { - child->resolveDimension(); - if (child->getStyle().display() == YGDisplayNone) { - YGZeroOutLayoutRecursively(child, layoutContext); - child->setHasNewLayout(true); - child->setDirty(false); - continue; - } - if (performLayout) { - // Set the initial position (relative to the owner). - const YGDirection childDirection = child->resolveDirection(direction); - const float mainDim = - isRow(mainAxis) ? availableInnerWidth : availableInnerHeight; - const float crossDim = - isRow(mainAxis) ? availableInnerHeight : availableInnerWidth; - child->setPosition( - childDirection, mainDim, crossDim, availableInnerWidth); - } - - if (child->getStyle().positionType() == YGPositionTypeAbsolute) { - continue; - } - if (child == singleFlexChild) { - child->setLayoutComputedFlexBasisGeneration(generationCount); - child->setLayoutComputedFlexBasis(FloatOptional(0)); - } else { - YGNodeComputeFlexBasisForChild( - node, - child, - availableInnerWidth, - widthMeasureMode, - availableInnerHeight, - availableInnerWidth, - availableInnerHeight, - heightMeasureMode, - direction, - config, - layoutMarkerData, - layoutContext, - depth, - generationCount); - } - - totalOuterFlexBasis += - (child->getLayout().computedFlexBasis + - child->getMarginForAxis(mainAxis, availableInnerWidth)) - .unwrap(); - } - - return totalOuterFlexBasis; -} - -// This function assumes that all the children of node have their -// computedFlexBasis properly computed(To do this use -// YGNodeComputeFlexBasisForChildren function). This function calculates -// YGCollectFlexItemsRowMeasurement -static CollectFlexItemsRowValues YGCalculateCollectFlexItemsRowValues( - yoga::Node* const node, - const YGDirection ownerDirection, - const float mainAxisownerSize, - const float availableInnerWidth, - const float availableInnerMainDim, - const uint32_t startOfLineIndex, - const uint32_t lineCount) { - CollectFlexItemsRowValues flexAlgoRowMeasurement = {}; - flexAlgoRowMeasurement.relativeChildren.reserve(node->getChildren().size()); - - float sizeConsumedOnCurrentLineIncludingMinConstraint = 0; - const YGFlexDirection mainAxis = resolveDirection( - node->getStyle().flexDirection(), node->resolveDirection(ownerDirection)); - const bool isNodeFlexWrap = node->getStyle().flexWrap() != YGWrapNoWrap; - const float gap = node->getGapForAxis(mainAxis, availableInnerWidth).unwrap(); - - // Add items to the current line until it's full or we run out of items. - uint32_t endOfLineIndex = startOfLineIndex; - for (; endOfLineIndex < node->getChildren().size(); endOfLineIndex++) { - auto child = node->getChild(endOfLineIndex); - if (child->getStyle().display() == YGDisplayNone || - child->getStyle().positionType() == YGPositionTypeAbsolute) { - continue; - } - - const bool isFirstElementInLine = (endOfLineIndex - startOfLineIndex) == 0; - - child->setLineIndex(lineCount); - const float childMarginMainAxis = - child->getMarginForAxis(mainAxis, availableInnerWidth).unwrap(); - const float childLeadingGapMainAxis = isFirstElementInLine ? 0.0f : gap; - const float flexBasisWithMinAndMaxConstraints = - YGNodeBoundAxisWithinMinAndMax( - child, - mainAxis, - child->getLayout().computedFlexBasis, - mainAxisownerSize) - .unwrap(); - - // If this is a multi-line flow and this item pushes us over the available - // size, we've hit the end of the current line. Break out of the loop and - // lay out the current line. - if (sizeConsumedOnCurrentLineIncludingMinConstraint + - flexBasisWithMinAndMaxConstraints + childMarginMainAxis + - childLeadingGapMainAxis > - availableInnerMainDim && - isNodeFlexWrap && flexAlgoRowMeasurement.itemsOnLine > 0) { - break; - } - - sizeConsumedOnCurrentLineIncludingMinConstraint += - flexBasisWithMinAndMaxConstraints + childMarginMainAxis + - childLeadingGapMainAxis; - flexAlgoRowMeasurement.sizeConsumedOnCurrentLine += - flexBasisWithMinAndMaxConstraints + childMarginMainAxis + - childLeadingGapMainAxis; - flexAlgoRowMeasurement.itemsOnLine++; - - if (child->isNodeFlexible()) { - flexAlgoRowMeasurement.totalFlexGrowFactors += child->resolveFlexGrow(); - - // Unlike the grow factor, the shrink factor is scaled relative to the - // child dimension. - flexAlgoRowMeasurement.totalFlexShrinkScaledFactors += - -child->resolveFlexShrink() * - child->getLayout().computedFlexBasis.unwrap(); - } - - flexAlgoRowMeasurement.relativeChildren.push_back(child); - } - - // The total flex factor needs to be floored to 1. - if (flexAlgoRowMeasurement.totalFlexGrowFactors > 0 && - flexAlgoRowMeasurement.totalFlexGrowFactors < 1) { - flexAlgoRowMeasurement.totalFlexGrowFactors = 1; - } - - // The total flex shrink factor needs to be floored to 1. - if (flexAlgoRowMeasurement.totalFlexShrinkScaledFactors > 0 && - flexAlgoRowMeasurement.totalFlexShrinkScaledFactors < 1) { - flexAlgoRowMeasurement.totalFlexShrinkScaledFactors = 1; - } - flexAlgoRowMeasurement.endOfLineIndex = endOfLineIndex; - return flexAlgoRowMeasurement; -} - -// It distributes the free space to the flexible items and ensures that the size -// of the flex items abide the min and max constraints. At the end of this -// function the child nodes would have proper size. Prior using this function -// please ensure that YGDistributeFreeSpaceFirstPass is called. -static float YGDistributeFreeSpaceSecondPass( - CollectFlexItemsRowValues& collectedFlexItemsValues, - yoga::Node* const node, - const YGFlexDirection mainAxis, - const YGFlexDirection crossAxis, - const float mainAxisownerSize, - const float availableInnerMainDim, - const float availableInnerCrossDim, - const float availableInnerWidth, - const float availableInnerHeight, - const bool mainAxisOverflows, - const YGMeasureMode measureModeCrossDim, - const bool performLayout, - const YGConfigRef config, - LayoutData& layoutMarkerData, - void* const layoutContext, - const uint32_t depth, - const uint32_t generationCount) { - float childFlexBasis = 0; - float flexShrinkScaledFactor = 0; - float flexGrowFactor = 0; - float deltaFreeSpace = 0; - const bool isMainAxisRow = isRow(mainAxis); - const bool isNodeFlexWrap = node->getStyle().flexWrap() != YGWrapNoWrap; - - for (auto currentRelativeChild : collectedFlexItemsValues.relativeChildren) { - childFlexBasis = YGNodeBoundAxisWithinMinAndMax( - currentRelativeChild, - mainAxis, - currentRelativeChild->getLayout().computedFlexBasis, - mainAxisownerSize) - .unwrap(); - float updatedMainSize = childFlexBasis; - - if (!yoga::isUndefined(collectedFlexItemsValues.remainingFreeSpace) && - collectedFlexItemsValues.remainingFreeSpace < 0) { - flexShrinkScaledFactor = - -currentRelativeChild->resolveFlexShrink() * childFlexBasis; - // Is this child able to shrink? - if (flexShrinkScaledFactor != 0) { - float childSize; - - if (!yoga::isUndefined( - collectedFlexItemsValues.totalFlexShrinkScaledFactors) && - collectedFlexItemsValues.totalFlexShrinkScaledFactors == 0) { - childSize = childFlexBasis + flexShrinkScaledFactor; - } else { - childSize = childFlexBasis + - (collectedFlexItemsValues.remainingFreeSpace / - collectedFlexItemsValues.totalFlexShrinkScaledFactors) * - flexShrinkScaledFactor; - } - - updatedMainSize = YGNodeBoundAxis( - currentRelativeChild, - mainAxis, - childSize, - availableInnerMainDim, - availableInnerWidth); - } - } else if ( - !yoga::isUndefined(collectedFlexItemsValues.remainingFreeSpace) && - collectedFlexItemsValues.remainingFreeSpace > 0) { - flexGrowFactor = currentRelativeChild->resolveFlexGrow(); - - // Is this child able to grow? - if (!yoga::isUndefined(flexGrowFactor) && flexGrowFactor != 0) { - updatedMainSize = YGNodeBoundAxis( - currentRelativeChild, - mainAxis, - childFlexBasis + - collectedFlexItemsValues.remainingFreeSpace / - collectedFlexItemsValues.totalFlexGrowFactors * - flexGrowFactor, - availableInnerMainDim, - availableInnerWidth); - } - } - - deltaFreeSpace += updatedMainSize - childFlexBasis; - - const float marginMain = - currentRelativeChild->getMarginForAxis(mainAxis, availableInnerWidth) - .unwrap(); - const float marginCross = - currentRelativeChild->getMarginForAxis(crossAxis, availableInnerWidth) - .unwrap(); - - float childCrossSize; - float childMainSize = updatedMainSize + marginMain; - YGMeasureMode childCrossMeasureMode; - YGMeasureMode childMainMeasureMode = YGMeasureModeExactly; - - const auto& childStyle = currentRelativeChild->getStyle(); - if (!childStyle.aspectRatio().isUndefined()) { - childCrossSize = isMainAxisRow - ? (childMainSize - marginMain) / childStyle.aspectRatio().unwrap() - : (childMainSize - marginMain) * childStyle.aspectRatio().unwrap(); - childCrossMeasureMode = YGMeasureModeExactly; - - childCrossSize += marginCross; - } else if ( - !yoga::isUndefined(availableInnerCrossDim) && - !YGNodeIsStyleDimDefined( - currentRelativeChild, crossAxis, availableInnerCrossDim) && - measureModeCrossDim == YGMeasureModeExactly && - !(isNodeFlexWrap && mainAxisOverflows) && - YGNodeAlignItem(node, currentRelativeChild) == YGAlignStretch && - currentRelativeChild->marginLeadingValue(crossAxis).unit != - YGUnitAuto && - currentRelativeChild->marginTrailingValue(crossAxis).unit != - YGUnitAuto) { - childCrossSize = availableInnerCrossDim; - childCrossMeasureMode = YGMeasureModeExactly; - } else if (!YGNodeIsStyleDimDefined( - currentRelativeChild, crossAxis, availableInnerCrossDim)) { - childCrossSize = availableInnerCrossDim; - childCrossMeasureMode = yoga::isUndefined(childCrossSize) - ? YGMeasureModeUndefined - : YGMeasureModeAtMost; - } else { - childCrossSize = - yoga::resolveValue( - currentRelativeChild->getResolvedDimension(dim[crossAxis]), - availableInnerCrossDim) - .unwrap() + - marginCross; - const bool isLoosePercentageMeasurement = - currentRelativeChild->getResolvedDimension(dim[crossAxis]).unit == - YGUnitPercent && - measureModeCrossDim != YGMeasureModeExactly; - childCrossMeasureMode = - yoga::isUndefined(childCrossSize) || isLoosePercentageMeasurement - ? YGMeasureModeUndefined - : YGMeasureModeExactly; - } - - YGConstrainMaxSizeForMode( - currentRelativeChild, - mainAxis, - availableInnerMainDim, - availableInnerWidth, - &childMainMeasureMode, - &childMainSize); - YGConstrainMaxSizeForMode( - currentRelativeChild, - crossAxis, - availableInnerCrossDim, - availableInnerWidth, - &childCrossMeasureMode, - &childCrossSize); - - const bool requiresStretchLayout = - !YGNodeIsStyleDimDefined( - currentRelativeChild, crossAxis, availableInnerCrossDim) && - YGNodeAlignItem(node, currentRelativeChild) == YGAlignStretch && - currentRelativeChild->marginLeadingValue(crossAxis).unit != - YGUnitAuto && - currentRelativeChild->marginTrailingValue(crossAxis).unit != YGUnitAuto; - - const float childWidth = isMainAxisRow ? childMainSize : childCrossSize; - const float childHeight = !isMainAxisRow ? childMainSize : childCrossSize; - - const YGMeasureMode childWidthMeasureMode = - isMainAxisRow ? childMainMeasureMode : childCrossMeasureMode; - const YGMeasureMode childHeightMeasureMode = - !isMainAxisRow ? childMainMeasureMode : childCrossMeasureMode; - - const bool isLayoutPass = performLayout && !requiresStretchLayout; - // Recursively call the layout algorithm for this child with the updated - // main size. - YGLayoutNodeInternal( - currentRelativeChild, - childWidth, - childHeight, - node->getLayout().direction(), - childWidthMeasureMode, - childHeightMeasureMode, - availableInnerWidth, - availableInnerHeight, - isLayoutPass, - isLayoutPass ? LayoutPassReason::kFlexLayout - : LayoutPassReason::kFlexMeasure, - config, - layoutMarkerData, - layoutContext, - depth, - generationCount); - node->setLayoutHadOverflow( - node->getLayout().hadOverflow() || - currentRelativeChild->getLayout().hadOverflow()); - } - return deltaFreeSpace; -} - -// It distributes the free space to the flexible items.For those flexible items -// whose min and max constraints are triggered, those flex item's clamped size -// is removed from the remaingfreespace. -static void YGDistributeFreeSpaceFirstPass( - CollectFlexItemsRowValues& collectedFlexItemsValues, - const YGFlexDirection mainAxis, - const float mainAxisownerSize, - const float availableInnerMainDim, - const float availableInnerWidth) { - float flexShrinkScaledFactor = 0; - float flexGrowFactor = 0; - float baseMainSize = 0; - float boundMainSize = 0; - float deltaFreeSpace = 0; - - for (auto currentRelativeChild : collectedFlexItemsValues.relativeChildren) { - float childFlexBasis = - YGNodeBoundAxisWithinMinAndMax( - currentRelativeChild, - mainAxis, - currentRelativeChild->getLayout().computedFlexBasis, - mainAxisownerSize) - .unwrap(); - - if (collectedFlexItemsValues.remainingFreeSpace < 0) { - flexShrinkScaledFactor = - -currentRelativeChild->resolveFlexShrink() * childFlexBasis; - - // Is this child able to shrink? - if (!yoga::isUndefined(flexShrinkScaledFactor) && - flexShrinkScaledFactor != 0) { - baseMainSize = childFlexBasis + - collectedFlexItemsValues.remainingFreeSpace / - collectedFlexItemsValues.totalFlexShrinkScaledFactors * - flexShrinkScaledFactor; - boundMainSize = YGNodeBoundAxis( - currentRelativeChild, - mainAxis, - baseMainSize, - availableInnerMainDim, - availableInnerWidth); - if (!yoga::isUndefined(baseMainSize) && - !yoga::isUndefined(boundMainSize) && - baseMainSize != boundMainSize) { - // By excluding this item's size and flex factor from remaining, this - // item's min/max constraints should also trigger in the second pass - // resulting in the item's size calculation being identical in the - // first and second passes. - deltaFreeSpace += boundMainSize - childFlexBasis; - collectedFlexItemsValues.totalFlexShrinkScaledFactors -= - (-currentRelativeChild->resolveFlexShrink() * - currentRelativeChild->getLayout().computedFlexBasis.unwrap()); - } - } - } else if ( - !yoga::isUndefined(collectedFlexItemsValues.remainingFreeSpace) && - collectedFlexItemsValues.remainingFreeSpace > 0) { - flexGrowFactor = currentRelativeChild->resolveFlexGrow(); - - // Is this child able to grow? - if (!yoga::isUndefined(flexGrowFactor) && flexGrowFactor != 0) { - baseMainSize = childFlexBasis + - collectedFlexItemsValues.remainingFreeSpace / - collectedFlexItemsValues.totalFlexGrowFactors * flexGrowFactor; - boundMainSize = YGNodeBoundAxis( - currentRelativeChild, - mainAxis, - baseMainSize, - availableInnerMainDim, - availableInnerWidth); - - if (!yoga::isUndefined(baseMainSize) && - !yoga::isUndefined(boundMainSize) && - baseMainSize != boundMainSize) { - // By excluding this item's size and flex factor from remaining, this - // item's min/max constraints should also trigger in the second pass - // resulting in the item's size calculation being identical in the - // first and second passes. - deltaFreeSpace += boundMainSize - childFlexBasis; - collectedFlexItemsValues.totalFlexGrowFactors -= flexGrowFactor; - } - } - } - } - collectedFlexItemsValues.remainingFreeSpace -= deltaFreeSpace; -} - -// Do two passes over the flex items to figure out how to distribute the -// remaining space. -// -// The first pass finds the items whose min/max constraints trigger, freezes -// them at those sizes, and excludes those sizes from the remaining space. -// -// The second pass sets the size of each flexible item. It distributes the -// remaining space amongst the items whose min/max constraints didn't trigger in -// the first pass. For the other items, it sets their sizes by forcing their -// min/max constraints to trigger again. -// -// This two pass approach for resolving min/max constraints deviates from the -// spec. The spec -// (https://www.w3.org/TR/CSS-flexbox-1/#resolve-flexible-lengths) describes a -// process that needs to be repeated a variable number of times. The algorithm -// implemented here won't handle all cases but it was simpler to implement and -// it mitigates performance concerns because we know exactly how many passes -// it'll do. -// -// At the end of this function the child nodes would have the proper size -// assigned to them. -// -static void YGResolveFlexibleLength( - yoga::Node* const node, - CollectFlexItemsRowValues& collectedFlexItemsValues, - const YGFlexDirection mainAxis, - const YGFlexDirection crossAxis, - const float mainAxisownerSize, - const float availableInnerMainDim, - const float availableInnerCrossDim, - const float availableInnerWidth, - const float availableInnerHeight, - const bool mainAxisOverflows, - const YGMeasureMode measureModeCrossDim, - const bool performLayout, - const YGConfigRef config, - LayoutData& layoutMarkerData, - void* const layoutContext, - const uint32_t depth, - const uint32_t generationCount) { - const float originalFreeSpace = collectedFlexItemsValues.remainingFreeSpace; - // First pass: detect the flex items whose min/max constraints trigger - YGDistributeFreeSpaceFirstPass( - collectedFlexItemsValues, - mainAxis, - mainAxisownerSize, - availableInnerMainDim, - availableInnerWidth); - - // Second pass: resolve the sizes of the flexible items - const float distributedFreeSpace = YGDistributeFreeSpaceSecondPass( - collectedFlexItemsValues, - node, - mainAxis, - crossAxis, - mainAxisownerSize, - availableInnerMainDim, - availableInnerCrossDim, - availableInnerWidth, - availableInnerHeight, - mainAxisOverflows, - measureModeCrossDim, - performLayout, - config, - layoutMarkerData, - layoutContext, - depth, - generationCount); - - collectedFlexItemsValues.remainingFreeSpace = - originalFreeSpace - distributedFreeSpace; -} - -static void YGJustifyMainAxis( - yoga::Node* const node, - CollectFlexItemsRowValues& collectedFlexItemsValues, - const uint32_t startOfLineIndex, - const YGFlexDirection mainAxis, - const YGFlexDirection crossAxis, - const YGMeasureMode measureModeMainDim, - const YGMeasureMode measureModeCrossDim, - const float mainAxisownerSize, - const float ownerWidth, - const float availableInnerMainDim, - const float availableInnerCrossDim, - const float availableInnerWidth, - const bool performLayout, - void* const layoutContext) { - const auto& style = node->getStyle(); - const float leadingPaddingAndBorderMain = - node->getLeadingPaddingAndBorder(mainAxis, ownerWidth).unwrap(); - const float trailingPaddingAndBorderMain = - node->getTrailingPaddingAndBorder(mainAxis, ownerWidth).unwrap(); - const float gap = node->getGapForAxis(mainAxis, ownerWidth).unwrap(); - // If we are using "at most" rules in the main axis, make sure that - // remainingFreeSpace is 0 when min main dimension is not given - if (measureModeMainDim == YGMeasureModeAtMost && - collectedFlexItemsValues.remainingFreeSpace > 0) { - if (!style.minDimensions()[dim[mainAxis]].isUndefined() && - !yoga::resolveValue( - style.minDimensions()[dim[mainAxis]], mainAxisownerSize) - .isUndefined()) { - // This condition makes sure that if the size of main dimension(after - // considering child nodes main dim, leading and trailing padding etc) - // falls below min dimension, then the remainingFreeSpace is reassigned - // considering the min dimension - - // `minAvailableMainDim` denotes minimum available space in which child - // can be laid out, it will exclude space consumed by padding and border. - const float minAvailableMainDim = - yoga::resolveValue( - style.minDimensions()[dim[mainAxis]], mainAxisownerSize) - .unwrap() - - leadingPaddingAndBorderMain - trailingPaddingAndBorderMain; - const float occupiedSpaceByChildNodes = - availableInnerMainDim - collectedFlexItemsValues.remainingFreeSpace; - collectedFlexItemsValues.remainingFreeSpace = yoga::maxOrDefined( - 0, minAvailableMainDim - occupiedSpaceByChildNodes); - } else { - collectedFlexItemsValues.remainingFreeSpace = 0; - } - } - - int numberOfAutoMarginsOnCurrentLine = 0; - for (uint32_t i = startOfLineIndex; - i < collectedFlexItemsValues.endOfLineIndex; - i++) { - auto child = node->getChild(i); - if (child->getStyle().positionType() != YGPositionTypeAbsolute) { - if (child->marginLeadingValue(mainAxis).unit == YGUnitAuto) { - numberOfAutoMarginsOnCurrentLine++; - } - if (child->marginTrailingValue(mainAxis).unit == YGUnitAuto) { - numberOfAutoMarginsOnCurrentLine++; - } - } - } - - // In order to position the elements in the main axis, we have two controls. - // The space between the beginning and the first element and the space between - // each two elements. - float leadingMainDim = 0; - float betweenMainDim = gap; - const YGJustify justifyContent = node->getStyle().justifyContent(); - - if (numberOfAutoMarginsOnCurrentLine == 0) { - switch (justifyContent) { - case YGJustifyCenter: - leadingMainDim = collectedFlexItemsValues.remainingFreeSpace / 2; - break; - case YGJustifyFlexEnd: - leadingMainDim = collectedFlexItemsValues.remainingFreeSpace; - break; - case YGJustifySpaceBetween: - if (collectedFlexItemsValues.itemsOnLine > 1) { - betweenMainDim += - yoga::maxOrDefined( - collectedFlexItemsValues.remainingFreeSpace, 0) / - (collectedFlexItemsValues.itemsOnLine - 1); - } - break; - case YGJustifySpaceEvenly: - // Space is distributed evenly across all elements - leadingMainDim = collectedFlexItemsValues.remainingFreeSpace / - (collectedFlexItemsValues.itemsOnLine + 1); - betweenMainDim += leadingMainDim; - break; - case YGJustifySpaceAround: - // Space on the edges is half of the space between elements - leadingMainDim = 0.5f * collectedFlexItemsValues.remainingFreeSpace / - collectedFlexItemsValues.itemsOnLine; - betweenMainDim += leadingMainDim * 2; - break; - case YGJustifyFlexStart: - break; - } - } - - collectedFlexItemsValues.mainDim = - leadingPaddingAndBorderMain + leadingMainDim; - collectedFlexItemsValues.crossDim = 0; - - float maxAscentForCurrentLine = 0; - float maxDescentForCurrentLine = 0; - bool isNodeBaselineLayout = YGIsBaselineLayout(node); - for (uint32_t i = startOfLineIndex; - i < collectedFlexItemsValues.endOfLineIndex; - i++) { - const auto child = node->getChild(i); - const Style& childStyle = child->getStyle(); - const LayoutResults& childLayout = child->getLayout(); - const bool isLastChild = i == collectedFlexItemsValues.endOfLineIndex - 1; - // remove the gap if it is the last element of the line - if (isLastChild) { - betweenMainDim -= gap; - } - if (childStyle.display() == YGDisplayNone) { - continue; - } - if (childStyle.positionType() == YGPositionTypeAbsolute && - child->isLeadingPositionDefined(mainAxis)) { - if (performLayout) { - // In case the child is position absolute and has left/top being - // defined, we override the position to whatever the user said (and - // margin/border). - child->setLayoutPosition( - child->getLeadingPosition(mainAxis, availableInnerMainDim) - .unwrap() + - node->getLeadingBorder(mainAxis) + - child->getLeadingMargin(mainAxis, availableInnerWidth).unwrap(), - pos[mainAxis]); - } - } else { - // Now that we placed the element, we need to update the variables. - // We need to do that only for relative elements. Absolute elements do not - // take part in that phase. - if (childStyle.positionType() != YGPositionTypeAbsolute) { - if (child->marginLeadingValue(mainAxis).unit == YGUnitAuto) { - collectedFlexItemsValues.mainDim += - collectedFlexItemsValues.remainingFreeSpace / - numberOfAutoMarginsOnCurrentLine; - } - - if (performLayout) { - child->setLayoutPosition( - childLayout.position[pos[mainAxis]] + - collectedFlexItemsValues.mainDim, - pos[mainAxis]); - } - - if (child->marginTrailingValue(mainAxis).unit == YGUnitAuto) { - collectedFlexItemsValues.mainDim += - collectedFlexItemsValues.remainingFreeSpace / - numberOfAutoMarginsOnCurrentLine; - } - bool canSkipFlex = - !performLayout && measureModeCrossDim == YGMeasureModeExactly; - if (canSkipFlex) { - // If we skipped the flex step, then we can't rely on the measuredDims - // because they weren't computed. This means we can't call - // YGNodeDimWithMargin. - collectedFlexItemsValues.mainDim += betweenMainDim + - child->getMarginForAxis(mainAxis, availableInnerWidth).unwrap() + - childLayout.computedFlexBasis.unwrap(); - collectedFlexItemsValues.crossDim = availableInnerCrossDim; - } else { - // The main dimension is the sum of all the elements dimension plus - // the spacing. - collectedFlexItemsValues.mainDim += betweenMainDim + - YGNodeDimWithMargin(child, mainAxis, availableInnerWidth); - - if (isNodeBaselineLayout) { - // If the child is baseline aligned then the cross dimension is - // calculated by adding maxAscent and maxDescent from the baseline. - const float ascent = YGBaseline(child, layoutContext) + - child - ->getLeadingMargin( - YGFlexDirectionColumn, availableInnerWidth) - .unwrap(); - const float descent = - child->getLayout().measuredDimensions[YGDimensionHeight] + - child - ->getMarginForAxis( - YGFlexDirectionColumn, availableInnerWidth) - .unwrap() - - ascent; - - maxAscentForCurrentLine = - yoga::maxOrDefined(maxAscentForCurrentLine, ascent); - maxDescentForCurrentLine = - yoga::maxOrDefined(maxDescentForCurrentLine, descent); - } else { - // The cross dimension is the max of the elements dimension since - // there can only be one element in that cross dimension in the case - // when the items are not baseline aligned - collectedFlexItemsValues.crossDim = yoga::maxOrDefined( - collectedFlexItemsValues.crossDim, - YGNodeDimWithMargin(child, crossAxis, availableInnerWidth)); - } - } - } else if (performLayout) { - child->setLayoutPosition( - childLayout.position[pos[mainAxis]] + - node->getLeadingBorder(mainAxis) + leadingMainDim, - pos[mainAxis]); - } - } - } - collectedFlexItemsValues.mainDim += trailingPaddingAndBorderMain; - - if (isNodeBaselineLayout) { - collectedFlexItemsValues.crossDim = - maxAscentForCurrentLine + maxDescentForCurrentLine; - } -} - -// -// This is the main routine that implements a subset of the flexbox layout -// algorithm described in the W3C CSS documentation: -// https://www.w3.org/TR/CSS3-flexbox/. -// -// Limitations of this algorithm, compared to the full standard: -// * Display property is always assumed to be 'flex' except for Text nodes, -// which are assumed to be 'inline-flex'. -// * The 'zIndex' property (or any form of z ordering) is not supported. Nodes -// are stacked in document order. -// * The 'order' property is not supported. The order of flex items is always -// defined by document order. -// * The 'visibility' property is always assumed to be 'visible'. Values of -// 'collapse' and 'hidden' are not supported. -// * There is no support for forced breaks. -// * It does not support vertical inline directions (top-to-bottom or -// bottom-to-top text). -// -// Deviations from standard: -// * Section 4.5 of the spec indicates that all flex items have a default -// minimum main size. For text blocks, for example, this is the width of the -// widest word. Calculating the minimum width is expensive, so we forego it -// and assume a default minimum main size of 0. -// * Min/Max sizes in the main axis are not honored when resolving flexible -// lengths. -// * The spec indicates that the default value for 'flexDirection' is 'row', -// but the algorithm below assumes a default of 'column'. -// -// Input parameters: -// - node: current node to be sized and laid out -// - availableWidth & availableHeight: available size to be used for sizing -// the node or YGUndefined if the size is not available; interpretation -// depends on layout flags -// - ownerDirection: the inline (text) direction within the owner -// (left-to-right or right-to-left) -// - widthMeasureMode: indicates the sizing rules for the width (see below -// for explanation) -// - heightMeasureMode: indicates the sizing rules for the height (see below -// for explanation) -// - performLayout: specifies whether the caller is interested in just the -// dimensions of the node or it requires the entire node and its subtree to -// be laid out (with final positions) -// -// Details: -// This routine is called recursively to lay out subtrees of flexbox -// elements. It uses the information in node.style, which is treated as a -// read-only input. It is responsible for setting the layout.direction and -// layout.measuredDimensions fields for the input node as well as the -// layout.position and layout.lineIndex fields for its child nodes. The -// layout.measuredDimensions field includes any border or padding for the -// node but does not include margins. -// -// The spec describes four different layout modes: "fill available", "max -// content", "min content", and "fit content". Of these, we don't use "min -// content" because we don't support default minimum main sizes (see above -// for details). Each of our measure modes maps to a layout mode from the -// spec (https://www.w3.org/TR/CSS3-sizing/#terms): -// - YGMeasureModeUndefined: max content -// - YGMeasureModeExactly: fill available -// - YGMeasureModeAtMost: fit content -// -// When calling YGNodelayoutImpl and YGLayoutNodeInternal, if the caller -// passes an available size of undefined then it must also pass a measure -// mode of YGMeasureModeUndefined in that dimension. -// -static void YGNodelayoutImpl( - yoga::Node* const node, - const float availableWidth, - const float availableHeight, - const YGDirection ownerDirection, - const YGMeasureMode widthMeasureMode, - const YGMeasureMode heightMeasureMode, - const float ownerWidth, - const float ownerHeight, - const bool performLayout, - const YGConfigRef config, - LayoutData& layoutMarkerData, - void* const layoutContext, - const uint32_t depth, - const uint32_t generationCount, - const LayoutPassReason reason) { - yoga::assertFatalWithNode( - node, - yoga::isUndefined(availableWidth) - ? widthMeasureMode == YGMeasureModeUndefined - : true, - "availableWidth is indefinite so widthMeasureMode must be " - "YGMeasureModeUndefined"); - yoga::assertFatalWithNode( - node, - yoga::isUndefined(availableHeight) - ? heightMeasureMode == YGMeasureModeUndefined - : true, - "availableHeight is indefinite so heightMeasureMode must be " - "YGMeasureModeUndefined"); - - (performLayout ? layoutMarkerData.layouts : layoutMarkerData.measures) += 1; - - // Set the resolved resolution in the node's layout. - const YGDirection direction = node->resolveDirection(ownerDirection); - node->setLayoutDirection(direction); - - const YGFlexDirection flexRowDirection = - resolveDirection(YGFlexDirectionRow, direction); - const YGFlexDirection flexColumnDirection = - resolveDirection(YGFlexDirectionColumn, direction); - - const YGEdge startEdge = - direction == YGDirectionLTR ? YGEdgeLeft : YGEdgeRight; - const YGEdge endEdge = direction == YGDirectionLTR ? YGEdgeRight : YGEdgeLeft; - - const float marginRowLeading = - node->getLeadingMargin(flexRowDirection, ownerWidth).unwrap(); - node->setLayoutMargin(marginRowLeading, startEdge); - const float marginRowTrailing = - node->getTrailingMargin(flexRowDirection, ownerWidth).unwrap(); - node->setLayoutMargin(marginRowTrailing, endEdge); - const float marginColumnLeading = - node->getLeadingMargin(flexColumnDirection, ownerWidth).unwrap(); - node->setLayoutMargin(marginColumnLeading, YGEdgeTop); - const float marginColumnTrailing = - node->getTrailingMargin(flexColumnDirection, ownerWidth).unwrap(); - node->setLayoutMargin(marginColumnTrailing, YGEdgeBottom); - - const float marginAxisRow = marginRowLeading + marginRowTrailing; - const float marginAxisColumn = marginColumnLeading + marginColumnTrailing; - - node->setLayoutBorder(node->getLeadingBorder(flexRowDirection), startEdge); - node->setLayoutBorder(node->getTrailingBorder(flexRowDirection), endEdge); - node->setLayoutBorder(node->getLeadingBorder(flexColumnDirection), YGEdgeTop); - node->setLayoutBorder( - node->getTrailingBorder(flexColumnDirection), YGEdgeBottom); - - node->setLayoutPadding( - node->getLeadingPadding(flexRowDirection, ownerWidth).unwrap(), - startEdge); - node->setLayoutPadding( - node->getTrailingPadding(flexRowDirection, ownerWidth).unwrap(), endEdge); - node->setLayoutPadding( - node->getLeadingPadding(flexColumnDirection, ownerWidth).unwrap(), - YGEdgeTop); - node->setLayoutPadding( - node->getTrailingPadding(flexColumnDirection, ownerWidth).unwrap(), - YGEdgeBottom); - - if (node->hasMeasureFunc()) { - YGNodeWithMeasureFuncSetMeasuredDimensions( - node, - availableWidth - marginAxisRow, - availableHeight - marginAxisColumn, - widthMeasureMode, - heightMeasureMode, - ownerWidth, - ownerHeight, - layoutMarkerData, - layoutContext, - reason); - return; - } - - const uint32_t childCount = YGNodeGetChildCount(node); - if (childCount == 0) { - YGNodeEmptyContainerSetMeasuredDimensions( - node, - availableWidth - marginAxisRow, - availableHeight - marginAxisColumn, - widthMeasureMode, - heightMeasureMode, - ownerWidth, - ownerHeight); - return; - } - - // If we're not being asked to perform a full layout we can skip the algorithm - // if we already know the size - if (!performLayout && - YGNodeFixedSizeSetMeasuredDimensions( - node, - availableWidth - marginAxisRow, - availableHeight - marginAxisColumn, - widthMeasureMode, - heightMeasureMode, - ownerWidth, - ownerHeight)) { - return; - } - - // At this point we know we're going to perform work. Ensure that each child - // has a mutable copy. - node->cloneChildrenIfNeeded(layoutContext); - // Reset layout flags, as they could have changed. - node->setLayoutHadOverflow(false); - - // STEP 1: CALCULATE VALUES FOR REMAINDER OF ALGORITHM - const YGFlexDirection mainAxis = - resolveDirection(node->getStyle().flexDirection(), direction); - const YGFlexDirection crossAxis = resolveCrossDirection(mainAxis, direction); - const bool isMainAxisRow = isRow(mainAxis); - const bool isNodeFlexWrap = node->getStyle().flexWrap() != YGWrapNoWrap; - - const float mainAxisownerSize = isMainAxisRow ? ownerWidth : ownerHeight; - const float crossAxisownerSize = isMainAxisRow ? ownerHeight : ownerWidth; - - const float paddingAndBorderAxisMain = - YGNodePaddingAndBorderForAxis(node, mainAxis, ownerWidth); - const float leadingPaddingAndBorderCross = - node->getLeadingPaddingAndBorder(crossAxis, ownerWidth).unwrap(); - const float trailingPaddingAndBorderCross = - node->getTrailingPaddingAndBorder(crossAxis, ownerWidth).unwrap(); - const float paddingAndBorderAxisCross = - leadingPaddingAndBorderCross + trailingPaddingAndBorderCross; - - YGMeasureMode measureModeMainDim = - isMainAxisRow ? widthMeasureMode : heightMeasureMode; - YGMeasureMode measureModeCrossDim = - isMainAxisRow ? heightMeasureMode : widthMeasureMode; - - const float paddingAndBorderAxisRow = - isMainAxisRow ? paddingAndBorderAxisMain : paddingAndBorderAxisCross; - const float paddingAndBorderAxisColumn = - isMainAxisRow ? paddingAndBorderAxisCross : paddingAndBorderAxisMain; - - // STEP 2: DETERMINE AVAILABLE SIZE IN MAIN AND CROSS DIRECTIONS - - float availableInnerWidth = YGNodeCalculateAvailableInnerDim( - node, - YGDimensionWidth, - availableWidth - marginAxisRow, - paddingAndBorderAxisRow, - ownerWidth); - float availableInnerHeight = YGNodeCalculateAvailableInnerDim( - node, - YGDimensionHeight, - availableHeight - marginAxisColumn, - paddingAndBorderAxisColumn, - ownerHeight); - - float availableInnerMainDim = - isMainAxisRow ? availableInnerWidth : availableInnerHeight; - const float availableInnerCrossDim = - isMainAxisRow ? availableInnerHeight : availableInnerWidth; - - // STEP 3: DETERMINE FLEX BASIS FOR EACH ITEM - - // Computed basis + margins + gap - float totalMainDim = 0; - totalMainDim += YGNodeComputeFlexBasisForChildren( - node, - availableInnerWidth, - availableInnerHeight, - widthMeasureMode, - heightMeasureMode, - direction, - mainAxis, - config, - performLayout, - layoutMarkerData, - layoutContext, - depth, - generationCount); - - if (childCount > 1) { - totalMainDim += - node->getGapForAxis(mainAxis, availableInnerCrossDim).unwrap() * - (childCount - 1); - } - - const bool mainAxisOverflows = - (measureModeMainDim != YGMeasureModeUndefined) && - totalMainDim > availableInnerMainDim; - - if (isNodeFlexWrap && mainAxisOverflows && - measureModeMainDim == YGMeasureModeAtMost) { - measureModeMainDim = YGMeasureModeExactly; - } - // STEP 4: COLLECT FLEX ITEMS INTO FLEX LINES - - // Indexes of children that represent the first and last items in the line. - uint32_t startOfLineIndex = 0; - uint32_t endOfLineIndex = 0; - - // Number of lines. - uint32_t lineCount = 0; - - // Accumulated cross dimensions of all lines so far. - float totalLineCrossDim = 0; - - const float crossAxisGap = - node->getGapForAxis(crossAxis, availableInnerCrossDim).unwrap(); - - // Max main dimension of all the lines. - float maxLineMainDim = 0; - CollectFlexItemsRowValues collectedFlexItemsValues; - for (; endOfLineIndex < childCount; - lineCount++, startOfLineIndex = endOfLineIndex) { - collectedFlexItemsValues = YGCalculateCollectFlexItemsRowValues( - node, - ownerDirection, - mainAxisownerSize, - availableInnerWidth, - availableInnerMainDim, - startOfLineIndex, - lineCount); - endOfLineIndex = collectedFlexItemsValues.endOfLineIndex; - - // If we don't need to measure the cross axis, we can skip the entire flex - // step. - const bool canSkipFlex = - !performLayout && measureModeCrossDim == YGMeasureModeExactly; - - // STEP 5: RESOLVING FLEXIBLE LENGTHS ON MAIN AXIS - // Calculate the remaining available space that needs to be allocated. If - // the main dimension size isn't known, it is computed based on the line - // length, so there's no more space left to distribute. - - bool sizeBasedOnContent = false; - // If we don't measure with exact main dimension we want to ensure we don't - // violate min and max - if (measureModeMainDim != YGMeasureModeExactly) { - const auto& minDimensions = node->getStyle().minDimensions(); - const auto& maxDimensions = node->getStyle().maxDimensions(); - const float minInnerWidth = - yoga::resolveValue(minDimensions[YGDimensionWidth], ownerWidth) - .unwrap() - - paddingAndBorderAxisRow; - const float maxInnerWidth = - yoga::resolveValue(maxDimensions[YGDimensionWidth], ownerWidth) - .unwrap() - - paddingAndBorderAxisRow; - const float minInnerHeight = - yoga::resolveValue(minDimensions[YGDimensionHeight], ownerHeight) - .unwrap() - - paddingAndBorderAxisColumn; - const float maxInnerHeight = - yoga::resolveValue(maxDimensions[YGDimensionHeight], ownerHeight) - .unwrap() - - paddingAndBorderAxisColumn; - - const float minInnerMainDim = - isMainAxisRow ? minInnerWidth : minInnerHeight; - const float maxInnerMainDim = - isMainAxisRow ? maxInnerWidth : maxInnerHeight; - - if (!yoga::isUndefined(minInnerMainDim) && - collectedFlexItemsValues.sizeConsumedOnCurrentLine < - minInnerMainDim) { - availableInnerMainDim = minInnerMainDim; - } else if ( - !yoga::isUndefined(maxInnerMainDim) && - collectedFlexItemsValues.sizeConsumedOnCurrentLine > - maxInnerMainDim) { - availableInnerMainDim = maxInnerMainDim; - } else { - bool useLegacyStretchBehaviour = - node->hasErrata(YGErrataStretchFlexBasis); - - if (!useLegacyStretchBehaviour && - ((!yoga::isUndefined( - collectedFlexItemsValues.totalFlexGrowFactors) && - collectedFlexItemsValues.totalFlexGrowFactors == 0) || - (!yoga::isUndefined(node->resolveFlexGrow()) && - node->resolveFlexGrow() == 0))) { - // If we don't have any children to flex or we can't flex the node - // itself, space we've used is all space we need. Root node also - // should be shrunk to minimum - availableInnerMainDim = - collectedFlexItemsValues.sizeConsumedOnCurrentLine; - } - - sizeBasedOnContent = !useLegacyStretchBehaviour; - } - } - - if (!sizeBasedOnContent && !yoga::isUndefined(availableInnerMainDim)) { - collectedFlexItemsValues.remainingFreeSpace = availableInnerMainDim - - collectedFlexItemsValues.sizeConsumedOnCurrentLine; - } else if (collectedFlexItemsValues.sizeConsumedOnCurrentLine < 0) { - // availableInnerMainDim is indefinite which means the node is being sized - // based on its content. sizeConsumedOnCurrentLine is negative which means - // the node will allocate 0 points for its content. Consequently, - // remainingFreeSpace is 0 - sizeConsumedOnCurrentLine. - collectedFlexItemsValues.remainingFreeSpace = - -collectedFlexItemsValues.sizeConsumedOnCurrentLine; - } - - if (!canSkipFlex) { - YGResolveFlexibleLength( - node, - collectedFlexItemsValues, - mainAxis, - crossAxis, - mainAxisownerSize, - availableInnerMainDim, - availableInnerCrossDim, - availableInnerWidth, - availableInnerHeight, - mainAxisOverflows, - measureModeCrossDim, - performLayout, - config, - layoutMarkerData, - layoutContext, - depth, - generationCount); - } - - node->setLayoutHadOverflow( - node->getLayout().hadOverflow() | - (collectedFlexItemsValues.remainingFreeSpace < 0)); - - // STEP 6: MAIN-AXIS JUSTIFICATION & CROSS-AXIS SIZE DETERMINATION - - // At this point, all the children have their dimensions set in the main - // axis. Their dimensions are also set in the cross axis with the exception - // of items that are aligned "stretch". We need to compute these stretch - // values and set the final positions. - - YGJustifyMainAxis( - node, - collectedFlexItemsValues, - startOfLineIndex, - mainAxis, - crossAxis, - measureModeMainDim, - measureModeCrossDim, - mainAxisownerSize, - ownerWidth, - availableInnerMainDim, - availableInnerCrossDim, - availableInnerWidth, - performLayout, - layoutContext); - - float containerCrossAxis = availableInnerCrossDim; - if (measureModeCrossDim == YGMeasureModeUndefined || - measureModeCrossDim == YGMeasureModeAtMost) { - // Compute the cross axis from the max cross dimension of the children. - containerCrossAxis = - YGNodeBoundAxis( - node, - crossAxis, - collectedFlexItemsValues.crossDim + paddingAndBorderAxisCross, - crossAxisownerSize, - ownerWidth) - - paddingAndBorderAxisCross; - } - - // If there's no flex wrap, the cross dimension is defined by the container. - if (!isNodeFlexWrap && measureModeCrossDim == YGMeasureModeExactly) { - collectedFlexItemsValues.crossDim = availableInnerCrossDim; - } - - // Clamp to the min/max size specified on the container. - collectedFlexItemsValues.crossDim = - YGNodeBoundAxis( - node, - crossAxis, - collectedFlexItemsValues.crossDim + paddingAndBorderAxisCross, - crossAxisownerSize, - ownerWidth) - - paddingAndBorderAxisCross; - - // STEP 7: CROSS-AXIS ALIGNMENT - // We can skip child alignment if we're just measuring the container. - if (performLayout) { - for (uint32_t i = startOfLineIndex; i < endOfLineIndex; i++) { - const auto child = node->getChild(i); - if (child->getStyle().display() == YGDisplayNone) { - continue; - } - if (child->getStyle().positionType() == YGPositionTypeAbsolute) { - // If the child is absolutely positioned and has a - // top/left/bottom/right set, override all the previously computed - // positions to set it correctly. - const bool isChildLeadingPosDefined = - child->isLeadingPositionDefined(crossAxis); - if (isChildLeadingPosDefined) { - child->setLayoutPosition( - child->getLeadingPosition(crossAxis, availableInnerCrossDim) - .unwrap() + - node->getLeadingBorder(crossAxis) + - child->getLeadingMargin(crossAxis, availableInnerWidth) - .unwrap(), - pos[crossAxis]); - } - // If leading position is not defined or calculations result in Nan, - // default to border + margin - if (!isChildLeadingPosDefined || - yoga::isUndefined(child->getLayout().position[pos[crossAxis]])) { - child->setLayoutPosition( - node->getLeadingBorder(crossAxis) + - child->getLeadingMargin(crossAxis, availableInnerWidth) - .unwrap(), - pos[crossAxis]); - } - } else { - float leadingCrossDim = leadingPaddingAndBorderCross; - - // For a relative children, we're either using alignItems (owner) or - // alignSelf (child) in order to determine the position in the cross - // axis - const YGAlign alignItem = YGNodeAlignItem(node, child); - - // If the child uses align stretch, we need to lay it out one more - // time, this time forcing the cross-axis size to be the computed - // cross size for the current line. - if (alignItem == YGAlignStretch && - child->marginLeadingValue(crossAxis).unit != YGUnitAuto && - child->marginTrailingValue(crossAxis).unit != YGUnitAuto) { - // If the child defines a definite size for its cross axis, there's - // no need to stretch. - if (!YGNodeIsStyleDimDefined( - child, crossAxis, availableInnerCrossDim)) { - float childMainSize = - child->getLayout().measuredDimensions[dim[mainAxis]]; - const auto& childStyle = child->getStyle(); - float childCrossSize = !childStyle.aspectRatio().isUndefined() - ? child->getMarginForAxis(crossAxis, availableInnerWidth) - .unwrap() + - (isMainAxisRow - ? childMainSize / childStyle.aspectRatio().unwrap() - : childMainSize * childStyle.aspectRatio().unwrap()) - : collectedFlexItemsValues.crossDim; - - childMainSize += - child->getMarginForAxis(mainAxis, availableInnerWidth) - .unwrap(); - - YGMeasureMode childMainMeasureMode = YGMeasureModeExactly; - YGMeasureMode childCrossMeasureMode = YGMeasureModeExactly; - YGConstrainMaxSizeForMode( - child, - mainAxis, - availableInnerMainDim, - availableInnerWidth, - &childMainMeasureMode, - &childMainSize); - YGConstrainMaxSizeForMode( - child, - crossAxis, - availableInnerCrossDim, - availableInnerWidth, - &childCrossMeasureMode, - &childCrossSize); - - const float childWidth = - isMainAxisRow ? childMainSize : childCrossSize; - const float childHeight = - !isMainAxisRow ? childMainSize : childCrossSize; - - auto alignContent = node->getStyle().alignContent(); - auto crossAxisDoesNotGrow = - alignContent != YGAlignStretch && isNodeFlexWrap; - const YGMeasureMode childWidthMeasureMode = - yoga::isUndefined(childWidth) || - (!isMainAxisRow && crossAxisDoesNotGrow) - ? YGMeasureModeUndefined - : YGMeasureModeExactly; - const YGMeasureMode childHeightMeasureMode = - yoga::isUndefined(childHeight) || - (isMainAxisRow && crossAxisDoesNotGrow) - ? YGMeasureModeUndefined - : YGMeasureModeExactly; - - YGLayoutNodeInternal( - child, - childWidth, - childHeight, - direction, - childWidthMeasureMode, - childHeightMeasureMode, - availableInnerWidth, - availableInnerHeight, - true, - LayoutPassReason::kStretch, - config, - layoutMarkerData, - layoutContext, - depth, - generationCount); - } - } else { - const float remainingCrossDim = containerCrossAxis - - YGNodeDimWithMargin(child, crossAxis, availableInnerWidth); - - if (child->marginLeadingValue(crossAxis).unit == YGUnitAuto && - child->marginTrailingValue(crossAxis).unit == YGUnitAuto) { - leadingCrossDim += - yoga::maxOrDefined(0.0f, remainingCrossDim / 2); - } else if ( - child->marginTrailingValue(crossAxis).unit == YGUnitAuto) { - // No-Op - } else if ( - child->marginLeadingValue(crossAxis).unit == YGUnitAuto) { - leadingCrossDim += yoga::maxOrDefined(0.0f, remainingCrossDim); - } else if (alignItem == YGAlignFlexStart) { - // No-Op - } else if (alignItem == YGAlignCenter) { - leadingCrossDim += remainingCrossDim / 2; - } else { - leadingCrossDim += remainingCrossDim; - } - } - // And we apply the position - child->setLayoutPosition( - child->getLayout().position[pos[crossAxis]] + totalLineCrossDim + - leadingCrossDim, - pos[crossAxis]); - } - } - } - - const float appliedCrossGap = lineCount != 0 ? crossAxisGap : 0.0f; - totalLineCrossDim += collectedFlexItemsValues.crossDim + appliedCrossGap; - maxLineMainDim = - yoga::maxOrDefined(maxLineMainDim, collectedFlexItemsValues.mainDim); - } - - // STEP 8: MULTI-LINE CONTENT ALIGNMENT - // currentLead stores the size of the cross dim - if (performLayout && (isNodeFlexWrap || YGIsBaselineLayout(node))) { - float crossDimLead = 0; - float currentLead = leadingPaddingAndBorderCross; - if (!yoga::isUndefined(availableInnerCrossDim)) { - const float remainingAlignContentDim = - availableInnerCrossDim - totalLineCrossDim; - switch (node->getStyle().alignContent()) { - case YGAlignFlexEnd: - currentLead += remainingAlignContentDim; - break; - case YGAlignCenter: - currentLead += remainingAlignContentDim / 2; - break; - case YGAlignStretch: - if (availableInnerCrossDim > totalLineCrossDim) { - crossDimLead = remainingAlignContentDim / lineCount; - } - break; - case YGAlignSpaceAround: - if (availableInnerCrossDim > totalLineCrossDim) { - currentLead += remainingAlignContentDim / (2 * lineCount); - if (lineCount > 1) { - crossDimLead = remainingAlignContentDim / lineCount; - } - } else { - currentLead += remainingAlignContentDim / 2; - } - break; - case YGAlignSpaceBetween: - if (availableInnerCrossDim > totalLineCrossDim && lineCount > 1) { - crossDimLead = remainingAlignContentDim / (lineCount - 1); - } - break; - case YGAlignAuto: - case YGAlignFlexStart: - case YGAlignBaseline: - break; - } - } - uint32_t endIndex = 0; - for (uint32_t i = 0; i < lineCount; i++) { - const uint32_t startIndex = endIndex; - uint32_t ii; - - // compute the line's height and find the endIndex - float lineHeight = 0; - float maxAscentForCurrentLine = 0; - float maxDescentForCurrentLine = 0; - for (ii = startIndex; ii < childCount; ii++) { - const auto child = node->getChild(ii); - if (child->getStyle().display() == YGDisplayNone) { - continue; - } - if (child->getStyle().positionType() != YGPositionTypeAbsolute) { - if (child->getLineIndex() != i) { - break; - } - if (YGNodeIsLayoutDimDefined(child, crossAxis)) { - lineHeight = yoga::maxOrDefined( - lineHeight, - child->getLayout().measuredDimensions[dim[crossAxis]] + - child->getMarginForAxis(crossAxis, availableInnerWidth) - .unwrap()); - } - if (YGNodeAlignItem(node, child) == YGAlignBaseline) { - const float ascent = YGBaseline(child, layoutContext) + - child - ->getLeadingMargin( - YGFlexDirectionColumn, availableInnerWidth) - .unwrap(); - const float descent = - child->getLayout().measuredDimensions[YGDimensionHeight] + - child - ->getMarginForAxis( - YGFlexDirectionColumn, availableInnerWidth) - .unwrap() - - ascent; - maxAscentForCurrentLine = - yoga::maxOrDefined(maxAscentForCurrentLine, ascent); - maxDescentForCurrentLine = - yoga::maxOrDefined(maxDescentForCurrentLine, descent); - lineHeight = yoga::maxOrDefined( - lineHeight, maxAscentForCurrentLine + maxDescentForCurrentLine); - } - } - } - endIndex = ii; - lineHeight += crossDimLead; - currentLead += i != 0 ? crossAxisGap : 0; - - if (performLayout) { - for (ii = startIndex; ii < endIndex; ii++) { - const auto child = node->getChild(ii); - if (child->getStyle().display() == YGDisplayNone) { - continue; - } - if (child->getStyle().positionType() != YGPositionTypeAbsolute) { - switch (YGNodeAlignItem(node, child)) { - case YGAlignFlexStart: { - child->setLayoutPosition( - currentLead + - child->getLeadingMargin(crossAxis, availableInnerWidth) - .unwrap(), - pos[crossAxis]); - break; - } - case YGAlignFlexEnd: { - child->setLayoutPosition( - currentLead + lineHeight - - child->getTrailingMargin(crossAxis, availableInnerWidth) - .unwrap() - - child->getLayout().measuredDimensions[dim[crossAxis]], - pos[crossAxis]); - break; - } - case YGAlignCenter: { - float childHeight = - child->getLayout().measuredDimensions[dim[crossAxis]]; - - child->setLayoutPosition( - currentLead + (lineHeight - childHeight) / 2, - pos[crossAxis]); - break; - } - case YGAlignStretch: { - child->setLayoutPosition( - currentLead + - child->getLeadingMargin(crossAxis, availableInnerWidth) - .unwrap(), - pos[crossAxis]); - - // Remeasure child with the line height as it as been only - // measured with the owners height yet. - if (!YGNodeIsStyleDimDefined( - child, crossAxis, availableInnerCrossDim)) { - const float childWidth = isMainAxisRow - ? (child->getLayout() - .measuredDimensions[YGDimensionWidth] + - child->getMarginForAxis(mainAxis, availableInnerWidth) - .unwrap()) - : lineHeight; - - const float childHeight = !isMainAxisRow - ? (child->getLayout() - .measuredDimensions[YGDimensionHeight] + - child->getMarginForAxis(crossAxis, availableInnerWidth) - .unwrap()) - : lineHeight; - - if (!(yoga::inexactEquals( - childWidth, - child->getLayout() - .measuredDimensions[YGDimensionWidth]) && - yoga::inexactEquals( - childHeight, - child->getLayout() - .measuredDimensions[YGDimensionHeight]))) { - YGLayoutNodeInternal( - child, - childWidth, - childHeight, - direction, - YGMeasureModeExactly, - YGMeasureModeExactly, - availableInnerWidth, - availableInnerHeight, - true, - LayoutPassReason::kMultilineStretch, - config, - layoutMarkerData, - layoutContext, - depth, - generationCount); - } - } - break; - } - case YGAlignBaseline: { - child->setLayoutPosition( - currentLead + maxAscentForCurrentLine - - YGBaseline(child, layoutContext) + - child - ->getLeadingPosition( - YGFlexDirectionColumn, availableInnerCrossDim) - .unwrap(), - YGEdgeTop); - - break; - } - case YGAlignAuto: - case YGAlignSpaceBetween: - case YGAlignSpaceAround: - break; - } - } - } - } - currentLead += lineHeight; - } - } - - // STEP 9: COMPUTING FINAL DIMENSIONS - - node->setLayoutMeasuredDimension( - YGNodeBoundAxis( - node, - YGFlexDirectionRow, - availableWidth - marginAxisRow, - ownerWidth, - ownerWidth), - YGDimensionWidth); - - node->setLayoutMeasuredDimension( - YGNodeBoundAxis( - node, - YGFlexDirectionColumn, - availableHeight - marginAxisColumn, - ownerHeight, - ownerWidth), - YGDimensionHeight); - - // If the user didn't specify a width or height for the node, set the - // dimensions based on the children. - if (measureModeMainDim == YGMeasureModeUndefined || - (node->getStyle().overflow() != YGOverflowScroll && - measureModeMainDim == YGMeasureModeAtMost)) { - // Clamp the size to the min/max size, if specified, and make sure it - // doesn't go below the padding and border amount. - node->setLayoutMeasuredDimension( - YGNodeBoundAxis( - node, mainAxis, maxLineMainDim, mainAxisownerSize, ownerWidth), - dim[mainAxis]); - - } else if ( - measureModeMainDim == YGMeasureModeAtMost && - node->getStyle().overflow() == YGOverflowScroll) { - node->setLayoutMeasuredDimension( - yoga::maxOrDefined( - yoga::minOrDefined( - availableInnerMainDim + paddingAndBorderAxisMain, - YGNodeBoundAxisWithinMinAndMax( - node, - mainAxis, - FloatOptional{maxLineMainDim}, - mainAxisownerSize) - .unwrap()), - paddingAndBorderAxisMain), - dim[mainAxis]); - } - - if (measureModeCrossDim == YGMeasureModeUndefined || - (node->getStyle().overflow() != YGOverflowScroll && - measureModeCrossDim == YGMeasureModeAtMost)) { - // Clamp the size to the min/max size, if specified, and make sure it - // doesn't go below the padding and border amount. - node->setLayoutMeasuredDimension( - YGNodeBoundAxis( - node, - crossAxis, - totalLineCrossDim + paddingAndBorderAxisCross, - crossAxisownerSize, - ownerWidth), - dim[crossAxis]); - - } else if ( - measureModeCrossDim == YGMeasureModeAtMost && - node->getStyle().overflow() == YGOverflowScroll) { - node->setLayoutMeasuredDimension( - yoga::maxOrDefined( - yoga::minOrDefined( - availableInnerCrossDim + paddingAndBorderAxisCross, - YGNodeBoundAxisWithinMinAndMax( - node, - crossAxis, - FloatOptional{ - totalLineCrossDim + paddingAndBorderAxisCross}, - crossAxisownerSize) - .unwrap()), - paddingAndBorderAxisCross), - dim[crossAxis]); - } - - // As we only wrapped in normal direction yet, we need to reverse the - // positions on wrap-reverse. - if (performLayout && node->getStyle().flexWrap() == YGWrapWrapReverse) { - for (uint32_t i = 0; i < childCount; i++) { - const auto child = node->getChild(i); - if (child->getStyle().positionType() != YGPositionTypeAbsolute) { - child->setLayoutPosition( - node->getLayout().measuredDimensions[dim[crossAxis]] - - child->getLayout().position[pos[crossAxis]] - - child->getLayout().measuredDimensions[dim[crossAxis]], - pos[crossAxis]); - } - } - } - - if (performLayout) { - // STEP 10: SIZING AND POSITIONING ABSOLUTE CHILDREN - for (auto child : node->getChildren()) { - if (child->getStyle().display() == YGDisplayNone || - child->getStyle().positionType() != YGPositionTypeAbsolute) { - continue; - } - const bool absolutePercentageAgainstPaddingEdge = - node->getConfig()->isExperimentalFeatureEnabled( - YGExperimentalFeatureAbsolutePercentageAgainstPaddingEdge); - - YGNodeAbsoluteLayoutChild( - node, - child, - absolutePercentageAgainstPaddingEdge - ? node->getLayout().measuredDimensions[YGDimensionWidth] - : availableInnerWidth, - isMainAxisRow ? measureModeMainDim : measureModeCrossDim, - absolutePercentageAgainstPaddingEdge - ? node->getLayout().measuredDimensions[YGDimensionHeight] - : availableInnerHeight, - direction, - config, - layoutMarkerData, - layoutContext, - depth, - generationCount); - } - - // STEP 11: SETTING TRAILING POSITIONS FOR CHILDREN - const bool needsMainTrailingPos = mainAxis == YGFlexDirectionRowReverse || - mainAxis == YGFlexDirectionColumnReverse; - const bool needsCrossTrailingPos = crossAxis == YGFlexDirectionRowReverse || - crossAxis == YGFlexDirectionColumnReverse; - - // Set trailing position if necessary. - if (needsMainTrailingPos || needsCrossTrailingPos) { - for (uint32_t i = 0; i < childCount; i++) { - const auto child = node->getChild(i); - if (child->getStyle().display() == YGDisplayNone) { - continue; - } - if (needsMainTrailingPos) { - YGNodeSetChildTrailingPosition(node, child, mainAxis); - } - - if (needsCrossTrailingPos) { - YGNodeSetChildTrailingPosition(node, child, crossAxis); - } - } - } - } -} - -bool gPrintChanges = false; -bool gPrintSkips = false; - -static const char* spacer = - " "; - -static const char* YGSpacer(const unsigned long level) { - const size_t spacerLen = strlen(spacer); - if (level > spacerLen) { - return &spacer[0]; - } else { - return &spacer[spacerLen - level]; - } -} - -static const char* YGMeasureModeName( - const YGMeasureMode mode, - const bool performLayout) { - constexpr auto N = enums::count(); - const char* kMeasureModeNames[N] = {"UNDEFINED", "EXACTLY", "AT_MOST"}; - const char* kLayoutModeNames[N] = { - "LAY_UNDEFINED", "LAY_EXACTLY", "LAY_AT_MOST"}; - - if (mode >= N) { - return ""; - } - - return performLayout ? kLayoutModeNames[mode] : kMeasureModeNames[mode]; -} - -static inline bool YGMeasureModeSizeIsExactAndMatchesOldMeasuredSize( - YGMeasureMode sizeMode, - float size, - float lastComputedSize) { - return sizeMode == YGMeasureModeExactly && - yoga::inexactEquals(size, lastComputedSize); -} - -static inline bool YGMeasureModeOldSizeIsUnspecifiedAndStillFits( - YGMeasureMode sizeMode, - float size, - YGMeasureMode lastSizeMode, - float lastComputedSize) { - return sizeMode == YGMeasureModeAtMost && - lastSizeMode == YGMeasureModeUndefined && - (size >= lastComputedSize || yoga::inexactEquals(size, lastComputedSize)); -} - -static inline bool YGMeasureModeNewMeasureSizeIsStricterAndStillValid( - YGMeasureMode sizeMode, - float size, - YGMeasureMode lastSizeMode, - float lastSize, - float lastComputedSize) { - return lastSizeMode == YGMeasureModeAtMost && - sizeMode == YGMeasureModeAtMost && !yoga::isUndefined(lastSize) && - !yoga::isUndefined(size) && !yoga::isUndefined(lastComputedSize) && - lastSize > size && - (lastComputedSize <= size || yoga::inexactEquals(size, lastComputedSize)); -} - -YOGA_EXPORT float YGRoundValueToPixelGrid( - const double value, - const double pointScaleFactor, - const bool forceCeil, - const bool forceFloor) { - double scaledValue = value * pointScaleFactor; - // We want to calculate `fractial` such that `floor(scaledValue) = scaledValue - // - fractial`. - double fractial = fmod(scaledValue, 1.0); - if (fractial < 0) { - // This branch is for handling negative numbers for `value`. - // - // Regarding `floor` and `ceil`. Note that for a number x, `floor(x) <= x <= - // ceil(x)` even for negative numbers. Here are a couple of examples: - // - x = 2.2: floor( 2.2) = 2, ceil( 2.2) = 3 - // - x = -2.2: floor(-2.2) = -3, ceil(-2.2) = -2 - // - // Regarding `fmodf`. For fractional negative numbers, `fmodf` returns a - // negative number. For example, `fmodf(-2.2) = -0.2`. However, we want - // `fractial` to be the number such that subtracting it from `value` will - // give us `floor(value)`. In the case of negative numbers, adding 1 to - // `fmodf(value)` gives us this. Let's continue the example from above: - // - fractial = fmodf(-2.2) = -0.2 - // - Add 1 to the fraction: fractial2 = fractial + 1 = -0.2 + 1 = 0.8 - // - Finding the `floor`: -2.2 - fractial2 = -2.2 - 0.8 = -3 - ++fractial; - } - if (yoga::inexactEquals(fractial, 0)) { - // First we check if the value is already rounded - scaledValue = scaledValue - fractial; - } else if (yoga::inexactEquals(fractial, 1.0)) { - scaledValue = scaledValue - fractial + 1.0; - } else if (forceCeil) { - // Next we check if we need to use forced rounding - scaledValue = scaledValue - fractial + 1.0; - } else if (forceFloor) { - scaledValue = scaledValue - fractial; - } else { - // Finally we just round the value - scaledValue = scaledValue - fractial + - (!yoga::isUndefined(fractial) && - (fractial > 0.5 || yoga::inexactEquals(fractial, 0.5)) - ? 1.0 - : 0.0); - } - return (yoga::isUndefined(scaledValue) || yoga::isUndefined(pointScaleFactor)) - ? YGUndefined - : (float) (scaledValue / pointScaleFactor); -} - -YOGA_EXPORT bool YGNodeCanUseCachedMeasurement( - const YGMeasureMode widthMode, - const float width, - const YGMeasureMode heightMode, - const float height, - const YGMeasureMode lastWidthMode, - const float lastWidth, - const YGMeasureMode lastHeightMode, - const float lastHeight, - const float lastComputedWidth, - const float lastComputedHeight, - const float marginRow, - const float marginColumn, - const YGConfigRef config) { - if ((!yoga::isUndefined(lastComputedHeight) && lastComputedHeight < 0) || - (!yoga::isUndefined(lastComputedWidth) && lastComputedWidth < 0)) { - return false; - } - bool useRoundedComparison = - config != nullptr && YGConfigGetPointScaleFactor(config) != 0; - const float effectiveWidth = useRoundedComparison - ? YGRoundValueToPixelGrid( - width, YGConfigGetPointScaleFactor(config), false, false) - : width; - const float effectiveHeight = useRoundedComparison - ? YGRoundValueToPixelGrid( - height, YGConfigGetPointScaleFactor(config), false, false) - : height; - const float effectiveLastWidth = useRoundedComparison - ? YGRoundValueToPixelGrid( - lastWidth, YGConfigGetPointScaleFactor(config), false, false) - : lastWidth; - const float effectiveLastHeight = useRoundedComparison - ? YGRoundValueToPixelGrid( - lastHeight, YGConfigGetPointScaleFactor(config), false, false) - : lastHeight; - - const bool hasSameWidthSpec = lastWidthMode == widthMode && - yoga::inexactEquals(effectiveLastWidth, effectiveWidth); - const bool hasSameHeightSpec = lastHeightMode == heightMode && - yoga::inexactEquals(effectiveLastHeight, effectiveHeight); - - const bool widthIsCompatible = - hasSameWidthSpec || - YGMeasureModeSizeIsExactAndMatchesOldMeasuredSize( - widthMode, width - marginRow, lastComputedWidth) || - YGMeasureModeOldSizeIsUnspecifiedAndStillFits( - widthMode, width - marginRow, lastWidthMode, lastComputedWidth) || - YGMeasureModeNewMeasureSizeIsStricterAndStillValid( - widthMode, - width - marginRow, - lastWidthMode, - lastWidth, - lastComputedWidth); - - const bool heightIsCompatible = - hasSameHeightSpec || - YGMeasureModeSizeIsExactAndMatchesOldMeasuredSize( - heightMode, height - marginColumn, lastComputedHeight) || - YGMeasureModeOldSizeIsUnspecifiedAndStillFits( - heightMode, - height - marginColumn, - lastHeightMode, - lastComputedHeight) || - YGMeasureModeNewMeasureSizeIsStricterAndStillValid( - heightMode, - height - marginColumn, - lastHeightMode, - lastHeight, - lastComputedHeight); - - return widthIsCompatible && heightIsCompatible; -} - -// -// This is a wrapper around the YGNodelayoutImpl function. It determines whether -// the layout request is redundant and can be skipped. -// -// Parameters: -// Input parameters are the same as YGNodelayoutImpl (see above) -// Return parameter is true if layout was performed, false if skipped -// -bool YGLayoutNodeInternal( - yoga::Node* const node, - const float availableWidth, - const float availableHeight, - const YGDirection ownerDirection, - const YGMeasureMode widthMeasureMode, - const YGMeasureMode heightMeasureMode, - const float ownerWidth, - const float ownerHeight, - const bool performLayout, - const LayoutPassReason reason, - const YGConfigRef config, - LayoutData& layoutMarkerData, - void* const layoutContext, - uint32_t depth, - const uint32_t generationCount) { - LayoutResults* layout = &node->getLayout(); - - depth++; - - const bool needToVisitNode = - (node->isDirty() && layout->generationCount != generationCount) || - layout->lastOwnerDirection != ownerDirection; - - if (needToVisitNode) { - // Invalidate the cached results. - layout->nextCachedMeasurementsIndex = 0; - layout->cachedLayout.availableWidth = -1; - layout->cachedLayout.availableHeight = -1; - layout->cachedLayout.widthMeasureMode = YGMeasureModeUndefined; - layout->cachedLayout.heightMeasureMode = YGMeasureModeUndefined; - layout->cachedLayout.computedWidth = -1; - layout->cachedLayout.computedHeight = -1; - } - - CachedMeasurement* cachedResults = nullptr; - - // Determine whether the results are already cached. We maintain a separate - // cache for layouts and measurements. A layout operation modifies the - // positions and dimensions for nodes in the subtree. The algorithm assumes - // that each node gets laid out a maximum of one time per tree layout, but - // multiple measurements may be required to resolve all of the flex - // dimensions. We handle nodes with measure functions specially here because - // they are the most expensive to measure, so it's worth avoiding redundant - // measurements if at all possible. - if (node->hasMeasureFunc()) { - const float marginAxisRow = - node->getMarginForAxis(YGFlexDirectionRow, ownerWidth).unwrap(); - const float marginAxisColumn = - node->getMarginForAxis(YGFlexDirectionColumn, ownerWidth).unwrap(); - - // First, try to use the layout cache. - if (YGNodeCanUseCachedMeasurement( - widthMeasureMode, - availableWidth, - heightMeasureMode, - availableHeight, - layout->cachedLayout.widthMeasureMode, - layout->cachedLayout.availableWidth, - layout->cachedLayout.heightMeasureMode, - layout->cachedLayout.availableHeight, - layout->cachedLayout.computedWidth, - layout->cachedLayout.computedHeight, - marginAxisRow, - marginAxisColumn, - config)) { - cachedResults = &layout->cachedLayout; - } else { - // Try to use the measurement cache. - for (uint32_t i = 0; i < layout->nextCachedMeasurementsIndex; i++) { - if (YGNodeCanUseCachedMeasurement( - widthMeasureMode, - availableWidth, - heightMeasureMode, - availableHeight, - layout->cachedMeasurements[i].widthMeasureMode, - layout->cachedMeasurements[i].availableWidth, - layout->cachedMeasurements[i].heightMeasureMode, - layout->cachedMeasurements[i].availableHeight, - layout->cachedMeasurements[i].computedWidth, - layout->cachedMeasurements[i].computedHeight, - marginAxisRow, - marginAxisColumn, - config)) { - cachedResults = &layout->cachedMeasurements[i]; - break; - } - } - } - } else if (performLayout) { - if (yoga::inexactEquals( - layout->cachedLayout.availableWidth, availableWidth) && - yoga::inexactEquals( - layout->cachedLayout.availableHeight, availableHeight) && - layout->cachedLayout.widthMeasureMode == widthMeasureMode && - layout->cachedLayout.heightMeasureMode == heightMeasureMode) { - cachedResults = &layout->cachedLayout; - } - } else { - for (uint32_t i = 0; i < layout->nextCachedMeasurementsIndex; i++) { - if (yoga::inexactEquals( - layout->cachedMeasurements[i].availableWidth, availableWidth) && - yoga::inexactEquals( - layout->cachedMeasurements[i].availableHeight, availableHeight) && - layout->cachedMeasurements[i].widthMeasureMode == widthMeasureMode && - layout->cachedMeasurements[i].heightMeasureMode == - heightMeasureMode) { - cachedResults = &layout->cachedMeasurements[i]; - break; - } - } - } - - if (!needToVisitNode && cachedResults != nullptr) { - layout->measuredDimensions[YGDimensionWidth] = cachedResults->computedWidth; - layout->measuredDimensions[YGDimensionHeight] = - cachedResults->computedHeight; - - (performLayout ? layoutMarkerData.cachedLayouts - : layoutMarkerData.cachedMeasures) += 1; - - if (gPrintChanges && gPrintSkips) { - yoga::log( - node, - YGLogLevelVerbose, - nullptr, - "%s%d.{[skipped] ", - YGSpacer(depth), - depth); - node->print(layoutContext); - yoga::log( - node, - YGLogLevelVerbose, - nullptr, - "wm: %s, hm: %s, aw: %f ah: %f => d: (%f, %f) %s\n", - YGMeasureModeName(widthMeasureMode, performLayout), - YGMeasureModeName(heightMeasureMode, performLayout), - availableWidth, - availableHeight, - cachedResults->computedWidth, - cachedResults->computedHeight, - LayoutPassReasonToString(reason)); - } - } else { - if (gPrintChanges) { - yoga::log( - node, - YGLogLevelVerbose, - nullptr, - "%s%d.{%s", - YGSpacer(depth), - depth, - needToVisitNode ? "*" : ""); - node->print(layoutContext); - yoga::log( - node, - YGLogLevelVerbose, - nullptr, - "wm: %s, hm: %s, aw: %f ah: %f %s\n", - YGMeasureModeName(widthMeasureMode, performLayout), - YGMeasureModeName(heightMeasureMode, performLayout), - availableWidth, - availableHeight, - LayoutPassReasonToString(reason)); - } - - YGNodelayoutImpl( - node, - availableWidth, - availableHeight, - ownerDirection, - widthMeasureMode, - heightMeasureMode, - ownerWidth, - ownerHeight, - performLayout, - config, - layoutMarkerData, - layoutContext, - depth, - generationCount, - reason); - - if (gPrintChanges) { - yoga::log( - node, - YGLogLevelVerbose, - nullptr, - "%s%d.}%s", - YGSpacer(depth), - depth, - needToVisitNode ? "*" : ""); - node->print(layoutContext); - yoga::log( - node, - YGLogLevelVerbose, - nullptr, - "wm: %s, hm: %s, d: (%f, %f) %s\n", - YGMeasureModeName(widthMeasureMode, performLayout), - YGMeasureModeName(heightMeasureMode, performLayout), - layout->measuredDimensions[YGDimensionWidth], - layout->measuredDimensions[YGDimensionHeight], - LayoutPassReasonToString(reason)); - } - - layout->lastOwnerDirection = ownerDirection; - - if (cachedResults == nullptr) { - if (layout->nextCachedMeasurementsIndex + 1 > - (uint32_t) layoutMarkerData.maxMeasureCache) { - layoutMarkerData.maxMeasureCache = - layout->nextCachedMeasurementsIndex + 1; - } - if (layout->nextCachedMeasurementsIndex == - LayoutResults::MaxCachedMeasurements) { - if (gPrintChanges) { - yoga::log( - node, YGLogLevelVerbose, nullptr, "Out of cache entries!\n"); - } - layout->nextCachedMeasurementsIndex = 0; - } - - CachedMeasurement* newCacheEntry; - if (performLayout) { - // Use the single layout cache entry. - newCacheEntry = &layout->cachedLayout; - } else { - // Allocate a new measurement cache entry. - newCacheEntry = - &layout->cachedMeasurements[layout->nextCachedMeasurementsIndex]; - layout->nextCachedMeasurementsIndex++; - } - - newCacheEntry->availableWidth = availableWidth; - newCacheEntry->availableHeight = availableHeight; - newCacheEntry->widthMeasureMode = widthMeasureMode; - newCacheEntry->heightMeasureMode = heightMeasureMode; - newCacheEntry->computedWidth = - layout->measuredDimensions[YGDimensionWidth]; - newCacheEntry->computedHeight = - layout->measuredDimensions[YGDimensionHeight]; - } - } - - if (performLayout) { - node->setLayoutDimension( - node->getLayout().measuredDimensions[YGDimensionWidth], - YGDimensionWidth); - node->setLayoutDimension( - node->getLayout().measuredDimensions[YGDimensionHeight], - YGDimensionHeight); - - node->setHasNewLayout(true); - node->setDirty(false); - } - - layout->generationCount = generationCount; - - LayoutType layoutType; - if (performLayout) { - layoutType = !needToVisitNode && cachedResults == &layout->cachedLayout - ? LayoutType::kCachedLayout - : LayoutType::kLayout; +YOGA_EXPORT void YGConfigSetLogger(const YGConfigRef config, YGLogger logger) { + if (logger != nullptr) { + static_cast(config)->setLogger(logger); } else { - layoutType = cachedResults != nullptr ? LayoutType::kCachedMeasure - : LayoutType::kMeasure; +#ifdef ANDROID + static_cast(config)->setLogger(&YGAndroidLog); +#else + static_cast(config)->setLogger(&YGDefaultLog); +#endif } - Event::publish(node, {layoutType, layoutContext}); - - return (needToVisitNode || cachedResults == nullptr); } YOGA_EXPORT void YGConfigSetPointScaleFactor( @@ -4108,184 +1017,13 @@ YOGA_EXPORT float YGConfigGetPointScaleFactor(const YGConfigRef config) { return static_cast(config)->getPointScaleFactor(); } -static void YGRoundToPixelGrid( - yoga::Node* const node, +YOGA_EXPORT float YGRoundValueToPixelGrid( + const double value, const double pointScaleFactor, - const double absoluteLeft, - const double absoluteTop) { - if (pointScaleFactor == 0.0f) { - return; - } - - const double nodeLeft = node->getLayout().position[YGEdgeLeft]; - const double nodeTop = node->getLayout().position[YGEdgeTop]; - - const double nodeWidth = node->getLayout().dimensions[YGDimensionWidth]; - const double nodeHeight = node->getLayout().dimensions[YGDimensionHeight]; - - const double absoluteNodeLeft = absoluteLeft + nodeLeft; - const double absoluteNodeTop = absoluteTop + nodeTop; - - const double absoluteNodeRight = absoluteNodeLeft + nodeWidth; - const double absoluteNodeBottom = absoluteNodeTop + nodeHeight; - - // If a node has a custom measure function we never want to round down its - // size as this could lead to unwanted text truncation. - const bool textRounding = node->getNodeType() == YGNodeTypeText; - - node->setLayoutPosition( - YGRoundValueToPixelGrid(nodeLeft, pointScaleFactor, false, textRounding), - YGEdgeLeft); - - node->setLayoutPosition( - YGRoundValueToPixelGrid(nodeTop, pointScaleFactor, false, textRounding), - YGEdgeTop); - - // We multiply dimension by scale factor and if the result is close to the - // whole number, we don't have any fraction To verify if the result is close - // to whole number we want to check both floor and ceil numbers - const bool hasFractionalWidth = - !yoga::inexactEquals(fmod(nodeWidth * pointScaleFactor, 1.0), 0) && - !yoga::inexactEquals(fmod(nodeWidth * pointScaleFactor, 1.0), 1.0); - const bool hasFractionalHeight = - !yoga::inexactEquals(fmod(nodeHeight * pointScaleFactor, 1.0), 0) && - !yoga::inexactEquals(fmod(nodeHeight * pointScaleFactor, 1.0), 1.0); - - node->setLayoutDimension( - YGRoundValueToPixelGrid( - absoluteNodeRight, - pointScaleFactor, - (textRounding && hasFractionalWidth), - (textRounding && !hasFractionalWidth)) - - YGRoundValueToPixelGrid( - absoluteNodeLeft, pointScaleFactor, false, textRounding), - YGDimensionWidth); - - node->setLayoutDimension( - YGRoundValueToPixelGrid( - absoluteNodeBottom, - pointScaleFactor, - (textRounding && hasFractionalHeight), - (textRounding && !hasFractionalHeight)) - - YGRoundValueToPixelGrid( - absoluteNodeTop, pointScaleFactor, false, textRounding), - YGDimensionHeight); - - const uint32_t childCount = YGNodeGetChildCount(node); - for (uint32_t i = 0; i < childCount; i++) { - YGRoundToPixelGrid( - node->getChild(i), pointScaleFactor, absoluteNodeLeft, absoluteNodeTop); - } -} - -YOGA_EXPORT void YGNodeCalculateLayoutWithContext( - const YGNodeRef nodeRef, - const float ownerWidth, - const float ownerHeight, - const YGDirection ownerDirection, - void* layoutContext) { - Event::publish(nodeRef, {layoutContext}); - LayoutData markerData = {}; - - const auto node = static_cast(nodeRef); - - // Increment the generation count. This will force the recursive routine to - // visit all dirty nodes at least once. Subsequent visits will be skipped if - // the input parameters don't change. - gCurrentGenerationCount.fetch_add(1, std::memory_order_relaxed); - node->resolveDimension(); - float width = YGUndefined; - YGMeasureMode widthMeasureMode = YGMeasureModeUndefined; - const auto& maxDimensions = node->getStyle().maxDimensions(); - if (YGNodeIsStyleDimDefined(node, YGFlexDirectionRow, ownerWidth)) { - width = - (yoga::resolveValue( - node->getResolvedDimension(dim[YGFlexDirectionRow]), ownerWidth) + - node->getMarginForAxis(YGFlexDirectionRow, ownerWidth)) - .unwrap(); - widthMeasureMode = YGMeasureModeExactly; - } else if (!yoga::resolveValue(maxDimensions[YGDimensionWidth], ownerWidth) - .isUndefined()) { - width = yoga::resolveValue(maxDimensions[YGDimensionWidth], ownerWidth) - .unwrap(); - widthMeasureMode = YGMeasureModeAtMost; - } else { - width = ownerWidth; - widthMeasureMode = yoga::isUndefined(width) ? YGMeasureModeUndefined - : YGMeasureModeExactly; - } - - float height = YGUndefined; - YGMeasureMode heightMeasureMode = YGMeasureModeUndefined; - if (YGNodeIsStyleDimDefined(node, YGFlexDirectionColumn, ownerHeight)) { - height = (yoga::resolveValue( - node->getResolvedDimension(dim[YGFlexDirectionColumn]), - ownerHeight) + - node->getMarginForAxis(YGFlexDirectionColumn, ownerWidth)) - .unwrap(); - heightMeasureMode = YGMeasureModeExactly; - } else if (!yoga::resolveValue(maxDimensions[YGDimensionHeight], ownerHeight) - .isUndefined()) { - height = yoga::resolveValue(maxDimensions[YGDimensionHeight], ownerHeight) - .unwrap(); - heightMeasureMode = YGMeasureModeAtMost; - } else { - height = ownerHeight; - heightMeasureMode = yoga::isUndefined(height) ? YGMeasureModeUndefined - : YGMeasureModeExactly; - } - if (YGLayoutNodeInternal( - node, - width, - height, - ownerDirection, - widthMeasureMode, - heightMeasureMode, - ownerWidth, - ownerHeight, - true, - LayoutPassReason::kInitial, - node->getConfig(), - markerData, - layoutContext, - 0, // tree root - gCurrentGenerationCount.load(std::memory_order_relaxed))) { - node->setPosition( - node->getLayout().direction(), ownerWidth, ownerHeight, ownerWidth); - YGRoundToPixelGrid( - node, node->getConfig()->getPointScaleFactor(), 0.0f, 0.0f); - -#ifdef DEBUG - if (node->getConfig()->shouldPrintTree()) { - YGNodePrint( - node, - (YGPrintOptions) (YGPrintOptionsLayout | YGPrintOptionsChildren | YGPrintOptionsStyle)); - } -#endif - } - - Event::publish(node, {layoutContext, &markerData}); -} - -YOGA_EXPORT void YGNodeCalculateLayout( - const YGNodeRef node, - const float ownerWidth, - const float ownerHeight, - const YGDirection ownerDirection) { - YGNodeCalculateLayoutWithContext( - node, ownerWidth, ownerHeight, ownerDirection, nullptr); -} - -YOGA_EXPORT void YGConfigSetLogger(const YGConfigRef config, YGLogger logger) { - if (logger != nullptr) { - static_cast(config)->setLogger(logger); - } else { -#ifdef ANDROID - static_cast(config)->setLogger(&YGAndroidLog); -#else - static_cast(config)->setLogger(&YGDefaultLog); -#endif - } + const bool forceCeil, + const bool forceFloor) { + return yoga::roundValueToPixelGrid( + value, pointScaleFactor, forceCeil, forceFloor); } YOGA_EXPORT void YGConfigSetExperimentalFeatureEnabled( @@ -4350,3 +1088,58 @@ YOGA_EXPORT void YGConfigSetCloneNodeFunc( const YGCloneNodeFunc callback) { static_cast(config)->setCloneNodeCallback(callback); } + +// TODO: This should not be part of the public API. Remove after removing +// ComponentKit usage of it. +YOGA_EXPORT bool YGNodeCanUseCachedMeasurement( + YGMeasureMode widthMode, + float availableWidth, + YGMeasureMode heightMode, + float availableHeight, + YGMeasureMode lastWidthMode, + float lastAvailableWidth, + YGMeasureMode lastHeightMode, + float lastAvailableHeight, + float lastComputedWidth, + float lastComputedHeight, + float marginRow, + float marginColumn, + YGConfigRef config) { + return yoga::canUseCachedMeasurement( + widthMode, + availableWidth, + heightMode, + availableHeight, + lastWidthMode, + lastAvailableWidth, + lastHeightMode, + lastAvailableHeight, + lastComputedWidth, + lastComputedHeight, + marginRow, + marginColumn, + static_cast(config)); +} + +YOGA_EXPORT void YGNodeCalculateLayout( + const YGNodeRef node, + const float ownerWidth, + const float ownerHeight, + const YGDirection ownerDirection) { + YGNodeCalculateLayoutWithContext( + node, ownerWidth, ownerHeight, ownerDirection, nullptr); +} + +YOGA_EXPORT void YGNodeCalculateLayoutWithContext( + const YGNodeRef nodeRef, + const float ownerWidth, + const float ownerHeight, + const YGDirection ownerDirection, + void* layoutContext) { + yoga::calculateLayout( + static_cast(nodeRef), + ownerWidth, + ownerHeight, + ownerDirection, + layoutContext); +} diff --git a/yoga/Yoga.h b/yoga/Yoga.h index 3085dce273..de8aa67c01 100644 --- a/yoga/Yoga.h +++ b/yoga/Yoga.h @@ -115,15 +115,17 @@ WIN_EXPORT void YGNodePrint(YGNodeRef node, YGPrintOptions options); WIN_EXPORT bool YGFloatIsUndefined(float value); +// TODO: This should not be part of the public API. Remove after removing +// ComponentKit usage of it. WIN_EXPORT bool YGNodeCanUseCachedMeasurement( YGMeasureMode widthMode, - float width, + float availableWidth, YGMeasureMode heightMode, - float height, + float availableHeight, YGMeasureMode lastWidthMode, - float lastWidth, + float lastAvailableWidth, YGMeasureMode lastHeightMode, - float lastHeight, + float lastAvailableHeight, float lastComputedWidth, float lastComputedHeight, float marginRow, diff --git a/yoga/algorithm/Align.h b/yoga/algorithm/Align.h new file mode 100644 index 0000000000..9f7f4c24a2 --- /dev/null +++ b/yoga/algorithm/Align.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include + +#include +#include + +namespace facebook::yoga { + +inline YGAlign resolveChildAlignment( + const yoga::Node* node, + const yoga::Node* child) { + const YGAlign align = child->getStyle().alignSelf() == YGAlignAuto + ? node->getStyle().alignItems() + : child->getStyle().alignSelf(); + if (align == YGAlignBaseline && isColumn(node->getStyle().flexDirection())) { + return YGAlignFlexStart; + } + return align; +} + +} // namespace facebook::yoga diff --git a/yoga/algorithm/Baseline.cpp b/yoga/algorithm/Baseline.cpp new file mode 100644 index 0000000000..89799f22b7 --- /dev/null +++ b/yoga/algorithm/Baseline.cpp @@ -0,0 +1,65 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include + +#include +#include +#include +#include + +namespace facebook::yoga { + +float calculateBaseline(const yoga::Node* node, void* layoutContext) { + if (node->hasBaselineFunc()) { + + Event::publish(node); + + const float baseline = node->baseline( + node->getLayout().measuredDimensions[YGDimensionWidth], + node->getLayout().measuredDimensions[YGDimensionHeight], + layoutContext); + + Event::publish(node); + + yoga::assertFatalWithNode( + node, + !std::isnan(baseline), + "Expect custom baseline function to not return NaN"); + return baseline; + } + + yoga::Node* baselineChild = nullptr; + const uint32_t childCount = YGNodeGetChildCount(node); + for (uint32_t i = 0; i < childCount; i++) { + auto child = node->getChild(i); + if (child->getLineIndex() > 0) { + break; + } + if (child->getStyle().positionType() == YGPositionTypeAbsolute) { + continue; + } + if (resolveChildAlignment(node, child) == YGAlignBaseline || + child->isReferenceBaseline()) { + baselineChild = child; + break; + } + + if (baselineChild == nullptr) { + baselineChild = child; + } + } + + if (baselineChild == nullptr) { + return node->getLayout().measuredDimensions[YGDimensionHeight]; + } + + const float baseline = calculateBaseline(baselineChild, layoutContext); + return baseline + baselineChild->getLayout().position[YGEdgeTop]; +} + +} // namespace facebook::yoga diff --git a/yoga/algorithm/Baseline.h b/yoga/algorithm/Baseline.h new file mode 100644 index 0000000000..2e6b015845 --- /dev/null +++ b/yoga/algorithm/Baseline.h @@ -0,0 +1,18 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include + +namespace facebook::yoga { + +// Calculate baseline represented as an offset from the top edge of the node. +float calculateBaseline(const yoga::Node* node, void* layoutContext); + +} // namespace facebook::yoga diff --git a/yoga/algorithm/Cache.cpp b/yoga/algorithm/Cache.cpp new file mode 100644 index 0000000000..8cabe4dac2 --- /dev/null +++ b/yoga/algorithm/Cache.cpp @@ -0,0 +1,123 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include + +#include +#include +#include + +namespace facebook::yoga { + +static inline bool sizeIsExactAndMatchesOldMeasuredSize( + YGMeasureMode sizeMode, + float size, + float lastComputedSize) { + return sizeMode == YGMeasureModeExactly && + yoga::inexactEquals(size, lastComputedSize); +} + +static inline bool oldSizeIsUnspecifiedAndStillFits( + YGMeasureMode sizeMode, + float size, + YGMeasureMode lastSizeMode, + float lastComputedSize) { + return sizeMode == YGMeasureModeAtMost && + lastSizeMode == YGMeasureModeUndefined && + (size >= lastComputedSize || yoga::inexactEquals(size, lastComputedSize)); +} + +static inline bool newMeasureSizeIsStricterAndStillValid( + YGMeasureMode sizeMode, + float size, + YGMeasureMode lastSizeMode, + float lastSize, + float lastComputedSize) { + return lastSizeMode == YGMeasureModeAtMost && + sizeMode == YGMeasureModeAtMost && !std::isnan(lastSize) && + !std::isnan(size) && !std::isnan(lastComputedSize) && lastSize > size && + (lastComputedSize <= size || yoga::inexactEquals(size, lastComputedSize)); +} + +bool canUseCachedMeasurement( + const YGMeasureMode widthMode, + const float availableWidth, + const YGMeasureMode heightMode, + const float availableHeight, + const YGMeasureMode lastWidthMode, + const float lastAvailableWidth, + const YGMeasureMode lastHeightMode, + const float lastAvailableHeight, + const float lastComputedWidth, + const float lastComputedHeight, + const float marginRow, + const float marginColumn, + const yoga::Config* const config) { + if ((!std::isnan(lastComputedHeight) && lastComputedHeight < 0) || + (!std::isnan(lastComputedWidth) && lastComputedWidth < 0)) { + return false; + } + + const float pointScaleFactor = config->getPointScaleFactor(); + + bool useRoundedComparison = config != nullptr && pointScaleFactor != 0; + const float effectiveWidth = useRoundedComparison + ? roundValueToPixelGrid(availableWidth, pointScaleFactor, false, false) + : availableWidth; + const float effectiveHeight = useRoundedComparison + ? roundValueToPixelGrid(availableHeight, pointScaleFactor, false, false) + : availableHeight; + const float effectiveLastWidth = useRoundedComparison + ? roundValueToPixelGrid( + lastAvailableWidth, pointScaleFactor, false, false) + : lastAvailableWidth; + const float effectiveLastHeight = useRoundedComparison + ? roundValueToPixelGrid( + lastAvailableHeight, pointScaleFactor, false, false) + : lastAvailableHeight; + + const bool hasSameWidthSpec = lastWidthMode == widthMode && + yoga::inexactEquals(effectiveLastWidth, effectiveWidth); + const bool hasSameHeightSpec = lastHeightMode == heightMode && + yoga::inexactEquals(effectiveLastHeight, effectiveHeight); + + const bool widthIsCompatible = + hasSameWidthSpec || + sizeIsExactAndMatchesOldMeasuredSize( + widthMode, availableWidth - marginRow, lastComputedWidth) || + oldSizeIsUnspecifiedAndStillFits( + widthMode, + availableWidth - marginRow, + lastWidthMode, + lastComputedWidth) || + newMeasureSizeIsStricterAndStillValid( + widthMode, + availableWidth - marginRow, + lastWidthMode, + lastAvailableWidth, + lastComputedWidth); + + const bool heightIsCompatible = + hasSameHeightSpec || + sizeIsExactAndMatchesOldMeasuredSize( + heightMode, availableHeight - marginColumn, lastComputedHeight) || + oldSizeIsUnspecifiedAndStillFits( + heightMode, + availableHeight - marginColumn, + lastHeightMode, + lastComputedHeight) || + newMeasureSizeIsStricterAndStillValid( + heightMode, + availableHeight - marginColumn, + lastHeightMode, + lastAvailableHeight, + lastComputedHeight); + + return widthIsCompatible && heightIsCompatible; +} + +} // namespace facebook::yoga diff --git a/yoga/algorithm/Cache.h b/yoga/algorithm/Cache.h new file mode 100644 index 0000000000..76643242dc --- /dev/null +++ b/yoga/algorithm/Cache.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include + +namespace facebook::yoga { + +bool canUseCachedMeasurement( + YGMeasureMode widthMode, + float availableWidth, + YGMeasureMode heightMode, + float availableHeight, + YGMeasureMode lastWidthMode, + float lastAvailableWidth, + YGMeasureMode lastHeightMode, + float lastAvailableHeight, + float lastComputedWidth, + float lastComputedHeight, + float marginRow, + float marginColumn, + const yoga::Config* config); + +} // namespace facebook::yoga diff --git a/yoga/algorithm/CalculateLayout.cpp b/yoga/algorithm/CalculateLayout.cpp new file mode 100644 index 0000000000..6150b89956 --- /dev/null +++ b/yoga/algorithm/CalculateLayout.cpp @@ -0,0 +1,2997 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace facebook::yoga { + +std::atomic gCurrentGenerationCount(0); + +bool calculateLayoutInternal( + yoga::Node* const node, + const float availableWidth, + const float availableHeight, + const YGDirection ownerDirection, + const YGMeasureMode widthMeasureMode, + const YGMeasureMode heightMeasureMode, + const float ownerWidth, + const float ownerHeight, + const bool performLayout, + const LayoutPassReason reason, + const yoga::Config* const config, + LayoutData& layoutMarkerData, + void* const layoutContext, + const uint32_t depth, + const uint32_t generationCount); + +static const std::array pos = {{ + YGEdgeTop, + YGEdgeBottom, + YGEdgeLeft, + YGEdgeRight, +}}; + +static const std::array dim = { + {YGDimensionHeight, YGDimensionHeight, YGDimensionWidth, YGDimensionWidth}}; + +static inline float paddingAndBorderForAxis( + const yoga::Node* const node, + const YGFlexDirection axis, + const float widthSize) { + return (node->getLeadingPaddingAndBorder(axis, widthSize) + + node->getTrailingPaddingAndBorder(axis, widthSize)) + .unwrap(); +} + +static bool isBaselineLayout(const yoga::Node* node) { + if (isColumn(node->getStyle().flexDirection())) { + return false; + } + if (node->getStyle().alignItems() == YGAlignBaseline) { + return true; + } + const uint32_t childCount = node->getChildCount(); + for (uint32_t i = 0; i < childCount; i++) { + auto child = node->getChild(i); + if (child->getStyle().positionType() != YGPositionTypeAbsolute && + child->getStyle().alignSelf() == YGAlignBaseline) { + return true; + } + } + + return false; +} + +static inline float dimensionWithMargin( + const yoga::Node* const node, + const YGFlexDirection axis, + const float widthSize) { + return node->getLayout().measuredDimensions[dim[axis]] + + (node->getLeadingMargin(axis, widthSize) + + node->getTrailingMargin(axis, widthSize)) + .unwrap(); +} + +static inline bool styleDefinesDimension( + const yoga::Node* const node, + const YGFlexDirection axis, + const float ownerSize) { + bool isUndefined = + yoga::isUndefined(node->getResolvedDimension(dim[axis]).value); + return !( + node->getResolvedDimension(dim[axis]).unit == YGUnitAuto || + node->getResolvedDimension(dim[axis]).unit == YGUnitUndefined || + (node->getResolvedDimension(dim[axis]).unit == YGUnitPoint && + !isUndefined && node->getResolvedDimension(dim[axis]).value < 0.0f) || + (node->getResolvedDimension(dim[axis]).unit == YGUnitPercent && + !isUndefined && + (node->getResolvedDimension(dim[axis]).value < 0.0f || + yoga::isUndefined(ownerSize)))); +} + +static inline bool isLayoutDimensionDefined( + const yoga::Node* const node, + const YGFlexDirection axis) { + const float value = node->getLayout().measuredDimensions[dim[axis]]; + return !yoga::isUndefined(value) && value >= 0.0f; +} + +static FloatOptional boundAxisWithinMinAndMax( + const yoga::Node* const node, + const YGFlexDirection axis, + const FloatOptional value, + const float axisSize) { + FloatOptional min; + FloatOptional max; + + if (isColumn(axis)) { + min = yoga::resolveValue( + node->getStyle().minDimensions()[YGDimensionHeight], axisSize); + max = yoga::resolveValue( + node->getStyle().maxDimensions()[YGDimensionHeight], axisSize); + } else if (isRow(axis)) { + min = yoga::resolveValue( + node->getStyle().minDimensions()[YGDimensionWidth], axisSize); + max = yoga::resolveValue( + node->getStyle().maxDimensions()[YGDimensionWidth], axisSize); + } + + if (max >= FloatOptional{0} && value > max) { + return max; + } + + if (min >= FloatOptional{0} && value < min) { + return min; + } + + return value; +} + +// Like boundAxisWithinMinAndMax but also ensures that the value doesn't +// go below the padding and border amount. +static inline float boundAxis( + const yoga::Node* const node, + const YGFlexDirection axis, + const float value, + const float axisSize, + const float widthSize) { + return yoga::maxOrDefined( + boundAxisWithinMinAndMax(node, axis, FloatOptional{value}, axisSize) + .unwrap(), + paddingAndBorderForAxis(node, axis, widthSize)); +} + +static void setChildTrailingPosition( + const yoga::Node* const node, + yoga::Node* const child, + const YGFlexDirection axis) { + const float size = child->getLayout().measuredDimensions[dim[axis]]; + child->setLayoutPosition( + node->getLayout().measuredDimensions[dim[axis]] - size - + child->getLayout().position[pos[axis]], + trailingEdge(axis)); +} + +static void constrainMaxSizeForMode( + const yoga::Node* const node, + const enum YGFlexDirection axis, + const float ownerAxisSize, + const float ownerWidth, + YGMeasureMode* mode, + float* size) { + const FloatOptional maxSize = + yoga::resolveValue( + node->getStyle().maxDimensions()[dim[axis]], ownerAxisSize) + + FloatOptional(node->getMarginForAxis(axis, ownerWidth)); + switch (*mode) { + case YGMeasureModeExactly: + case YGMeasureModeAtMost: + *size = (maxSize.isUndefined() || *size < maxSize.unwrap()) + ? *size + : maxSize.unwrap(); + break; + case YGMeasureModeUndefined: + if (!maxSize.isUndefined()) { + *mode = YGMeasureModeAtMost; + *size = maxSize.unwrap(); + } + break; + } +} + +static void computeFlexBasisForChild( + const yoga::Node* const node, + yoga::Node* const child, + const float width, + const YGMeasureMode widthMode, + const float height, + const float ownerWidth, + const float ownerHeight, + const YGMeasureMode heightMode, + const YGDirection direction, + const yoga::Config* const config, + LayoutData& layoutMarkerData, + void* const layoutContext, + const uint32_t depth, + const uint32_t generationCount) { + const YGFlexDirection mainAxis = + resolveDirection(node->getStyle().flexDirection(), direction); + const bool isMainAxisRow = isRow(mainAxis); + const float mainAxisSize = isMainAxisRow ? width : height; + const float mainAxisownerSize = isMainAxisRow ? ownerWidth : ownerHeight; + + float childWidth; + float childHeight; + YGMeasureMode childWidthMeasureMode; + YGMeasureMode childHeightMeasureMode; + + const FloatOptional resolvedFlexBasis = + yoga::resolveValue(child->resolveFlexBasisPtr(), mainAxisownerSize); + const bool isRowStyleDimDefined = + styleDefinesDimension(child, YGFlexDirectionRow, ownerWidth); + const bool isColumnStyleDimDefined = + styleDefinesDimension(child, YGFlexDirectionColumn, ownerHeight); + + if (!resolvedFlexBasis.isUndefined() && !yoga::isUndefined(mainAxisSize)) { + if (child->getLayout().computedFlexBasis.isUndefined() || + (child->getConfig()->isExperimentalFeatureEnabled( + YGExperimentalFeatureWebFlexBasis) && + child->getLayout().computedFlexBasisGeneration != generationCount)) { + const FloatOptional paddingAndBorder = + FloatOptional(paddingAndBorderForAxis(child, mainAxis, ownerWidth)); + child->setLayoutComputedFlexBasis( + yoga::maxOrDefined(resolvedFlexBasis, paddingAndBorder)); + } + } else if (isMainAxisRow && isRowStyleDimDefined) { + // The width is definite, so use that as the flex basis. + const FloatOptional paddingAndBorder = FloatOptional( + paddingAndBorderForAxis(child, YGFlexDirectionRow, ownerWidth)); + + child->setLayoutComputedFlexBasis(yoga::maxOrDefined( + yoga::resolveValue( + child->getResolvedDimensions()[YGDimensionWidth], ownerWidth), + paddingAndBorder)); + } else if (!isMainAxisRow && isColumnStyleDimDefined) { + // The height is definite, so use that as the flex basis. + const FloatOptional paddingAndBorder = FloatOptional( + paddingAndBorderForAxis(child, YGFlexDirectionColumn, ownerWidth)); + child->setLayoutComputedFlexBasis(yoga::maxOrDefined( + yoga::resolveValue( + child->getResolvedDimensions()[YGDimensionHeight], ownerHeight), + paddingAndBorder)); + } else { + // Compute the flex basis and hypothetical main size (i.e. the clamped flex + // basis). + childWidth = YGUndefined; + childHeight = YGUndefined; + childWidthMeasureMode = YGMeasureModeUndefined; + childHeightMeasureMode = YGMeasureModeUndefined; + + auto marginRow = + child->getMarginForAxis(YGFlexDirectionRow, ownerWidth).unwrap(); + auto marginColumn = + child->getMarginForAxis(YGFlexDirectionColumn, ownerWidth).unwrap(); + + if (isRowStyleDimDefined) { + childWidth = + yoga::resolveValue( + child->getResolvedDimensions()[YGDimensionWidth], ownerWidth) + .unwrap() + + marginRow; + childWidthMeasureMode = YGMeasureModeExactly; + } + if (isColumnStyleDimDefined) { + childHeight = + yoga::resolveValue( + child->getResolvedDimensions()[YGDimensionHeight], ownerHeight) + .unwrap() + + marginColumn; + childHeightMeasureMode = YGMeasureModeExactly; + } + + // The W3C spec doesn't say anything about the 'overflow' property, but all + // major browsers appear to implement the following logic. + if ((!isMainAxisRow && node->getStyle().overflow() == YGOverflowScroll) || + node->getStyle().overflow() != YGOverflowScroll) { + if (yoga::isUndefined(childWidth) && !yoga::isUndefined(width)) { + childWidth = width; + childWidthMeasureMode = YGMeasureModeAtMost; + } + } + + if ((isMainAxisRow && node->getStyle().overflow() == YGOverflowScroll) || + node->getStyle().overflow() != YGOverflowScroll) { + if (yoga::isUndefined(childHeight) && !yoga::isUndefined(height)) { + childHeight = height; + childHeightMeasureMode = YGMeasureModeAtMost; + } + } + + const auto& childStyle = child->getStyle(); + if (!childStyle.aspectRatio().isUndefined()) { + if (!isMainAxisRow && childWidthMeasureMode == YGMeasureModeExactly) { + childHeight = marginColumn + + (childWidth - marginRow) / childStyle.aspectRatio().unwrap(); + childHeightMeasureMode = YGMeasureModeExactly; + } else if ( + isMainAxisRow && childHeightMeasureMode == YGMeasureModeExactly) { + childWidth = marginRow + + (childHeight - marginColumn) * childStyle.aspectRatio().unwrap(); + childWidthMeasureMode = YGMeasureModeExactly; + } + } + + // If child has no defined size in the cross axis and is set to stretch, set + // the cross axis to be measured exactly with the available inner width + + const bool hasExactWidth = + !yoga::isUndefined(width) && widthMode == YGMeasureModeExactly; + const bool childWidthStretch = + resolveChildAlignment(node, child) == YGAlignStretch && + childWidthMeasureMode != YGMeasureModeExactly; + if (!isMainAxisRow && !isRowStyleDimDefined && hasExactWidth && + childWidthStretch) { + childWidth = width; + childWidthMeasureMode = YGMeasureModeExactly; + if (!childStyle.aspectRatio().isUndefined()) { + childHeight = + (childWidth - marginRow) / childStyle.aspectRatio().unwrap(); + childHeightMeasureMode = YGMeasureModeExactly; + } + } + + const bool hasExactHeight = + !yoga::isUndefined(height) && heightMode == YGMeasureModeExactly; + const bool childHeightStretch = + resolveChildAlignment(node, child) == YGAlignStretch && + childHeightMeasureMode != YGMeasureModeExactly; + if (isMainAxisRow && !isColumnStyleDimDefined && hasExactHeight && + childHeightStretch) { + childHeight = height; + childHeightMeasureMode = YGMeasureModeExactly; + + if (!childStyle.aspectRatio().isUndefined()) { + childWidth = + (childHeight - marginColumn) * childStyle.aspectRatio().unwrap(); + childWidthMeasureMode = YGMeasureModeExactly; + } + } + + constrainMaxSizeForMode( + child, + YGFlexDirectionRow, + ownerWidth, + ownerWidth, + &childWidthMeasureMode, + &childWidth); + constrainMaxSizeForMode( + child, + YGFlexDirectionColumn, + ownerHeight, + ownerWidth, + &childHeightMeasureMode, + &childHeight); + + // Measure the child + calculateLayoutInternal( + child, + childWidth, + childHeight, + direction, + childWidthMeasureMode, + childHeightMeasureMode, + ownerWidth, + ownerHeight, + false, + LayoutPassReason::kMeasureChild, + config, + layoutMarkerData, + layoutContext, + depth, + generationCount); + + child->setLayoutComputedFlexBasis(FloatOptional(yoga::maxOrDefined( + child->getLayout().measuredDimensions[dim[mainAxis]], + paddingAndBorderForAxis(child, mainAxis, ownerWidth)))); + } + child->setLayoutComputedFlexBasisGeneration(generationCount); +} + +static void layoutAbsoluteChild( + const yoga::Node* const node, + yoga::Node* const child, + const float width, + const YGMeasureMode widthMode, + const float height, + const YGDirection direction, + const yoga::Config* const config, + LayoutData& layoutMarkerData, + void* const layoutContext, + const uint32_t depth, + const uint32_t generationCount) { + const YGFlexDirection mainAxis = + resolveDirection(node->getStyle().flexDirection(), direction); + const YGFlexDirection crossAxis = resolveCrossDirection(mainAxis, direction); + const bool isMainAxisRow = isRow(mainAxis); + + float childWidth = YGUndefined; + float childHeight = YGUndefined; + YGMeasureMode childWidthMeasureMode = YGMeasureModeUndefined; + YGMeasureMode childHeightMeasureMode = YGMeasureModeUndefined; + + auto marginRow = child->getMarginForAxis(YGFlexDirectionRow, width).unwrap(); + auto marginColumn = + child->getMarginForAxis(YGFlexDirectionColumn, width).unwrap(); + + if (styleDefinesDimension(child, YGFlexDirectionRow, width)) { + childWidth = yoga::resolveValue( + child->getResolvedDimensions()[YGDimensionWidth], width) + .unwrap() + + marginRow; + } else { + // If the child doesn't have a specified width, compute the width based on + // the left/right offsets if they're defined. + if (child->isLeadingPositionDefined(YGFlexDirectionRow) && + child->isTrailingPosDefined(YGFlexDirectionRow)) { + childWidth = node->getLayout().measuredDimensions[YGDimensionWidth] - + (node->getLeadingBorder(YGFlexDirectionRow) + + node->getTrailingBorder(YGFlexDirectionRow)) - + (child->getLeadingPosition(YGFlexDirectionRow, width) + + child->getTrailingPosition(YGFlexDirectionRow, width)) + .unwrap(); + childWidth = + boundAxis(child, YGFlexDirectionRow, childWidth, width, width); + } + } + + if (styleDefinesDimension(child, YGFlexDirectionColumn, height)) { + childHeight = yoga::resolveValue( + child->getResolvedDimensions()[YGDimensionHeight], height) + .unwrap() + + marginColumn; + } else { + // If the child doesn't have a specified height, compute the height based on + // the top/bottom offsets if they're defined. + if (child->isLeadingPositionDefined(YGFlexDirectionColumn) && + child->isTrailingPosDefined(YGFlexDirectionColumn)) { + childHeight = node->getLayout().measuredDimensions[YGDimensionHeight] - + (node->getLeadingBorder(YGFlexDirectionColumn) + + node->getTrailingBorder(YGFlexDirectionColumn)) - + (child->getLeadingPosition(YGFlexDirectionColumn, height) + + child->getTrailingPosition(YGFlexDirectionColumn, height)) + .unwrap(); + childHeight = + boundAxis(child, YGFlexDirectionColumn, childHeight, height, width); + } + } + + // Exactly one dimension needs to be defined for us to be able to do aspect + // ratio calculation. One dimension being the anchor and the other being + // flexible. + const auto& childStyle = child->getStyle(); + if (yoga::isUndefined(childWidth) ^ yoga::isUndefined(childHeight)) { + if (!childStyle.aspectRatio().isUndefined()) { + if (yoga::isUndefined(childWidth)) { + childWidth = marginRow + + (childHeight - marginColumn) * childStyle.aspectRatio().unwrap(); + } else if (yoga::isUndefined(childHeight)) { + childHeight = marginColumn + + (childWidth - marginRow) / childStyle.aspectRatio().unwrap(); + } + } + } + + // If we're still missing one or the other dimension, measure the content. + if (yoga::isUndefined(childWidth) || yoga::isUndefined(childHeight)) { + childWidthMeasureMode = yoga::isUndefined(childWidth) + ? YGMeasureModeUndefined + : YGMeasureModeExactly; + childHeightMeasureMode = yoga::isUndefined(childHeight) + ? YGMeasureModeUndefined + : YGMeasureModeExactly; + + // If the size of the owner is defined then try to constrain the absolute + // child to that size as well. This allows text within the absolute child to + // wrap to the size of its owner. This is the same behavior as many browsers + // implement. + if (!isMainAxisRow && yoga::isUndefined(childWidth) && + widthMode != YGMeasureModeUndefined && !yoga::isUndefined(width) && + width > 0) { + childWidth = width; + childWidthMeasureMode = YGMeasureModeAtMost; + } + + calculateLayoutInternal( + child, + childWidth, + childHeight, + direction, + childWidthMeasureMode, + childHeightMeasureMode, + childWidth, + childHeight, + false, + LayoutPassReason::kAbsMeasureChild, + config, + layoutMarkerData, + layoutContext, + depth, + generationCount); + childWidth = child->getLayout().measuredDimensions[YGDimensionWidth] + + child->getMarginForAxis(YGFlexDirectionRow, width).unwrap(); + childHeight = child->getLayout().measuredDimensions[YGDimensionHeight] + + child->getMarginForAxis(YGFlexDirectionColumn, width).unwrap(); + } + + calculateLayoutInternal( + child, + childWidth, + childHeight, + direction, + YGMeasureModeExactly, + YGMeasureModeExactly, + childWidth, + childHeight, + true, + LayoutPassReason::kAbsLayout, + config, + layoutMarkerData, + layoutContext, + depth, + generationCount); + + if (child->isTrailingPosDefined(mainAxis) && + !child->isLeadingPositionDefined(mainAxis)) { + child->setLayoutPosition( + node->getLayout().measuredDimensions[dim[mainAxis]] - + child->getLayout().measuredDimensions[dim[mainAxis]] - + node->getTrailingBorder(mainAxis) - + child->getTrailingMargin(mainAxis, isMainAxisRow ? width : height) + .unwrap() - + child->getTrailingPosition(mainAxis, isMainAxisRow ? width : height) + .unwrap(), + leadingEdge(mainAxis)); + } else if ( + !child->isLeadingPositionDefined(mainAxis) && + node->getStyle().justifyContent() == YGJustifyCenter) { + child->setLayoutPosition( + (node->getLayout().measuredDimensions[dim[mainAxis]] - + child->getLayout().measuredDimensions[dim[mainAxis]]) / + 2.0f, + leadingEdge(mainAxis)); + } else if ( + !child->isLeadingPositionDefined(mainAxis) && + node->getStyle().justifyContent() == YGJustifyFlexEnd) { + child->setLayoutPosition( + (node->getLayout().measuredDimensions[dim[mainAxis]] - + child->getLayout().measuredDimensions[dim[mainAxis]]), + leadingEdge(mainAxis)); + } else if ( + node->getConfig()->isExperimentalFeatureEnabled( + YGExperimentalFeatureAbsolutePercentageAgainstPaddingEdge) && + child->isLeadingPositionDefined(mainAxis)) { + child->setLayoutPosition( + child->getLeadingPosition( + mainAxis, node->getLayout().measuredDimensions[dim[mainAxis]]) + .unwrap() + + node->getLeadingBorder(mainAxis) + + child + ->getLeadingMargin( + mainAxis, + node->getLayout().measuredDimensions[dim[mainAxis]]) + .unwrap(), + leadingEdge(mainAxis)); + } + + if (child->isTrailingPosDefined(crossAxis) && + !child->isLeadingPositionDefined(crossAxis)) { + child->setLayoutPosition( + node->getLayout().measuredDimensions[dim[crossAxis]] - + child->getLayout().measuredDimensions[dim[crossAxis]] - + node->getTrailingBorder(crossAxis) - + child->getTrailingMargin(crossAxis, isMainAxisRow ? height : width) + .unwrap() - + child + ->getTrailingPosition(crossAxis, isMainAxisRow ? height : width) + .unwrap(), + leadingEdge(crossAxis)); + + } else if ( + !child->isLeadingPositionDefined(crossAxis) && + resolveChildAlignment(node, child) == YGAlignCenter) { + child->setLayoutPosition( + (node->getLayout().measuredDimensions[dim[crossAxis]] - + child->getLayout().measuredDimensions[dim[crossAxis]]) / + 2.0f, + leadingEdge(crossAxis)); + } else if ( + !child->isLeadingPositionDefined(crossAxis) && + ((resolveChildAlignment(node, child) == YGAlignFlexEnd) ^ + (node->getStyle().flexWrap() == YGWrapWrapReverse))) { + child->setLayoutPosition( + (node->getLayout().measuredDimensions[dim[crossAxis]] - + child->getLayout().measuredDimensions[dim[crossAxis]]), + leadingEdge(crossAxis)); + } else if ( + node->getConfig()->isExperimentalFeatureEnabled( + YGExperimentalFeatureAbsolutePercentageAgainstPaddingEdge) && + child->isLeadingPositionDefined(crossAxis)) { + child->setLayoutPosition( + child->getLeadingPosition( + crossAxis, + node->getLayout().measuredDimensions[dim[crossAxis]]) + .unwrap() + + node->getLeadingBorder(crossAxis) + + child + ->getLeadingMargin( + crossAxis, + node->getLayout().measuredDimensions[dim[crossAxis]]) + .unwrap(), + leadingEdge(crossAxis)); + } +} + +static void measureNodeWithMeasureFunc( + yoga::Node* const node, + float availableWidth, + float availableHeight, + const YGMeasureMode widthMeasureMode, + const YGMeasureMode heightMeasureMode, + const float ownerWidth, + const float ownerHeight, + LayoutData& layoutMarkerData, + void* const layoutContext, + const LayoutPassReason reason) { + yoga::assertFatalWithNode( + node, + node->hasMeasureFunc(), + "Expected node to have custom measure function"); + + if (widthMeasureMode == YGMeasureModeUndefined) { + availableWidth = YGUndefined; + } + if (heightMeasureMode == YGMeasureModeUndefined) { + availableHeight = YGUndefined; + } + + const auto& padding = node->getLayout().padding; + const auto& border = node->getLayout().border; + const float paddingAndBorderAxisRow = padding[YGEdgeLeft] + + padding[YGEdgeRight] + border[YGEdgeLeft] + border[YGEdgeRight]; + const float paddingAndBorderAxisColumn = padding[YGEdgeTop] + + padding[YGEdgeBottom] + border[YGEdgeTop] + border[YGEdgeBottom]; + + // We want to make sure we don't call measure with negative size + const float innerWidth = yoga::isUndefined(availableWidth) + ? availableWidth + : yoga::maxOrDefined(0, availableWidth - paddingAndBorderAxisRow); + const float innerHeight = yoga::isUndefined(availableHeight) + ? availableHeight + : yoga::maxOrDefined(0, availableHeight - paddingAndBorderAxisColumn); + + if (widthMeasureMode == YGMeasureModeExactly && + heightMeasureMode == YGMeasureModeExactly) { + // Don't bother sizing the text if both dimensions are already defined. + node->setLayoutMeasuredDimension( + boundAxis( + node, YGFlexDirectionRow, availableWidth, ownerWidth, ownerWidth), + YGDimensionWidth); + node->setLayoutMeasuredDimension( + boundAxis( + node, + YGFlexDirectionColumn, + availableHeight, + ownerHeight, + ownerWidth), + YGDimensionHeight); + } else { + Event::publish(node); + + // Measure the text under the current constraints. + const YGSize measuredSize = node->measure( + innerWidth, + widthMeasureMode, + innerHeight, + heightMeasureMode, + layoutContext); + + layoutMarkerData.measureCallbacks += 1; + layoutMarkerData.measureCallbackReasonsCount[static_cast(reason)] += + 1; + + Event::publish( + node, + {layoutContext, + innerWidth, + widthMeasureMode, + innerHeight, + heightMeasureMode, + measuredSize.width, + measuredSize.height, + reason}); + + node->setLayoutMeasuredDimension( + boundAxis( + node, + YGFlexDirectionRow, + (widthMeasureMode == YGMeasureModeUndefined || + widthMeasureMode == YGMeasureModeAtMost) + ? measuredSize.width + paddingAndBorderAxisRow + : availableWidth, + ownerWidth, + ownerWidth), + YGDimensionWidth); + + node->setLayoutMeasuredDimension( + boundAxis( + node, + YGFlexDirectionColumn, + (heightMeasureMode == YGMeasureModeUndefined || + heightMeasureMode == YGMeasureModeAtMost) + ? measuredSize.height + paddingAndBorderAxisColumn + : availableHeight, + ownerHeight, + ownerWidth), + YGDimensionHeight); + } +} + +// For nodes with no children, use the available values if they were provided, +// or the minimum size as indicated by the padding and border sizes. +static void measureNodeWithoutChildren( + yoga::Node* const node, + const float availableWidth, + const float availableHeight, + const YGMeasureMode widthMeasureMode, + const YGMeasureMode heightMeasureMode, + const float ownerWidth, + const float ownerHeight) { + const auto& padding = node->getLayout().padding; + const auto& border = node->getLayout().border; + + float width = availableWidth; + if (widthMeasureMode == YGMeasureModeUndefined || + widthMeasureMode == YGMeasureModeAtMost) { + width = padding[YGEdgeLeft] + padding[YGEdgeRight] + border[YGEdgeLeft] + + border[YGEdgeRight]; + } + node->setLayoutMeasuredDimension( + boundAxis(node, YGFlexDirectionRow, width, ownerWidth, ownerWidth), + YGDimensionWidth); + + float height = availableHeight; + if (heightMeasureMode == YGMeasureModeUndefined || + heightMeasureMode == YGMeasureModeAtMost) { + height = padding[YGEdgeTop] + padding[YGEdgeBottom] + border[YGEdgeTop] + + border[YGEdgeBottom]; + } + node->setLayoutMeasuredDimension( + boundAxis(node, YGFlexDirectionColumn, height, ownerHeight, ownerWidth), + YGDimensionHeight); +} + +static bool measureNodeWithFixedSize( + yoga::Node* const node, + const float availableWidth, + const float availableHeight, + const YGMeasureMode widthMeasureMode, + const YGMeasureMode heightMeasureMode, + const float ownerWidth, + const float ownerHeight) { + if ((!yoga::isUndefined(availableWidth) && + widthMeasureMode == YGMeasureModeAtMost && availableWidth <= 0.0f) || + (!yoga::isUndefined(availableHeight) && + heightMeasureMode == YGMeasureModeAtMost && availableHeight <= 0.0f) || + (widthMeasureMode == YGMeasureModeExactly && + heightMeasureMode == YGMeasureModeExactly)) { + node->setLayoutMeasuredDimension( + boundAxis( + node, + YGFlexDirectionRow, + yoga::isUndefined(availableWidth) || + (widthMeasureMode == YGMeasureModeAtMost && + availableWidth < 0.0f) + ? 0.0f + : availableWidth, + ownerWidth, + ownerWidth), + YGDimensionWidth); + + node->setLayoutMeasuredDimension( + boundAxis( + node, + YGFlexDirectionColumn, + yoga::isUndefined(availableHeight) || + (heightMeasureMode == YGMeasureModeAtMost && + availableHeight < 0.0f) + ? 0.0f + : availableHeight, + ownerHeight, + ownerWidth), + YGDimensionHeight); + return true; + } + + return false; +} + +static void zeroOutLayoutRecursively( + yoga::Node* const node, + void* layoutContext) { + node->getLayout() = {}; + node->setLayoutDimension(0, 0); + node->setLayoutDimension(0, 1); + node->setHasNewLayout(true); + + node->iterChildrenAfterCloningIfNeeded( + zeroOutLayoutRecursively, layoutContext); +} + +static float calculateAvailableInnerDimension( + const yoga::Node* const node, + const YGDimension dimension, + const float availableDim, + const float paddingAndBorder, + const float ownerDim) { + float availableInnerDim = availableDim - paddingAndBorder; + // Max dimension overrides predefined dimension value; Min dimension in turn + // overrides both of the above + if (!yoga::isUndefined(availableInnerDim)) { + // We want to make sure our available height does not violate min and max + // constraints + const FloatOptional minDimensionOptional = yoga::resolveValue( + node->getStyle().minDimensions()[dimension], ownerDim); + const float minInnerDim = minDimensionOptional.isUndefined() + ? 0.0f + : minDimensionOptional.unwrap() - paddingAndBorder; + + const FloatOptional maxDimensionOptional = yoga::resolveValue( + node->getStyle().maxDimensions()[dimension], ownerDim); + + const float maxInnerDim = maxDimensionOptional.isUndefined() + ? FLT_MAX + : maxDimensionOptional.unwrap() - paddingAndBorder; + availableInnerDim = yoga::maxOrDefined( + yoga::minOrDefined(availableInnerDim, maxInnerDim), minInnerDim); + } + + return availableInnerDim; +} + +static float computeFlexBasisForChildren( + yoga::Node* const node, + const float availableInnerWidth, + const float availableInnerHeight, + YGMeasureMode widthMeasureMode, + YGMeasureMode heightMeasureMode, + YGDirection direction, + YGFlexDirection mainAxis, + const yoga::Config* const config, + bool performLayout, + LayoutData& layoutMarkerData, + void* const layoutContext, + const uint32_t depth, + const uint32_t generationCount) { + float totalOuterFlexBasis = 0.0f; + YGNodeRef singleFlexChild = nullptr; + const auto& children = node->getChildren(); + YGMeasureMode measureModeMainDim = + isRow(mainAxis) ? widthMeasureMode : heightMeasureMode; + // If there is only one child with flexGrow + flexShrink it means we can set + // the computedFlexBasis to 0 instead of measuring and shrinking / flexing the + // child to exactly match the remaining space + if (measureModeMainDim == YGMeasureModeExactly) { + for (auto child : children) { + if (child->isNodeFlexible()) { + if (singleFlexChild != nullptr || + yoga::inexactEquals(child->resolveFlexGrow(), 0.0f) || + yoga::inexactEquals(child->resolveFlexShrink(), 0.0f)) { + // There is already a flexible child, or this flexible child doesn't + // have flexGrow and flexShrink, abort + singleFlexChild = nullptr; + break; + } else { + singleFlexChild = child; + } + } + } + } + + for (auto child : children) { + child->resolveDimension(); + if (child->getStyle().display() == YGDisplayNone) { + zeroOutLayoutRecursively(child, layoutContext); + child->setHasNewLayout(true); + child->setDirty(false); + continue; + } + if (performLayout) { + // Set the initial position (relative to the owner). + const YGDirection childDirection = child->resolveDirection(direction); + const float mainDim = + isRow(mainAxis) ? availableInnerWidth : availableInnerHeight; + const float crossDim = + isRow(mainAxis) ? availableInnerHeight : availableInnerWidth; + child->setPosition( + childDirection, mainDim, crossDim, availableInnerWidth); + } + + if (child->getStyle().positionType() == YGPositionTypeAbsolute) { + continue; + } + if (child == singleFlexChild) { + child->setLayoutComputedFlexBasisGeneration(generationCount); + child->setLayoutComputedFlexBasis(FloatOptional(0)); + } else { + computeFlexBasisForChild( + node, + child, + availableInnerWidth, + widthMeasureMode, + availableInnerHeight, + availableInnerWidth, + availableInnerHeight, + heightMeasureMode, + direction, + config, + layoutMarkerData, + layoutContext, + depth, + generationCount); + } + + totalOuterFlexBasis += + (child->getLayout().computedFlexBasis + + child->getMarginForAxis(mainAxis, availableInnerWidth)) + .unwrap(); + } + + return totalOuterFlexBasis; +} + +// This function assumes that all the children of node have their +// computedFlexBasis properly computed(To do this use +// computeFlexBasisForChildren function). This function calculates +// YGCollectFlexItemsRowMeasurement +static CollectFlexItemsRowValues calculateCollectFlexItemsRowValues( + yoga::Node* const node, + const YGDirection ownerDirection, + const float mainAxisownerSize, + const float availableInnerWidth, + const float availableInnerMainDim, + const uint32_t startOfLineIndex, + const uint32_t lineCount) { + CollectFlexItemsRowValues flexAlgoRowMeasurement = {}; + flexAlgoRowMeasurement.relativeChildren.reserve(node->getChildren().size()); + + float sizeConsumedOnCurrentLineIncludingMinConstraint = 0; + const YGFlexDirection mainAxis = resolveDirection( + node->getStyle().flexDirection(), node->resolveDirection(ownerDirection)); + const bool isNodeFlexWrap = node->getStyle().flexWrap() != YGWrapNoWrap; + const float gap = node->getGapForAxis(mainAxis, availableInnerWidth).unwrap(); + + // Add items to the current line until it's full or we run out of items. + uint32_t endOfLineIndex = startOfLineIndex; + for (; endOfLineIndex < node->getChildren().size(); endOfLineIndex++) { + auto child = node->getChild(endOfLineIndex); + if (child->getStyle().display() == YGDisplayNone || + child->getStyle().positionType() == YGPositionTypeAbsolute) { + continue; + } + + const bool isFirstElementInLine = (endOfLineIndex - startOfLineIndex) == 0; + + child->setLineIndex(lineCount); + const float childMarginMainAxis = + child->getMarginForAxis(mainAxis, availableInnerWidth).unwrap(); + const float childLeadingGapMainAxis = isFirstElementInLine ? 0.0f : gap; + const float flexBasisWithMinAndMaxConstraints = + boundAxisWithinMinAndMax( + child, + mainAxis, + child->getLayout().computedFlexBasis, + mainAxisownerSize) + .unwrap(); + + // If this is a multi-line flow and this item pushes us over the available + // size, we've hit the end of the current line. Break out of the loop and + // lay out the current line. + if (sizeConsumedOnCurrentLineIncludingMinConstraint + + flexBasisWithMinAndMaxConstraints + childMarginMainAxis + + childLeadingGapMainAxis > + availableInnerMainDim && + isNodeFlexWrap && flexAlgoRowMeasurement.itemsOnLine > 0) { + break; + } + + sizeConsumedOnCurrentLineIncludingMinConstraint += + flexBasisWithMinAndMaxConstraints + childMarginMainAxis + + childLeadingGapMainAxis; + flexAlgoRowMeasurement.sizeConsumedOnCurrentLine += + flexBasisWithMinAndMaxConstraints + childMarginMainAxis + + childLeadingGapMainAxis; + flexAlgoRowMeasurement.itemsOnLine++; + + if (child->isNodeFlexible()) { + flexAlgoRowMeasurement.totalFlexGrowFactors += child->resolveFlexGrow(); + + // Unlike the grow factor, the shrink factor is scaled relative to the + // child dimension. + flexAlgoRowMeasurement.totalFlexShrinkScaledFactors += + -child->resolveFlexShrink() * + child->getLayout().computedFlexBasis.unwrap(); + } + + flexAlgoRowMeasurement.relativeChildren.push_back(child); + } + + // The total flex factor needs to be floored to 1. + if (flexAlgoRowMeasurement.totalFlexGrowFactors > 0 && + flexAlgoRowMeasurement.totalFlexGrowFactors < 1) { + flexAlgoRowMeasurement.totalFlexGrowFactors = 1; + } + + // The total flex shrink factor needs to be floored to 1. + if (flexAlgoRowMeasurement.totalFlexShrinkScaledFactors > 0 && + flexAlgoRowMeasurement.totalFlexShrinkScaledFactors < 1) { + flexAlgoRowMeasurement.totalFlexShrinkScaledFactors = 1; + } + flexAlgoRowMeasurement.endOfLineIndex = endOfLineIndex; + return flexAlgoRowMeasurement; +} + +// It distributes the free space to the flexible items and ensures that the size +// of the flex items abide the min and max constraints. At the end of this +// function the child nodes would have proper size. Prior using this function +// please ensure that distributeFreeSpaceFirstPass is called. +static float distributeFreeSpaceSecondPass( + CollectFlexItemsRowValues& collectedFlexItemsValues, + yoga::Node* const node, + const YGFlexDirection mainAxis, + const YGFlexDirection crossAxis, + const float mainAxisownerSize, + const float availableInnerMainDim, + const float availableInnerCrossDim, + const float availableInnerWidth, + const float availableInnerHeight, + const bool mainAxisOverflows, + const YGMeasureMode measureModeCrossDim, + const bool performLayout, + const yoga::Config* const config, + LayoutData& layoutMarkerData, + void* const layoutContext, + const uint32_t depth, + const uint32_t generationCount) { + float childFlexBasis = 0; + float flexShrinkScaledFactor = 0; + float flexGrowFactor = 0; + float deltaFreeSpace = 0; + const bool isMainAxisRow = isRow(mainAxis); + const bool isNodeFlexWrap = node->getStyle().flexWrap() != YGWrapNoWrap; + + for (auto currentRelativeChild : collectedFlexItemsValues.relativeChildren) { + childFlexBasis = boundAxisWithinMinAndMax( + currentRelativeChild, + mainAxis, + currentRelativeChild->getLayout().computedFlexBasis, + mainAxisownerSize) + .unwrap(); + float updatedMainSize = childFlexBasis; + + if (!yoga::isUndefined(collectedFlexItemsValues.remainingFreeSpace) && + collectedFlexItemsValues.remainingFreeSpace < 0) { + flexShrinkScaledFactor = + -currentRelativeChild->resolveFlexShrink() * childFlexBasis; + // Is this child able to shrink? + if (flexShrinkScaledFactor != 0) { + float childSize; + + if (!yoga::isUndefined( + collectedFlexItemsValues.totalFlexShrinkScaledFactors) && + collectedFlexItemsValues.totalFlexShrinkScaledFactors == 0) { + childSize = childFlexBasis + flexShrinkScaledFactor; + } else { + childSize = childFlexBasis + + (collectedFlexItemsValues.remainingFreeSpace / + collectedFlexItemsValues.totalFlexShrinkScaledFactors) * + flexShrinkScaledFactor; + } + + updatedMainSize = boundAxis( + currentRelativeChild, + mainAxis, + childSize, + availableInnerMainDim, + availableInnerWidth); + } + } else if ( + !yoga::isUndefined(collectedFlexItemsValues.remainingFreeSpace) && + collectedFlexItemsValues.remainingFreeSpace > 0) { + flexGrowFactor = currentRelativeChild->resolveFlexGrow(); + + // Is this child able to grow? + if (!std::isnan(flexGrowFactor) && flexGrowFactor != 0) { + updatedMainSize = boundAxis( + currentRelativeChild, + mainAxis, + childFlexBasis + + collectedFlexItemsValues.remainingFreeSpace / + collectedFlexItemsValues.totalFlexGrowFactors * + flexGrowFactor, + availableInnerMainDim, + availableInnerWidth); + } + } + + deltaFreeSpace += updatedMainSize - childFlexBasis; + + const float marginMain = + currentRelativeChild->getMarginForAxis(mainAxis, availableInnerWidth) + .unwrap(); + const float marginCross = + currentRelativeChild->getMarginForAxis(crossAxis, availableInnerWidth) + .unwrap(); + + float childCrossSize; + float childMainSize = updatedMainSize + marginMain; + YGMeasureMode childCrossMeasureMode; + YGMeasureMode childMainMeasureMode = YGMeasureModeExactly; + + const auto& childStyle = currentRelativeChild->getStyle(); + if (!childStyle.aspectRatio().isUndefined()) { + childCrossSize = isMainAxisRow + ? (childMainSize - marginMain) / childStyle.aspectRatio().unwrap() + : (childMainSize - marginMain) * childStyle.aspectRatio().unwrap(); + childCrossMeasureMode = YGMeasureModeExactly; + + childCrossSize += marginCross; + } else if ( + !std::isnan(availableInnerCrossDim) && + !styleDefinesDimension( + currentRelativeChild, crossAxis, availableInnerCrossDim) && + measureModeCrossDim == YGMeasureModeExactly && + !(isNodeFlexWrap && mainAxisOverflows) && + resolveChildAlignment(node, currentRelativeChild) == YGAlignStretch && + currentRelativeChild->marginLeadingValue(crossAxis).unit != + YGUnitAuto && + currentRelativeChild->marginTrailingValue(crossAxis).unit != + YGUnitAuto) { + childCrossSize = availableInnerCrossDim; + childCrossMeasureMode = YGMeasureModeExactly; + } else if (!styleDefinesDimension( + currentRelativeChild, crossAxis, availableInnerCrossDim)) { + childCrossSize = availableInnerCrossDim; + childCrossMeasureMode = yoga::isUndefined(childCrossSize) + ? YGMeasureModeUndefined + : YGMeasureModeAtMost; + } else { + childCrossSize = + yoga::resolveValue( + currentRelativeChild->getResolvedDimension(dim[crossAxis]), + availableInnerCrossDim) + .unwrap() + + marginCross; + const bool isLoosePercentageMeasurement = + currentRelativeChild->getResolvedDimension(dim[crossAxis]).unit == + YGUnitPercent && + measureModeCrossDim != YGMeasureModeExactly; + childCrossMeasureMode = + yoga::isUndefined(childCrossSize) || isLoosePercentageMeasurement + ? YGMeasureModeUndefined + : YGMeasureModeExactly; + } + + constrainMaxSizeForMode( + currentRelativeChild, + mainAxis, + availableInnerMainDim, + availableInnerWidth, + &childMainMeasureMode, + &childMainSize); + constrainMaxSizeForMode( + currentRelativeChild, + crossAxis, + availableInnerCrossDim, + availableInnerWidth, + &childCrossMeasureMode, + &childCrossSize); + + const bool requiresStretchLayout = + !styleDefinesDimension( + currentRelativeChild, crossAxis, availableInnerCrossDim) && + resolveChildAlignment(node, currentRelativeChild) == YGAlignStretch && + currentRelativeChild->marginLeadingValue(crossAxis).unit != + YGUnitAuto && + currentRelativeChild->marginTrailingValue(crossAxis).unit != YGUnitAuto; + + const float childWidth = isMainAxisRow ? childMainSize : childCrossSize; + const float childHeight = !isMainAxisRow ? childMainSize : childCrossSize; + + const YGMeasureMode childWidthMeasureMode = + isMainAxisRow ? childMainMeasureMode : childCrossMeasureMode; + const YGMeasureMode childHeightMeasureMode = + !isMainAxisRow ? childMainMeasureMode : childCrossMeasureMode; + + const bool isLayoutPass = performLayout && !requiresStretchLayout; + // Recursively call the layout algorithm for this child with the updated + // main size. + calculateLayoutInternal( + currentRelativeChild, + childWidth, + childHeight, + node->getLayout().direction(), + childWidthMeasureMode, + childHeightMeasureMode, + availableInnerWidth, + availableInnerHeight, + isLayoutPass, + isLayoutPass ? LayoutPassReason::kFlexLayout + : LayoutPassReason::kFlexMeasure, + config, + layoutMarkerData, + layoutContext, + depth, + generationCount); + node->setLayoutHadOverflow( + node->getLayout().hadOverflow() || + currentRelativeChild->getLayout().hadOverflow()); + } + return deltaFreeSpace; +} + +// It distributes the free space to the flexible items.For those flexible items +// whose min and max constraints are triggered, those flex item's clamped size +// is removed from the remaingfreespace. +static void distributeFreeSpaceFirstPass( + CollectFlexItemsRowValues& collectedFlexItemsValues, + const YGFlexDirection mainAxis, + const float mainAxisownerSize, + const float availableInnerMainDim, + const float availableInnerWidth) { + float flexShrinkScaledFactor = 0; + float flexGrowFactor = 0; + float baseMainSize = 0; + float boundMainSize = 0; + float deltaFreeSpace = 0; + + for (auto currentRelativeChild : collectedFlexItemsValues.relativeChildren) { + float childFlexBasis = + boundAxisWithinMinAndMax( + currentRelativeChild, + mainAxis, + currentRelativeChild->getLayout().computedFlexBasis, + mainAxisownerSize) + .unwrap(); + + if (collectedFlexItemsValues.remainingFreeSpace < 0) { + flexShrinkScaledFactor = + -currentRelativeChild->resolveFlexShrink() * childFlexBasis; + + // Is this child able to shrink? + if (!yoga::isUndefined(flexShrinkScaledFactor) && + flexShrinkScaledFactor != 0) { + baseMainSize = childFlexBasis + + collectedFlexItemsValues.remainingFreeSpace / + collectedFlexItemsValues.totalFlexShrinkScaledFactors * + flexShrinkScaledFactor; + boundMainSize = boundAxis( + currentRelativeChild, + mainAxis, + baseMainSize, + availableInnerMainDim, + availableInnerWidth); + if (!yoga::isUndefined(baseMainSize) && + !yoga::isUndefined(boundMainSize) && + baseMainSize != boundMainSize) { + // By excluding this item's size and flex factor from remaining, this + // item's min/max constraints should also trigger in the second pass + // resulting in the item's size calculation being identical in the + // first and second passes. + deltaFreeSpace += boundMainSize - childFlexBasis; + collectedFlexItemsValues.totalFlexShrinkScaledFactors -= + (-currentRelativeChild->resolveFlexShrink() * + currentRelativeChild->getLayout().computedFlexBasis.unwrap()); + } + } + } else if ( + !yoga::isUndefined(collectedFlexItemsValues.remainingFreeSpace) && + collectedFlexItemsValues.remainingFreeSpace > 0) { + flexGrowFactor = currentRelativeChild->resolveFlexGrow(); + + // Is this child able to grow? + if (!yoga::isUndefined(flexGrowFactor) && flexGrowFactor != 0) { + baseMainSize = childFlexBasis + + collectedFlexItemsValues.remainingFreeSpace / + collectedFlexItemsValues.totalFlexGrowFactors * flexGrowFactor; + boundMainSize = boundAxis( + currentRelativeChild, + mainAxis, + baseMainSize, + availableInnerMainDim, + availableInnerWidth); + + if (!yoga::isUndefined(baseMainSize) && + !yoga::isUndefined(boundMainSize) && + baseMainSize != boundMainSize) { + // By excluding this item's size and flex factor from remaining, this + // item's min/max constraints should also trigger in the second pass + // resulting in the item's size calculation being identical in the + // first and second passes. + deltaFreeSpace += boundMainSize - childFlexBasis; + collectedFlexItemsValues.totalFlexGrowFactors -= flexGrowFactor; + } + } + } + } + collectedFlexItemsValues.remainingFreeSpace -= deltaFreeSpace; +} + +// Do two passes over the flex items to figure out how to distribute the +// remaining space. +// +// The first pass finds the items whose min/max constraints trigger, freezes +// them at those sizes, and excludes those sizes from the remaining space. +// +// The second pass sets the size of each flexible item. It distributes the +// remaining space amongst the items whose min/max constraints didn't trigger in +// the first pass. For the other items, it sets their sizes by forcing their +// min/max constraints to trigger again. +// +// This two pass approach for resolving min/max constraints deviates from the +// spec. The spec +// (https://www.w3.org/TR/CSS-flexbox-1/#resolve-flexible-lengths) describes a +// process that needs to be repeated a variable number of times. The algorithm +// implemented here won't handle all cases but it was simpler to implement and +// it mitigates performance concerns because we know exactly how many passes +// it'll do. +// +// At the end of this function the child nodes would have the proper size +// assigned to them. +// +static void resolveFlexibleLength( + yoga::Node* const node, + CollectFlexItemsRowValues& collectedFlexItemsValues, + const YGFlexDirection mainAxis, + const YGFlexDirection crossAxis, + const float mainAxisownerSize, + const float availableInnerMainDim, + const float availableInnerCrossDim, + const float availableInnerWidth, + const float availableInnerHeight, + const bool mainAxisOverflows, + const YGMeasureMode measureModeCrossDim, + const bool performLayout, + const yoga::Config* const config, + LayoutData& layoutMarkerData, + void* const layoutContext, + const uint32_t depth, + const uint32_t generationCount) { + const float originalFreeSpace = collectedFlexItemsValues.remainingFreeSpace; + // First pass: detect the flex items whose min/max constraints trigger + distributeFreeSpaceFirstPass( + collectedFlexItemsValues, + mainAxis, + mainAxisownerSize, + availableInnerMainDim, + availableInnerWidth); + + // Second pass: resolve the sizes of the flexible items + const float distributedFreeSpace = distributeFreeSpaceSecondPass( + collectedFlexItemsValues, + node, + mainAxis, + crossAxis, + mainAxisownerSize, + availableInnerMainDim, + availableInnerCrossDim, + availableInnerWidth, + availableInnerHeight, + mainAxisOverflows, + measureModeCrossDim, + performLayout, + config, + layoutMarkerData, + layoutContext, + depth, + generationCount); + + collectedFlexItemsValues.remainingFreeSpace = + originalFreeSpace - distributedFreeSpace; +} + +static void YGJustifyMainAxis( + yoga::Node* const node, + CollectFlexItemsRowValues& collectedFlexItemsValues, + const uint32_t startOfLineIndex, + const YGFlexDirection mainAxis, + const YGFlexDirection crossAxis, + const YGMeasureMode measureModeMainDim, + const YGMeasureMode measureModeCrossDim, + const float mainAxisownerSize, + const float ownerWidth, + const float availableInnerMainDim, + const float availableInnerCrossDim, + const float availableInnerWidth, + const bool performLayout, + void* const layoutContext) { + const auto& style = node->getStyle(); + const float leadingPaddingAndBorderMain = + node->getLeadingPaddingAndBorder(mainAxis, ownerWidth).unwrap(); + const float trailingPaddingAndBorderMain = + node->getTrailingPaddingAndBorder(mainAxis, ownerWidth).unwrap(); + const float gap = node->getGapForAxis(mainAxis, ownerWidth).unwrap(); + // If we are using "at most" rules in the main axis, make sure that + // remainingFreeSpace is 0 when min main dimension is not given + if (measureModeMainDim == YGMeasureModeAtMost && + collectedFlexItemsValues.remainingFreeSpace > 0) { + if (!style.minDimensions()[dim[mainAxis]].isUndefined() && + !yoga::resolveValue( + style.minDimensions()[dim[mainAxis]], mainAxisownerSize) + .isUndefined()) { + // This condition makes sure that if the size of main dimension(after + // considering child nodes main dim, leading and trailing padding etc) + // falls below min dimension, then the remainingFreeSpace is reassigned + // considering the min dimension + + // `minAvailableMainDim` denotes minimum available space in which child + // can be laid out, it will exclude space consumed by padding and border. + const float minAvailableMainDim = + yoga::resolveValue( + style.minDimensions()[dim[mainAxis]], mainAxisownerSize) + .unwrap() - + leadingPaddingAndBorderMain - trailingPaddingAndBorderMain; + const float occupiedSpaceByChildNodes = + availableInnerMainDim - collectedFlexItemsValues.remainingFreeSpace; + collectedFlexItemsValues.remainingFreeSpace = yoga::maxOrDefined( + 0, minAvailableMainDim - occupiedSpaceByChildNodes); + } else { + collectedFlexItemsValues.remainingFreeSpace = 0; + } + } + + int numberOfAutoMarginsOnCurrentLine = 0; + for (uint32_t i = startOfLineIndex; + i < collectedFlexItemsValues.endOfLineIndex; + i++) { + auto child = node->getChild(i); + if (child->getStyle().positionType() != YGPositionTypeAbsolute) { + if (child->marginLeadingValue(mainAxis).unit == YGUnitAuto) { + numberOfAutoMarginsOnCurrentLine++; + } + if (child->marginTrailingValue(mainAxis).unit == YGUnitAuto) { + numberOfAutoMarginsOnCurrentLine++; + } + } + } + + // In order to position the elements in the main axis, we have two controls. + // The space between the beginning and the first element and the space between + // each two elements. + float leadingMainDim = 0; + float betweenMainDim = gap; + const YGJustify justifyContent = node->getStyle().justifyContent(); + + if (numberOfAutoMarginsOnCurrentLine == 0) { + switch (justifyContent) { + case YGJustifyCenter: + leadingMainDim = collectedFlexItemsValues.remainingFreeSpace / 2; + break; + case YGJustifyFlexEnd: + leadingMainDim = collectedFlexItemsValues.remainingFreeSpace; + break; + case YGJustifySpaceBetween: + if (collectedFlexItemsValues.itemsOnLine > 1) { + betweenMainDim += + yoga::maxOrDefined( + collectedFlexItemsValues.remainingFreeSpace, 0) / + (collectedFlexItemsValues.itemsOnLine - 1); + } + break; + case YGJustifySpaceEvenly: + // Space is distributed evenly across all elements + leadingMainDim = collectedFlexItemsValues.remainingFreeSpace / + (collectedFlexItemsValues.itemsOnLine + 1); + betweenMainDim += leadingMainDim; + break; + case YGJustifySpaceAround: + // Space on the edges is half of the space between elements + leadingMainDim = 0.5f * collectedFlexItemsValues.remainingFreeSpace / + collectedFlexItemsValues.itemsOnLine; + betweenMainDim += leadingMainDim * 2; + break; + case YGJustifyFlexStart: + break; + } + } + + collectedFlexItemsValues.mainDim = + leadingPaddingAndBorderMain + leadingMainDim; + collectedFlexItemsValues.crossDim = 0; + + float maxAscentForCurrentLine = 0; + float maxDescentForCurrentLine = 0; + bool isNodeBaselineLayout = isBaselineLayout(node); + for (uint32_t i = startOfLineIndex; + i < collectedFlexItemsValues.endOfLineIndex; + i++) { + const auto child = node->getChild(i); + const Style& childStyle = child->getStyle(); + const LayoutResults& childLayout = child->getLayout(); + const bool isLastChild = i == collectedFlexItemsValues.endOfLineIndex - 1; + // remove the gap if it is the last element of the line + if (isLastChild) { + betweenMainDim -= gap; + } + if (childStyle.display() == YGDisplayNone) { + continue; + } + if (childStyle.positionType() == YGPositionTypeAbsolute && + child->isLeadingPositionDefined(mainAxis)) { + if (performLayout) { + // In case the child is position absolute and has left/top being + // defined, we override the position to whatever the user said (and + // margin/border). + child->setLayoutPosition( + child->getLeadingPosition(mainAxis, availableInnerMainDim) + .unwrap() + + node->getLeadingBorder(mainAxis) + + child->getLeadingMargin(mainAxis, availableInnerWidth).unwrap(), + pos[mainAxis]); + } + } else { + // Now that we placed the element, we need to update the variables. + // We need to do that only for relative elements. Absolute elements do not + // take part in that phase. + if (childStyle.positionType() != YGPositionTypeAbsolute) { + if (child->marginLeadingValue(mainAxis).unit == YGUnitAuto) { + collectedFlexItemsValues.mainDim += + collectedFlexItemsValues.remainingFreeSpace / + numberOfAutoMarginsOnCurrentLine; + } + + if (performLayout) { + child->setLayoutPosition( + childLayout.position[pos[mainAxis]] + + collectedFlexItemsValues.mainDim, + pos[mainAxis]); + } + + if (child->marginTrailingValue(mainAxis).unit == YGUnitAuto) { + collectedFlexItemsValues.mainDim += + collectedFlexItemsValues.remainingFreeSpace / + numberOfAutoMarginsOnCurrentLine; + } + bool canSkipFlex = + !performLayout && measureModeCrossDim == YGMeasureModeExactly; + if (canSkipFlex) { + // If we skipped the flex step, then we can't rely on the measuredDims + // because they weren't computed. This means we can't call + // dimensionWithMargin. + collectedFlexItemsValues.mainDim += betweenMainDim + + child->getMarginForAxis(mainAxis, availableInnerWidth).unwrap() + + childLayout.computedFlexBasis.unwrap(); + collectedFlexItemsValues.crossDim = availableInnerCrossDim; + } else { + // The main dimension is the sum of all the elements dimension plus + // the spacing. + collectedFlexItemsValues.mainDim += betweenMainDim + + dimensionWithMargin(child, mainAxis, availableInnerWidth); + + if (isNodeBaselineLayout) { + // If the child is baseline aligned then the cross dimension is + // calculated by adding maxAscent and maxDescent from the baseline. + const float ascent = calculateBaseline(child, layoutContext) + + child + ->getLeadingMargin( + YGFlexDirectionColumn, availableInnerWidth) + .unwrap(); + const float descent = + child->getLayout().measuredDimensions[YGDimensionHeight] + + child + ->getMarginForAxis( + YGFlexDirectionColumn, availableInnerWidth) + .unwrap() - + ascent; + + maxAscentForCurrentLine = + yoga::maxOrDefined(maxAscentForCurrentLine, ascent); + maxDescentForCurrentLine = + yoga::maxOrDefined(maxDescentForCurrentLine, descent); + } else { + // The cross dimension is the max of the elements dimension since + // there can only be one element in that cross dimension in the case + // when the items are not baseline aligned + collectedFlexItemsValues.crossDim = yoga::maxOrDefined( + collectedFlexItemsValues.crossDim, + dimensionWithMargin(child, crossAxis, availableInnerWidth)); + } + } + } else if (performLayout) { + child->setLayoutPosition( + childLayout.position[pos[mainAxis]] + + node->getLeadingBorder(mainAxis) + leadingMainDim, + pos[mainAxis]); + } + } + } + collectedFlexItemsValues.mainDim += trailingPaddingAndBorderMain; + + if (isNodeBaselineLayout) { + collectedFlexItemsValues.crossDim = + maxAscentForCurrentLine + maxDescentForCurrentLine; + } +} + +// +// This is the main routine that implements a subset of the flexbox layout +// algorithm described in the W3C CSS documentation: +// https://www.w3.org/TR/CSS3-flexbox/. +// +// Limitations of this algorithm, compared to the full standard: +// * Display property is always assumed to be 'flex' except for Text nodes, +// which are assumed to be 'inline-flex'. +// * The 'zIndex' property (or any form of z ordering) is not supported. Nodes +// are stacked in document order. +// * The 'order' property is not supported. The order of flex items is always +// defined by document order. +// * The 'visibility' property is always assumed to be 'visible'. Values of +// 'collapse' and 'hidden' are not supported. +// * There is no support for forced breaks. +// * It does not support vertical inline directions (top-to-bottom or +// bottom-to-top text). +// +// Deviations from standard: +// * Section 4.5 of the spec indicates that all flex items have a default +// minimum main size. For text blocks, for example, this is the width of the +// widest word. Calculating the minimum width is expensive, so we forego it +// and assume a default minimum main size of 0. +// * Min/Max sizes in the main axis are not honored when resolving flexible +// lengths. +// * The spec indicates that the default value for 'flexDirection' is 'row', +// but the algorithm below assumes a default of 'column'. +// +// Input parameters: +// - node: current node to be sized and laid out +// - availableWidth & availableHeight: available size to be used for sizing +// the node or YGUndefined if the size is not available; interpretation +// depends on layout flags +// - ownerDirection: the inline (text) direction within the owner +// (left-to-right or right-to-left) +// - widthMeasureMode: indicates the sizing rules for the width (see below +// for explanation) +// - heightMeasureMode: indicates the sizing rules for the height (see below +// for explanation) +// - performLayout: specifies whether the caller is interested in just the +// dimensions of the node or it requires the entire node and its subtree to +// be laid out (with final positions) +// +// Details: +// This routine is called recursively to lay out subtrees of flexbox +// elements. It uses the information in node.style, which is treated as a +// read-only input. It is responsible for setting the layout.direction and +// layout.measuredDimensions fields for the input node as well as the +// layout.position and layout.lineIndex fields for its child nodes. The +// layout.measuredDimensions field includes any border or padding for the +// node but does not include margins. +// +// The spec describes four different layout modes: "fill available", "max +// content", "min content", and "fit content". Of these, we don't use "min +// content" because we don't support default minimum main sizes (see above +// for details). Each of our measure modes maps to a layout mode from the +// spec (https://www.w3.org/TR/CSS3-sizing/#terms): +// - YGMeasureModeUndefined: max content +// - YGMeasureModeExactly: fill available +// - YGMeasureModeAtMost: fit content +// +// When calling calculateLayoutImpl and calculateLayoutInternal, if the +// caller passes an available size of undefined then it must also pass a +// measure mode of YGMeasureModeUndefined in that dimension. +// +static void calculateLayoutImpl( + yoga::Node* const node, + const float availableWidth, + const float availableHeight, + const YGDirection ownerDirection, + const YGMeasureMode widthMeasureMode, + const YGMeasureMode heightMeasureMode, + const float ownerWidth, + const float ownerHeight, + const bool performLayout, + const yoga::Config* const config, + LayoutData& layoutMarkerData, + void* const layoutContext, + const uint32_t depth, + const uint32_t generationCount, + const LayoutPassReason reason) { + yoga::assertFatalWithNode( + node, + yoga::isUndefined(availableWidth) + ? widthMeasureMode == YGMeasureModeUndefined + : true, + "availableWidth is indefinite so widthMeasureMode must be " + "YGMeasureModeUndefined"); + yoga::assertFatalWithNode( + node, + yoga::isUndefined(availableHeight) + ? heightMeasureMode == YGMeasureModeUndefined + : true, + "availableHeight is indefinite so heightMeasureMode must be " + "YGMeasureModeUndefined"); + + (performLayout ? layoutMarkerData.layouts : layoutMarkerData.measures) += 1; + + // Set the resolved resolution in the node's layout. + const YGDirection direction = node->resolveDirection(ownerDirection); + node->setLayoutDirection(direction); + + const YGFlexDirection flexRowDirection = + resolveDirection(YGFlexDirectionRow, direction); + const YGFlexDirection flexColumnDirection = + resolveDirection(YGFlexDirectionColumn, direction); + + const YGEdge startEdge = + direction == YGDirectionLTR ? YGEdgeLeft : YGEdgeRight; + const YGEdge endEdge = direction == YGDirectionLTR ? YGEdgeRight : YGEdgeLeft; + + const float marginRowLeading = + node->getLeadingMargin(flexRowDirection, ownerWidth).unwrap(); + node->setLayoutMargin(marginRowLeading, startEdge); + const float marginRowTrailing = + node->getTrailingMargin(flexRowDirection, ownerWidth).unwrap(); + node->setLayoutMargin(marginRowTrailing, endEdge); + const float marginColumnLeading = + node->getLeadingMargin(flexColumnDirection, ownerWidth).unwrap(); + node->setLayoutMargin(marginColumnLeading, YGEdgeTop); + const float marginColumnTrailing = + node->getTrailingMargin(flexColumnDirection, ownerWidth).unwrap(); + node->setLayoutMargin(marginColumnTrailing, YGEdgeBottom); + + const float marginAxisRow = marginRowLeading + marginRowTrailing; + const float marginAxisColumn = marginColumnLeading + marginColumnTrailing; + + node->setLayoutBorder(node->getLeadingBorder(flexRowDirection), startEdge); + node->setLayoutBorder(node->getTrailingBorder(flexRowDirection), endEdge); + node->setLayoutBorder(node->getLeadingBorder(flexColumnDirection), YGEdgeTop); + node->setLayoutBorder( + node->getTrailingBorder(flexColumnDirection), YGEdgeBottom); + + node->setLayoutPadding( + node->getLeadingPadding(flexRowDirection, ownerWidth).unwrap(), + startEdge); + node->setLayoutPadding( + node->getTrailingPadding(flexRowDirection, ownerWidth).unwrap(), endEdge); + node->setLayoutPadding( + node->getLeadingPadding(flexColumnDirection, ownerWidth).unwrap(), + YGEdgeTop); + node->setLayoutPadding( + node->getTrailingPadding(flexColumnDirection, ownerWidth).unwrap(), + YGEdgeBottom); + + if (node->hasMeasureFunc()) { + measureNodeWithMeasureFunc( + node, + availableWidth - marginAxisRow, + availableHeight - marginAxisColumn, + widthMeasureMode, + heightMeasureMode, + ownerWidth, + ownerHeight, + layoutMarkerData, + layoutContext, + reason); + return; + } + + const auto childCount = node->getChildCount(); + if (childCount == 0) { + measureNodeWithoutChildren( + node, + availableWidth - marginAxisRow, + availableHeight - marginAxisColumn, + widthMeasureMode, + heightMeasureMode, + ownerWidth, + ownerHeight); + return; + } + + // If we're not being asked to perform a full layout we can skip the algorithm + // if we already know the size + if (!performLayout && + measureNodeWithFixedSize( + node, + availableWidth - marginAxisRow, + availableHeight - marginAxisColumn, + widthMeasureMode, + heightMeasureMode, + ownerWidth, + ownerHeight)) { + return; + } + + // At this point we know we're going to perform work. Ensure that each child + // has a mutable copy. + node->cloneChildrenIfNeeded(layoutContext); + // Reset layout flags, as they could have changed. + node->setLayoutHadOverflow(false); + + // STEP 1: CALCULATE VALUES FOR REMAINDER OF ALGORITHM + const YGFlexDirection mainAxis = + resolveDirection(node->getStyle().flexDirection(), direction); + const YGFlexDirection crossAxis = resolveCrossDirection(mainAxis, direction); + const bool isMainAxisRow = isRow(mainAxis); + const bool isNodeFlexWrap = node->getStyle().flexWrap() != YGWrapNoWrap; + + const float mainAxisownerSize = isMainAxisRow ? ownerWidth : ownerHeight; + const float crossAxisownerSize = isMainAxisRow ? ownerHeight : ownerWidth; + + const float paddingAndBorderAxisMain = + paddingAndBorderForAxis(node, mainAxis, ownerWidth); + const float leadingPaddingAndBorderCross = + node->getLeadingPaddingAndBorder(crossAxis, ownerWidth).unwrap(); + const float trailingPaddingAndBorderCross = + node->getTrailingPaddingAndBorder(crossAxis, ownerWidth).unwrap(); + const float paddingAndBorderAxisCross = + leadingPaddingAndBorderCross + trailingPaddingAndBorderCross; + + YGMeasureMode measureModeMainDim = + isMainAxisRow ? widthMeasureMode : heightMeasureMode; + YGMeasureMode measureModeCrossDim = + isMainAxisRow ? heightMeasureMode : widthMeasureMode; + + const float paddingAndBorderAxisRow = + isMainAxisRow ? paddingAndBorderAxisMain : paddingAndBorderAxisCross; + const float paddingAndBorderAxisColumn = + isMainAxisRow ? paddingAndBorderAxisCross : paddingAndBorderAxisMain; + + // STEP 2: DETERMINE AVAILABLE SIZE IN MAIN AND CROSS DIRECTIONS + + float availableInnerWidth = calculateAvailableInnerDimension( + node, + YGDimensionWidth, + availableWidth - marginAxisRow, + paddingAndBorderAxisRow, + ownerWidth); + float availableInnerHeight = calculateAvailableInnerDimension( + node, + YGDimensionHeight, + availableHeight - marginAxisColumn, + paddingAndBorderAxisColumn, + ownerHeight); + + float availableInnerMainDim = + isMainAxisRow ? availableInnerWidth : availableInnerHeight; + const float availableInnerCrossDim = + isMainAxisRow ? availableInnerHeight : availableInnerWidth; + + // STEP 3: DETERMINE FLEX BASIS FOR EACH ITEM + + // Computed basis + margins + gap + float totalMainDim = 0; + totalMainDim += computeFlexBasisForChildren( + node, + availableInnerWidth, + availableInnerHeight, + widthMeasureMode, + heightMeasureMode, + direction, + mainAxis, + config, + performLayout, + layoutMarkerData, + layoutContext, + depth, + generationCount); + + if (childCount > 1) { + totalMainDim += + node->getGapForAxis(mainAxis, availableInnerCrossDim).unwrap() * + (childCount - 1); + } + + const bool mainAxisOverflows = + (measureModeMainDim != YGMeasureModeUndefined) && + totalMainDim > availableInnerMainDim; + + if (isNodeFlexWrap && mainAxisOverflows && + measureModeMainDim == YGMeasureModeAtMost) { + measureModeMainDim = YGMeasureModeExactly; + } + // STEP 4: COLLECT FLEX ITEMS INTO FLEX LINES + + // Indexes of children that represent the first and last items in the line. + uint32_t startOfLineIndex = 0; + uint32_t endOfLineIndex = 0; + + // Number of lines. + uint32_t lineCount = 0; + + // Accumulated cross dimensions of all lines so far. + float totalLineCrossDim = 0; + + const float crossAxisGap = + node->getGapForAxis(crossAxis, availableInnerCrossDim).unwrap(); + + // Max main dimension of all the lines. + float maxLineMainDim = 0; + CollectFlexItemsRowValues collectedFlexItemsValues; + for (; endOfLineIndex < childCount; + lineCount++, startOfLineIndex = endOfLineIndex) { + collectedFlexItemsValues = calculateCollectFlexItemsRowValues( + node, + ownerDirection, + mainAxisownerSize, + availableInnerWidth, + availableInnerMainDim, + startOfLineIndex, + lineCount); + endOfLineIndex = collectedFlexItemsValues.endOfLineIndex; + + // If we don't need to measure the cross axis, we can skip the entire flex + // step. + const bool canSkipFlex = + !performLayout && measureModeCrossDim == YGMeasureModeExactly; + + // STEP 5: RESOLVING FLEXIBLE LENGTHS ON MAIN AXIS + // Calculate the remaining available space that needs to be allocated. If + // the main dimension size isn't known, it is computed based on the line + // length, so there's no more space left to distribute. + + bool sizeBasedOnContent = false; + // If we don't measure with exact main dimension we want to ensure we don't + // violate min and max + if (measureModeMainDim != YGMeasureModeExactly) { + const auto& minDimensions = node->getStyle().minDimensions(); + const auto& maxDimensions = node->getStyle().maxDimensions(); + const float minInnerWidth = + yoga::resolveValue(minDimensions[YGDimensionWidth], ownerWidth) + .unwrap() - + paddingAndBorderAxisRow; + const float maxInnerWidth = + yoga::resolveValue(maxDimensions[YGDimensionWidth], ownerWidth) + .unwrap() - + paddingAndBorderAxisRow; + const float minInnerHeight = + yoga::resolveValue(minDimensions[YGDimensionHeight], ownerHeight) + .unwrap() - + paddingAndBorderAxisColumn; + const float maxInnerHeight = + yoga::resolveValue(maxDimensions[YGDimensionHeight], ownerHeight) + .unwrap() - + paddingAndBorderAxisColumn; + + const float minInnerMainDim = + isMainAxisRow ? minInnerWidth : minInnerHeight; + const float maxInnerMainDim = + isMainAxisRow ? maxInnerWidth : maxInnerHeight; + + if (!yoga::isUndefined(minInnerMainDim) && + collectedFlexItemsValues.sizeConsumedOnCurrentLine < + minInnerMainDim) { + availableInnerMainDim = minInnerMainDim; + } else if ( + !yoga::isUndefined(maxInnerMainDim) && + collectedFlexItemsValues.sizeConsumedOnCurrentLine > + maxInnerMainDim) { + availableInnerMainDim = maxInnerMainDim; + } else { + bool useLegacyStretchBehaviour = + node->hasErrata(YGErrataStretchFlexBasis); + + if (!useLegacyStretchBehaviour && + ((!yoga::isUndefined( + collectedFlexItemsValues.totalFlexGrowFactors) && + collectedFlexItemsValues.totalFlexGrowFactors == 0) || + (!yoga::isUndefined(node->resolveFlexGrow()) && + node->resolveFlexGrow() == 0))) { + // If we don't have any children to flex or we can't flex the node + // itself, space we've used is all space we need. Root node also + // should be shrunk to minimum + availableInnerMainDim = + collectedFlexItemsValues.sizeConsumedOnCurrentLine; + } + + sizeBasedOnContent = !useLegacyStretchBehaviour; + } + } + + if (!sizeBasedOnContent && !yoga::isUndefined(availableInnerMainDim)) { + collectedFlexItemsValues.remainingFreeSpace = availableInnerMainDim - + collectedFlexItemsValues.sizeConsumedOnCurrentLine; + } else if (collectedFlexItemsValues.sizeConsumedOnCurrentLine < 0) { + // availableInnerMainDim is indefinite which means the node is being sized + // based on its content. sizeConsumedOnCurrentLine is negative which means + // the node will allocate 0 points for its content. Consequently, + // remainingFreeSpace is 0 - sizeConsumedOnCurrentLine. + collectedFlexItemsValues.remainingFreeSpace = + -collectedFlexItemsValues.sizeConsumedOnCurrentLine; + } + + if (!canSkipFlex) { + resolveFlexibleLength( + node, + collectedFlexItemsValues, + mainAxis, + crossAxis, + mainAxisownerSize, + availableInnerMainDim, + availableInnerCrossDim, + availableInnerWidth, + availableInnerHeight, + mainAxisOverflows, + measureModeCrossDim, + performLayout, + config, + layoutMarkerData, + layoutContext, + depth, + generationCount); + } + + node->setLayoutHadOverflow( + node->getLayout().hadOverflow() | + (collectedFlexItemsValues.remainingFreeSpace < 0)); + + // STEP 6: MAIN-AXIS JUSTIFICATION & CROSS-AXIS SIZE DETERMINATION + + // At this point, all the children have their dimensions set in the main + // axis. Their dimensions are also set in the cross axis with the exception + // of items that are aligned "stretch". We need to compute these stretch + // values and set the final positions. + + YGJustifyMainAxis( + node, + collectedFlexItemsValues, + startOfLineIndex, + mainAxis, + crossAxis, + measureModeMainDim, + measureModeCrossDim, + mainAxisownerSize, + ownerWidth, + availableInnerMainDim, + availableInnerCrossDim, + availableInnerWidth, + performLayout, + layoutContext); + + float containerCrossAxis = availableInnerCrossDim; + if (measureModeCrossDim == YGMeasureModeUndefined || + measureModeCrossDim == YGMeasureModeAtMost) { + // Compute the cross axis from the max cross dimension of the children. + containerCrossAxis = + boundAxis( + node, + crossAxis, + collectedFlexItemsValues.crossDim + paddingAndBorderAxisCross, + crossAxisownerSize, + ownerWidth) - + paddingAndBorderAxisCross; + } + + // If there's no flex wrap, the cross dimension is defined by the container. + if (!isNodeFlexWrap && measureModeCrossDim == YGMeasureModeExactly) { + collectedFlexItemsValues.crossDim = availableInnerCrossDim; + } + + // Clamp to the min/max size specified on the container. + collectedFlexItemsValues.crossDim = + boundAxis( + node, + crossAxis, + collectedFlexItemsValues.crossDim + paddingAndBorderAxisCross, + crossAxisownerSize, + ownerWidth) - + paddingAndBorderAxisCross; + + // STEP 7: CROSS-AXIS ALIGNMENT + // We can skip child alignment if we're just measuring the container. + if (performLayout) { + for (uint32_t i = startOfLineIndex; i < endOfLineIndex; i++) { + const auto child = node->getChild(i); + if (child->getStyle().display() == YGDisplayNone) { + continue; + } + if (child->getStyle().positionType() == YGPositionTypeAbsolute) { + // If the child is absolutely positioned and has a + // top/left/bottom/right set, override all the previously computed + // positions to set it correctly. + const bool isChildLeadingPosDefined = + child->isLeadingPositionDefined(crossAxis); + if (isChildLeadingPosDefined) { + child->setLayoutPosition( + child->getLeadingPosition(crossAxis, availableInnerCrossDim) + .unwrap() + + node->getLeadingBorder(crossAxis) + + child->getLeadingMargin(crossAxis, availableInnerWidth) + .unwrap(), + pos[crossAxis]); + } + // If leading position is not defined or calculations result in Nan, + // default to border + margin + if (!isChildLeadingPosDefined || + yoga::isUndefined(child->getLayout().position[pos[crossAxis]])) { + child->setLayoutPosition( + node->getLeadingBorder(crossAxis) + + child->getLeadingMargin(crossAxis, availableInnerWidth) + .unwrap(), + pos[crossAxis]); + } + } else { + float leadingCrossDim = leadingPaddingAndBorderCross; + + // For a relative children, we're either using alignItems (owner) or + // alignSelf (child) in order to determine the position in the cross + // axis + const YGAlign alignItem = resolveChildAlignment(node, child); + + // If the child uses align stretch, we need to lay it out one more + // time, this time forcing the cross-axis size to be the computed + // cross size for the current line. + if (alignItem == YGAlignStretch && + child->marginLeadingValue(crossAxis).unit != YGUnitAuto && + child->marginTrailingValue(crossAxis).unit != YGUnitAuto) { + // If the child defines a definite size for its cross axis, there's + // no need to stretch. + if (!styleDefinesDimension( + child, crossAxis, availableInnerCrossDim)) { + float childMainSize = + child->getLayout().measuredDimensions[dim[mainAxis]]; + const auto& childStyle = child->getStyle(); + float childCrossSize = !childStyle.aspectRatio().isUndefined() + ? child->getMarginForAxis(crossAxis, availableInnerWidth) + .unwrap() + + (isMainAxisRow + ? childMainSize / childStyle.aspectRatio().unwrap() + : childMainSize * childStyle.aspectRatio().unwrap()) + : collectedFlexItemsValues.crossDim; + + childMainSize += + child->getMarginForAxis(mainAxis, availableInnerWidth) + .unwrap(); + + YGMeasureMode childMainMeasureMode = YGMeasureModeExactly; + YGMeasureMode childCrossMeasureMode = YGMeasureModeExactly; + constrainMaxSizeForMode( + child, + mainAxis, + availableInnerMainDim, + availableInnerWidth, + &childMainMeasureMode, + &childMainSize); + constrainMaxSizeForMode( + child, + crossAxis, + availableInnerCrossDim, + availableInnerWidth, + &childCrossMeasureMode, + &childCrossSize); + + const float childWidth = + isMainAxisRow ? childMainSize : childCrossSize; + const float childHeight = + !isMainAxisRow ? childMainSize : childCrossSize; + + auto alignContent = node->getStyle().alignContent(); + auto crossAxisDoesNotGrow = + alignContent != YGAlignStretch && isNodeFlexWrap; + const YGMeasureMode childWidthMeasureMode = + yoga::isUndefined(childWidth) || + (!isMainAxisRow && crossAxisDoesNotGrow) + ? YGMeasureModeUndefined + : YGMeasureModeExactly; + const YGMeasureMode childHeightMeasureMode = + yoga::isUndefined(childHeight) || + (isMainAxisRow && crossAxisDoesNotGrow) + ? YGMeasureModeUndefined + : YGMeasureModeExactly; + + calculateLayoutInternal( + child, + childWidth, + childHeight, + direction, + childWidthMeasureMode, + childHeightMeasureMode, + availableInnerWidth, + availableInnerHeight, + true, + LayoutPassReason::kStretch, + config, + layoutMarkerData, + layoutContext, + depth, + generationCount); + } + } else { + const float remainingCrossDim = containerCrossAxis - + dimensionWithMargin(child, crossAxis, availableInnerWidth); + + if (child->marginLeadingValue(crossAxis).unit == YGUnitAuto && + child->marginTrailingValue(crossAxis).unit == YGUnitAuto) { + leadingCrossDim += + yoga::maxOrDefined(0.0f, remainingCrossDim / 2); + } else if ( + child->marginTrailingValue(crossAxis).unit == YGUnitAuto) { + // No-Op + } else if ( + child->marginLeadingValue(crossAxis).unit == YGUnitAuto) { + leadingCrossDim += yoga::maxOrDefined(0.0f, remainingCrossDim); + } else if (alignItem == YGAlignFlexStart) { + // No-Op + } else if (alignItem == YGAlignCenter) { + leadingCrossDim += remainingCrossDim / 2; + } else { + leadingCrossDim += remainingCrossDim; + } + } + // And we apply the position + child->setLayoutPosition( + child->getLayout().position[pos[crossAxis]] + totalLineCrossDim + + leadingCrossDim, + pos[crossAxis]); + } + } + } + + const float appliedCrossGap = lineCount != 0 ? crossAxisGap : 0.0f; + totalLineCrossDim += collectedFlexItemsValues.crossDim + appliedCrossGap; + maxLineMainDim = + yoga::maxOrDefined(maxLineMainDim, collectedFlexItemsValues.mainDim); + } + + // STEP 8: MULTI-LINE CONTENT ALIGNMENT + // currentLead stores the size of the cross dim + if (performLayout && (isNodeFlexWrap || isBaselineLayout(node))) { + float crossDimLead = 0; + float currentLead = leadingPaddingAndBorderCross; + if (!yoga::isUndefined(availableInnerCrossDim)) { + const float remainingAlignContentDim = + availableInnerCrossDim - totalLineCrossDim; + switch (node->getStyle().alignContent()) { + case YGAlignFlexEnd: + currentLead += remainingAlignContentDim; + break; + case YGAlignCenter: + currentLead += remainingAlignContentDim / 2; + break; + case YGAlignStretch: + if (availableInnerCrossDim > totalLineCrossDim) { + crossDimLead = remainingAlignContentDim / lineCount; + } + break; + case YGAlignSpaceAround: + if (availableInnerCrossDim > totalLineCrossDim) { + currentLead += remainingAlignContentDim / (2 * lineCount); + if (lineCount > 1) { + crossDimLead = remainingAlignContentDim / lineCount; + } + } else { + currentLead += remainingAlignContentDim / 2; + } + break; + case YGAlignSpaceBetween: + if (availableInnerCrossDim > totalLineCrossDim && lineCount > 1) { + crossDimLead = remainingAlignContentDim / (lineCount - 1); + } + break; + case YGAlignAuto: + case YGAlignFlexStart: + case YGAlignBaseline: + break; + } + } + uint32_t endIndex = 0; + for (uint32_t i = 0; i < lineCount; i++) { + const uint32_t startIndex = endIndex; + uint32_t ii; + + // compute the line's height and find the endIndex + float lineHeight = 0; + float maxAscentForCurrentLine = 0; + float maxDescentForCurrentLine = 0; + for (ii = startIndex; ii < childCount; ii++) { + const auto child = node->getChild(ii); + if (child->getStyle().display() == YGDisplayNone) { + continue; + } + if (child->getStyle().positionType() != YGPositionTypeAbsolute) { + if (child->getLineIndex() != i) { + break; + } + if (isLayoutDimensionDefined(child, crossAxis)) { + lineHeight = yoga::maxOrDefined( + lineHeight, + child->getLayout().measuredDimensions[dim[crossAxis]] + + child->getMarginForAxis(crossAxis, availableInnerWidth) + .unwrap()); + } + if (resolveChildAlignment(node, child) == YGAlignBaseline) { + const float ascent = calculateBaseline(child, layoutContext) + + child + ->getLeadingMargin( + YGFlexDirectionColumn, availableInnerWidth) + .unwrap(); + const float descent = + child->getLayout().measuredDimensions[YGDimensionHeight] + + child + ->getMarginForAxis( + YGFlexDirectionColumn, availableInnerWidth) + .unwrap() - + ascent; + maxAscentForCurrentLine = + yoga::maxOrDefined(maxAscentForCurrentLine, ascent); + maxDescentForCurrentLine = + yoga::maxOrDefined(maxDescentForCurrentLine, descent); + lineHeight = yoga::maxOrDefined( + lineHeight, maxAscentForCurrentLine + maxDescentForCurrentLine); + } + } + } + endIndex = ii; + lineHeight += crossDimLead; + currentLead += i != 0 ? crossAxisGap : 0; + + if (performLayout) { + for (ii = startIndex; ii < endIndex; ii++) { + const auto child = node->getChild(ii); + if (child->getStyle().display() == YGDisplayNone) { + continue; + } + if (child->getStyle().positionType() != YGPositionTypeAbsolute) { + switch (resolveChildAlignment(node, child)) { + case YGAlignFlexStart: { + child->setLayoutPosition( + currentLead + + child->getLeadingMargin(crossAxis, availableInnerWidth) + .unwrap(), + pos[crossAxis]); + break; + } + case YGAlignFlexEnd: { + child->setLayoutPosition( + currentLead + lineHeight - + child->getTrailingMargin(crossAxis, availableInnerWidth) + .unwrap() - + child->getLayout().measuredDimensions[dim[crossAxis]], + pos[crossAxis]); + break; + } + case YGAlignCenter: { + float childHeight = + child->getLayout().measuredDimensions[dim[crossAxis]]; + + child->setLayoutPosition( + currentLead + (lineHeight - childHeight) / 2, + pos[crossAxis]); + break; + } + case YGAlignStretch: { + child->setLayoutPosition( + currentLead + + child->getLeadingMargin(crossAxis, availableInnerWidth) + .unwrap(), + pos[crossAxis]); + + // Remeasure child with the line height as it as been only + // measured with the owners height yet. + if (!styleDefinesDimension( + child, crossAxis, availableInnerCrossDim)) { + const float childWidth = isMainAxisRow + ? (child->getLayout() + .measuredDimensions[YGDimensionWidth] + + child->getMarginForAxis(mainAxis, availableInnerWidth) + .unwrap()) + : lineHeight; + + const float childHeight = !isMainAxisRow + ? (child->getLayout() + .measuredDimensions[YGDimensionHeight] + + child->getMarginForAxis(crossAxis, availableInnerWidth) + .unwrap()) + : lineHeight; + + if (!(yoga::inexactEquals( + childWidth, + child->getLayout() + .measuredDimensions[YGDimensionWidth]) && + yoga::inexactEquals( + childHeight, + child->getLayout() + .measuredDimensions[YGDimensionHeight]))) { + calculateLayoutInternal( + child, + childWidth, + childHeight, + direction, + YGMeasureModeExactly, + YGMeasureModeExactly, + availableInnerWidth, + availableInnerHeight, + true, + LayoutPassReason::kMultilineStretch, + config, + layoutMarkerData, + layoutContext, + depth, + generationCount); + } + } + break; + } + case YGAlignBaseline: { + child->setLayoutPosition( + currentLead + maxAscentForCurrentLine - + calculateBaseline(child, layoutContext) + + child + ->getLeadingPosition( + YGFlexDirectionColumn, availableInnerCrossDim) + .unwrap(), + YGEdgeTop); + + break; + } + case YGAlignAuto: + case YGAlignSpaceBetween: + case YGAlignSpaceAround: + break; + } + } + } + } + currentLead += lineHeight; + } + } + + // STEP 9: COMPUTING FINAL DIMENSIONS + + node->setLayoutMeasuredDimension( + boundAxis( + node, + YGFlexDirectionRow, + availableWidth - marginAxisRow, + ownerWidth, + ownerWidth), + YGDimensionWidth); + + node->setLayoutMeasuredDimension( + boundAxis( + node, + YGFlexDirectionColumn, + availableHeight - marginAxisColumn, + ownerHeight, + ownerWidth), + YGDimensionHeight); + + // If the user didn't specify a width or height for the node, set the + // dimensions based on the children. + if (measureModeMainDim == YGMeasureModeUndefined || + (node->getStyle().overflow() != YGOverflowScroll && + measureModeMainDim == YGMeasureModeAtMost)) { + // Clamp the size to the min/max size, if specified, and make sure it + // doesn't go below the padding and border amount. + node->setLayoutMeasuredDimension( + boundAxis( + node, mainAxis, maxLineMainDim, mainAxisownerSize, ownerWidth), + dim[mainAxis]); + + } else if ( + measureModeMainDim == YGMeasureModeAtMost && + node->getStyle().overflow() == YGOverflowScroll) { + node->setLayoutMeasuredDimension( + yoga::maxOrDefined( + yoga::minOrDefined( + availableInnerMainDim + paddingAndBorderAxisMain, + boundAxisWithinMinAndMax( + node, + mainAxis, + FloatOptional{maxLineMainDim}, + mainAxisownerSize) + .unwrap()), + paddingAndBorderAxisMain), + dim[mainAxis]); + } + + if (measureModeCrossDim == YGMeasureModeUndefined || + (node->getStyle().overflow() != YGOverflowScroll && + measureModeCrossDim == YGMeasureModeAtMost)) { + // Clamp the size to the min/max size, if specified, and make sure it + // doesn't go below the padding and border amount. + node->setLayoutMeasuredDimension( + boundAxis( + node, + crossAxis, + totalLineCrossDim + paddingAndBorderAxisCross, + crossAxisownerSize, + ownerWidth), + dim[crossAxis]); + + } else if ( + measureModeCrossDim == YGMeasureModeAtMost && + node->getStyle().overflow() == YGOverflowScroll) { + node->setLayoutMeasuredDimension( + yoga::maxOrDefined( + yoga::minOrDefined( + availableInnerCrossDim + paddingAndBorderAxisCross, + boundAxisWithinMinAndMax( + node, + crossAxis, + FloatOptional{ + totalLineCrossDim + paddingAndBorderAxisCross}, + crossAxisownerSize) + .unwrap()), + paddingAndBorderAxisCross), + dim[crossAxis]); + } + + // As we only wrapped in normal direction yet, we need to reverse the + // positions on wrap-reverse. + if (performLayout && node->getStyle().flexWrap() == YGWrapWrapReverse) { + for (uint32_t i = 0; i < childCount; i++) { + const auto child = node->getChild(i); + if (child->getStyle().positionType() != YGPositionTypeAbsolute) { + child->setLayoutPosition( + node->getLayout().measuredDimensions[dim[crossAxis]] - + child->getLayout().position[pos[crossAxis]] - + child->getLayout().measuredDimensions[dim[crossAxis]], + pos[crossAxis]); + } + } + } + + if (performLayout) { + // STEP 10: SIZING AND POSITIONING ABSOLUTE CHILDREN + for (auto child : node->getChildren()) { + if (child->getStyle().display() == YGDisplayNone || + child->getStyle().positionType() != YGPositionTypeAbsolute) { + continue; + } + const bool absolutePercentageAgainstPaddingEdge = + config->isExperimentalFeatureEnabled( + YGExperimentalFeatureAbsolutePercentageAgainstPaddingEdge); + + layoutAbsoluteChild( + node, + child, + absolutePercentageAgainstPaddingEdge + ? node->getLayout().measuredDimensions[YGDimensionWidth] + : availableInnerWidth, + isMainAxisRow ? measureModeMainDim : measureModeCrossDim, + absolutePercentageAgainstPaddingEdge + ? node->getLayout().measuredDimensions[YGDimensionHeight] + : availableInnerHeight, + direction, + config, + layoutMarkerData, + layoutContext, + depth, + generationCount); + } + + // STEP 11: SETTING TRAILING POSITIONS FOR CHILDREN + const bool needsMainTrailingPos = mainAxis == YGFlexDirectionRowReverse || + mainAxis == YGFlexDirectionColumnReverse; + const bool needsCrossTrailingPos = crossAxis == YGFlexDirectionRowReverse || + crossAxis == YGFlexDirectionColumnReverse; + + // Set trailing position if necessary. + if (needsMainTrailingPos || needsCrossTrailingPos) { + for (uint32_t i = 0; i < childCount; i++) { + const auto child = node->getChild(i); + if (child->getStyle().display() == YGDisplayNone) { + continue; + } + if (needsMainTrailingPos) { + setChildTrailingPosition(node, child, mainAxis); + } + + if (needsCrossTrailingPos) { + setChildTrailingPosition(node, child, crossAxis); + } + } + } + } +} + +bool gPrintChanges = false; +bool gPrintSkips = false; + +static const char* spacer = + " "; + +static const char* spacerWithLength(const unsigned long level) { + const size_t spacerLen = strlen(spacer); + if (level > spacerLen) { + return &spacer[0]; + } else { + return &spacer[spacerLen - level]; + } +} + +static const char* measureModeName( + const YGMeasureMode mode, + const bool performLayout) { + constexpr auto N = enums::count(); + const char* kMeasureModeNames[N] = {"UNDEFINED", "EXACTLY", "AT_MOST"}; + const char* kLayoutModeNames[N] = { + "LAY_UNDEFINED", "LAY_EXACTLY", "LAY_AT_MOST"}; + + if (mode >= N) { + return ""; + } + + return performLayout ? kLayoutModeNames[mode] : kMeasureModeNames[mode]; +} + +// +// This is a wrapper around the calculateLayoutImpl function. It determines +// whether the layout request is redundant and can be skipped. +// +// Parameters: +// Input parameters are the same as calculateLayoutImpl (see above) +// Return parameter is true if layout was performed, false if skipped +// +bool calculateLayoutInternal( + yoga::Node* const node, + const float availableWidth, + const float availableHeight, + const YGDirection ownerDirection, + const YGMeasureMode widthMeasureMode, + const YGMeasureMode heightMeasureMode, + const float ownerWidth, + const float ownerHeight, + const bool performLayout, + const LayoutPassReason reason, + const yoga::Config* const config, + LayoutData& layoutMarkerData, + void* const layoutContext, + uint32_t depth, + const uint32_t generationCount) { + LayoutResults* layout = &node->getLayout(); + + depth++; + + const bool needToVisitNode = + (node->isDirty() && layout->generationCount != generationCount) || + layout->lastOwnerDirection != ownerDirection; + + if (needToVisitNode) { + // Invalidate the cached results. + layout->nextCachedMeasurementsIndex = 0; + layout->cachedLayout.availableWidth = -1; + layout->cachedLayout.availableHeight = -1; + layout->cachedLayout.widthMeasureMode = YGMeasureModeUndefined; + layout->cachedLayout.heightMeasureMode = YGMeasureModeUndefined; + layout->cachedLayout.computedWidth = -1; + layout->cachedLayout.computedHeight = -1; + } + + CachedMeasurement* cachedResults = nullptr; + + // Determine whether the results are already cached. We maintain a separate + // cache for layouts and measurements. A layout operation modifies the + // positions and dimensions for nodes in the subtree. The algorithm assumes + // that each node gets laid out a maximum of one time per tree layout, but + // multiple measurements may be required to resolve all of the flex + // dimensions. We handle nodes with measure functions specially here because + // they are the most expensive to measure, so it's worth avoiding redundant + // measurements if at all possible. + if (node->hasMeasureFunc()) { + const float marginAxisRow = + node->getMarginForAxis(YGFlexDirectionRow, ownerWidth).unwrap(); + const float marginAxisColumn = + node->getMarginForAxis(YGFlexDirectionColumn, ownerWidth).unwrap(); + + // First, try to use the layout cache. + if (canUseCachedMeasurement( + widthMeasureMode, + availableWidth, + heightMeasureMode, + availableHeight, + layout->cachedLayout.widthMeasureMode, + layout->cachedLayout.availableWidth, + layout->cachedLayout.heightMeasureMode, + layout->cachedLayout.availableHeight, + layout->cachedLayout.computedWidth, + layout->cachedLayout.computedHeight, + marginAxisRow, + marginAxisColumn, + config)) { + cachedResults = &layout->cachedLayout; + } else { + // Try to use the measurement cache. + for (uint32_t i = 0; i < layout->nextCachedMeasurementsIndex; i++) { + if (canUseCachedMeasurement( + widthMeasureMode, + availableWidth, + heightMeasureMode, + availableHeight, + layout->cachedMeasurements[i].widthMeasureMode, + layout->cachedMeasurements[i].availableWidth, + layout->cachedMeasurements[i].heightMeasureMode, + layout->cachedMeasurements[i].availableHeight, + layout->cachedMeasurements[i].computedWidth, + layout->cachedMeasurements[i].computedHeight, + marginAxisRow, + marginAxisColumn, + config)) { + cachedResults = &layout->cachedMeasurements[i]; + break; + } + } + } + } else if (performLayout) { + if (yoga::inexactEquals( + layout->cachedLayout.availableWidth, availableWidth) && + yoga::inexactEquals( + layout->cachedLayout.availableHeight, availableHeight) && + layout->cachedLayout.widthMeasureMode == widthMeasureMode && + layout->cachedLayout.heightMeasureMode == heightMeasureMode) { + cachedResults = &layout->cachedLayout; + } + } else { + for (uint32_t i = 0; i < layout->nextCachedMeasurementsIndex; i++) { + if (yoga::inexactEquals( + layout->cachedMeasurements[i].availableWidth, availableWidth) && + yoga::inexactEquals( + layout->cachedMeasurements[i].availableHeight, availableHeight) && + layout->cachedMeasurements[i].widthMeasureMode == widthMeasureMode && + layout->cachedMeasurements[i].heightMeasureMode == + heightMeasureMode) { + cachedResults = &layout->cachedMeasurements[i]; + break; + } + } + } + + if (!needToVisitNode && cachedResults != nullptr) { + layout->measuredDimensions[YGDimensionWidth] = cachedResults->computedWidth; + layout->measuredDimensions[YGDimensionHeight] = + cachedResults->computedHeight; + + (performLayout ? layoutMarkerData.cachedLayouts + : layoutMarkerData.cachedMeasures) += 1; + + if (gPrintChanges && gPrintSkips) { + yoga::log( + node, + YGLogLevelVerbose, + nullptr, + "%s%d.{[skipped] ", + spacerWithLength(depth), + depth); + node->print(layoutContext); + yoga::log( + node, + YGLogLevelVerbose, + nullptr, + "wm: %s, hm: %s, aw: %f ah: %f => d: (%f, %f) %s\n", + measureModeName(widthMeasureMode, performLayout), + measureModeName(heightMeasureMode, performLayout), + availableWidth, + availableHeight, + cachedResults->computedWidth, + cachedResults->computedHeight, + LayoutPassReasonToString(reason)); + } + } else { + if (gPrintChanges) { + yoga::log( + node, + YGLogLevelVerbose, + nullptr, + "%s%d.{%s", + spacerWithLength(depth), + depth, + needToVisitNode ? "*" : ""); + node->print(layoutContext); + yoga::log( + node, + YGLogLevelVerbose, + nullptr, + "wm: %s, hm: %s, aw: %f ah: %f %s\n", + measureModeName(widthMeasureMode, performLayout), + measureModeName(heightMeasureMode, performLayout), + availableWidth, + availableHeight, + LayoutPassReasonToString(reason)); + } + + calculateLayoutImpl( + node, + availableWidth, + availableHeight, + ownerDirection, + widthMeasureMode, + heightMeasureMode, + ownerWidth, + ownerHeight, + performLayout, + config, + layoutMarkerData, + layoutContext, + depth, + generationCount, + reason); + + if (gPrintChanges) { + yoga::log( + node, + YGLogLevelVerbose, + nullptr, + "%s%d.}%s", + spacerWithLength(depth), + depth, + needToVisitNode ? "*" : ""); + node->print(layoutContext); + yoga::log( + node, + YGLogLevelVerbose, + nullptr, + "wm: %s, hm: %s, d: (%f, %f) %s\n", + measureModeName(widthMeasureMode, performLayout), + measureModeName(heightMeasureMode, performLayout), + layout->measuredDimensions[YGDimensionWidth], + layout->measuredDimensions[YGDimensionHeight], + LayoutPassReasonToString(reason)); + } + + layout->lastOwnerDirection = ownerDirection; + + if (cachedResults == nullptr) { + if (layout->nextCachedMeasurementsIndex + 1 > + (uint32_t) layoutMarkerData.maxMeasureCache) { + layoutMarkerData.maxMeasureCache = + layout->nextCachedMeasurementsIndex + 1; + } + if (layout->nextCachedMeasurementsIndex == + LayoutResults::MaxCachedMeasurements) { + if (gPrintChanges) { + yoga::log( + node, YGLogLevelVerbose, nullptr, "Out of cache entries!\n"); + } + layout->nextCachedMeasurementsIndex = 0; + } + + CachedMeasurement* newCacheEntry; + if (performLayout) { + // Use the single layout cache entry. + newCacheEntry = &layout->cachedLayout; + } else { + // Allocate a new measurement cache entry. + newCacheEntry = + &layout->cachedMeasurements[layout->nextCachedMeasurementsIndex]; + layout->nextCachedMeasurementsIndex++; + } + + newCacheEntry->availableWidth = availableWidth; + newCacheEntry->availableHeight = availableHeight; + newCacheEntry->widthMeasureMode = widthMeasureMode; + newCacheEntry->heightMeasureMode = heightMeasureMode; + newCacheEntry->computedWidth = + layout->measuredDimensions[YGDimensionWidth]; + newCacheEntry->computedHeight = + layout->measuredDimensions[YGDimensionHeight]; + } + } + + if (performLayout) { + node->setLayoutDimension( + node->getLayout().measuredDimensions[YGDimensionWidth], + YGDimensionWidth); + node->setLayoutDimension( + node->getLayout().measuredDimensions[YGDimensionHeight], + YGDimensionHeight); + + node->setHasNewLayout(true); + node->setDirty(false); + } + + layout->generationCount = generationCount; + + LayoutType layoutType; + if (performLayout) { + layoutType = !needToVisitNode && cachedResults == &layout->cachedLayout + ? LayoutType::kCachedLayout + : LayoutType::kLayout; + } else { + layoutType = cachedResults != nullptr ? LayoutType::kCachedMeasure + : LayoutType::kMeasure; + } + Event::publish(node, {layoutType, layoutContext}); + + return (needToVisitNode || cachedResults == nullptr); +} + +void calculateLayout( + yoga::Node* const node, + const float ownerWidth, + const float ownerHeight, + const YGDirection ownerDirection, + void* layoutContext) { + Event::publish(node, {layoutContext}); + LayoutData markerData = {}; + + // Increment the generation count. This will force the recursive routine to + // visit all dirty nodes at least once. Subsequent visits will be skipped if + // the input parameters don't change. + gCurrentGenerationCount.fetch_add(1, std::memory_order_relaxed); + node->resolveDimension(); + float width = YGUndefined; + YGMeasureMode widthMeasureMode = YGMeasureModeUndefined; + const auto& maxDimensions = node->getStyle().maxDimensions(); + if (styleDefinesDimension(node, YGFlexDirectionRow, ownerWidth)) { + width = + (yoga::resolveValue( + node->getResolvedDimension(dim[YGFlexDirectionRow]), ownerWidth) + + node->getMarginForAxis(YGFlexDirectionRow, ownerWidth)) + .unwrap(); + widthMeasureMode = YGMeasureModeExactly; + } else if (!yoga::resolveValue(maxDimensions[YGDimensionWidth], ownerWidth) + .isUndefined()) { + width = yoga::resolveValue(maxDimensions[YGDimensionWidth], ownerWidth) + .unwrap(); + widthMeasureMode = YGMeasureModeAtMost; + } else { + width = ownerWidth; + widthMeasureMode = yoga::isUndefined(width) ? YGMeasureModeUndefined + : YGMeasureModeExactly; + } + + float height = YGUndefined; + YGMeasureMode heightMeasureMode = YGMeasureModeUndefined; + if (styleDefinesDimension(node, YGFlexDirectionColumn, ownerHeight)) { + height = (yoga::resolveValue( + node->getResolvedDimension(dim[YGFlexDirectionColumn]), + ownerHeight) + + node->getMarginForAxis(YGFlexDirectionColumn, ownerWidth)) + .unwrap(); + heightMeasureMode = YGMeasureModeExactly; + } else if (!yoga::resolveValue(maxDimensions[YGDimensionHeight], ownerHeight) + .isUndefined()) { + height = yoga::resolveValue(maxDimensions[YGDimensionHeight], ownerHeight) + .unwrap(); + heightMeasureMode = YGMeasureModeAtMost; + } else { + height = ownerHeight; + heightMeasureMode = yoga::isUndefined(height) ? YGMeasureModeUndefined + : YGMeasureModeExactly; + } + if (calculateLayoutInternal( + node, + width, + height, + ownerDirection, + widthMeasureMode, + heightMeasureMode, + ownerWidth, + ownerHeight, + true, + LayoutPassReason::kInitial, + node->getConfig(), + markerData, + layoutContext, + 0, // tree root + gCurrentGenerationCount.load(std::memory_order_relaxed))) { + node->setPosition( + node->getLayout().direction(), ownerWidth, ownerHeight, ownerWidth); + roundLayoutResultsToPixelGrid( + node, node->getConfig()->getPointScaleFactor(), 0.0f, 0.0f); + +#ifdef DEBUG + if (node->getConfig()->shouldPrintTree()) { + YGNodePrint( + node, + (YGPrintOptions) (YGPrintOptionsLayout | YGPrintOptionsChildren | YGPrintOptionsStyle)); + } +#endif + } + + Event::publish(node, {layoutContext, &markerData}); +} + +} // namespace facebook::yoga diff --git a/yoga/algorithm/CalculateLayout.h b/yoga/algorithm/CalculateLayout.h new file mode 100644 index 0000000000..e12dcc0dda --- /dev/null +++ b/yoga/algorithm/CalculateLayout.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include + +namespace facebook::yoga { + +void calculateLayout( + yoga::Node* const node, + const float ownerWidth, + const float ownerHeight, + const YGDirection ownerDirection, + void* layoutContext); + +} // namespace facebook::yoga diff --git a/yoga/algorithm/PixelGrid.cpp b/yoga/algorithm/PixelGrid.cpp new file mode 100644 index 0000000000..105e57a315 --- /dev/null +++ b/yoga/algorithm/PixelGrid.cpp @@ -0,0 +1,135 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include + +#include +#include + +namespace facebook::yoga { + +float roundValueToPixelGrid( + const double value, + const double pointScaleFactor, + const bool forceCeil, + const bool forceFloor) { + double scaledValue = value * pointScaleFactor; + // We want to calculate `fractial` such that `floor(scaledValue) = scaledValue + // - fractial`. + double fractial = fmod(scaledValue, 1.0); + if (fractial < 0) { + // This branch is for handling negative numbers for `value`. + // + // Regarding `floor` and `ceil`. Note that for a number x, `floor(x) <= x <= + // ceil(x)` even for negative numbers. Here are a couple of examples: + // - x = 2.2: floor( 2.2) = 2, ceil( 2.2) = 3 + // - x = -2.2: floor(-2.2) = -3, ceil(-2.2) = -2 + // + // Regarding `fmodf`. For fractional negative numbers, `fmodf` returns a + // negative number. For example, `fmodf(-2.2) = -0.2`. However, we want + // `fractial` to be the number such that subtracting it from `value` will + // give us `floor(value)`. In the case of negative numbers, adding 1 to + // `fmodf(value)` gives us this. Let's continue the example from above: + // - fractial = fmodf(-2.2) = -0.2 + // - Add 1 to the fraction: fractial2 = fractial + 1 = -0.2 + 1 = 0.8 + // - Finding the `floor`: -2.2 - fractial2 = -2.2 - 0.8 = -3 + ++fractial; + } + if (yoga::inexactEquals(fractial, 0)) { + // First we check if the value is already rounded + scaledValue = scaledValue - fractial; + } else if (yoga::inexactEquals(fractial, 1.0)) { + scaledValue = scaledValue - fractial + 1.0; + } else if (forceCeil) { + // Next we check if we need to use forced rounding + scaledValue = scaledValue - fractial + 1.0; + } else if (forceFloor) { + scaledValue = scaledValue - fractial; + } else { + // Finally we just round the value + scaledValue = scaledValue - fractial + + (!std::isnan(fractial) && + (fractial > 0.5 || yoga::inexactEquals(fractial, 0.5)) + ? 1.0 + : 0.0); + } + return (std::isnan(scaledValue) || std::isnan(pointScaleFactor)) + ? YGUndefined + : (float) (scaledValue / pointScaleFactor); +} + +void roundLayoutResultsToPixelGrid( + yoga::Node* const node, + const double pointScaleFactor, + const double absoluteLeft, + const double absoluteTop) { + if (pointScaleFactor == 0.0f) { + return; + } + + const double nodeLeft = node->getLayout().position[YGEdgeLeft]; + const double nodeTop = node->getLayout().position[YGEdgeTop]; + + const double nodeWidth = node->getLayout().dimensions[YGDimensionWidth]; + const double nodeHeight = node->getLayout().dimensions[YGDimensionHeight]; + + const double absoluteNodeLeft = absoluteLeft + nodeLeft; + const double absoluteNodeTop = absoluteTop + nodeTop; + + const double absoluteNodeRight = absoluteNodeLeft + nodeWidth; + const double absoluteNodeBottom = absoluteNodeTop + nodeHeight; + + // If a node has a custom measure function we never want to round down its + // size as this could lead to unwanted text truncation. + const bool textRounding = node->getNodeType() == YGNodeTypeText; + + node->setLayoutPosition( + roundValueToPixelGrid(nodeLeft, pointScaleFactor, false, textRounding), + YGEdgeLeft); + + node->setLayoutPosition( + roundValueToPixelGrid(nodeTop, pointScaleFactor, false, textRounding), + YGEdgeTop); + + // We multiply dimension by scale factor and if the result is close to the + // whole number, we don't have any fraction To verify if the result is close + // to whole number we want to check both floor and ceil numbers + const bool hasFractionalWidth = + !yoga::inexactEquals(fmod(nodeWidth * pointScaleFactor, 1.0), 0) && + !yoga::inexactEquals(fmod(nodeWidth * pointScaleFactor, 1.0), 1.0); + const bool hasFractionalHeight = + !yoga::inexactEquals(fmod(nodeHeight * pointScaleFactor, 1.0), 0) && + !yoga::inexactEquals(fmod(nodeHeight * pointScaleFactor, 1.0), 1.0); + + node->setLayoutDimension( + roundValueToPixelGrid( + absoluteNodeRight, + pointScaleFactor, + (textRounding && hasFractionalWidth), + (textRounding && !hasFractionalWidth)) - + roundValueToPixelGrid( + absoluteNodeLeft, pointScaleFactor, false, textRounding), + YGDimensionWidth); + + node->setLayoutDimension( + roundValueToPixelGrid( + absoluteNodeBottom, + pointScaleFactor, + (textRounding && hasFractionalHeight), + (textRounding && !hasFractionalHeight)) - + roundValueToPixelGrid( + absoluteNodeTop, pointScaleFactor, false, textRounding), + YGDimensionHeight); + + const uint32_t childCount = YGNodeGetChildCount(node); + for (uint32_t i = 0; i < childCount; i++) { + roundLayoutResultsToPixelGrid( + node->getChild(i), pointScaleFactor, absoluteNodeLeft, absoluteNodeTop); + } +} + +} // namespace facebook::yoga diff --git a/yoga/algorithm/PixelGrid.h b/yoga/algorithm/PixelGrid.h new file mode 100644 index 0000000000..24cb10d016 --- /dev/null +++ b/yoga/algorithm/PixelGrid.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include + +namespace facebook::yoga { + +// Round a point value to the nearest physical pixel based on DPI +// (pointScaleFactor) +float roundValueToPixelGrid( + const double value, + const double pointScaleFactor, + const bool forceCeil, + const bool forceFloor); + +// Round the layout results of a node and its subtree to the pixel grid. +void roundLayoutResultsToPixelGrid( + yoga::Node* const node, + const double pointScaleFactor, + const double absoluteLeft, + const double absoluteTop); + +} // namespace facebook::yoga diff --git a/yoga/debug/Assert.cpp b/yoga/debug/Assert.cpp index 863d7ad1bd..0f8eb1a1ca 100644 --- a/yoga/debug/Assert.cpp +++ b/yoga/debug/Assert.cpp @@ -33,12 +33,13 @@ void assertFatal(const bool condition, const char* message) { } void assertFatalWithNode( - const YGNodeRef node, + const YGNodeConstRef node, const bool condition, const char* message) { if (!condition) { yoga::log( - static_cast(node), + // TODO: Break log callbacks and make them const correct + static_cast(const_cast(node)), YGLogLevelFatal, nullptr, "%s\n", @@ -48,7 +49,7 @@ void assertFatalWithNode( } void assertFatalWithConfig( - const YGConfigRef config, + YGConfigRef config, const bool condition, const char* message) { if (!condition) { diff --git a/yoga/debug/Assert.h b/yoga/debug/Assert.h index 461eb8ed44..ae5a1d5ae8 100644 --- a/yoga/debug/Assert.h +++ b/yoga/debug/Assert.h @@ -16,7 +16,10 @@ namespace facebook::yoga { [[noreturn]] void fatalWithMessage(const char* message); void assertFatal(bool condition, const char* message); -void assertFatalWithNode(YGNodeRef node, bool condition, const char* message); +void assertFatalWithNode( + YGNodeConstRef node, + bool condition, + const char* message); void assertFatalWithConfig( YGConfigRef config, bool condition, diff --git a/yoga/node/Node.cpp b/yoga/node/Node.cpp index 18c6af9298..9db5655f0a 100644 --- a/yoga/node/Node.cpp +++ b/yoga/node/Node.cpp @@ -222,10 +222,11 @@ YGSize Node::measure( : measure_.noContext(this, width, widthMode, height, heightMode); } -float Node::baseline(float width, float height, void* layoutContext) { +float Node::baseline(float width, float height, void* layoutContext) const { return flags_.baselineUsesContext - ? baseline_.withContext(this, width, height, layoutContext) - : baseline_.noContext(this, width, height); + ? baseline_.withContext( + const_cast(this), width, height, layoutContext) + : baseline_.noContext(const_cast(this), width, height); } // Setters diff --git a/yoga/node/Node.h b/yoga/node/Node.h index 5401d90b6b..e0903327e6 100644 --- a/yoga/node/Node.h +++ b/yoga/node/Node.h @@ -121,7 +121,7 @@ class YOGA_EXPORT Node : public ::YGNode { return baseline_.noContext != nullptr; } - float baseline(float width, float height, void* layoutContext); + float baseline(float width, float height, void* layoutContext) const; bool hasErrata(YGErrata errata) const { return config_->hasErrata(errata); } @@ -171,6 +171,8 @@ class YOGA_EXPORT Node : public ::YGNode { Node* getChild(uint32_t index) const { return children_.at(index); } + std::size_t getChildCount() const { return children_.size(); } + Config* getConfig() const { return config_; } bool isDirty() const { return flags_.isDirty; }