andymatuschak.org: Square Signals

This article was published on Tuesday, August 01st, 2006 at 2:34 pm.

Polished Metal Buttons

Polished metal is really annoying. Apple’s using it in a huge number of applications, but it’s not at all available to independent developers. Even if it becomes an option next week, we won’t be able to use it because we’ll still need compatibility with Tiger.

That sucks.

Fortunately, I have too much time on my hands, so I can lend a hand to those of you struggling to implement this interface:

Polished Buttons

Download Polished Buttons 1.0.1

Polished Metal Window Screenshot

I am a pirate, but I cry myself to sleep.

My first word when embarking upon this epic voyage of interface piracy on the high seas was:

“what”

You see, I started out my morning as I always do: by running down to the cabin and rousing my faithful compatriots from their slumber. class-dump and F-Script never fail to carve up a royal cruiser. But lo, what do I see here when examining iWeb’s buttons? An image button? Huh? But… it says “Publish.” That’s no image.

Oh, how foolish I was.

It slowly became clear to me as I poked around that the text “Publish” wasn’t actually stored anywhere in the object—that it really was an image! So into the Resources/English.lproj directory of iWeb I dived. In that deep abyss, I discovered something… terrible. Something… too ghastly for words.

F-Script and I have seen a lot of crazy things in our days at sea—crabs with three legs, fuschia-colored gold, and even a ship using a patchwork quilt of Denny’s receipts as a sail—but nothing like this!

For their polished metal buttons, Apple had made images out of every single button in every single state. Have a “Publish” button? That’s three images! Localized into every one of seventy-four languages! Hey, why not, right? Then the work falls on the artist, not the programmer!

“Hey, Jon, we’re gonna need to change ‘Publish’ to ‘Publish to .Mac.’ It’s just not clear enough now. Soooo… go ahead and fire up the ol’ Photoshop, and I’ll be back in a week!”

We can rebuild him.

Fortunately, there’s a way to go about using these polished metal buttons that isn’t completely chowderheaded. Well, okay, ideally, I’d be rendering them at runtime using NSBezierPaths, but that’d be a huge pain in the ass, so I went about it the Photoshop way.

I started by picking one of the 492,174,572 sets of three images (normal, disabled, and pressed) in an app with polished metal and opened it up. Pixen will carve them up pretty well, and it won’t slow your computer down like Photoshop will. In fact if you use Pixen, it’ll even generate new RAM for you! Your computer will be faster with every launch! … please?

Ahem. Anyway.

I cut each of the three states into three pieces: the left bezel, a one-pixel-wide section of the middle, and the right bezel. I had to be careful to preserve the transparency, which can be tricky in Photoshop sometimes. Another thing that gave me trouble: make sure not to embed your screen’s color profile in the image slices when you save.

Implementing the control was easy once I had the slices. I just made NSButton and NSButtonCell subclasses, overriding the former to use the latter. I overrode - drawWithFrame:inView: in the button cell to render the button background using the images I’d extracted, changing them depending on the state of the button.

Getting the text to look like Apple’s was a little trickier. I fiddled with the values for a while and eventually settled on bold Lucida Grande 11.5pt. I drew the text in white (0.9 alpha) with - drawTitle:withFrame:inView: first, then in black (0.9 alpha / 0.6 alpha for disabled) a pixel higher. As far as I can tell, it’s pretty much identical to Apple’s rendering.

A picture is worth both a thousand words and a punch to the face.

Now, so far, my new button cell class doesn’t support images on the button. Which sucks! Most polished buttons have an icon in them, so that definitely needed to be fixed.

It wasn’t hard at all to get them rendering normally: I just used - drawImage:withFrame:inView:. Faded disabled images were free (thanks, Cocoa!). But something looked wrong! They didn’t look quite like Apple’s. What was missing?

I stared at zoomed-in screenshots for a few minutes and figured it out: the embossing. But how could I emboss arbitrary images like that? The answer lay in madness, but madness is fun. That’s right, you know what this calls for:

Core Image.

So I whipped up a quick method to desaturate and brighten the image. Then I had the cell render the filtered one under the original. Yeah, yeah, I #ifdef’d the embossing so developers who care about Panther support will survive. Here’s the bezeling method:

- (void)drawEmbossingHighlightForImage:(NSImage *)image withFrame:(NSRect)frame inView:(NSButton *)view
{
    // Desaturate the image and pump up its brightness to get a good embossing highlight.
    CIFilter *colorAdjust = [CIFilter filterWithName:@"CIColorControls"];
    [colorAdjust setValue:[NSNumber numberWithFloat:0.0] forKey:@"inputSaturation"];
    [colorAdjust setValue:[NSNumber numberWithFloat:0.75] forKey:@"inputBrightness"];
    [colorAdjust setValue:[CIImage imageWithData:[image TIFFRepresentation]] forKey:@"inputImage"];
    CIImage *result = [colorAdjust valueForKey:@"outputImage"];
    if ([view isFlipped])
    {
        // CIImage doesn't have a setFlipped, so we need to flip it the long way.
        CIFilter *transform = [CIFilter filterWithName:@"CIAffineTransform"];
        [transform setValue:result forKey:@"inputImage"];
        NSAffineTransform *affineTransform = [NSAffineTransform transform];
        [affineTransform translateXBy:0 yBy:[image size].height];
        [affineTransform scaleXBy:1 yBy:-1];
        [transform setValue:affineTransform forKey:@"inputTransform"];
        result = [transform valueForKey:@"outputImage"];
    }
    [result drawAtPoint:NSMakePoint(NSMidX(frame) - [image size].width / 2.0, NSMidY(frame) - [image size].width / 2.0)
               fromRect:(NSRect){NSZeroPoint, [image size]}
              operation:NSCompositeSourceAtop
               fraction:([self isEnabled] ? 0.6 : 0.4)];
}

Probably the most insane use of Core Image ever. Of this, I am proud. I accept gifts of chocolate and poison-covered keyboards.

More Polish

As for the polished window itself that you see in the shot above, I started with Matt Gemmell’s TunesWindow but modified the images rather significantly. I also replaced the resize indicator with one I wrested from iWeb’s cold, dead grasp via F-Script.

You get free code here, but I’m not doing all the work for you. The rest of your interface has to fit in with the polished metal style. As I continue working on this thing, there may be a few more abstracted controls I’ll throw up on my blog. (update: but probably not, since Leopard seems to include them)

I hope these button classes are helpful! Read the Readme when you try it, and make sure you remember to either add Core Image (QuartzCore.framework) to your project or comment out the embossing #define line in AMPolishedButtonCell.h.

Got Thoughts?

By all means share them, and start the conversation.

Leave a Comment

Currently you have JavaScript disabled. In order to post comments, please make sure JavaScript and Cookies are enabled, and reload the page.

You can follow any responses to this entry via its RSS comments feed.

If you're looking for something specific then give the search form below a try:

RSS Wordpress Grady (theme) Return to the Top ↑