diff --git a/BUCK b/BUCK index 69a9a077041b0b..7780ff8e1f6650 100644 --- a/BUCK +++ b/BUCK @@ -248,6 +248,7 @@ REACT_PUBLIC_HEADERS = { "React/RCTAnimationType.h": RCTVIEWS_PATH + "RCTAnimationType.h", "React/RCTAssert.h": RCTBASE_PATH + "RCTAssert.h", "React/RCTAutoInsetsProtocol.h": RCTVIEWS_PATH + "RCTAutoInsetsProtocol.h", + "React/RCTBorderCurve.h": RCTVIEWS_PATH + "RCTBorderCurve.h", "React/RCTBorderDrawing.h": RCTVIEWS_PATH + "RCTBorderDrawing.h", "React/RCTBorderStyle.h": RCTVIEWS_PATH + "RCTBorderStyle.h", "React/RCTBridge+Private.h": RCTBASE_PATH + "RCTBridge+Private.h", diff --git a/Libraries/Components/View/ReactNativeStyleAttributes.js b/Libraries/Components/View/ReactNativeStyleAttributes.js index 7736e5026db90c..542bfd3a88eedd 100644 --- a/Libraries/Components/View/ReactNativeStyleAttributes.js +++ b/Libraries/Components/View/ReactNativeStyleAttributes.js @@ -98,6 +98,7 @@ const ReactNativeStyleAttributes: {[string]: AnyAttributeType, ...} = { borderBottomRightRadius: true, borderBottomStartRadius: true, borderColor: colorAttributes, + borderCurve: true, borderEndColor: colorAttributes, borderLeftColor: colorAttributes, borderRadius: true, diff --git a/Libraries/NativeComponent/BaseViewConfig.ios.js b/Libraries/NativeComponent/BaseViewConfig.ios.js index d19693deb92953..95cf5dee34f16a 100644 --- a/Libraries/NativeComponent/BaseViewConfig.ios.js +++ b/Libraries/NativeComponent/BaseViewConfig.ios.js @@ -193,6 +193,7 @@ const validAttributesForNonEventProps = { removeClippedSubviews: true, borderRadius: true, borderColor: {process: require('../StyleSheet/processColor')}, + borderCurve: true, borderWidth: true, borderStyle: true, hitSlop: {diff: require('../Utilities/differ/insetsDiffer')}, diff --git a/Libraries/StyleSheet/StyleSheetTypes.js b/Libraries/StyleSheet/StyleSheetTypes.js index a12ed0cafc1af5..4b7aa36cda6c67 100644 --- a/Libraries/StyleSheet/StyleSheetTypes.js +++ b/Libraries/StyleSheet/StyleSheetTypes.js @@ -532,6 +532,7 @@ export type ____ViewStyle_InternalCore = $ReadOnly<{ backfaceVisibility?: 'visible' | 'hidden', backgroundColor?: ____ColorValue_Internal, borderColor?: ____ColorValue_Internal, + borderCurve?: 'circular' | 'continuous', borderBottomColor?: ____ColorValue_Internal, borderEndColor?: ____ColorValue_Internal, borderLeftColor?: ____ColorValue_Internal, diff --git a/React/Base/RCTConvert.h b/React/Base/RCTConvert.h index c4913f60c7ab3d..ad92515fd584d6 100644 --- a/React/Base/RCTConvert.h +++ b/React/Base/RCTConvert.h @@ -9,6 +9,7 @@ #import #import +#import #import #import #import @@ -130,6 +131,7 @@ typedef BOOL css_backface_visibility_t; + (RCTPointerEvents)RCTPointerEvents:(id)json; + (RCTAnimationType)RCTAnimationType:(id)json; + (RCTBorderStyle)RCTBorderStyle:(id)json; ++ (RCTBorderCurve)RCTBorderCurve:(id)json; + (RCTTextDecorationLineType)RCTTextDecorationLineType:(id)json; @end diff --git a/React/Base/RCTConvert.m b/React/Base/RCTConvert.m index c21ef072acd49d..b44a5430db1ab2 100644 --- a/React/Base/RCTConvert.m +++ b/React/Base/RCTConvert.m @@ -345,6 +345,15 @@ + (NSLocale *)NSLocale:(id)json RCTBorderStyleSolid, integerValue) +RCT_ENUM_CONVERTER( + RCTBorderCurve, + (@{ + @"circular" : @(RCTBorderCurveCircular), + @"continuous" : @(RCTBorderCurveContinuous), + }), + RCTBorderCurveCircular, + integerValue) + RCT_ENUM_CONVERTER( RCTTextDecorationLineType, (@{ diff --git a/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm b/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm index 06e7b5bd39ca76..bf691dba2e159f 100644 --- a/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +++ b/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm @@ -516,6 +516,18 @@ static void RCTReleaseRCTBorderColors(RCTBorderColors borderColors) CGColorRelease(borderColors.right); } +static CALayerCornerCurve CornerCurveFromBorderCurve(BorderCurve borderCurve) +{ + // The constants are available only starting from iOS 13 + // CALayerCornerCurve is a typealias on NSString * + switch (borderCurve) { + case BorderCurve::Continuous: + return @"continuous"; // kCACornerCurveContinuous; + case BorderCurve::Circular: + return @"circular"; // kCACornerCurveCircular; + } +} + static RCTBorderStyle RCTBorderStyleFromBorderStyle(BorderStyle borderStyle) { switch (borderStyle) { @@ -580,6 +592,9 @@ - (void)invalidateLayer layer.borderColor = borderColor; CGColorRelease(borderColor); layer.cornerRadius = (CGFloat)borderMetrics.borderRadii.topLeft; + if (@available(iOS 13.0, *)) { + layer.cornerCurve = CornerCurveFromBorderCurve(borderMetrics.borderCurves.topLeft); + } layer.backgroundColor = _backgroundColor.CGColor; } else { if (!_borderLayer) { diff --git a/React/Views/RCTBorderCurve.h b/React/Views/RCTBorderCurve.h new file mode 100644 index 00000000000000..96002659ed7085 --- /dev/null +++ b/React/Views/RCTBorderCurve.h @@ -0,0 +1,13 @@ +/* + * 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. + */ + +#import + +typedef NS_ENUM(NSInteger, RCTBorderCurve) { + RCTBorderCurveContinuous = 0, + RCTBorderCurveCircular, +}; diff --git a/React/Views/RCTView.h b/React/Views/RCTView.h index fe27daae1fb0af..b3df8e80a68b38 100644 --- a/React/Views/RCTView.h +++ b/React/Views/RCTView.h @@ -7,6 +7,7 @@ #import +#import #import #import #import @@ -93,6 +94,11 @@ extern const UIAccessibilityTraits SwitchAccessibilityTrait; @property (nonatomic, assign) CGFloat borderEndWidth; @property (nonatomic, assign) CGFloat borderWidth; +/** + * Border curve. + */ +@property (nonatomic, assign) RCTBorderCurve borderCurve; + /** * Border styles. */ diff --git a/React/Views/RCTView.m b/React/Views/RCTView.m index 03dcc94d9310dc..7fb45443248a77 100644 --- a/React/Views/RCTView.m +++ b/React/Views/RCTView.m @@ -10,6 +10,7 @@ #import #import "RCTAutoInsetsProtocol.h" +#import "RCTBorderCurve.h" #import "RCTBorderDrawing.h" #import "RCTI18nUtil.h" #import "RCTLog.h" @@ -126,6 +127,7 @@ - (instancetype)initWithFrame:(CGRect)frame _borderBottomRightRadius = -1; _borderBottomStartRadius = -1; _borderBottomEndRadius = -1; + _borderCurve = RCTBorderCurveCircular; _borderStyle = RCTBorderStyleSolid; _hitTestEdgeInsets = UIEdgeInsetsZero; @@ -945,6 +947,20 @@ -(void)setBorder##side##Radius : (CGFloat)radius \ setBorderRadius(TopEnd) setBorderRadius(BottomLeft) setBorderRadius(BottomRight) setBorderRadius(BottomStart) setBorderRadius(BottomEnd) +#pragma mark - Border Curve + +#define setBorderCurve(side) \ + -(void)setBorder##side##Curve : (RCTBorderCurve)curve \ + { \ + if (_border##side##Curve == curve) { \ + return; \ + } \ + _border##side##Curve = curve; \ + [self.layer setNeedsDisplay]; \ + } + + setBorderCurve() + #pragma mark - Border Style #define setBorderStyle(side) \ @@ -957,6 +973,6 @@ -(void)setBorder##side##Style : (RCTBorderStyle)style \ [self.layer setNeedsDisplay]; \ } - setBorderStyle() + setBorderStyle() - @end + @end diff --git a/React/Views/RCTViewManager.m b/React/Views/RCTViewManager.m index f10eea73bff7ed..f1bb13eb7cfa39 100644 --- a/React/Views/RCTViewManager.m +++ b/React/Views/RCTViewManager.m @@ -8,6 +8,7 @@ #import "RCTViewManager.h" #import "RCTAssert.h" +#import "RCTBorderCurve.h" #import "RCTBorderStyle.h" #import "RCTBridge.h" #import "RCTConvert+Transform.h" @@ -264,6 +265,19 @@ - (RCTShadowView *)shadowView view.removeClippedSubviews = json ? [RCTConvert BOOL:json] : defaultView.removeClippedSubviews; } } +RCT_CUSTOM_VIEW_PROPERTY(borderCurve, RCTBorderCurve, RCTView) +{ + if (@available(iOS 13.0, *)) { + switch ([RCTConvert RCTBorderCurve:json]) { + case RCTBorderCurveContinuous: + view.layer.cornerCurve = kCACornerCurveContinuous; + break; + case RCTBorderCurveCircular: + view.layer.cornerCurve = kCACornerCurveCircular; + break; + } + } +} RCT_CUSTOM_VIEW_PROPERTY(borderRadius, CGFloat, RCTView) { if ([view respondsToSelector:@selector(setBorderRadius:)]) { diff --git a/ReactCommon/react/renderer/components/view/ViewProps.cpp b/ReactCommon/react/renderer/components/view/ViewProps.cpp index 5be4a80699e40d..f04753da8935ee 100644 --- a/ReactCommon/react/renderer/components/view/ViewProps.cpp +++ b/ReactCommon/react/renderer/components/view/ViewProps.cpp @@ -67,6 +67,15 @@ ViewProps::ViewProps( "Color", sourceProps.borderColors, {})), + borderCurves( + Props::enablePropIteratorSetter ? sourceProps.borderCurves + : convertRawProp( + context, + rawProps, + "border", + "Curve", + sourceProps.borderCurves, + {})), borderStyles( Props::enablePropIteratorSetter ? sourceProps.borderStyles : convertRawProp( @@ -412,6 +421,7 @@ BorderMetrics ViewProps::resolveBorderMetrics( /* .borderWidths = */ borderWidths.resolve(isRTL, 0), /* .borderRadii = */ ensureNoOverlap(borderRadii.resolve(isRTL, 0), layoutMetrics.frame.size), + /* .borderCurves = */ borderCurves.resolve(isRTL, BorderCurve::Circular), /* .borderStyles = */ borderStyles.resolve(isRTL, BorderStyle::Solid), }; } diff --git a/ReactCommon/react/renderer/components/view/ViewProps.h b/ReactCommon/react/renderer/components/view/ViewProps.h index 21a425dc73fe7e..cc09768ad73655 100644 --- a/ReactCommon/react/renderer/components/view/ViewProps.h +++ b/ReactCommon/react/renderer/components/view/ViewProps.h @@ -51,6 +51,7 @@ class ViewProps : public YogaStylableProps, public AccessibilityProps { // Borders CascadedBorderRadii borderRadii{}; CascadedBorderColors borderColors{}; + CascadedBorderCurves borderCurves{}; CascadedBorderStyles borderStyles{}; // Shadow diff --git a/ReactCommon/react/renderer/components/view/conversions.h b/ReactCommon/react/renderer/components/view/conversions.h index f70c9f9b9b00cd..8a8a30295e51e2 100644 --- a/ReactCommon/react/renderer/components/view/conversions.h +++ b/ReactCommon/react/renderer/components/view/conversions.h @@ -563,6 +563,24 @@ inline void fromRawValue( react_native_assert(false); } +inline void fromRawValue( + const PropsParserContext &context, + const RawValue &value, + BorderCurve &result) { + react_native_assert(value.hasType()); + auto stringValue = (std::string)value; + if (stringValue == "circular") { + result = BorderCurve::Circular; + return; + } + if (stringValue == "continuous") { + result = BorderCurve::Continuous; + return; + } + LOG(FATAL) << "Could not parse BorderCurve:" << stringValue; + react_native_assert(false); +} + inline void fromRawValue( const PropsParserContext &context, const RawValue &value, diff --git a/ReactCommon/react/renderer/components/view/primitives.h b/ReactCommon/react/renderer/components/view/primitives.h index 16b02c2dd8c8a7..10fe3bf19c5229 100644 --- a/ReactCommon/react/renderer/components/view/primitives.h +++ b/ReactCommon/react/renderer/components/view/primitives.h @@ -77,6 +77,8 @@ inline static bool operator!=(ViewEvents const &lhs, ViewEvents const &rhs) { enum class BackfaceVisibility { Auto, Visible, Hidden }; +enum class BorderCurve { Circular, Continuous }; + enum class BorderStyle { Solid, Dotted, Dashed }; template @@ -202,11 +204,13 @@ struct CascadedRectangleCorners { }; using BorderWidths = RectangleEdges; +using BorderCurves = RectangleCorners; using BorderStyles = RectangleEdges; using BorderColors = RectangleEdges; using BorderRadii = RectangleCorners; using CascadedBorderWidths = CascadedRectangleEdges; +using CascadedBorderCurves = CascadedRectangleCorners; using CascadedBorderStyles = CascadedRectangleEdges; using CascadedBorderColors = CascadedRectangleEdges; using CascadedBorderRadii = CascadedRectangleCorners; @@ -215,6 +219,7 @@ struct BorderMetrics { BorderColors borderColors{}; BorderWidths borderWidths{}; BorderRadii borderRadii{}; + BorderCurves borderCurves{}; BorderStyles borderStyles{}; bool operator==(const BorderMetrics &rhs) const { @@ -222,11 +227,13 @@ struct BorderMetrics { this->borderColors, this->borderWidths, this->borderRadii, + this->borderCurves, this->borderStyles) == std::tie( rhs.borderColors, rhs.borderWidths, rhs.borderRadii, + rhs.borderCurves, rhs.borderStyles); } diff --git a/packages/rn-tester/Podfile.lock b/packages/rn-tester/Podfile.lock index 824da7bca361aa..96fed7c9a97f6a 100644 --- a/packages/rn-tester/Podfile.lock +++ b/packages/rn-tester/Podfile.lock @@ -753,7 +753,7 @@ DEPENDENCIES: - RCTRequired (from `../../Libraries/RCTRequired`) - RCTTypeSafety (from `../../Libraries/TypeSafety`) - React (from `../../`) - - React-bridging (from `../../ReactCommon/react/bridging`) + - React-bridging (from `../../ReactCommon`) - React-callinvoker (from `../../ReactCommon/callinvoker`) - React-Codegen (from `build/generated/ios`) - React-Core (from `../../`) @@ -826,7 +826,7 @@ EXTERNAL SOURCES: React: :path: "../../" React-bridging: - :path: "../../ReactCommon/react/bridging" + :path: "../../ReactCommon" React-callinvoker: :path: "../../ReactCommon/callinvoker" React-Codegen: @@ -905,11 +905,11 @@ SPEC CHECKSUMS: glog: 04b94705f318337d7ead9e6d17c019bd9b1f6b1b libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913 OpenSSL-Universal: ebc357f1e6bc71fa463ccb2fe676756aff50e88c - RCT-Folly: 9638863070ed4e7b2be5e91385745a0ad741e9c1 + RCT-Folly: 0080d0a6ebf2577475bda044aa59e2ca1f909cda RCTRequired: 1c8808cf84569265784a6c33984bbb506ada8c6e RCTTypeSafety: b6dcb5036a808864ee8cad66ca15f263c24661cc React: 8d809d414723bb5763093ddec7658066a21ccabc - React-bridging: 3ba3efbd3a2d7d99aad5658b8e48b1c134c16ecf + React-bridging: cc10a051eff1f03306a1d7659593d8aac3242bc3 React-callinvoker: 5f16202ad4e45f0607b1fae0f6955a8f7c87eef1 React-Codegen: 5adf19af97eb37a7d441c040521191e446255086 React-Core: 0cfb25c65d4dcb856b1807fe44a1ebe5e7ec9749 @@ -936,12 +936,12 @@ SPEC CHECKSUMS: React-RCTVibration: 0386f50996a153b3f39cecbe7d139763ac9a9fdf React-rncore: 6daa27c74047a9e13ce3412b99660274a5780603 React-runtimeexecutor: 97dca9247f4d3cfe0733384b189c6930fbd402b7 - ReactCommon: a34f02c7251e6725e744167b9381d5dd9d016591 + ReactCommon: 6cef8ed13ee2a9d7d4cf9660dbe6dd2ea6ba7104 ScreenshotManager: 71d047abd38a77310985b87f8136b620c5c61e88 SocketRocket: fccef3f9c5cedea1353a9ef6ada904fde10d6608 Yoga: 1b1a12ff3d86a10565ea7cbe057d42f5e5fb2a07 YogaKit: f782866e155069a2cca2517aafea43200b01fd5a -PODFILE CHECKSUM: e067e04e7697dc3cd3d3fbd7556f77c6eccf8075 +PODFILE CHECKSUM: 54d9bd86f3c8151531bd4da1d3ba2e2e1f9a6ca9 COCOAPODS: 1.11.3 diff --git a/packages/rn-tester/js/examples/View/ViewExample.js b/packages/rn-tester/js/examples/View/ViewExample.js index 2ea127b9df1992..fd15cf4c1cebb3 100644 --- a/packages/rn-tester/js/examples/View/ViewExample.js +++ b/packages/rn-tester/js/examples/View/ViewExample.js @@ -17,6 +17,7 @@ const { Text, TouchableWithoutFeedback, View, + Platform, } = require('react-native'); class ViewBorderStyleExample extends React.Component< @@ -360,12 +361,29 @@ exports.examples = [ title: 'Border Radius', render(): React.Node { return ( - - - Too much use of `borderRadius` (especially large radii) on anything - which is scrolling may result in dropped frames. Use sparingly. - - + <> + + + Too much use of `borderRadius` (especially large radii) on + anything which is scrolling may result in dropped frames. Use + sparingly. + + + {Platform.OS === 'ios' && ( + + + View with continuous border curve + + + )} + ); }, },