NSSegmentedCell subclass drawing - cocoa

I have created subclass of NSSegmentedCell and implemented drawWithFrame as following:
#import "CustomSegmentedCell.h"
#implementation CustomSegmentedCell
- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView {
int i=0, count=[self segmentCount];
NSRect segmentFrame=cellFrame;
for(i=0; i<count; i++) {
segmentFrame.size.width=[self widthForSegment:i];
[NSGraphicsContext saveGraphicsState];
// Make sure that segment drawing is not allowed to spill out into other segments
NSBezierPath* clipPath = [NSBezierPath bezierPathWithRect: segmentFrame];
[clipPath addClip];
[self drawSegment:i inFrame:segmentFrame withView:controlView];
[NSGraphicsContext restoreGraphicsState];
segmentFrame.origin.x+=segmentFrame.size.width;
}
_lastDrawRect=cellFrame;
}
#end
The problem is segments didn't get drawn on first launch of app, it gets visible only when i clicked with mouse on blank area where segmented Control suppose to drawn.Please let me know, what i am missing here.
Thanks,

Subclass and implement drawSegment:inFrame:withView:
- (void)drawSegment:(NSInteger)segment inFrame:(NSRect)frame withView:(NSView *)controlView
{
NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:frame xRadius:8 yRadius:8];
[path setClip];
[path setLineWidth:2.5];
[[NSColor grayColor] setStroke];
NSColor* bgColr;
if (segment%2) {
bgColr = [NSColor blackColor];
}
else {
bgColr = [NSColor redColor];
}
[bgColr setFill];
[NSBezierPath fillRect:frame];
}

Related

How to create inner transparent nsview with nonopaque window

I am trying to create opaque border in an non opaque window. Currently I look for subview and create bezierpath to fill outer region. Is there other way how to do this?
Example what I am trying to achieve:
#import "MyView2.h"
#import "MyView.h"
#interface MyView2() {
NSRect transparentSubviewRect;
}
#end
#implementation MyView2
- (void)awakeFromNib
{
NSArray *subviews = [self subviews];
for (NSView *view in subviews) {
if ([view isKindOfClass:[MyView class]]) {
transparentSubviewRect = [view frame];
}
}
}
- (void)drawRect:(NSRect)dirtyRect {
NSRect rect = [self frame];
NSBezierPath *path = [NSBezierPath bezierPath];
[path moveToPoint:NSMakePoint(NSMaxX(transparentSubviewRect), NSMaxY(transparentSubviewRect))];
[path lineToPoint:NSMakePoint(NSMinX(transparentSubviewRect), NSMaxY(transparentSubviewRect))];
[path lineToPoint:NSMakePoint(NSMinX(transparentSubviewRect), NSMinY(transparentSubviewRect))];
[path lineToPoint:NSMakePoint(NSMaxX(transparentSubviewRect), NSMinY(transparentSubviewRect))];
[path lineToPoint:NSMakePoint(NSMaxX(transparentSubviewRect), NSMaxY(transparentSubviewRect))];
[path closePath];
[path moveToPoint:NSMakePoint(NSMaxX(rect), NSMaxY(rect))];
[path curveToPoint: NSMakePoint(NSMaxX(rect), NSMinY(rect))
controlPoint1: NSMakePoint(NSMaxX(transparentSubviewRect), NSMaxY(transparentSubviewRect))
controlPoint2: NSMakePoint(NSMaxX(rect), NSMinY(transparentSubviewRect))];
[path lineToPoint:NSMakePoint(NSMinX(rect), NSMinY(rect))];
[path lineToPoint:NSMakePoint(NSMinX(rect), NSMaxY(rect))];
[path lineToPoint:NSMakePoint(NSMaxX(rect), NSMaxY(rect))];
[path lineToPoint:NSMakePoint(NSMaxX(rect), NSMaxY(rect))];
[path closePath];
[[NSColor windowBackgroundColor] setFill];
[path fill];
}
#end
#import "MyWindow.h"
#implementation MyWindow
- (BOOL)isOpaque
{
return NO;
}
- (NSColor *)backgroundColor
{
return [NSColor colorWithCalibratedWhite:0.0 alpha:0.3];
}
#end
Easier solution was to fill whole view and use NSCompositingOperationClear operation. Mandatory is opaque window + some transparent background color for window.
- (void)drawRect:(NSRect)dirtyRect {
[[NSColor windowBackgroundColor] setFill];
NSRectFill([self bounds]);
NSRectFillUsingOperation(transparentSubviewRect, NSCompositingOperationClear);
}

