NSBezierPath Not Drawing Correctly With NSTextField

Member
Posts: 64
Joined: 2005.06
Post: #1
I've been fighting this for a while now and I cannot come up with a solution as to why Quartz is not rendering correctly.

My problem is, I'm drawing a custom focus ring for an NSTextField, but it doesn't display correctly unless I resize the window first. I think that possibly, only the NSTextField (which has no background or bezel by the way) is being updated when the focus on it changes, but nothing I do makes it correct itself except for resizing the window.

This is what it's supposed to look like when the NSTextField is not first responder:
[Image: correct_nofocus.jpg]

This is what it's supposed to look like when it does have focus:
[Image: correct_highlight.jpg]

But this is what it looks like after losing focus, if I don't resize the window:
[Image: error_nofocus.jpg]

If the NSTextField becomes the first responder, this is what it looks like if you don't resize the window:
[Image: error_highlight.jpg]

The rest of the drawing code doesn't matter, but this is the section that deals directly with the NSTextField and the new background and bezel, and focus ring. By the way, this gets called from - (void)drawRect:(NSRect)aRect
Code:
- (void)drawValueField
{
    NSRect bounds = [self bounds];
    NSPoint center = NSMakePoint(NSMidX(bounds), NSMidY(bounds));
    NSBezierPath *control = [NSBezierPath bezierPath];
    
    double radius;
    
    if (labelStringAbsSize.height > valueStringAbsSize.height)
    {
        radius = labelStringAbsSize.height / 1.5;
    }
    else
    {
        radius = valueStringAbsSize.height / 1.5;
    }
    
    if ([[valueField window] isKeyWindow] && [[[valueField window] firstResponder] delegate] == valueField)
    {
        [[self valueFieldHighlightColor] set];
        [control setLineWidth: 2.5];
    }
    else
    {
        [[self valueFieldOutlineColor] set];
        [control setLineWidth: 1.0];
    }

    [control appendBezierPathWithArcWithCenter: NSMakePoint(radius * 5.0 + [self bodyPaddingSize] * 2.0 + labelStringAbsSize.width, center.y) radius: radius startAngle: 90.0 endAngle: 270.0];
    [control appendBezierPathWithArcWithCenter: NSMakePoint(bounds.size.width - radius * 2.0, center.y) radius: radius startAngle: 270.0 endAngle: 90.0];
    [control closePath];
    [control stroke];
    
    [control setLineWidth: 1.0];
    
    [control removeAllPoints];
    
    [[self valueFieldBackgroundColor] set];
    [control appendBezierPathWithArcWithCenter: NSMakePoint(radius * 5.0 + [self bodyPaddingSize] * 2.0 + labelStringAbsSize.width, center.y) radius: radius startAngle: 90.0 endAngle: 270.0];
    [control appendBezierPathWithArcWithCenter: NSMakePoint(bounds.size.width - radius * 2.0, center.y) radius: radius startAngle: 270.0 endAngle: 90.0];
    [control closePath];
    [control fill];

    [valueField setFrame: NSMakeRect(radius * 5.0 + [self bodyPaddingSize] * 2.0 + labelStringAbsSize.width, center.y - valueStringAbsSize.height / 2.0, bounds.size.width - radius * 6.0 - [self bodyPaddingSize] * 4.0 - labelStringAbsSize.width, valueStringAbsSize.height)];
}

Take note that the NSTextField gets resized on every call; this is because changing the window size, the label or any number of other things also changes the size of the entire custom control, which means NSTextField must also change size.

