Monday, November 5, 2007

The Leopard Ate My Code

or: How I Learned to Stop Worrying and Love the Leopard

Since Tiger appeared, many people have been jumping through some interesting hoops to get the iTunes-style "inset" text appearance in metal windows. Some people draw the text twice, where the first drawing is a lighter color and offset down by one pixel. Others are now apparently resorting to image editing products to get a similar result. I've mentioned elsewhere that drawing the text twice is not necessary, and now it gets even easier with Leopard.

Update: Ken points out in a comment that drawing the text twice can have better results than using NSShadow on Tiger. It's still much easier on Leopard.

Here's a simple way to do it on Tiger, using attributed strings and NSShadow:

NSTextField* textField = something from IB;
NSShadow* textShadow = [[NSShadow alloc] init];

[textShadow setShadowColor: [NSColor
colorWithCalibratedRed: 1.0 green: 1.0 blue: 1.0 alpha: 0.5]];
[textShadow setShadowOffset: NSMakeSize(0.0, -1.0)];
[textShadow setShadowBlurRadius: 0.0];

NSMutableAttributedString* newString =
[[NSMutableAttributedString alloc] initWithAttributedString:
[textField attributedStringValue]];

[newString addAttribute: NSShadowAttributeName value: textShadow
range: NSMakeRange(0, [newString length])];
[textField setAttributedStringValue: newString];
[newString release];

Not too bad, although the color values, pixel offset, etc. are hardcoded. Now let's see how much code it takes to achieve a similar result in Leopard:

[[textField cell] setBackgroundStyle: NSBackgroundStyleRaised];



Jean-Fran├žois said...

Less code means more time to hack. Awesome.

Paul said...

Apologies to Stanley Kubric.

Blake C. said...

Good to hear from you guys again :)

Anonymous said...

Drawing it twice is actually a closer way to replicate the look of engraved text on Tiger. If you use a shadow, you will lose font smoothing, for example.

Cocoa frameworks

Blake C. said...

Good catch- I didn't think about that.

Amin Negm-Awad said...

NSShadow* textShadow = [[NSShadow alloc] init];
should be
NSShadow* textShadow = [[[NSShadow alloc] init] autorelease];
(or an additional release message to textShadow)?

Blake C. said...

Good catch, amin. I lifted the example code from a project where the NSShadow* was stored in an ivar. I should have also lifted the -release part, or thrown in an -autorelease as you suggested.

Thanx for keeping me honest :)