How to draw a tapered line + oval shadow in Cocoa
Background:
The shot below is of Mail.app in OS X Lion. When the source list gets too long, a nice shadowy line appears just above the buttons at the bottom of the source list. When you scroll, the source list moves under that shadowy line. When you expand the window so that everything in the source list fits without scrolling, the shadowy line disappears.
The question:
How can I draw this shadowy line using Cocoa? I'm aware of NSShadow and such, but it seems to me there's more going on here than just a shadow. There's a line that subtly fades to points (as if开发者_JAVA百科 you applied a gradient mask to each end in Photoshop.) Likewise, the shadow is oval and tapers off as you approach the end of the lines. So it's not just a regular NSShadow, is it? (It's definitely not an image, as it scales nicely when you expand the width of the source view.)
Any tips on how to approach drawing this shape would be greatly appreciated.
And for the sticklers out there, no, this does not violate the NDA, as Mail.app has been shown publicly by Apple.
General Idea:
.
- Create a layer "Layer A" with dimensions
150px × 10px
and fill it with a Gradient with:- lower color:
#535e71
opacity:33%
- upper color:
#535e71
opacity:0%
- lower color:
- Create a layer "Layer B" with dimensions
150px × 1px
and fill it with solid#535e71
opacity:50%
- Compose "Layer A" and "Layer B" together into "Layer C".
- Apply reflected gradient mask from
#ffffff
to#000000
to "Layer C".
Visual Steps:
Functional Code:
MyView.h:
#import <Cocoa/Cocoa.h>
@interface MyView : NSView {
@private
}
@end
MyView.m:
#import "MyView.h"
@implementation MyView
- (CGImageRef)maskForRect:(NSRect)dirtyRect {
NSSize size = [self bounds].size;
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(NULL, size.width, size.height, 8, 0, colorSpace, kCGImageAlphaPremultipliedLast);
CGContextClipToRect(context, *(CGRect*)&dirtyRect);
CGRect rect = CGRectMake(0.0, 0.0, size.width, size.height);
size_t num_locations = 3;
CGFloat locations[3] = { 0.0, 0.5, 1.0 };
CGFloat components[12] = {
1.0, 1.0, 1.0, 1.0, // Start color
0.0, 0.0, 0.0, 1.0, // Middle color
1.0, 1.0, 1.0, 1.0, // End color
};
CGGradientRef myGradient = CGGradientCreateWithColorComponents(colorSpace, components, locations, num_locations);
CGPoint myStartPoint = CGPointMake(CGRectGetMinX(rect), CGRectGetMinY(rect));
CGPoint myEndPoint = CGPointMake(CGRectGetMaxX(rect), CGRectGetMinY(rect));
CGContextDrawLinearGradient(context, myGradient, myStartPoint, myEndPoint, 0);
CGImageRef theImage = CGBitmapContextCreateImage(context);
CGImageRef theMask = CGImageMaskCreate(CGImageGetWidth(theImage), CGImageGetHeight(theImage), CGImageGetBitsPerComponent(theImage), CGImageGetBitsPerPixel(theImage), CGImageGetBytesPerRow(theImage), CGImageGetDataProvider(theImage), NULL, YES);
[(id)theMask autorelease];
CGColorSpaceRelease(colorSpace);
CGContextRelease(context);
return theMask;
}
- (void)drawRect:(NSRect)dirtyRect {
NSRect nsRect = [self bounds];
CGRect rect = *(CGRect*)&nsRect;
CGRect lineRect = CGRectMake(rect.origin.x, rect.origin.y, rect.size.width, (CGFloat)1.0);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = (CGContextRef) [[NSGraphicsContext currentContext] graphicsPort];
CGContextClipToRect(context, *(CGRect*)&dirtyRect);
CGContextClipToMask(context, rect, [self maskForRect:dirtyRect]);
size_t num_locations = 2;
CGFloat locations[2] = { 0.0, 1.0 };
CGFloat components[8] = {
0.315, 0.371, 0.450, 0.3, // Bottom color
0.315, 0.371, 0.450, 0.0 // Top color
};
CGGradientRef myGradient = CGGradientCreateWithColorComponents(colorSpace, components, locations, num_locations);
CGPoint myStartPoint = CGPointMake(CGRectGetMinX(rect), CGRectGetMinY(rect));
CGPoint myEndPoint = CGPointMake(CGRectGetMinX(rect), CGRectGetMaxY(rect));
CGContextDrawLinearGradient(context, myGradient, myStartPoint, myEndPoint, 0);
CGContextSetRGBFillColor(context, 0.315, 0.371, 0.450, 0.5 );
CGContextFillRect(context, lineRect);
CGColorSpaceRelease(colorSpace);
}
@end
(My first time using pure low-level CoreGraphics, thus possibly sub-par optimal, open for improvements.)
This is an actual screenshot of what the code above produces:
The drawing stretches to the view's dimensions.
(I formerly had two techniques shown here: "Technique A" & "Technique B".
"Technique B" provided superior results and was way simpler to implement as well, so I ditched "Technique A".
Some comments may still refer to "Technique A" though. Just ignore them and enjoy the fully functional code snippet.).
How about an image that's stretched horizontally?
Or if you know how you'd make one in Photoshop, you could apply those same steps programmatically.
精彩评论