4

ユーザーがマウスオーバーするとハイライトを表示するカスタム NSMenuItem ビューを実装しています。これを行うために、コードはアクティブな色としてNSRectFill設定した後に呼び出します。[NSColor selectedMenuItemColor]しかし、結果は単なる単色ではなく、実際にはグラデーションを描画していること気付きました。非常に素晴らしいですが、この「魔法」がどのように機能するのか疑問に思っています。つまり、単色ではない独自の色を定義したい場合、どうすればよいでしょうか?

4

2 に答える 2

3

これが実際にどのように機能するかはわかりませんが、カスタムグラデーション (またはその他の描画操作) を使用して動作を再現する方法を見つけました。「トリック」はCGPatternRef、パターンを描画するためのコールバック関数を指定できる を使用することです。通常、このコールバック関数はパターンの 1 つの「セル」を描画しますが、非常に大きなパターン サイズ (例: CGFLOAT_MAX) を指定して、コールバックの 1 回の呼び出しで領域全体を埋めることができます。

テクニックを示すために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 に関するApple エンジニアの投稿によって裏付けられています。

[[NSColor selectedMenuItemColor] セット]; NSRectFill(someRect);

これが機能するのは、selectedMenuItemColor がたまたまグラデーションを描画するパターンであるためです。パターンを使ってほぼ何でも簡単に描くことができます […]

ただし、ハイライトされたメニュー項目の背景がそうであるように、これらのパターンを並べて表示する代わりに引き延ばして描画する方法については詳しく説明していません。そのスレッドの別の投稿では、「描画コード内の特殊なケース」であると主張していますが、彼は単に推測している可能性があります。

于 2013-03-26T17:11:21.853 に答える