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);