Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/cocoa/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Cocoa 如何+;[NSColor selectedMenuItemColor]神奇地绘制渐变?_Cocoa_Appkit_Nscolor - Fatal编程技术网

Cocoa 如何+;[NSColor selectedMenuItemColor]神奇地绘制渐变?

Cocoa 如何+;[NSColor selectedMenuItemColor]神奇地绘制渐变?,cocoa,appkit,nscolor,Cocoa,Appkit,Nscolor,我正在实现一个自定义的NSMenuItem视图,当用户将鼠标移到它上面时。为此,在将[NSColor selectedMenuItemColor]设置为活动颜色后,代码将调用NSRectFill。然而,我注意到结果不仅仅是纯色-它实际上绘制了一个渐变。非常好,但是想知道这个“魔法”是如何工作的——也就是说,如果我想定义我自己的颜色,而不仅仅是绘制实体,我会怎么做?我最好的猜测是,它被定义为某种类型,但是这并不能完全回答我的问题,因为看起来这些图案通常是平铺而不是拉伸的 这一点得到了一位苹果工程师

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

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

这一点得到了一位苹果工程师的证实,他说:

[[NSColor selectedMenuItemColor]set]; NSRectFill(sometrect)

这是因为selectedMenuItemColor是一种发生的模式 画梯度。你可以很容易地画出几乎任何有图案的东西[…]

不过,他没有详细说明如何拉伸这些图案,而不是平铺,因为高亮显示的菜单项背景是。该线程中的另一篇帖子声称这是“在绘图代码中”,但他可能只是在猜测。

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

为了演示该技术,这里有一个关于
NSColor
的类别,允许您从
NSGradient
创建颜色。当您
设置该颜色,然后使用它填充区域时,将绘制渐变(线性,从下到上,但您可以轻松更改)。这甚至适用于笔划路径或填充非矩形路径,如
[[NSBezierPath-bezierpath-withovalinrect:nsmakerrect(0,0,100,100)]填充]
,因为
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
结果:


太棒了……是的,连接到CGPattern功能肯定会越来越近!唯一缺少的是它如何处理视图的部分重画。也就是说,您只是在当前剪辑中绘制渐变,但在“润色”视图的一部分时,这可能与先前绘制的部分不一致?看起来要么它必须使用线程当前正在绘制的视图的内部知识,要么可能是某种模式阶段魔法?嗯,是的。我不知道该怎么做,或者是否有可能(使用公共API)…不幸的是,我无法在10.7上直接测试代码,但我已经确认了两件事:1)至少在包装代码以将cgColor绘制到当前Cocoa上下文的graphicsPort中时,它会像人们担心的那样符合dirtyRect的渐变,而2)无论是剪辑矩形还是绘制矩形,selectedMenuItemColor都会“神奇地”绘制。但同时,投票选出最佳猜测!
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];