In both Cocoa and the lower-level C APIs on Mac OS X, there are a lot of paired "begin" and "end" messages and functions. For example, if you want to use a transparency layer, you'll probably do something like this:
CGContextBeginTransparencyLayer(ctx, NULL);
// draw some things...
CGContextEndTransparencyLayer(ctx);
While that's fairly readable, if you want to do some more complex drawing, a more "nested" structure will quickly emerge. Throw in some state saving and restoring, and it can quickly become a mess. Here, I'm drawing an icon with a shadow, a gradient fill, and a little highlight on the top, sort of like the sidebar in Twitter for Mac:
CGContextSaveGState(ctx);
// set the shadow
CGContextBeginTransparencyLayer(ctx, NULL);
CGContextSaveGState(ctx);
CGContextClipToMask(ctx, maskBounds, mask);
// fill the context with the highlight color
CGContextRestoreGState(ctx);
CGContextSaveGState(ctx);
maskBounds.origin.y -= 1.0;
CGContextClipToMask(ctx, maskBounds, mask);
// draw the primary gradient
maskBounds.origin.y += 1.0;
CGContextRestoreGState(ctx);
CGContextEndTransparencyLayer(ctx);
CGContextRestoreGState(ctx);
Well, that's a bit of a mess. If you've used Photoshop, you know how to visualize a final composited image as layers. Core Graphics is more powerful1, because it doesn't restrict you to a single stack, but instead allows a tree2.
The obvious solution is to simply treat the begin/end function pairs as control structures, and indent the inner drawing code. While this gets the job done, Xcode3 won't like it, and every time you dare type a semicolon it will "fix" the indentation for you. If you cut and paste the code, it'll do the same thing, except this time you'll get all of your lines helpfully reindented (this is fun with multiline escaped macros).
I wanted a better solution. So I wrote these two simple functions:
typedef void(^CGStateBlock)();
void CGContextState(CGContextRef ctx, CGStateBlock actions)
{
CGContextSaveGState(ctx);
actions();
CGContextRestoreGState(ctx);
}
void CGContextTransparencyLayer(CGContextRef ctx, CGStateBlock actions)
{
CGContextBeginTransparencyLayer(ctx, NULL);
actions();
CGContextEndTransparencyLayer(ctx);
}
It's incredibly simple. Wrap a block with the begin/end functions. The result is that the previous block of code is turned into this:
CGContextState(ctx, ^{
// set the shadow
CGContextTransparencyLayer(ctx, ^{
CGContextState(ctx, ^{
CGContextClipToMask(ctx, maskBounds, mask);
// fill the context with the highlight color
});
CGContextState(ctx, ^{
maskBounds.origin.y -= 1.0;
CGContextClipToMask(ctx, maskBounds, mask);
// draw the primary gradient
maskBounds.origin.y += 1.0;
});
});
});
The structure is instantly clear. I can see exactly how each "layer" of the final rendering comes together. If I added the actual drawing code, the difference would be even clearer as the ratio of state saves/restores to drawing code changed. The beginning and ending of each nested state is obvious, where before it had to be determined either by reading from the top of by the use of extra newlines.
- Obviously, but I'm talking about the "user interface" of CG versus that of Photoshop.
- Hacking it with smart objects is a UX disaster and doesn't count. OK, maybe it does, since's it a lot better than nothing. Seriously though, someone needs to write "Photoshop for programmers". Not a book, an app. Basically Quartz Composer melded with Photoshop. I'd like to do it myself, and I know exactly how I'd like it to work, but writing a graphics editor is a tad bit of work.
- vim and emacs won't like it by default either, although I suppose it's probably not too hard to fix in either. The lack of good completion keeps me away from vim for Cocoa code.