Я реализую собственное представление NSMenuItem, которое показывает выделение как пользователь наводит на него курсор мыши. Для этого код вызывает NSRectFill
после установки [NSColor selectedMenuItemColor]
в качестве активного цвета. Однако я заметил, что в результате получается не просто сплошной цвет — вместо этого он фактически рисует градиент. Очень мило, но интересно, как работает это «волшебство» — например, если бы я хотел определить свой собственный цвет, который не просто рисовал сплошным цветом, как бы я это сделал?
Как +[NSColor selectedMenuItemColor] волшебным образом рисует градиент?
Ответы (2)
Я не знаю, как это на самом деле работает, но я нашел способ воспроизвести поведение с помощью пользовательских градиентов (или любых других операций рисования). «Хитрость» заключается в использовании 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];
Результат:
По моему мнению, он определяется как своего рода шаблонное изображение, однако это не полностью отвечает на мой вопрос, потому что похоже, что эти узоры обычно рисуются плиткой, а не растягиваются.
Это подтверждается сообщением инженера Apple на сайте Coco-dev в котором говорится:
[[NSColor selectedMenuItemColor] установлен]; NSRectFill(некоторыеRect);
Это работает, потому что selectedMenuItemColor — это шаблон, который рисует градиент. Вы могли бы так же легко нарисовать почти что угодно с помощью шаблона […]
Однако он не уточняет, как эти узоры можно рисовать растянутыми, а не мозаичными, как, например, выделенный фон пункта меню. В другом сообщении в этой ветке утверждается, что это «особый случай внутри кода рисования», но он может просто строить догадки.