From 8d5f429c0e5dd3966d7bcdd20d223ea901674456 Mon Sep 17 00:00:00 2001
From: streak324
Date: Sun, 13 Jul 2025 11:57:45 -0700
Subject: [PATCH] allow minimum window size to be set for Windows OS
---
src/conf.rs | 4 +++
src/native/windows.rs | 83 ++++++++++++++++++++++++++++++-------------
2 files changed, 62 insertions(+), 25 deletions(-)
diff --git a/src/conf.rs b/src/conf.rs
index a7a3d407..aee90035 100644
--- a/src/conf.rs
+++ b/src/conf.rs
@@ -221,6 +221,9 @@ pub struct Conf {
/// If `true`, the user can resize the window.
pub window_resizable: bool,
+ /// if `Some((width, height))`, the window will not be smaller than the specified size (Works only with Windows currently).
+ pub min_window_size: Option<(i32, i32)>,
+
/// Optional icon data used by the OS where applicable:
/// - On Windows, taskbar/title bar icon
/// - On macOS, Dock/title bar icon
@@ -274,6 +277,7 @@ impl Default for Conf {
fullscreen: false,
sample_count: 1,
window_resizable: true,
+ min_window_size: None,
icon: Some(Icon::miniquad_logo()),
platform: Default::default(),
}
diff --git a/src/native/windows.rs b/src/native/windows.rs
index fa6fc80c..5c070fe3 100644
--- a/src/native/windows.rs
+++ b/src/native/windows.rs
@@ -53,6 +53,7 @@ pub(crate) struct WindowsDisplay {
event_handler: Option>,
modal_resizing_timer: usize,
update_requested: bool,
+ opt_min_client_size: Option<(i32, i32)>,
}
impl WindowsDisplay {
@@ -286,6 +287,18 @@ unsafe extern "system" fn win32_wndproc(
let event_handler = payload.event_handler.as_mut().unwrap();
match umsg {
+ WM_GETMINMAXINFO => {
+ let minmax = lparam as *mut MINMAXINFO;
+ if !minmax.is_null() {
+ if let Some(min_client_size) = payload.opt_min_client_size {
+ let calculated_win_attribs = determine_window_size_from_client_size(min_client_size.0, min_client_size.1, payload.fullscreen, payload.window_resizable);
+ // Set minimum tracking size (when user resizes)
+ (*minmax).ptMinTrackSize.x = calculated_win_attribs.win_width;
+ (*minmax).ptMinTrackSize.y = calculated_win_attribs.win_height;
+ }
+ }
+ return 0;
+ },
WM_CLOSE => {
let mut d = crate::native_display().lock().unwrap();
// only give user a chance to intervene when sapp_quit() wasn't already called
@@ -634,26 +647,14 @@ unsafe fn set_icon(wnd: HWND, icon: &Icon) {
}
}
-unsafe fn create_window(
- window_title: &str,
- fullscreen: bool,
- resizable: bool,
- width: i32,
- height: i32,
-) -> (HWND, HDC) {
- let mut wndclassw: WNDCLASSW = std::mem::zeroed();
-
- wndclassw.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
- wndclassw.lpfnWndProc = Some(win32_wndproc);
- wndclassw.hInstance = GetModuleHandleW(NULL as _);
- wndclassw.hCursor = LoadCursorW(NULL as _, IDC_ARROW);
- wndclassw.hIcon = LoadIconW(NULL as _, IDI_WINLOGO);
- wndclassw.hbrBackground = GetStockObject(BLACK_BRUSH as i32) as HBRUSH;
- let class_name = "MINIQUADAPP\0".encode_utf16().collect::>();
- wndclassw.lpszClassName = class_name.as_ptr() as _;
- wndclassw.cbWndExtra = std::mem::size_of::<*mut std::ffi::c_void>() as i32;
- RegisterClassW(&wndclassw);
+struct CalculatedWindowAttributes {
+ pub win_style: DWORD,
+ pub win_ex_style: DWORD,
+ pub win_width: i32,
+ pub win_height: i32,
+}
+unsafe fn determine_window_size_from_client_size(client_width: i32, client_height: i32, fullscreen: bool, resizable: bool) -> CalculatedWindowAttributes {
let win_style: DWORD;
let win_ex_style: DWORD = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;
let mut rect = RECT {
@@ -680,25 +681,56 @@ unsafe fn create_window(
WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX
};
- rect.right = width;
- rect.bottom = height;
+ rect.right = client_width;
+ rect.bottom = client_height;
}
AdjustWindowRectEx(&rect as *const _ as _, win_style, false as _, win_ex_style);
+
let win_width = rect.right - rect.left;
let win_height = rect.bottom - rect.top;
+ return CalculatedWindowAttributes {
+ win_style: win_style,
+ win_ex_style: win_ex_style,
+ win_width,
+ win_height,
+ };
+}
+
+unsafe fn create_window(
+ window_title: &str,
+ fullscreen: bool,
+ resizable: bool,
+ width: i32,
+ height: i32,
+) -> (HWND, HDC) {
+ let mut wndclassw: WNDCLASSW = std::mem::zeroed();
+
+ wndclassw.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
+ wndclassw.lpfnWndProc = Some(win32_wndproc);
+ wndclassw.hInstance = GetModuleHandleW(NULL as _);
+ wndclassw.hCursor = LoadCursorW(NULL as _, IDC_ARROW);
+ wndclassw.hIcon = LoadIconW(NULL as _, IDI_WINLOGO);
+ wndclassw.hbrBackground = GetStockObject(BLACK_BRUSH as i32) as HBRUSH;
+ let class_name = "MINIQUADAPP\0".encode_utf16().collect::>();
+ wndclassw.lpszClassName = class_name.as_ptr() as _;
+ wndclassw.cbWndExtra = std::mem::size_of::<*mut std::ffi::c_void>() as i32;
+ RegisterClassW(&wndclassw);
+
+ let calculated_win_attribs = determine_window_size_from_client_size(width, height, fullscreen, resizable);
+
let class_name = "MINIQUADAPP\0".encode_utf16().collect::>();
let mut window_name = window_title.encode_utf16().collect::>();
window_name.push(0);
let hwnd = CreateWindowExW(
- win_ex_style, // dwExStyle
+ calculated_win_attribs.win_ex_style, // dwExStyle
class_name.as_ptr(), // lpClassName
window_name.as_ptr(), // lpWindowName
- win_style, // dwStyle
+ calculated_win_attribs.win_style, // dwStyle
CW_USEDEFAULT, // X
CW_USEDEFAULT, // Y
- win_width, // nWidth
- win_height, // nHeight
+ calculated_win_attribs.win_width, // nWidth
+ calculated_win_attribs.win_height, // nHeight
NULL as _, // hWndParent
NULL as _, // hMenu
GetModuleHandleW(NULL as _), // hInstance
@@ -924,6 +956,7 @@ where
event_handler: None,
modal_resizing_timer: 0,
update_requested: true,
+ opt_min_client_size: Some((conf.window_width, conf.window_height)),
};
display.init_dpi(conf.high_dpi);