Skip to content

Added border curve style prop ("Squircle" effect - iOS only) #33783

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Libraries/Components/View/ReactNativeStyleAttributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ const ReactNativeStyleAttributes: {[string]: AnyAttributeType, ...} = {
borderBottomRightRadius: true,
borderBottomStartRadius: true,
borderColor: colorAttributes,
borderCurve: true,
borderEndColor: colorAttributes,
borderLeftColor: colorAttributes,
borderRadius: true,
Expand Down
1 change: 1 addition & 0 deletions Libraries/NativeComponent/BaseViewConfig.ios.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')},
Expand Down
1 change: 1 addition & 0 deletions Libraries/StyleSheet/StyleSheetTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions React/Base/RCTConvert.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

#import <React/RCTAnimationType.h>
#import <React/RCTBorderStyle.h>
#import <React/RCTBorderCurve.h>
#import <React/RCTDefines.h>
#import <React/RCTLog.h>
#import <React/RCTPointerEvents.h>
Expand Down Expand Up @@ -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
Expand Down
9 changes: 9 additions & 0 deletions React/Base/RCTConvert.m
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,15 @@ + (NSLocale *)NSLocale:(id)json
RCTBorderStyleSolid,
integerValue)

RCT_ENUM_CONVERTER(
RCTBorderCurve,
(@{
@"circular" : @(RCTBorderCurveCircular),
@"continuous" : @(RCTBorderCurveContinuous),
}),
RCTBorderCurveCircular,
integerValue)

RCT_ENUM_CONVERTER(
RCTTextDecorationLineType,
(@{
Expand Down
13 changes: 13 additions & 0 deletions React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down
13 changes: 13 additions & 0 deletions React/Views/RCTBorderCurve.h
Original file line number Diff line number Diff line change
@@ -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 <Foundation/Foundation.h>

typedef NS_ENUM(NSInteger, RCTBorderCurve) {
RCTBorderCurveContinuous = 0,
RCTBorderCurveCircular,
};
6 changes: 6 additions & 0 deletions React/Views/RCTView.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#import <UIKit/UIKit.h>

#import <React/RCTBorderCurve.h>
#import <React/RCTBorderStyle.h>
#import <React/RCTComponent.h>
#import <React/RCTPointerEvents.h>
Expand Down Expand Up @@ -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.
*/
Expand Down
16 changes: 16 additions & 0 deletions React/Views/RCTView.m
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#import <React/RCTMockDef.h>

#import "RCTAutoInsetsProtocol.h"
#import "RCTBorderCurve.h"
#import "RCTBorderDrawing.h"
#import "RCTI18nUtil.h"
#import "RCTLog.h"
Expand Down Expand Up @@ -126,6 +127,7 @@ - (instancetype)initWithFrame:(CGRect)frame
_borderBottomRightRadius = -1;
_borderBottomStartRadius = -1;
_borderBottomEndRadius = -1;
_borderCurve = RCTBorderCurveCircular;
_borderStyle = RCTBorderStyleSolid;
_hitTestEdgeInsets = UIEdgeInsetsZero;

Expand Down Expand Up @@ -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) \
Expand Down
14 changes: 14 additions & 0 deletions React/Views/RCTViewManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#import "RCTViewManager.h"

#import "RCTAssert.h"
#import "RCTBorderCurve.h"
#import "RCTBorderStyle.h"
#import "RCTBridge.h"
#import "RCTConvert+Transform.h"
Expand Down Expand Up @@ -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:)]) {
Expand Down
10 changes: 10 additions & 0 deletions ReactCommon/react/renderer/components/view/ViewProps.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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),
};
}
Expand Down
1 change: 1 addition & 0 deletions ReactCommon/react/renderer/components/view/ViewProps.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ class ViewProps : public YogaStylableProps, public AccessibilityProps {
// Borders
CascadedBorderRadii borderRadii{};
CascadedBorderColors borderColors{};
CascadedBorderCurves borderCurves{};
CascadedBorderStyles borderStyles{};

// Shadow
Expand Down
18 changes: 18 additions & 0 deletions ReactCommon/react/renderer/components/view/conversions.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::string>());
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,
Expand Down
7 changes: 7 additions & 0 deletions ReactCommon/react/renderer/components/view/primitives.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 <typename T>
Expand Down Expand Up @@ -202,11 +204,13 @@ struct CascadedRectangleCorners {
};

using BorderWidths = RectangleEdges<Float>;
using BorderCurves = RectangleCorners<BorderCurve>;
using BorderStyles = RectangleEdges<BorderStyle>;
using BorderColors = RectangleEdges<SharedColor>;
using BorderRadii = RectangleCorners<Float>;

using CascadedBorderWidths = CascadedRectangleEdges<Float>;
using CascadedBorderCurves = CascadedRectangleCorners<BorderCurve>;
using CascadedBorderStyles = CascadedRectangleEdges<BorderStyle>;
using CascadedBorderColors = CascadedRectangleEdges<SharedColor>;
using CascadedBorderRadii = CascadedRectangleCorners<Float>;
Expand All @@ -215,18 +219,21 @@ struct BorderMetrics {
BorderColors borderColors{};
BorderWidths borderWidths{};
BorderRadii borderRadii{};
BorderCurves borderCurves{};
BorderStyles borderStyles{};

bool operator==(const BorderMetrics &rhs) const {
return std::tie(
this->borderColors,
this->borderWidths,
this->borderRadii,
this->borderCurves,
this->borderStyles) ==
std::tie(
rhs.borderColors,
rhs.borderWidths,
rhs.borderRadii,
rhs.borderCurves,
rhs.borderStyles);
}

Expand Down
30 changes: 24 additions & 6 deletions packages/rn-tester/js/examples/View/ViewExample.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const {
Text,
TouchableWithoutFeedback,
View,
Platform,
} = require('react-native');

class ViewBorderStyleExample extends React.Component<
Expand Down Expand Up @@ -360,12 +361,29 @@ exports.examples = [
title: 'Border Radius',
render(): React.Node {
return (
<View style={{borderWidth: 0.5, borderRadius: 5, padding: 5}}>
<Text style={{fontSize: 11}}>
Too much use of `borderRadius` (especially large radii) on anything
which is scrolling may result in dropped frames. Use sparingly.
</Text>
</View>
<>
<View style={{borderWidth: 0.5, borderRadius: 5, padding: 5}}>
<Text style={{fontSize: 11}}>
Too much use of `borderRadius` (especially large radii) on
anything which is scrolling may result in dropped frames. Use
sparingly.
</Text>
</View>
{Platform.OS === 'ios' && (
<View
style={{
borderRadius: 20,
padding: 8,
marginTop: 12,
backgroundColor: '#527FE4',
borderCurve: 'continuous',
}}>
<Text style={{fontSize: 16, color: 'white'}}>
View with continuous border curve
</Text>
</View>
)}
</>
);
},
},
Expand Down