Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Notification during NSWindow movement

How can I get a notification while a NSWindow's position is changed by dragging its titlebar? I know I can use the windowWillMove: and windowDidMove: notifications, but those will give me a notification only when a drag is started or finished.

like image 857
user651847 Avatar asked Oct 12 '22 13:10

user651847


1 Answers

I have a solution that allows you to determine the window's position while it is being dragged.

The two issues are that there's no built-in way to get notified while the window is being dragged and that the window's frame doesn't update until it stops moving. My approach works around these issues by setting up a repeating timer and keeping track of the cursor's displacement.

First, subscribe to NSWindowWillMoveNotification and NSWindowDidMoveNotification to determine when the window starts and stops moving.

[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(windowWillMove:)
                                             name:@"NSWindowWillMoveNotification"
                                           object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(windowDidMove:)
                                             name:@"NSWindowDidMoveNotification"
                                           object:nil];

When the window is about to move, record the position of the cursor and start a repeating timer that calls your own "window is being dragged" method.

- (void)windowWillMove:(NSNotification *)notification {
    if (notification.object == self.view.window) { // make sure we have the right window
        self.dragCursorStartPos = [NSEvent mouseLocation];
        const NSTimeInterval dragDelaySeconds = 0.1; // polling rate delay
        self.dragWindowTimer = [NSTimer scheduledTimerWithTimeInterval:dragDelaySeconds
                                                                target:self
                                                              selector:@selector(myMethod)
                                                              userInfo:nil
                                                               repeats:YES];
    }
}

When the window is finished moving, stop the repeating timer.

- (void)windowDidMove:(NSNotification *)notification {
    if (notification.object == self.view.window) { // make sure we have the right window
        if (self.dragWindowTimer != NULL) {
            [self.dragWindowTimer invalidate];
            self.dragWindowTimer = NULL;
        }
    }
}

Now, the clever/hacky part is that we determine the frame's actual position by calculating the cursor's displacement from its starting position and adding this displacement to the frame's reported origin, which hasn't changed since the window started moving.

- (void)myMethod {
    NSPoint cursorPos = [NSEvent mouseLocation];
    NSPoint cursorDisplacement = NSMakePoint(cursorPos.x - self.dragCursorStartPos.x, cursorPos.y - self.dragCursorStartPos.y);
    CGPoint frameOrigin = self.view.window.frame.origin;
    CGPoint actualFrameOrigin = CGPointMake(frameOrigin.x + cursorDisplacement.x, frameOrigin.y + cursorDisplacement.y);
    NSLog(@"The frame's actual origin is (%f, %f)", actualFrameOrigin.x, actualFrameOrigin.y);
}

The actualFrameOrigin point in myMethod will report where the frame actually is, even though the self.view.window.frame.origin point only updates when you stop dragging the window.

This approach lets you get notified while the window is being dragged and tells you its actual position, so you're all set!


The only issue I've found is that pressing the title bar quickly without moving the cursor will fire NSWindowWillMoveNotification but not NSWindowDidMoveNotification, which causes the timer to incorrectly keep repeating. To handle this case, we can check if the left mouse button is being held down in myMethod by checking if (pressedButtons & (1 << 0)) == (1 << 0). If the button is not held down, we just cancel the timer.

like image 145
VinceFior Avatar answered Nov 15 '22 14:11

VinceFior