diff --git a/Source/ASDisplayNode.h b/Source/ASDisplayNode.h index ed12d863..fae25a58 100644 --- a/Source/ASDisplayNode.h +++ b/Source/ASDisplayNode.h @@ -646,6 +646,7 @@ extern NSInteger const ASDefaultDrawingPriority; @property (nonatomic, assign) CGPoint anchorPoint; // default={0.5, 0.5} @property (nonatomic, assign) CGFloat zPosition; // default=0.0 @property (nonatomic, assign) CGFloat cornerRadius; // default=0.0 +@property (nonatomic, assign) CGFloat layerCornerRadius; // default=0.0 @property (nonatomic, assign) CATransform3D transform; // default=CATransform3DIdentity @property (nonatomic, assign) CATransform3D subnodeTransform; // default=CATransform3DIdentity diff --git a/Source/ASDisplayNode.mm b/Source/ASDisplayNode.mm index 480666d7..4e70f166 100644 --- a/Source/ASDisplayNode.mm +++ b/Source/ASDisplayNode.mm @@ -63,7 +63,7 @@ NSInteger const ASDefaultDrawingPriority = ASDefaultTransactionPriority; // We have to forward declare the protocol as this place otherwise it will not compile compiling with an Base SDK < iOS 10 @protocol CALayerDelegate; -@interface ASDisplayNode () +@interface ASDisplayNode () /** * See ASDisplayNodeInternal.h for ivars @@ -930,6 +930,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) if (loaded) { ASPerformBlockOnMainThread(^{ [self layout]; + [self _layoutClipCornersIfNeeded]; [self layoutDidFinish]; }); } @@ -1010,8 +1011,6 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) ASDisplayNodeLogEvent(self, @"calculatedSize: %@", NSStringFromCGSize(size)); return [ASLayout layoutWithLayoutElement:self size:ASSizeRangeClamp(constrainedSize, size) sublayouts:nil]; } - - [self _layoutClipCornersIfNeeded]; // Size calcualtion with layout elements BOOL measureLayoutSpec = _measurementOptions & ASDisplayNodePerformanceMeasurementOptionLayoutSpec; @@ -1512,7 +1511,7 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) if (isRight == YES) { CGContextTranslateCTM(ctx, -radius + 1, 0); } - if (isTop == NO) { + if (isTop == YES) { CGContextTranslateCTM(ctx, 0, -radius + 1); } UIBezierPath *roundedRect = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, radius * 2, radius * 2) cornerRadius:radius]; diff --git a/Source/Private/ASDisplayNode+AsyncDisplay.mm b/Source/Private/ASDisplayNode+AsyncDisplay.mm index 4d0ee8fb..beef3911 100644 --- a/Source/Private/ASDisplayNode+AsyncDisplay.mm +++ b/Source/Private/ASDisplayNode+AsyncDisplay.mm @@ -161,6 +161,8 @@ isCancelledBlock:(asdisplaynode_iscancelled_block_t)isCancelledBlock rasterizing:(BOOL)rasterizing { + ASDisplayNodeAssertMainThread(); + asyncdisplaykit_async_transaction_operation_block_t displayBlock = nil; ASDisplayNodeFlags flags; @@ -183,6 +185,8 @@ BOOL opaque = self.opaque; CGRect bounds = self.bounds; UIColor *backgroundColor = self.backgroundColor; + CGColorRef borderColor = self.borderColor; + CGFloat borderWidth = self.borderWidth; CGFloat contentsScaleForDisplay = _contentsScaleForDisplay; __instanceLock__.unlock(); @@ -236,22 +240,10 @@ CGContextRef currentContext = UIGraphicsGetCurrentContext(); UIImage *image = nil; - - ASDisplayNodeContextModifier willDisplayNodeContentWithRenderingContext = nil; - ASDisplayNodeContextModifier didDisplayNodeContentWithRenderingContext = nil; - if (currentContext) { - __instanceLock__.lock(); - willDisplayNodeContentWithRenderingContext = _willDisplayNodeContentWithRenderingContext; - didDisplayNodeContentWithRenderingContext = _didDisplayNodeContentWithRenderingContext; - __instanceLock__.unlock(); - } - - + // For -display methods, we don't have a context, and thus will not call the _willDisplayNodeContentWithRenderingContext or // _didDisplayNodeContentWithRenderingContext blocks. It's up to the implementation of -display... to do what it needs. - if (willDisplayNodeContentWithRenderingContext != nil) { - willDisplayNodeContentWithRenderingContext(currentContext, drawParameters); - } + [self __willDisplayNodeContentWithRenderingContext:currentContext drawParameters:drawParameters]; if (usesImageDisplay) { // If we are using a display method, we'll get an image back directly. image = [self.class displayWithParameters:drawParameters isCancelled:isCancelledBlock]; @@ -259,9 +251,7 @@ [self.class drawRect:bounds withParameters:drawParameters isCancelled:isCancelledBlock isRasterizing:rasterizing]; } - if (didDisplayNodeContentWithRenderingContext != nil) { - didDisplayNodeContentWithRenderingContext(currentContext, drawParameters); - } + [self __didDisplayNodeContentWithRenderingContext:currentContext image:&image drawParameters:drawParameters backgroundColor:backgroundColor borderWidth:borderWidth borderColor:borderColor]; if (shouldCreateGraphicsContext) { CHECK_CANCELLED_AND_RETURN_NIL( UIGraphicsEndImageContext(); ); @@ -290,113 +280,92 @@ return displayBlock; } -// -//- (void)setRenderingContextBlocks -//{ -// CGFloat contentsScale = self.contentsScale; -// -// weakify(self); -// self.willDisplayNodeContentWithRenderingContext = ^(CGContextRef context) { -// strongify(self); -// CGRect bounds = CGContextGetClipBoundingBox(context); -// -// if (self.copiedPlaceholderColor) { -// UIBezierPath *placeholderRect = [UIBezierPath bezierPathWithRect:bounds]; -// [self.copiedPlaceholderColor setFill]; -// [placeholderRect fill]; -// } -// }; -// -// // After the image has been drawn into the drawing context -// self.didDisplayNodeContentWithRenderingContext = ^(CGContextRef context) { -// strongify(self); -// [PIImageNode darkenContextIfNecessary:context]; -// -// if (self.snapshotDecodedContext) { -// self.decodedContextImage = UIGraphicsGetImageFromCurrentImageContext(); -// } -// if (self.washColor) { -// CGRect bounds = CGContextGetClipBoundingBox(context); -// [self.washColor setFill]; -// CGContextFillRect(context, CGRectMake(0, 0, bounds.size.width, bounds.size.height)); -// } -// [self drawRoundedCornerOverlayWithContext:context contentsScale:contentsScale]; -// }; -//} -//- (void)drawRoundedCornerOverlayWithContext:(CGContextRef)context contentsScale:(CGFloat)contentsScale -//{ -//} +- (void)drawRoundedCornerOverlayWithContext:(CGContextRef)context contentsScale:(CGFloat)contentsScale +{ +} -- (void)__willDisplayNodeContentWithRenderingContext:(CGContextRef)context +- (void)__willDisplayNodeContentWithRenderingContext:(CGContextRef)context drawParameters:(id _Nullable)drawParameters { - if (context) { -// __instanceLock__.lock(); -// ASCornerRoundingType cornerRoundingType = _cornerRoundingType; -// CGFloat cornerRadius = _cornerRadius; -// __instanceLock__.unlock(); -// -// if (cornerRoundingType == ASCornerRoundingTypePrecomposited && cornerRadius > 0.0) { -// ASDisplayNodeAssert(context == UIGraphicsGetCurrentContext(), @"context is expected to be pushed on UIGraphics stack %@", self); -// // TODO: This clip path should be removed if we are rasterizing. -// CGRect boundingBox = CGContextGetClipBoundingBox(context); -// [[UIBezierPath bezierPathWithRoundedRect:boundingBox cornerRadius:cornerRadius] addClip]; -// } + __instanceLock__.lock(); + ASCornerRoundingType cornerRoundingType = _cornerRoundingType; + CGFloat cornerRadius = _cornerRadius; + ASDisplayNodeContextModifier willDisplayNodeContentWithRenderingContext = _willDisplayNodeContentWithRenderingContext; + __instanceLock__.unlock(); + + if (cornerRoundingType == ASCornerRoundingTypePrecomposited && cornerRadius > 0.0) { + ASDisplayNodeAssert(context == UIGraphicsGetCurrentContext(), @"context is expected to be pushed on UIGraphics stack %@", self); + // TODO: This clip path should be removed if we are rasterizing. + CGRect boundingBox = CGContextGetClipBoundingBox(context); + [[UIBezierPath bezierPathWithRoundedRect:boundingBox cornerRadius:cornerRadius] addClip]; + } - if (_willDisplayNodeContentWithRenderingContext) { - _willDisplayNodeContentWithRenderingContext(context); + if (willDisplayNodeContentWithRenderingContext) { + willDisplayNodeContentWithRenderingContext(context, drawParameters); } } } -- (void)__didDisplayNodeContentWithRenderingContext:(CGContextRef)context backgroundColor:(UIColor *)backgroundColor +- (void)__didDisplayNodeContentWithRenderingContext:(CGContextRef)context image:(UIImage **)image drawParameters:(id _Nullable)drawParameters backgroundColor:(UIColor *)backgroundColor borderWidth:(CGFloat)borderWidth borderColor:(CGColorRef)borderColor { - if (context == NULL) { + if (context == NULL && *image == NULL) { return; } - if (_didDisplayNodeContentWithRenderingContext) { - _didDisplayNodeContentWithRenderingContext(context); - } - __instanceLock__.lock(); - ASCornerRoundingType cornerRoundingType = _cornerRoundingType; - CGFloat cornerRadius = _cornerRadius; - CGFloat contentsScale = _contentsScaleForDisplay; + ASCornerRoundingType cornerRoundingType = _cornerRoundingType; + CGFloat cornerRadius = _cornerRadius; + CGFloat contentsScale = _contentsScaleForDisplay; + ASDisplayNodeContextModifier didDisplayNodeContentWithRenderingContext = _didDisplayNodeContentWithRenderingContext; __instanceLock__.unlock(); - if (cornerRoundingType == ASCornerRoundingTypePrecomposited && cornerRadius > 0.0) { - ASDisplayNodeAssert(context == UIGraphicsGetCurrentContext(), @"context is expected to be pushed on UIGraphics stack %@", self); + if (context != NULL) { + if (didDisplayNodeContentWithRenderingContext) { + didDisplayNodeContentWithRenderingContext(context, drawParameters); + } + } - CGRect bounds = CGContextGetClipBoundingBox(context); + if (cornerRoundingType == ASCornerRoundingTypePrecomposited && cornerRadius > 0.0f) { + CGRect bounds = CGRectZero; + if (context == NULL) { + bounds = (CGRect){ CGPointZero, [*image size] }; + CGFloat white = 0.0f, alpha = 0.0f; + [backgroundColor getWhite:&white alpha:&alpha]; + UIGraphicsBeginImageContextWithOptions(bounds.size, (alpha == 1.0f), contentsScale); + [*image drawInRect:bounds]; + } else { + bounds = CGContextGetClipBoundingBox(context); + } - //we need to outset the bounds by some amount. 1 might not be enough so lets just use 5. - UIBezierPath *roundedHole = [UIBezierPath bezierPathWithRect:CGRectInset(bounds, -5, -5)]; + ASDisplayNodeAssert(UIGraphicsGetCurrentContext(), @"context is expected to be pushed on UIGraphics stack %@", self); + + UIBezierPath *roundedHole = [UIBezierPath bezierPathWithRect:bounds]; [roundedHole appendPath:[UIBezierPath bezierPathWithRoundedRect:bounds cornerRadius:cornerRadius * contentsScale]]; roundedHole.usesEvenOddFillRule = YES; UIBezierPath *roundedPath = nil; -// if (self.borderWidth != 0) { // optimization: Don't create roundedPath and stroke if borderWidth is 0 -// CGFloat strokeThickness = self.borderWidth * contentsScale; -// CGFloat strokeInset = (strokeThickness+1)/2-1; -// roundedPath = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(bounds, strokeInset, strokeInset) -// cornerRadius:self.cornerRadius * contentsScale]; -// roundedPath.lineWidth = strokeThickness; -// [[UIColor colorWithCGColor: self.borderColor] setStroke]; -// } + if (borderWidth > 0.0f) { // Don't create roundedPath and stroke if borderWidth is 0.0 + CGFloat strokeThickness = borderWidth * contentsScale; + CGFloat strokeInset = ((strokeThickness + 1.0f) / 2.0f) - 1.0f; + roundedPath = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(bounds, strokeInset, strokeInset) + cornerRadius:_cornerRadius * contentsScale]; + roundedPath.lineWidth = strokeThickness; + [[UIColor colorWithCGColor:borderColor] setStroke]; + } - if (backgroundColor && ![backgroundColor isEqual:[UIColor clearColor]]) { - [backgroundColor setFill]; - [roundedHole fill]; - [roundedPath stroke]; //Won't do anything if borderWidth is 0 and roundedPath is nil. - } else { - [roundedHole fillWithBlendMode:kCGBlendModeClear alpha:1.0]; - [roundedPath strokeWithBlendMode:kCGBlendModeNormal alpha:1.0]; //Won't do anything if borderWidth is 0 - //and roundedPath is nil. + // Punch out the corners by copying the backgroundColor over them. + // This works for everything from clearColor to opaque colors. + [backgroundColor setFill]; + [roundedHole fillWithBlendMode:kCGBlendModeCopy alpha:1.0f]; + + [roundedPath stroke]; // Won't do anything if borderWidth is 0 and roundedPath is nil. + + if (*image) { + *image = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); } } - } - (void)displayAsyncLayer:(_ASDisplayLayer *)asyncLayer asynchronously:(BOOL)asynchronously diff --git a/Source/Private/ASDisplayNodeInternal.h b/Source/Private/ASDisplayNodeInternal.h index 7555e093..bbdd9d3b 100644 --- a/Source/Private/ASDisplayNodeInternal.h +++ b/Source/Private/ASDisplayNodeInternal.h @@ -175,6 +175,10 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo // keeps track of nodes/subnodes that have not finished display, used with placeholders ASWeakSet *_pendingDisplayNodes; + + CGFloat _cornerRadius; + ASCornerRoundingType _cornerRoundingType; + CALayer *_clipCornerLayers[4]; ASDisplayNodeContextModifier _willDisplayNodeContentWithRenderingContext; ASDisplayNodeContextModifier _didDisplayNodeContentWithRenderingContext;