2009 03 15Custom NSThemeFrame
After seeing Safari 4 drawing its tab bar in a custom way, I wondered how to do that. Each window has a frame view (the superview
of contentView
) that draws the window, replacing that view's drawRect:
with our own will let us draw it ourselves !
First, replace the window's frame drawRect:
with our own :
// Get window's frame view class id class = [[[window contentView] superview] class]; // Add our drawRect: to the frame class Method m0 = class_getInstanceMethod([self class], @selector(drawRect:)); class_addMethod(class, @selector(drawRectOriginal:), method_getImplementation(m0), method_getTypeEncoding(m0)); // Exchange methods Method m1 = class_getInstanceMethod(class, @selector(drawRect:)); Method m2 = class_getInstanceMethod(class, @selector(drawRectOriginal:)); method_exchangeImplementations(m1, m2);
Cocoa will then call our method each time the window frame needs to be drawn. We can draw an entirely custom frame or draw over the existing one. (The attached sample does the latter)
A standard Cocoa window has rounded corners that need to be accounted for by building a clipping path to clip our custom drawing to the window shape :
- Get the rounded corner radius with
roundedCornerRadius
- Using this radius, build a rounded corner path that describes the window
- Intersect that path with the current
rect
- Draw our stuff
- (void)drawRect:(NSRect)rect { // Call original drawing method [self drawRectOriginal:rect]; // // Build clipping path : intersection of frame clip (bezier path with rounded corners) and rect argument // NSRect windowRect = [[self window] frame]; windowRect.origin = NSMakePoint(0, 0); float cornerRadius = [self roundedCornerRadius]; [[NSBezierPath bezierPathWithRoundedRect:windowRect xRadius:cornerRadius yRadius:cornerRadius] addClip]; [[NSBezierPath bezierPathWithRect:rect] addClip]; // Any custom drawing goes here ... }Sample Code Custom frame drawing.zip draws an
NSImageNameActionTemplate
image and a custom color (in kCGBlendModeColorDodge
) over the existing frameI guess it's possible. I wanted to draw over the existing frame, letting it handle drawing of buttons, toolbar, resize container, etc. Replacing the frame would mean I'd have to do it, and that's too much work ;)
Cool tip! I was going to look into doing something like this for the app I'm working on to experiment with giving it a bit of a darker look. Not sure if it'll look good or just weird yet though, it's a fine line between giving an app a unique style and just messing with things that are already good. This technique seems easy enough to quickly find out though.
Thanks for sharing a way of doing it!
You're welcome !
I just tried darkening the window, it quickly looks very dark. You might have to draw the title and the toolbar labels yourself.
Great snippet!
I'm searching for a way to make the two bottom corners of a textured window have 'sharp' or 'square' corner like Safari, as I want a status bar down there. I once found out around 7 years ago, but I lost that snippet.
My solution was done when the window was initialized somehow, it didn't use more than 1 or 2 lines, but as I recall it, it didn't seem clear that it should/could be done this way.I believe that I made a window in IB, then when loaded, I changed it from non-textured to textured, while I also changed some styling or something similar.
Do you by any chance know how to do this?
Jens, you're looking for NSWindow's setBottomCornerRounded.
http://parmanoir.com/NSWindow_goodies_:_bottomCornerRounded,_usesLightBottomGradient
Patrick, you are a genious! :)
It's exactly what I've been looking for. -And I would probably not have found it without your help, as it seems it's a private API - it gives me a compiler warning that NSWindow may not respond to it, but I just checked that it respondsToSelector:@selector(setBottomCornerRounded) and I got a YES. =)
It's probably also far better than my 6 year old solution, thankyou a billion!You're welcome ! That's the sort of thing you can stumble upon when playing around with F-Script. It will let you explore all Cocoa classes and methods at runtime.
Can you update this to work with Xcode 5? For some reason it won't let me build against the SDK.
can't you just replace the themeview with your own object? i mean, the NSWindow somehow sets this object, so i'm sure you can simply replace it. i guess you don't need to method swizzle NSThemeFrame, but instead create your subclass of NSThemeFrame and use that instead. I've just had a look. there's a _borderView instvar in NSWindow which keeps this theme frame so it could work if you change it, but i'm sure you'd need to do some housekeeping as well. like setting up the view in the window properly and such stuff...
Karsten