4

我正在实现一个自定义 NSMenuItem 视图,当用户将鼠标悬停在它上面时会显示一个突出显示。为此,代码NSRectFill在设置[NSColor selectedMenuItemColor]为活动颜色后调用。然而,我注意到结果不仅仅是纯色——它实际上是绘制了一个渐变。非常好,但想知道这个“魔法”是如何工作的——也就是说,如果我想定义我自己的颜色,而不仅仅是绘制纯色,我会怎么做?

4

2 回答 2

3

我不知道这实际上是如何工作的,但我找到了一种使用自定义渐变(或任何其他绘图操作)复制行为的方法。“技巧”是使用 a CGPatternRef,它允许您指定用于绘制模式的回调函数。通常,此回调函数绘制模式的一个“单元格”,但您可以指定一个非常大的模式大小(例如CGFLOAT_MAX),以便能够在一次回调调用中填充整个区域。

为了演示该技术,这里有一个类别NSColor,允许您从NSGradient. 当您set使用该颜色然后使用它填充一个区域时,将绘制渐变(线性,从下到上,但您可以轻松更改)。这甚至适用于描边路径或填充非矩形路径,例如[[NSBezierPath bezierPathWithOvalInRect:NSMakeRect(0, 0, 100, 100)] fill]因为NSBezierPath自动剪辑绘图。

//NSColor+Gradient.h
#import <Cocoa/Cocoa.h>

@interface NSColor (Gradient)

+ (NSColor *)my_gradientColorWithGradient:(NSGradient *)gradient;

@end

//NSColor+Gradient.m
#import "NSColor+Gradient.h"
#import <objc/runtime.h>

static void DrawGradientPattern(void * info, CGContextRef context)
{
    NSGraphicsContext *currentContext = [NSGraphicsContext currentContext];
    CGRect clipRect = CGContextGetClipBoundingBox(context);
    [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:context flipped:NO]];
    NSGradient *gradient = (__bridge NSGradient *)info;
    [gradient drawInRect:NSRectFromCGRect(clipRect) angle:90.0];
    [NSGraphicsContext setCurrentContext:currentContext];
}

@implementation NSColor (Gradient)

+ (NSColor *)my_gradientColorWithGradient:(NSGradient *)gradient
{
    CGColorSpaceRef colorSpace = CGColorSpaceCreatePattern(NULL);
    CGPatternCallbacks callbacks;
    callbacks.drawPattern = &DrawGradientPattern;
    callbacks.releaseInfo = NULL;
    CGPatternRef pattern = CGPatternCreate((__bridge void *)(gradient), CGRectMake(0, 0, CGFLOAT_MAX, CGFLOAT_MAX), CGAffineTransformIdentity, CGFLOAT_MAX, CGFLOAT_MAX, kCGPatternTilingConstantSpacing, true, &callbacks);
    const CGFloat components[4] = {1.0, 1.0, 1.0, 1.0};
    CGColorRef cgColor = CGColorCreateWithPattern(colorSpace, pattern, components); 
    CGColorSpaceRelease(colorSpace);
    NSColor *color = [NSColor colorWithCGColor:cgColor];
    objc_setAssociatedObject(color, "gradient", gradient, OBJC_ASSOCIATION_RETAIN);
    return color;
}

@end

使用示例:

NSArray *colors = @[ [NSColor redColor], [NSColor blueColor] ];
NSGradient *gradient = [[NSGradient alloc] initWithColors:colors];
NSColor *gradientColor = [NSColor my_gradientColorWithGradient:gradient];
[gradientColor set];
NSRectFill(NSMakeRect(0, 0, 100, 100));
[[NSBezierPath bezierPathWithOvalInRect:NSMakeRect(100, 0, 100, 100)] fill];

结果:

截屏

于 2013-03-26T20:57:45.967 回答
0

我最好的猜测是它被定义为某种图案图像,但这并不能完全回答我的问题,因为看起来这些图案通常会被平铺而不是拉伸。

苹果工程师在 cocoa-dev 上的帖子证实了这一点,该帖子指出:

[[NSColor selectedMenuItemColor] 设置]; NSRectFill(someRect);

这是因为 selectedMenuItemColor 是一个碰巧绘制渐变的模式。你可以很容易地用图案画出几乎任何东西 […]

不过,他没有详细说明如何将这些图案拉伸而不是平铺,就像突出显示的菜单项背景一样。该线程中的另一篇文章声称这是“绘图代码中的特殊情况”,但他可能只是在猜测。

于 2013-03-26T17:11:21.853 回答