July 31, 2005

Pimp My Code, Part 3: Gradient TableViews

This week, I received mail from a padawan learner (aka, "youngling") who has written a tableView class which draws the cool gradient background behind selected cells, like you see in the left-hand column of Mail 1.0 or iPhoto or iTunes or the Finder.

Yes, some of you may be saying, "Well, if gradient backgrounds are the standard when you're selecting a new data source to drive other UI elements, why doesn't Apple just build them into NSTableView?" Hah! Impatient, you are. Much to learn, you have.

Sadly, the AppKit guys aren't that "big" on adding features just because every program that "ships" with the Mac implements them. For instance, you may also notice that in Mail when you drag a message to a mailbox (or in Finder when you drag a file onto the shelf-thing at the left) you get a nice, rounded-corner blue border around the selected row, whereas in YOUR Cocoa programs you get an ugly black square. No rounded-corners for you!

So, without much further ado, here is the NSTableView subclass, as submitted:

NSTableView subclass, BEFORE
@implementation ACSourceListOutlineView

- (void)awakeFromNib
{
// source lists have different cell spacing
[self setIntercellSpacing:NSMakeSize(0.0, 1.0)];

// use custom gradiented text cell
[[self outlineTableColumn] setDataCell:
[[[ACSourceListTextCell alloc] init] autorelease]];
}

- (id)_highlightColorForCell:(NSCell *)cell
{
return nil;
}

- (void)highlightSelectionInClipRect:(NSRect)clipRect
{
// don't do anything when we're not selected
// this check may be a bit superfluous, but it's still a good idea
to keep it here -- the documentation doesn't say that when this
method is called, the outline view will have a row selected
// also notice that multiple selection is disallowed; we can safely
use [self selectedRow]
int selectedRow = [self selectedRow];
if(selectedRow == -1) return;

[self lockFocus];

// get the gradient image
// we should only draw a blue gradient when our view is active
NSImage *gradient;
if(([[self window] firstResponder] == self) && [[self window]
isMainWindow] && [[self window] isKeyWindow])
{
gradient = [NSImage imageNamed:@"highlight_blue.tiff"];
}
else
{
gradient = [NSImage imageNamed:@"highlight_grey.tiff"];
}
[gradient setScalesWhenResized:YES];
[gradient setFlipped:YES];

// get the rect we're drawing in
NSRect drawingRect = [self rectOfRow:selectedRow];

// get gradient size
NSSize gradientSize = [gradient size];

// get image rect
NSRect imageRect;
imageRect.origin = NSZeroPoint;
imageRect.size = gradientSize;

// draw
if(drawingRect.size.width != 0 && drawingRect.size.height != 0)
{
[gradient drawInRect:drawingRect
fromRect:imageRect
operation:NSCompositeSourceOver
fraction:1.];
}

[self unlockFocus];
}

@end

First off, note that there's some funny spacing going on here, which I strongly discourage. Don't try to line up your "=", and don't split up a single method onto multiple lines. I know it seems easy to read the parameters, but, really, when you're looking at the code your biggest problem is getting the gestalt of what's going on, not reading each parameter. When you glance at a method that's broken up
 it
makes
reading
slower
like
this
because
we're
used
to
having
multiple words
on a
single line.

Annoying, isn't it? Don't write code like you're ee cummings. You aren't. (That is the deepest secret nobody knows.)

The code itself is generally pretty reasonably written, except for other spacing-type nits, and the fact that it's not broken up by what is being subclassed, which makes it hard for newbies to figure out why certain methods exist at all. "Hey, why do we implement -_highlightColorForCell:? We never call it." Yes, but it's a private method that gets called by NSTableView, and if you don't override it you'll get drawing glitches when the NSTableView tries to highlight the cell AND your code draws its gradient highlight.

So, here's the code as I might rewrite it, knowing nothing about what it does. Note that I'm assuming line wrap is ON in your editor, and you have indent on wrap set to four spaces. The multi-line statements you see below are me simulating line wrapping (on a VERY narrow window!):

NSTableView subclass, JUST COSMETIC FIXES
@implementation ACSourceListOutlineView

