comparison foosdk/sdk/foobar2000/helpers/WindowPositionUtils.cpp @ 1:20d02a178406 default tip

*: check in everything else yay
author Paper <paper@tflc.us>
date Mon, 05 Jan 2026 02:15:46 -0500
parents
children
comparison
equal deleted inserted replaced
0:e9bb126753e7 1:20d02a178406
1 #include "StdAfx.h"
2 #include "WindowPositionUtils.h"
3
4 #define FB2K_WPU_DEBUG 0
5 namespace {
6 static BOOL GetParentWndRect(CWindow wndParent, CRect& rc) {
7 if (!wndParent.IsIconic()) {
8 return wndParent.GetWindowRect(rc);
9 }
10 WINDOWPLACEMENT pl = { sizeof(pl) };
11 if (!wndParent.GetWindowPlacement(&pl)) return FALSE;
12 rc = pl.rcNormalPosition;
13 return TRUE;
14 }
15
16 struct DeOverlapState {
17 CWindow m_thisWnd;
18 CPoint m_topLeft;
19 bool m_match;
20 };
21 static BOOL CALLBACK MyEnumChildProc(HWND wnd, LPARAM param) {
22 DeOverlapState* state = reinterpret_cast<DeOverlapState*>(param);
23 if (wnd != state->m_thisWnd && IsWindowVisible(wnd)) {
24 CRect rc;
25 if (GetWindowRect(wnd, rc)) {
26 if (rc.TopLeft() == state->m_topLeft) {
27 state->m_match = true; return FALSE;
28 }
29 }
30 }
31 return TRUE;
32 }
33 static bool DeOverlapTest(CWindow wnd, CPoint topLeft) {
34 DeOverlapState state = {};
35 state.m_thisWnd = wnd; state.m_topLeft = topLeft; state.m_match = false;
36 EnumThreadWindows(GetCurrentThreadId(), MyEnumChildProc, reinterpret_cast<LPARAM>(&state));
37 return state.m_match;
38 }
39 static int DeOverlapDelta() {
40 return pfc::max_t<int>(GetSystemMetrics(SM_CYCAPTION), 1);
41 }
42 static void DeOverlap(CWindow wnd, CRect& rc) {
43 const int delta = DeOverlapDelta();
44 for (;;) {
45 if (!DeOverlapTest(wnd, rc.TopLeft())) break;
46 rc.OffsetRect(delta, delta);
47 }
48 }
49 }
50
51 bool cfgDialogPositionData::grabFrom(CWindow wnd) {
52 CRect rc;
53 if (!GetClientRectAsSC(wnd, rc)) {
54 return false;
55 }
56 const CSize DPI = QueryScreenDPIEx(wnd);
57 m_dpiX = DPI.cx; m_dpiY = DPI.cy;
58 m_width = rc.Width(); m_height = rc.Height();
59 m_posX = m_posY = posInvalid;
60 CWindow parent = wnd.GetParent();
61 if (parent != NULL) {
62 CRect rcParent;
63 if (GetParentWndRect(parent, rcParent)) {
64 m_posX = rc.left - rcParent.left;
65 m_posY = rc.top - rcParent.top;
66 }
67 } else {
68 m_posX = rc.left; m_posY = rc.top;
69 }
70 return true;
71 }
72
73 pfc::string8 cfgDialogPositionData::debug() const {
74 pfc::string_formatter ret;
75 if (m_width != sizeInvalid) ret << "W: " << m_width << "\n";
76 if (m_height != sizeInvalid) ret << "H: " << m_height << "\n";
77 if (m_posX != posInvalid) ret << "X: " << m_posX << "\n";
78 if (m_posY != posInvalid) ret << "Y: " << m_posY << "\n";
79 if (m_dpiX != dpiInvalid) ret << "DPI-X: " << m_dpiX << "\n";
80 if (m_dpiY != dpiInvalid) ret << "DPI-Y: " << m_dpiY << "\n";
81 return ret;
82 }
83
84 cfgDialogPositionData cfgDialogPositionData::reDPI( CSize screenDPI ) const {
85 cfgDialogPositionData v = *this;
86 if (screenDPI.cx == 0 || screenDPI.cy == 0) {
87 PFC_ASSERT(!"Should not get here - something seriously wrong with the OS");
88 return v;
89 }
90 if (v.m_dpiX != dpiInvalid && v.m_dpiX != screenDPI.cx) {
91 if (v.m_width != sizeInvalid) v.m_width = MulDiv(v.m_width, screenDPI.cx, v.m_dpiX);
92 if (v.m_posX != posInvalid) v.m_posX = MulDiv(v.m_posX, screenDPI.cx, v.m_dpiX);
93 }
94 if (v.m_dpiY != dpiInvalid && v.m_dpiY != screenDPI.cy) {
95 if (v.m_height != sizeInvalid) v.m_height = MulDiv(v.m_height, screenDPI.cy, v.m_dpiY);
96 if (v.m_posY != posInvalid) v.m_posY = MulDiv(v.m_posY, screenDPI.cy, v.m_dpiY);
97 }
98 v.m_dpiX = screenDPI.cx;
99 v.m_dpiY = screenDPI.cy;
100 return v;
101 }
102
103
104 bool cfgDialogPositionData::overrideDefaultSize(t_uint32 width, t_uint32 height) {
105 bool rv = false;
106 if (m_width == sizeInvalid && m_height == sizeInvalid) {
107 m_width = width; m_height = height; m_posX = m_posY = posInvalid;
108 m_dpiX = m_dpiY = 96;
109 rv = true;
110 }
111 return rv;
112 }
113
114 bool cfgDialogPositionData::applyTo(CWindow wnd) const {
115 #if FB2K_WPU_DEBUG
116 FB2K_console_formatter() << "cfgDialogPositionData::applyTo(0x" << pfc::format_window( wnd ) << ")";
117 FB2K_console_formatter() << "data:\n" << this->debug();
118 #endif
119 const auto v = reDPI(QueryScreenDPIEx(wnd));
120 #if FB2K_WPU_DEBUG
121 FB2K_console_formatter() << "after reDPI:\n" << v.debug();
122 #endif
123 CWindow wndParent = wnd.GetParent();
124 UINT flags = SWP_NOACTIVATE | SWP_NOZORDER;
125 CRect rc;
126 if (!GetClientRectAsSC(wnd, rc)) return false;
127 if (v.m_width != v.sizeInvalid && v.m_height != v.sizeInvalid && (wnd.GetWindowLong(GWL_STYLE) & WS_SIZEBOX) != 0) {
128 rc.right = rc.left + v.m_width;
129 rc.bottom = rc.top + v.m_height;
130 } else {
131 flags |= SWP_NOSIZE;
132 }
133 if (wndParent != NULL) {
134 CRect rcParent;
135 if (GetParentWndRect(wndParent, rcParent)) {
136 if (v.m_posX != v.posInvalid && v.m_posY != v.posInvalid) {
137 rc.MoveToXY(rcParent.TopLeft() + CPoint(v.m_posX, v.m_posY));
138 } else {
139 CPoint center = rcParent.CenterPoint();
140 rc.MoveToXY(center.x - rc.Width() / 2, center.y - rc.Height() / 2);
141 }
142 }
143 } else {
144 if (v.m_posX != v.posInvalid && v.m_posY != v.posInvalid ) {
145 rc.MoveToXY( v.m_posX, v.m_posY );
146 }
147 }
148 if (!AdjustWindowRectHelper(wnd, rc)) return FALSE;
149
150 DeOverlap(wnd, rc);
151
152 {
153 CRect rcAdjust(0, 0, 1, 1);
154 if (wndParent != NULL) {
155 CRect temp;
156 if (wndParent.GetWindowRect(temp)) rcAdjust = temp;
157 }
158 AdjustRectToScreenArea(rc, rcAdjust);
159 }
160
161
162 return wnd.SetWindowPos(NULL, rc, flags);
163 }
164
165 void cfgDialogPosition::read_from_window(HWND wnd) {
166 cfgDialogPositionData data;
167 if (data.grabFrom(wnd)) this->set(data);
168 }
169
170 bool cfgDialogPosition::apply_to_window(HWND wnd) {
171 auto data = this->get();
172 return data.applyTo(wnd);
173 }
174
175 bool cfgWindowPositionData::grabFrom(CWindow wnd) {
176 WINDOWPLACEMENT wp = {sizeof(wp)};
177 bool rv = !! wnd.GetWindowPlacement(&wp);
178 if (rv) {
179 if ( !wnd.IsWindowVisible() ) wp.showCmd = SW_HIDE;
180 this->m_wp = wp;
181 m_dpi = QueryScreenDPIEx(wnd);
182 PFC_ASSERT( m_dpi.cx > 0 && m_dpi.cy > 0 );
183 }
184 return rv;
185 }
186
187 static void reDPI(WINDOWPLACEMENT& wp, SIZE from, SIZE to) {
188 wp.rcNormalPosition.left = MulDiv(wp.rcNormalPosition.left, to.cx, from.cx);
189 wp.rcNormalPosition.right = MulDiv(wp.rcNormalPosition.right, to.cx, from.cx);
190 wp.rcNormalPosition.top = MulDiv(wp.rcNormalPosition.top, to.cy, from.cy);
191 wp.rcNormalPosition.bottom = MulDiv(wp.rcNormalPosition.bottom, to.cy, from.cy);
192 }
193
194 bool applyWindowPlacement(HWND window, WINDOWPLACEMENT const& data, bool allowHidden); // window_placement_helper.cpp
195
196 bool cfgWindowPositionData::applyTo(CWindow wnd, bool allowHidden) const {
197 WINDOWPLACEMENT wp = m_wp;
198 if ( wp.length == 0 ) return false;
199 auto dpi = QueryScreenDPIEx(wnd);
200 if (dpi.cx != m_dpi.cx || dpi.cy != m_dpi.cy) {
201 reDPI( wp, m_dpi, dpi );
202 }
203 return applyWindowPlacement(wnd, wp, allowHidden);
204 }
205
206 void cfgWindowPosition::read_from_window(HWND wnd) {
207 // grabFrom might work partially, fail to obtain size due to window being hidden, use last values
208 cfgWindowPositionData data = get();
209 if ( data.grabFrom( wnd ) ) set(data);
210 }
211
212 bool cfgWindowPosition::apply_to_window(HWND wnd, bool allowHidden) {
213 auto data = get();
214 return data.applyTo( wnd, allowHidden );
215 }
216
217 void cfgWindowPosition::windowCreated(HWND wnd, bool allowHidden, DWORD showHow) {
218 auto data = get();
219 switch (showHow) {
220 case SW_HIDE:
221 case SW_MINIMIZE:
222 data.m_wp.showCmd = showHow;
223 break;
224 }
225 if (!data.applyTo(wnd, allowHidden)) {
226 ::ShowWindow( wnd, showHow);
227 }
228 }