From 826b56a41be62ab8a558a2a31b161ecc7577e1fe Mon Sep 17 00:00:00 2001 From: Alex Akers Date: Mon, 27 Apr 2015 03:50:07 -0700 Subject: [PATCH] [React Native] Update RCTView border implementation --- Examples/UIExplorer/BorderExample.js | 20 +- Examples/UIExplorer/UIExplorerBlock.js | 4 + React/Base/RCTUtils.h | 3 + React/Base/RCTUtils.m | 10 + React/Views/RCTView.h | 7 - React/Views/RCTView.m | 510 +++++++++++++++++-------- 6 files changed, 391 insertions(+), 163 deletions(-) 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; +}