// NSObject (NSNibAwaking)

- (void)awakeFromNib
{
// source lists have different cell spacing
[self setIntercellSpacing:NSMakeSize(0.0, 1.0)];

// use custom gradiented text cell
[[self outlineTableColumn] setDataCell:
[[[ACSourceListTextCell alloc] init] autorelease]];
}


// NSTableView

- (void)highlightSelectionInClipRect:(NSRect)clipRect
{
// don't do anything when we're not selected
// this check may be a bit superfluous, but it's still a good idea
to keep it here -- the documentation doesn't say that when this
method is called, the outline view will have a row selected
// also notice that multiple selection is disallowed; we can safely
use [self selectedRow]
int selectedRow = [self selectedRow];
if (selectedRow == -1) return;

[self lockFocus]; {
// we should only draw a blue gradient when our view is active
NSImage *gradient;
if (([[self window] firstResponder] == self) && [[self window]
isMainWindow] && [[self window] isKeyWindow])
gradient = [NSImage imageNamed:@"highlight_blue.tiff"];
else
gradient = [NSImage imageNamed:@"highlight_grey.tiff"];
[gradient setScalesWhenResized:YES];
[gradient setFlipped:YES];

NSRect drawingRect = [self rectOfRow:selectedRow];
NSSize gradientSize = [gradient size];

// draw
if(!NSIsEmptyRect(drawingRect))
[gradient drawInRect:drawingRect fromRect:(NSRect){NSZeroPoint,
gradientSize} operation:NSCompositeSourceOver fraction:1.0];

} [self unlockFocus];
}


// NSTableView (private)

- (id)_highlightColorForCell:(NSCell *)cell
{
return nil;
}

@end

Ok, I changed some more stuff. I don't like extra braces around one-line if statements. I know, that way you can add extra lines any time you want and you don't have to think blah blah blah. But we're trying to make our code more READABLE. That's the key. You can spend 10 seconds adding braces if you end up writing another line. It's like you're slicing bread before every meal just in case you decide to make a sandwich, and then 90% of the time you just throw the bread away. Seriously, people, pretend that each line of code you write represents another tiny portion of W's shrunken soul that you are throwing away. We're talking about a precious commodity! Conserve it!

Note that I use NSIsEmptyRect() now. First off, it's almost always a bad idea to compare a float to zero using "myFloat == 0.0". Floats often have tiny round-off errors. Look it up in your C books. For example, is "(1.0 - 0.5 - 0.5) == 0.0" true? NOT NECESSARILY. You should use "myFloat < 0.0001" or some such. Or, in this case, use NSIsEmptyRect, which saves you like five words.

I got rid of the pedantic comments, but kept the pithy ones. Seriously, "get gradient size"? That's what "gradientSize = [gradient size]" means? Because, I'm thinking, if you can't figure that out, you not only can't write Objective-C, you can't read english.

Also, I toasted "imageRect" altogether. Not only did I save three lines (W's soul, people! His essence!), but, also, I closed off the question of "is imageRect going to be used in multiple places?" that the old code begged. And, as an added bonus, my code runs a TEENY BIT faster in some cases (left as exercise to reader to figure that out). I know I told you not to optimize while you code, but, honestly, if you can make smaller, clearer, AND faster code all at once... you go, girlfriend.

--

Now, many would be content to stop there, but as it happens I'm the author of one of the first gradient tableViews out there, and mine is open source, courtesy of The Omni Group. Please see their source license on how this can be used:

NSTableView subclass, UBER WIL SHIPLEY VERSION
@interface OAGradientTableView (Private)
- (void)_windowDidChangeKeyNotification:(NSNotification *)notification;
@end

// CoreGraphics gradient helpers

typedef struct {
float red1, green1, blue1, alpha1;
float red2, green2, blue2, alpha2;
} _twoColorsType;

void _linearColorBlendFunction(void *info, const float *in, float *out)
{
_twoColorsType *twoColors = info;

out[0] = (1.0 - *in) * twoColors->red1 + *in * twoColors->red2;
out[1] = (1.0 - *in) * twoColors->green1 + *in * twoColors->green2;
out[2] = (1.0 - *in) * twoColors->blue1 + *in * twoColors->blue2;
out[3] = (1.0 - *in) * twoColors->alpha1 + *in * twoColors->alpha2;
}

void _linearColorReleaseInfoFunction(void *info)
{
free(info);
}

static const CGFunctionCallbacks linearFunctionCallbacks = {0,
&_linearColorBlendFunction, &_linearColorReleaseInfoFunction};


@implementation OAGradientTableView

// NSObject

- (void)dealloc;
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[super dealloc];
}


