Search code examples
objective-cxcodemacostransparentnswindow

How to differentiate the mouseDown event from mouseDragged in a Transparent NSWindow



I have a Transparent NSWindow with an simple icon in it that can be dragged around the screen.
My code is:
.h:

@interface CustomView : NSWindow{
}

@property (assign) NSPoint initialLocation;

.m

@synthesize initialLocation;

- (id) initWithContentRect: (NSRect) contentRect
                 styleMask: (NSUInteger) aStyle
                   backing: (NSBackingStoreType) bufferingType
                     defer: (BOOL) flag{
    if (![super initWithContentRect: contentRect 
                          styleMask: NSBorderlessWindowMask 
                            backing: bufferingType 
                              defer: flag]) return nil;
    [self setBackgroundColor: [NSColor clearColor]];
    [self setOpaque:NO];
    [NSApp activateIgnoringOtherApps:YES];
    return self;
}

- (void)mouseDragged:(NSEvent *)theEvent {
    NSRect screenVisibleFrame = [[NSScreen mainScreen] visibleFrame];
    NSRect windowFrame = [self frame];
    NSPoint newOrigin = windowFrame.origin;

    // Get the mouse location in window coordinates.
    NSPoint currentLocation = [theEvent locationInWindow];
    // Update the origin with the difference between the new mouse location and the old mouse location.
    newOrigin.x += (currentLocation.x - initialLocation.x);
    newOrigin.y += (currentLocation.y - initialLocation.y);

    // Don't let window get dragged up under the menu bar
    if ((newOrigin.y + windowFrame.size.height) > (screenVisibleFrame.origin.y + screenVisibleFrame.size.height)) {
        newOrigin.y = screenVisibleFrame.origin.y + (screenVisibleFrame.size.height - windowFrame.size.height);
    }

    // Move the window to the new location
    [self setFrameOrigin:newOrigin];
}

- (void)mouseDown:(NSEvent *)theEvent {    
    // Get the mouse location in window coordinates.
    self.initialLocation = [theEvent locationInWindow];
}

I want to display a NSPopover when the users clicks the image of the transparent window. But, as you can see in the code, the mouseDown event is used to get the mouse location (the code above was taken from an example).
What can i do to know when the user clicks the icon just to drag it around or simply clicked it to display the NSPopover?
Thank you


Solution

  • This is the classic situation of receiving the defining event after you need it in order to begin the action. Specifically, you can't know if the mouseDown is the beginning of a drag until after the drag starts. However, you want to act upon that mouseDown if a drag doesn't start.

    In iOS (I realize that's not directly relevant to the code here, but it is instructional), there's an entire API built around letting iOS attempt to make these kinds of decisions for you. The entire Gesture system is based on the idea that the user starts to do something that might be one of many different actions, and thus needs to be resolved over time, possibly resulting in cancelled actions during the tracking period.

    On OS X, we don't have many systems to help out with this, so if you have something that needs to handle a click and a drag differentially, you will need to defer your next action until a guard time has passed, and if that passes, you can perform the original action. In this case, you will likely want to do the following:

    In the mouseDown, begin an NSTimer set for an appropriate guard time (not so long that people will accidentally move the pointer, and not so short that you'll trigger before the user drags) in order to call you back later to trigger the popover.

    In the mouseDragged, use a guard area to make sure that if the user just twitches a little, it doesn't count as a drag. This can be irritating, as it sometimes results in needing to drag something farther than it seems necessary in order to begin a drag, so you'll want to either find a magic constant somewhere, or do some experimentation. When the guard area is exceeded, then begin your legitimate drag operation by canceling the NSTimer with [timer invalidate] and do your drag.

    In the callback for the timer, display your popover. If the user dragged, the NSTimer will have been invalidated, causing it not to fire, and so the popover won't be displayed.