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
This commit is contained in:
Nick Gerleman
2022-10-13 08:18:49 -07:00
committed by Facebook GitHub Bot
parent 05dd228317
commit 582533dbc6
7 changed files with 115 additions and 13 deletions

View File

@@ -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( YGFloatOptional YGNode::getLeadingPosition(
const YGFlexDirection axis, const YGFlexDirection axis,
const float axisSize) const { const float axisSize) const {
@@ -163,6 +187,15 @@ YGFloatOptional YGNode::getMarginForAxis(
return getLeadingMargin(axis, widthSize) + getTrailingMargin(axis, widthSize); 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( YGSize YGNode::measure(
float width, float width,
YGMeasureMode widthMode, YGMeasureMode widthMode,

View File

@@ -204,6 +204,14 @@ public:
YGEdge edge, YGEdge edge,
CompactValue defaultValue); 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 // Methods related to positions, margin, padding and border
YGFloatOptional getLeadingPosition( YGFloatOptional getLeadingPosition(
const YGFlexDirection axis, const YGFlexDirection axis,
@@ -236,6 +244,9 @@ public:
YGFloatOptional getMarginForAxis( YGFloatOptional getMarginForAxis(
const YGFlexDirection axis, const YGFlexDirection axis,
const float widthSize) const; const float widthSize) const;
YGFloatOptional getGapForAxis(
const YGFlexDirection axis,
const float widthSize) const;
// Setters // Setters
void setContext(void* context) { context_ = context; } void setContext(void* context) { context_ = context; }

View File

@@ -184,6 +184,20 @@ void YGNodeToString(
appendEdges(str, "padding", style.padding()); appendEdges(str, "padding", style.padding());
appendEdges(str, "border", style.border()); 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, "width", style.dimensions()[YGDimensionWidth]);
appendNumberIfNotAuto(str, "height", style.dimensions()[YGDimensionHeight]); appendNumberIfNotAuto(str, "height", style.dimensions()[YGDimensionHeight]);
appendNumberIfNotAuto( appendNumberIfNotAuto(

View File

@@ -22,7 +22,7 @@ bool operator==(const YGStyle& lhs, const YGStyle& rhs) {
YGValueEqual(lhs.flexBasis(), rhs.flexBasis()) && YGValueEqual(lhs.flexBasis(), rhs.flexBasis()) &&
lhs.margin() == rhs.margin() && lhs.position() == rhs.position() && lhs.margin() == rhs.margin() && lhs.position() == rhs.position() &&
lhs.padding() == rhs.padding() && lhs.border() == rhs.border() && 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.minDimensions() == rhs.minDimensions() &&
lhs.maxDimensions() == rhs.maxDimensions(); lhs.maxDimensions() == rhs.maxDimensions();

View File

@@ -29,6 +29,7 @@ class YOGA_EXPORT YGStyle {
public: public:
using Dimensions = Values<YGDimension>; using Dimensions = Values<YGDimension>;
using Edges = Values<YGEdge>; using Edges = Values<YGEdge>;
using Gutters = Values<YGGutter>;
template <typename T> template <typename T>
struct BitfieldRef { struct BitfieldRef {
@@ -113,6 +114,7 @@ private:
Edges position_ = {}; Edges position_ = {};
Edges padding_ = {}; Edges padding_ = {};
Edges border_ = {}; Edges border_ = {};
Gutters gap_ = {};
Dimensions dimensions_{CompactValue::ofAuto()}; Dimensions dimensions_{CompactValue::ofAuto()};
Dimensions minDimensions_ = {}; Dimensions minDimensions_ = {};
Dimensions maxDimensions_ = {}; Dimensions maxDimensions_ = {};
@@ -210,6 +212,9 @@ public:
const Edges& border() const { return border_; } const Edges& border() const { return border_; }
IdxRef<YGEdge, &YGStyle::border_> border() { return {*this}; } IdxRef<YGEdge, &YGStyle::border_> border() { return {*this}; }
const Gutters& gap() const { return gap_; }
IdxRef<YGGutter, &YGStyle::gap_> gap() { return {*this}; }
const Dimensions& dimensions() const { return dimensions_; } const Dimensions& dimensions() const { return dimensions_; }
IdxRef<YGDimension, &YGStyle::dimensions_> dimensions() { return {*this}; } IdxRef<YGDimension, &YGStyle::dimensions_> dimensions() { return {*this}; }

View File

@@ -797,6 +797,27 @@ YOGA_EXPORT float YGNodeStyleGetBorder(
return static_cast<YGValue>(border).value; return static_cast<YGValue>(border).value;
} }
YOGA_EXPORT void YGNodeStyleSetGap(
const YGNodeRef node,
const YGGutter gutter,
const float gapLength) {
auto length = detail::CompactValue::ofMaybe<YGUnitPoint>(gapLength);
updateIndexedStyleProp<MSVC_HINT(gap)>(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<YGValue>(gapLength).value;
}
// Yoga specific properties, not compatible with flexbox specification // Yoga specific properties, not compatible with flexbox specification
// TODO(T26792433): Change the API to accept YGFloatOptional. // TODO(T26792433): Change the API to accept YGFloatOptional.
@@ -1972,6 +1993,7 @@ static YGCollectFlexItemsRowValues YGCalculateCollectFlexItemsRowValues(
const YGFlexDirection mainAxis = YGResolveFlexDirection( const YGFlexDirection mainAxis = YGResolveFlexDirection(
node->getStyle().flexDirection(), node->resolveDirection(ownerDirection)); node->getStyle().flexDirection(), node->resolveDirection(ownerDirection));
const bool isNodeFlexWrap = node->getStyle().flexWrap() != YGWrapNoWrap; 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. // Add items to the current line until it's full or we run out of items.
uint32_t endOfLineIndex = startOfLineIndex; uint32_t endOfLineIndex = startOfLineIndex;
@@ -1981,9 +2003,13 @@ static YGCollectFlexItemsRowValues YGCalculateCollectFlexItemsRowValues(
child->getStyle().positionType() == YGPositionTypeAbsolute) { child->getStyle().positionType() == YGPositionTypeAbsolute) {
continue; continue;
} }
const bool isFirstElementInLine = (endOfLineIndex - startOfLineIndex) == 0;
child->setLineIndex(lineCount); child->setLineIndex(lineCount);
const float childMarginMainAxis = const float childMarginMainAxis =
child->getMarginForAxis(mainAxis, availableInnerWidth).unwrap(); child->getMarginForAxis(mainAxis, availableInnerWidth).unwrap();
const float childLeadingGapMainAxis = isFirstElementInLine ? 0.0f : gap;
const float flexBasisWithMinAndMaxConstraints = const float flexBasisWithMinAndMaxConstraints =
YGNodeBoundAxisWithinMinAndMax( YGNodeBoundAxisWithinMinAndMax(
child, child,
@@ -1996,16 +2022,19 @@ static YGCollectFlexItemsRowValues YGCalculateCollectFlexItemsRowValues(
// size, we've hit the end of the current line. Break out of the loop and // size, we've hit the end of the current line. Break out of the loop and
// lay out the current line. // lay out the current line.
if (sizeConsumedOnCurrentLineIncludingMinConstraint + if (sizeConsumedOnCurrentLineIncludingMinConstraint +
flexBasisWithMinAndMaxConstraints + childMarginMainAxis > flexBasisWithMinAndMaxConstraints + childMarginMainAxis +
childLeadingGapMainAxis >
availableInnerMainDim && availableInnerMainDim &&
isNodeFlexWrap && flexAlgoRowMeasurement.itemsOnLine > 0) { isNodeFlexWrap && flexAlgoRowMeasurement.itemsOnLine > 0) {
break; break;
} }
sizeConsumedOnCurrentLineIncludingMinConstraint += sizeConsumedOnCurrentLineIncludingMinConstraint +=
flexBasisWithMinAndMaxConstraints + childMarginMainAxis; flexBasisWithMinAndMaxConstraints + childMarginMainAxis +
childLeadingGapMainAxis;
flexAlgoRowMeasurement.sizeConsumedOnCurrentLine += flexAlgoRowMeasurement.sizeConsumedOnCurrentLine +=
flexBasisWithMinAndMaxConstraints + childMarginMainAxis; flexBasisWithMinAndMaxConstraints + childMarginMainAxis +
childLeadingGapMainAxis;
flexAlgoRowMeasurement.itemsOnLine++; flexAlgoRowMeasurement.itemsOnLine++;
if (child->isNodeFlexible()) { if (child->isNodeFlexible()) {
@@ -2415,6 +2444,7 @@ static void YGJustifyMainAxis(
node->getLeadingPaddingAndBorder(mainAxis, ownerWidth).unwrap(); node->getLeadingPaddingAndBorder(mainAxis, ownerWidth).unwrap();
const float trailingPaddingAndBorderMain = const float trailingPaddingAndBorderMain =
node->getTrailingPaddingAndBorder(mainAxis, ownerWidth).unwrap(); 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 // If we are using "at most" rules in the main axis, make sure that
// remainingFreeSpace is 0 when min main dimension is not given // remainingFreeSpace is 0 when min main dimension is not given
if (measureModeMainDim == YGMeasureModeAtMost && if (measureModeMainDim == YGMeasureModeAtMost &&
@@ -2462,7 +2492,7 @@ static void YGJustifyMainAxis(
// The space between the beginning and the first element and the space between // The space between the beginning and the first element and the space between
// each two elements. // each two elements.
float leadingMainDim = 0; float leadingMainDim = 0;
float betweenMainDim = 0; float betweenMainDim = gap;
const YGJustify justifyContent = node->getStyle().justifyContent(); const YGJustify justifyContent = node->getStyle().justifyContent();
if (numberOfAutoMarginsOnCurrentLine == 0) { if (numberOfAutoMarginsOnCurrentLine == 0) {
@@ -2475,24 +2505,22 @@ static void YGJustifyMainAxis(
break; break;
case YGJustifySpaceBetween: case YGJustifySpaceBetween:
if (collectedFlexItemsValues.itemsOnLine > 1) { if (collectedFlexItemsValues.itemsOnLine > 1) {
betweenMainDim = betweenMainDim +=
YGFloatMax(collectedFlexItemsValues.remainingFreeSpace, 0) / YGFloatMax(collectedFlexItemsValues.remainingFreeSpace, 0) /
(collectedFlexItemsValues.itemsOnLine - 1); (collectedFlexItemsValues.itemsOnLine - 1);
} else {
betweenMainDim = 0;
} }
break; break;
case YGJustifySpaceEvenly: case YGJustifySpaceEvenly:
// Space is distributed evenly across all elements // Space is distributed evenly across all elements
betweenMainDim = collectedFlexItemsValues.remainingFreeSpace / leadingMainDim = collectedFlexItemsValues.remainingFreeSpace /
(collectedFlexItemsValues.itemsOnLine + 1); (collectedFlexItemsValues.itemsOnLine + 1);
leadingMainDim = betweenMainDim; betweenMainDim += leadingMainDim;
break; break;
case YGJustifySpaceAround: case YGJustifySpaceAround:
// Space on the edges is half of the space between elements // Space on the edges is half of the space between elements
betweenMainDim = collectedFlexItemsValues.remainingFreeSpace / leadingMainDim = 0.5f * collectedFlexItemsValues.remainingFreeSpace /
collectedFlexItemsValues.itemsOnLine; collectedFlexItemsValues.itemsOnLine;
leadingMainDim = betweenMainDim / 2; betweenMainDim += leadingMainDim * 2;
break; break;
case YGJustifyFlexStart: case YGJustifyFlexStart:
break; break;
@@ -2890,6 +2918,9 @@ static void YGNodelayoutImpl(
// Accumulated cross dimensions of all lines so far. // Accumulated cross dimensions of all lines so far.
float totalLineCrossDim = 0; float totalLineCrossDim = 0;
const float crossAxisGap =
node->getGapForAxis(crossAxis, availableInnerCrossDim).unwrap();
// Max main dimension of all the lines. // Max main dimension of all the lines.
float maxLineMainDim = 0; float maxLineMainDim = 0;
YGCollectFlexItemsRowValues collectedFlexItemsValues; 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 = maxLineMainDim =
YGFloatMax(maxLineMainDim, collectedFlexItemsValues.mainDim); YGFloatMax(maxLineMainDim, collectedFlexItemsValues.mainDim);
} }
@@ -3304,6 +3336,7 @@ static void YGNodelayoutImpl(
} }
endIndex = ii; endIndex = ii;
lineHeight += crossDimLead; lineHeight += crossDimLead;
currentLead += i != 0 ? crossAxisGap : 0;
if (performLayout) { if (performLayout) {
for (ii = startIndex; ii < endIndex; ii++) { for (ii = startIndex; ii < endIndex; ii++) {

View File

@@ -232,6 +232,12 @@ WIN_EXPORT YGValue YGNodeStyleGetPadding(YGNodeConstRef node, YGEdge edge);
WIN_EXPORT void YGNodeStyleSetBorder(YGNodeRef node, YGEdge edge, float border); WIN_EXPORT void YGNodeStyleSetBorder(YGNodeRef node, YGEdge edge, float border);
WIN_EXPORT float YGNodeStyleGetBorder(YGNodeConstRef node, YGEdge edge); 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 YGNodeStyleSetWidth(YGNodeRef node, float width);
WIN_EXPORT void YGNodeStyleSetWidthPercent(YGNodeRef node, float width); WIN_EXPORT void YGNodeStyleSetWidthPercent(YGNodeRef node, float width);
WIN_EXPORT void YGNodeStyleSetWidthAuto(YGNodeRef node); WIN_EXPORT void YGNodeStyleSetWidthAuto(YGNodeRef node);