// NSView

- (void)viewWillMoveToWindow:(NSWindow *)newWindow;
{
[[NSNotificationCenter defaultCenter] removeObserver:self
name:NSWindowDidResignKeyNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(_windowDidChangeKeyNotification:)
name:NSWindowDidResignKeyNotification object:newWindow];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:NSWindowDidBecomeKeyNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(_windowDidChangeKeyNotification:)
name:NSWindowDidBecomeKeyNotification object:newWindow];
}


// NSTableView

- (void)highlightSelectionInClipRect:(NSRect)rect;
{
// Take the color apart
NSColor *alternateSelectedControlColor = [NSColor
alternateSelectedControlColor];
float hue, saturation, brightness, alpha;
[[alternateSelectedControlColor
colorUsingColorSpaceName:NSDeviceRGBColorSpace] getHue:&hue
saturation:&saturation brightness:&brightness alpha:&alpha];

// Create synthetic darker and lighter versions
NSColor *lighterColor = [NSColor colorWithDeviceHue:hue
saturation:MAX(0.0, saturation-.12) brightness:MIN(1.0,
brightness+0.30) alpha:alpha];
NSColor *darkerColor = [NSColor colorWithDeviceHue:hue
saturation:MIN(1.0, (saturation > .04) ? saturation+0.12 :
0.0) brightness:MAX(0.0, brightness-0.045) alpha:alpha];

// If this view isn't key, use the gray version of the dark color.
Note that this varies from the standard gray version that NSCell
returns as its highlightColorWithFrame: when the cell is not in a
key view, in that this is a lot darker. Mike and I think this is
justified for this kind of view -- if you're using the dark
selection color to show the selected status, it makes sense to
leave it dark.
NSResponder *firstResponder = [[self window] firstResponder];
if (![firstResponder isKindOfClass:[NSView class]] ||
![(NSView *)firstResponder isDescendantOf:self] || ![[self
window] isKeyWindow]) {
alternateSelectedControlColor = [[alternateSelectedControlColor
colorUsingColorSpaceName:NSDeviceWhiteColorSpace]
colorUsingColorSpaceName:NSDeviceRGBColorSpace];
lighterColor = [[lighterColor
colorUsingColorSpaceName:NSDeviceWhiteColorSpace]
colorUsingColorSpaceName:NSDeviceRGBColorSpace];
darkerColor = [[darkerColor
colorUsingColorSpaceName:NSDeviceWhiteColorSpace]
colorUsingColorSpaceName:NSDeviceRGBColorSpace];
}

// Set up the helper function for drawing washes
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
_twoColorsType *twoColors = malloc(sizeof(_twoColorsType)); // We
malloc() the helper data because we may draw this wash during
printing, in which case it won't necessarily be evaluated
immediately. We need for all the data the shading function needs
to draw to potentially outlive us.
[lighterColor getRed:&twoColors->red1 green:&twoColors->green1
blue:&twoColors->blue1 alpha:&twoColors->alpha1];
[darkerColor getRed:&twoColors->red2 green:&twoColors->green2
blue:&twoColors->blue2 alpha:&twoColors->alpha2];
static const float domainAndRange[8] = {0.0, 1.0, 0.0, 1.0, 0.0, 1.0,
0.0, 1.0};
CGFunctionRef linearBlendFunctionRef = CGFunctionCreate(twoColors, 1,
domainAndRange, 4, domainAndRange, &linearFunctionCallbacks);

NSIndexSet *selectedRowIndexes = [self selectedRowIndexes];
unsigned int rowIndex = [selectedRowIndexes
indexGreaterThanOrEqualToIndex:0];

while (rowIndex != NSNotFound) {
unsigned int endOfCurrentRunRowIndex, newRowIndex = rowIndex;
do {
endOfCurrentRunRowIndex = newRowIndex;
newRowIndex = [selectedRowIndexes
indexGreaterThanIndex:endOfCurrentRunRowIndex];
} while (newRowIndex == endOfCurrentRunRowIndex + 1);

NSRect rowRect = NSUnionRect([self rectOfRow:rowIndex],
[self rectOfRow:endOfCurrentRunRowIndex]);

NSRect topBar, washRect;
NSDivideRect(rowRect, &topBar, &washRect, 1.0, NSMinYEdge);

// Draw the top line of pixels of the selected row in the
alternateSelectedControlColor
[alternateSelectedControlColor set];
NSRectFill(topBar);

// Draw a soft wash underneath it
CGContextRef context = [[NSGraphicsContext currentContext]
graphicsPort];
CGContextSaveGState(context); {
CGContextClipToRect(context, (CGRect){{NSMinX(washRect),
NSMinY(washRect)}, {NSWidth(washRect),
NSHeight(washRect)}});
CGShadingRef cgShading = CGShadingCreateAxial(colorSpace,
CGPointMake(0, NSMinY(washRect)), CGPointMake(0,
NSMaxY(washRect)), linearBlendFunctionRef, NO, NO);
CGContextDrawShading(context, cgShading);
CGShadingRelease(cgShading);
} CGContextRestoreGState(context);

rowIndex = newRowIndex;
}


CGFunctionRelease(linearBlendFunctionRef);
CGColorSpaceRelease(colorSpace);
}

