Win32: Window Placement DPI and Scale Adjustment

When using multiple monitors that differ in scale and/or dpi, the
varying sizes of the window titles and borders can cause the placement
of those windows to be out by a small amount. This patch adjusts for
those differences on Windows 10 and newer.

see D10863 for details and examples.

Differential Revision: https://developer.blender.org/D10863

Reviewed by Ray Molenkamp
This commit is contained in:
Harley Acheson 2021-06-29 09:28:20 -07:00
parent 2ff714269e
commit 999f1f7504
Notes: blender-bot 2023-02-21 17:59:30 +01:00
Referenced by issue #88384, Window size/location are not properly saved in default start-up file
2 changed files with 69 additions and 38 deletions

View File

@ -38,6 +38,7 @@
#include <assert.h>
#include <math.h>
#include <shellscalingapi.h>
#include <string.h>
#include <windowsx.h>
@ -80,13 +81,10 @@ GHOST_WindowWin32::GHOST_WindowWin32(GHOST_SystemWin32 *system,
m_wintab(NULL),
m_lastPointerTabletData(GHOST_TABLET_DATA_NONE),
m_normal_state(GHOST_kWindowStateNormal),
m_user32(NULL),
m_user32(::LoadLibrary("user32.dll")),
m_parentWindowHwnd(parentwindow ? parentwindow->m_hWnd : HWND_DESKTOP),
m_debug_context(is_debug)
{
wchar_t *title_16 = alloc_utf16_from_8((char *)title, 0);
RECT win_rect = {left, top, (long)(left + width), (long)(top + height)};
DWORD style = parentwindow ?
WS_POPUPWINDOW | WS_CAPTION | WS_MAXIMIZEBOX | WS_MINIMIZEBOX | WS_SIZEBOX :
WS_OVERLAPPEDWINDOW;
@ -105,27 +103,10 @@ GHOST_WindowWin32::GHOST_WindowWin32(GHOST_SystemWin32 *system,
*/
}
/* Monitor details. */
MONITORINFOEX monitor;
monitor.cbSize = sizeof(MONITORINFOEX);
monitor.dwFlags = 0;
GetMonitorInfo(MonitorFromRect(&win_rect, MONITOR_DEFAULTTONEAREST), &monitor);
/* Constrain requested size and position to fit within this monitor. */
width = min(monitor.rcWork.right - monitor.rcWork.left, win_rect.right - win_rect.left);
height = min(monitor.rcWork.bottom - monitor.rcWork.top, win_rect.bottom - win_rect.top);
win_rect.left = min(max(monitor.rcWork.left, win_rect.left), monitor.rcWork.right - width);
win_rect.right = win_rect.left + width;
win_rect.top = min(max(monitor.rcWork.top, win_rect.top), monitor.rcWork.bottom - height);
win_rect.bottom = win_rect.top + height;
/* Adjust to allow for caption, borders, shadows, scaling, etc. Resulting values can be
* correctly outside of monitor bounds. Note: You cannot specify WS_OVERLAPPED when calling. */
AdjustWindowRectEx(&win_rect, style & ~WS_OVERLAPPED, FALSE, extended_style);
/* But never allow a top position that can hide part of the title bar. */
win_rect.top = max(monitor.rcWork.top, win_rect.top);
RECT win_rect = {left, top, (long)(left + width), (long)(top + height)};
adjustWindowRectForClosestMonitor(&win_rect, style, extended_style);
wchar_t *title_16 = alloc_utf16_from_8((char *)title, 0);
m_hWnd = ::CreateWindowExW(extended_style, // window extended style
s_windowClassName, // pointer to registered class name
title_16, // pointer to window name
@ -153,8 +134,6 @@ GHOST_WindowWin32::GHOST_WindowWin32(GHOST_SystemWin32 *system,
return;
}
m_user32 = ::LoadLibrary("user32.dll");
RegisterTouchWindow(m_hWnd, 0);
/* Register as droptarget. OleInitialize(0) required first, done in GHOST_SystemWin32. */
@ -187,22 +166,22 @@ GHOST_WindowWin32::GHOST_WindowWin32(GHOST_SystemWin32 *system,
::ShowWindow(m_hWnd, nCmdShow);
#ifdef WIN32_COMPOSITING
if (alphaBackground && parentwindowhwnd == 0) {
if (alphaBackground && parentwindowhwnd == 0) {
HRESULT hr = S_OK;
HRESULT hr = S_OK;
/* Create and populate the Blur Behind structure. */
DWM_BLURBEHIND bb = {0};
/* Create and populate the Blur Behind structure. */
DWM_BLURBEHIND bb = {0};
/* Enable Blur Behind and apply to the entire client area. */
bb.dwFlags = DWM_BB_ENABLE | DWM_BB_BLURREGION;
bb.fEnable = true;
bb.hRgnBlur = CreateRectRgn(0, 0, -1, -1);
/* Enable Blur Behind and apply to the entire client area. */
bb.dwFlags = DWM_BB_ENABLE | DWM_BB_BLURREGION;
bb.fEnable = true;
bb.hRgnBlur = CreateRectRgn(0, 0, -1, -1);
/* Apply Blur Behind. */
hr = DwmEnableBlurBehindWindow(m_hWnd, &bb);
DeleteObject(bb.hRgnBlur);
}
/* Apply Blur Behind. */
hr = DwmEnableBlurBehindWindow(m_hWnd, &bb);
DeleteObject(bb.hRgnBlur);
}
#endif
/* Force an initial paint of the window. */
@ -267,6 +246,47 @@ GHOST_WindowWin32::~GHOST_WindowWin32()
}
}
void GHOST_WindowWin32::adjustWindowRectForClosestMonitor(LPRECT win_rect,
DWORD dwStyle,
DWORD dwExStyle)
{
/* Get Details of the closest monitor. */
HMONITOR hmonitor = MonitorFromRect(win_rect, MONITOR_DEFAULTTONEAREST);
MONITORINFOEX monitor;
monitor.cbSize = sizeof(MONITORINFOEX);
monitor.dwFlags = 0;
GetMonitorInfo(hmonitor, &monitor);
/* Constrain requested size and position to fit within this monitor. */
LONG width = min(monitor.rcWork.right - monitor.rcWork.left, win_rect->right - win_rect->left);
LONG height = min(monitor.rcWork.bottom - monitor.rcWork.top, win_rect->bottom - win_rect->top);
win_rect->left = min(max(monitor.rcWork.left, win_rect->left), monitor.rcWork.right - width);
win_rect->right = win_rect->left + width;
win_rect->top = min(max(monitor.rcWork.top, win_rect->top), monitor.rcWork.bottom - height);
win_rect->bottom = win_rect->top + height;
/* With Windows 10 and newer we can adjust for chrome that differs with DPI and scale. */
GHOST_WIN32_AdjustWindowRectExForDpi fpAdjustWindowRectExForDpi = nullptr;
if (m_user32) {
fpAdjustWindowRectExForDpi = (GHOST_WIN32_AdjustWindowRectExForDpi)::GetProcAddress(
m_user32, "AdjustWindowRectExForDpi");
}
/* Adjust to allow for caption, borders, shadows, scaling, etc. Resulting values can be
* correctly outside of monitor bounds. Note: You cannot specify WS_OVERLAPPED when calling. */
if (fpAdjustWindowRectExForDpi) {
UINT dpiX, dpiY;
GetDpiForMonitor(hmonitor, MDT_EFFECTIVE_DPI, &dpiX, &dpiY);
fpAdjustWindowRectExForDpi(win_rect, dwStyle & ~WS_OVERLAPPED, FALSE, dwExStyle, dpiX);
}
else {
AdjustWindowRectEx(win_rect, dwStyle & ~WS_OVERLAPPED, FALSE, dwExStyle);
}
/* But never allow a top position that can hide part of the title bar. */
win_rect->top = max(monitor.rcWork.top, win_rect->top);
}
bool GHOST_WindowWin32::getValid() const
{
return GHOST_Window::getValid() && m_hWnd != 0 && m_hDC != 0;

View File

@ -43,6 +43,9 @@ class GHOST_DropTargetWin32;
// typedefs for user32 functions to allow dynamic loading of Windows 10 DPI scaling functions
typedef UINT(API *GHOST_WIN32_GetDpiForWindow)(HWND);
typedef BOOL(API *GHOST_WIN32_AdjustWindowRectExForDpi)(
LPRECT lpRect, DWORD dwStyle, BOOL bMenu, DWORD dwExStyle, UINT dpi);
struct GHOST_PointerInfoWin32 {
GHOST_TInt32 pointerId;
GHOST_TInt32 isPrimary;
@ -98,6 +101,14 @@ class GHOST_WindowWin32 : public GHOST_Window {
*/
~GHOST_WindowWin32();
/**
* Adjusts a requested window rect to fit and position correctly in monitor.
* \param win_rect: pointer to rectangle that will be modified.
* \param dwStyle: The Window Style of the window whose required size is to be calculated.
* \param dwExStyle: The Extended Window Style of the window.
*/
void adjustWindowRectForClosestMonitor(LPRECT win_rect, DWORD dwStyle, DWORD dwExStyle);
/**
* Returns indication as to whether the window is valid.
* \return The validity of the window.