diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/yoga/YogaErrata.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/yoga/YogaErrata.java index 99f53f5a8187cb..04307de408c503 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/yoga/YogaErrata.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/yoga/YogaErrata.java @@ -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); @@ -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); diff --git a/packages/react-native/ReactCommon/yoga/yoga/YGEnums.cpp b/packages/react-native/ReactCommon/yoga/yoga/YGEnums.cpp index 04fd7be9336bec..d14db83cbf817c 100644 --- a/packages/react-native/ReactCommon/yoga/yoga/YGEnums.cpp +++ b/packages/react-native/ReactCommon/yoga/yoga/YGEnums.cpp @@ -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: diff --git a/packages/react-native/ReactCommon/yoga/yoga/YGEnums.h b/packages/react-native/ReactCommon/yoga/yoga/YGEnums.h index e0454394dae4d8..e6076decefbc81 100644 --- a/packages/react-native/ReactCommon/yoga/yoga/YGEnums.h +++ b/packages/react-native/ReactCommon/yoga/yoga/YGEnums.h @@ -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) diff --git a/packages/react-native/ReactCommon/yoga/yoga/algorithm/AbsoluteLayout.cpp b/packages/react-native/ReactCommon/yoga/yoga/algorithm/AbsoluteLayout.cpp index bf3a3354352260..22574cf805522d 100644 --- a/packages/react-native/ReactCommon/yoga/yoga/algorithm/AbsoluteLayout.cpp +++ b/packages/react-native/ReactCommon/yoga/yoga/algorithm/AbsoluteLayout.cpp @@ -13,23 +13,88 @@ 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->getFlexStartBorder(mainAxis, direction), + 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: + child->setLayoutPosition( + (parent->getLayout().measuredDimension(dimension(mainAxis)) - + child->getLayout().measuredDimension(dimension(mainAxis))) / + 2.0f, + 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->getFlexStartBorder(crossAxis, direction) + + 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, @@ -93,6 +158,109 @@ 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, + containingNode->getLayout().measuredDimension(dimension(axis))) + + 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, @@ -155,8 +323,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 = @@ -203,9 +371,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) { diff --git a/packages/react-native/ReactCommon/yoga/yoga/algorithm/CalculateLayout.h b/packages/react-native/ReactCommon/yoga/yoga/algorithm/CalculateLayout.h index 26f4aa47a9dc6d..955cbdbb594426 100644 --- a/packages/react-native/ReactCommon/yoga/yoga/algorithm/CalculateLayout.h +++ b/packages/react-native/ReactCommon/yoga/yoga/algorithm/CalculateLayout.h @@ -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)); } diff --git a/packages/react-native/ReactCommon/yoga/yoga/enums/Errata.h b/packages/react-native/ReactCommon/yoga/yoga/enums/Errata.h index 134f5fffc012fe..5bda0c0b9c7b57 100644 --- a/packages/react-native/ReactCommon/yoga/yoga/enums/Errata.h +++ b/packages/react-native/ReactCommon/yoga/yoga/enums/Errata.h @@ -20,6 +20,7 @@ enum class Errata : uint32_t { StretchFlexBasis = YGErrataStretchFlexBasis, StartingEndingEdgeFromFlexDirection = YGErrataStartingEndingEdgeFromFlexDirection, PositionStaticBehavesLikeRelative = YGErrataPositionStaticBehavesLikeRelative, + AbsolutePositioning = YGErrataAbsolutePositioning, All = YGErrataAll, Classic = YGErrataClassic, };