- (void)selectRow:(int)row byExtendingSelection:(BOOL)extend;
{
[super selectRow:row byExtendingSelection:extend];
[self setNeedsDisplay:YES]; // we display extra because we draw
multiple contiguous selected rows differently, so changing
one row's selection can change how others draw.
}

- (void)deselectRow:(int)row;
{
[super deselectRow:row];
[self setNeedsDisplay:YES]; // we display extra because we draw
multiple contiguous selected rows differently, so changing
one row's selection can change how others draw.
}


// NSTableView (Private)

- (id)_highlightColorForCell:(NSCell *)cell;
{
return nil;
}

@end


@implementation OAGradientTableView (Private)

- (void)_windowDidChangeKeyNotification:(NSNotification
*)notification;
{
[self setNeedsDisplay:YES];
}

@end

Ok, right off the bat you probably are saying, "I may be a padawan learner (or youngling, as it were), but even I can see that your rewritten code is more lines of code than the code you were tasked with pimping.

Ah, no patience, you have. Always Objective-C, code is not! The source code I was sent requires two external images. The source code re-imagined requires NO external resources. (And it runs faster, has a smaller memory footprint, and prints faster, but, again, we're not thinking of speed yet, right?) Those external resources need to be maintained. They need to be installed in the right place. And, most importantly, they need to be understood. If you don't know what they look like, you have to pull them up in your editor, which means interrupting your flow.

Which leads me to an important rule: In general, if I can replace an image with code, I do so. This is not something that's intuitive, and it took me many years to decide that this is the best policy. Code is easier to change and understand than images are. And having fewer files in your project is EVEN BETTER than having fewer lines of code in a single file. Think of each file as a project-complexity multiplier, and each line of code as a project-complexity adder. Every file in the project has to be understood. (The most confusing projects I've seen are the ones that have 400 files, each of which has like one or two routines. That way leads madness.)

Also, note that the images are static -- they come in two colors, blue and grey. My code actually looks at the highlight color you set in preferences, and generates a dark and light version of that color. So, if you like to highlight in pink, my tableView draws in two shades of it. In contrast, the Finder and iPhoto and iTunes all use blue, even if your highlight color is yellow. Whaaaa? How is that clear?

