From 1373a7057b9d66830868832a9684ad80ae5d9d45 Mon Sep 17 00:00:00 2001 From: Nick Gerleman Date: Thu, 13 Oct 2022 08:18:49 -0700 Subject: [PATCH] Implement gap/row-gap/column-gap (within the C ABI) Summary: This extracts the core changes from https://github.com/facebook/yoga/pull/1116, to support gap/row-gap/column-gap, mostly identical, apart from the rename of gaps -> gutters. The core functionality in this PR looks to be well tested from the fixtures added. I am not an expert in the internals of Yoga, but I am seeing everything that I would expect to. The space for the gap is accounted for in line-breaking, and the accumulated gaps limit the available line-length, before sizing flexible children, so items are sized correctly as to accommodate the gap. Then the gap is used for spacing during main axis and cross-axis justification. Changelog: [Genral][Added] - Implement gap/row-gap/column-gap (within the yoga C ABI) Reviewed By: javache Differential Revision: D39922410 fbshipit-source-id: 5850f22032169028bd8383b49dd240b335c11d3d --- ReactCommon/yoga/yoga/YGNode.cpp | 33 ++++++++++++++++ ReactCommon/yoga/yoga/YGNode.h | 11 ++++++ ReactCommon/yoga/yoga/YGNodePrint.cpp | 14 +++++++ ReactCommon/yoga/yoga/YGStyle.cpp | 2 +- ReactCommon/yoga/yoga/YGStyle.h | 5 +++ ReactCommon/yoga/yoga/Yoga.cpp | 57 +++++++++++++++++++++------ ReactCommon/yoga/yoga/Yoga.h | 6 +++ 7 files changed, 115 insertions(+), 13 deletions(-) diff --git a/ReactCommon/yoga/yoga/YGNode.cpp b/ReactCommon/yoga/yoga/YGNode.cpp index 32eabd1d5f9253..5e52b6158b8c70 100644 --- a/ReactCommon/yoga/yoga/YGNode.cpp +++ b/ReactCommon/yoga/yoga/YGNode.cpp @@ -83,6 +83,30 @@ CompactValue YGNode::computeEdgeValueForColumn( } } +CompactValue YGNode::computeRowGap( + const YGStyle::Gutters& gutters, + CompactValue defaultValue) { + if (!gutters[YGGutterRow].isUndefined()) { + return gutters[YGGutterRow]; + } else if (!gutters[YGGutterAll].isUndefined()) { + return gutters[YGGutterAll]; + } else { + return defaultValue; + } +} + +CompactValue YGNode::computeColumnGap( + const YGStyle::Gutters& gutters, + CompactValue defaultValue) { + if (!gutters[YGGutterColumn].isUndefined()) { + return gutters[YGGutterColumn]; + } else if (!gutters[YGGutterAll].isUndefined()) { + return gutters[YGGutterAll]; + } else { + return defaultValue; + } +} + YGFloatOptional YGNode::getLeadingPosition( const YGFlexDirection axis, const float axisSize) const { @@ -163,6 +187,15 @@ YGFloatOptional YGNode::getMarginForAxis( return getLeadingMargin(axis, widthSize) + getTrailingMargin(axis, widthSize); } +YGFloatOptional YGNode::getGapForAxis( + const YGFlexDirection axis, + const float widthSize) const { + auto gap = YGFlexDirectionIsRow(axis) + ? computeColumnGap(style_.gap(), CompactValue::ofZero()) + : computeRowGap(style_.gap(), CompactValue::ofZero()); + return YGResolveValue(gap, widthSize); +} + YGSize YGNode::measure( float width, YGMeasureMode widthMode, diff --git a/ReactCommon/yoga/yoga/YGNode.h b/ReactCommon/yoga/yoga/YGNode.h index 8c511236770a22..ac20b1098b7ae5 100644 --- a/ReactCommon/yoga/yoga/YGNode.h +++ b/ReactCommon/yoga/yoga/YGNode.h @@ -204,6 +204,14 @@ struct YOGA_EXPORT YGNode { YGEdge edge, CompactValue defaultValue); + static CompactValue computeRowGap( + const YGStyle::Gutters& gutters, + CompactValue defaultValue); + + static CompactValue computeColumnGap( + const YGStyle::Gutters& gutters, + CompactValue defaultValue); + // Methods related to positions, margin, padding and border YGFloatOptional getLeadingPosition( const YGFlexDirection axis, @@ -236,6 +244,9 @@ struct YOGA_EXPORT YGNode { YGFloatOptional getMarginForAxis( const YGFlexDirection axis, const float widthSize) const; + YGFloatOptional getGapForAxis( + const YGFlexDirection axis, + const float widthSize) const; // Setters void setContext(void* context) { context_ = context; } diff --git a/ReactCommon/yoga/yoga/YGNodePrint.cpp b/ReactCommon/yoga/yoga/YGNodePrint.cpp index d46373c4b8144a..6149cf53bcd2fc 100644 --- a/ReactCommon/yoga/yoga/YGNodePrint.cpp +++ b/ReactCommon/yoga/yoga/YGNodePrint.cpp @@ -184,6 +184,20 @@ void YGNodeToString( appendEdges(str, "padding", style.padding()); appendEdges(str, "border", style.border()); + if (YGNode::computeColumnGap( + style.gap(), detail::CompactValue::ofUndefined()) != + YGNode::computeColumnGap( + YGNode().getStyle().gap(), detail::CompactValue::ofUndefined())) { + appendNumberIfNotUndefined( + str, "column-gap", style.gap()[YGGutterColumn]); + } + if (YGNode::computeRowGap( + style.gap(), detail::CompactValue::ofUndefined()) != + YGNode::computeRowGap( + YGNode().getStyle().gap(), detail::CompactValue::ofUndefined())) { + appendNumberIfNotUndefined(str, "row-gap", style.gap()[YGGutterRow]); + } + appendNumberIfNotAuto(str, "width", style.dimensions()[YGDimensionWidth]); appendNumberIfNotAuto(str, "height", style.dimensions()[YGDimensionHeight]); appendNumberIfNotAuto( diff --git a/ReactCommon/yoga/yoga/YGStyle.cpp b/ReactCommon/yoga/yoga/YGStyle.cpp index f8bba25d5e5fe2..37153f2e9276f3 100644 --- a/ReactCommon/yoga/yoga/YGStyle.cpp +++ b/ReactCommon/yoga/yoga/YGStyle.cpp @@ -22,7 +22,7 @@ bool operator==(const YGStyle& lhs, const YGStyle& rhs) { YGValueEqual(lhs.flexBasis(), rhs.flexBasis()) && lhs.margin() == rhs.margin() && lhs.position() == rhs.position() && lhs.padding() == rhs.padding() && lhs.border() == rhs.border() && - lhs.dimensions() == rhs.dimensions() && + lhs.gap() == rhs.gap() && lhs.dimensions() == rhs.dimensions() && lhs.minDimensions() == rhs.minDimensions() && lhs.maxDimensions() == rhs.maxDimensions(); diff --git a/ReactCommon/yoga/yoga/YGStyle.h b/ReactCommon/yoga/yoga/YGStyle.h index b463623abc98ec..858b7cd1b4ab63 100644 --- a/ReactCommon/yoga/yoga/YGStyle.h +++ b/ReactCommon/yoga/yoga/YGStyle.h @@ -29,6 +29,7 @@ class YOGA_EXPORT YGStyle { public: using Dimensions = Values; using Edges = Values; + using Gutters = Values; template struct BitfieldRef { @@ -113,6 +114,7 @@ class YOGA_EXPORT YGStyle { Edges position_ = {}; Edges padding_ = {}; Edges border_ = {}; + Gutters gap_ = {}; Dimensions dimensions_{CompactValue::ofAuto()}; Dimensions minDimensions_ = {}; Dimensions maxDimensions_ = {}; @@ -210,6 +212,9 @@ class YOGA_EXPORT YGStyle { const Edges& border() const { return border_; } IdxRef border() { return {*this}; } + const Gutters& gap() const { return gap_; } + IdxRef gap() { return {*this}; } + const Dimensions& dimensions() const { return dimensions_; } IdxRef dimensions() { return {*this}; } diff --git a/ReactCommon/yoga/yoga/Yoga.cpp b/ReactCommon/yoga/yoga/Yoga.cpp index e246895ecf13e6..a5c70b478df000 100644 --- a/ReactCommon/yoga/yoga/Yoga.cpp +++ b/ReactCommon/yoga/yoga/Yoga.cpp @@ -797,6 +797,27 @@ YOGA_EXPORT float YGNodeStyleGetBorder( return static_cast(border).value; } +YOGA_EXPORT void YGNodeStyleSetGap( + const YGNodeRef node, + const YGGutter gutter, + const float gapLength) { + auto length = detail::CompactValue::ofMaybe(gapLength); + updateIndexedStyleProp(node, &YGStyle::gap, gutter, length); +} + +YOGA_EXPORT float YGNodeStyleGetGap( + const YGNodeConstRef node, + const YGGutter gutter) { + auto gapLength = node->getStyle().gap()[gutter]; + if (gapLength.isUndefined() || gapLength.isAuto()) { + // TODO(T26792433): Rather than returning YGUndefined, change the api to + // return YGFloatOptional. + return YGUndefined; + } + + return static_cast(gapLength).value; +} + // Yoga specific properties, not compatible with flexbox specification // TODO(T26792433): Change the API to accept YGFloatOptional. @@ -1972,6 +1993,7 @@ static YGCollectFlexItemsRowValues YGCalculateCollectFlexItemsRowValues( const YGFlexDirection mainAxis = YGResolveFlexDirection( 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; @@ -1981,9 +2003,13 @@ static YGCollectFlexItemsRowValues YGCalculateCollectFlexItemsRowValues( 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, @@ -1996,16 +2022,19 @@ static YGCollectFlexItemsRowValues YGCalculateCollectFlexItemsRowValues( // 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 > + flexBasisWithMinAndMaxConstraints + childMarginMainAxis + + childLeadingGapMainAxis > availableInnerMainDim && isNodeFlexWrap && flexAlgoRowMeasurement.itemsOnLine > 0) { break; } sizeConsumedOnCurrentLineIncludingMinConstraint += - flexBasisWithMinAndMaxConstraints + childMarginMainAxis; + flexBasisWithMinAndMaxConstraints + childMarginMainAxis + + childLeadingGapMainAxis; flexAlgoRowMeasurement.sizeConsumedOnCurrentLine += - flexBasisWithMinAndMaxConstraints + childMarginMainAxis; + flexBasisWithMinAndMaxConstraints + childMarginMainAxis + + childLeadingGapMainAxis; flexAlgoRowMeasurement.itemsOnLine++; if (child->isNodeFlexible()) { @@ -2415,6 +2444,7 @@ static void YGJustifyMainAxis( 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 && @@ -2462,7 +2492,7 @@ static void YGJustifyMainAxis( // The space between the beginning and the first element and the space between // each two elements. float leadingMainDim = 0; - float betweenMainDim = 0; + float betweenMainDim = gap; const YGJustify justifyContent = node->getStyle().justifyContent(); if (numberOfAutoMarginsOnCurrentLine == 0) { @@ -2475,24 +2505,22 @@ static void YGJustifyMainAxis( break; case YGJustifySpaceBetween: if (collectedFlexItemsValues.itemsOnLine > 1) { - betweenMainDim = + betweenMainDim += YGFloatMax(collectedFlexItemsValues.remainingFreeSpace, 0) / (collectedFlexItemsValues.itemsOnLine - 1); - } else { - betweenMainDim = 0; } break; case YGJustifySpaceEvenly: // Space is distributed evenly across all elements - betweenMainDim = collectedFlexItemsValues.remainingFreeSpace / + leadingMainDim = collectedFlexItemsValues.remainingFreeSpace / (collectedFlexItemsValues.itemsOnLine + 1); - leadingMainDim = betweenMainDim; + betweenMainDim += leadingMainDim; break; case YGJustifySpaceAround: // Space on the edges is half of the space between elements - betweenMainDim = collectedFlexItemsValues.remainingFreeSpace / + leadingMainDim = 0.5f * collectedFlexItemsValues.remainingFreeSpace / collectedFlexItemsValues.itemsOnLine; - leadingMainDim = betweenMainDim / 2; + betweenMainDim += leadingMainDim * 2; break; case YGJustifyFlexStart: break; @@ -2890,6 +2918,9 @@ static void YGNodelayoutImpl( // 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; YGCollectFlexItemsRowValues collectedFlexItemsValues; @@ -3209,7 +3240,8 @@ static void YGNodelayoutImpl( } } - totalLineCrossDim += collectedFlexItemsValues.crossDim; + const float appliedCrossGap = lineCount != 0 ? crossAxisGap : 0.0f; + totalLineCrossDim += collectedFlexItemsValues.crossDim + appliedCrossGap; maxLineMainDim = YGFloatMax(maxLineMainDim, collectedFlexItemsValues.mainDim); } @@ -3304,6 +3336,7 @@ static void YGNodelayoutImpl( } endIndex = ii; lineHeight += crossDimLead; + currentLead += i != 0 ? crossAxisGap : 0; if (performLayout) { for (ii = startIndex; ii < endIndex; ii++) { diff --git a/ReactCommon/yoga/yoga/Yoga.h b/ReactCommon/yoga/yoga/Yoga.h index 15d2060d205c02..07beea4e527e83 100644 --- a/ReactCommon/yoga/yoga/Yoga.h +++ b/ReactCommon/yoga/yoga/Yoga.h @@ -232,6 +232,12 @@ WIN_EXPORT YGValue YGNodeStyleGetPadding(YGNodeConstRef node, YGEdge edge); WIN_EXPORT void YGNodeStyleSetBorder(YGNodeRef node, YGEdge edge, float border); WIN_EXPORT float YGNodeStyleGetBorder(YGNodeConstRef node, YGEdge edge); +WIN_EXPORT void YGNodeStyleSetGap( + YGNodeRef node, + YGGutter gutter, + float gapLength); +WIN_EXPORT float YGNodeStyleGetGap(YGNodeConstRef node, YGGutter gutter); + WIN_EXPORT void YGNodeStyleSetWidth(YGNodeRef node, float width); WIN_EXPORT void YGNodeStyleSetWidthPercent(YGNodeRef node, float width); WIN_EXPORT void YGNodeStyleSetWidthAuto(YGNodeRef node);