drawRect not called on PDFView subclass

I have a custom PDFView subclass and have overridden -mouseDown, -mouseDragged etc to draw a rectangle over the view. Here is the code I am using:
#implementation MyPDFView {
NSPoint clickLocation;
NSRect selection;
}
- (void)mouseDown:(NSEvent *)theEvent {
NSPoint clickLocationOnWindow = [self.window mouseLocationOutsideOfEventStream];
clickLocation = [self convertPoint:clickLocationOnWindow fromView:nil];
NSLog(#"%#", NSStringFromPoint(clickLocation));
}
- (void)mouseDragged:(NSEvent *)theEvent {
NSPoint mouseLocationOnWindow = [self.window mouseLocationOutsideOfEventStream];
NSPoint currentLocation = [self convertPoint:mouseLocationOnWindow fromView:nil];
CGFloat lowerX = fmin(clickLocation.x, currentLocation.x);
CGFloat lowerY = fmin(clickLocation.y, currentLocation.y);
CGFloat upperX = fmax(clickLocation.x, currentLocation.x);
CGFloat upperY = fmax(clickLocation.y, currentLocation.y);
selection = NSMakeRect(lowerX, lowerY, upperX-lowerX, upperY-lowerY);
[self setNeedsDisplay:YES];
NSLog(#"%#", NSStringFromRect(selection));
}
- (void)drawRect:(NSRect)dirtyRect
{
// Drawing code here.
NSLog(#"drawRect");
NSBezierPath *bp = [NSBezierPath bezierPathWithRect:selection];
[[NSColor blueColor] set];
[bp fill];
}
#end
The NSRect is calculated correctly, but when I call [self setNeedsDisplay], -drawRect is never called, and the rectangle is never drawn.
Is there any reason why -drawRect is never called on a PDFView subclass?
I have a similar use case. According to the docs, override PDFView's drawPage method instead. Continue to call setNeedsDisplay on the PDFView. It works, but it's a little slow. Working on overlaying a view right now instead.

Equivalent of clipToBounds for Mac (NSView's layer)

I have a class "GlobalView" which extends NSView and has two subviews GreenRectView (which extends NSView).
I use the CALayer from my GreenRectView in order to be able to rotate (with setAffineTransform:) my view around a point other than the default bottom-left point. Thus I used "setAnchorPoint". Everything works well except that when my green rect is rotated , it is not draw outside the bounds of its view. In iOS it is possible to use clipToBounds:NO to accept that drawing is display outside bounds but in Mac it doesn't work with masksToBounds... How can I rotate my greenrect and display it entirely whatever the angle of rotation.
See picture :
And here is the code :
#implementation GlobalView
- (id)initWithFrame:(NSRect)frame {
self = [super initWithFrame:frame];
if (self) {
// Initialization code here.
g = [[GreenRectView alloc]initWithPoint:NSMakePoint(200, 200)];
[self addSubview:g];
g = [[GreenRectView alloc]initWithPoint:NSMakePoint(100, 400)];
[self addSubview:g];
}
return self;
}
#import <Quartz/Quartz.h>
#include <ApplicationServices/ApplicationServices.h>
#implementation GreenRectView
- (id)initWithFrame:(NSRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self setWantsLayer:YES];
layer = [self layer];
[self setLayer:layer];
[layer setAnchorPoint:NSMakePoint(0.0,0.5)];
[layer setMasksToBounds:NO];
[menuLayer addSublayer:layer];
}
return self;
}
- (id)initWithPoint:(CGPoint)p {
return [self initWithFrame:NSMakeRect(p.x, p.y, 120, 100)];
}
- (void)drawRect:(NSRect)dirtyRect {
[[NSColor greenColor] setFill];
NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:NSMakeRect(0,0, self.frame.size.width, self.frame.size.height) xRadius:5.5 yRadius:5.5];
[path stroke];
}

Custom NSMenu like view not displaying selectedMenuItemColor correctly

I wrote my own NSMenu like class to show dynamic search results below a NSSearchField in a borderless NSWindow. It's working well but doesn't draw the magic selectedMenuItemColor correctly if I add some padding to the top of the subview. I put a 5 pixel padding at the top of container view to mimic NSMenu and when I do it blue selected gradient looks off. A picture & code should make this clear:
Here is my drawRect code inside my item view (remember this is just a regular NSView):
-(void) drawRect:(NSRect)dirtyRect {
CGRect b = self.bounds;
if (selected) {
[NSGraphicsContext saveGraphicsState];
[[NSColor selectedMenuItemColor] set];
NSRectFill( b );
[NSGraphicsContext restoreGraphicsState];
if (textField) {
textField.textColor = [NSColor selectedMenuItemTextColor];
}
} else {
[[NSColor clearColor] set];
NSRectFillUsingOperation(b, NSCompositeSourceOver);
if (textField) {
textField.textColor = [NSColor blackColor];
}
}
}
You have to get the pattern phase origin to match your view frame.
That is, selectedMenuItemColor is actually a pattern, not a color, and that pattern is designed to display "properly" in "standard menu item height" increments. Because you have added the padding, now it is not being drawn at the "standard" location.
Try this:
-(void) drawRect:(NSRect)dirtyRect {
CGRect b = self.bounds;
if (selected) {
NSPoint origin = [self frame].origin;
curContext = [NSGraphicsContext currentContext];
[curContext saveGraphicsState];
[curContext setPatternPhase: origin];
[[NSColor selectedMenuItemColor] set];
NSRectFill( b );
[curContext restoreGraphicsState];
if (textField) {
textField.textColor = [NSColor selectedMenuItemTextColor];
}
} else {
[[NSColor clearColor] set];
NSRectFillUsingOperation(b, NSCompositeSourceOver);
if (textField) {
textField.textColor = [NSColor blackColor];
}
}
}

How to draw smooth path with NSBezierPath

I am draing path with NSBezierPath currently:
- (void)drawRect:(NSRect)dirtyRect
{
[[NSColor blueColor] set];
[path stroke];
}
But the line seems pretty ugly(not smooth)
Then I googled for some solutions and I got this:
- (void)drawRect:(NSRect)dirtyRect
{
NSGraphicsContext *graphicsContext;
BOOL oldShouldAntialias;
graphicsContext = [NSGraphicsContext currentContext];
oldShouldAntialias = [graphicsContext shouldAntialias];
[graphicsContext setShouldAntialias:NO];
[[NSColor blueColor] set];
[path stroke];
[graphicsContext setShouldAntialias:oldShouldAntialias];
}
But for me, noting changed, I still got the ugly path.
Any suggestion?
Thanks for answer.
Here's the details:
Create a cocoa application (not document-based)
Create a new class "StretchView"
StretchView.h
#import <Cocoa/Cocoa.h>
#interface StretchView : NSView {
NSBezierPath *path;
}
-(void)myTimerAction:(NSTimer *) timer;
#end
StretchView.m
#import "StretchView.h"
#implementation StretchView
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
path = [[NSBezierPath alloc] init];
NSPoint p = CGPointMake(0, 0);
[path moveToPoint:p];
myTimer = [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:#selector(myTimerAction:)
userInfo:nil
repeats:YES];
}
return self;
}
- (void)drawRect:(NSRect)dirtyRect
{
NSLog(#"drawRect");
[[NSColor blueColor] set];
[path stroke];
}
-(void)myTimerAction:(NSTimer *) timer
{
NSPoint p = CGPointMake(a, b); //a, b is random int val
[path lineToPoint:p];
[path moveToPoint:p];
[path closePath];
[self setNeedsDisplay:YES];
}
-(void)dealloc
{
[path release];
[super dealloc];
}
#end
3.Open the Interface builder and drag a "Scroll view" to the main window
4.Choose the "Scroll view" and set the class "StretchView" (in class identity window)
Using lineToPoint will just create straight zigzaggy lines, even as part of a bezier path. If by "smooth" you mean "curvy", then you should probably check out curveToPoint instead. Also, note that these "toPoint" methods already move the point, no need to moveToPoint unless you intend to move to a different point than where your line ended.

Resources