Also note my tableView registers for notifications when the window becomes and loses "key" state, which is when your highlighted items are supposed to switch to grey instead of colored. The code I was given just hopes that the tableView will be asked to redraw itself in this case, which is often the case but not always. My code also checks to see if the firstResponder is a subview of this tableView, so if you're editing text inside one of the cells of this tableView, mine will still draw the colorful gradient (his goes gray in this case).

And, mine does a really cool effect where it does a gentle was across ALL the selected cells in a tableView, even if they are discontiguous. He documents that his won't work in this case (which is a fine way to save on coding; the first version of mine did the same thing), but it's cooler to handle multiples, because forcing single selection is almost always teh suck.

Finally, I just think it's cooler to use CG to do gradients (they call them "shadings") than to use images. For one thing, it's hella fast (then again, drawing a stretched image is pretty fast, too). More importantly, it's resolution-independent, which will be a Big Deal in 10.5 ("Liger"). But even right now, you could print my tableView on a super-hi-res printer and the gradients would look boo-ty-full (and the PDF files would be small).

My tableView is actually pretty old code, and I didn't want to modify it right before publishing it, because that's like adding "one last feature" before doing a major release. (Eg, invitation to disaster.) Can you make this code more efficient? Maybe there are newer APIs on CGShading which require less crap... I wrote this about three years ago, and I was young and foolish, then.

Now, I'm just old and foolish. Youngling.

Labels:

32 Comments:

Anonymous Brehaut said...

To be a hella pedantic, C provides in 'float.h' the #define'd constants FLT_EPSILON, DBL_EPSILON, and LDBL_EPSILON that represent the error of their respective types.

so to compare a float with zero you would go something like
if ((0 + FLT_EPSILON > myfloat) && (0 - FLT_EPSILON < myfloat)) { dostuff(); }

Ok, so you don't need the zero's for that case, but in general.

July 31, 2005 4:52 AM

 
Blogger Davey said...

While we're on the table subject, could you reveal the way to change the mouse cursor when dragging over an item in the table view? I want it to be something different than that damn plus cursor... :-)

July 31, 2005 8:07 AM

 
Blogger Mike Lee said...

It also bears note that one should not write code like Alan Cumming.

