[React Native] Update RCTView border implementation

This commit is contained in:
Alex Akers 2015-04-27 03:50:07 -07:00
parent 136431cc2f
commit 826b56a41b
6 changed files with 391 additions and 163 deletions

View File

@ -57,6 +57,17 @@ var styles = StyleSheet.create({
borderLeftWidth: 40, borderLeftWidth: 40,
borderLeftColor: 'blue', borderLeftColor: 'blue',
}, },
border5: {
borderRadius: 50,
borderTopWidth: 10,
borderTopColor: 'red',
borderRightWidth: 20,
borderRightColor: 'yellow',
borderBottomWidth: 30,
borderBottomColor: 'green',
borderLeftWidth: 40,
borderLeftColor: 'blue',
},
}); });
exports.title = 'Border'; exports.title = 'Border';
@ -71,7 +82,7 @@ exports.examples = [
}, },
{ {
title: 'Equal-Width / Same-Color', title: 'Equal-Width / Same-Color',
description: 'borderWidth & borderColor', description: 'borderWidth & borderColor & borderRadius',
render() { render() {
return <View style={[styles.box, styles.borderRadius]} />; return <View style={[styles.box, styles.borderRadius]} />;
} }
@ -97,4 +108,11 @@ exports.examples = [
return <View style={[styles.box, styles.border4]} />; return <View style={[styles.box, styles.border4]} />;
} }
}, },
{
title: 'Custom Borders',
description: 'border*Width & border*Color',
render() {
return <View style={[styles.box, styles.border5]} />;
}
},
]; ];

View File

@ -70,6 +70,7 @@ var styles = StyleSheet.create({
}, },
titleContainer: { titleContainer: {
borderWidth: 0.5, borderWidth: 0.5,
borderRadius: 2.5,
borderColor: '#d6d7da', borderColor: '#d6d7da',
backgroundColor: '#f6f7f8', backgroundColor: '#f6f7f8',
paddingHorizontal: 10, paddingHorizontal: 10,
@ -78,8 +79,10 @@ var styles = StyleSheet.create({
titleRow: { titleRow: {
flexDirection: 'row', flexDirection: 'row',
justifyContent: 'space-between', justifyContent: 'space-between',
backgroundColor: 'transparent',
}, },
titleText: { titleText: {
backgroundColor: 'transparent',
fontSize: 14, fontSize: 14,
fontWeight: '500', fontWeight: '500',
}, },
@ -97,6 +100,7 @@ var styles = StyleSheet.create({
height: 8, height: 8,
}, },
children: { children: {
backgroundColor: 'transparent',
padding: 10, padding: 10,
} }
}); });

View File

@ -46,3 +46,6 @@ RCT_EXTERN BOOL RCTClassOverridesInstanceMethod(Class cls, SEL selector);
// TODO(#6472857): create NSErrors and automatically convert them over the bridge. // TODO(#6472857): create NSErrors and automatically convert them over the bridge.
RCT_EXTERN NSDictionary *RCTMakeError(NSString *message, id toStringify, NSDictionary *extraData); RCT_EXTERN NSDictionary *RCTMakeError(NSString *message, id toStringify, NSDictionary *extraData);
RCT_EXTERN NSDictionary *RCTMakeAndLogError(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);

View File

@ -201,3 +201,13 @@ NSDictionary *RCTMakeAndLogError(NSString *message, id toStringify, NSDictionary
RCTLogError(@"\nError: %@", error); RCTLogError(@"\nError: %@", error);
return 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;
}

View File

@ -13,13 +13,6 @@
#import "RCTPointerEvents.h" #import "RCTPointerEvents.h"
typedef NS_ENUM(NSInteger, RCTBorderSide) {
RCTBorderSideTop,
RCTBorderSideRight,
RCTBorderSideBottom,
RCTBorderSideLeft
};
@protocol RCTAutoInsetsProtocol; @protocol RCTAutoInsetsProtocol;
@interface RCTView : UIView @interface RCTView : UIView

View File

