-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdpi_wnd_behavior.cpp
More file actions
221 lines (175 loc) · 7.28 KB
/
dpi_wnd_behavior.cpp
File metadata and controls
221 lines (175 loc) · 7.28 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
// GNU LESSER GENERAL PUBLIC LICENSE
// Version 3, 29 June 2007
//
// Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
//
// Everyone is permitted to copy and distribute verbatim copies of this license
// document, but changing it is not allowed.
//
// This version of the GNU Lesser General Public License incorporates the terms
// and conditions of version 3 of the GNU General Public License, supplemented
// by the additional permissions listed below.
#include "stdafx.h"
#include "dpi_wnd_behavior.h"
#include <windowsx.h> // GetWindowFont
#include <cassert>
#include <tuple>
namespace {
HFONT ScaleFontByDpi(HFONT old_font, unsigned previousDpiY, unsigned newDpiY) {
if (LOGFONT font_settings = {};
::GetObject(old_font, sizeof(font_settings), &font_settings)) {
// Scale font.
font_settings.lfHeight =
::MulDiv(font_settings.lfHeight, newDpiY, previousDpiY);
return ::CreateFontIndirect(&font_settings);
}
// Fallback to default system font when unable to get font.
// "For uiAction values that contain strings within their associated
// structures, only Unicode (LOGFONTW) strings are supported in this
// function."
//
// See
// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-systemparametersinfofordpi
if (LOGFONTW font_settings = {}; ::SystemParametersInfoForDpi(
SPI_GETICONTITLELOGFONT, sizeof(font_settings), &font_settings, FALSE,
newDpiY)) {
return ::CreateFontIndirectW(&font_settings);
}
return nullptr;
}
// Get the font for the window, then delete it.
BOOL DeleteWindowFont(HWND window) {
// Get a handle to the font.
HFONT font{GetWindowFont(window)};
if (!font) return FALSE;
SetWindowFont(window, nullptr, FALSE);
return ::DeleteObject(font);
}
BOOL ApplyDpiToWindowFont(HWND window, unsigned previousDpiY,
unsigned newDpiY) {
const HFONT old_font{GetWindowFont(window)};
// Scale font by DPI.
const HFONT new_font{ScaleFontByDpi(old_font, previousDpiY, newDpiY)};
if (new_font) {
// Set a new font to our window.
::SendMessage(window, WM_SETFONT, reinterpret_cast<WPARAM>(new_font),
MAKELPARAM(TRUE, 0));
return old_font ? ::DeleteObject(old_font) : TRUE;
}
return FALSE;
}
/**
* @brief Update window child controls and DPI dependent things.
* @param parentWnd Window.
* @param previousDpiX Old X DPI value for window.
* @param previousDpiY Old Y DPI value for window.
*/
void ApplyDpiToWindow(HWND parentWnd, unsigned previousDpiX,
unsigned previousDpiY) {
const unsigned new_dpi_y{::GetDpiForWindow(parentWnd)};
ApplyDpiToWindowFont(parentWnd, previousDpiY, new_dpi_y);
const auto change_dpi_request = std::make_tuple(previousDpiX, previousDpiY);
// Return value is not used.
// See
// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-enumchildwindows
(void)::EnumChildWindows(
parentWnd,
[](HWND child_window, LPARAM lParam) {
auto* parent_old_dpis =
reinterpret_cast<std::tuple<unsigned, unsigned>*>(lParam);
const auto& [old_dpi_x, old_dpi_y] = *parent_old_dpis;
const unsigned new_x_dpi{::GetDpiForWindow(child_window)};
// Microsoft docs claim they are same.
const unsigned new_y_dpi{new_x_dpi};
// Child window rect in screen coordinates.
RECT rc_child;
::GetWindowRect(child_window, &rc_child);
// Parent window rect in screen coordinates.
const HWND parent_window{::GetParent(child_window)};
RECT rc_parent;
::GetWindowRect(parent_window, &rc_parent);
// Transforming the child window pos from screen space to parent
// window space.
POINT child_pos{rc_child.left, rc_child.top};
::ScreenToClient(parent_window, &child_pos);
const int scaled_x{::MulDiv(child_pos.x, new_x_dpi, old_dpi_x)};
const int scaled_y{::MulDiv(child_pos.y, new_y_dpi, old_dpi_y)};
const int scaled_width{
::MulDiv(rc_child.right - rc_child.left, new_x_dpi, old_dpi_x)};
const int scaled_height{
::MulDiv(rc_child.bottom - rc_child.top, new_y_dpi, old_dpi_y)};
::SetWindowPos(child_window, nullptr, scaled_x, scaled_y, scaled_width,
scaled_height, SWP_NOZORDER | SWP_NOACTIVATE);
// Apply DPI scaling for font in all child controls.
ApplyDpiToWindow(child_window, old_dpi_y, new_y_dpi);
return 1;
},
reinterpret_cast<LPARAM>(&change_dpi_request));
}
} // namespace
DpiWindowBehavior::DpiWindowBehavior(bool applyDpiOnCreate)
: m_window_handle{nullptr},
m_previous_dpi_x{USER_DEFAULT_SCREEN_DPI},
m_previous_dpi_y{USER_DEFAULT_SCREEN_DPI},
m_current_dpi_x{USER_DEFAULT_SCREEN_DPI},
m_current_dpi_y{USER_DEFAULT_SCREEN_DPI},
m_apply_dpi_on_create{applyDpiOnCreate} {}
BOOL DpiWindowBehavior::OnCreateWindow(HWND window) {
assert(window);
m_window_handle = window;
// Windows docs say DPI is same for X and Y.
m_previous_dpi_x = m_previous_dpi_y =
std::exchange(m_current_dpi_x, ::GetDpiForWindow(window));
m_current_dpi_y = m_current_dpi_x;
return ApplyDpiToWindow(m_apply_dpi_on_create);
}
void DpiWindowBehavior::OnDestroyWindow() {
assert(m_window_handle);
DeleteWindowFont(m_window_handle);
m_window_handle = nullptr;
}
[[nodiscard]] int DpiWindowBehavior::ScaleOnX(int value) const {
return ::MulDiv(value, m_current_dpi_x, m_previous_dpi_x);
}
[[nodiscard]] int DpiWindowBehavior::ScaleOnY(int value) const {
return ::MulDiv(value, m_current_dpi_y, m_previous_dpi_y);
}
LRESULT DpiWindowBehavior::OnWindowDpiChanged(WPARAM wParam, LPARAM lParam) {
assert(m_window_handle);
// Get new window rectangle Windows suggests to us.
const auto* new_rect = reinterpret_cast<RECT*>(lParam);
assert(new_rect);
::SetWindowPos(m_window_handle, nullptr, new_rect->left, new_rect->top,
new_rect->right - new_rect->left,
new_rect->bottom - new_rect->top,
SWP_NOZORDER | SWP_NOACTIVATE);
// Apply font scaling and notify children.
::ApplyDpiToWindow(m_window_handle, m_previous_dpi_x, m_previous_dpi_y);
m_previous_dpi_x = std::exchange(m_current_dpi_x, LOWORD(wParam));
m_previous_dpi_y = std::exchange(m_current_dpi_y, HIWORD(wParam));
return 0;
}
BOOL DpiWindowBehavior::ApplyDpiToWindow(bool recompute_window_size) {
assert(m_window_handle);
BOOL rc{TRUE};
if (RECT rc_window = {}; ::GetClientRect(m_window_handle, &rc_window)) {
// Compute and apply new window rectangle based on DPI.
const BOOL window_has_menu{::GetMenu(m_window_handle) ? TRUE : FALSE};
rc = ::AdjustWindowRectExForDpi(
&rc_window, GetWindowLong(m_window_handle, GWL_STYLE), window_has_menu,
GetWindowLong(m_window_handle, GWL_EXSTYLE), m_current_dpi_y);
assert(rc);
if (rc) {
rc = ::SetWindowPos(m_window_handle, nullptr, 0, 0,
rc_window.right - rc_window.left,
rc_window.bottom - rc_window.top,
SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
assert(rc);
}
}
if (rc && recompute_window_size) {
// Apply font scaling and notify children.
::ApplyDpiToWindow(m_window_handle, m_previous_dpi_x, m_previous_dpi_y);
}
return rc;
}