问题描述
我实施了一个自定义NSMenuItem视图,显示突出显示随着用户将鼠标悬停在其上。为此,在将 [NSColor selectedMenuItemColor]
设置为活动颜色之后,代码调用 NSRectFill
但是,我注意到结果不是简单的纯色 - 它实际上绘制了渐变。非常好,但想知道这个魔法是如何工作的 - 也就是说,如果我想定义我自己的颜色,而不只是绘制坚实的,我会怎么样?
I'm implementing a custom NSMenuItem view that shows a highlight as the user mouses over it. To do this, the code calls NSRectFill
after setting [NSColor selectedMenuItemColor]
as the active color. However, I noticed that the result is not simply a solid color — it actually draws a gradient instead. Very nice, but wondering how this "magic" works — i.e. if I wanted to define my own color that didn't just draw solid, how would I?
推荐答案
我不知道这实际上如何工作,但我发现了一种方式来复制自定义渐变(或任何其他绘图操作)的行为。 技巧是使用 CGPatternRef
,它允许您指定一个回调函数来绘制模式。通常,此回调函数绘制模式的一个单元格,但您可以只指定非常大的模式大小(例如 CGFLOAT_MAX
),以便能够填充整个区域一个调用回调。
I don't know how this actually works, but I found a way to replicate the behavior with custom gradients (or any other drawing operations). The "trick" is to use a CGPatternRef
, which allows you to specify a callback function for drawing the pattern. Normally, this callback function draws one "cell" of the pattern, but you can just specify a very large pattern size (e.g. CGFLOAT_MAX
) to be able to fill the entire area in one invocation of the callback.
为了演示这个技术,这里有一个类别 NSColor
,允许你创建一个颜色来自 NSGradient
。当您设置
该颜色,然后使用它填充一个区域,渐变绘制(线性,从底部到顶部,但你可以很容易地改变)。这甚至适用于描边路径或填充非矩形路径,如 [[NSBezierPath bezierPathWithOvalInRect:NSMakeRect(0,0,100,100)] fill]
,因为 NSBezierPath
自动剪裁图形。
To demonstrate the technique, here's a category on NSColor
that allows you to create a color from an NSGradient
. When you set
that color and then use it to fill an area, the gradient is drawn (linear, from bottom to top, but you can easily change that). This even works for stroking paths or filling non-rectangular paths, like [[NSBezierPath bezierPathWithOvalInRect:NSMakeRect(0, 0, 100, 100)] fill]
because NSBezierPath
automatically clips the drawing.
//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];
结果:
这篇关于如何+ [NSColor selectedMenuItemColor]神奇地绘制一个渐变?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!