@ -12,9 +12,10 @@
#import "RCTAutoInsetsProtocol.h" #import "RCTAutoInsetsProtocol.h"
#import "RCTConvert.h" #import "RCTConvert.h"
#import "RCTLog.h" #import "RCTLog.h"
#import "RCTUtils.h"
#import "UIView+React.h" #import "UIView+React.h"
static const RCTBorderSide RCTBorderSideCount = 4; static void *RCTViewCornerRadiusKVOContext = &RCTViewCornerRadiusKVOContext;
static UIView *RCTViewHitTest(UIView *view, CGPoint point, UIEvent *event) static UIView *RCTViewHitTest(UIView *view, CGPoint point, UIEvent *event)
{ {
@ -30,6 +31,10 @@ static UIView *RCTViewHitTest(UIView *view, CGPoint point, UIEvent *event)
return nil; 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) @implementation UIView (RCTViewUnmounting)
- (void)react_remountAllSubviews - (void)react_remountAllSubviews
@ -107,8 +112,39 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
@implementation RCTView @implementation RCTView
{ {
NSMutableArray *_reactSubviews; NSMutableArray *_reactSubviews;
CAShapeLayer *_borderLayers[RCTBorderSideCount]; UIColor *_backgroundColor;
CGFloat _borderWidths[RCTBorderSideCount]; }
- (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 - (NSString *)accessibilityLabel
@ -381,189 +417,353 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
if (_reactSubviews) { if (_reactSubviews) {
[self updateClippedSubviews]; [self updateClippedSubviews];
} }
for (RCTBorderSide side = 0; side < RCTBorderSideCount; side++) {
if (_borderLayers[side]) [self updatePathForShapeLayerForSide:side];
}
} }
- (void)layoutSublayersOfLayer:(CALayer *)layer #pragma mark - Borders
{
[super layoutSublayersOfLayer:layer];
const CGRect bounds = layer.bounds; - (UIColor *)backgroundColor
for (RCTBorderSide side = 0; side < RCTBorderSideCount; side++) { {
_borderLayers[side].frame = bounds; return _backgroundColor;
}
} }
- (BOOL)getTrapezoidPoints:(CGPoint[4])outPoints forSide:(RCTBorderSide)side - (void)setBackgroundColor:(UIColor *)backgroundColor
{ {
const CGRect bounds = self.layer.bounds; if ([_backgroundColor isEqual:backgroundColor]) {
const CGFloat minX = CGRectGetMinX(bounds); return;
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;
} }
_backgroundColor = backgroundColor;
return YES; [self.layer setNeedsDisplay];
} }
- (CAShapeLayer *)createShapeLayerIfNotExistsForSide:(RCTBorderSide)side - (UIImage *)generateBorderImage:(out CGRect *)contentsCenter
{ {
CAShapeLayer *borderLayer = _borderLayers[side]; const CGFloat maxRadius = MIN(self.bounds.size.height, self.bounds.size.width) / 2.0;
if (!borderLayer) { const CGFloat radius = MAX(0, MIN(self.layer.cornerRadius, maxRadius));
borderLayer = [CAShapeLayer layer];
borderLayer.fillColor = self.layer.borderColor; const CGFloat borderWidth = MAX(0, _borderWidth);
[self.layer addSublayer:borderLayer]; const CGFloat topWidth = _borderTopWidth >= 0 ? _borderTopWidth : borderWidth;
_borderLayers[side] = borderLayer; 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 CGContextAddPath(ctx, path);
{
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;
CGPathRelease(path); CGPathRelease(path);
}
- (void)updateBorderLayers if (radius > 0 && topWidth > 0 && rightWidth > 0 && bottomWidth > 0 && leftWidth > 0) {
{ const UIEdgeInsets insetEdgeInsets = UIEdgeInsetsMake(topWidth, leftWidth, bottomWidth, rightWidth);
BOOL widthsAndColorsSame = YES; const CGRect insetRect = UIEdgeInsetsInsetRect(rect, insetEdgeInsets);
CGFloat width = _borderWidths[0]; CGPathRef insetPath = RCTPathCreateWithRoundedRect(insetRect, leftRadius, topRadius, rightRadius, topRadius, leftRadius, bottomRadius, rightRadius, bottomRadius, NULL);
CGColorRef color = _borderLayers[0].fillColor; CGContextAddPath(ctx, insetPath);
for (RCTBorderSide side = 1; side < RCTBorderSideCount; side++) { CGPathRelease(insetPath);
CAShapeLayer *layer = _borderLayers[side];
if (_borderWidths[side] != width || (layer && !CGColorEqualToColor(layer.fillColor, color))) {
widthsAndColorsSame = NO;
break;
}
} }
if (widthsAndColorsSame) {
// Set main layer border CGContextEOClip(ctx);
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;
}
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 { } 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 if (!didSet) {
self.layer.borderWidth = 0; topLeft = CGPointMake(leftWidth, topWidth);
}
// Set up border layers didSet = NO;
for (RCTBorderSide side = 0; side < RCTBorderSideCount; side++) { CGPoint bottomLeft;
[self updatePathForShapeLayerForSide:side]; 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 if (RCTRunningInTestEnvironment()) {
{ const CGSize size = self.bounds.size;
_borderWidths[side] = width; UIGraphicsBeginImageContextWithOptions(size, NO, image.scale);
[self updateBorderLayers]; [image drawInRect:(CGRect){CGPointZero, size}];
} image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
#define BORDER_WIDTH(SIDE) \ contentsCenter = CGRectMake(0, 0, 1, 1);
- (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;
} }
[self updateBorderLayers];
layer.contents = (id)image.CGImage;
layer.contentsCenter = contentsCenter;
layer.contentsScale = image.scale;
layer.magnificationFilter = kCAFilterNearest;
} }
- (void)setBorderColor:(CGColorRef)borderColor #pragma mark Border Color
{
self.layer.borderColor = borderColor; #define setBorderColor(side) \
for (RCTBorderSide side = 0; side < RCTBorderSideCount; side++) { - (void)setBorder##side##Color:(CGColorRef)border##side##Color \
_borderLayers[side].fillColor = borderColor; { \
if (CGColorEqualToColor(_border##side##Color, border##side##Color)) { \
return; \
} \
_border##side##Color = border##side##Color; \
[self.layer setNeedsDisplay]; \
} }
[self updateBorderLayers];
}
- (CGColorRef)borderColor setBorderColor()
{ setBorderColor(Top)
return self.layer.borderColor; 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 @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;
}