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.
previous month february 2008 next month
MTWTFSS
        1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29    
Go to today