self = [super initWithCockneyAccentGuv'nah]

Also, one shouldn't write as like one is cumming.

self = self = self = self = oooooooooooohyeah!

July 31, 2005 8:30 AM

 
Anonymous Anonymous said...

Who is W? Wil?

July 31, 2005 10:56 AM

 
Blogger Wil Shipley said...

brehaut:

Actually, I'd use:if (ABS(myFloat) < SOME_EPSILON) ...if I wanted to check equality.

I wouldn't use FLT_EPSILON, though; I think your reading of FLT_EPSILON is incorrect. Gnu says: This is the minimum positive floating point number of type float such that 1.0 + FLT_EPSILON != 1.0 is true. It's supposed to be no greater than 1E-5.

Imagining we're in the realm of integers, and there's an INT_EPSILON defined the same way, so INT_EPSILON would be 1. So imagine the statment (again, using only integers):if ((0 + 1 > myInt) && (0 - 1 < myInt)) ...

That's exactly mathematically equivalent to (0 == myInt).

Similarly, the statement you wrote is mathematically exactly equivalent to (myFloat == 0), so you haven't actually solved the round-off problem.

Remember, the amount of error can be arbitrarily large -- I could start with a myFloat = 100000.0 and subtract 0.1 from it 1000000 times, and the error would be 10000x if I started with 1.0 and subtracted 0.1 10 times. So, you really have to pick your epsilon based on the context.

For example, in graphics, if a pixel's size is 1.0x1.0, it's often reasonable to assume that 0.00001 should just go ahead and be on an even pixel boundary.

July 31, 2005 12:37 PM

 
Anonymous Anonymous said...

Actually, I'd use:if (ABS(myFloat) < SOME_EPSILON) ...if I wanted to check equality.

Since we're having such a good time being so pedantic, that should be fabs(myFloat), not abs(myFloat).

July 31, 2005 8:49 PM

 
Blogger Drew Hamlin said...

anonymous, note that abs() and ABS() are very different. Also note that fabs() is for double comparison, not floats.

If you don't know it, there is a super-cool trick in Xcode to figure this stuff out (that Wil actually first showed me): command-double-click a method.

July 31, 2005 8:53 PM

 
Anonymous Anonymous said...

MIN(1.0, (saturation > .04) ? saturation+0.12 : 0.0)

What's this do? What are the question mark and colon for?

August 01, 2005 8:41 AM

 
Anonymous Marcus S. Zarra said...

That is an inline conditional.

It reads like this:

if (saturation > .04)
return saturation+0.12;
else
return 0.0;

The only difference is that instead of returning to a method, the result of the inline conditional is plugged into the function call MIN as a parameter.

It is pretty basic C syntax. I am sure most books on C explain it more throughly.

August 01, 2005 9:25 AM

 
Anonymous kevin said...

This question may have an obvious answer, but i'll ask anyway. How do you know about NSTableView's private methods? Moreover, how do you know which ones have to be overridden?

August 01, 2005 9:35 AM

 
Anonymous Anonymous said...

?: is the conditional expression operator in C. See here for more details.

August 01, 2005 12:26 PM

 
Anonymous Anonymous said...

I generally lean towards drawing methods having as little code in them as possible, so I'd actually spend the memory for ivars to hold the colors and the CGShading, rather than create them each time you get to -highlightSelectionInClipRect:

Other than that, I agree with Wil that code is preferrable to images, in most cases.

-jcr

August 01, 2005 8:15 PM

 
Anonymous Anonymous said...

Wil,
I find your ideas very interesting but don't get one important thing. You talk about code beauty, about readability and your blog is in colors (white text on the black background) which make my eyes crying...

August 02, 2005 1:00 AM

 
Anonymous Anonymous said...

@ Anon - I actually code with white on black, I find it easier on the eyes.

in Mail when you drag a message to a mailbox (or in Finder when you drag a file onto the shelf-thing at the left) you get a nice, rounded-corner blue border around the selected row, whereas in YOUR Cocoa programs you get an ugly black square.

Any chance someone (Wil or otherwise) wants to fill us in on how to override the appearance of said black square?

August 02, 2005 8:57 PM

 
Blogger Mike Marmarou said...

I didn't see a comment for it, but to really make this look like Apple, it might be nice to change the font color to white on all selected rows. Here is a little code that'll do that. Note that the column "playing" is an NSImageCell and should not have it's font color set.

- (void)tableView:(NSTableView *)aTableView willDisplayCell:(id)aCell
forTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex
{
NSString *identifier = [aTableColumn identifier];
NSIndexSet *selectedRows = [aTableView selectedRowIndexes];

if (![identifier isEqualToString:@"playing"]) {
if([selectedRows containsIndex:rowIndex])
[aCell setTextColor:[NSColor whiteColor]];
else
[aCell setTextColor:[NSColor blackColor]];
}
}

August 03, 2005 9:45 AM

 
Anonymous Anonymous said...

Mike:

It's not the controller who should decide whether the text shall be black or white when selected. It's the cell's job.

August 05, 2005 4:33 AM

 
Blogger Mike Marmarou said...

Does it really matter? And why would I go through the trouble of subclassing the table cell when this works perfectly? There is no point when this code won't function properly, and it allows me to easily change other things, like font, font size, adding images, etc, without creating a new subclass.

August 05, 2005 11:51 PM

 
Blogger boaz said...

Just drop this method into a subclass of OAGradientTableView, and it'll automatically set the text color for selected rows:

- (void)drawRow:(int)rowIndex clipRect:(NSRect)clipRec
{
NSRange columnsToFixTextColorIn = [self columnsInRect:clipRec];
NSColor* newTextColor = ([[self selectedRowIndexes] containsIndex:rowIndex]) ? [NSColor alternateSelectedControlTextColor] : [NSColor textColor];

int i;
for (i = columnsToFixTextColorIn.location; i < columnsToFixTextColorIn.location + columnsToFixTextColorIn.length; i++) {
NSCell* rowCell = [[[self tableColumns] objectAtIndex:i] dataCellForRow:rowIndex];
if ([rowCell isKindOfClass:[NSTextFieldCell class]]) {
[(NSTextFieldCell*)rowCell setTextColor:newTextColor];
}
}
[super drawRow:rowIndex clipRect:clipRec];
}

And man does Blogger make it tough to enter source code. It took me longer to get this formatted than to write it. And I'm still not sure if it's going to get mangled when I hit post.

August 06, 2005 7:09 PM

 
Blogger Arenzera said...

Wil,

I confess that I am a youngling and that I have much to learn. Can you please explain what you meant by saying this:

"Yes, some of you may be saying, "Well, if gradient backgrounds are the standard when you're selecting a new data source to drive other UI elements, why doesn't Apple just build them into NSTableView?" Hah! Impatient, you are. Much to learn, you have."

Thank you,

Kiel Gillard (a student developer who was lucky enough to attend your excellent Student Sunday presentation at WWDC).

August 14, 2005 8:16 AM

 
Anonymous Dominik Wagner said...

Hi Wil!

where do you get that CGShading is quicker than drawing a stretched pixel image? Do you have any numbers backing this? I admit that it is cleaner and resolution independent, but really quicker?

August 21, 2005 6:53 AM

 
Blogger Davey said...

Arenzera,

I think Wil was describing (with clever Yoda-style language ;-) how Apple always takes their sweet time before giving us more standard eye-candy and functionality to use in our apps.

The one example that sticks out in my head was the blue-and-white-striped-NSTableView, which didn't become "standard" in Cocoa until Panther.

- Davey (also a student developer, but not old enough to attend WWDC :-( .

August 21, 2005 8:59 AM

 
Blogger Wil Shipley said...

Dominik:

I haven't done precise timing tests on CGShading, but I did roughly test it out when I stuck it in Graffle 3.0, and was pretty happy with it. I don't honestly remember the comparison between it and pixel shading.

I think it is _more likely_ to be optimized in _more cases_ than the pixel-stretch technique, either now or in the future, but I could be wrong.

-W

August 21, 2005 3:26 PM

 
Blogger Arenzera said...

Ta

August 22, 2005 1:54 AM

 
Anonymous David Dunham said...

So how do you prevent the NSWindowDidResignKeyNotification from causing menu extras from redrawing your entire window (and changing your gradient look)? Mail.app seems to manage this...

January 21, 2006 10:21 PM

 
Anonymous Michael said...

What about when a NSTextFieldCell is selected and we would like the text to change to the highlight color? Some sort of Apple magic seems to accomplish this after returning from _highlightColorForCell.

April 06, 2006 3:53 PM

 
Anonymous Michael said...

What about when a NSTextFieldCell is selected and we would like the text to change to the highlight color? Some sort of Apple magic seems to accomplish this after returning from _highlightColorForCell.

April 06, 2006 3:56 PM

 
Anonymous Anonymous said...

I'm glad someone showed me this page. It sums up most of the principles of good programming and goes through a lot of the symptoms of bad programming.

But I can still program better than you, Wil. In my sleep.

August 21, 2006 11:05 PM

 
Blogger Wil Shipley said...

But I can still program better than you, Wil. In my sleep.

Wow!

August 22, 2006 12:05 AM

 
Anonymous Anonymous said...

Wow!

Like I said, it's really no big deal. I mostly work on programming better than myself. That's more of a challenge.

August 24, 2007 11:42 PM

 
Anonymous Anonymous said...

?: is the conditional expression operator in C.

Actually it's called the ternary operator. For good reason.

August 24, 2007 11:45 PM

 
Blogger Wil Shipley said...

Because it's beloved by shore birds?

August 31, 2007 3:09 AM

 
Blogger Wil Shipley said...

Like I said, it's really no big deal. I mostly work on programming better than myself. That's more of a challenge.

You should start a blog to pass on your wisdom, and expose yourself to the judgments of others.

August 31, 2007 3:12 AM

 

Post a Comment

<< Home