Mouse Cursor on Layered NSView Cocoa - objective-c

I have few views, some are siblings, and some are subviews.
I need to have different Mouse Cursors. It works if I have only one view. But as soon as I move to overlapped view or a subview, the mouseExit of first and mouseEnter of second doesn't give me desired result, and I get the default Arrow cursor.
This image will help to understand
If I start moving mouse from left to right. The cursor changes to Hand, and in the overlapped it changes to Grab, but as I end the overlapped it become Arrow.
Moving from right to left gives me opposite, starts with Grab Cursor, changes to Hand in the overlapped area, and Arrow while I expect Hand Cursor.
The codes:
#implementation HandView
- (void)updateTrackingAreas
{
[super updateTrackingAreas];
if (self.trackingArea)
{
[self removeTrackingArea:self.trackingArea];
}
NSTrackingAreaOptions options = NSTrackingInVisibleRect | NSTrackingMouseEnteredAndExited | NSTrackingActiveInKeyWindow;
self.trackingArea = [[NSTrackingArea alloc] initWithRect:NSZeroRect options:options owner:self userInfo:nil];
[self addTrackingArea:self.trackingArea];
}
- (void)mouseEntered:(NSEvent *)event
{
[super mouseEntered:event];
[[NSCursor pointingHandCursor] set];
}
- (void)mouseExited:(NSEvent *)event
{
[super mouseExited:event];
[[NSCursor arrowCursor] set];
}
And
#implementation GrabView
- (void)updateTrackingAreas
{
[super updateTrackingAreas];
if (self.trackingArea)
{
[self removeTrackingArea:self.trackingArea];
}
NSTrackingAreaOptions options = NSTrackingInVisibleRect | NSTrackingMouseEnteredAndExited | NSTrackingActiveInKeyWindow;
self.trackingArea = [[NSTrackingArea alloc] initWithRect:NSZeroRect options:options owner:self userInfo:nil];
[self addTrackingArea:self.trackingArea];
}
- (void)mouseEntered:(NSEvent *)event
{
[super mouseEntered:event];
[[NSCursor closedHandCursor] set];
}
- (void)mouseExited:(NSEvent *)event
{
[super mouseExited:event];
[[NSCursor arrowCursor] set];
}
#end
Any directions would help me for fix this.
Thanks.

Related

Cocoa nsview change cursor

