Fix: macOS: Support multi-monitor window positioning
This commit brings multi-monitor window positioning support to the macOS GHOST backend. This fixes a plethora of issues with macOS window creation and positioning, such as: * Windows not being properly restored when loading a file with Load UI * Users default startup windows not being properly restored on multiple screens * Temporary windows (Settings, Render, Playblast, etc..) wrongly appearing in unexpected places / other screens * Duplicating an area into a new window (AKA popping out an editor) not working on non-primary screens. * etc.. Internally, this makes all macOS windows coordinates be relative to the user primary monitor, instead of being local to the currently focused one. I have tested this to properly work using all sorts of multiple screen arrangements, and can also confirm that restoring windows from screens that do not exist anymore / are now out of bounds (due to being unplugged or re-arranged) also works properly, in which case they get snapped back to the closest available screen similarly to other backends. This fixes issue #126410 and implements behavior described in TODO task #69819. Pull Request: https://projects.blender.org/blender/blender/pulls/141159
This commit is contained in:
@@ -686,10 +686,8 @@ uint64_t GHOST_SystemCocoa::getMilliSeconds() const
|
||||
|
||||
uint8_t GHOST_SystemCocoa::getNumDisplays() const
|
||||
{
|
||||
/* Note that OS X supports monitor hot plug.
|
||||
* We do not support multiple monitors at the moment. */
|
||||
@autoreleasepool {
|
||||
return NSScreen.screens.count;
|
||||
return [[NSScreen screens] count];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -697,7 +695,7 @@ void GHOST_SystemCocoa::getMainDisplayDimensions(uint32_t &width, uint32_t &heig
|
||||
{
|
||||
@autoreleasepool {
|
||||
/* Get visible frame, that is frame excluding dock and top menu bar. */
|
||||
const NSRect frame = [[NSScreen mainScreen] visibleFrame];
|
||||
const NSRect frame = [GHOST_WindowCocoa::getPrimaryScreen() visibleFrame];
|
||||
|
||||
/* Returns max window contents (excluding title bar...). */
|
||||
const NSRect contentRect = [NSWindow
|
||||
@@ -730,18 +728,13 @@ GHOST_IWindow *GHOST_SystemCocoa::createWindow(const char *title,
|
||||
GHOST_IWindow *window = nullptr;
|
||||
@autoreleasepool {
|
||||
/* Get the available rect for including window contents. */
|
||||
const NSRect frame = [[NSScreen mainScreen] visibleFrame];
|
||||
const NSRect contentRect = [NSWindow
|
||||
contentRectForFrameRect:frame
|
||||
const NSRect primaryScreenFrame = [GHOST_WindowCocoa::getPrimaryScreen() visibleFrame];
|
||||
const NSRect primaryScreenContentRect = [NSWindow
|
||||
contentRectForFrameRect:primaryScreenFrame
|
||||
styleMask:(NSWindowStyleMaskTitled | NSWindowStyleMaskClosable |
|
||||
NSWindowStyleMaskMiniaturizable)];
|
||||
|
||||
int32_t bottom = (contentRect.size.height - 1) - height - top;
|
||||
|
||||
/* Ensures window top left is inside this available rect. */
|
||||
left = left > contentRect.origin.x ? left : contentRect.origin.x;
|
||||
/* Add `contentRect.origin.y` to respect dock-size. */
|
||||
bottom = bottom > contentRect.origin.y ? bottom + contentRect.origin.y : contentRect.origin.y;
|
||||
const int32_t bottom = primaryScreenContentRect.size.height - top - height;
|
||||
|
||||
window = new GHOST_WindowCocoa(this,
|
||||
title,
|
||||
|
||||
@@ -184,10 +184,18 @@ class GHOST_WindowCocoa : public GHOST_Window {
|
||||
void screenToClientIntern(int32_t inX, int32_t inY, int32_t &outX, int32_t &outY) const;
|
||||
|
||||
/**
|
||||
* Gets the screen the window is displayed in
|
||||
* \return The NSScreen object
|
||||
* Return the screen the window is displayed in.
|
||||
* \return The current screen NSScreen object
|
||||
*/
|
||||
NSScreen *getScreen();
|
||||
NSScreen *getScreen() const;
|
||||
|
||||
/**
|
||||
* Return the primary screen, the screen defined as "Main Display" in macOS Settings, source of
|
||||
* all screen coordinates.
|
||||
* \note This function is placed in WindowCocoa since SystemCocoa cannot include Obj-C types.
|
||||
* \return The primary screen NSScreen object
|
||||
*/
|
||||
static NSScreen *getPrimaryScreen();
|
||||
|
||||
/**
|
||||
* Sets the state of the window (normal, minimized, maximized).
|
||||
|
||||
@@ -365,6 +365,11 @@ GHOST_WindowCocoa::GHOST_WindowCocoa(GHOST_SystemCocoa *systemCocoa,
|
||||
styleMask:styleMask
|
||||
backing:NSBackingStoreBuffered
|
||||
defer:NO];
|
||||
/* By default, AppKit repositions the window in the context of the current "mainMonitor"
|
||||
* (the monitor which has focus), bypass this by forcing the window back into its correct
|
||||
* position. Since we use global screen coordinate indexed on the first, primary screen.
|
||||
*/
|
||||
[m_window setFrameOrigin:NSMakePoint(left, bottom)];
|
||||
|
||||
/* Forbid to resize the window below the blender defined minimum one. */
|
||||
const NSSize minSize = {320, 240};
|
||||
@@ -600,13 +605,24 @@ void GHOST_WindowCocoa::getWindowBounds(GHOST_Rect &bounds) const
|
||||
GHOST_ASSERT(getValid(), "GHOST_WindowCocoa::getWindowBounds(): window invalid");
|
||||
|
||||
@autoreleasepool {
|
||||
const NSRect screenSize = m_window.screen.visibleFrame;
|
||||
const NSRect rect = m_window.frame;
|
||||
/* All coordinates are based off the primary screen. */
|
||||
const NSRect screenFrame = [getPrimaryScreen() visibleFrame];
|
||||
const NSRect windowFrame = m_window.frame;
|
||||
|
||||
bounds.m_b = screenSize.size.height - (rect.origin.y - screenSize.origin.y);
|
||||
bounds.m_l = rect.origin.x - screenSize.origin.x;
|
||||
bounds.m_r = rect.origin.x - screenSize.origin.x + rect.size.width;
|
||||
bounds.m_t = screenSize.size.height - (rect.origin.y + rect.size.height - screenSize.origin.y);
|
||||
/* Flip the Y axis, from bottom left coordinate to top left, which is the expected coordinate
|
||||
* return format for GHOST, even though the Window Manager later reflips it to bottom-left
|
||||
* this is the expected coordinate system for all GHOST backends
|
||||
*/
|
||||
|
||||
const int32_t screenMaxY = screenFrame.origin.y + screenFrame.size.height;
|
||||
|
||||
/* Flip the coordinates vertically from a bottom-left origin to a top-left origin,
|
||||
* as expected by GHOST. */
|
||||
bounds.m_b = screenMaxY - windowFrame.origin.y;
|
||||
bounds.m_t = screenMaxY - windowFrame.origin.y - windowFrame.size.height;
|
||||
|
||||
bounds.m_l = windowFrame.origin.x;
|
||||
bounds.m_r = windowFrame.origin.x + windowFrame.size.width;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -615,19 +631,25 @@ void GHOST_WindowCocoa::getClientBounds(GHOST_Rect &bounds) const
|
||||
GHOST_ASSERT(getValid(), "GHOST_WindowCocoa::getClientBounds(): window invalid");
|
||||
|
||||
@autoreleasepool {
|
||||
const NSRect screenSize = m_window.screen.visibleFrame;
|
||||
/* All coordinates are based off the primary screen. */
|
||||
const NSRect screenFrame = [getPrimaryScreen() visibleFrame];
|
||||
/* Screen Content Rectangle (excluding Menu Bar and Dock). */
|
||||
const NSRect screenContentRect = [NSWindow contentRectForFrameRect:screenFrame
|
||||
styleMask:[m_window styleMask]];
|
||||
|
||||
/* Max window contents as screen size (excluding title bar...). */
|
||||
const NSRect contentRect = [BlenderWindow contentRectForFrameRect:screenSize
|
||||
styleMask:[m_window styleMask]];
|
||||
const NSRect windowFrame = m_window.frame;
|
||||
/* Window Content Rectangle (excluding Titlebar and borders) */
|
||||
const NSRect windowContentRect = [m_window contentRectForFrameRect:windowFrame];
|
||||
|
||||
const NSRect rect = [m_window contentRectForFrameRect:[m_window frame]];
|
||||
const int32_t screenMaxY = screenContentRect.origin.y + screenContentRect.size.height;
|
||||
|
||||
bounds.m_b = contentRect.size.height - (rect.origin.y - contentRect.origin.y);
|
||||
bounds.m_l = rect.origin.x - contentRect.origin.x;
|
||||
bounds.m_r = rect.origin.x - contentRect.origin.x + rect.size.width;
|
||||
bounds.m_t = contentRect.size.height -
|
||||
(rect.origin.y + rect.size.height - contentRect.origin.y);
|
||||
/* Flip the coordinates vertically from a bottom-left origin to a top-left origin,
|
||||
* as expected by GHOST. */
|
||||
bounds.m_b = screenMaxY - windowContentRect.origin.y;
|
||||
bounds.m_t = screenMaxY - windowContentRect.origin.y - windowContentRect.size.height;
|
||||
|
||||
bounds.m_l = windowContentRect.origin.x;
|
||||
bounds.m_r = windowContentRect.origin.x + windowContentRect.size.width;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -763,11 +785,17 @@ void GHOST_WindowCocoa::clientToScreenIntern(int32_t inX,
|
||||
outY = screenCoord.origin.y;
|
||||
}
|
||||
|
||||
NSScreen *GHOST_WindowCocoa::getScreen()
|
||||
NSScreen *GHOST_WindowCocoa::getScreen() const
|
||||
{
|
||||
return m_window.screen;
|
||||
}
|
||||
|
||||
NSScreen *GHOST_WindowCocoa::getPrimaryScreen()
|
||||
{
|
||||
/* The first element of the screens array is guaranted to be the primary screen by AppKit. */
|
||||
return [[NSScreen screens] firstObject];
|
||||
}
|
||||
|
||||
/* called for event, when window leaves monitor to another */
|
||||
void GHOST_WindowCocoa::setNativePixelSize()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user