blob: 3b006ed01126fab940d77ffbf07a81aa59559b0c [file] [log] [blame]
//
// NSBezierPath+MCAdditions.m
//
// Created by Sean Patrick O'Brien on 4/1/08.
// Copyright 2008 MolokoCacao. All rights reserved.
//
#import "NSBezierPath+MCAdditions.h"
#import "third_party/google_toolbox_for_mac/src/AppKit/GTMNSBezierPath+CGPath.h"
// remove/comment out this line of you don't want to use undocumented functions
#define MCBEZIER_USE_PRIVATE_FUNCTION
#ifdef MCBEZIER_USE_PRIVATE_FUNCTION
extern CGPathRef CGContextCopyPath(CGContextRef context);
#endif
static void CGPathCallback(void *info, const CGPathElement *element)
{
NSBezierPath *path = info;
CGPoint *points = element->points;
switch (element->type) {
case kCGPathElementMoveToPoint:
{
[path moveToPoint:NSMakePoint(points[0].x, points[0].y)];
break;
}
case kCGPathElementAddLineToPoint:
{
[path lineToPoint:NSMakePoint(points[0].x, points[0].y)];
break;
}
case kCGPathElementAddQuadCurveToPoint:
{
// NOTE: This is untested.
NSPoint currentPoint = [path currentPoint];
NSPoint interpolatedPoint = NSMakePoint((currentPoint.x + 2*points[0].x) / 3, (currentPoint.y + 2*points[0].y) / 3);
[path curveToPoint:NSMakePoint(points[1].x, points[1].y) controlPoint1:interpolatedPoint controlPoint2:interpolatedPoint];
break;
}
case kCGPathElementAddCurveToPoint:
{
[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)];
break;
}
case kCGPathElementCloseSubpath:
{
[path closePath];
break;
}
}
}
@implementation NSBezierPath (MCAdditions)
+ (NSBezierPath *)bezierPathWithCGPath:(CGPathRef)pathRef
{
NSBezierPath *path = [NSBezierPath bezierPath];
CGPathApply(pathRef, path, CGPathCallback);
return path;
}
- (NSBezierPath *)pathWithStrokeWidth:(CGFloat)strokeWidth
{
#ifdef MCBEZIER_USE_PRIVATE_FUNCTION
NSBezierPath *path = [self copy];
CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];
CGPathRef pathRef = [path gtm_CGPath];
[path release];
CGContextSaveGState(context);
CGContextBeginPath(context);
CGContextAddPath(context, pathRef);
CGContextSetLineWidth(context, strokeWidth);
CGContextReplacePathWithStrokedPath(context);
CGPathRef strokedPathRef = CGContextCopyPath(context);
CGContextBeginPath(context);
NSBezierPath *strokedPath = [NSBezierPath bezierPathWithCGPath:strokedPathRef];
CGContextRestoreGState(context);
CFRelease(pathRef);
CFRelease(strokedPathRef);
return strokedPath;
#else
return nil;
#endif // MCBEZIER_USE_PRIVATE_FUNCTION
}
- (void)fillWithInnerShadow:(NSShadow *)shadow
{
[NSGraphicsContext saveGraphicsState];
NSSize offset = shadow.shadowOffset;
NSSize originalOffset = offset;
CGFloat radius = shadow.shadowBlurRadius;
NSRect bounds = NSInsetRect(self.bounds, -(ABS(offset.width) + radius), -(ABS(offset.height) + radius));
// The context's user transform isn't automatically applied to shadow offsets.
offset.height += bounds.size.height;
shadow.shadowOffset = offset;
NSAffineTransform *transform = [NSAffineTransform transform];
if ([[NSGraphicsContext currentContext] isFlipped])
[transform translateXBy:0 yBy:bounds.size.height];
else
[transform translateXBy:0 yBy:-bounds.size.height];
NSBezierPath *drawingPath = [NSBezierPath bezierPathWithRect:bounds];
[drawingPath setWindingRule:NSEvenOddWindingRule];
[drawingPath appendBezierPath:self];
[drawingPath transformUsingAffineTransform:transform];
[self addClip];
[shadow set];
[[NSColor blackColor] set];
[drawingPath fill];
shadow.shadowOffset = originalOffset;
[NSGraphicsContext restoreGraphicsState];
}
- (void)drawBlurWithColor:(NSColor *)color radius:(CGFloat)radius
{
NSRect bounds = NSInsetRect(self.bounds, -radius, -radius);
NSShadow *shadow = [[NSShadow alloc] init];
shadow.shadowOffset = NSMakeSize(0, bounds.size.height);
shadow.shadowBlurRadius = radius;
shadow.shadowColor = color;
NSBezierPath *path = [self copy];
NSAffineTransform *transform = [NSAffineTransform transform];
if ([[NSGraphicsContext currentContext] isFlipped])
[transform translateXBy:0 yBy:bounds.size.height];
else
[transform translateXBy:0 yBy:-bounds.size.height];
[path transformUsingAffineTransform:transform];
[NSGraphicsContext saveGraphicsState];
[shadow set];
[[NSColor blackColor] set];
NSRectClip(bounds);
[path fill];
[NSGraphicsContext restoreGraphicsState];
[path release];
[shadow release];
}
// Credit for the next two methods goes to Matt Gemmell
- (void)strokeInside
{
/* Stroke within path using no additional clipping rectangle. */
[self strokeInsideWithinRect:NSZeroRect];
}
- (void)strokeInsideWithinRect:(NSRect)clipRect
{
NSGraphicsContext *thisContext = [NSGraphicsContext currentContext];
float lineWidth = [self lineWidth];
/* Save the current graphics context. */
[thisContext saveGraphicsState];
/* Double the stroke width, since -stroke centers strokes on paths. */
[self setLineWidth:(lineWidth * 2.0)];
/* Clip drawing to this path; draw nothing outwith the path. */
[self setClip];
/* Further clip drawing to clipRect, usually the view's frame. */
if (clipRect.size.width > 0.0 && clipRect.size.height > 0.0) {
[NSBezierPath clipRect:clipRect];
}
/* Stroke the path. */
[self stroke];
/* Restore the previous graphics context. */
[thisContext restoreGraphicsState];
[self setLineWidth:lineWidth];
}
@end