Skip to content

Commit

Permalink
feat: Add logical border radius implementation (#35572)
Browse files Browse the repository at this point in the history
Summary:
This PR implements logical border-radius as requested on #34425. This implementation includes the addition of the following style properties

- `borderEndEndRadius`, equivalent to `borderBottomEndRadius`.
- `borderEndStartRadius`, equivalent to `borderBottomStartRadius`.
- `borderStartEndRadius`, equivalent to `borderTopEndRadius`.
- `borderStartStartRadius`, equivalent to `borderTopStartRadius`.

## Changelog

[GENERAL] [ADDED] - Add logical border-radius implementation

Pull Request resolved: #35572

Test Plan:
1. Open the RNTester app and navigate to the `RTLExample` page
2. Test the new style properties through the `Logical Border Radii Start/End` section

https://user-images.githubusercontent.com/11707729/206623732-6d542347-93f9-40da-be97-f7dcd5f66ca9.mov

Reviewed By: necolas

Differential Revision: D42002043

Pulled By: NickGerleman

fbshipit-source-id: a0aa9783c280398b437aeb7a00c6eb3f767657a5
  • Loading branch information
gabrieldonadel authored and facebook-github-bot committed Jan 6, 2023
1 parent 66927ec commit 4ae4984
Show file tree
Hide file tree
Showing 20 changed files with 234 additions and 32 deletions.
4 changes: 4 additions & 0 deletions Libraries/Animated/NativeAnimatedHelper.js
Original file line number Diff line number Diff line change
Expand Up @@ -391,11 +391,15 @@ const SUPPORTED_STYLES = {
borderBottomLeftRadius: true,
borderBottomRightRadius: true,
borderBottomStartRadius: true,
borderEndEndRadius: true,
borderEndStartRadius: true,
borderRadius: true,
borderTopEndRadius: true,
borderTopLeftRadius: true,
borderTopRightRadius: true,
borderTopStartRadius: true,
borderStartEndRadius: true,
borderStartStartRadius: true,
elevation: true,
opacity: true,
transform: true,
Expand Down
4 changes: 4 additions & 0 deletions Libraries/Components/View/ReactNativeStyleAttributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,14 @@ const ReactNativeStyleAttributes: {[string]: AnyAttributeType, ...} = {
borderColor: colorAttributes,
borderCurve: true,
borderEndColor: colorAttributes,
borderEndEndRadius: true,
borderEndStartRadius: true,
borderLeftColor: colorAttributes,
borderRadius: true,
borderRightColor: colorAttributes,
borderStartColor: colorAttributes,
borderStartEndRadius: true,
borderStartStartRadius: true,
borderStyle: true,
borderTopColor: colorAttributes,
borderTopEndRadius: true,
Expand Down
5 changes: 4 additions & 1 deletion Libraries/Components/View/ViewNativeComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,10 @@ export const __INTERNAL_VIEW_CONFIG: PartialViewConfig =
borderTopEndRadius: true,
borderBottomStartRadius: true,
borderBottomEndRadius: true,

borderEndEndRadius: true,
borderEndStartRadius: true,
borderStartEndRadius: true,
borderStartStartRadius: true,
borderStyle: true,
hitSlop: true,
pointerEvents: true,
Expand Down
4 changes: 4 additions & 0 deletions Libraries/NativeComponent/BaseViewConfig.ios.js
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,10 @@ const validAttributesForNonEventProps = {
borderBottomRightRadius: true,
borderBottomStartRadius: true,
borderBottomEndRadius: true,
borderEndEndRadius: true,
borderEndStartRadius: true,
borderStartEndRadius: true,
borderStartStartRadius: true,
display: true,
zIndex: true,

Expand Down
4 changes: 4 additions & 0 deletions Libraries/StyleSheet/StyleSheetTypes.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,12 +240,16 @@ export interface ViewStyle extends FlexStyle, ShadowStyleIOS, TransformsStyle {
borderBottomWidth?: number | undefined;
borderColor?: ColorValue | undefined;
borderEndColor?: ColorValue | undefined;
borderEndEndRadius?: number | undefined;
borderEndStartRadius?: number | undefined;
borderLeftColor?: ColorValue | undefined;
borderLeftWidth?: number | undefined;
borderRadius?: number | undefined;
borderRightColor?: ColorValue | undefined;
borderRightWidth?: number | undefined;
borderStartColor?: ColorValue | undefined;
borderStartEndRadius?: number | undefined;
borderStartStartRadius?: number | undefined;
borderStyle?: 'solid' | 'dotted' | 'dashed' | undefined;
borderTopColor?: ColorValue | undefined;
borderTopEndRadius?: number | undefined;
Expand Down
4 changes: 4 additions & 0 deletions Libraries/StyleSheet/StyleSheetTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -703,6 +703,10 @@ export type ____ViewStyle_InternalCore = $ReadOnly<{
borderBottomLeftRadius?: number | AnimatedNode,
borderBottomRightRadius?: number | AnimatedNode,
borderBottomStartRadius?: number | AnimatedNode,
borderEndEndRadius?: number | AnimatedNode,
borderEndStartRadius?: number | AnimatedNode,
borderStartEndRadius?: number | AnimatedNode,
borderStartStartRadius?: number | AnimatedNode,
borderTopEndRadius?: number | AnimatedNode,
borderTopLeftRadius?: number | AnimatedNode,
borderTopRightRadius?: number | AnimatedNode,
Expand Down
4 changes: 4 additions & 0 deletions React/Views/RCTView.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ extern const UIAccessibilityTraits SwitchAccessibilityTrait;
@property (nonatomic, assign) CGFloat borderBottomRightRadius;
@property (nonatomic, assign) CGFloat borderBottomStartRadius;
@property (nonatomic, assign) CGFloat borderBottomEndRadius;
@property (nonatomic, assign) CGFloat borderEndEndRadius;
@property (nonatomic, assign) CGFloat borderEndStartRadius;
@property (nonatomic, assign) CGFloat borderStartEndRadius;
@property (nonatomic, assign) CGFloat borderStartStartRadius;

/**
* Border colors (actually retained).
Expand Down
34 changes: 22 additions & 12 deletions React/Views/RCTView.m
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,10 @@ - (instancetype)initWithFrame:(CGRect)frame
_borderBottomRightRadius = -1;
_borderBottomStartRadius = -1;
_borderBottomEndRadius = -1;
_borderEndEndRadius = -1;
_borderEndStartRadius = -1;
_borderStartEndRadius = -1;
_borderStartStartRadius = -1;
_borderCurve = RCTBorderCurveCircular;
_borderStyle = RCTBorderStyleSolid;
_hitTestEdgeInsets = UIEdgeInsetsZero;
Expand Down Expand Up @@ -667,11 +671,16 @@ - (RCTCornerRadii)cornerRadii
CGFloat bottomLeftRadius;
CGFloat bottomRightRadius;

const CGFloat logicalTopStartRadius = RCTDefaultIfNegativeTo(_borderStartStartRadius, _borderTopStartRadius);
const CGFloat logicalTopEndRadius = RCTDefaultIfNegativeTo(_borderStartEndRadius, _borderTopEndRadius);
const CGFloat logicalBottomStartRadius = RCTDefaultIfNegativeTo(_borderEndStartRadius, _borderBottomStartRadius);
const CGFloat logicalBottomEndRadius = RCTDefaultIfNegativeTo(_borderEndEndRadius, _borderBottomEndRadius);

if ([[RCTI18nUtil sharedInstance] doLeftAndRightSwapInRTL]) {
const CGFloat topStartRadius = RCTDefaultIfNegativeTo(_borderTopLeftRadius, _borderTopStartRadius);
const CGFloat topEndRadius = RCTDefaultIfNegativeTo(_borderTopRightRadius, _borderTopEndRadius);
const CGFloat bottomStartRadius = RCTDefaultIfNegativeTo(_borderBottomLeftRadius, _borderBottomStartRadius);
const CGFloat bottomEndRadius = RCTDefaultIfNegativeTo(_borderBottomRightRadius, _borderBottomEndRadius);
const CGFloat topStartRadius = RCTDefaultIfNegativeTo(_borderTopLeftRadius, logicalTopStartRadius);
const CGFloat topEndRadius = RCTDefaultIfNegativeTo(_borderTopRightRadius, logicalTopEndRadius);
const CGFloat bottomStartRadius = RCTDefaultIfNegativeTo(_borderBottomLeftRadius, logicalBottomStartRadius);
const CGFloat bottomEndRadius = RCTDefaultIfNegativeTo(_borderBottomRightRadius, logicalBottomEndRadius);

const CGFloat directionAwareTopLeftRadius = isRTL ? topEndRadius : topStartRadius;
const CGFloat directionAwareTopRightRadius = isRTL ? topStartRadius : topEndRadius;
Expand All @@ -683,10 +692,10 @@ - (RCTCornerRadii)cornerRadii
bottomLeftRadius = RCTDefaultIfNegativeTo(radius, directionAwareBottomLeftRadius);
bottomRightRadius = RCTDefaultIfNegativeTo(radius, directionAwareBottomRightRadius);
} else {
const CGFloat directionAwareTopLeftRadius = isRTL ? _borderTopEndRadius : _borderTopStartRadius;
const CGFloat directionAwareTopRightRadius = isRTL ? _borderTopStartRadius : _borderTopEndRadius;
const CGFloat directionAwareBottomLeftRadius = isRTL ? _borderBottomEndRadius : _borderBottomStartRadius;
const CGFloat directionAwareBottomRightRadius = isRTL ? _borderBottomStartRadius : _borderBottomEndRadius;
const CGFloat directionAwareTopLeftRadius = isRTL ? logicalTopEndRadius : logicalTopStartRadius;
const CGFloat directionAwareTopRightRadius = isRTL ? logicalTopStartRadius : logicalTopEndRadius;
const CGFloat directionAwareBottomLeftRadius = isRTL ? logicalBottomEndRadius : logicalBottomStartRadius;
const CGFloat directionAwareBottomRightRadius = isRTL ? logicalBottomStartRadius : logicalBottomEndRadius;

topLeftRadius =
RCTDefaultIfNegativeTo(radius, RCTDefaultIfNegativeTo(_borderTopLeftRadius, directionAwareTopLeftRadius));
Expand Down Expand Up @@ -946,7 +955,8 @@ -(void)setBorder##side##Radius : (CGFloat)radius \

setBorderRadius() setBorderRadius(TopLeft) setBorderRadius(TopRight) setBorderRadius(TopStart)
setBorderRadius(TopEnd) setBorderRadius(BottomLeft) setBorderRadius(BottomRight)
setBorderRadius(BottomStart) setBorderRadius(BottomEnd)
setBorderRadius(BottomStart) setBorderRadius(BottomEnd) setBorderRadius(EndEnd)
setBorderRadius(EndStart) setBorderRadius(StartEnd) setBorderRadius(StartStart)

#pragma mark - Border Curve

Expand All @@ -960,7 +970,7 @@ -(void)setBorder##side##Curve : (RCTBorderCurve)curve \
[self.layer setNeedsDisplay]; \
}

setBorderCurve()
setBorderCurve()

#pragma mark - Border Style

Expand All @@ -974,6 +984,6 @@ -(void)setBorder##side##Style : (RCTBorderStyle)style \
[self.layer setNeedsDisplay]; \
}

setBorderStyle()
setBorderStyle()

@end
@end
4 changes: 4 additions & 0 deletions React/Views/RCTViewManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,10 @@ - (RCTShadowView *)shadowView
RCT_VIEW_BORDER_RADIUS_PROPERTY(BottomRight)
RCT_VIEW_BORDER_RADIUS_PROPERTY(BottomStart)
RCT_VIEW_BORDER_RADIUS_PROPERTY(BottomEnd)
RCT_VIEW_BORDER_RADIUS_PROPERTY(EndEnd)
RCT_VIEW_BORDER_RADIUS_PROPERTY(EndStart)
RCT_VIEW_BORDER_RADIUS_PROPERTY(StartEnd)
RCT_VIEW_BORDER_RADIUS_PROPERTY(StartStart)

RCT_REMAP_VIEW_PROPERTY(display, reactDisplay, YGDisplay)
RCT_REMAP_VIEW_PROPERTY(zIndex, reactZIndex, NSInteger)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,10 @@ public class ViewProps {
public static final String BORDER_TOP_END_RADIUS = "borderTopEndRadius";
public static final String BORDER_BOTTOM_START_RADIUS = "borderBottomStartRadius";
public static final String BORDER_BOTTOM_END_RADIUS = "borderBottomEndRadius";
public static final String BORDER_END_END_RADIUS = "borderEndEndRadius";
public static final String BORDER_END_START_RADIUS = "borderEndStartRadius";
public static final String BORDER_START_END_RADIUS = "borderStartEndRadius";
public static final String BORDER_START_START_RADIUS = "borderStartStartRadius";
public static final String BORDER_START_COLOR = "borderStartColor";
public static final String BORDER_END_COLOR = "borderEndColor";
public static final String ON_LAYOUT = "onLayout";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ object ReactMapBufferPropSetter {
private const val CORNER_BOTTOM_END = 6
private const val CORNER_BOTTOM_START = 7
private const val CORNER_ALL = 8
private const val CORNER_END_END = 9
private const val CORNER_END_START = 10
private const val CORNER_START_END = 11
private const val CORNER_START_START = 12

private const val NATIVE_DRAWABLE_KIND = 0
private const val NATIVE_DRAWABLE_ATTRIBUTE = 1
Expand Down Expand Up @@ -365,6 +369,10 @@ object ReactMapBufferPropSetter {
CORNER_TOP_END -> 6
CORNER_BOTTOM_START -> 7
CORNER_BOTTOM_END -> 8
CORNER_END_END -> 9
CORNER_END_START -> 10
CORNER_START_END -> 11
CORNER_START_START -> 12
else -> throw IllegalArgumentException("Unknown key for border style: $key")
}
val borderRadius = entry.doubleValue
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,11 @@ public enum BorderRadiusLocation {
TOP_START,
TOP_END,
BOTTOM_START,
BOTTOM_END
BOTTOM_END,
END_END,
END_START,
START_END,
START_START
}

public ReactViewBackgroundDrawable(Context context) {
Expand Down Expand Up @@ -271,7 +275,7 @@ public void setRadius(float radius) {

public void setRadius(float radius, int position) {
if (mBorderCornerRadii == null) {
mBorderCornerRadii = new float[8];
mBorderCornerRadii = new float[12];
Arrays.fill(mBorderCornerRadii, YogaConstants.UNDEFINED);
}

Expand Down Expand Up @@ -581,6 +585,11 @@ private void updatePath() {
float bottomStartRadius = getBorderRadius(BorderRadiusLocation.BOTTOM_START);
float bottomEndRadius = getBorderRadius(BorderRadiusLocation.BOTTOM_END);

float endEndRadius = getBorderRadius(BorderRadiusLocation.END_END);
float endStartRadius = getBorderRadius(BorderRadiusLocation.END_START);
float startEndRadius = getBorderRadius(BorderRadiusLocation.START_END);
float startStartRadius = getBorderRadius(BorderRadiusLocation.START_START);

if (I18nUtil.getInstance().doLeftAndRightSwapInRTL(mContext)) {
if (YogaConstants.isUndefined(topStartRadius)) {
topStartRadius = topLeftRadius;
Expand All @@ -598,20 +607,44 @@ private void updatePath() {
bottomEndRadius = bottomRightRadius;
}

final float directionAwareTopLeftRadius = isRTL ? topEndRadius : topStartRadius;
final float directionAwareTopRightRadius = isRTL ? topStartRadius : topEndRadius;
final float directionAwareBottomLeftRadius = isRTL ? bottomEndRadius : bottomStartRadius;
final float directionAwareBottomRightRadius = isRTL ? bottomStartRadius : bottomEndRadius;
final float logicalTopStartRadius =
YogaConstants.isUndefined(topStartRadius) ? startStartRadius : topStartRadius;
final float logicalTopEndRadius =
YogaConstants.isUndefined(topEndRadius) ? startEndRadius : topEndRadius;
final float logicalBottomStartRadius =
YogaConstants.isUndefined(bottomStartRadius) ? endStartRadius : bottomStartRadius;
final float logicalBottomEndRadius =
YogaConstants.isUndefined(bottomEndRadius) ? endEndRadius : bottomEndRadius;

final float directionAwareTopLeftRadius = isRTL ? logicalTopEndRadius : logicalTopStartRadius;
final float directionAwareTopRightRadius =
isRTL ? logicalTopStartRadius : logicalTopEndRadius;
final float directionAwareBottomLeftRadius =
isRTL ? logicalBottomEndRadius : logicalBottomStartRadius;
final float directionAwareBottomRightRadius =
isRTL ? logicalBottomStartRadius : logicalBottomEndRadius;

topLeftRadius = directionAwareTopLeftRadius;
topRightRadius = directionAwareTopRightRadius;
bottomLeftRadius = directionAwareBottomLeftRadius;
bottomRightRadius = directionAwareBottomRightRadius;
} else {
final float directionAwareTopLeftRadius = isRTL ? topEndRadius : topStartRadius;
final float directionAwareTopRightRadius = isRTL ? topStartRadius : topEndRadius;
final float directionAwareBottomLeftRadius = isRTL ? bottomEndRadius : bottomStartRadius;
final float directionAwareBottomRightRadius = isRTL ? bottomStartRadius : bottomEndRadius;
final float logicalTopStartRadius =
YogaConstants.isUndefined(topStartRadius) ? startStartRadius : topStartRadius;
final float logicalTopEndRadius =
YogaConstants.isUndefined(topEndRadius) ? startEndRadius : topEndRadius;
final float logicalBottomStartRadius =
YogaConstants.isUndefined(bottomStartRadius) ? endStartRadius : bottomStartRadius;
final float logicalBottomEndRadius =
YogaConstants.isUndefined(bottomEndRadius) ? endEndRadius : bottomEndRadius;

final float directionAwareTopLeftRadius = isRTL ? logicalTopEndRadius : logicalTopStartRadius;
final float directionAwareTopRightRadius =
isRTL ? logicalTopStartRadius : logicalTopEndRadius;
final float directionAwareBottomLeftRadius =
isRTL ? logicalBottomEndRadius : logicalBottomStartRadius;
final float directionAwareBottomRightRadius =
isRTL ? logicalBottomStartRadius : logicalBottomEndRadius;

if (!YogaConstants.isUndefined(directionAwareTopLeftRadius)) {
topLeftRadius = directionAwareTopLeftRadius;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,10 @@ public void nextFocusUp(ReactViewGroup view, int viewId) {
ViewProps.BORDER_TOP_END_RADIUS,
ViewProps.BORDER_BOTTOM_START_RADIUS,
ViewProps.BORDER_BOTTOM_END_RADIUS,
ViewProps.BORDER_END_END_RADIUS,
ViewProps.BORDER_END_START_RADIUS,
ViewProps.BORDER_START_END_RADIUS,
ViewProps.BORDER_START_START_RADIUS,
},
defaultFloat = YogaConstants.UNDEFINED)
public void setBorderRadius(ReactViewGroup view, int index, float borderRadius) {
Expand Down
10 changes: 9 additions & 1 deletion ReactAndroid/src/main/jni/react/fabric/viewPropConversions.h
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@ constexpr MapBuffer::Key CORNER_TOP_END = 5;
constexpr MapBuffer::Key CORNER_BOTTOM_END = 6;
constexpr MapBuffer::Key CORNER_BOTTOM_START = 7;
constexpr MapBuffer::Key CORNER_ALL = 8;
constexpr MapBuffer::Key CORNER_END_END = 9;
constexpr MapBuffer::Key CORNER_END_START = 10;
constexpr MapBuffer::Key CORNER_START_END = 11;
constexpr MapBuffer::Key CORNER_START_START = 12;

inline void putOptionalFloat(
MapBufferBuilder &builder,
Expand All @@ -122,7 +126,7 @@ inline void putOptionalFloat(
}

MapBuffer convertBorderRadii(CascadedBorderRadii const &radii) {
MapBufferBuilder builder(9);
MapBufferBuilder builder(13);
putOptionalFloat(builder, CORNER_TOP_LEFT, radii.topLeft);
putOptionalFloat(builder, CORNER_TOP_RIGHT, radii.topRight);
putOptionalFloat(builder, CORNER_BOTTOM_RIGHT, radii.bottomRight);
Expand All @@ -132,6 +136,10 @@ MapBuffer convertBorderRadii(CascadedBorderRadii const &radii) {
putOptionalFloat(builder, CORNER_BOTTOM_END, radii.bottomEnd);
putOptionalFloat(builder, CORNER_BOTTOM_START, radii.bottomStart);
putOptionalFloat(builder, CORNER_ALL, radii.all);
putOptionalFloat(builder, CORNER_END_END, radii.endEnd);
putOptionalFloat(builder, CORNER_END_START, radii.endStart);
putOptionalFloat(builder, CORNER_START_END, radii.startEnd);
putOptionalFloat(builder, CORNER_START_START, radii.startStart);
return builder.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -772,6 +772,7 @@ void YogaLayoutableShadowNode::swapLeftAndRightInViewProps(
auto &props = const_cast<ViewProps &>(typedCasting);

// Swap border node values, borderRadii, borderColors and borderStyles.

if (props.borderRadii.topLeft.has_value()) {
props.borderRadii.topStart = props.borderRadii.topLeft;
props.borderRadii.topLeft.reset();
Expand Down

0 comments on commit 4ae4984

Please sign in to comment.