Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix issue where we were not centering absolute nodes correctly when justifying #41690

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public enum YogaErrata {
STRETCH_FLEX_BASIS(1),
STARTING_ENDING_EDGE_FROM_FLEX_DIRECTION(2),
POSITION_STATIC_BEHAVES_LIKE_RELATIVE(4),
ABSOLUTE_POSITIONING(8),
ALL(2147483647),
CLASSIC(2147483646);

Expand All @@ -33,6 +34,7 @@ public static YogaErrata fromInt(int value) {
case 1: return STRETCH_FLEX_BASIS;
case 2: return STARTING_ENDING_EDGE_FROM_FLEX_DIRECTION;
case 4: return POSITION_STATIC_BEHAVES_LIKE_RELATIVE;
case 8: return ABSOLUTE_POSITIONING;
case 2147483647: return ALL;
case 2147483646: return CLASSIC;
default: throw new IllegalArgumentException("Unknown enum value: " + value);
Expand Down
2 changes: 2 additions & 0 deletions packages/react-native/ReactCommon/yoga/yoga/YGEnums.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ const char* YGErrataToString(const YGErrata value) {
return "starting-ending-edge-from-flex-direction";
case YGErrataPositionStaticBehavesLikeRelative:
return "position-static-behaves-like-relative";
case YGErrataAbsolutePositioning:
return "absolute-positioning";
case YGErrataAll:
return "all";
case YGErrataClassic:
Expand Down
1 change: 1 addition & 0 deletions packages/react-native/ReactCommon/yoga/yoga/YGEnums.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ YG_ENUM_DECL(
YGErrataStretchFlexBasis = 1,
YGErrataStartingEndingEdgeFromFlexDirection = 2,
YGErrataPositionStaticBehavesLikeRelative = 4,
YGErrataAbsolutePositioning = 8,
YGErrataAll = 2147483647,
YGErrataClassic = 2147483646)
YG_DEFINE_ENUM_FLAG_OPERATORS(YGErrata)
Expand Down
236 changes: 208 additions & 28 deletions packages/react-native/ReactCommon/yoga/yoga/algorithm/AbsoluteLayout.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,101 @@

namespace facebook::yoga {

/*
* Absolutely positioned nodes do not participate in flex layout and thus their
* positions can be determined independently from the rest of their siblings.
* For each axis there are essentially two cases:
*
* 1) The node has insets defined. In this case we can just use these to
* determine the position of the node.
* 2) The node does not have insets defined. In this case we look at the style
* of the parent to position the node. Things like justify content and
* align content will move absolute children around. If none of these
* special properties are defined, the child is positioned at the start
* (defined by flex direction) of the leading flex line.
*
* This function does that positioning for the given axis. The spec has more
* information on this topic: https://www.w3.org/TR/css-flexbox-1/#abspos-items
*/
static void positionAbsoluteChild(
static void justifyAbsoluteChild(
const yoga::Node* const parent,
yoga::Node* child,
const Direction direction,
const FlexDirection mainAxis,
const float containingBlockWidth) {
const Justify parentJustifyContent = parent->getStyle().justifyContent();
switch (parentJustifyContent) {
case Justify::FlexStart:
case Justify::SpaceBetween:
child->setLayoutPosition(
child->getFlexStartMargin(mainAxis, direction, containingBlockWidth) +
parent->getLayout().border(flexStartEdge(mainAxis)) +
parent->getLayout().padding(flexStartEdge(mainAxis)),
flexStartEdge(mainAxis));
break;
case Justify::FlexEnd:
child->setLayoutPosition(
(parent->getLayout().measuredDimension(dimension(mainAxis)) -
child->getLayout().measuredDimension(dimension(mainAxis))),
flexStartEdge(mainAxis));
break;
case Justify::Center:
case Justify::SpaceAround:
case Justify::SpaceEvenly:
const float parentContentBoxSize =
parent->getLayout().measuredDimension(dimension(mainAxis)) -
parent->getLayout().border(flexStartEdge(mainAxis)) -
parent->getLayout().border(flexEndEdge(mainAxis)) -
parent->getLayout().padding(flexStartEdge(mainAxis)) -
parent->getLayout().padding(flexEndEdge(mainAxis));
const float childOuterSize =
child->getLayout().measuredDimension(dimension(mainAxis)) +
child->getMarginForAxis(mainAxis, containingBlockWidth);
child->setLayoutPosition(
(parentContentBoxSize - childOuterSize) / 2.0f +
parent->getLayout().border(flexStartEdge(mainAxis)) +
parent->getLayout().padding(flexStartEdge(mainAxis)) +
child->getFlexStartMargin(
mainAxis, direction, containingBlockWidth),
flexStartEdge(mainAxis));
break;
}
}

static void alignAbsoluteChild(
const yoga::Node* const parent,
yoga::Node* child,
const Direction direction,
const FlexDirection crossAxis,
const float containingBlockWidth) {
Align itemAlign = resolveChildAlignment(parent, child);
const Wrap parentWrap = parent->getStyle().flexWrap();
if (parentWrap == Wrap::WrapReverse) {
if (itemAlign == Align::FlexEnd) {
itemAlign = Align::FlexStart;
} else if (itemAlign != Align::Center) {
itemAlign = Align::FlexEnd;
}
}

switch (itemAlign) {
case Align::Auto:
case Align::FlexStart:
case Align::Baseline:
case Align::SpaceAround:
case Align::SpaceBetween:
case Align::Stretch:
case Align::SpaceEvenly:
child->setLayoutPosition(
parent->getLayout().border(flexStartEdge(crossAxis)) +
parent->getLayout().padding(flexStartEdge(crossAxis)) +
child->getFlexStartMargin(
crossAxis, direction, containingBlockWidth),
flexStartEdge(crossAxis));
break;
case Align::FlexEnd:
child->setLayoutPosition(
(parent->getLayout().measuredDimension(dimension(crossAxis)) -
child->getLayout().measuredDimension(dimension(crossAxis))),
flexStartEdge(crossAxis));
break;
case Align::Center:
child->setLayoutPosition(
(parent->getLayout().measuredDimension(dimension(crossAxis)) -
child->getLayout().measuredDimension(dimension(crossAxis))) /
2.0f,
flexStartEdge(crossAxis));
break;
}
}

// To ensure no breaking changes, we preserve the legacy way of positioning
// absolute children and determine if we should use it using an errata.
static void positionAbsoluteChildLegacy(
const yoga::Node* const containingNode,
const yoga::Node* const parent,
yoga::Node* child,
Expand Down Expand Up @@ -93,6 +171,106 @@ static void positionAbsoluteChild(
}
}

/*
* Absolutely positioned nodes do not participate in flex layout and thus their
* positions can be determined independently from the rest of their siblings.
* For each axis there are essentially two cases:
*
* 1) The node has insets defined. In this case we can just use these to
* determine the position of the node.
* 2) The node does not have insets defined. In this case we look at the style
* of the parent to position the node. Things like justify content and
* align content will move absolute children around. If none of these
* special properties are defined, the child is positioned at the start
* (defined by flex direction) of the leading flex line.
*
* This function does that positioning for the given axis. The spec has more
* information on this topic: https://www.w3.org/TR/css-flexbox-1/#abspos-items
*/
static void positionAbsoluteChildImpl(
const yoga::Node* const containingNode,
const yoga::Node* const parent,
yoga::Node* child,
const Direction direction,
const FlexDirection axis,
const bool isMainAxis,
const float containingBlockWidth,
const float containingBlockHeight) {
const bool isAxisRow = isRow(axis);
const float containingBlockSize =
isAxisRow ? containingBlockWidth : containingBlockHeight;

// The inline-start position takes priority over the end position in the case
// that they are both set and the node has a fixed width. Thus we only have 2
// cases here: if inline-start is defined and if inline-end is defined.
//
// Despite checking inline-start to honor prioritization of insets, we write
// to the flex-start edge because this algorithm works by positioning on the
// flex-start edge and then filling in the flex-end direction at the end if
// necessary.
if (child->isInlineStartPositionDefined(axis, direction)) {
const float positionRelativeToInlineStart =
child->getInlineStartPosition(axis, direction, containingBlockSize) +
containingNode->getInlineStartBorder(axis, direction) +
child->getInlineStartMargin(axis, direction, containingBlockSize);
const float positionRelativeToFlexStart =
inlineStartEdge(axis, direction) != flexStartEdge(axis)
? getPositionOfOppositeEdge(
positionRelativeToInlineStart, axis, containingNode, child)
: positionRelativeToInlineStart;

child->setLayoutPosition(positionRelativeToFlexStart, flexStartEdge(axis));
} else if (child->isInlineEndPositionDefined(axis, direction)) {
const float positionRelativeToInlineStart =
containingNode->getLayout().measuredDimension(dimension(axis)) -
child->getLayout().measuredDimension(dimension(axis)) -
containingNode->getInlineEndBorder(axis, direction) -
child->getInlineEndMargin(axis, direction, containingBlockSize) -
child->getInlineEndPosition(axis, direction, containingBlockSize);
const float positionRelativeToFlexStart =
inlineStartEdge(axis, direction) != flexStartEdge(axis)
? getPositionOfOppositeEdge(
positionRelativeToInlineStart, axis, containingNode, child)
: positionRelativeToInlineStart;

child->setLayoutPosition(positionRelativeToFlexStart, flexStartEdge(axis));
} else {
isMainAxis ? justifyAbsoluteChild(
parent, child, direction, axis, containingBlockWidth)
: alignAbsoluteChild(
parent, child, direction, axis, containingBlockWidth);
}
}

static void positionAbsoluteChild(
const yoga::Node* const containingNode,
const yoga::Node* const parent,
yoga::Node* child,
const Direction direction,
const FlexDirection axis,
const bool isMainAxis,
const float containingBlockWidth,
const float containingBlockHeight) {
child->hasErrata(Errata::AbsolutePositioning) ? positionAbsoluteChildLegacy(
containingNode,
parent,
child,
direction,
axis,
isMainAxis,
containingBlockWidth,
containingBlockHeight)
: positionAbsoluteChildImpl(
containingNode,
parent,
child,
direction,
axis,
isMainAxis,
containingBlockWidth,
containingBlockHeight);
}

void layoutAbsoluteChild(
const yoga::Node* const containingNode,
const yoga::Node* const node,
Expand Down Expand Up @@ -155,8 +333,8 @@ void layoutAbsoluteChild(
.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 the child doesn't have a specified height, compute the height based
// on the top/bottom offsets if they're defined.
if (child->isFlexStartPositionDefined(FlexDirection::Column, direction) &&
child->isFlexEndPositionDefined(FlexDirection::Column, direction)) {
childHeight =
Expand Down Expand Up @@ -203,9 +381,9 @@ void layoutAbsoluteChild(
: SizingMode::StretchFit;

// 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.
// 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 != SizingMode::MaxContent &&
yoga::isDefined(containingBlockWidth) && containingBlockWidth > 0) {
Expand All @@ -220,8 +398,8 @@ void layoutAbsoluteChild(
direction,
childWidthSizingMode,
childHeightSizingMode,
childWidth,
childHeight,
containingBlockWidth,
containingBlockHeight,
false,
LayoutPassReason::kAbsMeasureChild,
layoutMarkerData,
Expand All @@ -240,8 +418,8 @@ void layoutAbsoluteChild(
direction,
SizingMode::StretchFit,
SizingMode::StretchFit,
childWidth,
childHeight,
containingBlockWidth,
containingBlockHeight,
true,
LayoutPassReason::kAbsLayout,
layoutMarkerData,
Expand Down Expand Up @@ -290,8 +468,10 @@ void layoutAbsoluteDescendants(
containingNode,
currentNode,
child,
containingNode->getLayout().measuredDimension(Dimension::Width),
containingNode->getLayout().measuredDimension(Dimension::Height),
containingNode->getLayout().measuredDimension(Dimension::Width) -
containingNode->getBorderForAxis(FlexDirection::Row),
containingNode->getLayout().measuredDimension(Dimension::Height) -
containingNode->getBorderForAxis(FlexDirection::Column),
widthSizingMode,
currentNodeDirection,
layoutMarkerData,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,25 @@ bool calculateLayoutInternal(
const uint32_t depth,
const uint32_t generationCount);

// Given an offset to an edge, returns the offset to the opposite edge on the
// same axis. This assumes that the width/height of both nodes is determined at
// this point.
inline float getPositionOfOppositeEdge(
float position,
FlexDirection axis,
const yoga::Node* const containingNode,
const yoga::Node* const node) {
return containingNode->getLayout().measuredDimension(dimension(axis)) -
node->getLayout().measuredDimension(dimension(axis)) - position;
}

inline void setChildTrailingPosition(
const yoga::Node* const node,
yoga::Node* const child,
const FlexDirection axis) {
const float size = child->getLayout().measuredDimension(dimension(axis));
child->setLayoutPosition(
node->getLayout().measuredDimension(dimension(axis)) - size -
child->getLayout().position(flexStartEdge(axis)),
getPositionOfOppositeEdge(
child->getLayout().position(flexStartEdge(axis)), axis, node, child),
flexEndEdge(axis));
}

Expand Down
1 change: 1 addition & 0 deletions packages/react-native/ReactCommon/yoga/yoga/enums/Errata.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ enum class Errata : uint32_t {
StretchFlexBasis = YGErrataStretchFlexBasis,
StartingEndingEdgeFromFlexDirection = YGErrataStartingEndingEdgeFromFlexDirection,
PositionStaticBehavesLikeRelative = YGErrataPositionStaticBehavesLikeRelative,
AbsolutePositioning = YGErrataAbsolutePositioning,
All = YGErrataAll,
Classic = YGErrataClassic,
};
Expand Down
5 changes: 5 additions & 0 deletions packages/react-native/ReactCommon/yoga/yoga/node/Node.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,11 @@ float Node::getFlexEndPaddingAndBorder(
getFlexEndBorder(axis, direction);
}

float Node::getBorderForAxis(FlexDirection axis) const {
return getInlineStartBorder(axis, Direction::LTR) +
getInlineEndBorder(axis, Direction::LTR);
}

float Node::getMarginForAxis(FlexDirection axis, float widthSize) const {
// The total margin for a given axis does not depend on the direction
// so hardcoding LTR here to avoid piping direction to this function
Expand Down
1 change: 1 addition & 0 deletions packages/react-native/ReactCommon/yoga/yoga/node/Node.h
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,7 @@ class YG_EXPORT Node : public ::YGNode {
FlexDirection axis,
Direction direction,
float widthSize) const;
float getBorderForAxis(FlexDirection axis) const;
float getMarginForAxis(FlexDirection axis, float widthSize) const;
float getGapForAxis(FlexDirection axis) const;
// Setters
Expand Down
Loading