I tried to change the default cursor in my cocoa application. I read about this but the standard approach isn't working for me.
I try to add to my OpenGLView subclass this method:
- (void) resetCursorRects
{
[super resetCursorRects];
NSCursor * myCur = [[NSCursor alloc] initWithImage:[NSImage imageNamed:#"1.png"] hotSpot:NSMakePoint(8,0)];
[self addCursorRect: [self bounds]
cursor: myCur];
NSLog(#"Reset cursor rect!");
}
It`s not working. Why?
There're two ways you can do it. First - the most simple - is to change the cursor while the mouse enters the view and leaves it.
- (void)mouseEntered:(NSEvent *)event
{
[super mouseEntered:event];
[[NSCursor pointingHandCursor] set];
}
- (void)mouseExited:(NSEvent *)event
{
[super mouseExited:event];
[[NSCursor arrowCursor] set];
}
Another way is to create tracking area (i.e. in awakeFromNib-method), and override - (void)cursorUpdate:-method
- (void)createTrackingArea
{
NSTrackingAreaOptions options = NSTrackingInVisibleRect | NSTrackingCursorUpdate;
NSTrackingArea *area = [[NSTrackingArea alloc] initWithRect:self.bounds options:options owner:self userInfo:nil];
[self addTrackingArea:area];
}
- (void)cursorUpdate:(NSEvent *)event
{
[[NSCursor pointingHandCursor] set];
}
For those of you looking for a Swift solution, the syntax is:
override func mouseEntered(with event: NSEvent) {
super.mouseEntered(with: event)
NSCursor.pointingHand.set()
}

Cocoa - NSCursor resets to the default cursor when a key is pressed

I'm working on an application that has a window with a fully sized NSOpenGLView. I'm using [view addCursorRect] and [cursor set] to show a custom cursor, but when I press any key on the keyboard, the cursor resets to the default arrow. I've also tried overriding resetCursorRects and calling invalidateCursorRects when a key is pressed, which results in a flickering cursor.
The cursor switches back to my custom cursor when I click anywhere in the view, so I suppose the keyboard presses somehow unfocus my view. Is there any way to prevent the view from becoming unfocused when I press a key?
You need to remember your cursor settings + when you need to reset call e.g refreshCursor method. Example implementation:
- (void)refreshCursor
{
NSCursor *cursor = nil;
if ([self graphic]) {
cursor = [graphic hoverCursorForPoint:[self mousePosition]];
} else if ([self group]){
cursor = [NSCursor openHandCursor];
}
[self setHoverCursor:cursor];
[self refresh];
}
- (void)updateTrackingAreas
{
[super updateTrackingAreas];
[self removeTrackingArea:[self trackingArea]];
NSTrackingArea *area = [[NSTrackingArea alloc] initWithRect:[self visibleRect]
options:NSTrackingActiveAlways|NSTrackingMouseEnteredAndExited|NSTrackingMouseMoved
owner:self userInfo:nil];
[self setTrackingArea:area];
[self addTrackingArea:[self trackingArea]];
}
- (void)setCursorRects
{
[self discardCursorRects];
if ([self hoverCursor]) {
[self addCursorRect:[self visibleRect] cursor:[self hoverCursor]];
}
}
- (void)setHoverCursor:(NSCursor *)hoverCursor {
if (_hoverCursor != hoverCursor) {
_hoverCursor = hoverCursor;
[self setCursorRects];
}
}
- (void)resetCursorRects {
[super resetCursorRects];
[self setCursorRects];
}
- (void)mouseExited:(NSEvent *)event
{
[[NSCursor currentSystemCursor] set];
}
- (void)mouseEntered:(NSEvent *)event
{
}
- (void)mouseMoved:(NSEvent *)event
{
[self refreshCursor];
}
- (void)keyUp:(NSEvent *)event
{
[self refreshCursor];
}
//allow key events
- (BOOL)acceptsFirstResponder
{
return YES;
}

NSView mouseMoved event sometimes doesn't fire

I have a video playing app where an NSView is shown, tracked to the mouse coordinates, to show the time position when the user hovers over a certain area.
This works perfectly 70% of the time, however it frequently doesn't fire at all. The times when this is most likely to occur seem to be when bringing the mouse inside the view for the first time and after hovering the mouse outside of the area and then back inside again.
The code inside the NSView subclass is as follows:
- (void)viewDidMoveToWindow
{
if ([self window]) {
[self resetTrackingRect];
}
}
- (void)clearTrackingRect
{
if (rolloverTrackingRectTag > 0)
{
[self removeTrackingRect:rolloverTrackingRectTag];
rolloverTrackingRectTag = 0;
}
}
- (void)resetTrackingRect
{
[self clearTrackingRect];
rolloverTrackingRectTag = [self addTrackingRect:[self visibleRect]
owner:self userData:NULL assumeInside:NO];
}
- (void)resetCursorRects
{
[super resetCursorRects];
[self resetTrackingRect];
}
- (void)mouseEntered:(NSEvent *)theEvent
{
// Only ask for mouse move events when inside rect because they are expensive
[[self window] setAcceptsMouseMovedEvents:YES];
[[self window] makeFirstResponder:self];
// Tells the observer to show the time view
[[NSNotificationCenter defaultCenter] postNotificationName:#"MWTimelineHover" object:self userInfo:[NSDictionary dictionaryWithObjectsAndKeys:theEvent,#"event",nil]];
}
- (void)mouseExited:(NSEvent *)theEvent
{
[[self window] setAcceptsMouseMovedEvents:NO];
[[self window] resignFirstResponder];
// Tells the observer to hide the time view
[[NSNotificationCenter defaultCenter] postNotificationName:#"MWTimelineHoverLeave" object:self];
}
- (void)mouseMoved:(NSEvent *)theEvent
{
[super mouseMoved:theEvent];
// Tells the observer to show the time view
[[NSNotificationCenter defaultCenter] postNotificationName:#"MWTimelineHover" object:self userInfo:[NSDictionary dictionaryWithObjectsAndKeys:theEvent,#"event",nil]];
}
Note: on the occasions when it stalls, mouseExited is not called and the view has not lost firstResponder status. I am also not dragging the mouse, simply moving it normally.
You need to use NSTrackingArea. Here is reference NSTrackingArea Class Reference.
- (void)commonInit {
CGRect rect = CGRectMake(0.0f, 0.0f, self.frame.size.width, self.frame.size.height);
NSTrackingAreaOptions options = NSTrackingActiveInKeyWindow | NSTrackingMouseMoved | NSTrackingInVisibleRect;
_trackingArea = [[NSTrackingArea alloc] initWithRect:rect options:options owner:self userInfo:nil];
[self addTrackingArea:_trackingArea];
}
Hope it helps you.

How to force the cursor to be an "arrowCursor" when it hovers a NSButton that is inside a NSTextView?

Okay, here is the problem:
I have a NSTextView and I add my custom NSButton using:
[_textView addSubview:button];
Then, inside my NSButton subclass, I have (along with the NSTrackingArea stuff):
- (void)mouseEntered:(NSEvent *)event{
[[NSCursor arrowCursor] set];
}
- (void)mouseExited:(NSEvent *)theEvent{
[[NSCursor arrowCursor] set];
}
- (void)mouseDown:(NSEvent *)theEvent{
[[NSCursor arrowCursor] set];
}
- (void)mouseUp:(NSEvent *)theEvent{
[[NSCursor arrowCursor] set];
}
But when I hover it, the cursor remains the same IBeamCursor (because it's a NSTextView). Only when I press the button, the cursor gets updated. And then, when I move the mouse, still inside the button, the cursor goes back to the IBeamCursor.
Any ideas on how to do this? Thank you!
Adding a tracking area that only tracks enter/exit events seems to be not enough for NSTextView subviews. Somehow the textview always wins and sets it's IBeamCursor.
You can try to always enable tracking for mouse move events (NSTrackingMouseMoved) when adding the tracking area in your NSButton subclass:
#import "SSWHoverButton.h"
#interface SSWHoverButton()
{
NSTrackingArea* trackingArea;
}
#end
#implementation SSWHoverButton
- (void)mouseMoved:(NSEvent*)theEvent
{
[[NSCursor arrowCursor] set];
}
- (void)updateTrackingAreas
{
if(trackingArea != nil)
{
[self removeTrackingArea:trackingArea];
}
NSTrackingAreaOptions opts = (NSTrackingMouseMoved|NSTrackingActiveAlways);
trackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds]
options:opts
owner:self
userInfo:nil];
[self addTrackingArea:trackingArea];
}
- (void)dealloc
{
[self removeTrackingArea:trackingArea];
}
#end
Swift 5 variant:
import Cocoa
class InsideTextButton: NSButton {
var trackingArea: NSTrackingArea?
override func mouseMoved(with event: NSEvent) {
NSCursor.arrow.set()
}
override func updateTrackingAreas() {
if let area = trackingArea {
removeTrackingArea(area)
}
trackingArea = NSTrackingArea(rect: self.bounds, options: [.mouseMoved, .activeAlways], owner: self, userInfo: nil)
if let area = trackingArea {
addTrackingArea(area)
}
}
deinit {
if let area = trackingArea {
removeTrackingArea(area)
}
}
}

NSStatusItem right click menu

I'm working on a status bar app that has a left and right click. I've got the start of this working by following the tips from other posts but I'm not sure how to go about showing a menu on right click.
I use a subclassed NSView as the custom view of my NSStatusItem and have the right and left clicks executing different functions:
- (void)mouseDown:(NSEvent *)theEvent{
[super mouseDown:theEvent];
if ([theEvent modifierFlags] & NSCommandKeyMask){
[self.target performSelectorOnMainThread:self.rightAction withObject:nil waitUntilDone:NO];
}else{
[self.target performSelectorOnMainThread:self.action withObject:nil waitUntilDone:NO];
}
}
- (void)rightMouseDown:(NSEvent *)theEvent{
[super rightMouseDown:theEvent];
[self.target performSelectorOnMainThread:self.rightAction withObject:nil waitUntilDone:NO];
}
How can I show a menu on right click, the same way the standard NSStatusItem does on left click?
NSStatusItem popUpStatusItemMenu: did the trick. I am calling it from my right click action and passing in the menu I want to show and it's showing it! This is not what I would have expected this function to do, but it's working.
Here's the important parts of what my code looks like:
- (void)showMenu{
// check if we are showing the highlighted state of the custom status item view
if(self.statusItemView.clicked){
// show the right click menu
[self.statusItem popUpStatusItemMenu:self.rightClickMenu];
}
}
// menu delegate method to unhighlight the custom status bar item view
- (void)menuDidClose:(NSMenu *)menu{
[self.statusItemView setHighlightState:NO];
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification{
// setup custom view that implements mouseDown: and rightMouseDown:
self.statusItemView = [[ISStatusItemView alloc] init];
self.statusItemView.image = [NSImage imageNamed:#"menu.png"];
self.statusItemView.alternateImage = [NSImage imageNamed:#"menu_alt.png"];
self.statusItemView.target = self;
self.statusItemView.action = #selector(mainAction);
self.statusItemView.rightAction = #selector(showMenu);
// set menu delegate
[self.rightClickMenu setDelegate:self];
// use the custom view in the status bar item
self.statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength];
[self.statusItem setView:self.statusItemView];
}
Here is the implementation for the custom view:
#implementation ISStatusItemView
#synthesize image = _image;
#synthesize alternateImage = _alternateImage;
#synthesize clicked = _clicked;
#synthesize action = _action;
#synthesize rightAction = _rightAction;
#synthesize target = _target;
- (void)setHighlightState:(BOOL)state{
if(self.clicked != state){
self.clicked = state;
[self setNeedsDisplay:YES];
}
}
- (void)drawImage:(NSImage *)aImage centeredInRect:(NSRect)aRect{
NSRect imageRect = NSMakeRect((CGFloat)round(aRect.size.width*0.5f-aImage.size.width*0.5f),
(CGFloat)round(aRect.size.height*0.5f-aImage.size.height*0.5f),
aImage.size.width,
aImage.size.height);
[aImage drawInRect:imageRect fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0f];
}
- (void)drawRect:(NSRect)rect{
if(self.clicked){
[[NSColor selectedMenuItemColor] set];
NSRectFill(rect);
if(self.alternateImage){
[self drawImage:self.alternateImage centeredInRect:rect];
}else if(self.image){
[self drawImage:self.image centeredInRect:rect];
}
}else if(self.image){
[self drawImage:self.image centeredInRect:rect];
}
}
- (void)mouseDown:(NSEvent *)theEvent{
[super mouseDown:theEvent];
[self setHighlightState:!self.clicked];
if ([theEvent modifierFlags] & NSCommandKeyMask){
[self.target performSelectorOnMainThread:self.rightAction withObject:nil waitUntilDone:NO];
}else{
[self.target performSelectorOnMainThread:self.action withObject:nil waitUntilDone:NO];
}
}
- (void)rightMouseDown:(NSEvent *)theEvent{
[super rightMouseDown:theEvent];
[self setHighlightState:!self.clicked];
[self.target performSelectorOnMainThread:self.rightAction withObject:nil waitUntilDone:NO];
}
- (void)dealloc{
self.target = nil;
self.action = nil;
self.rightAction = nil;
[super dealloc];
}
#end
One option is to just fake the left mouse down:
- (void)rightMouseDown: (NSEvent *)event {
NSEvent * newEvent;
newEvent = [NSEvent mouseEventWithType:NSLeftMouseDown
location:[event locationInWindow]
modifierFlags:[event modifierFlags]
timestamp:CFAbsoluteTimeGetCurrent()
windowNumber:[event windowNumber]
context:[event context]
eventNumber:[event eventNumber]
clickCount:[event clickCount]
pressure:[event pressure]];
[self mouseDown:newEvent];
}
Added little something for when you need title in your view
- (void)drawRect:(NSRect)rect{
if(self.clicked){
[[NSColor selectedMenuItemColor] set];
NSRectFill(rect);
if(self.alternateImage){
[self drawImage:self.alternateImage centeredInRect:rect];
}else if(self.image){
[self drawImage:self.image centeredInRect:rect];
} else {
[self drawTitleInRect:rect];
}
} else if(self.image){
[self drawImage:self.image centeredInRect:rect];
} else {
[self drawTitleInRect:rect];
}
}
-(void)drawTitleInRect:(CGRect)rect
{
CGSize size = [_title sizeWithAttributes:nil];
CGRect newRect = CGRectMake(MAX((rect.size.width - size.width)/2.f,0.f),
MAX((rect.size.height - size.height)/2.f,0.f),
size.width,
size.height);
NSDictionary *attributes = #{NSForegroundColorAttributeName : self.clicked?[NSColor highlightColor]:[NSColor textColor]
};
[_title drawInRect:newRect withAttributes:attributes];
}
- (void)statusItemAction {
NSEvent *event = NSApp.currentEvent;
if (event.type == NSEventTypeRightMouseDown || (event.modifierFlags & NSEventModifierFlagControl)) {
[self toggleMenu];
} else {
[self togglePopOver];
}
}