Redrawed inset NSShadow on a Custom View using -setClip method - cocoa

I have and odd problem, related with the answer of this question:
Draw an Inset NSShadow and Inset Stroke
I use this code into the drawRect method of a custom view. I have exactly this:
- (void)drawRect:(NSRect)rect
{
// Create and fill the shown path
NSBezierPath *path = [NSBezierPath
bezierPathWithRoundedRect:[self bounds]
xRadius:4.0f
yRadius:4.0f];
[[NSColor colorWithCalibratedWhite:0.8f alpha:0.2f] set];
[path fill];
// Save the graphics state for shadow
[NSGraphicsContext saveGraphicsState];
// Set the shown path as the clip
[path setClip];
// Create and stroke the shadow
NSShadow * shadow = [[[NSShadow alloc] init] autorelease];
[shadow setShadowColor:[NSColor colorWithCalibratedWhite:0.0f alpha:0.8f]];
[shadow setShadowBlurRadius:2.0];
[shadow set];
[path stroke];
// Restore the graphics state
[NSGraphicsContext restoreGraphicsState];
if ( highlight && [[self window] firstResponder] == self ) {
NSSetFocusRingStyle(NSFocusRingOnly);
[[NSBezierPath bezierPathWithRect:[self bounds]] fill];
}
}
The problem comes when I add a Multiline Label (either sibling or child of my custom view).
When my program window loses the focus and I come back to it, my inner shadow / stroke go darker. It seems that the shadows superimpose. It's strange because as said, if my window only have this custom view, it goes well.
If I comment the line
[path setClip];
the shadow isn't superimposed anymore, but I don't get the desired effect of rounded corners (similar than NSBox).
I tried what happens with a Push Button instead of a Multiline Label, and by losing / getting the window focus it has no problems, but when I click the button the shadow gets superimposed.
I find the problem is similar than here, but in Cocoa instead of Java:
Java setClip seems to redraw
Thanks for your help!

You should never use -setClip unless you know what you're doing. You should use -addClip instead, which respects the existing clipping paths.

Related

What's the preferable way to draw a collection of lines in an NSView?

Let's say there's a collection of lines in some instance variable in my NSView instance. This collection stays the same over time. How would you draw the collection?
I currently draw to an NSImage when the collection is set up, and override -drawRect: to display the image's contents. Is there a better way?
Tested out two options so far :
Draw to an NSImage when the collection is set up, and override -drawRect: to display the image's contents.
Draw the list of lines in -drawRect: itself.
Benchmarks show the image drawing to be much faster. If I resize my window and it has, say; 10000 line segments, then the resize is incredibly slow if I use the second option. Not so with the first.
What's the preferable (eg. most efficient) strategy?
Example code :
- (void)drawSegments:(NSSize)size segments:(segment *)segments count:(int)nb_segments {
NSBitmapImageRep *lineRepresentation = [self bitmapForPixels:linePixels];
[NSGraphicsContext setCurrentContext:
[NSGraphicsContext graphicsContextWithBitmapImageRep:lineRepresentation]];
memset(*linePixels, 0, pixels*4);
NSBezierPath *path = [NSBezierPath bezierPath];
for (int i=0 ; i<nb_segments ; i++) {
[path moveToPoint:NSMakePoint(segments[i].x1, segments[i].y1)];
[path lineToPoint:NSMakePoint(segments[i].x2, segments[i].y2)];
}
[[NSColor blackColor] setStroke];
[path stroke];
[lineRepresentation release];
}
- (NSBitmapImageRep *)bitmapForPixels:(int **)pixels {
NSBitmapImageRep *representation = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:((unsigned char **)pixels)
pixelsWide:SCREEN_SIZE.width
pixelsHigh:SCREEN_SIZE.height
bitsPerSample:8
samplesPerPixel:4
hasAlpha:true
isPlanar:false
colorSpaceName:NSDeviceRGBColorSpace
bitmapFormat:NSAlphaFirstBitmapFormat
bytesPerRow:4*SCREEN_SIZE.width
bitsPerPixel:32];
return representation;
}

