Custom View in NSMenuItem does not highlight correctly - objective-c

I try to use a custom view for a NSMenuitem, which works. Unfortunately, I have some difficulties with the highlighting (mouseover). I already followed the instructions in other threads to implement drawRect: in my NSView subclass to do the blue highlighting manually. This seems to work, but the highlighting color is not correct. It appears too dark compared to regular menu items and interestingly the subviews of my custom view use the correct highlighting color (see screenshot).
Any ideas on how to fix this issue?
My current drawRect: method in the NSView subclass looks like this:
- (void)drawRect:(NSRect)dirtyRect
{
[super drawRect:dirtyRect];
BOOL isHighlighted = [[self enclosingMenuItem] isHighlighted];
if (isHighlighted)
{
[[NSColor selectedMenuItemColor] setFill];
NSRectFill(dirtyRect);
[self.profileNameView setTextColor:[NSColor whiteColor]];
[self.securedIPView setTextColor:[NSColor whiteColor]];
[self.separatorView setTextColor:[NSColor whiteColor]];
[self.connectionTimeView setTextColor:[NSColor whiteColor]];
}
else
{
[self.profileNameView setTextColor:[NSColor controlTextColor]];
[self.securedIPView setTextColor:[NSColor disabledControlTextColor]];
[self.separatorView setTextColor:[NSColor disabledControlTextColor]];
[self.connectionTimeView setTextColor:[NSColor disabledControlTextColor]];
}
}
The resulting highlighting looks like:

Related

Rounded NSView in a Transparent Window

I'm trying to make a transparent NSWindow with a rounded view in there.
I'm trying to have a rounded view with a transparent window.
This is what it looks like now: (see the little dots in the corners)
Here's another example with the border radius set to 10px (set in NSView drawRect):
I am using code from this Apple sample: https://developer.apple.com/library/mac/#samplecode/RoundTransparentWindow/Introduction/Intro.html
Specifically this method in my NSWindow subclass:
- (id)initWithContentRect:(NSRect)contentRect
styleMask:(NSUInteger)aStyle
backing:(NSBackingStoreType)bufferingType
defer:(BOOL)flag {
// Using NSBorderlessWindowMask results in a window without a title bar.
self = [super initWithContentRect:contentRect styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO];
if (self != nil) {
// Start with no transparency for all drawing into the window
[self setAlphaValue:1.0];
// Turn off opacity so that the parts of the window that are not drawn into are transparent.
[self setOpaque:NO];
[self setBackgroundColor:[NSColor clearColor]];
}
return self;
}
And this in my NSView subclass:
- (void)drawRect:(NSRect)dirtyRect
{
[[NSColor redColor] set];
NSBezierPath* thePath = [NSBezierPath bezierPath];
[thePath appendBezierPathWithRoundedRect:dirtyRect xRadius:3 yRadius:3];
[thePath fill];
}
Can anyone tell me what I'm missing here?
Thanks.
Are you looking for something like the following, where there's a red outline (stroke), but the center area is transparent?
If so, to achieve that, I used the following code:
- (void)drawRect:(NSRect)frame {
frame = NSInsetRect(self.frame, 3.0, 3.0);
[NSBezierPath setDefaultLineWidth:6.0];
NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:frame
xRadius:6.0 yRadius:6.0];
[[NSColor redColor] set];
[path stroke];
}
If that's what you're looking for, you can probably use that as a starting point. You'll want to make sure that you inset the frame rect one half of the stroke line width, so as to avoid the problem with clipping the corners like you were seeing.
Not sure if this is what you are looking for but there is a great class by Matt Gemmell called MAAttachedWindow and can be found here: http://mattgemmell.com/2007/10/03/maattachedwindow-nswindow-subclass/
It's a little older but still works great for me when I need to do a 'floating' popup window and configure transparency, border radii, and even add a small arrow for context if desired. I use it all the time.

NSTextField bug with FocusRingType: None

