diff --git a/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm b/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm index 07bfcaa6da..0cee6c5569 100644 --- a/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +++ b/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm @@ -27,7 +27,7 @@ using namespace facebook::react; - (instancetype)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { - static const auto defaultProps = std::make_shared(); + static auto const defaultProps = std::make_shared(); _props = defaultProps; } return self; @@ -101,16 +101,16 @@ using namespace facebook::react; RCTAssert( propsRawPtr && ([self class] == [RCTViewComponentView class] || - typeid(*propsRawPtr).hash_code() != typeid(const ViewProps).hash_code()), + typeid(*propsRawPtr).hash_code() != typeid(ViewProps const).hash_code()), @"`RCTViewComponentView` subclasses (and `%@` particularly) must setup `_props`" " instance variable with a default value in the constructor.", NSStringFromClass([self class])); #endif - const auto &oldViewProps = *std::static_pointer_cast(oldProps ?: _props); - const auto &newViewProps = *std::static_pointer_cast(props); + auto const &oldViewProps = *std::static_pointer_cast(oldProps ?: _props); + auto const &newViewProps = *std::static_pointer_cast(props); - _props = std::static_pointer_cast(props); + _props = std::static_pointer_cast(props); BOOL needsInvalidateLayer = NO; @@ -251,8 +251,8 @@ using namespace facebook::react; - (void)updateEventEmitter:(SharedEventEmitter)eventEmitter { - assert(std::dynamic_pointer_cast(eventEmitter)); - _eventEmitter = std::static_pointer_cast(eventEmitter); + assert(std::dynamic_pointer_cast(eventEmitter)); + _eventEmitter = std::static_pointer_cast(eventEmitter); } - (void)updateLayoutMetrics:(LayoutMetrics)layoutMetrics oldLayoutMetrics:(LayoutMetrics)oldLayoutMetrics @@ -374,15 +374,14 @@ static RCTBorderStyle RCTBorderStyleFromBorderStyle(BorderStyle borderStyle) return; } - const auto borderMetrics = - _props->resolveBorderMetrics(_layoutMetrics.layoutDirection == LayoutDirection::RightToLeft); + auto const borderMetrics = _props->resolveBorderMetrics(_layoutMetrics); // Stage 1. Shadow Path - BOOL layerHasShadow = layer.shadowOpacity > 0 && CGColorGetAlpha(layer.shadowColor) > 0; + BOOL const layerHasShadow = layer.shadowOpacity > 0 && CGColorGetAlpha(layer.shadowColor) > 0; if (layerHasShadow) { if (CGColorGetAlpha(_backgroundColor.CGColor) > 0.999) { // If view has a solid background color, calculate shadow path from border. - const RCTCornerInsets cornerInsets = + RCTCornerInsets const cornerInsets = RCTGetCornerInsets(RCTCornerRadiiFromBorderRadii(borderMetrics.borderRadii), UIEdgeInsetsZero); CGPathRef shadowPath = RCTPathCreateWithRoundedRect(self.bounds, cornerInsets, nil); layer.shadowPath = shadowPath; @@ -396,7 +395,7 @@ static RCTBorderStyle RCTBorderStyleFromBorderStyle(BorderStyle borderStyle) } // Stage 2. Border Rendering - const bool useCoreAnimationBorderRendering = + bool const useCoreAnimationBorderRendering = borderMetrics.borderColors.isUniform() && borderMetrics.borderWidths.isUniform() && borderMetrics.borderStyles.isUniform() && borderMetrics.borderRadii.isUniform() && borderMetrics.borderStyles.left == BorderStyle::Solid && @@ -458,7 +457,7 @@ static RCTBorderStyle RCTBorderStyleFromBorderStyle(BorderStyle borderStyle) _borderLayer.contents = (id)image.CGImage; _borderLayer.contentsScale = image.scale; - const BOOL isResizable = !UIEdgeInsetsEqualToEdgeInsets(image.capInsets, UIEdgeInsetsZero); + BOOL isResizable = !UIEdgeInsetsEqualToEdgeInsets(image.capInsets, UIEdgeInsetsZero); if (isResizable) { _borderLayer.contentsCenter = contentsCenter; } else { @@ -530,14 +529,14 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view) - (NSArray *)accessibilityCustomActions { - const auto &accessibilityActions = _props->accessibilityActions; + auto const &accessibilityActions = _props->accessibilityActions; if (accessibilityActions.size() == 0) { return nil; } NSMutableArray *customActions = [NSMutableArray array]; - for (const auto &accessibilityAction : accessibilityActions) { + for (auto const &accessibilityAction : accessibilityActions) { [customActions addObject:[[UIAccessibilityCustomAction alloc] initWithName:RCTNSStringFromString(accessibilityAction) target:self diff --git a/ReactCommon/fabric/components/view/ViewProps.cpp b/ReactCommon/fabric/components/view/ViewProps.cpp index f2dc4572f8..99e421281b 100644 --- a/ReactCommon/fabric/components/view/ViewProps.cpp +++ b/ReactCommon/fabric/components/view/ViewProps.cpp @@ -7,6 +7,8 @@ #include "ViewProps.h" +#include + #include #include #include @@ -16,9 +18,9 @@ namespace facebook { namespace react { -ViewProps::ViewProps(const YGStyle &yogaStyle) : YogaStylableProps(yogaStyle) {} +ViewProps::ViewProps(YGStyle const &yogaStyle) : YogaStylableProps(yogaStyle) {} -ViewProps::ViewProps(const ViewProps &sourceProps, const RawProps &rawProps) +ViewProps::ViewProps(ViewProps const &sourceProps, RawProps const &rawProps) : Props(sourceProps, rawProps), YogaStylableProps(sourceProps, rawProps), AccessibilityProps(sourceProps, rawProps), @@ -77,7 +79,47 @@ ViewProps::ViewProps(const ViewProps &sourceProps, const RawProps &rawProps) #pragma mark - Convenience Methods -BorderMetrics ViewProps::resolveBorderMetrics(bool isRTL) const { +static BorderRadii ensureNoOverlap(BorderRadii const &radii, Size const &size) { + // "Corner curves must not overlap: When the sum of any two adjacent border + // radii exceeds the size of the border box, UAs must proportionally reduce + // the used values of all border radii until none of them overlap." + // Source: https://www.w3.org/TR/css-backgrounds-3/#corner-overlap + + auto insets = EdgeInsets{ + /* .left = */ radii.topLeft + radii.bottomLeft, + /* .top = */ radii.topLeft + radii.topRight, + /* .right = */ radii.topRight + radii.bottomRight, + /* .bottom = */ radii.bottomLeft + radii.bottomRight, + }; + + auto insetsScale = EdgeInsets{ + /* .left = */ + insets.left > 0 ? std::min((Float)1.0, size.height / insets.left) : 0, + /* .top = */ + insets.top > 0 ? std::min((Float)1.0, size.width / insets.top) : 0, + /* .right = */ + insets.right > 0 ? std::min((Float)1.0, size.height / insets.right) : 0, + /* .bottom = */ + insets.bottom > 0 ? std::min((Float)1.0, size.width / insets.bottom) : 0, + }; + + return BorderRadii{ + /* topLeft = */ + radii.topLeft * std::min(insetsScale.top, insetsScale.left), + /* topRight = */ + radii.topRight * std::min(insetsScale.top, insetsScale.right), + /* bottomLeft = */ + radii.bottomLeft * std::min(insetsScale.bottom, insetsScale.left), + /* bottomRight = */ + radii.bottomRight * std::min(insetsScale.bottom, insetsScale.right), + }; +} + +BorderMetrics ViewProps::resolveBorderMetrics( + LayoutMetrics const &layoutMetrics) const { + auto isRTL = + bool{layoutMetrics.layoutDirection == LayoutDirection::RightToLeft}; + auto borderWidths = CascadedBorderWidths{ /* .left = */ optionalFloatFromYogaValue(yogaStyle.border[YGEdgeLeft]), /* .top = */ optionalFloatFromYogaValue(yogaStyle.border[YGEdgeTop]), @@ -96,7 +138,8 @@ BorderMetrics ViewProps::resolveBorderMetrics(bool isRTL) const { return { /* .borderColors = */ borderColors.resolve(isRTL, {}), /* .borderWidths = */ borderWidths.resolve(isRTL, 0), - /* .borderRadii = */ borderRadii.resolve(isRTL, 0), + /* .borderRadii = */ + ensureNoOverlap(borderRadii.resolve(isRTL, 0), layoutMetrics.frame.size), /* .borderStyles = */ borderStyles.resolve(isRTL, BorderStyle::Solid), }; } diff --git a/ReactCommon/fabric/components/view/ViewProps.h b/ReactCommon/fabric/components/view/ViewProps.h index c026db5b72..4ca4cd7583 100644 --- a/ReactCommon/fabric/components/view/ViewProps.h +++ b/ReactCommon/fabric/components/view/ViewProps.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -20,50 +21,50 @@ namespace react { class ViewProps; -using SharedViewProps = std::shared_ptr; +using SharedViewProps = std::shared_ptr; class ViewProps : public Props, public YogaStylableProps, public AccessibilityProps { public: ViewProps() = default; - ViewProps(const YGStyle &yogaStyle); - ViewProps(const ViewProps &sourceProps, const RawProps &rawProps); + ViewProps(YGStyle const &yogaStyle); + ViewProps(ViewProps const &sourceProps, RawProps const &rawProps); #pragma mark - Props // Color - const Float opacity{1.0}; - const SharedColor foregroundColor{}; - const SharedColor backgroundColor{}; + Float const opacity{1.0}; + SharedColor const foregroundColor{}; + SharedColor const backgroundColor{}; // Borders - const CascadedBorderRadii borderRadii{}; - const CascadedBorderColors borderColors{}; - const CascadedBorderStyles borderStyles{}; + CascadedBorderRadii const borderRadii{}; + CascadedBorderColors const borderColors{}; + CascadedBorderStyles const borderStyles{}; // Shadow - const SharedColor shadowColor{}; - const Size shadowOffset{}; - const Float shadowOpacity{}; - const Float shadowRadius{}; + SharedColor const shadowColor{}; + Size const shadowOffset{}; + Float const shadowOpacity{}; + Float const shadowRadius{}; // Transform - const Transform transform{}; - const bool backfaceVisibility{}; - const bool shouldRasterize{}; - const int zIndex{}; + Transform transform{}; + bool const backfaceVisibility{}; + bool const shouldRasterize{}; + int const zIndex{}; // Events - const PointerEventsMode pointerEvents{}; - const EdgeInsets hitSlop{}; - const bool onLayout{}; + PointerEventsMode const pointerEvents{}; + EdgeInsets const hitSlop{}; + bool const onLayout{}; - const bool collapsable{true}; + bool const collapsable{true}; #pragma mark - Convenience Methods - BorderMetrics resolveBorderMetrics(bool isRTL) const; + BorderMetrics resolveBorderMetrics(LayoutMetrics const &layoutMetrics) const; #pragma mark - DebugStringConvertible