diff --git a/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm b/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm index 24f924c64..1a7ec25e0 100644 --- a/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +++ b/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm @@ -137,10 +137,10 @@ using namespace facebook::react; // `border` if ( - oldViewProps.borderWidth != newViewProps.borderWidth || - oldViewProps.borderStyle != newViewProps.borderStyle || - oldViewProps.borderRadius != newViewProps.borderRadius || - oldViewProps.borderColor != newViewProps.borderColor + oldViewProps.borderWidths != newViewProps.borderWidths || + oldViewProps.borderStyles != newViewProps.borderStyles || + oldViewProps.borderRadii != newViewProps.borderRadii || + oldViewProps.borderColors != newViewProps.borderColors ) { [self invalidateBorder]; } @@ -174,10 +174,15 @@ using namespace facebook::react; { const auto &props = *std::dynamic_pointer_cast(_props); - bool useCoreAnimationBorderRendering = - props.borderStyle == BorderStyle::Solid && - props.borderWidth.isUniform() && - props.borderRadius.isUniform(); + const auto borderMetrics = + props.resolveBorderMetrics(_layoutMetrics.layoutDirection == LayoutDirection::RightToLeft); + + const bool useCoreAnimationBorderRendering = + borderMetrics.borderColors.isUniform() && + borderMetrics.borderWidths.isUniform() && + borderMetrics.borderStyles.isUniform() && + borderMetrics.borderRadii.isUniform() && + borderMetrics.borderStyles.left == BorderStyle::Solid; CALayer *layer = self.layer; if (_isCoreAnimationBorderRenderingEnabled != useCoreAnimationBorderRendering) { @@ -190,11 +195,10 @@ using namespace facebook::react; } if (useCoreAnimationBorderRendering) { - layer.borderWidth = (CGFloat)props.borderWidth.left; - layer.borderColor = RCTCGColorRefFromSharedColor(props.borderColor); - layer.cornerRadius = (CGFloat)props.borderRadius.topLeft; - _contentView.layer.cornerRadius = (CGFloat)props.borderRadius.topLeft; - _contentView.layer.masksToBounds = YES; + layer.borderWidth = (CGFloat)borderMetrics.borderWidths.left; + layer.borderColor = RCTCGColorRefFromSharedColor(borderMetrics.borderColors.left); + layer.cornerRadius = (CGFloat)borderMetrics.borderRadii.topLeft; + _contentView.layer.cornerRadius = (CGFloat)borderMetrics.borderRadii.topLeft; } else { // Not supported yet. } diff --git a/ReactCommon/fabric/components/view/ViewProps.cpp b/ReactCommon/fabric/components/view/ViewProps.cpp index 72af1c675..f656882fc 100644 --- a/ReactCommon/fabric/components/view/ViewProps.cpp +++ b/ReactCommon/fabric/components/view/ViewProps.cpp @@ -8,6 +8,7 @@ #include "ViewProps.h" #include +#include #include #include #include @@ -24,10 +25,10 @@ ViewProps::ViewProps(const ViewProps &sourceProps, const RawProps &rawProps): opacity(convertRawProp(rawProps, "opacity", sourceProps.opacity, (Float)1.0)), foregroundColor(convertRawProp(rawProps, "foregroundColor", sourceProps.foregroundColor)), backgroundColor(convertRawProp(rawProps, "backgroundColor", sourceProps.backgroundColor)), - borderWidth(convertRawProp(rawProps, "borderWidth", sourceProps.borderWidth)), - borderRadius(convertRawProp(rawProps, "borderRadius", sourceProps.borderRadius)), - borderColor(convertRawProp(rawProps, "borderColor", sourceProps.borderColor)), - borderStyle(convertRawProp(rawProps, "borderStyle", sourceProps.borderStyle)), + borderWidths(convertRawProp(rawProps, "border", "Width", sourceProps.borderWidths)), + borderRadii(convertRawProp(rawProps, "border", "Radius", sourceProps.borderRadii)), + borderColors(convertRawProp(rawProps, "border", "Color", sourceProps.borderColors)), + borderStyles(convertRawProp(rawProps, "border", "Style", sourceProps.borderStyles)), shadowColor(convertRawProp(rawProps, "shadowColor", sourceProps.shadowColor)), shadowOffset(convertRawProp(rawProps, "shadowOffset", sourceProps.shadowOffset)), shadowOpacity(convertRawProp(rawProps, "shadowOpacity", sourceProps.shadowOpacity)), @@ -40,6 +41,17 @@ ViewProps::ViewProps(const ViewProps &sourceProps, const RawProps &rawProps): hitSlop(convertRawProp(rawProps, "hitSlop", sourceProps.hitSlop)), onLayout(convertRawProp(rawProps, "onLayout", sourceProps.onLayout)) {}; +#pragma mark - Convenience Methods + +BorderMetrics ViewProps::resolveBorderMetrics(bool isRTL) const { + return { + .borderColors = borderColors.resolve(isRTL, {}), + .borderWidths = borderWidths.resolve(isRTL, 0), + .borderRadii = borderRadii.resolve(isRTL, 0), + .borderStyles = borderStyles.resolve(isRTL, BorderStyle::Solid) + }; +} + #pragma mark - DebugStringConvertible SharedDebugStringConvertibleList ViewProps::getDebugProps() const { diff --git a/ReactCommon/fabric/components/view/ViewProps.h b/ReactCommon/fabric/components/view/ViewProps.h index d245e1c5e..9ac4fa296 100644 --- a/ReactCommon/fabric/components/view/ViewProps.h +++ b/ReactCommon/fabric/components/view/ViewProps.h @@ -39,10 +39,10 @@ public: const SharedColor backgroundColor {}; // Borders - const EdgeInsets borderWidth {}; - const CornerInsets borderRadius {}; - const SharedColor borderColor {}; - const BorderStyle borderStyle {}; + const CascadedBorderWidths borderWidths {}; + const CascadedBorderRadii borderRadii {}; + const CascadedBorderColors borderColors {}; + const CascadedBorderStyles borderStyles {}; // Shadow const SharedColor shadowColor {}; @@ -61,6 +61,10 @@ public: const EdgeInsets hitSlop {}; const bool onLayout {}; +#pragma mark - Convenience Methods + + BorderMetrics resolveBorderMetrics(bool isRTL) const; + #pragma mark - DebugStringConvertible SharedDebugStringConvertibleList getDebugProps() const override; diff --git a/ReactCommon/fabric/components/view/primitives.h b/ReactCommon/fabric/components/view/primitives.h index c7239ec3e..0a540d766 100644 --- a/ReactCommon/fabric/components/view/primitives.h +++ b/ReactCommon/fabric/components/view/primitives.h @@ -8,7 +8,9 @@ #pragma once #include +#include #include +#include namespace facebook { namespace react { @@ -157,5 +159,112 @@ enum class BorderStyle { Dashed }; +template +struct CascadedRectangleEdges { + using Counterpart = RectangleEdges; + using OptionalT = folly::Optional; + + OptionalT left {}; + OptionalT top {}; + OptionalT right {}; + OptionalT bottom {}; + OptionalT start {}; + OptionalT end {}; + OptionalT horizontal {}; + OptionalT vertical {}; + OptionalT all {}; + + Counterpart resolve(bool isRTL, T defaults) const { + const auto leading = isRTL ? end : start; + const auto trailing = isRTL ? start : end; + const auto horizontalOrAllOrDefault = horizontal.value_or(all.value_or(defaults)); + const auto verticalOrAllOrDefault = vertical.value_or(all.value_or(defaults)); + + return Counterpart { + .left = left.value_or(leading.value_or(horizontalOrAllOrDefault)), + .right = right.value_or(trailing.value_or(horizontalOrAllOrDefault)), + .top = top.value_or(verticalOrAllOrDefault), + .bottom = bottom.value_or(verticalOrAllOrDefault) + }; + } + + bool operator==(const CascadedRectangleEdges &rhs) const { + return + std::tie(this->left, this->top, this->right, this->bottom, this->start, this->end, this->horizontal, this->vertical, this->all) == + std::tie(rhs.left, rhs.top, rhs.right, rhs.bottom, rhs.start, rhs.end, rhs.horizontal, rhs.vertical, rhs.all); + } + + bool operator!=(const CascadedRectangleEdges &rhs) const { + return !(*this == rhs); + } +}; + +template +struct CascadedRectangleCorners { + using Counterpart = RectangleCorners; + using OptionalT = folly::Optional; + + OptionalT topLeft {}; + OptionalT topRight {}; + OptionalT bottomLeft {}; + OptionalT bottomRight {}; + OptionalT topStart {}; + OptionalT topEnd {}; + OptionalT bottomStart {}; + OptionalT bottomEnd {}; + OptionalT all {}; + + Counterpart resolve(bool isRTL, T defaults) const { + const auto topLeading = isRTL ? topEnd : topStart; + const auto topTrailing = isRTL ? topStart : topEnd; + const auto bottomLeading = isRTL ? bottomEnd : bottomStart; + const auto bottomTrailing = isRTL ? bottomStart : bottomEnd; + + return Counterpart { + .topLeft = topLeft.value_or(topLeading.value_or(all.value_or(defaults))), + .topRight = topRight.value_or(topTrailing.value_or(all.value_or(defaults))), + .bottomLeft = bottomLeft.value_or(topLeading.value_or(all.value_or(defaults))), + .bottomRight = bottomRight.value_or(topTrailing.value_or(all.value_or(defaults))) + }; + } + + bool operator==(const CascadedRectangleCorners &rhs) const { + return + std::tie(this->topLeft, this->topRight, this->bottomLeft, this->bottomRight, this->topStart, this->topEnd, this->bottomStart, this->bottomEnd, this->all) == + std::tie(rhs.topLeft, rhs.topRight, rhs.bottomLeft, rhs.bottomRight, rhs.topStart, rhs.topEnd, rhs.bottomStart, rhs.bottomEnd, rhs.all); + } + + bool operator!=(const CascadedRectangleCorners &rhs) const { + return !(*this == rhs); + } +}; + +using BorderWidths = RectangleEdges; +using BorderStyles = RectangleEdges; +using BorderColors = RectangleEdges; +using BorderRadii = RectangleCorners; + +using CascadedBorderWidths = CascadedRectangleEdges; +using CascadedBorderStyles = CascadedRectangleEdges; +using CascadedBorderColors = CascadedRectangleEdges; +using CascadedBorderRadii = CascadedRectangleCorners; + +struct BorderMetrics { + BorderColors borderColors {}; + BorderWidths borderWidths {}; + BorderRadii borderRadii {}; + BorderStyles borderStyles {}; + + bool operator==(const BorderMetrics &rhs) const { + return + std::tie(this->borderColors, this->borderWidths, this->borderRadii, this->borderStyles) == + std::tie(rhs.borderColors, rhs.borderWidths, rhs.borderRadii, rhs.borderStyles); + } + + bool operator!=(const BorderMetrics &rhs) const { + return !(*this == rhs); + } +}; + } // namespace react } // namespace facebook diff --git a/ReactCommon/fabric/components/view/propsConversions.h b/ReactCommon/fabric/components/view/propsConversions.h index 64d91b310..da1b7e1fd 100644 --- a/ReactCommon/fabric/components/view/propsConversions.h +++ b/ReactCommon/fabric/components/view/propsConversions.h @@ -13,7 +13,7 @@ namespace facebook { namespace react { -static std::array convertRawProp( +static inline std::array convertRawProp( const RawProps &rawProps, const std::string &widthName, const std::string &heightName, @@ -26,7 +26,7 @@ static std::array convertRawProp( return dimentions; } -static std::array convertRawProp( +static inline std::array convertRawProp( const RawProps &rawProps, const std::string &prefix, const std::string &suffix, @@ -46,7 +46,7 @@ static std::array convertRawProp( return result; } -static std::array convertRawProp( +static inline std::array convertRawProp( const RawProps &rawProps, const std::array &sourceValue, const std::array &defaultValue @@ -61,7 +61,7 @@ static std::array convertRawProp( return result; } -static YGStyle convertRawProp(const RawProps &rawProps, const YGStyle &sourceValue) { +static inline YGStyle convertRawProp(const RawProps &rawProps, const YGStyle &sourceValue) { YGStyle yogaStyle; yogaStyle.direction = convertRawProp(rawProps, "direction", sourceValue.direction, yogaStyle.direction); yogaStyle.flexDirection = convertRawProp(rawProps, "flexDirection", sourceValue.flexDirection, yogaStyle.flexDirection); @@ -88,5 +88,53 @@ static YGStyle convertRawProp(const RawProps &rawProps, const YGStyle &sourceVal return yogaStyle; } +template +static inline CascadedRectangleCorners convertRawProp( + const RawProps &rawProps, + const std::string &prefix, + const std::string &suffix, + const CascadedRectangleCorners &sourceValue +) { + CascadedRectangleCorners result; + + result.topLeft = convertRawProp(rawProps, prefix + "TopLeft" + suffix, sourceValue.topLeft); + result.topRight = convertRawProp(rawProps, prefix + "TopRight" + suffix, sourceValue.topRight); + result.bottomLeft = convertRawProp(rawProps, prefix + "BottomLeft" + suffix, sourceValue.bottomLeft); + result.bottomRight = convertRawProp(rawProps, prefix + "BottomRight" + suffix, sourceValue.bottomRight); + + result.topStart = convertRawProp(rawProps, prefix + "TopStart" + suffix, sourceValue.topStart); + result.topEnd = convertRawProp(rawProps, prefix + "TopEnd" + suffix, sourceValue.topEnd); + result.bottomStart = convertRawProp(rawProps, prefix + "BottomStart" + suffix, sourceValue.bottomStart); + result.bottomEnd = convertRawProp(rawProps, prefix + "BottomEnd" + suffix, sourceValue.bottomEnd); + + result.all = convertRawProp(rawProps, prefix + suffix, sourceValue.all); + + return result; +} + +template +static inline CascadedRectangleEdges convertRawProp( + const RawProps &rawProps, + const std::string &prefix, + const std::string &suffix, + const CascadedRectangleEdges &sourceValue +) { + CascadedRectangleEdges result; + + result.left = convertRawProp(rawProps, prefix + "Left" + suffix, sourceValue.left); + result.right = convertRawProp(rawProps, prefix + "Right" + suffix, sourceValue.right); + result.top = convertRawProp(rawProps, prefix + "Top" + suffix, sourceValue.top); + result.bottom = convertRawProp(rawProps, prefix + "Bottom" + suffix, sourceValue.bottom); + + result.start = convertRawProp(rawProps, prefix + "Start" + suffix, sourceValue.start); + result.end = convertRawProp(rawProps, prefix + "End" + suffix, sourceValue.end); + result.horizontal = convertRawProp(rawProps, prefix + "Horizontal" + suffix, sourceValue.horizontal); + result.vertical = convertRawProp(rawProps, prefix + "Vertical" + suffix, sourceValue.vertical); + + result.all = convertRawProp(rawProps, prefix + suffix, sourceValue.all); + + return result; +} + } // namespace react } // namespace facebook