From 1b5f8d3ee557be54c0fb261bcc07ae0ad534a06c Mon Sep 17 00:00:00 2001 From: Ramanpreet Nara Date: Wed, 18 Oct 2017 19:29:42 -0700 Subject: [PATCH] iOS: Implement border(Top|Bottom)(Start|End)Radius and border(Start|End)(Color|Width) RN styles Reviewed By: shergin Differential Revision: D5874536 fbshipit-source-id: 5ad237bddb70745aef0341cddb172da5ee388c38 --- .../View/ReactNativeStyleAttributes.js | 2 + .../Components/View/ViewStylePropTypes.js | 6 + Libraries/StyleSheet/LayoutPropTypes.js | 12 ++ React/Views/RCTShadowView.h | 2 + React/Views/RCTShadowView.m | 17 ++- React/Views/RCTView.h | 8 ++ React/Views/RCTView.m | 115 ++++++++++++++++-- React/Views/RCTViewManager.m | 8 ++ 8 files changed, 156 insertions(+), 14 deletions(-) diff --git a/Libraries/Components/View/ReactNativeStyleAttributes.js b/Libraries/Components/View/ReactNativeStyleAttributes.js index b5cebe4f7df1c0..821c5fa9167e77 100644 --- a/Libraries/Components/View/ReactNativeStyleAttributes.js +++ b/Libraries/Components/View/ReactNativeStyleAttributes.js @@ -40,6 +40,8 @@ ReactNativeStyleAttributes.borderColor = colorAttributes; ReactNativeStyleAttributes.borderLeftColor = colorAttributes; ReactNativeStyleAttributes.borderRightColor = colorAttributes; ReactNativeStyleAttributes.borderTopColor = colorAttributes; +ReactNativeStyleAttributes.borderStartColor = colorAttributes; +ReactNativeStyleAttributes.borderEndColor = colorAttributes; ReactNativeStyleAttributes.color = colorAttributes; ReactNativeStyleAttributes.shadowColor = colorAttributes; ReactNativeStyleAttributes.textDecorationColor = colorAttributes; diff --git a/Libraries/Components/View/ViewStylePropTypes.js b/Libraries/Components/View/ViewStylePropTypes.js index 49fff29d81883d..3c6886249a74ce 100644 --- a/Libraries/Components/View/ViewStylePropTypes.js +++ b/Libraries/Components/View/ViewStylePropTypes.js @@ -31,11 +31,17 @@ var ViewStylePropTypes = { borderRightColor: ColorPropType, borderBottomColor: ColorPropType, borderLeftColor: ColorPropType, + borderStartColor: ColorPropType, + borderEndColor: ColorPropType, borderRadius: ReactPropTypes.number, borderTopLeftRadius: ReactPropTypes.number, borderTopRightRadius: ReactPropTypes.number, + borderTopStartRadius: ReactPropTypes.number, + borderTopEndRadius: ReactPropTypes.number, borderBottomLeftRadius: ReactPropTypes.number, borderBottomRightRadius: ReactPropTypes.number, + borderBottomStartRadius: ReactPropTypes.number, + borderBottomEndRadius: ReactPropTypes.number, borderStyle: ReactPropTypes.oneOf(['solid', 'dotted', 'dashed']), borderWidth: ReactPropTypes.number, borderTopWidth: ReactPropTypes.number, diff --git a/Libraries/StyleSheet/LayoutPropTypes.js b/Libraries/StyleSheet/LayoutPropTypes.js index f448965987d16f..d6c4c9964fc5c9 100644 --- a/Libraries/StyleSheet/LayoutPropTypes.js +++ b/Libraries/StyleSheet/LayoutPropTypes.js @@ -325,6 +325,18 @@ var LayoutPropTypes = { */ borderTopWidth: ReactPropTypes.number, + /** + * When direction is `ltr`, `borderStartWidth` is equivalent to `borderLeftWidth`. + * When direction is `rtl`, `borderStartWidth` is equivalent to `borderRightWidth`. + */ + borderStartWidth: ReactPropTypes.number, + + /** + * When direction is `ltr`, `borderEndWidth` is equivalent to `borderRightWidth`. + * When direction is `rtl`, `borderEndWidth` is equivalent to `borderLeftWidth`. + */ + borderEndWidth: ReactPropTypes.number, + /** `borderRightWidth` works like `border-right-width` in CSS. * See https://developer.mozilla.org/en-US/docs/Web/CSS/border-right-width * for more details. diff --git a/React/Views/RCTShadowView.h b/React/Views/RCTShadowView.h index ca9ac1665bdb48..884cb3f581256f 100644 --- a/React/Views/RCTShadowView.h +++ b/React/Views/RCTShadowView.h @@ -122,6 +122,8 @@ typedef void (^RCTApplierBlock)(NSDictionary *viewRegistry @property (nonatomic, assign) float borderLeftWidth; @property (nonatomic, assign) float borderBottomWidth; @property (nonatomic, assign) float borderRightWidth; +@property (nonatomic, assign) float borderStartWidth; +@property (nonatomic, assign) float borderEndWidth; /** * Margin. Defaults to { 0, 0, 0, 0 }. diff --git a/React/Views/RCTShadowView.m b/React/Views/RCTShadowView.m index 602b815dbe9c41..5879e8b05f8571 100644 --- a/React/Views/RCTShadowView.m +++ b/React/Views/RCTShadowView.m @@ -26,6 +26,8 @@ typedef NS_ENUM(unsigned int, meta_prop_t) { META_PROP_TOP, META_PROP_RIGHT, META_PROP_BOTTOM, + META_PROP_START, + META_PROP_END, META_PROP_HORIZONTAL, META_PROP_VERTICAL, META_PROP_ALL, @@ -121,8 +123,17 @@ static void RCTProcessMetaPropsMargin(const YGValue metaProps[META_PROP_COUNT], } static void RCTProcessMetaPropsBorder(const YGValue metaProps[META_PROP_COUNT], YGNodeRef node) { - YGNodeStyleSetBorder(node, YGEdgeStart, metaProps[META_PROP_LEFT].value); - YGNodeStyleSetBorder(node, YGEdgeEnd, metaProps[META_PROP_RIGHT].value); + if (![[RCTI18nUtil sharedInstance] doesRTLFlipLeftAndRightStyles]) { + YGNodeStyleSetBorder(node, YGEdgeStart, metaProps[META_PROP_START].value); + YGNodeStyleSetBorder(node, YGEdgeEnd, metaProps[META_PROP_END].value); + YGNodeStyleSetBorder(node, YGEdgeLeft, metaProps[META_PROP_LEFT].value); + YGNodeStyleSetBorder(node, YGEdgeRight, metaProps[META_PROP_RIGHT].value); + } else { + const float start = YGFloatIsUndefined(metaProps[META_PROP_START].value) ? metaProps[META_PROP_LEFT].value : metaProps[META_PROP_START].value; + const float end = YGFloatIsUndefined(metaProps[META_PROP_END].value) ? metaProps[META_PROP_RIGHT].value : metaProps[META_PROP_END].value; + YGNodeStyleSetBorder(node, YGEdgeStart, start); + YGNodeStyleSetBorder(node, YGEdgeEnd, end); + } YGNodeStyleSetBorder(node, YGEdgeTop, metaProps[META_PROP_TOP].value); YGNodeStyleSetBorder(node, YGEdgeBottom, metaProps[META_PROP_BOTTOM].value); YGNodeStyleSetBorder(node, YGEdgeHorizontal, metaProps[META_PROP_HORIZONTAL].value); @@ -557,6 +568,8 @@ - (float)border##prop##Width \ RCT_BORDER_PROPERTY(Left, LEFT) RCT_BORDER_PROPERTY(Bottom, BOTTOM) RCT_BORDER_PROPERTY(Right, RIGHT) +RCT_BORDER_PROPERTY(Start, START) +RCT_BORDER_PROPERTY(End, END) // Dimensions #define RCT_DIMENSION_PROPERTY(setProp, getProp, cssProp) \ diff --git a/React/Views/RCTView.h b/React/Views/RCTView.h index ddf85dd55451bc..5c223987d30372 100644 --- a/React/Views/RCTView.h +++ b/React/Views/RCTView.h @@ -70,8 +70,12 @@ @property (nonatomic, assign) CGFloat borderRadius; @property (nonatomic, assign) CGFloat borderTopLeftRadius; @property (nonatomic, assign) CGFloat borderTopRightRadius; +@property (nonatomic, assign) CGFloat borderTopStartRadius; +@property (nonatomic, assign) CGFloat borderTopEndRadius; @property (nonatomic, assign) CGFloat borderBottomLeftRadius; @property (nonatomic, assign) CGFloat borderBottomRightRadius; +@property (nonatomic, assign) CGFloat borderBottomStartRadius; +@property (nonatomic, assign) CGFloat borderBottomEndRadius; /** * Border colors (actually retained). @@ -80,6 +84,8 @@ @property (nonatomic, assign) CGColorRef borderRightColor; @property (nonatomic, assign) CGColorRef borderBottomColor; @property (nonatomic, assign) CGColorRef borderLeftColor; +@property (nonatomic, assign) CGColorRef borderStartColor; +@property (nonatomic, assign) CGColorRef borderEndColor; @property (nonatomic, assign) CGColorRef borderColor; /** @@ -89,6 +95,8 @@ @property (nonatomic, assign) CGFloat borderRightWidth; @property (nonatomic, assign) CGFloat borderBottomWidth; @property (nonatomic, assign) CGFloat borderLeftWidth; +@property (nonatomic, assign) CGFloat borderStartWidth; +@property (nonatomic, assign) CGFloat borderEndWidth; @property (nonatomic, assign) CGFloat borderWidth; /** diff --git a/React/Views/RCTView.m b/React/Views/RCTView.m index a63512b443d708..9559a36e39bc36 100644 --- a/React/Views/RCTView.m +++ b/React/Views/RCTView.m @@ -15,6 +15,7 @@ #import "RCTLog.h" #import "RCTUtils.h" #import "UIView+React.h" +#import "RCTI18nUtil.h" @implementation UIView (RCTViewUnmounting) @@ -110,10 +111,16 @@ - (instancetype)initWithFrame:(CGRect)frame _borderRightWidth = -1; _borderBottomWidth = -1; _borderLeftWidth = -1; + _borderStartWidth = -1; + _borderEndWidth = -1; _borderTopLeftRadius = -1; _borderTopRightRadius = -1; + _borderTopStartRadius = -1; + _borderTopEndRadius = -1; _borderBottomLeftRadius = -1; _borderBottomRightRadius = -1; + _borderBottomStartRadius = -1; + _borderBottomEndRadius = -1; _borderStyle = RCTBorderStyleSolid; _hitTestEdgeInsets = UIEdgeInsetsZero; @@ -127,7 +134,10 @@ - (instancetype)initWithFrame:(CGRect)frame - (void)setReactLayoutDirection:(UIUserInterfaceLayoutDirection)layoutDirection { - _reactLayoutDirection = layoutDirection; + if (_reactLayoutDirection != layoutDirection) { + _reactLayoutDirection = layoutDirection; + [self.layer setNeedsDisplay]; + } if ([self respondsToSelector:@selector(setSemanticContentAttribute:)]) { self.semanticContentAttribute = @@ -424,26 +434,77 @@ - (void)setBackgroundColor:(UIColor *)backgroundColor [self.layer setNeedsDisplay]; } +static CGFloat RCTDefaultIfNegativeTo(CGFloat defaultValue, CGFloat x) { + return x >= 0 ? x : defaultValue; +}; + - (UIEdgeInsets)bordersAsInsets { const CGFloat borderWidth = MAX(0, _borderWidth); + const BOOL isRTL = _reactLayoutDirection == UIUserInterfaceLayoutDirectionRightToLeft; + + if ([[RCTI18nUtil sharedInstance] doesRTLFlipLeftAndRightStyles]) { + const CGFloat borderStartWidth = RCTDefaultIfNegativeTo(_borderLeftWidth, _borderStartWidth); + const CGFloat borderEndWidth = RCTDefaultIfNegativeTo(_borderRightWidth, _borderEndWidth); + + const CGFloat directionAwareBorderLeftWidth = isRTL ? borderEndWidth : borderStartWidth; + const CGFloat directionAwareBorderRightWidth = isRTL ? borderStartWidth : borderEndWidth; + + return (UIEdgeInsets) { + RCTDefaultIfNegativeTo(borderWidth, _borderTopWidth), + RCTDefaultIfNegativeTo(borderWidth, directionAwareBorderLeftWidth), + RCTDefaultIfNegativeTo(borderWidth, _borderBottomWidth), + RCTDefaultIfNegativeTo(borderWidth, directionAwareBorderRightWidth), + }; + } + + const CGFloat directionAwareBorderLeftWidth = isRTL ? _borderEndWidth : _borderStartWidth; + const CGFloat directionAwareBorderRightWidth = isRTL ? _borderStartWidth : _borderEndWidth; return (UIEdgeInsets) { - _borderTopWidth >= 0 ? _borderTopWidth : borderWidth, - _borderLeftWidth >= 0 ? _borderLeftWidth : borderWidth, - _borderBottomWidth >= 0 ? _borderBottomWidth : borderWidth, - _borderRightWidth >= 0 ? _borderRightWidth : borderWidth, + RCTDefaultIfNegativeTo(borderWidth, _borderTopWidth), + RCTDefaultIfNegativeTo(borderWidth, RCTDefaultIfNegativeTo(_borderLeftWidth, directionAwareBorderLeftWidth)), + RCTDefaultIfNegativeTo(borderWidth, _borderBottomWidth), + RCTDefaultIfNegativeTo(borderWidth, RCTDefaultIfNegativeTo(_borderRightWidth, directionAwareBorderRightWidth)), }; } - (RCTCornerRadii)cornerRadii { - // Get corner radii + const BOOL isRTL = _reactLayoutDirection == UIUserInterfaceLayoutDirectionRightToLeft; const CGFloat radius = MAX(0, _borderRadius); - const CGFloat topLeftRadius = _borderTopLeftRadius >= 0 ? _borderTopLeftRadius : radius; - const CGFloat topRightRadius = _borderTopRightRadius >= 0 ? _borderTopRightRadius : radius; - const CGFloat bottomLeftRadius = _borderBottomLeftRadius >= 0 ? _borderBottomLeftRadius : radius; - const CGFloat bottomRightRadius = _borderBottomRightRadius >= 0 ? _borderBottomRightRadius : radius; + + CGFloat topLeftRadius; + CGFloat topRightRadius; + CGFloat bottomLeftRadius; + CGFloat bottomRightRadius; + + if ([[RCTI18nUtil sharedInstance] doesRTLFlipLeftAndRightStyles]) { + 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 directionAwareTopLeftRadius = isRTL ? topEndRadius : topStartRadius; + const CGFloat directionAwareTopRightRadius = isRTL ? topStartRadius : topEndRadius; + const CGFloat directionAwareBottomLeftRadius = isRTL ? bottomEndRadius : bottomStartRadius; + const CGFloat directionAwareBottomRightRadius = isRTL ? bottomStartRadius : bottomEndRadius; + + topLeftRadius = RCTDefaultIfNegativeTo(radius, directionAwareTopLeftRadius); + topRightRadius = RCTDefaultIfNegativeTo(radius, directionAwareTopRightRadius); + 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; + + topLeftRadius = RCTDefaultIfNegativeTo(radius, RCTDefaultIfNegativeTo(_borderTopLeftRadius, directionAwareTopLeftRadius)); + topRightRadius = RCTDefaultIfNegativeTo(radius, RCTDefaultIfNegativeTo(_borderTopRightRadius, directionAwareTopRightRadius)); + bottomLeftRadius = RCTDefaultIfNegativeTo(radius, RCTDefaultIfNegativeTo(_borderBottomLeftRadius, directionAwareBottomLeftRadius)); + bottomRightRadius = RCTDefaultIfNegativeTo(radius, RCTDefaultIfNegativeTo(_borderBottomRightRadius, directionAwareBottomRightRadius)); + } // Get scale factors required to prevent radii from overlapping const CGSize size = self.bounds.size; @@ -463,11 +524,31 @@ - (RCTCornerRadii)cornerRadii - (RCTBorderColors)borderColors { + const BOOL isRTL = _reactLayoutDirection == UIUserInterfaceLayoutDirectionRightToLeft; + + if ([[RCTI18nUtil sharedInstance] doesRTLFlipLeftAndRightStyles]) { + const CGColorRef borderStartColor = _borderStartColor ?: _borderLeftColor; + const CGColorRef borderEndColor = _borderEndColor ?: _borderRightColor; + + const CGColorRef directionAwareBorderLeftColor = isRTL ? borderEndColor : borderStartColor; + const CGColorRef directionAwareBorderRightColor = isRTL ? borderStartColor : borderEndColor; + + return (RCTBorderColors){ + _borderTopColor ?: _borderColor, + directionAwareBorderLeftColor ?: _borderColor, + _borderBottomColor ?: _borderColor, + directionAwareBorderRightColor ?: _borderColor, + }; + } + + const CGColorRef directionAwareBorderLeftColor = isRTL ? _borderEndColor : _borderStartColor; + const CGColorRef directionAwareBorderRightColor = isRTL ? _borderStartColor : _borderEndColor; + return (RCTBorderColors){ _borderTopColor ?: _borderColor, - _borderLeftColor ?: _borderColor, + directionAwareBorderLeftColor ?: _borderLeftColor ?: _borderColor, _borderBottomColor ?: _borderColor, - _borderRightColor ?: _borderColor, + directionAwareBorderRightColor ?: _borderRightColor ?: _borderColor, }; } @@ -656,6 +737,8 @@ - (void)setBorder##side##Color:(CGColorRef)color \ setBorderColor(Right) setBorderColor(Bottom) setBorderColor(Left) +setBorderColor(Start) +setBorderColor(End) #pragma mark - Border Width @@ -674,6 +757,8 @@ - (void)setBorder##side##Width:(CGFloat)width \ setBorderWidth(Right) setBorderWidth(Bottom) setBorderWidth(Left) +setBorderWidth(Start) +setBorderWidth(End) #pragma mark - Border Radius @@ -690,8 +775,12 @@ - (void)setBorder##side##Radius:(CGFloat)radius \ setBorderRadius() setBorderRadius(TopLeft) setBorderRadius(TopRight) +setBorderRadius(TopStart) +setBorderRadius(TopEnd) setBorderRadius(BottomLeft) setBorderRadius(BottomRight) +setBorderRadius(BottomStart) +setBorderRadius(BottomEnd) #pragma mark - Border Style @@ -714,6 +803,8 @@ - (void)dealloc CGColorRelease(_borderRightColor); CGColorRelease(_borderBottomColor); CGColorRelease(_borderLeftColor); + CGColorRelease(_borderStartColor); + CGColorRelease(_borderEndColor); } @end diff --git a/React/Views/RCTViewManager.m b/React/Views/RCTViewManager.m index 72de246dba539b..f4e1b830985720 100644 --- a/React/Views/RCTViewManager.m +++ b/React/Views/RCTViewManager.m @@ -246,6 +246,8 @@ - (RCTViewManagerUIBlock)uiBlockToAmendWithShadowViewRegistry:(__unused NSDictio RCT_VIEW_BORDER_PROPERTY(Right) RCT_VIEW_BORDER_PROPERTY(Bottom) RCT_VIEW_BORDER_PROPERTY(Left) +RCT_VIEW_BORDER_PROPERTY(Start) +RCT_VIEW_BORDER_PROPERTY(End) #define RCT_VIEW_BORDER_RADIUS_PROPERTY(SIDE) \ RCT_CUSTOM_VIEW_PROPERTY(border##SIDE##Radius, CGFloat, RCTView) \ @@ -257,8 +259,12 @@ - (RCTViewManagerUIBlock)uiBlockToAmendWithShadowViewRegistry:(__unused NSDictio RCT_VIEW_BORDER_RADIUS_PROPERTY(TopLeft) RCT_VIEW_BORDER_RADIUS_PROPERTY(TopRight) +RCT_VIEW_BORDER_RADIUS_PROPERTY(TopStart) +RCT_VIEW_BORDER_RADIUS_PROPERTY(TopEnd) RCT_VIEW_BORDER_RADIUS_PROPERTY(BottomLeft) RCT_VIEW_BORDER_RADIUS_PROPERTY(BottomRight) +RCT_VIEW_BORDER_RADIUS_PROPERTY(BottomStart) +RCT_VIEW_BORDER_RADIUS_PROPERTY(BottomEnd) RCT_REMAP_VIEW_PROPERTY(display, reactDisplay, YGDisplay) RCT_REMAP_VIEW_PROPERTY(zIndex, reactZIndex, NSInteger) @@ -286,6 +292,8 @@ - (RCTViewManagerUIBlock)uiBlockToAmendWithShadowViewRegistry:(__unused NSDictio RCT_EXPORT_SHADOW_PROPERTY(borderRightWidth, float) RCT_EXPORT_SHADOW_PROPERTY(borderBottomWidth, float) RCT_EXPORT_SHADOW_PROPERTY(borderLeftWidth, float) +RCT_EXPORT_SHADOW_PROPERTY(borderStartWidth, float) +RCT_EXPORT_SHADOW_PROPERTY(borderEndWidth, float) RCT_EXPORT_SHADOW_PROPERTY(borderWidth, float) RCT_EXPORT_SHADOW_PROPERTY(marginTop, YGValue)