Just for good measure, here's the code I use to set up the NSTextField (this gets called from initWithFrame:)
Code:
- (void)setValueStringAttributesWithString:(NSString *)newString color:(NSColor *)newColor size:(float)newSize
{
    NSFont *systemFont = [NSFont systemFontOfSize: newSize];
    NSAttributedString *attValueString;
    
    valueString = newString;
    valueStringColor = newColor;
    valueStringFontSize = newSize;
    
    [valueStringAttributes release];
    valueStringAttributes = [NSMutableDictionary dictionaryWithCapacity: 1];
    [valueStringAttributes setObject: systemFont forKey: NSFontAttributeName];
    [valueStringAttributes setObject: newColor forKey: NSForegroundColorAttributeName];
    
    [valueField release];
    valueField = [[NSTextField alloc] initWithFrame: NSMakeRect(0, 0, 50, 50)];
    
    attValueString = [[NSAttributedString alloc] initWithString: newString attributes: valueStringAttributes];
    
    [valueField setAllowsEditingTextAttributes: YES];
    [valueField setAttributedStringValue: attValueString];
    [valueField setTextColor: newColor];
    
    [attValueString release];
    
    [valueField setFocusRingType: NSFocusRingTypeNone];
    [valueField setBordered: NO];
    [valueField setBezeled: NO];
    [valueField setDrawsBackground: NO];
    
    valueStringAbsSize = [valueString sizeWithAttributes: valueStringAttributes];

    [self addSubview: valueField];
}
Quote this message in a reply
Moderator
Posts: 508
Joined: 2002.09
Post: #2
Use the delegate methods from NSTextField, such as:

– textShouldBeginEditing:
– textDidBeginEditing:
– textDidChange:
– textShouldEndEditing:
– textDidEndEditing:

which then call [self setNeedsDisplay:YES] (with self being the NSTextField ofcourse)

"When you dream, there are no rules..."
Quote this message in a reply
Member
Posts: 64
Joined: 2005.06
Post: #3
There's no delegate methods to detect when the NSTextField gains focus; textShouldBeginEditing and textDidBeginEditing, only get called once the NSTextField not only has focus, but the user has started to type in it. The only real way to check is in the drawing routine since it gets called regularly.

I have a strong feeling that the problem is that the NSTextField is refreshing, but nothing around it is refreshing. That seems to be why anything drawn directly under the NSTextField updates when it changes focus, but nothing that's not directly under it.
Quote this message in a reply
Moderator
Posts: 508
Joined: 2002.09
Post: #4
I forgot to add to my previous post that you may have to override the focus gaining methods from NSView (not the ones about lockFocus etc..)

Look around the subclasses of NSTextField and I'm sure you'll find a method that you'll need to override.

"When you dream, there are no rules..."
Quote this message in a reply
Member
Posts: 64
Joined: 2005.06
Post: #5
Trying it out, even if I force the refresh of the entire area, there's an ever so slight delay when drawing the update and oddly enough, it causes the text to flash bold then light, and keeps repeating itself. I think it's doing that because it's drawing the text again over the old text without first clearing it out.
Quote this message in a reply
Member
Posts: 64
Joined: 2005.06
Post: #6
Time for an update!

I got it to work, but by brute force.
Code:
[self setNeedsDisplayInRect: NSMakeRect([valueField frame].origin.x, [valueField frame].origin.y - valueStringAbsSize.height, bounds.size.width, [valueField frame].size.height)];
    [self setNeedsDisplayInRect: NSMakeRect([valueField frame].origin.x, [valueField frame].origin.y + valueStringAbsSize.height, bounds.size.width, [valueField frame].size.height)];
    [self setNeedsDisplayInRect: NSMakeRect([valueField frame].origin.x - radius * 2.0, 0, radius * 2.0, bounds.size.height)];
    [self setNeedsDisplayInRect: NSMakeRect([valueField frame].origin.x + [valueField frame].size.width, 0, radius * 2.0, bounds.size.height)];

Essentially I'm refreshing the immediate area around the editfield. Unfortunately this is the most brute force of methods, and there's an ever-so-slight delay when refreshing the focus ring; it would actually be a lot faster if there was a method for updating everything not in the given rect, that way I could just feed it the frame of the NSTextField.
Quote this message in a reply
Member
Posts: 64
Joined: 2005.06
Post: #7
Another update, it doesn't work as well as I thought. The text cursor (the blinking thing) is not refreshed (draws multiples) if you move it using the arrow keys and it still won't draw correctly the first time the program starts up (that is, if the control is the first responder on startup).

Edit: I've fixed the text cursor drawing problem (it draws correctly now), but I still can't fix the startup drawing error.
Quote this message in a reply
Post Reply 

Possibly Related Threads...
Thread: Author Replies: Views: Last Post
  Sampling NSBezierPath ipeku 2 3,533 Feb 16, 2012 04:01 PM
Last Post: ipeku
  Correctly texturing a sphere. DoG 5 4,509 Jan 10, 2003 04:33 AM
Last Post: DoG