setStringValue in NSTextField will always change height

I have a NSTextField in a view where layout is totally controlled by constraints and translatesAutoresizingMaskIntoConstraints is NO. I tried to use setStringValue to change the content like this:
[[self textfield] setStringValue:#"1\n2\n3\n"];
Then the height is changed to 4 lines which is not what I want. I need a NSTextField that can show only one line but still I can use up and down arrow keys to go into different lines. It is just like use option+enter to insert a newline in NSTextField.
I also tried to keep the height:
NSRect originalFrame = [[self textfield] frame];
[[self textfield] setStringValue:#"1\n2\n3\n"];
NSRect newFrame = [[self textfield] frame];
newFrame.size.height = originalFrame.size.height;
[[self textfield] setFrame:newFrame];
It doesn't work. I checked intrinsicContentSize and it returns (width=-1, height=73). Is there anything I can set to NSTextField so the height is of only one line like 22?
This is happening because you have set the auto layout of textfield with respect to the view. So just fixed the height of your textfield by clicking on the height checkbox like that below :-

How can I programmatically render fullscreen openGL at a specific resolution?

I am working on a OSX/Cocoa graphics application which (for performance reasons) I would like to render at 640x480 when the user selects "full screen" mode. For what it's worth, the content is a custom NSView which draws using openGL.
I understand that rather than actually change the user's resolution, it's preferable to change the backbuffer (as explained on another SO question here: Programmatically change resolution OS X).
Following that advice, I end up with the following two methods (see below) to toggle between fullscreen and windowed. The trouble is that when I go fullscreen, the content does indeed render at 640x480 but is not scaled (IE it appears as if we stayed at the window's resolution and "zoomed" into a 640x480 corner of the render).
I'm probably missing something obvious here - I suppose I could translate the render according to the actual screen resolution to "center" it, but that seems overcomplicated?
- (void)goFullscreen{
// Bounce if we're already fullscreen
if(_isFullscreen){return;}
// Save original size and position
NSRect frame = [self.window.contentView frame];
original_size = frame.size;
original_position = frame.origin;
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:NO],NSFullScreenModeAllScreens,
nil];
// In lieu of changing resolution, we set the backbuffer to 640x480
GLint dim[2] = {640, 480};
CGLSetParameter([[self openGLContext] CGLContextObj], kCGLCPSurfaceBackingSize, dim);
CGLEnable ([[self openGLContext] CGLContextObj], kCGLCESurfaceBackingSize);
// Go fullscreen!
[self enterFullScreenMode:[NSScreen mainScreen] withOptions:options];
_isFullscreen=true;
}
- (void)goWindowed{
// Bounce if we're already windowed
if(!_isFullscreen){return;}
// Reset backbuffer
GLint dim[2] = {original_size.width, original_size.height};
CGLSetParameter([[self openGLContext] CGLContextObj], kCGLCPSurfaceBackingSize, dim);
CGLEnable ([[self openGLContext] CGLContextObj], kCGLCESurfaceBackingSize);
// Go windowed!
[self exitFullScreenModeWithOptions:nil];
[self.window makeFirstResponder:self];
_isFullscreen=false;
}
Update
Here's now to do something similar to datenwolf's suggestion below, but not using openGL (useful for non-gl content).
// Render into a specific size
renderDimensions = NSMakeSize(640, 480);
NSImage *drawIntoImage = [[NSImage alloc] initWithSize:renderDimensions];
[drawIntoImage lockFocus];
[self drawViewOfSize:renderDimensions];
[drawIntoImage unlockFocus];
[self syphonSendImage:drawIntoImage];
// Resize to fit preview area and draw
NSSize newSize = NSMakeSize(self.frame.size.width, self.frame.size.height);
[drawIntoImage setSize: newSize];
[[NSColor blackColor] set];
[self lockFocus];
[NSBezierPath fillRect:self.frame];
[drawIntoImage drawAtPoint:NSZeroPoint fromRect:self.frame operation:NSCompositeCopy fraction:1];
[self unlockFocus];
Use a FBO with a texture of the desired target resolution attached and render to that FBO/texture in said resolution. Then switch to the main framebuffer and draw a full screen quad using the texture rendered to just before. Use whatever magnification filter you like best. If you want to bring out the big guns you could implement a Lancosz / sinc interpolator in the fragment shader to upscaling the intermediary texture.

Draw NSView background partially, with a gradient

I have a NSView, subclassed, with custom background drawing, filling it with a gradient.
In IB, I've put a checkbox on it, somewhere in the middle.
This is the drawRect method.
-(void) drawRect:(NSRect)dirtyRect {
CGFloat sc = 0.9f;
CGFloat ec = 0.6f;
NSColor* startingColor = [NSColor colorWithDeviceRed:sc green:sc blue:sc alpha:1];
NSColor* endingColor = [NSColor colorWithDeviceRed:ec green:ec blue:ec alpha:1];
NSGradient *grad = [[NSGradient alloc] initWithStartingColor:startingColor endingColor:endingColor];
[grad drawInRect:dirtyRect angle:270];
}
What happens is, this same method gets called to draw the whole view area first and then for the part, where NSButton (checkbox) lies on top of it. OF course the checkbox background is drawn with a complete gradient and it is not right, since the portion is much smaller. The same happens with other controls I put on the said NSView.
What is the suggested approach on such thing?
One option is to make controls height the same as the views' but this will result in problems in the future.
The answer is, always draw the WHOLE area of the view, not just the dirtyRect
[grad drawInRect:[self bounds] angle:270];

How do I get the inner/client size of a NSView subclass?

I am doing manual layouting for my Cocoa application and at some point I need to figure out what the inner size of a NSView subclass is. (E.g. What is the height available for my child view inside of a NSBox?)
One of the reasons is that I am using a coordinate system with origin at the top-left and need to perform coordinate transformations.
I could not figure out a way to get this size so far and would be glad if somebody can give me a hint.
Another very interesting property I would like to know is the minimum size of a view.
-bounds is the one you're looking for in most views. NSBox is a bit of a special case, however, since you want to look at the bounds of the box's content view, not the bounds of the box view itself (the box view includes the title, edges, etc.). Also, the bounds rect is always the real size of the box, while the frame rect can be modified relative to the bounds to apply transformations to the view's contents (such as squashing a 200x200 image into a 200x100 frame).
So, for most views you just use [parentView bounds], and for NSBox you'll use [[theBox contentView] bounds], and you'll use [[theBox contentView] addSubview: myView] rather than [parentView addSubview: myView] to add your content.
Unfortunately, there is no standard way to do this for all NSView subclasses. In your specific example, the position and size of a child view within an NSBox can be computed as follows:
NSRect availableRect = [someNSBox bounds];
NSSize boxMargins = [someBox contentViewMargins];
availableRect = NSInsetRect(availableRect, boxMargins.width, boxMargins.height);
If you find yourself using this often, you could create a category on NSBox as follows:
// MyNSBoxCategories.h
#interface NSBox (MyCategories)
- (NSRect)contentFrame;
#end
// MyNSBoxCategories.m
#implementation NSBox (MyCategories)
- (NSRect)contentFrame
{
NSRect frameRect = [self bounds];
NSSize margins = [self contentViewMargins];
return NSInsetRect(frameRect, margins.width, margins.height);
}
#end
And you would use it like so:
#import "MyNSBoxCategories.h"
//...
NSRect frameRect = [someNSBox contentFrame];
[myContentView setFrame:frameRect];
[someNSBox addSubview:myContentView];
The bounds property of NSView returns an NSRect with the origin (usually (0,0)) and the size of an NSView. See this Apple Developer documentation page.
I'm not sure (I never had to go too deep in that stuff), but isn't it [NSView bounds]?
http://www.cocoadev.com/index.pl?DifferenceBetweenFrameAndBounds

Resources