From cf65593294ad8c1997343a1f8e632dbd37dc08aa Mon Sep 17 00:00:00 2001 From: Jorge Cabiedes Acosta Date: Tue, 13 Aug 2024 14:28:36 -0700 Subject: [PATCH] Add support for assymetrical border radii when using % Summary: as title Changelog: [Internal] Differential Revision: D61148739 --- .../View/RCTViewComponentView.mm | 16 ++-- .../React/Fabric/Utils/RCTBoxShadow.mm | 12 ++- .../React/Views/RCTBorderDrawing.h | 12 ++- .../React/Views/RCTBorderDrawing.m | 34 +++++--- packages/react-native/React/Views/RCTView.m | 4 +- .../components/view/BaseViewProps.cpp | 83 ++++++++++++------- .../renderer/components/view/primitives.h | 11 ++- .../rn-tester/js/examples/View/ViewExample.js | 19 +++++ 8 files changed, 130 insertions(+), 61 deletions(-) diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm index eda039efbd046a..a5556ea73f0de6 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm @@ -604,10 +604,14 @@ - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event static RCTCornerRadii RCTCornerRadiiFromBorderRadii(BorderRadii borderRadii) { return RCTCornerRadii{ - .topLeft = (CGFloat)borderRadii.topLeft, - .topRight = (CGFloat)borderRadii.topRight, - .bottomLeft = (CGFloat)borderRadii.bottomLeft, - .bottomRight = (CGFloat)borderRadii.bottomRight}; + .topLeftHorizontal = (CGFloat)borderRadii.topLeft.horizontal, + .topLeftVertical = (CGFloat)borderRadii.topLeft.vertical, + .topRightHorizontal = (CGFloat)borderRadii.topRight.horizontal, + .topRightVertical = (CGFloat)borderRadii.topRight.vertical, + .bottomLeftHorizontal = (CGFloat)borderRadii.bottomLeft.horizontal, + .bottomLeftVertical = (CGFloat)borderRadii.bottomLeft.vertical, + .bottomRightHorizontal = (CGFloat)borderRadii.bottomRight.horizontal, + .bottomRightVertical = (CGFloat)borderRadii.bottomRight.vertical}; } static RCTBorderColors RCTCreateRCTBorderColorsFromBorderColors(BorderColors borderColors) @@ -728,7 +732,7 @@ - (void)invalidateLayer CGColorRef borderColor = RCTCreateCGColorRefFromSharedColor(borderMetrics.borderColors.left); layer.borderColor = borderColor; CGColorRelease(borderColor); - layer.cornerRadius = (CGFloat)borderMetrics.borderRadii.topLeft; + layer.cornerRadius = (CGFloat)borderMetrics.borderRadii.topLeft.horizontal; layer.cornerCurve = CornerCurveFromBorderCurve(borderMetrics.borderCurves.topLeft); @@ -791,7 +795,7 @@ - (void)invalidateLayer if (self.clipsToBounds) { if (borderMetrics.borderRadii.isUniform()) { // In this case we can simply use `cornerRadius` exclusively. - cornerRadius = borderMetrics.borderRadii.topLeft; + cornerRadius = borderMetrics.borderRadii.topLeft.horizontal; } else { // In this case we have to generate masking layer manually. CGPathRef path = RCTPathCreateWithRoundedRect( diff --git a/packages/react-native/React/Fabric/Utils/RCTBoxShadow.mm b/packages/react-native/React/Fabric/Utils/RCTBoxShadow.mm index a3516ee49b3d5b..2e3ec6911b287d 100644 --- a/packages/react-native/React/Fabric/Utils/RCTBoxShadow.mm +++ b/packages/react-native/React/Fabric/Utils/RCTBoxShadow.mm @@ -30,10 +30,14 @@ static CGFloat adjustedCornerRadius(CGFloat cornerRadius, CGFloat spreadDistance static RCTCornerRadii cornerRadiiForBoxShadow(RCTCornerRadii cornerRadii, CGFloat spreadDistance) { return { - adjustedCornerRadius(cornerRadii.topLeft, spreadDistance), - adjustedCornerRadius(cornerRadii.topRight, spreadDistance), - adjustedCornerRadius(cornerRadii.bottomLeft, spreadDistance), - adjustedCornerRadius(cornerRadii.bottomRight, spreadDistance)}; + adjustedCornerRadius(cornerRadii.topLeftHorizontal, spreadDistance), + adjustedCornerRadius(cornerRadii.topLeftVertical, spreadDistance), + adjustedCornerRadius(cornerRadii.topRightHorizontal, spreadDistance), + adjustedCornerRadius(cornerRadii.topRightVertical, spreadDistance), + adjustedCornerRadius(cornerRadii.bottomLeftHorizontal, spreadDistance), + adjustedCornerRadius(cornerRadii.bottomLeftVertical, spreadDistance), + adjustedCornerRadius(cornerRadii.bottomRightHorizontal, spreadDistance), + adjustedCornerRadius(cornerRadii.bottomRightVertical, spreadDistance)}; } // Returns the smallest CGRect that will contain all shadows and the layer itself. diff --git a/packages/react-native/React/Views/RCTBorderDrawing.h b/packages/react-native/React/Views/RCTBorderDrawing.h index 4c92868f7c607e..36b80ae8236f12 100644 --- a/packages/react-native/React/Views/RCTBorderDrawing.h +++ b/packages/react-native/React/Views/RCTBorderDrawing.h @@ -11,10 +11,14 @@ #import typedef struct { - CGFloat topLeft; - CGFloat topRight; - CGFloat bottomLeft; - CGFloat bottomRight; + CGFloat topLeftHorizontal; + CGFloat topLeftVertical; + CGFloat topRightHorizontal; + CGFloat topRightVertical; + CGFloat bottomLeftHorizontal; + CGFloat bottomLeftVertical; + CGFloat bottomRightHorizontal; + CGFloat bottomRightVertical; } RCTCornerRadii; typedef struct { diff --git a/packages/react-native/React/Views/RCTBorderDrawing.m b/packages/react-native/React/Views/RCTBorderDrawing.m index fa2f5e50cb59dc..244337bbcf8581 100644 --- a/packages/react-native/React/Views/RCTBorderDrawing.m +++ b/packages/react-native/React/Views/RCTBorderDrawing.m @@ -19,9 +19,12 @@ BOOL RCTBorderInsetsAreEqual(UIEdgeInsets borderInsets) BOOL RCTCornerRadiiAreEqual(RCTCornerRadii cornerRadii) { - return ABS(cornerRadii.topLeft - cornerRadii.topRight) < RCTViewBorderThreshold && - ABS(cornerRadii.topLeft - cornerRadii.bottomLeft) < RCTViewBorderThreshold && - ABS(cornerRadii.topLeft - cornerRadii.bottomRight) < RCTViewBorderThreshold; + return ABS(cornerRadii.topLeftVertical - cornerRadii.topRightVertical) < RCTViewBorderThreshold && + ABS(cornerRadii.topLeftHorizontal - cornerRadii.topRightHorizontal) < RCTViewBorderThreshold && + ABS(cornerRadii.topLeftVertical - cornerRadii.bottomLeftVertical) < RCTViewBorderThreshold && + ABS(cornerRadii.topLeftHorizontal - cornerRadii.bottomLeftHorizontal) < RCTViewBorderThreshold && + ABS(cornerRadii.topLeftVertical - cornerRadii.bottomRightVertical) < RCTViewBorderThreshold && + ABS(cornerRadii.topLeftHorizontal - cornerRadii.bottomRightHorizontal) < RCTViewBorderThreshold; } BOOL RCTBorderColorsAreEqual(RCTBorderColors borderColors) @@ -35,20 +38,20 @@ RCTCornerInsets RCTGetCornerInsets(RCTCornerRadii cornerRadii, UIEdgeInsets edge { return (RCTCornerInsets){ { - MAX(0, cornerRadii.topLeft - edgeInsets.left), - MAX(0, cornerRadii.topLeft - edgeInsets.top), + MAX(0, cornerRadii.topLeftHorizontal - edgeInsets.left), + MAX(0, cornerRadii.topLeftVertical - edgeInsets.top), }, { - MAX(0, cornerRadii.topRight - edgeInsets.right), - MAX(0, cornerRadii.topRight - edgeInsets.top), + MAX(0, cornerRadii.topRightHorizontal - edgeInsets.right), + MAX(0, cornerRadii.topRightVertical - edgeInsets.top), }, { - MAX(0, cornerRadii.bottomLeft - edgeInsets.left), - MAX(0, cornerRadii.bottomLeft - edgeInsets.bottom), + MAX(0, cornerRadii.bottomLeftHorizontal - edgeInsets.left), + MAX(0, cornerRadii.bottomLeftVertical - edgeInsets.bottom), }, { - MAX(0, cornerRadii.bottomRight - edgeInsets.right), - MAX(0, cornerRadii.bottomRight - edgeInsets.bottom), + MAX(0, cornerRadii.bottomRightHorizontal - edgeInsets.right), + MAX(0, cornerRadii.bottomRightVertical - edgeInsets.bottom), }}; } @@ -159,8 +162,13 @@ CGPathRef RCTPathCreateWithRoundedRect(CGRect bounds, RCTCornerInsets cornerInse NS_INLINE BOOL RCTCornerRadiiAreAboveThreshold(RCTCornerRadii cornerRadii) { return ( - cornerRadii.topLeft > RCTViewBorderThreshold || cornerRadii.topRight > RCTViewBorderThreshold || - cornerRadii.bottomLeft > RCTViewBorderThreshold || cornerRadii.bottomRight > RCTViewBorderThreshold); + cornerRadii.topLeftHorizontal > RCTViewBorderThreshold || cornerRadii.topLeftVertical > RCTViewBorderThreshold || + cornerRadii.topRightHorizontal > RCTViewBorderThreshold || + cornerRadii.topRightVertical > RCTViewBorderThreshold || + cornerRadii.bottomLeftHorizontal > RCTViewBorderThreshold || + cornerRadii.bottomLeftVertical > RCTViewBorderThreshold || + cornerRadii.bottomRightHorizontal > RCTViewBorderThreshold || + cornerRadii.bottomRightVertical > RCTViewBorderThreshold); } static CGPathRef RCTPathCreateOuterOutline(BOOL drawToEdge, CGRect rect, RCTCornerRadii cornerRadii) diff --git a/packages/react-native/React/Views/RCTView.m b/packages/react-native/React/Views/RCTView.m index 7b6f5e7a6d43e8..534002769e4595 100644 --- a/packages/react-native/React/Views/RCTView.m +++ b/packages/react-native/React/Views/RCTView.m @@ -821,7 +821,7 @@ - (void)displayLayer:(CALayer *)layer backgroundColor = [_backgroundColor resolvedColorWithTraitCollection:self.traitCollection].CGColor; if (useIOSBorderRendering) { - layer.cornerRadius = cornerRadii.topLeft; + layer.cornerRadius = cornerRadii.topLeftHorizontal; layer.borderColor = borderColors.left; layer.borderWidth = borderInsets.left; layer.backgroundColor = backgroundColor; @@ -929,7 +929,7 @@ - (void)updateClippingForLayer:(CALayer *)layer if (self.clipsToBounds) { const RCTCornerRadii cornerRadii = [self cornerRadii]; if (RCTCornerRadiiAreEqual(cornerRadii)) { - cornerRadius = cornerRadii.topLeft; + cornerRadius = cornerRadii.topLeftHorizontal; } else { CAShapeLayer *shapeLayer = [CAShapeLayer layer]; diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.cpp b/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.cpp index f23240ec914b01..252ada79124c06 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.cpp @@ -379,10 +379,10 @@ static BorderRadii ensureNoOverlap(const BorderRadii& radii, const Size& size) { // 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, + /* .left = */ radii.topLeft.horizontal + radii.bottomLeft.horizontal, + /* .top = */ radii.topLeft.vertical + radii.topRight.vertical, + /* .right = */ radii.topRight.horizontal + radii.bottomRight.horizontal, + /* .bottom = */ radii.bottomLeft.vertical + radii.bottomRight.vertical, }; auto insetsScale = EdgeInsets{ @@ -398,17 +398,33 @@ static BorderRadii ensureNoOverlap(const BorderRadii& radii, const Size& size) { return BorderRadii{ /* topLeft = */ - static_cast( - radii.topLeft * std::min(insetsScale.top, insetsScale.left)), + {static_cast( + radii.topLeft.horizontal * + std::min(insetsScale.top, insetsScale.left)), + static_cast( + radii.topLeft.vertical * + std::min(insetsScale.top, insetsScale.left))}, /* topRight = */ - static_cast( - radii.topRight * std::min(insetsScale.top, insetsScale.right)), + {static_cast( + radii.topRight.horizontal * + std::min(insetsScale.top, insetsScale.right)), + static_cast( + radii.topRight.vertical * + std::min(insetsScale.top, insetsScale.right))}, /* bottomLeft = */ - static_cast( - radii.bottomLeft * std::min(insetsScale.bottom, insetsScale.left)), + {static_cast( + radii.bottomLeft.horizontal * + std::min(insetsScale.bottom, insetsScale.left)), + static_cast( + radii.bottomLeft.vertical * + std::min(insetsScale.bottom, insetsScale.left))}, /* bottomRight = */ - static_cast( - radii.bottomRight * std::min(insetsScale.bottom, insetsScale.right)), + {static_cast( + radii.bottomRight.horizontal * + std::min(insetsScale.bottom, insetsScale.right)), + static_cast( + radii.bottomRight.vertical * + std::min(insetsScale.bottom, insetsScale.right))}, }; } @@ -417,28 +433,33 @@ static BorderRadii radiiPercentToPoint( const Size& size) { return BorderRadii{ /* topLeft = */ - (radii.topLeft.unit == UnitType::Percent) - ? static_cast( - (radii.topLeft.value / 100) * std::max(size.width, size.height)) - : static_cast(radii.topLeft.value), + {(radii.topLeft.unit == UnitType::Percent) + ? static_cast((radii.topLeft.value / 100) * size.width) + : static_cast(radii.topLeft.value), + (radii.topLeft.unit == UnitType::Percent) + ? static_cast((radii.topLeft.value / 100) * size.height) + : static_cast(radii.topLeft.value)}, /* topRight = */ - (radii.topRight.unit == UnitType::Percent) - ? static_cast( - (radii.topRight.value / 100) * - std::max(size.width, size.height)) - : static_cast(radii.topRight.value), + {(radii.topRight.unit == UnitType::Percent) + ? static_cast((radii.topRight.value / 100) * size.width) + : static_cast(radii.topRight.value), + (radii.topRight.unit == UnitType::Percent) + ? static_cast((radii.topRight.value / 100) * size.height) + : static_cast(radii.topRight.value)}, /* bottomLeft = */ - (radii.bottomLeft.unit == UnitType::Percent) - ? static_cast( - (radii.bottomLeft.value / 100) * - std::max(size.width, size.height)) - : static_cast(radii.bottomLeft.value), + {(radii.bottomLeft.unit == UnitType::Percent) + ? static_cast((radii.bottomLeft.value / 100) * size.width) + : static_cast(radii.bottomLeft.value), + (radii.bottomLeft.unit == UnitType::Percent) + ? static_cast((radii.bottomLeft.value / 100) * size.height) + : static_cast(radii.bottomLeft.value)}, /* bottomRight = */ - (radii.bottomRight.unit == UnitType::Percent) - ? static_cast( - (radii.bottomRight.value / 100) * - std::max(size.width, size.height)) - : static_cast(radii.bottomRight.value), + {(radii.bottomRight.unit == UnitType::Percent) + ? static_cast((radii.bottomRight.value / 100) * size.width) + : static_cast(radii.bottomRight.value), + (radii.bottomRight.unit == UnitType::Percent) + ? static_cast((radii.bottomRight.value / 100) * size.height) + : static_cast(radii.bottomRight.value)}, }; } 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 4e38d898cff77a..963d75efde60e0 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/primitives.h +++ b/packages/react-native/ReactCommon/react/renderer/components/view/primitives.h @@ -92,6 +92,15 @@ enum class BorderCurve : uint8_t { Circular, Continuous }; enum class BorderStyle : uint8_t { Solid, Dotted, Dashed }; +struct CornerRadii { + float vertical; + float horizontal; + + bool operator==(const CornerRadii& other) const { + return vertical == other.vertical && horizontal == other.horizontal; + } +}; + enum class Cursor : uint8_t { Auto, Alias, @@ -289,7 +298,7 @@ using BorderWidths = RectangleEdges; using BorderCurves = RectangleCorners; using BorderStyles = RectangleEdges; using BorderColors = RectangleEdges; -using BorderRadii = RectangleCorners; +using BorderRadii = RectangleCorners; using CascadedBorderWidths = CascadedRectangleEdges; using CascadedBorderCurves = CascadedRectangleCorners; diff --git a/packages/rn-tester/js/examples/View/ViewExample.js b/packages/rn-tester/js/examples/View/ViewExample.js index 949e25b44a7868..5cda36f53f36a6 100644 --- a/packages/rn-tester/js/examples/View/ViewExample.js +++ b/packages/rn-tester/js/examples/View/ViewExample.js @@ -733,6 +733,25 @@ export default ({ borderBottomRightRadius: '40%', }} /> + + ); },