I have the following situation in my multi-monitor setup:
In this example I want to position a window exactly at the coordinates depicted with the yellow arrow. All I do have however, are the coordinates of an NSView that is a subview of the contentView of an NSWindow that spans the entire (bigger,upper) secondary monitor.
Here's how the global coordinate space is defined:
Thus y is increasing going down from green arrow, decreasing going up from green arrow.
Problem:
How do I convert the point depicted by the yellow arrow ({100,100}, NSView inside NSWindow inside this NSScreen) into that global coordinate system. (Note: In an NSView the coordinate system has {0,0} in the bottom left corner, increasing upwards.)
I believe the correct answer is {-196, -980}, but whats the code to get this conversion for any window on any screen?
I have spent too much time on this problem already, so any help is very appreciated.
(Not sure if relevant, but the bottom screen has a retina resolution display.)
Mac OS uses different coordinate systems in various places. Views can define if they have an upwards or downwards pointing y-axis (isFlipped
). A window's origin is expressed in "screen coordinates" with an upwards y-axis. Screens are arranged using the global coordinate system with y pointing down.
It's better not to try to do the conversion of all the coordinate spaces yourself but let the responsible objects do the job:
NSView *yellowView; // your view that contains the point with the yellow arrow.
NSPoint yellowPoint = { 100, 100 };
NSPoint pointInWindow = [yellowView convertPoint:yellowPoint toView:nil];
NSPoint pointOnScreen = [[yellowView window] convertRectToScreen:(CGRect){.origin=pointInWindow}];
NSWindow *newWindow = [[NSWindow alloc] initWithContentRect:(CGRect){ pointOnScreen, {32, 32}} styleMask: ...];
The Swift (4.0) version of the accepted answer is basically the same:
let yellowView: NSView // your view that contains the point with the yellow arrow.
let yellowPoint = NSPoint(x: 100, y: 100)
let pointInWindow = yellowView.convert(yellowPoint, to: nil)
let pointOnScreen = yellowView.window?.convertToScreen(NSRect(origin: pointInWindow, size: .zero)).origin ?? .zero
let contentRect = NSRect(origin: pointOnScreen, size: NSSize(width: 32, height: 32))
let newWindow = NSWindow(contentRect: contentRect, styleMask: ...)
The following is an alternate way to do it:
let someView: NSView // Some existing view
var rect: NSRect
rect = NSRect(x: 100, y: 100, width: 0, height: 0)
rect = someView.convert(rect, to: nil)
rect = someView.window?.convertToScreen(rect) ?? rect
rect.size = NSSize(width: 32, height: 32)
let newWindow = NSWindow(contentRect: rect, styleMask: ...)
The latter way simply sets the rect up ahead of time. Here's a play-by-play for those who like walkthroughs:
1. Create a rectangle. Initialize a zero-sized rectangle at the desired location within the view's coordinate system.
let someView: NSView // Some existing view
var rect = NSRect(x: 100, y: 100, width: 0, height: 0)
2. Convert from view to window. Convert the rectangle from the view's coordinate system to the window's coordinate system by specifying nil
for the destination view
.
rect = someView.convert(rect, to: nil)
3. Convert from window to screen. Next, convert the rectangle from the window's coordinate system to the screen's coordinate system.
Note that someView.window
may be nil
, so we use optional chaining (i.e. the ?
in window?
) and fallback to the original value of rect
if that's the case. This probably isn't necessary, but it's a good habit to get into.
rect = someView.window?.convertToScreen(rect) ?? rect
4. Set the rectangle's size. Update the rectangle with the desired size of the new window.
rect.size = NSSize(width: 32, height: 32)
5. Create the window. Initialize a new window with the converted rectangle.
let newWindow = NSWindow(contentRect: rect, styleMask: ...)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With