diff --git a/packages/react-native/React/Fabric/Surface/RCTFabricSurface.mm b/packages/react-native/React/Fabric/Surface/RCTFabricSurface.mm index 2a5a3a66346f..47273238ff57 100644 --- a/packages/react-native/React/Fabric/Surface/RCTFabricSurface.mm +++ b/packages/react-native/React/Fabric/Surface/RCTFabricSurface.mm @@ -214,7 +214,8 @@ - (void)setMinimumSize:(CGSize)minimumSize maximumSize:(CGSize)maximumSize viewp if (!isnan(viewportOffset.x) && !isnan(viewportOffset.y)) { layoutContext.viewportOffset = RCTPointFromCGPoint(viewportOffset); } - + layoutContext.viewportSize = layoutConstraints.maximumSize; + _surfaceHandler->constraintLayout(layoutConstraints, layoutContext); } @@ -236,6 +237,8 @@ - (CGSize)sizeThatFitsMinimumSize:(CGSize)minimumSize maximumSize:(CGSize)maximu layoutConstraints.minimumSize = RCTSizeFromCGSize(minimumSize); layoutConstraints.maximumSize = RCTSizeFromCGSize(maximumSize); + layoutContext.viewportSize = layoutConstraints.maximumSize; + return RCTCGSizeFromSize(_surfaceHandler->measure(layoutConstraints, layoutContext)); } diff --git a/packages/react-native/React/Views/RCTLayout.m b/packages/react-native/React/Views/RCTLayout.m index 356866a420c6..ea2911874843 100644 --- a/packages/react-native/React/Views/RCTLayout.m +++ b/packages/react-native/React/Views/RCTLayout.m @@ -87,6 +87,8 @@ CGFloat RCTCoreGraphicsFloatFromYogaValue(YGValue value, CGFloat baseFloatValue) case YGUnitFitContent: case YGUnitStretch: return baseFloatValue; + case YGUnitDynamic: + return RCTCoreGraphicsFloatFromYogaFloat(YGUndefined); } } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/yoga/YogaUnit.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/yoga/YogaUnit.java index 5731a040d93d..54238a9b029b 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/yoga/YogaUnit.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/yoga/YogaUnit.java @@ -16,7 +16,8 @@ public enum YogaUnit { AUTO(3), MAX_CONTENT(4), FIT_CONTENT(5), - STRETCH(6); + STRETCH(6), + DYNAMIC(7); private final int mIntValue; @@ -37,6 +38,7 @@ public static YogaUnit fromInt(int value) { case 4: return MAX_CONTENT; case 5: return FIT_CONTENT; case 6: return STRETCH; + case 7: return DYNAMIC; default: throw new IllegalArgumentException("Unknown enum value: " + value); } } diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/SurfaceHandlerBinding.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/SurfaceHandlerBinding.cpp index c0f5df5ccb48..3264f113963d 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/SurfaceHandlerBinding.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/SurfaceHandlerBinding.cpp @@ -60,6 +60,7 @@ void SurfaceHandlerBinding::setLayoutConstraints( context.swapLeftAndRightInRTL = (doLeftAndRightSwapInRTL != 0u); context.pointScaleFactor = pixelDensity; context.viewportOffset = {.x = offsetX, .y = offsetY}; + context.viewportSize = {.width = maxWidth, .height = maxHeight}; context.fontSizeMultiplier = fontScale; surfaceHandler_.constraintLayout(constraints, context); diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/YogaLayoutableShadowNode.cpp b/packages/react-native/ReactCommon/react/renderer/components/view/YogaLayoutableShadowNode.cpp index c630cb420188..6cd8ec2417b0 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/YogaLayoutableShadowNode.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/view/YogaLayoutableShadowNode.cpp @@ -143,7 +143,9 @@ YogaLayoutableShadowNode::YogaLayoutableShadowNode( } if (fragment.props) { - updateYogaProps(); + auto& sourceProps = + static_cast(*sourceShadowNode.getProps()); + updateYogaProps(sourceProps.calcExpressions); } if (fragment.children) { @@ -375,15 +377,18 @@ void YogaLayoutableShadowNode::updateYogaChildren() { yogaNode_.setDirty(!isClean); } -void YogaLayoutableShadowNode::updateYogaProps() { +void YogaLayoutableShadowNode::updateYogaProps( + const CalcExpressions& previousCalcExpressions) { ensureUnsealed(); auto& props = static_cast(*props_); auto styleResult = applyAliasedProps(props.yogaStyle, props); // Resetting `dirty` flag only if `yogaStyle` portion of `Props` was - // changed. - if (!YGNodeIsDirty(&yogaNode_) && (styleResult != yogaNode_.style())) { + // changed or calc expressions changed. + if (!YGNodeIsDirty(&yogaNode_) && + (props.calcExpressions != previousCalcExpressions || + styleResult != yogaNode_.style())) { yogaNode_.setDirty(true); } @@ -888,6 +893,29 @@ YogaLayoutableShadowNode& YogaLayoutableShadowNode::shadowNodeFromContext( *static_cast(YGNodeGetContext(yogaNode))); } +YGValue YogaLayoutableShadowNode::yogaNodeCalcValueResolver( + YGNodeConstRef yogaNode, + YGValueDynamicID id, + YGValueDynamicContext context) { + if (!yogaNode) { + return {}; + } + + auto& node = shadowNodeFromContext(yogaNode); + auto& props = static_cast(*node.props_); + if (!props.calcExpressions.contains(id)) { + return {}; + } + + auto& calc = props.calcExpressions.at(id); + return YGValue( + calc.resolve( + context.referenceLength, + threadLocalLayoutContext.viewportSize.width, + threadLocalLayoutContext.viewportSize.height), + YGUnitPoint); +}; + yoga::Config& YogaLayoutableShadowNode::initializeYogaConfig( yoga::Config& config, YGConfigConstRef previousConfig) { diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/YogaLayoutableShadowNode.h b/packages/react-native/ReactCommon/react/renderer/components/view/YogaLayoutableShadowNode.h index 25714d5f489d..5a4993821e99 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/YogaLayoutableShadowNode.h +++ b/packages/react-native/ReactCommon/react/renderer/components/view/YogaLayoutableShadowNode.h @@ -53,7 +53,7 @@ class YogaLayoutableShadowNode : public LayoutableShadowNode { void updateYogaChildren(); - void updateYogaProps(); + void updateYogaProps(const CalcExpressions& previousCalcExpressions = {}); /* * Sets layoutable size of node. @@ -82,6 +82,11 @@ class YogaLayoutableShadowNode : public LayoutableShadowNode { Rect getContentBounds() const; + static YGValue yogaNodeCalcValueResolver( + YGNodeConstRef yogaNode, + YGValueDynamicID id, + YGValueDynamicContext context); + protected: /** * Subclasses which provide MeasurableYogaNode may override to signal that a diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/YogaStylableProps.cpp b/packages/react-native/ReactCommon/react/renderer/components/view/YogaStylableProps.cpp index ae2da15fc0a4..bae0e4938ae8 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/YogaStylableProps.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/view/YogaStylableProps.cpp @@ -12,13 +12,23 @@ #include #include #include +#include #include #include +#include "YogaLayoutableShadowNode.h" #include "conversions.h" namespace facebook::react { +YGValue yogaNodeCalcValueResolver( + YGNodeConstRef yogaNode, + YGValueDynamicID id, + YGValueDynamicContext context) { + return YogaLayoutableShadowNode::yogaNodeCalcValueResolver( + yogaNode, id, context); +} + YogaStylableProps::YogaStylableProps( const PropsParserContext& context, const YogaStylableProps& sourceProps, @@ -31,6 +41,8 @@ YogaStylableProps::YogaStylableProps( ? sourceProps.yogaStyle : convertRawProp(context, rawProps, sourceProps.yogaStyle); + calcExpressions = + buildCalcExpressions(context, rawProps, sourceProps.calcExpressions); if (!ReactNativeFeatureFlags::enableCppPropsIteratorSetter()) { convertRawPropAliases(context, sourceProps, rawProps); } @@ -121,6 +133,119 @@ static inline const T getFieldValue( REBUILD_YG_FIELD_SWITCH_CASE_INDEXED( \ position, setPosition, yoga::Edge::All, "inset"); +#define APPLY_CALC_COMMON(fieldName, setPoints, setPercent, setDynamic) \ + { \ + if (const auto* rawValue = rawProps.at(fieldName, nullptr, nullptr)) { \ + const auto& value = *rawValue; \ + if (value.hasType()) { \ + constexpr auto key = fnv1a(fieldName); \ + auto isCalcExpression{false}; \ + auto parsed = parseCSSProperty((std::string)value); \ + if (std::holds_alternative(parsed)) { \ + auto calc = std::get(parsed); \ + if (calc.isPointsOnly()) { \ + setPoints(calc.px); \ + } else if (calc.isPercentOnly()) { \ + setPercent(calc.percent); \ + } else { \ + setDynamic(key); \ + calcExpressions[key] = std::move(calc); \ + isCalcExpression = true; \ + } \ + } \ + if (!isCalcExpression && calcExpressions.count(key)) { \ + calcExpressions.erase(key); \ + } \ + } \ + } \ + } + +#define APPLY_CALC_YG_INDEXED(setter, index, fieldName, LengthType) \ + APPLY_CALC_COMMON( \ + fieldName, \ + [&](float points) { \ + yogaStyle.setter(index, LengthType::points(points)); \ + }, \ + [&](float percent) { \ + yogaStyle.setter(index, LengthType::percent(percent)); \ + }, \ + [&](YGValueDynamicID dynamicId) { \ + yogaStyle.setter( \ + index, \ + LengthType::dynamic(&yogaNodeCalcValueResolver, dynamicId)); \ + }) + +#define APPLY_CALC_YG_FIELD(setter, fieldName, LengthType) \ + APPLY_CALC_COMMON( \ + fieldName, \ + [&](float points) { yogaStyle.setter(LengthType::points(points)); }, \ + [&](float percent) { yogaStyle.setter(LengthType::percent(percent)); }, \ + [&](YGValueDynamicID dynamicId) { \ + yogaStyle.setter( \ + LengthType::dynamic(&yogaNodeCalcValueResolver, dynamicId)); \ + }) + +#define APPLY_CALC_YG_DIMENSION(setter, widthStr, heightStr) \ + APPLY_CALC_YG_INDEXED( \ + setter, yoga::Dimension::Width, widthStr, yoga::StyleSizeLength) \ + APPLY_CALC_YG_INDEXED( \ + setter, yoga::Dimension::Height, heightStr, yoga::StyleSizeLength) + +#define APPLY_CALC_YG_EDGES_MARGIN(setter, LengthType, prefix) \ + APPLY_CALC_YG_INDEXED(setter, yoga::Edge::Left, prefix "Left", LengthType) \ + APPLY_CALC_YG_INDEXED(setter, yoga::Edge::Top, prefix "Top", LengthType) \ + APPLY_CALC_YG_INDEXED(setter, yoga::Edge::Right, prefix "Right", LengthType) \ + APPLY_CALC_YG_INDEXED( \ + setter, yoga::Edge::Bottom, prefix "Bottom", LengthType) \ + APPLY_CALC_YG_INDEXED(setter, yoga::Edge::Start, prefix "Start", LengthType) \ + APPLY_CALC_YG_INDEXED(setter, yoga::Edge::End, prefix "End", LengthType) \ + APPLY_CALC_YG_INDEXED( \ + setter, yoga::Edge::Horizontal, prefix "Horizontal", LengthType) \ + APPLY_CALC_YG_INDEXED( \ + setter, yoga::Edge::Vertical, prefix "Vertical", LengthType) \ + APPLY_CALC_YG_INDEXED(setter, yoga::Edge::All, prefix, LengthType) + +#define APPLY_CALC_YG_EDGES_PADDING(setter, LengthType, prefix) \ + APPLY_CALC_YG_INDEXED(setter, yoga::Edge::Left, prefix "Left", LengthType) \ + APPLY_CALC_YG_INDEXED(setter, yoga::Edge::Top, prefix "Top", LengthType) \ + APPLY_CALC_YG_INDEXED(setter, yoga::Edge::Right, prefix "Right", LengthType) \ + APPLY_CALC_YG_INDEXED( \ + setter, yoga::Edge::Bottom, prefix "Bottom", LengthType) \ + APPLY_CALC_YG_INDEXED(setter, yoga::Edge::Start, prefix "Start", LengthType) \ + APPLY_CALC_YG_INDEXED(setter, yoga::Edge::End, prefix "End", LengthType) \ + APPLY_CALC_YG_INDEXED( \ + setter, yoga::Edge::Horizontal, prefix "Horizontal", LengthType) \ + APPLY_CALC_YG_INDEXED( \ + setter, yoga::Edge::Vertical, prefix "Vertical", LengthType) \ + APPLY_CALC_YG_INDEXED(setter, yoga::Edge::All, prefix, LengthType) + +#define APPLY_CALC_YG_EDGES_POSITION() \ + APPLY_CALC_YG_INDEXED( \ + setPosition, yoga::Edge::Left, "left", yoga::StyleLength) \ + APPLY_CALC_YG_INDEXED( \ + setPosition, yoga::Edge::Top, "top", yoga::StyleLength) \ + APPLY_CALC_YG_INDEXED( \ + setPosition, yoga::Edge::Right, "right", yoga::StyleLength) \ + APPLY_CALC_YG_INDEXED( \ + setPosition, yoga::Edge::Bottom, "bottom", yoga::StyleLength) \ + APPLY_CALC_YG_INDEXED( \ + setPosition, yoga::Edge::Start, "start", yoga::StyleLength) \ + APPLY_CALC_YG_INDEXED( \ + setPosition, yoga::Edge::End, "end", yoga::StyleLength) \ + APPLY_CALC_YG_INDEXED( \ + setPosition, yoga::Edge::Horizontal, "insetInline", yoga::StyleLength) \ + APPLY_CALC_YG_INDEXED( \ + setPosition, yoga::Edge::Vertical, "insetBlock", yoga::StyleLength) \ + APPLY_CALC_YG_INDEXED( \ + setPosition, yoga::Edge::All, "inset", yoga::StyleLength) + +#define APPLY_CALC_YG_GUTTER() \ + APPLY_CALC_YG_INDEXED( \ + setGap, yoga::Gutter::Row, "rowGap", yoga::StyleLength) \ + APPLY_CALC_YG_INDEXED( \ + setGap, yoga::Gutter::Column, "columnGap", yoga::StyleLength) \ + APPLY_CALC_YG_INDEXED(setGap, yoga::Gutter::All, "gap", yoga::StyleLength) + void YogaStylableProps::setProp( const PropsParserContext& context, RawPropsPropNameHash hash, @@ -522,4 +647,20 @@ void YogaStylableProps::convertRawPropAliases( yoga::StyleLength::undefined()); } +CalcExpressions YogaStylableProps::buildCalcExpressions( + const PropsParserContext& context, + const RawProps& rawProps, + const CalcExpressions& defaultValue) { + auto calcExpressions = defaultValue; + APPLY_CALC_YG_DIMENSION(setDimension, "width", "height") + APPLY_CALC_YG_DIMENSION(setMinDimension, "minWidth", "minHeight") + APPLY_CALC_YG_DIMENSION(setMaxDimension, "maxWidth", "maxHeight") + APPLY_CALC_YG_FIELD(setFlexBasis, "flexBasis", yoga::StyleSizeLength) + APPLY_CALC_YG_GUTTER() + APPLY_CALC_YG_EDGES_POSITION() + APPLY_CALC_YG_EDGES_MARGIN(setMargin, yoga::StyleLength, "margin") + APPLY_CALC_YG_EDGES_PADDING(setPadding, yoga::StyleLength, "padding") + return calcExpressions; +} + } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/YogaStylableProps.h b/packages/react-native/ReactCommon/react/renderer/components/view/YogaStylableProps.h index 6febf08d5ce4..2c5cc67cb25c 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/YogaStylableProps.h +++ b/packages/react-native/ReactCommon/react/renderer/components/view/YogaStylableProps.h @@ -9,8 +9,10 @@ #include +#include #include #include +#include #include namespace facebook::react { @@ -57,6 +59,8 @@ class YogaStylableProps : public Props { yoga::Style::Length paddingBlockStart; yoga::Style::Length paddingBlockEnd; + CalcExpressions calcExpressions; + #if RN_DEBUG_STRING_CONVERTIBLE #pragma mark - DebugStringConvertible (Partial) @@ -70,6 +74,11 @@ class YogaStylableProps : public Props { const PropsParserContext &context, const YogaStylableProps &sourceProps, const RawProps &rawProps); + + CalcExpressions buildCalcExpressions( + const PropsParserContext &context, + const RawProps &rawProps, + const CalcExpressions &defaultValue); }; } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/primitives.h b/packages/react-native/ReactCommon/react/renderer/components/view/primitives.h index 0bcd3ca6f8bf..1bf4f69ecea1 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/primitives.h +++ b/packages/react-native/ReactCommon/react/renderer/components/view/primitives.h @@ -7,6 +7,7 @@ #pragma once +#include #include #include #include @@ -16,6 +17,7 @@ #include #include #include +#include namespace facebook::react { @@ -248,4 +250,6 @@ struct BorderMetrics { bool operator==(const BorderMetrics &rhs) const = default; }; +using CalcExpressions = std::unordered_map; + } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/core/LayoutContext.h b/packages/react-native/ReactCommon/react/renderer/core/LayoutContext.h index 3709f7902e7e..f54f90159d40 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/LayoutContext.h +++ b/packages/react-native/ReactCommon/react/renderer/core/LayoutContext.h @@ -59,6 +59,11 @@ struct LayoutContext { * If React Native takes up entire screen, it will be {0, 0}. */ Point viewportOffset{}; + + /* + * Viewport size is size of the React Native's root view. + */ + Size viewportSize{}; }; inline bool operator==(const LayoutContext &lhs, const LayoutContext &rhs) @@ -68,13 +73,15 @@ inline bool operator==(const LayoutContext &lhs, const LayoutContext &rhs) lhs.affectedNodes, lhs.swapLeftAndRightInRTL, lhs.fontSizeMultiplier, - lhs.viewportOffset) == + lhs.viewportOffset, + lhs.viewportSize) == std::tie( rhs.pointScaleFactor, rhs.affectedNodes, rhs.swapLeftAndRightInRTL, rhs.fontSizeMultiplier, - rhs.viewportOffset); + rhs.viewportOffset, + rhs.viewportSize); } } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/core/graphicsConversions.h b/packages/react-native/ReactCommon/react/renderer/core/graphicsConversions.h index ccc6510b140c..77044d5aff04 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/graphicsConversions.h +++ b/packages/react-native/ReactCommon/react/renderer/core/graphicsConversions.h @@ -69,6 +69,10 @@ inline folly::dynamic toDynamic(const YGValue &dimension) return dimension.value; case YGUnitPercent: return std::format("{}%", dimension.value); + case YGUnitDynamic: + // YGValue do not support YGUnitDynamic yet. + // Return placeholder that won't parse as valid calc. + return "calc(dynamic)"; } return nullptr; diff --git a/packages/react-native/ReactCommon/react/renderer/css/CSSCalc.h b/packages/react-native/ReactCommon/react/renderer/css/CSSCalc.h new file mode 100644 index 000000000000..f9ecc402df68 --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/css/CSSCalc.h @@ -0,0 +1,350 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include + +namespace facebook::react { + +/** + * Representation of CSS calc() function. + * https://www.w3.org/TR/css-values-4/#calc-func + */ +struct CSSCalc { + float px{0.0f}; + float percent{0.0f}; + float vw{0.0f}; + float vh{0.0f}; + bool unitless{false}; + + constexpr bool operator==(const CSSCalc &rhs) const = default; + + constexpr CSSCalc operator+(const CSSCalc &rhs) const -> + { + return CSSCalc{ + px + rhs.px, + percent + rhs.percent, + vw + rhs.vw, + vh + rhs.vh, + unitless && rhs.unitless}; + } + + constexpr CSSCalc operator-(const CSSCalc &rhs) const + { + return CSSCalc{ + px - rhs.px, + percent - rhs.percent, + vw - rhs.vw, + vh - rhs.vh, + unitless && rhs.unitless}; + } + + constexpr CSSCalc operator*(float scalar) const + { + return CSSCalc{ + px * scalar, percent * scalar, vw * scalar, vh * scalar, unitless}; + } + + constexpr CSSCalc operator/(float scalar) const + { + if (scalar == 0.0f) { + return CSSCalc{}; + } + return CSSCalc{ + px / scalar, percent / scalar, vw / scalar, vh / scalar, unitless}; + } + + constexpr CSSCalc operator-() const + { + return CSSCalc{-px, -percent, -vw, -vh, unitless}; + } + + float resolve(float percentRef, float viewportWidth, float viewportHeight) + const + { + return px + (percent * percentRef * 0.01f) + (vw * viewportWidth * 0.01f) + + (vh * viewportHeight * 0.01f); + } + + constexpr bool isUnitless() const + { + return unitless; + } + + constexpr bool isPointsOnly() const + { + return percent == 0.0f && vw == 0.0f && vh == 0.0f && !unitless; + } + + constexpr bool isPercentOnly() const + { + return px == 0.0f && vw == 0.0f && vh == 0.0f && !unitless; + } + + constexpr bool isZero() const + { + return px == 0.0f && percent == 0.0f && vw == 0.0f && vh == 0.0f; + } + + static constexpr CSSCalc fromNumber(float value) + { + return CSSCalc{value, 0.0f, 0.0f, 0.0f, true}; + } + + static constexpr CSSCalc fromPoints(float value) + { + return CSSCalc{value, 0.0f, 0.0f, 0.0f, false}; + } + + static constexpr CSSCalc fromPercent(float value) + { + return CSSCalc{0.0f, value, 0.0f, 0.0f, false}; + } + + static constexpr CSSCalc fromVw(float value) + { + return CSSCalc{0.0f, 0.0f, value, 0.0f, false}; + } + + static constexpr CSSCalc fromVh(float value) + { + return CSSCalc{0.0f, 0.0f, 0.0f, value, false}; + } + + static constexpr std::optional fromLength(float value, CSSLengthUnit unit) + { + switch (unit) { + case CSSLengthUnit::Px: + return fromPoints(value); + case CSSLengthUnit::Vw: + return fromVw(value); + case CSSLengthUnit::Vh: + return fromVh(value); + default: + return std::nullopt; + } + } +}; + +template <> +struct CSSDataTypeParser { + static constexpr auto consumeFunctionBlock( + const CSSFunctionBlock &func, + CSSValueParser &parser) -> std::optional + { + if (!iequals(func.name, "calc")) { + return std::nullopt; + } + + return parseCalcExpression(parser); + } + + static constexpr auto parseCalcExpression(CSSValueParser &parser) + -> std::optional + { + parser.syntaxParser().consumeWhitespace(); + auto result = parseAddSub(parser); + parser.syntaxParser().consumeWhitespace(); + return result; + } + + static constexpr auto consumeSimpleBlock( + const CSSSimpleBlock &block, + CSSValueParser &parser) -> std::optional + { + if (block.openBracketType != CSSTokenType::OpenParen) { + return std::nullopt; + } + + return parseCalcContents(parser); + } + + private: + static constexpr auto parseAddSub(CSSValueParser &parser) + -> std::optional + { + auto left = parseMulDiv(parser); + if (!left) { + return std::nullopt; + } + + while (true) { + auto savedParser = parser.syntaxParser(); + parser.syntaxParser().consumeWhitespace(); + + auto opResult = + parser.syntaxParser().consumeComponentValue>( + CSSDelimiter::None, [](const CSSPreservedToken &token) { + if (token.type() == CSSTokenType::Delim) { + auto sv = token.stringValue(); + if (!sv.empty() && (sv[0] == '+' || sv[0] == '-')) { + return std::optional{sv[0]}; + } + } + return std::optional{}; + }); + + if (!opResult) { + parser.syntaxParser() = savedParser; + break; + } + + parser.syntaxParser().consumeWhitespace(); + auto right = parseMulDiv(parser); + if (!right) { + return std::nullopt; + } + + if (left->isUnitless() != right->isUnitless()) { + return std::nullopt; + } + + if (*opResult == '+') { + left = *left + *right; + } else { + left = *left - *right; + } + } + + return left; + } + + static constexpr auto parseMulDiv(CSSValueParser &parser) + -> std::optional + { + auto left = parseUnary(parser); + if (!left) { + return std::nullopt; + } + + while (true) { + auto savedParser = parser.syntaxParser(); + parser.syntaxParser().consumeWhitespace(); + + auto opResult = + parser.syntaxParser().consumeComponentValue>( + CSSDelimiter::None, [](const CSSPreservedToken &token) { + if (token.type() == CSSTokenType::Delim) { + auto sv = token.stringValue(); + if (!sv.empty() && (sv[0] == '*' || sv[0] == '/')) { + return std::optional{sv[0]}; + } + } + return std::optional{}; + }); + + if (!opResult) { + parser.syntaxParser() = savedParser; + break; + } + + parser.syntaxParser().consumeWhitespace(); + auto right = parseUnary(parser); + if (!right) { + return std::nullopt; + } + + if (*opResult == '*') { + if (right->isUnitless()) { + left = *left * right->px; + } else if (left->isUnitless()) { + float scalar = left->px; + left = *right * scalar; + } else { + return std::nullopt; + } + } else { + if (!right->isUnitless() || right->px == 0.0f) { + return std::nullopt; + } + left = *left / right->px; + } + } + + return left; + } + + static constexpr auto parseUnary(CSSValueParser &parser) + -> std::optional + { + auto savedParser = parser.syntaxParser(); + + auto opResult = + parser.syntaxParser().consumeComponentValue>( + CSSDelimiter::None, [](const CSSPreservedToken &token) { + if (token.type() == CSSTokenType::Delim) { + auto sv = token.stringValue(); + if (!sv.empty() && (sv[0] == '+' || sv[0] == '-')) { + return std::optional{sv[0]}; + } + } + return std::optional{}; + }); + + if (opResult) { + parser.syntaxParser().consumeWhitespace(); + auto value = parseUnary(parser); + if (!value) { + return std::nullopt; + } + return *opResult == '-' ? -*value : *value; + } + + parser.syntaxParser() = savedParser; + return parsePrimary(parser); + } + + static constexpr auto parsePrimary(CSSValueParser &parser) + -> std::optional + { + auto value = + parser.parseNextValue(); + + if (std::holds_alternative(value)) { + return CSSCalc::fromNumber(std::get(value).value); + } + + if (std::holds_alternative(value)) { + return CSSCalc::fromPercent(std::get(value).value); + } + + if (std::holds_alternative(value)) { + const auto &length = std::get(value); + return CSSCalc::fromLength(length.value, length.unit); + } + + if (std::holds_alternative(value)) { + return std::get(value); + } + + return std::nullopt; + } + + static constexpr auto parseCalcContents(CSSValueParser &parser) + -> std::optional + { + parser.syntaxParser().consumeWhitespace(); + auto result = parseAddSub(parser); + parser.syntaxParser().consumeWhitespace(); + return result; + } +}; + +static_assert(CSSDataType); + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/css/tests/CSSCalcTest.cpp b/packages/react-native/ReactCommon/react/renderer/css/tests/CSSCalcTest.cpp new file mode 100644 index 000000000000..dbbb3b33e290 --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/css/tests/CSSCalcTest.cpp @@ -0,0 +1,419 @@ +/* + * 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::react { + +inline std::optional parseCalc(std::string_view css) { + auto result = parseCSSProperty(css); + if (std::holds_alternative(result)) { + return std::get(result); + } + return std::nullopt; +} + +TEST(CSSCalc, simple_pixel_value) { + auto result = parseCalc("calc(10px)"); + ASSERT_TRUE(result.has_value()); + EXPECT_FLOAT_EQ(result->px, 10.0f); + EXPECT_FLOAT_EQ(result->percent, 0.0f); + EXPECT_FLOAT_EQ(result->vw, 0.0f); + EXPECT_FLOAT_EQ(result->vh, 0.0f); +} + +TEST(CSSCalc, simple_percentage_value) { + auto result = parseCalc("calc(50%)"); + ASSERT_TRUE(result.has_value()); + EXPECT_FLOAT_EQ(result->px, 0.0f); + EXPECT_FLOAT_EQ(result->percent, 50.0f); + EXPECT_FLOAT_EQ(result->vw, 0.0f); + EXPECT_FLOAT_EQ(result->vh, 0.0f); +} + +TEST(CSSCalc, simple_vw_value) { + auto result = parseCalc("calc(100vw)"); + ASSERT_TRUE(result.has_value()); + EXPECT_FLOAT_EQ(result->px, 0.0f); + EXPECT_FLOAT_EQ(result->percent, 0.0f); + EXPECT_FLOAT_EQ(result->vw, 100.0f); + EXPECT_FLOAT_EQ(result->vh, 0.0f); +} + +TEST(CSSCalc, simple_vh_value) { + auto result = parseCalc("calc(100vh)"); + ASSERT_TRUE(result.has_value()); + EXPECT_FLOAT_EQ(result->px, 0.0f); + EXPECT_FLOAT_EQ(result->percent, 0.0f); + EXPECT_FLOAT_EQ(result->vw, 0.0f); + EXPECT_FLOAT_EQ(result->vh, 100.0f); +} + +TEST(CSSCalc, addition_same_units) { + auto result = parseCalc("calc(10px + 20px)"); + ASSERT_TRUE(result.has_value()); + EXPECT_FLOAT_EQ(result->px, 30.0f); + EXPECT_FLOAT_EQ(result->percent, 0.0f); +} + +TEST(CSSCalc, subtraction_same_units) { + auto result = parseCalc("calc(50% - 20%)"); + ASSERT_TRUE(result.has_value()); + EXPECT_FLOAT_EQ(result->px, 0.0f); + EXPECT_FLOAT_EQ(result->percent, 30.0f); +} + +TEST(CSSCalc, mixed_units_addition) { + auto result = parseCalc("calc(100% - 20px)"); + ASSERT_TRUE(result.has_value()); + EXPECT_FLOAT_EQ(result->px, -20.0f); + EXPECT_FLOAT_EQ(result->percent, 100.0f); + EXPECT_FLOAT_EQ(result->vw, 0.0f); + EXPECT_FLOAT_EQ(result->vh, 0.0f); +} + +TEST(CSSCalc, mixed_units_complex) { + auto result = parseCalc("calc(50% + 10px - 5vw + 2vh)"); + ASSERT_TRUE(result.has_value()); + EXPECT_FLOAT_EQ(result->px, 10.0f); + EXPECT_FLOAT_EQ(result->percent, 50.0f); + EXPECT_FLOAT_EQ(result->vw, -5.0f); + EXPECT_FLOAT_EQ(result->vh, 2.0f); +} + +TEST(CSSCalc, multiplication_by_number) { + auto result = parseCalc("calc(50% * 2)"); + ASSERT_TRUE(result.has_value()); + EXPECT_FLOAT_EQ(result->px, 0.0f); + EXPECT_FLOAT_EQ(result->percent, 100.0f); +} + +TEST(CSSCalc, number_times_unit) { + auto result = parseCalc("calc(2 * 50%)"); + ASSERT_TRUE(result.has_value()); + EXPECT_FLOAT_EQ(result->px, 0.0f); + EXPECT_FLOAT_EQ(result->percent, 100.0f); +} + +TEST(CSSCalc, chained_unitless_products_then_length) { + auto result = parseCalc("calc(2 * 3 * 10px)"); + ASSERT_TRUE(result.has_value()); + EXPECT_FLOAT_EQ(result->px, 60.0f); + EXPECT_FLOAT_EQ(result->percent, 0.0f); +} + +TEST(CSSCalc, unitless_division_then_length_multiplication) { + auto result = parseCalc("calc(10 / 2 * 5px)"); + ASSERT_TRUE(result.has_value()); + EXPECT_FLOAT_EQ(result->px, 25.0f); + EXPECT_FLOAT_EQ(result->percent, 0.0f); +} + +TEST(CSSCalc, division_by_number) { + auto result = parseCalc("calc(100px / 4)"); + ASSERT_TRUE(result.has_value()); + EXPECT_FLOAT_EQ(result->px, 25.0f); + EXPECT_FLOAT_EQ(result->percent, 0.0f); +} + +TEST(CSSCalc, complex_expression_with_precedence) { + auto result = parseCalc("calc((100% - 20px) * 2)"); + ASSERT_TRUE(result.has_value()); + EXPECT_FLOAT_EQ(result->px, -40.0f); + EXPECT_FLOAT_EQ(result->percent, 200.0f); +} + +TEST(CSSCalc, operator_precedence_mul_before_add) { + auto result = parseCalc("calc(10px + 20px * 2)"); + ASSERT_TRUE(result.has_value()); + EXPECT_FLOAT_EQ(result->px, 50.0f); +} + +TEST(CSSCalc, nested_parentheses) { + auto result = parseCalc("calc(((10px)))"); + ASSERT_TRUE(result.has_value()); + EXPECT_FLOAT_EQ(result->px, 10.0f); +} + +TEST(CSSCalc, negative_values) { + auto result = parseCalc("calc(-10px)"); + ASSERT_TRUE(result.has_value()); + EXPECT_FLOAT_EQ(result->px, -10.0f); +} + +TEST(CSSCalc, unary_plus) { + auto result = parseCalc("calc(+20px)"); + ASSERT_TRUE(result.has_value()); + EXPECT_FLOAT_EQ(result->px, 20.0f); +} + +TEST(CSSCalc, resolve_simple_percentage) { + auto result = parseCalc("calc(50%)"); + ASSERT_TRUE(result.has_value()); + float resolved = result->resolve(200.0f, 0.0f, 0.0f); + EXPECT_FLOAT_EQ(resolved, 100.0f); +} + +TEST(CSSCalc, resolve_mixed_units) { + auto result = parseCalc("calc(100% - 20px)"); + ASSERT_TRUE(result.has_value()); + float resolved = result->resolve(200.0f, 0.0f, 0.0f); + EXPECT_FLOAT_EQ(resolved, 180.0f); +} + +TEST(CSSCalc, resolve_with_viewport_units) { + auto result = parseCalc("calc(50vw + 10vh)"); + ASSERT_TRUE(result.has_value()); + float resolved = result->resolve(0.0f, 400.0f, 800.0f); + EXPECT_FLOAT_EQ(resolved, 280.0f); +} + +TEST(CSSCalc, resolve_all_units) { + auto result = parseCalc("calc(10px + 25% + 10vw + 5vh)"); + ASSERT_TRUE(result.has_value()); + float resolved = result->resolve(100.0f, 200.0f, 400.0f); + EXPECT_FLOAT_EQ(resolved, 75.0f); +} + +TEST(CSSCalc, invalid_expression_empty) { + auto result = parseCalc("calc()"); + EXPECT_FALSE(result.has_value()); +} + +TEST(CSSCalc, invalid_multiplication_of_units) { + auto result = parseCalc("calc(10px * 20px)"); + EXPECT_FALSE(result.has_value()); +} + +TEST(CSSCalc, invalid_division_by_unit) { + auto result = parseCalc("calc(100px / 10px)"); + EXPECT_FALSE(result.has_value()); +} + +TEST(CSSCalc, division_by_zero) { + auto result = parseCalc("calc(100px / 0)"); + EXPECT_FALSE(result.has_value()); +} + +TEST(CSSCalc, invalid_addition_of_number_and_length) { + auto result = parseCalc("calc(1 + 10px)"); + EXPECT_FALSE(result.has_value()); +} + +TEST(CSSCalc, invalid_subtraction_of_percent_and_number) { + auto result = parseCalc("calc(100% - 2)"); + EXPECT_FALSE(result.has_value()); +} + +TEST(CSSCalc, invalid_trailing_operator) { + auto result = parseCalc("calc(10px +)"); + EXPECT_FALSE(result.has_value()); +} + +TEST(CSSCalc, invalid_missing_rhs_in_group) { + auto result = parseCalc("calc((10px +))"); + EXPECT_FALSE(result.has_value()); +} + +TEST(CSSCalc, zero_is_parsed_as_number) { + auto result = parseCalc("calc(0 + 10px)"); + EXPECT_FALSE(result.has_value()); +} + +TEST(CSSCalc, whitespace_handling) { + auto result = parseCalc("calc( 100% - 20px )"); + ASSERT_TRUE(result.has_value()); + EXPECT_FLOAT_EQ(result->px, -20.0f); + EXPECT_FLOAT_EQ(result->percent, 100.0f); +} + +TEST(CSSCalc, case_insensitive) { + auto result = parseCalc("CALC(10PX + 5VW)"); + ASSERT_TRUE(result.has_value()); + EXPECT_FLOAT_EQ(result->px, 10.0f); + EXPECT_FLOAT_EQ(result->vw, 5.0f); +} + +TEST(CSSCalc, addition_operator) { + CSSCalc a{10.0f, 20.0f, 5.0f, 2.0f, false}; + CSSCalc b{5.0f, 10.0f, 3.0f, 1.0f, false}; + auto result = a + b; + EXPECT_FLOAT_EQ(result.px, 15.0f); + EXPECT_FLOAT_EQ(result.percent, 30.0f); + EXPECT_FLOAT_EQ(result.vw, 8.0f); + EXPECT_FLOAT_EQ(result.vh, 3.0f); +} + +TEST(CSSCalc, subtraction_operator) { + CSSCalc a{10.0f, 20.0f, 5.0f, 2.0f, false}; + CSSCalc b{5.0f, 10.0f, 3.0f, 1.0f, false}; + auto result = a - b; + EXPECT_FLOAT_EQ(result.px, 5.0f); + EXPECT_FLOAT_EQ(result.percent, 10.0f); + EXPECT_FLOAT_EQ(result.vw, 2.0f); + EXPECT_FLOAT_EQ(result.vh, 1.0f); +} + +TEST(CSSCalc, multiplication_operator) { + CSSCalc a{10.0f, 20.0f, 5.0f, 2.0f, false}; + auto result = a * 2.0f; + EXPECT_FLOAT_EQ(result.px, 20.0f); + EXPECT_FLOAT_EQ(result.percent, 40.0f); + EXPECT_FLOAT_EQ(result.vw, 10.0f); + EXPECT_FLOAT_EQ(result.vh, 4.0f); +} + +TEST(CSSCalc, division_operator) { + CSSCalc a{20.0f, 40.0f, 10.0f, 4.0f, false}; + auto result = a / 2.0f; + EXPECT_FLOAT_EQ(result.px, 10.0f); + EXPECT_FLOAT_EQ(result.percent, 20.0f); + EXPECT_FLOAT_EQ(result.vw, 5.0f); + EXPECT_FLOAT_EQ(result.vh, 2.0f); +} + +TEST(CSSCalc, negation_operator) { + CSSCalc a{10.0f, 20.0f, 5.0f, 2.0f, false}; + auto result = -a; + EXPECT_FLOAT_EQ(result.px, -10.0f); + EXPECT_FLOAT_EQ(result.percent, -20.0f); + EXPECT_FLOAT_EQ(result.vw, -5.0f); + EXPECT_FLOAT_EQ(result.vh, -2.0f); +} + +TEST(CSSCalc, is_unitless) { + auto number = CSSCalc::fromNumber(10.0f); + EXPECT_TRUE(number.isUnitless()); + + auto points = CSSCalc::fromPoints(10.0f); + EXPECT_FALSE(points.isUnitless()); +} + +TEST(CSSCalc, is_points_only) { + auto pointsOnly = CSSCalc::fromPoints(10.0f); + EXPECT_TRUE(pointsOnly.isPointsOnly()); + + CSSCalc withPercent{10.0f, 5.0f, 0.0f, 0.0f, false}; + EXPECT_FALSE(withPercent.isPointsOnly()); + + auto number = CSSCalc::fromNumber(10.0f); + EXPECT_FALSE(number.isPointsOnly()); +} + +TEST(CSSCalc, is_percent_only) { + auto percentOnly = CSSCalc::fromPercent(50.0f); + EXPECT_TRUE(percentOnly.isPercentOnly()); + + CSSCalc withPx{10.0f, 50.0f, 0.0f, 0.0f, false}; + EXPECT_FALSE(withPx.isPercentOnly()); +} + +TEST(CSSCalc, is_zero) { + CSSCalc zero{0.0f, 0.0f, 0.0f, 0.0f, false}; + EXPECT_TRUE(zero.isZero()); + + CSSCalc nonZero{0.1f, 0.0f, 0.0f, 0.0f, false}; + EXPECT_FALSE(nonZero.isZero()); +} + +TEST(CSSCalc, from_points) { + auto result = CSSCalc::fromPoints(25.0f); + EXPECT_FLOAT_EQ(result.px, 25.0f); + EXPECT_FLOAT_EQ(result.percent, 0.0f); + EXPECT_FLOAT_EQ(result.vw, 0.0f); + EXPECT_FLOAT_EQ(result.vh, 0.0f); +} + +TEST(CSSCalc, from_percent) { + auto result = CSSCalc::fromPercent(75.0f); + EXPECT_FLOAT_EQ(result.px, 0.0f); + EXPECT_FLOAT_EQ(result.percent, 75.0f); + EXPECT_FLOAT_EQ(result.vw, 0.0f); + EXPECT_FLOAT_EQ(result.vh, 0.0f); +} + +TEST(CSSCalc, from_vw) { + auto result = CSSCalc::fromVw(30.0f); + EXPECT_FLOAT_EQ(result.px, 0.0f); + EXPECT_FLOAT_EQ(result.percent, 0.0f); + EXPECT_FLOAT_EQ(result.vw, 30.0f); + EXPECT_FLOAT_EQ(result.vh, 0.0f); +} + +TEST(CSSCalc, from_vh) { + auto result = CSSCalc::fromVh(45.0f); + EXPECT_FLOAT_EQ(result.px, 0.0f); + EXPECT_FLOAT_EQ(result.percent, 0.0f); + EXPECT_FLOAT_EQ(result.vw, 0.0f); + EXPECT_FLOAT_EQ(result.vh, 45.0f); +} + +TEST(CSSCalc, from_length) { + auto px = CSSCalc::fromLength(10.0f, CSSLengthUnit::Px); + ASSERT_TRUE(px.has_value()); + EXPECT_FLOAT_EQ(px->px, 10.0f); + EXPECT_FLOAT_EQ(px->percent, 0.0f); + + auto vw = CSSCalc::fromLength(50.0f, CSSLengthUnit::Vw); + ASSERT_TRUE(vw.has_value()); + EXPECT_FLOAT_EQ(vw->vw, 50.0f); + + auto vh = CSSCalc::fromLength(25.0f, CSSLengthUnit::Vh); + ASSERT_TRUE(vh.has_value()); + EXPECT_FLOAT_EQ(vh->vh, 25.0f); + + auto unsupported = CSSCalc::fromLength(10.0f, CSSLengthUnit::Em); + EXPECT_FALSE(unsupported.has_value()); +} + +TEST(CSSCalc, division_by_zero_operator) { + CSSCalc a{100.0f, 0.0f, 0.0f, 0.0f, false}; + auto result = a / 0.0f; + EXPECT_TRUE(result.isZero()); +} + +TEST(CSSCalc, negation_preserves_unitless) { + auto unitless = CSSCalc::fromNumber(5.0f); + auto negated = -unitless; + EXPECT_TRUE(negated.isUnitless()); + EXPECT_FLOAT_EQ(negated.px, -5.0f); +} + +TEST(CSSCalc, nested_calc) { + auto result = parseCalc("calc(calc(10px))"); + ASSERT_TRUE(result.has_value()); + EXPECT_FLOAT_EQ(result->px, 10.0f); +} + +TEST(CSSCalc, equality) { + CSSCalc a{10.0f, 20.0f, 5.0f, 2.0f, false}; + CSSCalc b{10.0f, 20.0f, 5.0f, 2.0f, false}; + CSSCalc c{10.0f, 20.0f, 5.0f, 2.0f, true}; + EXPECT_EQ(a, b); + EXPECT_NE(a, c); +} + +TEST(CSSCalc, invalid_wrong_function) { + auto result = parseCalc("min(10px)"); + EXPECT_FALSE(result.has_value()); +} + +TEST(CSSCalc, invalid_missing_calc_function) { + auto result = parseCalc("10px"); + EXPECT_FALSE(result.has_value()); +} + +TEST(CSSCalc, is_zero_unitless) { + auto unitlessZero = CSSCalc::fromNumber(0.0f); + EXPECT_TRUE(unitlessZero.isZero()); + EXPECT_TRUE(unitlessZero.isUnitless()); +} + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/yoga/yoga/YGEnums.cpp b/packages/react-native/ReactCommon/yoga/yoga/YGEnums.cpp index 538b0b0847e3..ed83d9620157 100644 --- a/packages/react-native/ReactCommon/yoga/yoga/YGEnums.cpp +++ b/packages/react-native/ReactCommon/yoga/yoga/YGEnums.cpp @@ -283,6 +283,8 @@ const char* YGUnitToString(const YGUnit value) { return "fit-content"; case YGUnitStretch: return "stretch"; + case YGUnitDynamic: + return "dynamic"; } return "unknown"; } diff --git a/packages/react-native/ReactCommon/yoga/yoga/YGEnums.h b/packages/react-native/ReactCommon/yoga/yoga/YGEnums.h index aa0b1d4350db..54d47c9da63d 100644 --- a/packages/react-native/ReactCommon/yoga/yoga/YGEnums.h +++ b/packages/react-native/ReactCommon/yoga/yoga/YGEnums.h @@ -150,7 +150,8 @@ YG_ENUM_DECL( YGUnitAuto, YGUnitMaxContent, YGUnitFitContent, - YGUnitStretch) + YGUnitStretch, + YGUnitDynamic) YG_ENUM_DECL( YGWrap, diff --git a/packages/react-native/ReactCommon/yoga/yoga/YGNodeStyle.cpp b/packages/react-native/ReactCommon/yoga/yoga/YGNodeStyle.cpp index e43a38e64eb8..0f4866d7842c 100644 --- a/packages/react-native/ReactCommon/yoga/yoga/YGNodeStyle.cpp +++ b/packages/react-native/ReactCommon/yoga/yoga/YGNodeStyle.cpp @@ -226,6 +226,14 @@ void YGNodeStyleSetFlexBasisStretch(const YGNodeRef node) { node, StyleSizeLength::ofStretch()); } +void YGNodeStyleSetFlexBasisDynamic( + const YGNodeRef node, + YGValueDynamic callback, + YGValueDynamicID id) { + updateStyle<&Style::flexBasis, &Style::setFlexBasis>( + node, StyleSizeLength::dynamic(callback, id)); +} + YGValue YGNodeStyleGetFlexBasis(const YGNodeConstRef node) { return (YGValue)resolveRef(node)->style().flexBasis(); } @@ -249,6 +257,15 @@ YGValue YGNodeStyleGetPosition(YGNodeConstRef node, YGEdge edge) { return (YGValue)resolveRef(node)->style().position(scopedEnum(edge)); } +void YGNodeStyleSetPositionDynamic( + YGNodeRef node, + YGEdge edge, + YGValueDynamic callback, + YGValueDynamicID id) { + updateStyle<&Style::position, &Style::setPosition>( + node, scopedEnum(edge), StyleLength::dynamic(callback, id)); +} + void YGNodeStyleSetMargin(YGNodeRef node, YGEdge edge, float points) { updateStyle<&Style::margin, &Style::setMargin>( node, scopedEnum(edge), StyleLength::points(points)); @@ -268,6 +285,15 @@ YGValue YGNodeStyleGetMargin(YGNodeConstRef node, YGEdge edge) { return (YGValue)resolveRef(node)->style().margin(scopedEnum(edge)); } +void YGNodeStyleSetMarginDynamic( + YGNodeRef node, + YGEdge edge, + YGValueDynamic callback, + YGValueDynamicID id) { + updateStyle<&Style::margin, &Style::setMargin>( + node, scopedEnum(edge), StyleLength::dynamic(callback, id)); +} + void YGNodeStyleSetPadding(YGNodeRef node, YGEdge edge, float points) { updateStyle<&Style::padding, &Style::setPadding>( node, scopedEnum(edge), StyleLength::points(points)); @@ -282,6 +308,15 @@ YGValue YGNodeStyleGetPadding(YGNodeConstRef node, YGEdge edge) { return (YGValue)resolveRef(node)->style().padding(scopedEnum(edge)); } +void YGNodeStyleSetPaddingDynamic( + YGNodeRef node, + YGEdge edge, + YGValueDynamic callback, + YGValueDynamicID id) { + updateStyle<&Style::padding, &Style::setPadding>( + node, scopedEnum(edge), StyleLength::dynamic(callback, id)); +} + void YGNodeStyleSetBorder( const YGNodeRef node, const YGEdge edge, @@ -299,6 +334,15 @@ float YGNodeStyleGetBorder(const YGNodeConstRef node, const YGEdge edge) { return static_cast(border).value; } +void YGNodeStyleSetBorderDynamic( + YGNodeRef node, + YGEdge edge, + YGValueDynamic callback, + YGValueDynamicID id) { + updateStyle<&Style::border, &Style::setBorder>( + node, scopedEnum(edge), StyleLength::dynamic(callback, id)); +} + void YGNodeStyleSetGap( const YGNodeRef node, const YGGutter gutter, @@ -312,6 +356,15 @@ void YGNodeStyleSetGapPercent(YGNodeRef node, YGGutter gutter, float percent) { node, scopedEnum(gutter), StyleLength::percent(percent)); } +void YGNodeStyleSetGapDynamic( + YGNodeRef node, + YGGutter gutter, + YGValueDynamic callback, + YGValueDynamicID id) { + updateStyle<&Style::gap, &Style::setGap>( + node, scopedEnum(gutter), StyleLength::dynamic(callback, id)); +} + YGValue YGNodeStyleGetGap(const YGNodeConstRef node, const YGGutter gutter) { return (YGValue)resolveRef(node)->style().gap(scopedEnum(gutter)); } @@ -365,6 +418,14 @@ void YGNodeStyleSetWidthStretch(YGNodeRef node) { node, Dimension::Width, StyleSizeLength::ofStretch()); } +void YGNodeStyleSetWidthDynamic( + YGNodeRef node, + YGValueDynamic callback, + YGValueDynamicID id) { + updateStyle<&Style::dimension, &Style::setDimension>( + node, Dimension::Width, StyleSizeLength::dynamic(callback, id)); +} + YGValue YGNodeStyleGetWidth(YGNodeConstRef node) { return (YGValue)resolveRef(node)->style().dimension(Dimension::Width); } @@ -399,6 +460,14 @@ void YGNodeStyleSetHeightStretch(YGNodeRef node) { node, Dimension::Height, StyleSizeLength::ofStretch()); } +void YGNodeStyleSetHeightDynamic( + YGNodeRef node, + YGValueDynamic callback, + YGValueDynamicID id) { + updateStyle<&Style::dimension, &Style::setDimension>( + node, Dimension::Height, StyleSizeLength::dynamic(callback, id)); +} + YGValue YGNodeStyleGetHeight(YGNodeConstRef node) { return (YGValue)resolveRef(node)->style().dimension(Dimension::Height); } @@ -428,6 +497,14 @@ void YGNodeStyleSetMinWidthStretch(const YGNodeRef node) { node, Dimension::Width, StyleSizeLength::ofStretch()); } +void YGNodeStyleSetMinWidthDynamic( + const YGNodeRef node, + YGValueDynamic callback, + YGValueDynamicID id) { + updateStyle<&Style::minDimension, &Style::setMinDimension>( + node, Dimension::Width, StyleSizeLength::dynamic(callback, id)); +} + YGValue YGNodeStyleGetMinWidth(const YGNodeConstRef node) { return (YGValue)resolveRef(node)->style().minDimension(Dimension::Width); } @@ -459,6 +536,14 @@ void YGNodeStyleSetMinHeightStretch(const YGNodeRef node) { node, Dimension::Height, StyleSizeLength::ofStretch()); } +void YGNodeStyleSetMinHeightDynamic( + const YGNodeRef node, + YGValueDynamic callback, + YGValueDynamicID id) { + updateStyle<&Style::minDimension, &Style::setMinDimension>( + node, Dimension::Height, StyleSizeLength::dynamic(callback, id)); +} + YGValue YGNodeStyleGetMinHeight(const YGNodeConstRef node) { return (YGValue)resolveRef(node)->style().minDimension(Dimension::Height); } @@ -488,6 +573,14 @@ void YGNodeStyleSetMaxWidthStretch(const YGNodeRef node) { node, Dimension::Width, StyleSizeLength::ofStretch()); } +void YGNodeStyleSetMaxWidthDynamic( + const YGNodeRef node, + YGValueDynamic callback, + YGValueDynamicID id) { + updateStyle<&Style::maxDimension, &Style::setMaxDimension>( + node, Dimension::Width, StyleSizeLength::dynamic(callback, id)); +} + YGValue YGNodeStyleGetMaxWidth(const YGNodeConstRef node) { return (YGValue)resolveRef(node)->style().maxDimension(Dimension::Width); } @@ -519,6 +612,14 @@ void YGNodeStyleSetMaxHeightStretch(const YGNodeRef node) { node, Dimension::Height, StyleSizeLength::ofStretch()); } +void YGNodeStyleSetMaxHeightDynamic( + const YGNodeRef node, + YGValueDynamic callback, + YGValueDynamicID id) { + updateStyle<&Style::maxDimension, &Style::setMaxDimension>( + node, Dimension::Height, StyleSizeLength::dynamic(callback, id)); +} + YGValue YGNodeStyleGetMaxHeight(const YGNodeConstRef node) { return (YGValue)resolveRef(node)->style().maxDimension(Dimension::Height); } diff --git a/packages/react-native/ReactCommon/yoga/yoga/YGNodeStyle.h b/packages/react-native/ReactCommon/yoga/yoga/YGNodeStyle.h index a1c7e09561f8..b1785ba65a6f 100644 --- a/packages/react-native/ReactCommon/yoga/yoga/YGNodeStyle.h +++ b/packages/react-native/ReactCommon/yoga/yoga/YGNodeStyle.h @@ -75,6 +75,10 @@ YG_EXPORT void YGNodeStyleSetFlexBasisAuto(YGNodeRef node); YG_EXPORT void YGNodeStyleSetFlexBasisMaxContent(YGNodeRef node); YG_EXPORT void YGNodeStyleSetFlexBasisFitContent(YGNodeRef node); YG_EXPORT void YGNodeStyleSetFlexBasisStretch(YGNodeRef node); +YG_EXPORT void YGNodeStyleSetFlexBasisDynamic( + YGNodeRef node, + YGValueDynamic callback, + YGValueDynamicID id); YG_EXPORT YGValue YGNodeStyleGetFlexBasis(YGNodeConstRef node); YG_EXPORT void @@ -83,27 +87,53 @@ YG_EXPORT void YGNodeStyleSetPositionPercent(YGNodeRef node, YGEdge edge, float position); YG_EXPORT YGValue YGNodeStyleGetPosition(YGNodeConstRef node, YGEdge edge); YG_EXPORT void YGNodeStyleSetPositionAuto(YGNodeRef node, YGEdge edge); +YG_EXPORT void YGNodeStyleSetPositionDynamic( + YGNodeRef node, + YGEdge edge, + YGValueDynamic callback, + YGValueDynamicID id); YG_EXPORT void YGNodeStyleSetMargin(YGNodeRef node, YGEdge edge, float margin); YG_EXPORT void YGNodeStyleSetMarginPercent(YGNodeRef node, YGEdge edge, float margin); YG_EXPORT void YGNodeStyleSetMarginAuto(YGNodeRef node, YGEdge edge); +YG_EXPORT void YGNodeStyleSetMarginDynamic( + YGNodeRef node, + YGEdge edge, + YGValueDynamic callback, + YGValueDynamicID id); YG_EXPORT YGValue YGNodeStyleGetMargin(YGNodeConstRef node, YGEdge edge); YG_EXPORT void YGNodeStyleSetPadding(YGNodeRef node, YGEdge edge, float padding); YG_EXPORT void YGNodeStyleSetPaddingPercent(YGNodeRef node, YGEdge edge, float padding); +YG_EXPORT void YGNodeStyleSetPaddingDynamic( + YGNodeRef node, + YGEdge edge, + YGValueDynamic callback, + YGValueDynamicID id); YG_EXPORT YGValue YGNodeStyleGetPadding(YGNodeConstRef node, YGEdge edge); YG_EXPORT void YGNodeStyleSetBorder(YGNodeRef node, YGEdge edge, float border); +YG_EXPORT void YGNodeStyleSetBorderDynamic( + YGNodeRef node, + YGEdge edge, + YGValueDynamic callback, + YGValueDynamicID id); YG_EXPORT float YGNodeStyleGetBorder(YGNodeConstRef node, YGEdge edge); YG_EXPORT void YGNodeStyleSetGap(YGNodeRef node, YGGutter gutter, float gapLength); YG_EXPORT void YGNodeStyleSetGapPercent(YGNodeRef node, YGGutter gutter, float gapLength); +YG_EXPORT void YGNodeStyleSetGapDynamic( + YGNodeRef node, + YGGutter gutter, + YGValueDynamic callback, + YGValueDynamicID id); + YG_EXPORT YGValue YGNodeStyleGetGap(YGNodeConstRef node, YGGutter gutter); YG_EXPORT void YGNodeStyleSetBoxSizing(YGNodeRef node, YGBoxSizing boxSizing); @@ -115,6 +145,10 @@ YG_EXPORT void YGNodeStyleSetWidthAuto(YGNodeRef node); YG_EXPORT void YGNodeStyleSetWidthMaxContent(YGNodeRef node); YG_EXPORT void YGNodeStyleSetWidthFitContent(YGNodeRef node); YG_EXPORT void YGNodeStyleSetWidthStretch(YGNodeRef node); +YG_EXPORT void YGNodeStyleSetWidthDynamic( + YGNodeRef node, + YGValueDynamic callback, + YGValueDynamicID id); YG_EXPORT YGValue YGNodeStyleGetWidth(YGNodeConstRef node); YG_EXPORT void YGNodeStyleSetHeight(YGNodeRef node, float height); @@ -123,6 +157,10 @@ YG_EXPORT void YGNodeStyleSetHeightAuto(YGNodeRef node); YG_EXPORT void YGNodeStyleSetHeightMaxContent(YGNodeRef node); YG_EXPORT void YGNodeStyleSetHeightFitContent(YGNodeRef node); YG_EXPORT void YGNodeStyleSetHeightStretch(YGNodeRef node); +YG_EXPORT void YGNodeStyleSetHeightDynamic( + YGNodeRef node, + YGValueDynamic callback, + YGValueDynamicID id); YG_EXPORT YGValue YGNodeStyleGetHeight(YGNodeConstRef node); YG_EXPORT void YGNodeStyleSetMinWidth(YGNodeRef node, float minWidth); @@ -130,6 +168,10 @@ YG_EXPORT void YGNodeStyleSetMinWidthPercent(YGNodeRef node, float minWidth); YG_EXPORT void YGNodeStyleSetMinWidthMaxContent(YGNodeRef node); YG_EXPORT void YGNodeStyleSetMinWidthFitContent(YGNodeRef node); YG_EXPORT void YGNodeStyleSetMinWidthStretch(YGNodeRef node); +YG_EXPORT void YGNodeStyleSetMinWidthDynamic( + YGNodeRef node, + YGValueDynamic callback, + YGValueDynamicID id); YG_EXPORT YGValue YGNodeStyleGetMinWidth(YGNodeConstRef node); YG_EXPORT void YGNodeStyleSetMinHeight(YGNodeRef node, float minHeight); @@ -137,6 +179,10 @@ YG_EXPORT void YGNodeStyleSetMinHeightPercent(YGNodeRef node, float minHeight); YG_EXPORT void YGNodeStyleSetMinHeightMaxContent(YGNodeRef node); YG_EXPORT void YGNodeStyleSetMinHeightFitContent(YGNodeRef node); YG_EXPORT void YGNodeStyleSetMinHeightStretch(YGNodeRef node); +YG_EXPORT void YGNodeStyleSetMinHeightDynamic( + YGNodeRef node, + YGValueDynamic callback, + YGValueDynamicID id); YG_EXPORT YGValue YGNodeStyleGetMinHeight(YGNodeConstRef node); YG_EXPORT void YGNodeStyleSetMaxWidth(YGNodeRef node, float maxWidth); @@ -144,6 +190,10 @@ YG_EXPORT void YGNodeStyleSetMaxWidthPercent(YGNodeRef node, float maxWidth); YG_EXPORT void YGNodeStyleSetMaxWidthMaxContent(YGNodeRef node); YG_EXPORT void YGNodeStyleSetMaxWidthFitContent(YGNodeRef node); YG_EXPORT void YGNodeStyleSetMaxWidthStretch(YGNodeRef node); +YG_EXPORT void YGNodeStyleSetMaxWidthDynamic( + YGNodeRef node, + YGValueDynamic callback, + YGValueDynamicID id); YG_EXPORT YGValue YGNodeStyleGetMaxWidth(YGNodeConstRef node); YG_EXPORT void YGNodeStyleSetMaxHeight(YGNodeRef node, float maxHeight); @@ -151,6 +201,10 @@ YG_EXPORT void YGNodeStyleSetMaxHeightPercent(YGNodeRef node, float maxHeight); YG_EXPORT void YGNodeStyleSetMaxHeightMaxContent(YGNodeRef node); YG_EXPORT void YGNodeStyleSetMaxHeightFitContent(YGNodeRef node); YG_EXPORT void YGNodeStyleSetMaxHeightStretch(YGNodeRef node); +YG_EXPORT void YGNodeStyleSetMaxHeightDynamic( + YGNodeRef node, + YGValueDynamic callback, + YGValueDynamicID id); YG_EXPORT YGValue YGNodeStyleGetMaxHeight(YGNodeConstRef node); YG_EXPORT void YGNodeStyleSetAspectRatio(YGNodeRef node, float aspectRatio); diff --git a/packages/react-native/ReactCommon/yoga/yoga/YGValue.h b/packages/react-native/ReactCommon/yoga/yoga/YGValue.h index 138135229b96..27f7524d2de4 100644 --- a/packages/react-native/ReactCommon/yoga/yoga/YGValue.h +++ b/packages/react-native/ReactCommon/yoga/yoga/YGValue.h @@ -25,6 +25,8 @@ constexpr float YGUndefined = std::numeric_limits::quiet_NaN(); YG_EXTERN_C_BEGIN +typedef const struct YGNode* YGNodeConstRef; + /** * Structure used to represent a dimension in a style. */ @@ -53,6 +55,28 @@ YG_EXPORT extern const YGValue YGValueZero; */ YG_EXPORT bool YGFloatIsUndefined(float value); +/** + * Host-defined identifier for a dynamic style value. + */ +typedef uint32_t YGValueDynamicID; + +/** + * Layout context passed to YGValueDynamic for resolving dynamic values. + * May be extended with additional fields (e.g. containing block dimensions). + */ +typedef struct YGValueDynamicContext { + float referenceLength; +} YGValueDynamicContext; + +/** + * Called during layout to resolve a dynamic style value (e.g. calc()). + * Must return a YGValue with unit YGUnitPoint. + */ +typedef YGValue (*YGValueDynamic)( + YGNodeConstRef node, + YGValueDynamicID id, + YGValueDynamicContext context); + YG_EXTERN_C_END // Equality operators for comparison of YGValue in C++ @@ -72,6 +96,7 @@ inline bool operator==(const YGValue& lhs, const YGValue& rhs) { case YGUnitPoint: case YGUnitPercent: return lhs.value == rhs.value; + case YGUnitDynamic: default: return false; } diff --git a/packages/react-native/ReactCommon/yoga/yoga/algorithm/AbsoluteLayout.cpp b/packages/react-native/ReactCommon/yoga/yoga/algorithm/AbsoluteLayout.cpp index 181dfcb1b0de..d4ce6967df97 100644 --- a/packages/react-native/ReactCommon/yoga/yoga/algorithm/AbsoluteLayout.cpp +++ b/packages/react-native/ReactCommon/yoga/yoga/algorithm/AbsoluteLayout.cpp @@ -20,7 +20,7 @@ static inline void setFlexStartLayoutPosition( const FlexDirection axis, const float containingBlockWidth) { float position = child->style().computeFlexStartMargin( - axis, direction, containingBlockWidth) + + axis, direction, containingBlockWidth, child) + parent->getLayout().border(flexStartEdge(axis)); // https://www.w3.org/TR/css-grid-1/#abspos @@ -42,7 +42,7 @@ static inline void setFlexEndLayoutPosition( const float containingBlockWidth) { float flexEndPosition = parent->getLayout().border(flexEndEdge(axis)) + child->style().computeFlexEndMargin( - axis, direction, containingBlockWidth); + axis, direction, containingBlockWidth, child); // https://www.w3.org/TR/css-grid-1/#abspos // absolute positioned grid items are positioned relative to the padding edge @@ -79,12 +79,12 @@ static inline void setCenterLayoutPosition( const float childOuterSize = child->getLayout().measuredDimension(dimension(axis)) + - child->style().computeMarginForAxis(axis, containingBlockWidth); + child->style().computeMarginForAxis(axis, containingBlockWidth, child); float position = (parentContentBoxSize - childOuterSize) / 2.0f + parent->getLayout().border(flexStartEdge(axis)) + child->style().computeFlexStartMargin( - axis, direction, containingBlockWidth); + axis, direction, containingBlockWidth, child); // https://www.w3.org/TR/css-grid-1/#abspos // absolute positioned grid items are positioned relative to the padding edge @@ -210,10 +210,11 @@ static void positionAbsoluteChild( !child->style().isInlineStartPositionAuto(axis, direction)) { const float positionRelativeToInlineStart = child->style().computeInlineStartPosition( - axis, direction, containingBlockSize) + - containingNode->style().computeInlineStartBorder(axis, direction) + + axis, direction, containingBlockSize, child) + + containingNode->style().computeInlineStartBorder( + axis, direction, containingNode) + child->style().computeInlineStartMargin( - axis, direction, containingBlockSize); + axis, direction, containingBlockSize, child); const float positionRelativeToFlexStart = inlineStartEdge(axis, direction) != flexStartEdge(axis) ? getPositionOfOppositeEdge( @@ -227,11 +228,12 @@ static void positionAbsoluteChild( const float positionRelativeToInlineStart = containingNode->getLayout().measuredDimension(dimension(axis)) - child->getLayout().measuredDimension(dimension(axis)) - - containingNode->style().computeInlineEndBorder(axis, direction) - + containingNode->style().computeInlineEndBorder( + axis, direction, containingNode) - child->style().computeInlineEndMargin( - axis, direction, containingBlockSize) - + axis, direction, containingBlockSize, child) - child->style().computeInlineEndPosition( - axis, direction, containingBlockSize); + axis, direction, containingBlockSize, child); const float positionRelativeToFlexStart = inlineStartEdge(axis, direction) != flexStartEdge(axis) ? getPositionOfOppositeEdge( @@ -275,9 +277,9 @@ void layoutAbsoluteChild( SizingMode childHeightSizingMode = SizingMode::MaxContent; auto marginRow = child->style().computeMarginForAxis( - FlexDirection::Row, containingBlockWidth); + FlexDirection::Row, containingBlockWidth, child); auto marginColumn = child->style().computeMarginForAxis( - FlexDirection::Column, containingBlockWidth); + FlexDirection::Column, containingBlockWidth, child); if (child->hasDefiniteLength(Dimension::Width, containingBlockWidth)) { childWidth = child @@ -301,13 +303,13 @@ void layoutAbsoluteChild( childWidth = containingNode->getLayout().measuredDimension(Dimension::Width) - (containingNode->style().computeFlexStartBorder( - FlexDirection::Row, direction) + + FlexDirection::Row, direction, containingNode) + containingNode->style().computeFlexEndBorder( - FlexDirection::Row, direction)) - + FlexDirection::Row, direction, containingNode)) - (child->style().computeFlexStartPosition( - FlexDirection::Row, direction, containingBlockWidth) + + FlexDirection::Row, direction, containingBlockWidth, child) + child->style().computeFlexEndPosition( - FlexDirection::Row, direction, containingBlockWidth)); + FlexDirection::Row, direction, containingBlockWidth, child)); childWidth = boundAxis( child, FlexDirection::Row, @@ -341,13 +343,13 @@ void layoutAbsoluteChild( childHeight = containingNode->getLayout().measuredDimension(Dimension::Height) - (containingNode->style().computeFlexStartBorder( - FlexDirection::Column, direction) + + FlexDirection::Column, direction, containingNode) + containingNode->style().computeFlexEndBorder( - FlexDirection::Column, direction)) - + FlexDirection::Column, direction, containingNode)) - (child->style().computeFlexStartPosition( - FlexDirection::Column, direction, containingBlockHeight) + + FlexDirection::Column, direction, containingBlockHeight, child) + child->style().computeFlexEndPosition( - FlexDirection::Column, direction, containingBlockHeight)); + FlexDirection::Column, direction, containingBlockHeight, child)); childHeight = boundAxis( child, FlexDirection::Column, @@ -410,10 +412,10 @@ void layoutAbsoluteChild( generationCount); childWidth = child->getLayout().measuredDimension(Dimension::Width) + child->style().computeMarginForAxis( - FlexDirection::Row, containingBlockWidth); + FlexDirection::Row, containingBlockWidth, child); childHeight = child->getLayout().measuredDimension(Dimension::Height) + child->style().computeMarginForAxis( - FlexDirection::Column, containingBlockWidth); + FlexDirection::Column, containingBlockWidth, child); } calculateLayoutInternal( @@ -473,12 +475,13 @@ bool layoutAbsoluteDescendants( const float containingBlockWidth = absoluteErrata ? containingNodeAvailableInnerWidth : containingNode->getLayout().measuredDimension(Dimension::Width) - - containingNode->style().computeBorderForAxis(FlexDirection::Row); + containingNode->style().computeBorderForAxis( + FlexDirection::Row, containingNode); const float containingBlockHeight = absoluteErrata ? containingNodeAvailableInnerHeight : containingNode->getLayout().measuredDimension(Dimension::Height) - containingNode->style().computeBorderForAxis( - FlexDirection::Column); + FlexDirection::Column, containingNode); layoutAbsoluteChild( containingNode, diff --git a/packages/react-native/ReactCommon/yoga/yoga/algorithm/BoundAxis.h b/packages/react-native/ReactCommon/yoga/yoga/algorithm/BoundAxis.h index d9ebc68c5b3c..f90b1ad2bc97 100644 --- a/packages/react-native/ReactCommon/yoga/yoga/algorithm/BoundAxis.h +++ b/packages/react-native/ReactCommon/yoga/yoga/algorithm/BoundAxis.h @@ -22,9 +22,9 @@ inline float paddingAndBorderForAxis( const Direction direction, const float widthSize) { return node->style().computeInlineStartPaddingAndBorder( - axis, direction, widthSize) + + axis, direction, widthSize, node) + node->style().computeInlineEndPaddingAndBorder( - axis, direction, widthSize); + axis, direction, widthSize, node); } inline FloatOptional boundAxisWithinMinAndMax( @@ -39,14 +39,14 @@ inline FloatOptional boundAxisWithinMinAndMax( if (isColumn(axis)) { min = node->style().resolvedMinDimension( - direction, Dimension::Height, axisSize, widthSize); + direction, Dimension::Height, axisSize, widthSize, node); max = node->style().resolvedMaxDimension( - direction, Dimension::Height, axisSize, widthSize); + direction, Dimension::Height, axisSize, widthSize, node); } else if (isRow(axis)) { min = node->style().resolvedMinDimension( - direction, Dimension::Width, axisSize, widthSize); + direction, Dimension::Width, axisSize, widthSize, node); max = node->style().resolvedMaxDimension( - direction, Dimension::Width, axisSize, widthSize); + direction, Dimension::Width, axisSize, widthSize, node); } if (max >= FloatOptional{0} && value > max) { diff --git a/packages/react-native/ReactCommon/yoga/yoga/algorithm/CalculateLayout.cpp b/packages/react-native/ReactCommon/yoga/yoga/algorithm/CalculateLayout.cpp index d25435071bff..4066710d5946 100644 --- a/packages/react-native/ReactCommon/yoga/yoga/algorithm/CalculateLayout.cpp +++ b/packages/react-native/ReactCommon/yoga/yoga/algorithm/CalculateLayout.cpp @@ -45,8 +45,8 @@ void constrainMaxSizeForMode( /*in_out*/ float* size) { const FloatOptional maxSize = node->style().resolvedMaxDimension( - direction, dimension(axis), ownerAxisSize, ownerWidth) + - FloatOptional(node->style().computeMarginForAxis(axis, ownerWidth)); + direction, dimension(axis), ownerAxisSize, ownerWidth, node) + + FloatOptional(node->style().computeMarginForAxis(axis, ownerWidth, node)); switch (*mode) { case SizingMode::StretchFit: case SizingMode::FitContent: @@ -142,10 +142,10 @@ static void computeFlexBasisForChild( childWidthSizingMode = SizingMode::MaxContent; childHeightSizingMode = SizingMode::MaxContent; - auto marginRow = - child->style().computeMarginForAxis(FlexDirection::Row, ownerWidth); - auto marginColumn = - child->style().computeMarginForAxis(FlexDirection::Column, ownerWidth); + auto marginRow = child->style().computeMarginForAxis( + FlexDirection::Row, ownerWidth, child); + auto marginColumn = child->style().computeMarginForAxis( + FlexDirection::Column, ownerWidth, child); if (isRowStyleDimDefined) { childWidth = child @@ -540,14 +540,14 @@ float calculateAvailableInnerDimension( // constraints const FloatOptional minDimensionOptional = node->style().resolvedMinDimension( - direction, dimension, ownerDim, ownerWidth); + direction, dimension, ownerDim, ownerWidth, node); const float minInnerDim = minDimensionOptional.isUndefined() ? 0.0f : minDimensionOptional.unwrap() - paddingAndBorder; const FloatOptional maxDimensionOptional = node->style().resolvedMaxDimension( - direction, dimension, ownerDim, ownerWidth); + direction, dimension, ownerDim, ownerWidth, node); const float maxInnerDim = maxDimensionOptional.isUndefined() ? FLT_MAX @@ -637,7 +637,8 @@ static float computeFlexBasisForChildren( totalOuterFlexBasis += (child->getLayout().computedFlexBasis.unwrap() + - child->style().computeMarginForAxis(mainAxis, availableInnerWidth)); + child->style().computeMarginForAxis( + mainAxis, availableInnerWidth, child)); } return totalOuterFlexBasis; @@ -731,9 +732,9 @@ static float distributeFreeSpaceSecondPass( deltaFreeSpace += updatedMainSize - childFlexBasis; const float marginMain = currentLineChild->style().computeMarginForAxis( - mainAxis, availableInnerWidth); + mainAxis, availableInnerWidth, currentLineChild); const float marginCross = currentLineChild->style().computeMarginForAxis( - crossAxis, availableInnerWidth); + crossAxis, availableInnerWidth, currentLineChild); float childCrossSize = YGUndefined; float childMainSize = updatedMainSize + marginMain; @@ -1025,13 +1026,13 @@ static void justifyMainAxis( const float leadingPaddingAndBorderMain = node->style().computeFlexStartPaddingAndBorder( - mainAxis, direction, ownerWidth); + mainAxis, direction, ownerWidth, node); const float trailingPaddingAndBorderMain = node->style().computeFlexEndPaddingAndBorder( - mainAxis, direction, ownerWidth); + mainAxis, direction, ownerWidth, node); const float gap = - node->style().computeGapForAxis(mainAxis, availableInnerMainDim); + node->style().computeGapForAxis(mainAxis, availableInnerMainDim, node); // 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 (sizingModeMainDim == SizingMode::FitContent && @@ -1039,7 +1040,11 @@ static void justifyMainAxis( if (style.minDimension(dimension(mainAxis)).isDefined() && style .resolvedMinDimension( - direction, dimension(mainAxis), mainAxisOwnerSize, ownerWidth) + direction, + dimension(mainAxis), + mainAxisOwnerSize, + ownerWidth, + node) .isDefined()) { // This condition makes sure that if the size of main dimension(after // considering child nodes main dim, leading and trailing padding etc) @@ -1048,11 +1053,14 @@ static void justifyMainAxis( // `minAvailableMainDim` denotes minimum available space in which child // can be laid out, it will exclude space consumed by padding and border. - const float minAvailableMainDim = - style - .resolvedMinDimension( - direction, dimension(mainAxis), mainAxisOwnerSize, ownerWidth) - .unwrap() - + const float minAvailableMainDim = style + .resolvedMinDimension( + direction, + dimension(mainAxis), + mainAxisOwnerSize, + ownerWidth, + node) + .unwrap() - leadingPaddingAndBorderMain - trailingPaddingAndBorderMain; const float occupiedSpaceByChildNodes = availableInnerMainDim - flexLine.layout.remainingFreeSpace; @@ -1147,8 +1155,8 @@ static void justifyMainAxis( // 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. - flexLine.layout.mainDim += - child->style().computeMarginForAxis(mainAxis, availableInnerWidth) + + flexLine.layout.mainDim += child->style().computeMarginForAxis( + mainAxis, availableInnerWidth, child) + childLayout.computedFlexBasis.unwrap(); flexLine.layout.crossDim = availableInnerCrossDim; } else { @@ -1162,11 +1170,11 @@ static void justifyMainAxis( // calculated by adding maxAscent and maxDescent from the baseline. const float ascent = calculateBaseline(child) + child->style().computeFlexStartMargin( - FlexDirection::Column, direction, availableInnerWidth); + FlexDirection::Column, direction, availableInnerWidth, child); const float descent = child->getLayout().measuredDimension(Dimension::Height) + child->style().computeMarginForAxis( - FlexDirection::Column, availableInnerWidth) - + FlexDirection::Column, availableInnerWidth, child) - ascent; maxAscentForCurrentLine = @@ -1293,49 +1301,51 @@ static void calculateLayoutImpl( direction == Direction::LTR ? PhysicalEdge::Right : PhysicalEdge::Left; const float marginRowLeading = node->style().computeInlineStartMargin( - flexRowDirection, direction, ownerWidth); + flexRowDirection, direction, ownerWidth, node); node->setLayoutMargin(marginRowLeading, startEdge); const float marginRowTrailing = node->style().computeInlineEndMargin( - flexRowDirection, direction, ownerWidth); + flexRowDirection, direction, ownerWidth, node); node->setLayoutMargin(marginRowTrailing, endEdge); const float marginColumnLeading = node->style().computeInlineStartMargin( - flexColumnDirection, direction, ownerWidth); + flexColumnDirection, direction, ownerWidth, node); node->setLayoutMargin(marginColumnLeading, PhysicalEdge::Top); const float marginColumnTrailing = node->style().computeInlineEndMargin( - flexColumnDirection, direction, ownerWidth); + flexColumnDirection, direction, ownerWidth, node); node->setLayoutMargin(marginColumnTrailing, PhysicalEdge::Bottom); const float marginAxisRow = marginRowLeading + marginRowTrailing; const float marginAxisColumn = marginColumnLeading + marginColumnTrailing; node->setLayoutBorder( - node->style().computeInlineStartBorder(flexRowDirection, direction), + node->style().computeInlineStartBorder(flexRowDirection, direction, node), startEdge); node->setLayoutBorder( - node->style().computeInlineEndBorder(flexRowDirection, direction), + node->style().computeInlineEndBorder(flexRowDirection, direction, node), endEdge); node->setLayoutBorder( - node->style().computeInlineStartBorder(flexColumnDirection, direction), + node->style().computeInlineStartBorder( + flexColumnDirection, direction, node), PhysicalEdge::Top); node->setLayoutBorder( - node->style().computeInlineEndBorder(flexColumnDirection, direction), + node->style().computeInlineEndBorder( + flexColumnDirection, direction, node), PhysicalEdge::Bottom); node->setLayoutPadding( node->style().computeInlineStartPadding( - flexRowDirection, direction, ownerWidth), + flexRowDirection, direction, ownerWidth, node), startEdge); node->setLayoutPadding( node->style().computeInlineEndPadding( - flexRowDirection, direction, ownerWidth), + flexRowDirection, direction, ownerWidth, node), endEdge); node->setLayoutPadding( node->style().computeInlineStartPadding( - flexColumnDirection, direction, ownerWidth), + flexColumnDirection, direction, ownerWidth, node), PhysicalEdge::Top); node->setLayoutPadding( node->style().computeInlineEndPadding( - flexColumnDirection, direction, ownerWidth), + flexColumnDirection, direction, ownerWidth, node), PhysicalEdge::Bottom); if (node->hasMeasureFunc()) { @@ -1419,7 +1429,7 @@ static void calculateLayoutImpl( paddingAndBorderForAxis(node, crossAxis, direction, ownerWidth); const float leadingPaddingAndBorderCross = node->style().computeFlexStartPaddingAndBorder( - crossAxis, direction, ownerWidth); + crossAxis, direction, ownerWidth, node); SizingMode sizingModeMainDim = isMainAxisRow ? widthSizingMode : heightSizingMode; @@ -1515,7 +1525,7 @@ static void calculateLayoutImpl( if (childCount > 1) { totalMainDim += - node->style().computeGapForAxis(mainAxis, availableInnerMainDim) * + node->style().computeGapForAxis(mainAxis, availableInnerMainDim, node) * static_cast(childCount - 1); } @@ -1540,7 +1550,7 @@ static void calculateLayoutImpl( float totalLineCrossDim = 0; const float crossAxisGap = - node->style().computeGapForAxis(crossAxis, availableInnerCrossDim); + node->style().computeGapForAxis(crossAxis, availableInnerCrossDim, node); // Max main dimension of all the lines. float maxLineMainDim = 0; @@ -1573,25 +1583,25 @@ static void calculateLayoutImpl( const float minInnerWidth = style .resolvedMinDimension( - direction, Dimension::Width, ownerWidth, ownerWidth) + direction, Dimension::Width, ownerWidth, ownerWidth, node) .unwrap() - paddingAndBorderAxisRow; const float maxInnerWidth = style .resolvedMaxDimension( - direction, Dimension::Width, ownerWidth, ownerWidth) + direction, Dimension::Width, ownerWidth, ownerWidth, node) .unwrap() - paddingAndBorderAxisRow; const float minInnerHeight = style .resolvedMinDimension( - direction, Dimension::Height, ownerHeight, ownerWidth) + direction, Dimension::Height, ownerHeight, ownerWidth, node) .unwrap() - paddingAndBorderAxisColumn; const float maxInnerHeight = style .resolvedMaxDimension( - direction, Dimension::Height, ownerHeight, ownerWidth) + direction, Dimension::Height, ownerHeight, ownerWidth, node) .unwrap() - paddingAndBorderAxisColumn; @@ -1746,14 +1756,14 @@ static void calculateLayoutImpl( const auto& childStyle = child->style(); float childCrossSize = childStyle.aspectRatio().isDefined() ? child->style().computeMarginForAxis( - crossAxis, availableInnerWidth) + + crossAxis, availableInnerWidth, child) + (isMainAxisRow ? childMainSize / childStyle.aspectRatio().unwrap() : childMainSize * childStyle.aspectRatio().unwrap()) : flexLine.layout.crossDim; childMainSize += child->style().computeMarginForAxis( - mainAxis, availableInnerWidth); + mainAxis, availableInnerWidth, child); SizingMode childMainSizingMode = SizingMode::StretchFit; SizingMode childCrossSizingMode = SizingMode::StretchFit; @@ -1936,16 +1946,19 @@ static void calculateLayoutImpl( lineHeight, child->getLayout().measuredDimension(dimension(crossAxis)) + child->style().computeMarginForAxis( - crossAxis, availableInnerWidth)); + crossAxis, availableInnerWidth, child)); } if (resolveChildAlignment(node, child) == Align::Baseline) { const float ascent = calculateBaseline(child) + child->style().computeFlexStartMargin( - FlexDirection::Column, direction, availableInnerWidth); + FlexDirection::Column, + direction, + availableInnerWidth, + child); const float descent = child->getLayout().measuredDimension(Dimension::Height) + child->style().computeMarginForAxis( - FlexDirection::Column, availableInnerWidth) - + FlexDirection::Column, availableInnerWidth, child) - ascent; maxAscentForCurrentLine = yoga::maxOrDefined(maxAscentForCurrentLine, ascent); @@ -1975,7 +1988,7 @@ static void calculateLayoutImpl( child->setLayoutPosition( currentLead + child->style().computeFlexStartPosition( - crossAxis, direction, availableInnerWidth), + crossAxis, direction, availableInnerWidth, child), flexStartEdge(crossAxis)); break; } @@ -1983,7 +1996,7 @@ static void calculateLayoutImpl( child->setLayoutPosition( currentLead + lineHeight - child->style().computeFlexEndMargin( - crossAxis, direction, availableInnerWidth) - + crossAxis, direction, availableInnerWidth, child) - child->getLayout().measuredDimension( dimension(crossAxis)), flexStartEdge(crossAxis)); @@ -2002,7 +2015,7 @@ static void calculateLayoutImpl( child->setLayoutPosition( currentLead + child->style().computeFlexStartMargin( - crossAxis, direction, availableInnerWidth), + crossAxis, direction, availableInnerWidth, child), flexStartEdge(crossAxis)); // Remeasure child with the line height as it as been only @@ -2012,13 +2025,13 @@ static void calculateLayoutImpl( const float childWidth = isMainAxisRow ? (child->getLayout().measuredDimension(Dimension::Width) + child->style().computeMarginForAxis( - mainAxis, availableInnerWidth)) + mainAxis, availableInnerWidth, child)) : leadPerLine + lineHeight; const float childHeight = !isMainAxisRow ? (child->getLayout().measuredDimension(Dimension::Height) + child->style().computeMarginForAxis( - crossAxis, availableInnerWidth)) + crossAxis, availableInnerWidth, child)) : leadPerLine + lineHeight; if (!(yoga::inexactEquals( @@ -2054,7 +2067,8 @@ static void calculateLayoutImpl( child->style().computeFlexStartPosition( FlexDirection::Column, direction, - availableInnerCrossDim), + availableInnerCrossDim, + child), PhysicalEdge::Top); break; @@ -2276,10 +2290,10 @@ bool calculateLayoutInternal( // 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->style().computeMarginForAxis(FlexDirection::Row, ownerWidth); - const float marginAxisColumn = - node->style().computeMarginForAxis(FlexDirection::Column, ownerWidth); + const float marginAxisRow = node->style().computeMarginForAxis( + FlexDirection::Row, ownerWidth, node); + const float marginAxisColumn = node->style().computeMarginForAxis( + FlexDirection::Column, ownerWidth, node); // First, try to use the layout cache. if (canUseCachedMeasurement( @@ -2454,15 +2468,16 @@ void calculateLayout( ownerWidth, ownerWidth) .unwrap() + - node->style().computeMarginForAxis(FlexDirection::Row, ownerWidth)); + node->style().computeMarginForAxis( + FlexDirection::Row, ownerWidth, node)); widthSizingMode = SizingMode::StretchFit; } else if (style .resolvedMaxDimension( - direction, Dimension::Width, ownerWidth, ownerWidth) + direction, Dimension::Width, ownerWidth, ownerWidth, node) .isDefined()) { width = style .resolvedMaxDimension( - direction, Dimension::Width, ownerWidth, ownerWidth) + direction, Dimension::Width, ownerWidth, ownerWidth, node) .unwrap(); widthSizingMode = SizingMode::FitContent; } else { @@ -2481,16 +2496,22 @@ void calculateLayout( ownerHeight, ownerWidth) .unwrap() + - node->style().computeMarginForAxis(FlexDirection::Column, ownerWidth)); + node->style().computeMarginForAxis( + FlexDirection::Column, ownerWidth, node)); heightSizingMode = SizingMode::StretchFit; } else if (style .resolvedMaxDimension( - direction, Dimension::Height, ownerHeight, ownerWidth) + direction, + Dimension::Height, + ownerHeight, + ownerWidth, + node) .isDefined()) { - height = style - .resolvedMaxDimension( - direction, Dimension::Height, ownerHeight, ownerWidth) - .unwrap(); + height = + style + .resolvedMaxDimension( + direction, Dimension::Height, ownerHeight, ownerWidth, node) + .unwrap(); heightSizingMode = SizingMode::FitContent; } else { height = ownerHeight; diff --git a/packages/react-native/ReactCommon/yoga/yoga/algorithm/FlexLine.cpp b/packages/react-native/ReactCommon/yoga/yoga/algorithm/FlexLine.cpp index dc0a300add24..0f6e2e2c9b2a 100644 --- a/packages/react-native/ReactCommon/yoga/yoga/algorithm/FlexLine.cpp +++ b/packages/react-native/ReactCommon/yoga/yoga/algorithm/FlexLine.cpp @@ -37,7 +37,7 @@ FlexLine calculateFlexLine( resolveDirection(node->style().flexDirection(), direction); const bool isNodeFlexWrap = node->style().flexWrap() != Wrap::NoWrap; const float gap = - node->style().computeGapForAxis(mainAxis, availableInnerMainDim); + node->style().computeGapForAxis(mainAxis, availableInnerMainDim, node); const auto childrenEnd = node->getLayoutChildren().end(); // Add items to the current line until it's full or we run out of items. @@ -60,8 +60,8 @@ FlexLine calculateFlexLine( } child->setLineIndex(lineCount); - const float childMarginMainAxis = - child->style().computeMarginForAxis(mainAxis, availableInnerWidth); + const float childMarginMainAxis = child->style().computeMarginForAxis( + mainAxis, availableInnerWidth, child); const float childLeadingGapMainAxis = child == firstElementInLine ? 0.0f : gap; const float flexBasisWithMinAndMaxConstraints = diff --git a/packages/react-native/ReactCommon/yoga/yoga/enums/Unit.h b/packages/react-native/ReactCommon/yoga/yoga/enums/Unit.h index 685b1caecee6..9176ad21e517 100644 --- a/packages/react-native/ReactCommon/yoga/yoga/enums/Unit.h +++ b/packages/react-native/ReactCommon/yoga/yoga/enums/Unit.h @@ -23,11 +23,12 @@ enum class Unit : uint8_t { MaxContent = YGUnitMaxContent, FitContent = YGUnitFitContent, Stretch = YGUnitStretch, + Dynamic = YGUnitDynamic, }; template <> constexpr int32_t ordinalCount() { - return 7; + return 8; } constexpr Unit scopedEnum(YGUnit unscoped) { diff --git a/packages/react-native/ReactCommon/yoga/yoga/node/Node.cpp b/packages/react-native/ReactCommon/yoga/yoga/node/Node.cpp index d42bd5f9e707..e98eda8d0502 100644 --- a/packages/react-native/ReactCommon/yoga/yoga/node/Node.cpp +++ b/packages/react-native/ReactCommon/yoga/yoga/node/Node.cpp @@ -87,7 +87,7 @@ float Node::dimensionWithMargin( const FlexDirection axis, const float widthSize) { return getLayout().measuredDimension(dimension(axis)) + - style_.computeMarginForAxis(axis, widthSize); + style_.computeMarginForAxis(axis, widthSize, this); } bool Node::isLayoutDimensionDefined(const FlexDirection axis) { @@ -273,10 +273,10 @@ float Node::relativePosition( } if (style_.isInlineStartPositionDefined(axis, direction) && !style_.isInlineStartPositionAuto(axis, direction)) { - return style_.computeInlineStartPosition(axis, direction, axisSize); + return style_.computeInlineStartPosition(axis, direction, axisSize, this); } - return -1 * style_.computeInlineEndPosition(axis, direction, axisSize); + return -1 * style_.computeInlineEndPosition(axis, direction, axisSize, this); } void Node::setPosition( @@ -309,19 +309,19 @@ void Node::setPosition( const auto crossAxisTrailingEdge = inlineEndEdge(crossAxis, direction); setLayoutPosition( - (style_.computeInlineStartMargin(mainAxis, direction, ownerWidth) + + (style_.computeInlineStartMargin(mainAxis, direction, ownerWidth, this) + relativePositionMain), mainAxisLeadingEdge); setLayoutPosition( - (style_.computeInlineEndMargin(mainAxis, direction, ownerWidth) + + (style_.computeInlineEndMargin(mainAxis, direction, ownerWidth, this) + relativePositionMain), mainAxisTrailingEdge); setLayoutPosition( - (style_.computeInlineStartMargin(crossAxis, direction, ownerWidth) + + (style_.computeInlineStartMargin(crossAxis, direction, ownerWidth, this) + relativePositionCross), crossAxisLeadingEdge); setLayoutPosition( - (style_.computeInlineEndMargin(crossAxis, direction, ownerWidth) + + (style_.computeInlineEndMargin(crossAxis, direction, ownerWidth, this) + relativePositionCross), crossAxisTrailingEdge); } @@ -343,14 +343,15 @@ FloatOptional Node::resolveFlexBasis( FlexDirection flexDirection, float referenceLength, float ownerWidth) const { - FloatOptional value = processFlexBasis().resolve(referenceLength); + FloatOptional value = processFlexBasis().resolve(referenceLength, this); if (style_.boxSizing() == BoxSizing::BorderBox) { return value; } Dimension dim = dimension(flexDirection); - FloatOptional dimensionPaddingAndBorder = FloatOptional{ - style_.computePaddingAndBorderForDimension(direction, dim, ownerWidth)}; + FloatOptional dimensionPaddingAndBorder = + FloatOptional{style_.computePaddingAndBorderForDimension( + direction, dim, ownerWidth, this)}; return value + (dimensionPaddingAndBorder.isDefined() ? dimensionPaddingAndBorder diff --git a/packages/react-native/ReactCommon/yoga/yoga/node/Node.h b/packages/react-native/ReactCommon/yoga/yoga/node/Node.h index d22c4c1ef040..caf076a7c818 100644 --- a/packages/react-native/ReactCommon/yoga/yoga/node/Node.h +++ b/packages/react-native/ReactCommon/yoga/yoga/node/Node.h @@ -88,7 +88,7 @@ class YG_EXPORT Node : public ::YGNode { * https://www.w3.org/TR/css-sizing-3/#definite */ inline bool hasDefiniteLength(Dimension dimension, float ownerSize) { - auto usedValue = getProcessedDimension(dimension).resolve(ownerSize); + auto usedValue = getProcessedDimension(dimension).resolve(ownerSize, this); return usedValue.isDefined() && usedValue.unwrap() >= 0.0f; } @@ -186,14 +186,14 @@ class YG_EXPORT Node : public ::YGNode { float referenceLength, float ownerWidth) const { FloatOptional value = - getProcessedDimension(dimension).resolve(referenceLength); + getProcessedDimension(dimension).resolve(referenceLength, this); if (style_.boxSizing() == BoxSizing::BorderBox) { return value; } FloatOptional dimensionPaddingAndBorder = FloatOptional{style_.computePaddingAndBorderForDimension( - direction, dimension, ownerWidth)}; + direction, dimension, ownerWidth, this)}; return value + (dimensionPaddingAndBorder.isDefined() ? dimensionPaddingAndBorder diff --git a/packages/react-native/ReactCommon/yoga/yoga/style/Style.h b/packages/react-native/ReactCommon/yoga/yoga/style/Style.h index eedeaba2e50b..7b0dc3c827b4 100644 --- a/packages/react-native/ReactCommon/yoga/yoga/style/Style.h +++ b/packages/react-native/ReactCommon/yoga/yoga/style/Style.h @@ -293,14 +293,15 @@ class YG_EXPORT Style { Direction direction, Dimension axis, float referenceLength, - float ownerWidth) const { - FloatOptional value = minDimension(axis).resolve(referenceLength); + float ownerWidth, + YGNodeConstRef node) const { + FloatOptional value = minDimension(axis).resolve(referenceLength, node); if (boxSizing() == BoxSizing::BorderBox) { return value; } FloatOptional dimensionPaddingAndBorder = FloatOptional{ - computePaddingAndBorderForDimension(direction, axis, ownerWidth)}; + computePaddingAndBorderForDimension(direction, axis, ownerWidth, node)}; return value + (dimensionPaddingAndBorder.isDefined() ? dimensionPaddingAndBorder @@ -318,14 +319,15 @@ class YG_EXPORT Style { Direction direction, Dimension axis, float referenceLength, - float ownerWidth) const { - FloatOptional value = maxDimension(axis).resolve(referenceLength); + float ownerWidth, + YGNodeConstRef node) const { + FloatOptional value = maxDimension(axis).resolve(referenceLength, node); if (boxSizing() == BoxSizing::BorderBox) { return value; } FloatOptional dimensionPaddingAndBorder = FloatOptional{ - computePaddingAndBorderForDimension(direction, axis, ownerWidth)}; + computePaddingAndBorderForDimension(direction, axis, ownerWidth, node)}; return value + (dimensionPaddingAndBorder.isDefined() ? dimensionPaddingAndBorder @@ -408,100 +410,123 @@ class YG_EXPORT Style { float computeFlexStartPosition( FlexDirection axis, Direction direction, - float axisSize) const { + float axisSize, + YGNodeConstRef node) const { return computePosition(flexStartEdge(axis), direction) - .resolve(axisSize) + .resolve(axisSize, node) .unwrapOrDefault(0.0f); } float computeInlineStartPosition( FlexDirection axis, Direction direction, - float axisSize) const { + float axisSize, + YGNodeConstRef node) const { return computePosition(inlineStartEdge(axis, direction), direction) - .resolve(axisSize) + .resolve(axisSize, node) .unwrapOrDefault(0.0f); } float computeFlexEndPosition( FlexDirection axis, Direction direction, - float axisSize) const { + float axisSize, + YGNodeConstRef node) const { return computePosition(flexEndEdge(axis), direction) - .resolve(axisSize) + .resolve(axisSize, node) .unwrapOrDefault(0.0f); } float computeInlineEndPosition( FlexDirection axis, Direction direction, - float axisSize) const { + float axisSize, + YGNodeConstRef node) const { return computePosition(inlineEndEdge(axis, direction), direction) - .resolve(axisSize) + .resolve(axisSize, node) .unwrapOrDefault(0.0f); } float computeFlexStartMargin( FlexDirection axis, Direction direction, - float widthSize) const { + float widthSize, + YGNodeConstRef node) const { return computeMargin(flexStartEdge(axis), direction) - .resolve(widthSize) + .resolve(widthSize, node) .unwrapOrDefault(0.0f); } float computeInlineStartMargin( FlexDirection axis, Direction direction, - float widthSize) const { + float widthSize, + YGNodeConstRef node) const { return computeMargin(inlineStartEdge(axis, direction), direction) - .resolve(widthSize) + .resolve(widthSize, node) .unwrapOrDefault(0.0f); } float computeFlexEndMargin( FlexDirection axis, Direction direction, - float widthSize) const { + float widthSize, + YGNodeConstRef node) const { return computeMargin(flexEndEdge(axis), direction) - .resolve(widthSize) + .resolve(widthSize, node) .unwrapOrDefault(0.0f); } float computeInlineEndMargin( FlexDirection axis, Direction direction, - float widthSize) const { + float widthSize, + YGNodeConstRef node) const { return computeMargin(inlineEndEdge(axis, direction), direction) - .resolve(widthSize) + .resolve(widthSize, node) .unwrapOrDefault(0.0f); } - float computeFlexStartBorder(FlexDirection axis, Direction direction) const { + float computeFlexStartBorder( + FlexDirection axis, + Direction direction, + YGNodeConstRef node) const { return maxOrDefined( - computeBorder(flexStartEdge(axis), direction).resolve(0.0f).unwrap(), + computeBorder(flexStartEdge(axis), direction) + .resolve(0.0f, node) + .unwrap(), 0.0f); } - float computeInlineStartBorder(FlexDirection axis, Direction direction) - const { + float computeInlineStartBorder( + FlexDirection axis, + Direction direction, + YGNodeConstRef node) const { return maxOrDefined( computeBorder(inlineStartEdge(axis, direction), direction) - .resolve(0.0f) + .resolve(0.0f, node) .unwrap(), 0.0f); } - float computeFlexEndBorder(FlexDirection axis, Direction direction) const { + float computeFlexEndBorder( + FlexDirection axis, + Direction direction, + YGNodeConstRef node) const { return maxOrDefined( - computeBorder(flexEndEdge(axis), direction).resolve(0.0f).unwrap(), + computeBorder(flexEndEdge(axis), direction) + .resolve(0.0f, node) + .unwrap(), 0.0f); } - float computeInlineEndBorder(FlexDirection axis, Direction direction) const { + float computeInlineEndBorder( + FlexDirection axis, + Direction direction, + YGNodeConstRef node) const { return maxOrDefined( computeBorder(inlineEndEdge(axis, direction), direction) - .resolve(0.0f) + .resolve(0.0f, node) .unwrap(), 0.0f); } @@ -509,10 +534,11 @@ class YG_EXPORT Style { float computeFlexStartPadding( FlexDirection axis, Direction direction, - float widthSize) const { + float widthSize, + YGNodeConstRef node) const { return maxOrDefined( computePadding(flexStartEdge(axis), direction) - .resolve(widthSize) + .resolve(widthSize, node) .unwrap(), 0.0f); } @@ -520,10 +546,11 @@ class YG_EXPORT Style { float computeInlineStartPadding( FlexDirection axis, Direction direction, - float widthSize) const { + float widthSize, + YGNodeConstRef node) const { return maxOrDefined( computePadding(inlineStartEdge(axis, direction), direction) - .resolve(widthSize) + .resolve(widthSize, node) .unwrap(), 0.0f); } @@ -531,10 +558,11 @@ class YG_EXPORT Style { float computeFlexEndPadding( FlexDirection axis, Direction direction, - float widthSize) const { + float widthSize, + YGNodeConstRef node) const { return maxOrDefined( computePadding(flexEndEdge(axis), direction) - .resolve(widthSize) + .resolve(widthSize, node) .unwrap(), 0.0f); } @@ -542,10 +570,11 @@ class YG_EXPORT Style { float computeInlineEndPadding( FlexDirection axis, Direction direction, - float widthSize) const { + float widthSize, + YGNodeConstRef node) const { return maxOrDefined( computePadding(inlineEndEdge(axis, direction), direction) - .resolve(widthSize) + .resolve(widthSize, node) .unwrap(), 0.0f); } @@ -553,70 +582,82 @@ class YG_EXPORT Style { float computeInlineStartPaddingAndBorder( FlexDirection axis, Direction direction, - float widthSize) const { - return computeInlineStartPadding(axis, direction, widthSize) + - computeInlineStartBorder(axis, direction); + float widthSize, + YGNodeConstRef node) const { + return computeInlineStartPadding(axis, direction, widthSize, node) + + computeInlineStartBorder(axis, direction, node); } float computeFlexStartPaddingAndBorder( FlexDirection axis, Direction direction, - float widthSize) const { - return computeFlexStartPadding(axis, direction, widthSize) + - computeFlexStartBorder(axis, direction); + float widthSize, + YGNodeConstRef node) const { + return computeFlexStartPadding(axis, direction, widthSize, node) + + computeFlexStartBorder(axis, direction, node); } float computeInlineEndPaddingAndBorder( FlexDirection axis, Direction direction, - float widthSize) const { - return computeInlineEndPadding(axis, direction, widthSize) + - computeInlineEndBorder(axis, direction); + float widthSize, + YGNodeConstRef node) const { + return computeInlineEndPadding(axis, direction, widthSize, node) + + computeInlineEndBorder(axis, direction, node); } float computeFlexEndPaddingAndBorder( FlexDirection axis, Direction direction, - float widthSize) const { - return computeFlexEndPadding(axis, direction, widthSize) + - computeFlexEndBorder(axis, direction); + float widthSize, + YGNodeConstRef node) const { + return computeFlexEndPadding(axis, direction, widthSize, node) + + computeFlexEndBorder(axis, direction, node); } float computePaddingAndBorderForDimension( Direction direction, Dimension dimension, - float widthSize) const { + float widthSize, + YGNodeConstRef node) const { FlexDirection flexDirectionForDimension = dimension == Dimension::Width ? FlexDirection::Row : FlexDirection::Column; return computeFlexStartPaddingAndBorder( - flexDirectionForDimension, direction, widthSize) + + flexDirectionForDimension, direction, widthSize, node) + computeFlexEndPaddingAndBorder( - flexDirectionForDimension, direction, widthSize); + flexDirectionForDimension, direction, widthSize, node); } - float computeBorderForAxis(FlexDirection axis) const { - return computeInlineStartBorder(axis, Direction::LTR) + - computeInlineEndBorder(axis, Direction::LTR); + float computeBorderForAxis(FlexDirection axis, YGNodeConstRef node) const { + return computeInlineStartBorder(axis, Direction::LTR, node) + + computeInlineEndBorder(axis, Direction::LTR, node); } - float computeMarginForAxis(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 - return computeInlineStartMargin(axis, Direction::LTR, widthSize) + - computeInlineEndMargin(axis, Direction::LTR, widthSize); + float computeMarginForAxis( + FlexDirection axis, + float widthSize, + YGNodeConstRef node) const { + return computeInlineStartMargin(axis, Direction::LTR, widthSize, node) + + computeInlineEndMargin(axis, Direction::LTR, widthSize, node); } - float computeGapForAxis(FlexDirection axis, float ownerSize) const { + float computeGapForAxis( + FlexDirection axis, + float ownerSize, + YGNodeConstRef node) const { auto gap = isRow(axis) ? computeColumnGap() : computeRowGap(); - return maxOrDefined(gap.resolve(ownerSize).unwrap(), 0.0f); + return maxOrDefined(gap.resolve(ownerSize, node).unwrap(), 0.0f); } - float computeGapForDimension(Dimension dimension, float ownerSize) const { + float computeGapForDimension( + Dimension dimension, + float ownerSize, + YGNodeConstRef node) const { auto gap = dimension == Dimension::Width ? computeColumnGap() : computeRowGap(); - return maxOrDefined(gap.resolve(ownerSize).unwrap(), 0.0f); + return maxOrDefined(gap.resolve(ownerSize, node).unwrap(), 0.0f); } bool flexStartMarginIsAuto(FlexDirection axis, Direction direction) const { diff --git a/packages/react-native/ReactCommon/yoga/yoga/style/StyleLength.h b/packages/react-native/ReactCommon/yoga/yoga/style/StyleLength.h index 8099ce7df4e4..5a833da58e1c 100644 --- a/packages/react-native/ReactCommon/yoga/yoga/style/StyleLength.h +++ b/packages/react-native/ReactCommon/yoga/yoga/style/StyleLength.h @@ -49,6 +49,10 @@ class StyleLength { return StyleLength{{}, Unit::Undefined}; } + static StyleLength dynamic(YGValueDynamic callback, YGValueDynamicID id) { + return StyleLength{callback, id}; + } + constexpr bool isAuto() const { return unit_ == Unit::Auto; } @@ -65,15 +69,31 @@ class StyleLength { return unit_ == Unit::Percent; } + constexpr bool isDynamic() const { + return unit_ == Unit::Dynamic; + } + constexpr bool isDefined() const { return !isUndefined(); } constexpr FloatOptional value() const { - return value_; + if (isDynamic()) { + return FloatOptional{}; + } + return payload_.value; + } + + YGValueDynamic callback() const { + return isDynamic() ? payload_.dynamic.callback : nullptr; + } + + constexpr YGValueDynamicID callbackId() const { + return isDynamic() ? payload_.dynamic.id : 0; } - constexpr FloatOptional resolve(float referenceLength) { + constexpr FloatOptional resolve(float referenceLength, YGNodeConstRef node) + const { #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wswitch-enum" @@ -83,34 +103,73 @@ class StyleLength { #pragma clang diagnostic pop #endif case Unit::Point: - return value_; + return payload_.value; case Unit::Percent: - return FloatOptional{value_.unwrap() * referenceLength * 0.01f}; + return FloatOptional{payload_.value.unwrap() * referenceLength * 0.01f}; + case Unit::Dynamic: + if (payload_.dynamic.callback != nullptr && node != nullptr) { + auto value = payload_.dynamic.callback( + node, + payload_.dynamic.id, + YGValueDynamicContext{referenceLength}); + return FloatOptional{value.value}; + } + return FloatOptional{}; default: return FloatOptional{}; } } explicit constexpr operator YGValue() const { - return YGValue{value_.unwrap(), unscopedEnum(unit_)}; + return YGValue{value().unwrap(), unscopedEnum(unit_)}; } constexpr bool operator==(const StyleLength& rhs) const { - return value_ == rhs.value_ && unit_ == rhs.unit_; + if (unit_ != rhs.unit_) { + return false; + } + if (isDynamic()) { + return payload_.dynamic.callback == rhs.payload_.dynamic.callback && + payload_.dynamic.id == rhs.payload_.dynamic.id; + } + return payload_.value == rhs.payload_.value; } constexpr bool inexactEquals(const StyleLength& other) const { - return unit_ == other.unit_ && - facebook::yoga::inexactEquals(value_, other.value_); + if (unit_ != other.unit_) { + return false; + } + if (isDynamic()) { + return payload_.dynamic.callback == other.payload_.dynamic.callback && + payload_.dynamic.id == other.payload_.dynamic.id; + } + return facebook::yoga::inexactEquals(payload_.value, other.payload_.value); } private: + struct YGValueDynamicData { + YGValueDynamic callback; + YGValueDynamicID id; + }; + + union Payload { + constexpr Payload() : value{} {} + constexpr explicit Payload(FloatOptional val) : value(val) {} + constexpr Payload(YGValueDynamic callback, YGValueDynamicID id) + : dynamic{callback, id} {} + + FloatOptional value; + YGValueDynamicData dynamic; + }; + // We intentionally do not allow direct construction using value and unit, to // avoid invalid, or redundant combinations. constexpr StyleLength(FloatOptional value, Unit unit) - : value_(value), unit_(unit) {} + : payload_(value), unit_(unit) {} + constexpr StyleLength(YGValueDynamic callback, YGValueDynamicID id) + : payload_(callback, id), unit_(Unit::Dynamic) {} - FloatOptional value_{}; + Payload payload_{}; Unit unit_{Unit::Undefined}; }; diff --git a/packages/react-native/ReactCommon/yoga/yoga/style/StyleSizeLength.h b/packages/react-native/ReactCommon/yoga/yoga/style/StyleSizeLength.h index 76e079b2da58..9a67cd0cb882 100644 --- a/packages/react-native/ReactCommon/yoga/yoga/style/StyleSizeLength.h +++ b/packages/react-native/ReactCommon/yoga/yoga/style/StyleSizeLength.h @@ -68,6 +68,10 @@ class StyleSizeLength { return StyleSizeLength{{}, Unit::Undefined}; } + static StyleSizeLength dynamic(YGValueDynamic callback, YGValueDynamicID id) { + return StyleSizeLength{callback, id}; + } + constexpr bool isAuto() const { return unit_ == Unit::Auto; } @@ -100,11 +104,27 @@ class StyleSizeLength { return unit_ == Unit::Percent; } + constexpr bool isDynamic() const { + return unit_ == Unit::Dynamic; + } + constexpr FloatOptional value() const { - return value_; + if (isDynamic()) { + return FloatOptional{}; + } + return payload_.value; + } + + YGValueDynamic callback() const { + return isDynamic() ? payload_.dynamic.callback : nullptr; + } + + constexpr YGValueDynamicID callbackId() const { + return isDynamic() ? payload_.dynamic.id : 0; } - constexpr FloatOptional resolve(float referenceLength) const { + constexpr FloatOptional resolve(float referenceLength, YGNodeConstRef node) + const { #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wswitch-enum" @@ -114,34 +134,73 @@ class StyleSizeLength { #pragma clang diagnostic pop #endif case Unit::Point: - return value_; + return payload_.value; case Unit::Percent: - return FloatOptional{value_.unwrap() * referenceLength * 0.01f}; + return FloatOptional{payload_.value.unwrap() * referenceLength * 0.01f}; + case Unit::Dynamic: + if (payload_.dynamic.callback != nullptr && node != nullptr) { + auto value = payload_.dynamic.callback( + node, + payload_.dynamic.id, + YGValueDynamicContext{referenceLength}); + return FloatOptional{value.value}; + } + return FloatOptional{}; default: return FloatOptional{}; } } explicit constexpr operator YGValue() const { - return YGValue{value_.unwrap(), unscopedEnum(unit_)}; + return YGValue{value().unwrap(), unscopedEnum(unit_)}; } constexpr bool operator==(const StyleSizeLength& rhs) const { - return value_ == rhs.value_ && unit_ == rhs.unit_; + if (unit_ != rhs.unit_) { + return false; + } + if (isDynamic()) { + return payload_.dynamic.callback == rhs.payload_.dynamic.callback && + payload_.dynamic.id == rhs.payload_.dynamic.id; + } + return payload_.value == rhs.payload_.value; } constexpr bool inexactEquals(const StyleSizeLength& other) const { - return unit_ == other.unit_ && - facebook::yoga::inexactEquals(value_, other.value_); + if (unit_ != other.unit_) { + return false; + } + if (isDynamic()) { + return payload_.dynamic.callback == other.payload_.dynamic.callback && + payload_.dynamic.id == other.payload_.dynamic.id; + } + return facebook::yoga::inexactEquals(payload_.value, other.payload_.value); } private: + struct YGValueDynamicData { + YGValueDynamic callback; + YGValueDynamicID id; + }; + + union Payload { + constexpr Payload() : value{} {} + constexpr explicit Payload(FloatOptional val) : value(val) {} + constexpr Payload(YGValueDynamic callback, YGValueDynamicID id) + : dynamic{callback, id} {} + + FloatOptional value; + YGValueDynamicData dynamic; + }; + // We intentionally do not allow direct construction using value and unit, to // avoid invalid, or redundant combinations. constexpr StyleSizeLength(FloatOptional value, Unit unit) - : value_(value), unit_(unit) {} + : payload_(value), unit_(unit) {} + constexpr StyleSizeLength(YGValueDynamic callback, YGValueDynamicID id) + : payload_(callback, id), unit_(Unit::Dynamic) {} - FloatOptional value_{}; + Payload payload_{}; Unit unit_{Unit::Undefined}; }; diff --git a/packages/react-native/ReactCommon/yoga/yoga/style/StyleValueHandle.h b/packages/react-native/ReactCommon/yoga/yoga/style/StyleValueHandle.h index d9c6ae05791e..3615fefe7f98 100644 --- a/packages/react-native/ReactCommon/yoga/yoga/style/StyleValueHandle.h +++ b/packages/react-native/ReactCommon/yoga/yoga/style/StyleValueHandle.h @@ -49,6 +49,10 @@ class StyleValueHandle { return type() == Type::Auto; } + constexpr bool isDynamic() const { + return type() == Type::Dynamic; + } + private: friend class StyleValuePool; @@ -62,7 +66,8 @@ class StyleValueHandle { Percent, Number, Auto, - Keyword + Keyword, + Dynamic }; // Intentionally leaving out auto as a fast path diff --git a/packages/react-native/ReactCommon/yoga/yoga/style/StyleValuePool.h b/packages/react-native/ReactCommon/yoga/yoga/style/StyleValuePool.h index dfee30ade9f1..f21b6651110b 100644 --- a/packages/react-native/ReactCommon/yoga/yoga/style/StyleValuePool.h +++ b/packages/react-native/ReactCommon/yoga/yoga/style/StyleValuePool.h @@ -32,6 +32,8 @@ class StyleValuePool { handle.setType(StyleValueHandle::Type::Undefined); } else if (length.isAuto()) { handle.setType(StyleValueHandle::Type::Auto); + } else if (length.isDynamic()) { + storeDynamic(handle, length.callback(), length.callbackId()); } else { auto type = length.isPoints() ? StyleValueHandle::Type::Point : StyleValueHandle::Type::Percent; @@ -50,6 +52,8 @@ class StyleValuePool { storeKeyword(handle, StyleValueHandle::Keyword::Stretch); } else if (sizeValue.isFitContent()) { storeKeyword(handle, StyleValueHandle::Keyword::FitContent); + } else if (sizeValue.isDynamic()) { + storeDynamic(handle, sizeValue.callback(), sizeValue.callbackId()); } else { auto type = sizeValue.isPoints() ? StyleValueHandle::Type::Point : StyleValueHandle::Type::Percent; @@ -70,6 +74,9 @@ class StyleValuePool { return StyleLength::undefined(); } else if (handle.isAuto()) { return StyleLength::ofAuto(); + } else if (handle.isDynamic()) { + return StyleLength::dynamic( + getDynamicCallback(handle), getDynamicCallbackID(handle)); } else { assert( handle.type() == StyleValueHandle::Type::Point || @@ -95,6 +102,9 @@ class StyleValuePool { return StyleSizeLength::ofFitContent(); } else if (handle.isKeyword(StyleValueHandle::Keyword::Stretch)) { return StyleSizeLength::ofStretch(); + } else if (handle.isDynamic()) { + return StyleSizeLength::dynamic( + getDynamicCallback(handle), getDynamicCallbackID(handle)); } else { assert( handle.type() == StyleValueHandle::Type::Point || @@ -121,6 +131,31 @@ class StyleValuePool { } } + void storeDynamic( + StyleValueHandle& handle, + YGValueDynamic callback, + YGValueDynamicID id) { + handle.setType(StyleValueHandle::Type::Dynamic); + auto packed = static_cast(reinterpret_cast(callback)); + + if (handle.isValueIndexed()) { + auto oldIndex = handle.value(); + auto newIndex = buffer_.replace(oldIndex, packed); + if (newIndex == oldIndex) { + [[maybe_unused]] auto replacedIndex = buffer_.replace( + static_cast(newIndex + 2), static_cast(id)); + } else { + buffer_.push(static_cast(id)); + } + handle.setValue(newIndex); + } else { + auto newIndex = buffer_.push(packed); + buffer_.push(static_cast(id)); + handle.setValue(newIndex); + handle.setValueIsIndexed(); + } + } + private: void storeValue( StyleValueHandle& handle, @@ -155,6 +190,19 @@ class StyleValuePool { } } + YGValueDynamic getDynamicCallback(StyleValueHandle handle) const { + assert(handle.isDynamic()); + assert(handle.isValueIndexed()); + return reinterpret_cast( + static_cast(buffer_.get64(handle.value()))); + } + + YGValueDynamicID getDynamicCallbackID(StyleValueHandle handle) const { + assert(handle.isDynamic()); + assert(handle.isValueIndexed()); + return buffer_.get32(handle.value() + 2); + } + static constexpr bool isIntegerPackable(float f) { constexpr uint16_t kMaxInlineAbsValue = (1 << 11) - 1;