There seems to be a bug in NSTextField. When the application launches it all draws correctly. But as soon as I click in the textField the view gets all messed up. To more specific, whenever I type drawRect: gets called but with a smaller rect causing all the problems.
When I select the text it draws correctly again. The only solution is to set the FocusRingType to visible (example: NSFocusRingTypeDefault). But I would like to have it without the ring. Is this possible?
Here is the code I am using:
-(id)initWithFrame:(NSRect)frameRect
{
self = [super initWithFrame:frameRect];
if(self)
{
// Add a label
NSTextField *textField = [[NSTextField alloc] initWithFrame:CGRectMake(0, 0, frameRect.size.width, frameRect.size.height)];
[[textField cell] setPlaceholderString:#"URL or search term..."];
[textField setTextColor:[NSColor greyColor]];
[textField setBackgroundColor:[NSColor clearColor]];
[textField setFont:[NSFont fontWithName:#"Open Sans" size:20]];
[textField setDrawsBackground:FALSE];
[textField setBordered:FALSE];
[textField setFocusRingType:NSFocusRingTypeNone];
[self addSubview:textField];
}
return self;
}
-(void)drawRect:(NSRect)dirtyRect
{
NSInteger borderWdith = 2;
// Create the path to the button
NSBezierPath *aPath = [NSBezierPath bezierPathWithRoundedRect:CGRectMake(borderWdith, borderWdith,
dirtyRect.size.width-(borderWdith*2),
dirtyRect.size.height-(borderWdith*2))
xRadius:3 yRadius:3];
// Fill the button with white
[[NSColor whiteColor] set];
[aPath fill];
}
Tricks like setting editable to TRUE/FALSE in drawRect did not work. Also setting to different focusRingTypes in the method failed.
Okay, so I managed to sort of fix this drawing problem. In the view init method I set a global CGRect variable which was set to the initial frame.
Each time -(void)drawRect:(CGRect)dirtyRect was called, I overwrote dirtyRect with the global variable.
Hope this helps anyone. I guess this isn't the best solution, but works :)

Highlighting an Edited NSBrowserCell & Draw A Focus Ring on its NSText?

What I'm trying to do
I'm trying to add edit-in-place functionality to the Connection Kit's NSBrowser. I'd like this behaviour to be functionally and visually similar to Finder's implementation.
The visual effect I'm aiming for
What I've got so far
The arrows indicate focus ring & cell highlighting in Finder's implementation, and the lack of it in mine.
I have tried
Setting the background colour of the cell in the controller, in it's drawInteriorWithFrame method
The same for the field editor
setFocusRingType:NSFocusRingTypeDefault for the field editor & cell both in the controller & the draw method
Manually drawing the highlight color in the draw method
Various combinations of the above, and undoubtedly some I've forgotten.
The best I've managed was getting the area surrounding the cell's image coloured with the highlight colour.
Is there some fundamental that I'm missing here? Could someone please suggest a starting point for approaching this? Is drawInteriorWithFrame the place to be doing this?
I've got editing working fine - I'm just having trouble with the visual aspects.
Code to allow editing:
// In the main controller
int selectedColumn = [browser selectedColumn];
int selectedRow = [browser selectedRowInColumn:selectedColumn];
NSMatrix *theMatrix = [browser matrixInColumn:selectedColumn];
NSRect cellFrame = [theMatrix cellFrameAtRow:selectedRow column:0];
NSText *fieldEditor = [[browser window] fieldEditor:YES
forObject:editingCell];
[cell editWithFrame:cellFrame
inView:theMatrix
editor:fieldEditor
delegate:self
event:nil];
And in my subclass of NSBrowserCell:
- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView {
image = [[self representedObject] iconWithSize:[self imageSize]];
[self setImage:image];
NSRect imageFrame, highlightRect, textFrame;
// Divide the cell into 2 parts, the image part (on the left) and the text part.
NSDivideRect(cellFrame, &imageFrame, &textFrame, ICON_INSET_HORIZ + ICON_TEXT_SPACING + [self imageSize].width, NSMinXEdge);
imageFrame.origin.x += ICON_INSET_HORIZ;
imageFrame.size = [self imageSize];
[super drawInteriorWithFrame:cellFrame inView:controlView];
}
- (void)editWithFrame:(NSRect)aRect inView:(NSView *)controlView editor:(NSText *)textObj delegate:(id)anObject event:(NSEvent *)theEvent {
NSRect imageRect, textRect;
NSDivideRect(aRect , &imageRect, &textRect, 20, NSMinXEdge);
self.editing = YES;
[super editWithFrame: textRect inView: controlView editor:textObj delegate:anObject event:theEvent];
}
You have to draw the focus ring yourself.
Add the following in drawWithFrame in your NSBrowserCell subclass
[NSGraphicsContext saveGraphicsState];
[[controlView superview] lockFocus];
NSSetFocusRingStyle(NSFocusRingAbove);
[[NSBezierPath bezierPathWithRect:NSInsetRect(frame,-1,-1)] fill];
[[controlView superview] unlockFocus];
[NSGraphicsContext restoreGraphicsState];
Try subclassing field editor object and override drawRect function like this :
- (void)drawRect:(NSRect)rect {
[super drawRect:rect];
NSSetFocusRingStyle(NSFocusRingOnly);
NSRectFill([self bounds]);
}
Hope this helps.

Change cursor for full screen NSWindow

I am trying to make an overlay window that will allow drawing at the ShieldingWindowLevel, however when the window appears the cursor is still the default pointer. I would like to change it to the crosshairs. Having controller NSCursors before I am baffled why resetCursorRects is not ever called.
I manually create the window as follows (in my AppController class):
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Create the window
NSRect frame = [[NSScreen mainScreen] frame];
// Provide a small area on the right to move the cursor in-and-out of the window.
frame.size.width = frame.size.width - 20;
self.window = [[NSWindow alloc] initWithContentRect:frame
styleMask:NSBorderlessWindowMask
backing:NSBackingStoreBuffered
defer:NO];
[self.window setAcceptsMouseMovedEvents:YES];
[self.window setOpaque:NO];
[self.window setLevel:CGShieldingWindowLevel()];
[self.window setBackgroundColor:[NSColor colorWithDeviceRed:0.0 green:0.0 blue:1.0 alpha:0.2]];
// Create the subview
ScreenOverlayView *subview = [[ScreenOverlayView alloc] initWithFrame:NSZeroRect];
[[self.window contentView] addSubview:subview];
// Add subview and show window
[self.window setContentView:subview];
[self.window makeFirstResponder:subview];
[self.window orderFrontRegardless];
}
With the following NSView subclass:
#implementation ScreenOverlayView
- (void) resetCursorRects {
[super resetCursorRects];
[self addCursorRect: [self bounds]
cursor: [NSCursor crosshairCursor]];
}
// ...
#end
I created a sample project to show this case and posted it to github, the most interesting files are ScreenOverlayView.m and AppDelegate.m.
I should point out that I have also spent a good deal of time trying to get this working with an NSTrackingArea, as you can see in the sample project. Tracking Area works if the mouse enters the view after it has appeared, but not if it is inside to start with. Using MouseEnter and MouseLeave would be fine if I had some way to set the initial cursor, but it will only change for a split second before changing back.
How can I get resetCursorRects to be invoked -OR- how can I set the cursor when I move it to the superview?
The key is that you really need to create a custom subclass of NSWindow, in order to counteract some of the default behavior that borderless windows (NSBorderlessWindowMask) have.
An updated version of your sample project is at http://www.markdouma.com/developer/full-screen-overlay.zip.
In it, I created a custom MDScreenOverlayWindow class that overrides NSWindow's canBecomeKeyWindow method like below:
// Windows created with NSBorderlessWindowMask normally can't be key,
but we want ours to be
- (BOOL)canBecomeKeyWindow {
return YES;
}
This will allow your view to become key and basically all your other stuff to work properly.
The other thing that may be of note is the drawRect: method. (It looks like you may be coming from iOS). You might want to look into NSBezierPath, as it could potentially simplify some of your drawing code. For example, I believe the drawing code you had could be consolidated into the following:
- (void)drawRect:(NSRect)rect {
// the color should probably be "pre-multiplied" by the alpha
// premultiplied version:
[[NSColor colorWithCalibratedRed:0.8 green:0.0 blue:0.0 alpha:0.8] set];
[NSBezierPath setDefaultLineWidth:2.0];
[NSBezierPath strokeLineFromPoint:currentLocation toPoint:downLocation];
}

NSWindow Shadow Outline

I am drawing a custom window by setting a custom content view for the window. When I draw the custom view I give it rounded corners and a nice outline to mimic a proper window.
However, I see another 1 px outline around the window which strays from the edge at the corners. I have found that if I turn off the shadow it goes away, but obviously as this wants to act like a window I need the shadow. Here's what I mean about the 1px outline:
How can I prevent this?
EDIT
Code for drawing the custom window's content view:
NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:[self bounds] cornerRadius:5];
NSGradient* aGradient = [[[NSGradient alloc] initWithColorsAndLocations:
[NSColor colorWithDeviceRed:0.5569 green:0.5137 blue:0.4588 alpha:1.0000], 0.0,
[NSColor colorWithDeviceRed:0.5569 green:0.5137 blue:0.4588 alpha:1.0000], 1.0,
nil] autorelease];
[aGradient drawInBezierPath:path angle:90];
[path setLineWidth:4];
[[NSColor colorWithDeviceRed:0.4235 green:0.3922 blue:0.3451 alpha:0.9000] setStroke];
[path strokeInside];
[path setLineWidth:3];
[[NSColor colorWithDeviceRed:0.8431 green:0.8314 blue:0.8078 alpha:1.0000] setStroke];
[path strokeInside];
[path setLineWidth:1];
[[NSColor colorWithDeviceRed:0.4235 green:0.3922 blue:0.3451 alpha:0.9000] setStroke];
[path strokeInside];
Don't ask me how I got this, but this will solve your problem.
Define a category for NSWindow with the following content:
#implementation NSWindow(NoShadowRim)
- (id)_shadowRimInfo {
return #{
#"kCUIMeasureWindowFrameRimDensity": [NSNumber numberWithInt:0]
};
}
#end
DISCLAIMER: This overrides the internal method of NSWindow, so use it at your own risk. It may break with any OS X update.
You need to tell the window to recompute its shadow by sending it -invalidateShadow.
Try:
[[self window] display];
[[self window] setHasShadow:NO];
[[self window] setHasShadow:YES];
This line contouring the window area is drawn automatically. I have a window which has this line running accurately around bottom rounded corners. You have to setup the window as non-opaque and the background color to transparent:
[self setOpaque:NO];
[self setBackgroundColor:[NSColor clearColor]];
The somewhere in the contentView -drawRect: you do
[NSGraphicsContext saveGraphicsState];
[pathWithBottomRoundedCorner addClip];
// your drawing here...
[NSGraphicsContext restoreGraphicsState];
That should work.
As I understand correctly, shadows are drawn by windows server. When you draw custom NSWindow with rounded corners or other not rectangular shapes, window server don't count those transparent pixels and dont drop shadow under them.
I developed some hack to avoid such behavior. Just drop additional shadow under your path, something like this:
NSShadow *headShadow = [[NSShadow alloc] init];
[headShadow setShadowColor:[NSColor colorWithSRGBRed:0.0
green:0.0
blue:0.0
alpha:0.16]];
[headShadow setShadowBlurRadius:0.0f];
[headShadow setShadowOffset:NSMakeSize(0.0f, 0.0f)];
[headShadow set];
Ideally for perfect result i fink shadow must be equal to window servers.