1// 2// NSBezierPath+MCAdditions.m 3// 4// Created by Sean Patrick O'Brien on 4/1/08. 5// Copyright 2008 MolokoCacao. All rights reserved. 6// 7 8#import "NSBezierPath+MCAdditions.h" 9 10#import "third_party/GTM/AppKit/GTMNSBezierPath+CGPath.h" 11 12// remove/comment out this line of you don't want to use undocumented functions 13#define MCBEZIER_USE_PRIVATE_FUNCTION 14 15#ifdef MCBEZIER_USE_PRIVATE_FUNCTION 16extern CGPathRef CGContextCopyPath(CGContextRef context); 17#endif 18 19static void CGPathCallback(void *info, const CGPathElement *element) 20{ 21 NSBezierPath *path = info; 22 CGPoint *points = element->points; 23 24 switch (element->type) { 25 case kCGPathElementMoveToPoint: 26 { 27 [path moveToPoint:NSMakePoint(points[0].x, points[0].y)]; 28 break; 29 } 30 case kCGPathElementAddLineToPoint: 31 { 32 [path lineToPoint:NSMakePoint(points[0].x, points[0].y)]; 33 break; 34 } 35 case kCGPathElementAddQuadCurveToPoint: 36 { 37 // NOTE: This is untested. 38 NSPoint currentPoint = [path currentPoint]; 39 NSPoint interpolatedPoint = NSMakePoint((currentPoint.x + 2*points[0].x) / 3, (currentPoint.y + 2*points[0].y) / 3); 40 [path curveToPoint:NSMakePoint(points[1].x, points[1].y) controlPoint1:interpolatedPoint controlPoint2:interpolatedPoint]; 41 break; 42 } 43 case kCGPathElementAddCurveToPoint: 44 { 45 [path curveToPoint:NSMakePoint(points[2].x, points[2].y) controlPoint1:NSMakePoint(points[0].x, points[0].y) controlPoint2:NSMakePoint(points[1].x, points[1].y)]; 46 break; 47 } 48 case kCGPathElementCloseSubpath: 49 { 50 [path closePath]; 51 break; 52 } 53 } 54} 55 56@implementation NSBezierPath (MCAdditions) 57 58+ (NSBezierPath *)bezierPathWithCGPath:(CGPathRef)pathRef 59{ 60 NSBezierPath *path = [NSBezierPath bezierPath]; 61 CGPathApply(pathRef, path, CGPathCallback); 62 63 return path; 64} 65 66- (NSBezierPath *)pathWithStrokeWidth:(CGFloat)strokeWidth 67{ 68#ifdef MCBEZIER_USE_PRIVATE_FUNCTION 69 NSBezierPath *path = [self copy]; 70 CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort]; 71 CGPathRef pathRef = [path gtm_CGPath]; 72 [path release]; 73 74 CGContextSaveGState(context); 75 76 CGContextBeginPath(context); 77 CGContextAddPath(context, pathRef); 78 CGContextSetLineWidth(context, strokeWidth); 79 CGContextReplacePathWithStrokedPath(context); 80 CGPathRef strokedPathRef = CGContextCopyPath(context); 81 CGContextBeginPath(context); 82 NSBezierPath *strokedPath = [NSBezierPath bezierPathWithCGPath:strokedPathRef]; 83 84 CGContextRestoreGState(context); 85 86 CFRelease(pathRef); 87 CFRelease(strokedPathRef); 88 89 return strokedPath; 90#else 91 return nil; 92#endif // MCBEZIER_USE_PRIVATE_FUNCTION 93} 94 95- (void)fillWithInnerShadow:(NSShadow *)shadow 96{ 97 [NSGraphicsContext saveGraphicsState]; 98 99 NSSize offset = shadow.shadowOffset; 100 NSSize originalOffset = offset; 101 CGFloat radius = shadow.shadowBlurRadius; 102 NSRect bounds = NSInsetRect(self.bounds, -(ABS(offset.width) + radius), -(ABS(offset.height) + radius)); 103 104 // The context's user transform isn't automatically applied to shadow offsets. 105 offset.height += bounds.size.height; 106 shadow.shadowOffset = offset; 107 NSAffineTransform *transform = [NSAffineTransform transform]; 108 if ([[NSGraphicsContext currentContext] isFlipped]) 109 [transform translateXBy:0 yBy:bounds.size.height]; 110 else 111 [transform translateXBy:0 yBy:-bounds.size.height]; 112 113 NSBezierPath *drawingPath = [NSBezierPath bezierPathWithRect:bounds]; 114 [drawingPath setWindingRule:NSEvenOddWindingRule]; 115 [drawingPath appendBezierPath:self]; 116 [drawingPath transformUsingAffineTransform:transform]; 117 118 [self addClip]; 119 [shadow set]; 120 [[NSColor blackColor] set]; 121 [drawingPath fill]; 122 123 shadow.shadowOffset = originalOffset; 124 125 [NSGraphicsContext restoreGraphicsState]; 126} 127 128- (void)drawBlurWithColor:(NSColor *)color radius:(CGFloat)radius 129{ 130 NSRect bounds = NSInsetRect(self.bounds, -radius, -radius); 131 NSShadow *shadow = [[NSShadow alloc] init]; 132 shadow.shadowOffset = NSMakeSize(0, bounds.size.height); 133 shadow.shadowBlurRadius = radius; 134 shadow.shadowColor = color; 135 NSBezierPath *path = [self copy]; 136 NSAffineTransform *transform = [NSAffineTransform transform]; 137 if ([[NSGraphicsContext currentContext] isFlipped]) 138 [transform translateXBy:0 yBy:bounds.size.height]; 139 else 140 [transform translateXBy:0 yBy:-bounds.size.height]; 141 [path transformUsingAffineTransform:transform]; 142 143 [NSGraphicsContext saveGraphicsState]; 144 145 [shadow set]; 146 [[NSColor blackColor] set]; 147 NSRectClip(bounds); 148 [path fill]; 149 150 [NSGraphicsContext restoreGraphicsState]; 151 152 [path release]; 153 [shadow release]; 154} 155 156// Credit for the next two methods goes to Matt Gemmell 157- (void)strokeInside 158{ 159 /* Stroke within path using no additional clipping rectangle. */ 160 [self strokeInsideWithinRect:NSZeroRect]; 161} 162 163- (void)strokeInsideWithinRect:(NSRect)clipRect 164{ 165 NSGraphicsContext *thisContext = [NSGraphicsContext currentContext]; 166 float lineWidth = [self lineWidth]; 167 168 /* Save the current graphics context. */ 169 [thisContext saveGraphicsState]; 170 171 /* Double the stroke width, since -stroke centers strokes on paths. */ 172 [self setLineWidth:(lineWidth * 2.0)]; 173 174 /* Clip drawing to this path; draw nothing outwith the path. */ 175 [self setClip]; 176 177 /* Further clip drawing to clipRect, usually the view's frame. */ 178 if (clipRect.size.width > 0.0 && clipRect.size.height > 0.0) { 179 [NSBezierPath clipRect:clipRect]; 180 } 181 182 /* Stroke the path. */ 183 [self stroke]; 184 185 /* Restore the previous graphics context. */ 186 [thisContext restoreGraphicsState]; 187 [self setLineWidth:lineWidth]; 188} 189 190@end 191