diff --git a/Libraries/Components/View/ReactNativeStyleAttributes.js b/Libraries/Components/View/ReactNativeStyleAttributes.js index 7736e5026db9..542bfd3a88ee 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 d19693deb929..95cf5dee34f1 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 a12ed0cafc1a..4b7aa36cda6c 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 c4913f60c7ab..2f2af8ba6aef 100644 --- a/React/Base/RCTConvert.h +++ b/React/Base/RCTConvert.h @@ -10,6 +10,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 c21ef072acd4..98c14225daa6 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 06e7b5bd39ca..ce636ddf6645 100644 --- a/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +++ b/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm @@ -516,6 +516,16 @@ static void RCTReleaseRCTBorderColors(RCTBorderColors borderColors) CGColorRelease(borderColors.right); } +static CALayerCornerCurve CornerCurveFromBorderCurve(BorderCurve borderCurve) +{ + switch (borderCurve) { + case BorderCurve::Continuous: + return kCACornerCurveContinuous; + case BorderCurve::Circular: + return kCACornerCurveCircular; + } +} + static RCTBorderStyle RCTBorderStyleFromBorderStyle(BorderStyle borderStyle) { switch (borderStyle) { @@ -580,6 +590,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 000000000000..b67637e31f50 --- /dev/null +++ b/React/Views/RCTBorderCurve.h @@ -0,0 +1,13 @@ +/* + * Copyright (c) Facebook, Inc. and its 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 fe27daae1fb0..b3df8e80a68b 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 03dcc94d9310..76f11b629854 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) \ diff --git a/React/Views/RCTViewManager.m b/React/Views/RCTViewManager.m index f10eea73bff7..f1bb13eb7cfa 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 5be4a80699e4..f04753da8935 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 21a425dc73fe..cc09768ad736 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 f70c9f9b9b00..8a8a30295e51 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 16b02c2dd8c8..10fe3bf19c52 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/js/examples/View/ViewExample.js b/packages/rn-tester/js/examples/View/ViewExample.js index 2ea127b9df19..fd15cf4c1ceb 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 + + + )} + ); }, },