What paradigm behind NSColor's drawing methods?
-
I've always been amazed by -[NSColor set], which "Sets the color of
subsequent drawing to the color that the receiver represents".
This seems absurd. Instead of affecting the receiver, the receiver is
affecting some other object, and the identify of the affected object
is not even hinted at.
Instead, I would expect there to be a method like like:
-[NSGraphicsContext setColor:(NSColor*)]
and then you could also have a getter (and I cannot find a real-life
equivalent of this),
-(NSColor*)[NSGraphicsContext color]
I've just re-read the Cocoa Drawing Guide but still can't comprehend
the paradigm behind NSColor's -set, -setFill, -setStroke etc. Can
someone please straighten me out? Or are these methods the
unfortunate legacy of some bad decision made 15 years ago?
Jerry Krinock -
On Feb 1, 2008, at 1:41 PM, Jerry Krinock wrote:> I've always been amazed by -[NSColor set], which "Sets the color of
> subsequent drawing to the color that the receiver represents".
>
> This seems absurd. Instead of affecting the receiver, the receiver
> is affecting some other object, and the identify of the affected
> object is not even hinted at.
>
> Instead, I would expect there to be a method like like:
>
> -[NSGraphicsContext setColor:(NSColor*)]
>
> and then you could also have a getter (and I cannot find a real-life
> equivalent of this),
>
> -(NSColor*)[NSGraphicsContext color]
>
> I've just re-read the Cocoa Drawing Guide but still can't comprehend
> the paradigm behind NSColor's -set, -setFill, -setStroke etc. Can
> someone please straighten me out? Or are these methods the
> unfortunate legacy of some bad decision made 15 years ago?
Pretty much none of the drawing operations take a context - you don't
do "[[[NSGraphicsContext] currentContext] fillPath: aBezierPath]" nor
is there "[[[NSGraphicsContext currentContext] drawImage: anImage
atPoint: aPoint fromRect: aRect ...]" nor similar for string drawing.
Also, none of the "procedural" drawing routines (NSFillRect, etc...)
take a graphics context.
Basically, anything that draws effects the the current context, and if
you want to change that for setting color, you'd want to do it
everywhere, and adding an explicit context to every single drawing
related operation would be a major pain.
The paradigm behind NSColor is no different than any of the other
ones. It doesn't seem absurd, or even out of line for how other
graphics based APIs work (after all, glSetColor() doesn't take a
glContext either, nor did Quickdraw SetColor(), etc...). You could
wonder if (fill and stroke) color should be an attribute of the bezier
path instead, like line width is (or like it was on QuickdrawGX), but
that's a different philosophy.
And in reality, it works well as is - I've never had a problem where I
set the color of the wrong drawing context (the only problem I've ever
has was not remembering to set it appropriately beforehand). What
circumstances are you running into that not having an explicit drawing
context parameter is a problem?
Glenn Andreas <gandreas...>
<http://www.gandreas.com/> wicked fun!
quadrium | prime : build, mutate, evolve, animate : the next
generation of fractal art -
On 2008 Feb, 01, at 12:47, glenn andreas wrote:> What circumstances are you running into that not having an explicit
> drawing context parameter is a problem?
Drawing a focus ring, in -drawRect of a custom control...
[NSGraphicsContext saveGraphicsState];
NSSetFocusRingStyle(NSFocusRingOnly);
[[NSColor keyboardFocusIndicatorColor] set];
NSFrameRect([self visibleRect]);
[NSGraphicsContext restoreGraphicsState];
In the Cocoa Drawing Guide I read that:
"Important: Saving and restoring the current graphics state is an
expensive operation and should done as little as possible. With the
exception of clipping paths, most state attributes can be undone
manually in your code without saving and restoring the entire state."
They seem to be suggesting that instead of -saveGraphicsState and -
restoreGraphicsState, I should manually "get" the color and the
FocusRingStyle before "setting", so I can set them back to their
original values after I am done. But I can't do that because there
are no getters for these.
As far as the rest of your reply, OK I understand that there are many
other methods that use the drawing context as an implied "object",
that this is done for convenience, and I should orient my thinking
into this paradigm. Thanks for the explanation. -
On Feb 1, 2008, at 3:18 PM, Jerry Krinock wrote:>
> On 2008 Feb, 01, at 12:47, glenn andreas wrote:
>
>> What circumstances are you running into that not having an explicit
>> drawing context parameter is a problem?
>
> Drawing a focus ring, in -drawRect of a custom control...
>
> [NSGraphicsContext saveGraphicsState];
> NSSetFocusRingStyle(NSFocusRingOnly);
> [[NSColor keyboardFocusIndicatorColor] set];
> NSFrameRect([self visibleRect]);
> [NSGraphicsContext restoreGraphicsState];
>
> In the Cocoa Drawing Guide I read that:
>
> "Important: Saving and restoring the current graphics state is an
> expensive operation and should done as little as possible. With the
> exception of clipping paths, most state attributes can be undone
> manually in your code without saving and restoring the entire state."
>
> They seem to be suggesting that instead of -saveGraphicsState and -
> restoreGraphicsState, I should manually "get" the color and the
> FocusRingStyle before "setting", so I can set them back to their
> original values after I am done. But I can't do that because there
> are no getters for these.
>
There's actually a lot of things that you can't do the "get old state,
change, set back when done", including setting a shadow and (in some
cases) change the transform (you can set the inverse of the transform
to "undo", but that introduces precision errors and assumes that the
transform has an inverse, which it won't for some degenerate cases),
so I'd actually consider the guide to be misleading...
Note that the underlying Quartz API is the same way - there are very
few "get current state" operations in comparison to the things you can
set (9 CGContextGet... vs 36 CGContextSet...) and that
CGContextSaveState and CGContextRestoreState are used all over the
place (and I can't find any comment suggesting that you avoid using
them because they are expensive - on the contrary, there are many
comments about how you need to use them because that's the only way to
do something).
I'd suggest filing a bug against any api that doesn't have a "get
current" version, as well as against that documentation (since you
obviously found that it didn't synch with your experience).
Remember, don't expect things to change if you don't file bugs - and
filing them is like voting in Chicago (vote early, vote often).
In the mean time, use saveGraphicsState/restoreGraphicsState when you
have to and don't worry about performance until Shark tells you it is
a problem...
Glenn Andreas <gandreas...>
<http://www.gandreas.com/> wicked fun!
quadrium | prime : build, mutate, evolve, animate : the next
generation of fractal art -
> [NSGraphicsContext saveGraphicsState];
> NSSetFocusRingStyle(NSFocusRingOnly);
> [[NSColor keyboardFocusIndicatorColor] set];
> NSFrameRect([self visibleRect]);
> [NSGraphicsContext restoreGraphicsState];
I this specific case, just don't bother restoring the graphics state. Code it like this:
NSSetFocusRingStyle(NSFocusRingOnly);
[[NSColor keyboardFocusIndicatorColor] set];
NSFrameRect([self visibleRect]);
If you plan to perform any drawing after drawing the focus ring, you can set whatever color you want then. Any other drawing by your classes or framework classes is forced to set the color and other state information it needs because drawing in different drawRect: implementation has no way of knowing what the current graphics state is. Therefore, it doesn't matter what state you leave behind.
Most importantly of all, there is already an implicit [NSGraphicsContext saveGraphicsState]; and [NSGraphicsContext restoreGraphicsState]; pair around drawRect:. That's one of the reasons you shouldn't call -drawRect: directly yourself. Call -display or -setNeedsDisplay: and they will take care of setting up the graphics state and tearing it down. -
On Feb 1, 2008, at 11:41 AM, Jerry Krinock wrote:> I've just re-read the Cocoa Drawing Guide but still can't
> comprehend the paradigm behind NSColor's -set, -setFill, -setStroke
> etc. Can someone please straighten me out? Or are these methods
> the unfortunate legacy of some bad decision made 15 years ago?
Unlike some other frameworks, NSColor can be subclassed, and
NSGraphicsContext cannot know how to handle arbitrary NSColor
subclasses.
For example, [[NSColor selectedMenuItemColor] set] sets a pattern
color into the context, with the actual pattern depending on several
factors, including whether Aqua or Graphite is chosen in System
Prefs. It also modifies the pattern phase of the context. There's
no way for NSGraphicsContext to know it needed to do those things for
an arbitrary NSColor.
"Dumb" value-type colors could be set directly on the graphics
context (which is more or less what Quartz does). If you have
"smart" object colors, that can do arbitrarily sophisticated things
to the graphics context, then those smarts need to live in the color
class itself.
-Peter -
I may have to change the way I have been drawing focus rings. A quick
search and Apple's examples indicates that it is necessary to save the
graphics state before drawing a focus ring and restore it after. This
must be particularly true if you draw the focus ring outside the
visible rect and/or clipping path of the focused view. -
On Feb 1, 2008, at 8:29 PM, Erik Buck wrote:> I may have to change the way I have been drawing focus rings. A
> quick search and Apple's examples indicates that it is necessary to
> save the graphics state before drawing a focus ring and restore it
> after. This must be particularly true if you draw the focus ring
> outside the visible rect and/or clipping path of the focused view.
I believe it is indeed the case due to the focus ring being drawn
outside the view's actual bounds.
I used to see bugs (in Tiger and earlier) when full keyboard access
was turned on, a control had keyboard focus and then you used the
mouse to track it. If you clicked on the control and while keeping
the mouse down, tracked on/off the control repeatedly, the focus ring
would re-render itself over and over without first clearing the area.
This led to an ever-darkening focus ring.
I do not see this in Leopard anymore, so perhaps the ability to now
have overlapping views had something to do with the fix?
Anyhow, when I first worked on my custom UI, I had struggled with the
focus ring being outside and ultimately "cheated" since I needed my
own look. I never use the provided focus ring APIs; instead, I just
render a bezier path _inside_ the view's bounds. And, since my UI
controls are physically large, the extra few pixels around the actual
control's body just added a bit of "slop" area to the hit region.
___________________________________________________________
Ricky A. Sharp mailto:<rsharp...>
Instant Interactive(tm) http://www.instantinteractive.com -
On 2008 Feb, 01, at 13:44, glenn andreas wrote:> I'd suggest filing a bug against any api that doesn't have a "get
> current" version, as well as against that documentation (since you
> obviously found that it didn't synch with your experience).
Well, if one is wrong then the other is correct, and only Apple knows
which is which. So I submitted Bug ID# 5722899 suggesting that they
either provide more getters, or else delete that advice from the Cocoa
Drawing Guide.


