mirror of
https://github.com/status-im/react-native.git
synced 2025-02-04 13:44:04 +00:00
[React Native] Update RCTView border implementation
This commit is contained in:
parent
136431cc2f
commit
826b56a41b
@ -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]} />;
|
||||||
|
}
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -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);
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user