diff --git a/Examples/UIExplorer/BorderExample.js b/Examples/UIExplorer/BorderExample.js
index d9c2acf9a..1790dc491 100644
--- a/Examples/UIExplorer/BorderExample.js
+++ b/Examples/UIExplorer/BorderExample.js
@@ -57,6 +57,17 @@ var styles = StyleSheet.create({
borderLeftWidth: 40,
borderLeftColor: 'blue',
},
+ border5: {
+ borderRadius: 50,
+ borderTopWidth: 10,
+ borderTopColor: 'red',
+ borderRightWidth: 20,
+ borderRightColor: 'yellow',
+ borderBottomWidth: 30,
+ borderBottomColor: 'green',
+ borderLeftWidth: 40,
+ borderLeftColor: 'blue',
+ },
});
exports.title = 'Border';
@@ -71,7 +82,7 @@ exports.examples = [
},
{
title: 'Equal-Width / Same-Color',
- description: 'borderWidth & borderColor',
+ description: 'borderWidth & borderColor & borderRadius',
render() {
return ;
}
@@ -97,4 +108,11 @@ exports.examples = [
return ;
}
},
+ {
+ title: 'Custom Borders',
+ description: 'border*Width & border*Color',
+ render() {
+ return ;
+ }
+ },
];
diff --git a/Examples/UIExplorer/UIExplorerBlock.js b/Examples/UIExplorer/UIExplorerBlock.js
index 924415e01..e7c2a2a8e 100644
--- a/Examples/UIExplorer/UIExplorerBlock.js
+++ b/Examples/UIExplorer/UIExplorerBlock.js
@@ -70,6 +70,7 @@ var styles = StyleSheet.create({
},
titleContainer: {
borderWidth: 0.5,
+ borderRadius: 2.5,
borderColor: '#d6d7da',
backgroundColor: '#f6f7f8',
paddingHorizontal: 10,
@@ -78,8 +79,10 @@ var styles = StyleSheet.create({
titleRow: {
flexDirection: 'row',
justifyContent: 'space-between',
+ backgroundColor: 'transparent',
},
titleText: {
+ backgroundColor: 'transparent',
fontSize: 14,
fontWeight: '500',
},
@@ -97,6 +100,7 @@ var styles = StyleSheet.create({
height: 8,
},
children: {
+ backgroundColor: 'transparent',
padding: 10,
}
});
diff --git a/React/Base/RCTUtils.h b/React/Base/RCTUtils.h
index 812a65122..1c0412568 100644
--- a/React/Base/RCTUtils.h
+++ b/React/Base/RCTUtils.h
@@ -46,3 +46,6 @@ RCT_EXTERN BOOL RCTClassOverridesInstanceMethod(Class cls, SEL selector);
// TODO(#6472857): create NSErrors and automatically convert them over the bridge.
RCT_EXTERN NSDictionary *RCTMakeError(NSString *message, id toStringify, NSDictionary *extraData);
RCT_EXTERN NSDictionary *RCTMakeAndLogError(NSString *message, id toStringify, NSDictionary *extraData);
+
+// Returns YES if React is running in a test environment
+RCT_EXTERN BOOL RCTRunningInTestEnvironment(void);
diff --git a/React/Base/RCTUtils.m b/React/Base/RCTUtils.m
index cea45c324..0b4a3873c 100644
--- a/React/Base/RCTUtils.m
+++ b/React/Base/RCTUtils.m
@@ -201,3 +201,13 @@ NSDictionary *RCTMakeAndLogError(NSString *message, id toStringify, NSDictionary
RCTLogError(@"\nError: %@", error);
return error;
}
+
+BOOL RCTRunningInTestEnvironment(void)
+{
+ static BOOL _isTestEnvironment = NO;
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ _isTestEnvironment = (NSClassFromString(@"SenTestCase") != nil || NSClassFromString(@"XCTest") != nil);
+ });
+ return _isTestEnvironment;
+}
diff --git a/React/Views/RCTView.h b/React/Views/RCTView.h
index 73fe2c7cb..1a4bcb400 100644
--- a/React/Views/RCTView.h
+++ b/React/Views/RCTView.h
@@ -13,13 +13,6 @@
#import "RCTPointerEvents.h"
-typedef NS_ENUM(NSInteger, RCTBorderSide) {
- RCTBorderSideTop,
- RCTBorderSideRight,
- RCTBorderSideBottom,
- RCTBorderSideLeft
-};
-
@protocol RCTAutoInsetsProtocol;
@interface RCTView : UIView
diff --git a/React/Views/RCTView.m b/React/Views/RCTView.m
index d40798302..c0786b5ab 100644
--- a/React/Views/RCTView.m
+++ b/React/Views/RCTView.m
@@ -12,9 +12,10 @@
#import "RCTAutoInsetsProtocol.h"
#import "RCTConvert.h"
#import "RCTLog.h"
+#import "RCTUtils.h"
#import "UIView+React.h"
-static const RCTBorderSide RCTBorderSideCount = 4;
+static void *RCTViewCornerRadiusKVOContext = &RCTViewCornerRadiusKVOContext;
static UIView *RCTViewHitTest(UIView *view, CGPoint point, UIEvent *event)
{
@@ -30,6 +31,10 @@ static UIView *RCTViewHitTest(UIView *view, CGPoint point, UIEvent *event)
return nil;
}
+static BOOL RCTEllipseGetIntersectionsWithLine(CGRect ellipseBoundingRect, CGPoint p1, CGPoint p2, CGPoint intersections[2]);
+static CGPathRef RCTPathCreateWithRoundedRect(CGRect rect, CGFloat topLeftRadiusX, CGFloat topLeftRadiusY, CGFloat topRightRadiusX, CGFloat topRightRadiusY, CGFloat bottomLeftRadiusX, CGFloat bottomLeftRadiusY, CGFloat bottomRightRadiusX, CGFloat bottomRightRadiusY, const CGAffineTransform *transform);
+static void RCTPathAddEllipticArc(CGMutablePathRef path, const CGAffineTransform *m, CGFloat x, CGFloat y, CGFloat xRadius, CGFloat yRadius, CGFloat startAngle, CGFloat endAngle, bool clockwise);
+
@implementation UIView (RCTViewUnmounting)
- (void)react_remountAllSubviews
@@ -107,8 +112,39 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
@implementation RCTView
{
NSMutableArray *_reactSubviews;
- CAShapeLayer *_borderLayers[RCTBorderSideCount];
- CGFloat _borderWidths[RCTBorderSideCount];
+ UIColor *_backgroundColor;
+}
+
+- (instancetype)initWithFrame:(CGRect)frame
+{
+ if ((self = [super initWithFrame:frame])) {
+ _borderWidth = -1;
+ _borderTopWidth = -1;
+ _borderRightWidth = -1;
+ _borderBottomWidth = -1;
+ _borderLeftWidth = -1;
+
+ _backgroundColor = [super backgroundColor];
+ [super setBackgroundColor:[UIColor clearColor]];
+
+ [self.layer addObserver:self forKeyPath:@"cornerRadius" options:0 context:RCTViewCornerRadiusKVOContext];
+ }
+
+ return self;
+}
+
+- (void)dealloc
+{
+ [self.layer removeObserver:self forKeyPath:@"cornerRadius" context:RCTViewCornerRadiusKVOContext];
+}
+
+- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
+{
+ if (context == RCTViewCornerRadiusKVOContext) {
+ [self.layer setNeedsDisplay];
+ } else {
+ [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
+ }
}
- (NSString *)accessibilityLabel
@@ -381,189 +417,353 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
if (_reactSubviews) {
[self updateClippedSubviews];
}
-
- for (RCTBorderSide side = 0; side < RCTBorderSideCount; side++) {
- if (_borderLayers[side]) [self updatePathForShapeLayerForSide:side];
- }
}
-- (void)layoutSublayersOfLayer:(CALayer *)layer
-{
- [super layoutSublayersOfLayer:layer];
+#pragma mark - Borders
- const CGRect bounds = layer.bounds;
- for (RCTBorderSide side = 0; side < RCTBorderSideCount; side++) {
- _borderLayers[side].frame = bounds;
- }
+- (UIColor *)backgroundColor
+{
+ return _backgroundColor;
}
-- (BOOL)getTrapezoidPoints:(CGPoint[4])outPoints forSide:(RCTBorderSide)side
+- (void)setBackgroundColor:(UIColor *)backgroundColor
{
- const CGRect bounds = self.layer.bounds;
- const CGFloat minX = CGRectGetMinX(bounds);
- const CGFloat maxX = CGRectGetMaxX(bounds);
- const CGFloat minY = CGRectGetMinY(bounds);
- const CGFloat maxY = CGRectGetMaxY(bounds);
-
-#define BW(SIDE) [self borderWidthForSide:RCTBorderSide##SIDE]
-
- switch (side) {
- case RCTBorderSideRight:
- outPoints[0] = CGPointMake(maxX - BW(Right), maxY - BW(Bottom));
- outPoints[1] = CGPointMake(maxX - BW(Right), minY + BW(Top));
- outPoints[2] = CGPointMake(maxX, minY);
- outPoints[3] = CGPointMake(maxX, maxY);
- break;
- case RCTBorderSideBottom:
- outPoints[0] = CGPointMake(minX + BW(Left), maxY - BW(Bottom));
- outPoints[1] = CGPointMake(maxX - BW(Right), maxY - BW(Bottom));
- outPoints[2] = CGPointMake(maxX, maxY);
- outPoints[3] = CGPointMake(minX, maxY);
- break;
- case RCTBorderSideLeft:
- outPoints[0] = CGPointMake(minX + BW(Left), minY + BW(Top));
- outPoints[1] = CGPointMake(minX + BW(Left), maxY - BW(Bottom));
- outPoints[2] = CGPointMake(minX, maxY);
- outPoints[3] = CGPointMake(minX, minY);
- break;
- case RCTBorderSideTop:
- outPoints[0] = CGPointMake(maxX - BW(Right), minY + BW(Top));
- outPoints[1] = CGPointMake(minX + BW(Left), minY + BW(Top));
- outPoints[2] = CGPointMake(minX, minY);
- outPoints[3] = CGPointMake(maxX, minY);
- break;
+ if ([_backgroundColor isEqual:backgroundColor]) {
+ return;
}
-
- return YES;
+ _backgroundColor = backgroundColor;
+ [self.layer setNeedsDisplay];
}
-- (CAShapeLayer *)createShapeLayerIfNotExistsForSide:(RCTBorderSide)side
+- (UIImage *)generateBorderImage:(out CGRect *)contentsCenter
{
- CAShapeLayer *borderLayer = _borderLayers[side];
- if (!borderLayer) {
- borderLayer = [CAShapeLayer layer];
- borderLayer.fillColor = self.layer.borderColor;
- [self.layer addSublayer:borderLayer];
- _borderLayers[side] = borderLayer;
+ const CGFloat maxRadius = MIN(self.bounds.size.height, self.bounds.size.width) / 2.0;
+ const CGFloat radius = MAX(0, MIN(self.layer.cornerRadius, maxRadius));
+
+ const CGFloat borderWidth = MAX(0, _borderWidth);
+ const CGFloat topWidth = _borderTopWidth >= 0 ? _borderTopWidth : borderWidth;
+ const CGFloat rightWidth = _borderRightWidth >= 0 ? _borderRightWidth : borderWidth;
+ const CGFloat bottomWidth = _borderBottomWidth >= 0 ? _borderBottomWidth : borderWidth;
+ const CGFloat leftWidth = _borderLeftWidth >= 0 ? _borderLeftWidth : borderWidth;
+
+ const CGFloat topRadius = MAX(0, radius - topWidth);
+ const CGFloat rightRadius = MAX(0, radius - rightWidth);
+ const CGFloat bottomRadius = MAX(0, radius - bottomWidth);
+ const CGFloat leftRadius = MAX(0, radius - leftWidth);
+
+ const UIEdgeInsets edgeInsets = UIEdgeInsetsMake(topWidth + topRadius, leftWidth + leftRadius, bottomWidth + bottomRadius, rightWidth + rightRadius);
+ const CGSize size = CGSizeMake(edgeInsets.left + 1 + edgeInsets.right, edgeInsets.top + 1 + edgeInsets.bottom);
+
+ UIScreen *screen = self.window.screen ?: [UIScreen mainScreen];
+ UIGraphicsBeginImageContextWithOptions(size, NO, screen.scale * 2);
+
+ CGContextRef ctx = UIGraphicsGetCurrentContext();
+ const CGRect rect = {CGPointZero, size};
+ CGPathRef path = CGPathCreateWithRoundedRect(rect, radius, radius, NULL);
+
+ if (_backgroundColor) {
+ CGContextSaveGState(ctx);
+
+ CGContextAddPath(ctx, path);
+ CGContextSetFillColorWithColor(ctx, _backgroundColor.CGColor);
+ CGContextFillPath(ctx);
+
+ CGContextRestoreGState(ctx);
}
- return borderLayer;
-}
-- (void)updatePathForShapeLayerForSide:(RCTBorderSide)side
-{
- CAShapeLayer *borderLayer = [self createShapeLayerIfNotExistsForSide:side];
-
- CGPoint trapezoidPoints[4];
- [self getTrapezoidPoints:trapezoidPoints forSide:side];
-
- CGMutablePathRef path = CGPathCreateMutable();
- CGPathAddLines(path, NULL, trapezoidPoints, 4);
- CGPathCloseSubpath(path);
- borderLayer.path = path;
+ CGContextAddPath(ctx, path);
CGPathRelease(path);
-}
-- (void)updateBorderLayers
-{
- BOOL widthsAndColorsSame = YES;
- CGFloat width = _borderWidths[0];
- CGColorRef color = _borderLayers[0].fillColor;
- for (RCTBorderSide side = 1; side < RCTBorderSideCount; side++) {
- CAShapeLayer *layer = _borderLayers[side];
- if (_borderWidths[side] != width || (layer && !CGColorEqualToColor(layer.fillColor, color))) {
- widthsAndColorsSame = NO;
- break;
- }
+ if (radius > 0 && topWidth > 0 && rightWidth > 0 && bottomWidth > 0 && leftWidth > 0) {
+ const UIEdgeInsets insetEdgeInsets = UIEdgeInsetsMake(topWidth, leftWidth, bottomWidth, rightWidth);
+ const CGRect insetRect = UIEdgeInsetsInsetRect(rect, insetEdgeInsets);
+ CGPathRef insetPath = RCTPathCreateWithRoundedRect(insetRect, leftRadius, topRadius, rightRadius, topRadius, leftRadius, bottomRadius, rightRadius, bottomRadius, NULL);
+ CGContextAddPath(ctx, insetPath);
+ CGPathRelease(insetPath);
}
- if (widthsAndColorsSame) {
- // Set main layer border
- if (width) {
- _borderWidth = self.layer.borderWidth = width;
- }
- if (color) {
- self.layer.borderColor = color;
- }
-
- // Remove border layers
- for (RCTBorderSide side = 0; side < RCTBorderSideCount; side++) {
- [_borderLayers[side] removeFromSuperlayer];
- _borderLayers[side] = nil;
- }
+ CGContextEOClip(ctx);
+ BOOL hasEqualColor = !_borderTopColor && !_borderRightColor && !_borderBottomColor && !_borderLeftColor;
+ BOOL hasEqualBorder = _borderWidth >= 0 && _borderTopWidth < 0 && _borderRightWidth < 0 && _borderBottomWidth < 0 && _borderLeftWidth < 0;
+ if (radius <= 0 && hasEqualBorder && hasEqualColor) {
+ CGContextSetStrokeColorWithColor(ctx, _borderColor);
+ CGContextSetLineWidth(ctx, 2 * _borderWidth);
+ CGContextClipToRect(ctx, rect);
+ CGContextStrokeRect(ctx, rect);
+ } else if (radius <= 0 && hasEqualColor) {
+ CGContextSetFillColorWithColor(ctx, _borderColor);
+ CGContextAddRect(ctx, rect);
+ const CGRect insetRect = UIEdgeInsetsInsetRect(rect, edgeInsets);
+ CGContextAddRect(ctx, insetRect);
+ CGContextEOFillPath(ctx);
} else {
+ BOOL didSet = NO;
+ CGPoint topLeft;
+ if (topRadius > 0 && leftRadius > 0) {
+ CGPoint points[2];
+ RCTEllipseGetIntersectionsWithLine(CGRectMake(leftWidth, topWidth, 2 * leftRadius, 2 * topRadius), CGPointMake(0, 0), CGPointMake(leftWidth, topWidth), points);
+ if (!isnan(points[1].x) && !isnan(points[1].y)) {
+ topLeft = points[1];
+ didSet = YES;
+ }
+ }
- // Clear main layer border
- self.layer.borderWidth = 0;
+ if (!didSet) {
+ topLeft = CGPointMake(leftWidth, topWidth);
+ }
- // Set up border layers
- for (RCTBorderSide side = 0; side < RCTBorderSideCount; side++) {
- [self updatePathForShapeLayerForSide:side];
+ didSet = NO;
+ CGPoint bottomLeft;
+ if (bottomRadius > 0 && leftRadius > 0) {
+ CGPoint points[2];
+ RCTEllipseGetIntersectionsWithLine(CGRectMake(leftWidth, (size.height - bottomWidth) - 2 * bottomRadius, 2 * leftRadius, 2 * bottomRadius), CGPointMake(0, size.height), CGPointMake(leftWidth, size.height - bottomWidth), points);
+ if (!isnan(points[1].x) && !isnan(points[1].y)) {
+ bottomLeft = points[1];
+ didSet = YES;
+ }
+ }
+
+ if (!didSet) {
+ bottomLeft = CGPointMake(leftWidth, size.height - bottomWidth);
+ }
+
+ didSet = NO;
+ CGPoint topRight;
+ if (topRadius > 0 && rightRadius > 0) {
+ CGPoint points[2];
+ RCTEllipseGetIntersectionsWithLine(CGRectMake((size.width - rightWidth) - 2 * rightRadius, topWidth, 2 * rightRadius, 2 * topRadius), CGPointMake(size.width, 0), CGPointMake(size.width - rightWidth, topWidth), points);
+ if (!isnan(points[0].x) && !isnan(points[0].y)) {
+ topRight = points[0];
+ didSet = YES;
+ }
+ }
+
+ if (!didSet) {
+ topRight = CGPointMake(size.width - rightWidth, topWidth);
+ }
+
+ didSet = NO;
+ CGPoint bottomRight;
+ if (bottomRadius > 0 && rightRadius > 0) {
+ CGPoint points[2];
+ RCTEllipseGetIntersectionsWithLine(CGRectMake((size.width - rightWidth) - 2 * rightRadius, (size.height - bottomWidth) - 2 * bottomRadius, 2 * rightRadius, 2 * bottomRadius), CGPointMake(size.width, size.height), CGPointMake(size.width - rightWidth, size.height - bottomWidth), points);
+ if (!isnan(points[0].x) && !isnan(points[0].y)) {
+ bottomRight = points[0];
+ didSet = YES;
+ }
+ }
+
+ if (!didSet) {
+ bottomRight = CGPointMake(size.width - rightWidth, size.height - bottomWidth);
+ }
+
+ // RIGHT
+ if (rightWidth > 0) {
+ CGContextSaveGState(ctx);
+
+ const CGPoint points[] = {
+ CGPointMake(size.width, 0),
+ topRight,
+ bottomRight,
+ CGPointMake(size.width, size.height),
+ };
+
+ CGContextSetFillColorWithColor(ctx, _borderRightColor ?: _borderColor);
+ CGContextAddLines(ctx, points, sizeof(points)/sizeof(*points));
+ CGContextFillPath(ctx);
+
+ CGContextRestoreGState(ctx);
+ }
+
+ // BOTTOM
+ if (bottomWidth > 0) {
+ CGContextSaveGState(ctx);
+
+ const CGPoint points[] = {
+ CGPointMake(0, size.height),
+ bottomLeft,
+ bottomRight,
+ CGPointMake(size.width, size.height),
+ };
+
+ CGContextSetFillColorWithColor(ctx, _borderBottomColor ?: _borderColor);
+ CGContextAddLines(ctx, points, sizeof(points)/sizeof(*points));
+ CGContextFillPath(ctx);
+
+ CGContextRestoreGState(ctx);
+ }
+
+ // LEFT
+ if (leftWidth > 0) {
+ CGContextSaveGState(ctx);
+
+ const CGPoint points[] = {
+ CGPointMake(0, 0),
+ topLeft,
+ bottomLeft,
+ CGPointMake(0, size.height),
+ };
+
+ CGContextSetFillColorWithColor(ctx, _borderLeftColor ?: _borderColor);
+ CGContextAddLines(ctx, points, sizeof(points)/sizeof(*points));
+ CGContextFillPath(ctx);
+
+ CGContextRestoreGState(ctx);
+ }
+
+ // TOP
+ if (topWidth > 0) {
+ CGContextSaveGState(ctx);
+
+ const CGPoint points[] = {
+ CGPointMake(0, 0),
+ topLeft,
+ topRight,
+ CGPointMake(size.width, 0),
+ };
+
+ CGContextSetFillColorWithColor(ctx, _borderTopColor ?: _borderColor);
+ CGContextAddLines(ctx, points, sizeof(points)/sizeof(*points));
+ CGContextFillPath(ctx);
+
+ CGContextRestoreGState(ctx);
}
}
+
+ UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
+ UIGraphicsEndImageContext();
+
+ *contentsCenter = CGRectMake(edgeInsets.left / size.width, edgeInsets.top / size.height, 1.0 / size.width, 1.0 / size.height);
+ return [image resizableImageWithCapInsets:edgeInsets];
}
-- (CGFloat)borderWidthForSide:(RCTBorderSide)side
+- (void)displayLayer:(CALayer *)layer
{
- return _borderWidths[side] ?: _borderWidth;
-}
+ CGRect contentsCenter;
+ UIImage *image = [self generateBorderImage:&contentsCenter];
-- (void)setBorderWidth:(CGFloat)width forSide:(RCTBorderSide)side
-{
- _borderWidths[side] = width;
- [self updateBorderLayers];
-}
+ if (RCTRunningInTestEnvironment()) {
+ const CGSize size = self.bounds.size;
+ UIGraphicsBeginImageContextWithOptions(size, NO, image.scale);
+ [image drawInRect:(CGRect){CGPointZero, size}];
+ image = UIGraphicsGetImageFromCurrentImageContext();
+ UIGraphicsEndImageContext();
-#define BORDER_WIDTH(SIDE) \
-- (CGFloat)border##SIDE##Width { return [self borderWidthForSide:RCTBorderSide##SIDE]; } \
-- (void)setBorder##SIDE##Width:(CGFloat)width { [self setBorderWidth:width forSide:RCTBorderSide##SIDE]; }
-
-BORDER_WIDTH(Top)
-BORDER_WIDTH(Right)
-BORDER_WIDTH(Bottom)
-BORDER_WIDTH(Left)
-
-- (CGColorRef)borderColorForSide:(RCTBorderSide)side
-{
- return _borderLayers[side].fillColor ?: self.layer.borderColor;
-}
-
-- (void)setBorderColor:(CGColorRef)color forSide:(RCTBorderSide)side
-{
- [self createShapeLayerIfNotExistsForSide:side].fillColor = color;
- [self updateBorderLayers];
-}
-
-#define BORDER_COLOR(SIDE) \
-- (CGColorRef)border##SIDE##Color { return [self borderColorForSide:RCTBorderSide##SIDE]; } \
-- (void)setBorder##SIDE##Color:(CGColorRef)color { [self setBorderColor:color forSide:RCTBorderSide##SIDE]; }
-
-BORDER_COLOR(Top)
-BORDER_COLOR(Right)
-BORDER_COLOR(Bottom)
-BORDER_COLOR(Left)
-
-- (void)setBorderWidth:(CGFloat)borderWidth
-{
- _borderWidth = borderWidth;
- for (RCTBorderSide side = 0; side < RCTBorderSideCount; side++) {
- _borderWidths[side] = borderWidth;
+ contentsCenter = CGRectMake(0, 0, 1, 1);
}
- [self updateBorderLayers];
+
+ layer.contents = (id)image.CGImage;
+ layer.contentsCenter = contentsCenter;
+ layer.contentsScale = image.scale;
+ layer.magnificationFilter = kCAFilterNearest;
}
-- (void)setBorderColor:(CGColorRef)borderColor
-{
- self.layer.borderColor = borderColor;
- for (RCTBorderSide side = 0; side < RCTBorderSideCount; side++) {
- _borderLayers[side].fillColor = borderColor;
+#pragma mark Border Color
+
+#define setBorderColor(side) \
+ - (void)setBorder##side##Color:(CGColorRef)border##side##Color \
+ { \
+ if (CGColorEqualToColor(_border##side##Color, border##side##Color)) { \
+ return; \
+ } \
+ _border##side##Color = border##side##Color; \
+ [self.layer setNeedsDisplay]; \
}
- [self updateBorderLayers];
-}
-- (CGColorRef)borderColor
-{
- return self.layer.borderColor;
-}
+setBorderColor()
+setBorderColor(Top)
+setBorderColor(Right)
+setBorderColor(Bottom)
+setBorderColor(Left)
+
+#pragma mark - Border Width
+
+#define setBorderWidth(side) \
+ - (void)setBorder##side##Width:(CGFloat)border##side##Width \
+ { \
+ if (_border##side##Width == border##side##Width) { \
+ return; \
+ } \
+ _border##side##Width = border##side##Width; \
+ [self.layer setNeedsDisplay]; \
+ }
+
+setBorderWidth()
+setBorderWidth(Top)
+setBorderWidth(Right)
+setBorderWidth(Bottom)
+setBorderWidth(Left)
@end
+
+static void RCTPathAddEllipticArc(CGMutablePathRef path, const CGAffineTransform *m, CGFloat x, CGFloat y, CGFloat xRadius, CGFloat yRadius, CGFloat startAngle, CGFloat endAngle, bool clockwise)
+{
+ CGFloat xScale = 1, yScale = 1, radius = 0;
+ if (xRadius != 0) {
+ xScale = 1;
+ yScale = yRadius / xRadius;
+ radius = xRadius;
+ } else if (yRadius != 0) {
+ xScale = xRadius / yRadius;
+ yScale = 1;
+ radius = yRadius;
+ }
+
+ CGAffineTransform t = CGAffineTransformMakeTranslation(x, y);
+ t = CGAffineTransformScale(t, xScale, yScale);
+ if (m != NULL) {
+ t = CGAffineTransformConcat(t, *m);
+ }
+
+ CGPathAddArc(path, &t, 0, 0, radius, startAngle, endAngle, clockwise);
+}
+
+static CGPathRef RCTPathCreateWithRoundedRect(CGRect rect, CGFloat topLeftRadiusX, CGFloat topLeftRadiusY, CGFloat topRightRadiusX, CGFloat topRightRadiusY, CGFloat bottomLeftRadiusX, CGFloat bottomLeftRadiusY, CGFloat bottomRightRadiusX, CGFloat bottomRightRadiusY, const CGAffineTransform *transform)
+{
+ const CGFloat minX = CGRectGetMinX(rect);
+ const CGFloat minY = CGRectGetMinY(rect);
+ const CGFloat maxX = CGRectGetMaxX(rect);
+ const CGFloat maxY = CGRectGetMaxY(rect);
+
+ CGMutablePathRef path = CGPathCreateMutable();
+ RCTPathAddEllipticArc(path, transform, minX + topLeftRadiusX, minY + topLeftRadiusY, topLeftRadiusX, topLeftRadiusY, M_PI, 3 * M_PI_2, false);
+ RCTPathAddEllipticArc(path, transform, maxX - topRightRadiusX, minY + topRightRadiusY, topRightRadiusX, topRightRadiusY, 3 * M_PI_2, 0, false);
+ RCTPathAddEllipticArc(path, transform, maxX - bottomRightRadiusX, maxY - bottomRightRadiusY, bottomRightRadiusX, bottomRightRadiusY, 0, M_PI_2, false);
+ RCTPathAddEllipticArc(path, transform, minX + bottomLeftRadiusX, maxY - bottomLeftRadiusY, bottomLeftRadiusX, bottomLeftRadiusY, M_PI_2, M_PI, false);
+ CGPathCloseSubpath(path);
+ return path;
+}
+
+static BOOL RCTEllipseGetIntersectionsWithLine(CGRect ellipseBoundingRect, CGPoint p1, CGPoint p2, CGPoint intersections[2])
+{
+ const CGFloat ellipseCenterX = CGRectGetMidX(ellipseBoundingRect);
+ const CGFloat ellipseCenterY = CGRectGetMidY(ellipseBoundingRect);
+
+ // ellipseBoundingRect.origin.x -= ellipseCenterX;
+ // ellipseBoundingRect.origin.y -= ellipseCenterY;
+
+ p1.x -= ellipseCenterX;
+ p1.y -= ellipseCenterY;
+
+ p2.x -= ellipseCenterX;
+ p2.y -= ellipseCenterY;
+
+ const CGFloat m = (p2.y - p1.y) / (p2.x - p1.x);
+ const CGFloat a = ellipseBoundingRect.size.width / 2;
+ const CGFloat b = ellipseBoundingRect.size.height / 2;
+ const CGFloat c = p1.y - m * p1.x;
+ const CGFloat A = (b * b + a * a * m * m);
+ const CGFloat B = 2 * a * a * c * m;
+ const CGFloat D = sqrt((a * a * (b * b - c * c)) / A + pow(B / (2 * A), 2));
+
+ const CGFloat x_ = -B / (2 * A);
+ const CGFloat x1 = x_ + D;
+ const CGFloat x2 = x_ - D;
+ const CGFloat y1 = m * x1 + c;
+ const CGFloat y2 = m * x2 + c;
+
+ intersections[0] = CGPointMake(x1 + ellipseCenterX, y1 + ellipseCenterY);
+ intersections[1] = CGPointMake(x2 + ellipseCenterX, y2 + ellipseCenterY);
+ return YES;
+}