简体   繁体   English

如何在Cocoa中绘制锥形线+椭圆形阴影

[英]How to draw a tapered line + oval shadow in Cocoa

Background: 背景:

The shot below is of Mail.app in OS X Lion. 下面的镜头是OS X Lion中的Mail.app。 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? 如何使用Cocoa绘制这条阴影线? I'm aware of NSShadow and such, but it seems to me there's more going on here than just a shadow. 我知道NSShadow等等,但在我看来,这里发生的事情不仅仅是阴影。 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. 有一条线巧妙地淡化到点(就好像你在Photoshop中的每一端应用了渐变蒙版。)同样,阴影是椭圆形的 ,当你接近线的末端时逐渐变细。 So it's not just a regular NSShadow, is it? 所以它不只是常规的NSShadow,是吗? (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. 对于坚持不懈的人来说,不,这并不违反NDA,因为Mail.app已被Apple公开展示。

General Idea: 大概的概念:

.

  1. Create a layer "Layer A" with dimensions 150px × 10px 创建尺寸为150px × 10px的图层“图层A”
    and fill it with a Gradient with: 并用渐变填充:
    • lower color: #535e71 opacity: 33% 较低的颜色: #535e71 不透明度: 33%
    • upper color: #535e71 opacity: 0% 鞋面颜色: #535e71 不透明度: 0%
  2. Create a layer "Layer B" with dimensions 150px × 1px 创建一个尺寸为150px × 1px的图层“图层B”
    and fill it with solid #535e71 opacity: 50% 并填充固体#535e71 不透明度: 50%
  3. Compose "Layer A" and "Layer B" together into "Layer C". “层A”和“层B” 组合成“层C”。
  4. Apply reflected gradient mask from #ffffff to #000000 to "Layer C". #ffffff 反射渐变蒙版应用到#000000到“Layer C”。

Visual Steps: 视觉步骤:

在此输入图像描述

Functional Code: 功能代码:

MyView.h : MyView.h

#import <Cocoa/Cocoa.h>

@interface MyView : NSView {
@private

}

@end

MyView.m : 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.) (我第一次使用纯粹的低级CoreGraphics,因此可能低于最佳值,可以进行改进。)

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". (我以前在这里展示了两种技术:“技术A”和“技术B”。
"Technique B" provided superior results and was way simpler to implement as well, so I ditched "Technique A". “技术B”提供了优异的结果,并且实现起来也更简单,所以我放弃了“技术A”。
Some comments may still refer to "Technique A" though. 有些评论可能仍然提到“技术A”。 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. 或者如果您知道如何在Photoshop中制作一个,则可以通过编程方式应用相同的步骤。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM