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 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.
.
150px × 10px
#535e71
opacity: 33%
#535e71
opacity: 0%
150px × 1px
#535e71
opacity: 50%
#ffffff
to #000000
to "Layer C".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.).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With