diff foosdk/sdk/foobar2000/helpers/WindowPositionUtils.h @ 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
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/foosdk/sdk/foobar2000/helpers/WindowPositionUtils.h	Mon Jan 05 02:15:46 2026 -0500
@@ -0,0 +1,264 @@
+#pragma once
+
+#ifdef _WIN32
+
+#include "win32_misc.h"
+#include "../SDK/cfg_var.h"
+
+static BOOL AdjustWindowRectHelper(CWindow wnd, CRect & rc) {
+	const DWORD style = wnd.GetWindowLong(GWL_STYLE), exstyle = wnd.GetWindowLong(GWL_EXSTYLE);
+	return AdjustWindowRectEx(&rc,style,(style & WS_POPUP) ? wnd.GetMenu() != NULL : FALSE, exstyle);
+}
+
+static void AdjustRectToScreenArea(CRect & rc, CRect rcParent) {
+	HMONITOR monitor = MonitorFromRect(rcParent,MONITOR_DEFAULTTONEAREST);
+	MONITORINFO mi = {sizeof(MONITORINFO)};
+	if (GetMonitorInfo(monitor,&mi)) {
+		const CRect clip = mi.rcWork;
+		if (rc.right > clip.right) rc.OffsetRect(clip.right - rc.right, 0);
+		if (rc.bottom > clip.bottom) rc.OffsetRect(0, clip.bottom - rc.bottom);
+		if (rc.left < clip.left) rc.OffsetRect(clip.left - rc.left, 0);
+		if (rc.top < clip.top) rc.OffsetRect(0, clip.top - rc.top);
+	}
+}
+
+static BOOL GetClientRectAsSC(CWindow wnd, CRect & rc) {
+	CRect temp;
+	if (!wnd.GetClientRect(temp)) return FALSE;
+	if (temp.IsRectNull()) return FALSE;
+	if (!wnd.ClientToScreen(temp)) return FALSE;
+	rc = temp;
+	return TRUE;
+}
+
+
+static BOOL CenterWindowGetRect(CWindow wnd, CWindow wndParent, CRect & out) {
+	CRect parent, child;
+	if (!wndParent.GetWindowRect(&parent) || !wnd.GetWindowRect(&child)) return FALSE;
+	{
+		CPoint origin = parent.CenterPoint();
+		origin.Offset( - child.Width() / 2, - child.Height() / 2);
+		child.OffsetRect( origin - child.TopLeft() );
+	}
+	AdjustRectToScreenArea(child, parent);
+	out = child;
+	return TRUE;
+}
+
+static BOOL CenterWindowAbove(CWindow wnd, CWindow wndParent) {
+	CRect rc;
+	if (!CenterWindowGetRect(wnd, wndParent, rc)) return FALSE;
+	return wnd.SetWindowPos(NULL,rc,SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
+}
+
+static BOOL ShowWindowCentered(CWindow wnd,CWindow wndParent) {
+	CRect rc;
+	if (!CenterWindowGetRect(wnd, wndParent, rc)) return FALSE;
+	return wnd.SetWindowPos(HWND_TOP,rc,SWP_NOSIZE | SWP_SHOWWINDOW);
+}
+
+typedef cfg_struct_t<SIZE> cfgWindowSize;
+
+class cfgWindowSizeTracker {
+public:
+	cfgWindowSizeTracker(cfgWindowSize & p_var) : m_var(p_var) {}
+
+	bool Apply(HWND p_wnd) {
+		bool retVal = false;
+		m_applied = false;
+		auto s = m_var.get();
+		if (s.cx > 0 && s.cy > 0) {
+			CRect rect(0,0,s.cx,s.cy);
+			if (AdjustWindowRectHelper(p_wnd, rect)) {
+				SetWindowPos(p_wnd,NULL,0,0,rect.right-rect.left,rect.bottom-rect.top,SWP_NOMOVE|SWP_NOACTIVATE|SWP_NOZORDER);
+				retVal = true;
+			}
+		}
+		m_applied = true;
+		return retVal;
+	}
+
+	BOOL ProcessWindowMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT & lResult) {
+		if (uMsg == WM_SIZE && m_applied) {
+			if (lParam != 0) {
+				m_var.set({ (short)LOWORD(lParam), (short)HIWORD(lParam) });
+			}
+		}
+		return FALSE;
+	}
+private:
+	cfgWindowSize & m_var;
+	bool m_applied = false;
+};
+
+class cfgDialogSizeTracker : public cfgWindowSizeTracker {
+public:
+	cfgDialogSizeTracker(cfgWindowSize & p_var) : cfgWindowSizeTracker(p_var) {}
+	BOOL ProcessWindowMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT & lResult) {
+		if (cfgWindowSizeTracker::ProcessWindowMessage(hWnd, uMsg, wParam, lParam, lResult)) return TRUE;
+		if (uMsg == WM_INITDIALOG) Apply(hWnd);
+		return FALSE;
+	}
+};
+
+struct cfgDialogPositionData {
+	static constexpr int32_t
+		posInvalid = 0x80000000;
+	static constexpr uint32_t 
+		sizeInvalid = 0xFFFFFFFF,
+		dpiInvalid = 0;
+
+	uint32_t m_width = sizeInvalid, m_height = sizeInvalid;
+	int32_t m_posX = posInvalid, m_posY = posInvalid;
+	uint32_t m_dpiX = dpiInvalid, m_dpiY = dpiInvalid;
+
+	cfgDialogPositionData reDPI(CSize) const;
+	bool grabFrom(CWindow wnd);
+	bool applyTo(CWindow wnd) const;
+
+	bool overrideDefaultSize(t_uint32 width, t_uint32 height);
+
+	pfc::string8 debug() const;
+};
+
+struct cfgWindowPositionData {
+	WINDOWPLACEMENT m_wp = {};
+	SIZE m_dpi = {};
+
+	bool grabFrom(CWindow wnd);
+	bool applyTo(CWindow wnd, bool allowHidden = false) const;
+};
+
+FB2K_STREAM_READER_OVERLOAD(cfgDialogPositionData) {
+	stream >> value.m_width >> value.m_height;
+	try {
+		stream >> value.m_posX >> value.m_posY >> value.m_dpiX >> value.m_dpiY;
+	} catch (exception_io_data const &) {
+		value.m_posX = value.m_posY = cfgDialogPositionData::posInvalid;
+		value.m_dpiX = value.m_dpiY = cfgDialogPositionData::dpiInvalid;
+	}
+	return stream;
+}
+FB2K_STREAM_WRITER_OVERLOAD(cfgDialogPositionData) {
+	return stream << value.m_width << value.m_height << value.m_posX << value.m_posY << value.m_dpiX << value.m_dpiY;
+}
+
+class cfgDialogPosition : public cfg_struct_t<cfgDialogPositionData> {
+public:
+	cfgDialogPosition(const GUID& id) : cfg_struct_t(id) {}
+
+	//! Read and save size data from HWND.
+	void read_from_window(HWND);
+	//! Apply saved size data to HWND.
+	bool apply_to_window(HWND);
+
+	void AddWindow(HWND wnd) { apply_to_window(wnd); }
+	void RemoveWindow(HWND wnd) { read_from_window(wnd); }
+};
+
+class cfgWindowPosition : public cfg_struct_t<cfgWindowPositionData> {
+public:
+	cfgWindowPosition(const GUID & id) : cfg_struct_t(id) {}
+
+	//! Read and save size data from HWND.
+	void read_from_window(HWND);
+	//! Apply saved size data to HWND.
+	bool apply_to_window(HWND, bool allowHidden = false);
+	//! New window created, show it with saved metrics.
+	void windowCreated(HWND, bool allowHidden = false, DWORD showHow = SW_SHOW);
+};
+
+class cfgDialogPositionTracker {
+public:
+	cfgDialogPositionTracker(cfgDialogPosition & p_var) : m_var(p_var) {}
+	~cfgDialogPositionTracker() {Cleanup();}
+
+	BOOL ProcessWindowMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT & lResult) {
+		if (uMsg == WM_CREATE || uMsg == WM_INITDIALOG) {
+			Cleanup();
+			m_wnd = hWnd;
+			m_var.AddWindow(m_wnd);
+		} else if (uMsg == WM_DESTROY) {
+			PFC_ASSERT( hWnd == m_wnd );
+			Cleanup();
+		}
+		return FALSE;
+	}
+
+private:
+	void Cleanup() {
+		if (m_wnd != NULL) {
+			m_var.RemoveWindow(m_wnd);
+			m_wnd = NULL;
+		}
+	}
+	cfgDialogPosition & m_var;
+	CWindow m_wnd;
+};
+
+//! DPI-safe window size var \n
+//! Stores size in pixel and original DPI\n
+//! Use with cfgWindowSizeTracker2
+struct cfgWindowSize2_data {
+	CSize m_size = CSize(0, 0), m_dpi = CSize(0, 0);
+};
+
+class cfgWindowSize2 : public cfg_struct_t< cfgWindowSize2_data > {
+public:
+	cfgWindowSize2(const GUID & p_guid) : cfg_struct_t(p_guid) {}
+
+	bool is_valid() {
+		auto v = cfg_struct_t::get();
+		return v.m_size.cx > 0 && v.m_size.cy > 0;
+	}
+
+	CSize get( CSize forDPI ) {
+		auto v = cfg_struct_t::get();
+		if ( forDPI == v.m_dpi ) return v.m_size;
+
+		CSize ret;
+		ret.cx = MulDiv( v.m_size.cx, forDPI.cx, v.m_dpi.cx );
+		ret.cy = MulDiv( v.m_size.cy, forDPI.cy, v.m_dpi.cy );
+		return ret;
+	}
+};
+
+//! Forward messages to this class to utilize cfgWindowSize2
+class cfgWindowSizeTracker2 : public CMessageMap {
+public:
+	cfgWindowSizeTracker2( cfgWindowSize2 & var ) : m_var(var) {}
+
+	BEGIN_MSG_MAP_EX(cfgWindowSizeTracker2)
+		if (uMsg == WM_CREATE || uMsg == WM_INITDIALOG) {
+			Apply(hWnd);
+		}
+		MSG_WM_SIZE( OnSize )
+	END_MSG_MAP()
+
+	bool Apply(HWND p_wnd) {
+		bool retVal = false;
+		m_applied = false;
+		if (m_var.is_valid()) {
+			CRect rect( CPoint(0,0), m_var.get( m_DPI ) );
+			if (AdjustWindowRectHelper(p_wnd, rect)) {
+				SetWindowPos(p_wnd,NULL,0,0,rect.right-rect.left,rect.bottom-rect.top,SWP_NOMOVE|SWP_NOACTIVATE|SWP_NOZORDER);
+				retVal = true;
+			}
+		}
+		m_applied = true;
+		return retVal;
+	}
+
+private:
+	void OnSize(UINT nType, CSize size) {
+		if ( m_applied && size.cx > 0 && size.cy > 0 ) {
+			m_var.set( { size, m_DPI } );
+		}
+		SetMsgHandled(FALSE);
+	}
+	cfgWindowSize2 & m_var;
+	bool m_applied = false;
+	const CSize m_DPI = QueryScreenDPIEx();
+};
+
+#endif // _WIN32