From fa75aa70ce0f2c077aba715ecbbf9498814fcd37 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Sat, 8 Aug 2026 23:17:14 +0200 Subject: [PATCH 1/7] X11 impl --- examples/render_wgpu/src/main.rs | 6 ++++-- src/platform/x11/xcb_connection/size_hints.rs | 10 +++++++++- src/settings.rs | 17 +++++++++++++++++ 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/examples/render_wgpu/src/main.rs b/examples/render_wgpu/src/main.rs index f72944d5..7d254377 100644 --- a/examples/render_wgpu/src/main.rs +++ b/examples/render_wgpu/src/main.rs @@ -211,8 +211,10 @@ impl WindowHandler for WgpuExample { fn main() -> Result<(), baseview::Error> { env_logger::builder().filter_level(LevelFilter::Debug).init(); - let window_open_options = - WindowSettings::new().with_title("WGPU on Baseview").with_size(LogicalSize::new(512, 512)); + let window_open_options = WindowSettings::new() + .with_title("WGPU on Baseview") + .with_size(LogicalSize::new(512, 512)) + .with_max_size(LogicalSize::new(512, 512)); Window::create(window_open_options, |c| pollster::block_on(WgpuExample::new(c)))? .run_until_closed()?; diff --git a/src/platform/x11/xcb_connection/size_hints.rs b/src/platform/x11/xcb_connection/size_hints.rs index 9d2157ba..9a869364 100644 --- a/src/platform/x11/xcb_connection/size_hints.rs +++ b/src/platform/x11/xcb_connection/size_hints.rs @@ -1,5 +1,5 @@ use crate::WindowSettings; -use dpi::PhysicalSize; +use dpi::{PhysicalSize, Size}; use x11rb::properties::WmSizeHints; pub fn get_size_hints(settings: &WindowSettings, scale_factor: f64) -> WmSizeHints { @@ -7,11 +7,19 @@ pub fn get_size_hints(settings: &WindowSettings, scale_factor: f64) -> WmSizeHin if !settings.resizable { size_hints = size_hints.with_fixed_size(settings.size.to_physical(scale_factor)); + } else { + size_hints.min_size = settings.min_size.map(|s| to_size_hint(s, scale_factor)); + size_hints.max_size = settings.max_size.map(|s| to_size_hint(s, scale_factor)); } size_hints } +fn to_size_hint(size: Size, scale_factor: f64) -> (i32, i32) { + let size = size.to_physical(scale_factor); + (size.width, size.height) +} + pub trait WmSizeHintsExt: Sized { fn with_fixed_size(self, size: PhysicalSize) -> Self; } diff --git a/src/settings.rs b/src/settings.rs index 22d51f3f..65ff592b 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -30,6 +30,9 @@ pub struct WindowSettings { /// Whether the window can be resized. pub resizable: bool, + pub min_size: Option, + pub max_size: Option, + /// A fallback scale factor, if Baseview couldn't get one from the platform. /// /// If the platform does already provide an accurate scaling factor, this doesn't do anything. @@ -111,6 +114,18 @@ impl WindowSettings { self } + #[inline] + pub fn with_min_size>(mut self, min_size: impl Into>) -> Self { + self.min_size = min_size.into().map(S::into); + self + } + + #[inline] + pub fn with_max_size>(mut self, max_size: impl Into>) -> Self { + self.max_size = max_size.into().map(S::into); + self + } + /// Sets [`gl_config`](Self::gl_config) to the given value. #[cfg(feature = "opengl")] #[inline] @@ -129,6 +144,8 @@ impl Default for WindowSettings { wait_for_parent: false, fallback_scale_factor: None, resizable: true, + min_size: None, + max_size: None, #[cfg(feature = "opengl")] gl_config: None, } From 3d5c553bf6a8dd973e6515b3eea5b8be4a6c5fd3 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Sun, 9 Aug 2026 10:06:23 +0200 Subject: [PATCH 2/7] Add fetching min/max size --- examples/plugin_clack/src/gui.rs | 18 ++++++- src/lib.rs | 1 + src/platform/x11/window_shared.rs | 19 ++++---- src/platform/x11/window_thread.rs | 33 ++++++++----- src/platform/x11/xcb_connection.rs | 2 +- src/platform/x11/xcb_connection/size_hints.rs | 37 +++++++------- src/utils.rs | 48 +++++++++++++++++++ src/window.rs | 16 +++++++ 8 files changed, 131 insertions(+), 43 deletions(-) create mode 100644 src/utils.rs diff --git a/examples/plugin_clack/src/gui.rs b/examples/plugin_clack/src/gui.rs index 613ae6a6..4c5e8caa 100644 --- a/examples/plugin_clack/src/gui.rs +++ b/examples/plugin_clack/src/gui.rs @@ -93,8 +93,22 @@ impl PluginGuiImpl for ExamplePluginMainThread<'_> { }) } - fn adjust_size(&mut self, size: GuiSize) -> Option { - Some(size) // Not supported yet + fn adjust_size(&mut self, mut size: GuiSize) -> Option { + let Some(gui) = &self.gui else { return None }; + + if let Some(max_size) = gui.handle.max_size() { + let max_size = window_size_to_gui_size(max_size); + size.width = size.width.min(max_size.width); + size.height = size.height.min(max_size.height); + } + + if let Some(min_size) = gui.handle.min_size() { + let min_size = window_size_to_gui_size(min_size); + size.width = size.width.max(min_size.width); + size.height = size.height.max(min_size.height); + } + + Some(size) } fn set_size(&mut self, size: GuiSize) -> Result<(), PluginError> { diff --git a/src/lib.rs b/src/lib.rs index a7f6a6e6..8872fe2b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,4 +28,5 @@ pub use window::*; #[allow(unused)] pub(crate) use tracing::*; +mod utils; pub(crate) mod wrappers; diff --git a/src/platform/x11/window_shared.rs b/src/platform/x11/window_shared.rs index 119427fd..b04d67b9 100644 --- a/src/platform/x11/window_shared.rs +++ b/src/platform/x11/window_shared.rs @@ -1,9 +1,10 @@ use crate::platform::x11::event_loop::EventLoop; use crate::platform::x11::visual_info::WindowVisualConfig; use crate::platform::x11::window_thread::WindowThreadShared; -use crate::platform::x11::xcb_connection::{get_size_hints, WmSizeHintsExt}; +use crate::platform::x11::xcb_connection::get_size_hints; use crate::platform::x11::xcb_window::XcbWindow; use crate::platform::*; +use crate::utils::SizingStrategy; use crate::{warn, MouseCursor, WindowHandler, WindowSettings, WindowSize}; use calloop::LoopSignal; use dpi::{PhysicalSize, Size}; @@ -51,7 +52,7 @@ pub(crate) struct WindowInner { pub(crate) scaling_factor: ScalingFactor, window_size: Cell>, - pub(crate) is_resizable: bool, + pub(crate) sizing_strategy: SizingStrategy, mouse_cursor: Cell, pub(crate) visual_id: Visualid, @@ -76,7 +77,9 @@ impl WindowInner { let physical_size = options.size.to_physical(initial_scale_factor); - let size_hints = get_size_hints(&options, initial_scale_factor); + let sizing_strategy = SizingStrategy::from_settings(&options, initial_scale_factor); + + let size_hints = get_size_hints(&sizing_strategy, physical_size); #[cfg(feature = "opengl")] let visual_info = @@ -127,7 +130,7 @@ impl WindowInner { system: scaling.into(), suggested: options.fallback_scale_factor.into(), }, - is_resizable: options.resizable, + sizing_strategy, mouse_cursor: MouseCursor::default().into(), loop_signal: ev_loop.get_signal(), @@ -193,8 +196,8 @@ impl WindowInner { let new_physical_size = size.to_physical(self.scaling_factor.get()); self.xcb_window.resize(new_physical_size)?.check()?; - if !self.is_resizable { - let size_hints = WmSizeHints::new().with_fixed_size(new_physical_size.cast()); + if !self.sizing_strategy.is_resizable() { + let size_hints = get_size_hints(&self.sizing_strategy, new_physical_size); self.xcb_window.set_size_hints(size_hints)?.check()?; } @@ -222,8 +225,8 @@ impl WindowInner { } self.xcb_window.resize(new_size.cast())?.check()?; // Will not call handler, as size is the same as above. - if !self.is_resizable { - let size_hints = WmSizeHints::new().with_fixed_size(new_size.cast()); + if !self.sizing_strategy.is_resizable() { + let size_hints = get_size_hints(&self.sizing_strategy, new_size); self.xcb_window.set_size_hints(size_hints)?.check()?; } diff --git a/src/platform/x11/window_thread.rs b/src/platform/x11/window_thread.rs index 9e10e728..1445f487 100644 --- a/src/platform/x11/window_thread.rs +++ b/src/platform/x11/window_thread.rs @@ -3,6 +3,7 @@ use crate::handler::WindowHandlerBuilder; use crate::host::HostCallbacks; use crate::platform::x11::event_loop::{EventLoop, MainThreadCaller}; use crate::platform::x11::window_shared::WindowInner; +use crate::utils::SizingStrategy; use crate::warn; use crate::window::WindowInitializer; use crate::{WindowContext, WindowSettings, WindowSize}; @@ -12,7 +13,7 @@ use std::cell::{Cell, RefCell}; use std::panic::resume_unwind; use std::rc::Rc; use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering}; -use std::sync::{mpsc, Mutex}; +use std::sync::{mpsc, Mutex, OnceLock}; use std::thread; use std::thread::JoinHandle; @@ -22,7 +23,7 @@ pub(crate) struct WindowThreadShared { size: AtomicU32, final_error: Mutex>, stopped_requested_from_host: AtomicBool, - is_resizable: AtomicBool, + sizing_strategy: OnceLock, } impl WindowThreadShared { @@ -33,14 +34,14 @@ impl WindowThreadShared { size: 0.into(), scaling_factor: 0.into(), stopped_requested_from_host: false.into(), - is_resizable: true.into(), + sizing_strategy: OnceLock::new(), } } fn init(&self, window: &WindowInner) { self.set_size(window.get_size()); self.set_scaling_factor(window.scale_factor()); - self.set_resizable(window.is_resizable); + let Ok(()) = self.sizing_strategy.set(window.sizing_strategy) else { unreachable!() }; } pub fn get_size(&self) -> PhysicalSize { @@ -56,12 +57,8 @@ impl WindowThreadShared { self.size.store(bytes, Ordering::Relaxed); } - pub fn set_resizable(&self, resizable: bool) { - self.is_resizable.store(resizable, Ordering::Relaxed); - } - - pub fn is_resizable(&self) -> bool { - self.is_resizable.load(Ordering::Relaxed) + pub fn sizing_strategy(&self) -> SizingStrategy { + self.sizing_strategy.get().copied().unwrap_or_default() } pub fn get_scaling_factor(&self) -> f64 { @@ -213,7 +210,21 @@ impl WindowThreadHandle { } pub fn is_resizable(&self) -> bool { - self.shared.is_resizable() + self.shared.sizing_strategy().is_resizable() + } + + pub fn min_size(&self) -> Option { + let min_size = self.shared.sizing_strategy().min_size()?; + let scale_factor = self.shared.get_scaling_factor(); + + Some(WindowSize::from_physical(min_size.cast(), scale_factor)) + } + + pub fn max_size(&self) -> Option { + let max_size = self.shared.sizing_strategy().max_size()?; + let scale_factor = self.shared.get_scaling_factor(); + + Some(WindowSize::from_physical(max_size.cast(), scale_factor)) } pub fn handle_main_thread_callback(&self) { diff --git a/src/platform/x11/xcb_connection.rs b/src/platform/x11/xcb_connection.rs index a82c55ff..4dd4d44c 100644 --- a/src/platform/x11/xcb_connection.rs +++ b/src/platform/x11/xcb_connection.rs @@ -14,7 +14,7 @@ use crate::MouseCursor; mod get_property; pub use get_property::GetPropertyError; mod size_hints; -pub use size_hints::{get_size_hints, WmSizeHintsExt}; +pub use size_hints::get_size_hints; x11rb::atom_manager! { pub Atoms: AtomsCookie { diff --git a/src/platform/x11/xcb_connection/size_hints.rs b/src/platform/x11/xcb_connection/size_hints.rs index 9a869364..2dd516c6 100644 --- a/src/platform/x11/xcb_connection/size_hints.rs +++ b/src/platform/x11/xcb_connection/size_hints.rs @@ -1,32 +1,27 @@ -use crate::WindowSettings; -use dpi::{PhysicalSize, Size}; +use crate::utils::SizingStrategy; +use dpi::{PhysicalSize, Pixel}; use x11rb::properties::WmSizeHints; -pub fn get_size_hints(settings: &WindowSettings, scale_factor: f64) -> WmSizeHints { +pub fn get_size_hints( + strategy: &SizingStrategy, current_size: PhysicalSize, +) -> WmSizeHints { let mut size_hints = WmSizeHints::default(); - if !settings.resizable { - size_hints = size_hints.with_fixed_size(settings.size.to_physical(scale_factor)); - } else { - size_hints.min_size = settings.min_size.map(|s| to_size_hint(s, scale_factor)); - size_hints.max_size = settings.max_size.map(|s| to_size_hint(s, scale_factor)); + match strategy { + SizingStrategy::Fixed => { + size_hints.min_size = Some(to_size_hint(current_size)); + size_hints.max_size = size_hints.min_size; + } + SizingStrategy::Resizable { min_size, max_size } => { + size_hints.min_size = min_size.map(to_size_hint); + size_hints.max_size = max_size.map(to_size_hint); + } } size_hints } -fn to_size_hint(size: Size, scale_factor: f64) -> (i32, i32) { - let size = size.to_physical(scale_factor); +fn to_size_hint(size: PhysicalSize) -> (i32, i32) { + let size = size.cast(); (size.width, size.height) } - -pub trait WmSizeHintsExt: Sized { - fn with_fixed_size(self, size: PhysicalSize) -> Self; -} - -impl WmSizeHintsExt for WmSizeHints { - fn with_fixed_size(mut self, size: PhysicalSize) -> Self { - self.max_size = Some((size.width, size.height)); - self - } -} diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 00000000..e12f4e60 --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,48 @@ +use crate::WindowSettings; +use dpi::PhysicalSize; + +#[cfg(target_os = "linux")] +type NativeSize = PhysicalSize; + +#[derive(Copy, Clone)] +pub(crate) enum SizingStrategy { + Fixed, + Resizable { min_size: Option, max_size: Option }, +} + +impl SizingStrategy { + pub fn from_settings(settings: &WindowSettings, scale_factor: f64) -> Self { + if !settings.resizable { + return Self::Fixed; + } + + Self::Resizable { + min_size: settings.min_size.map(|s| s.to_physical(scale_factor)), + max_size: settings.max_size.map(|s| s.to_physical(scale_factor)), + } + } + + pub fn is_resizable(&self) -> bool { + matches!(self, Self::Resizable { .. }) + } + + pub fn min_size(&self) -> Option { + match self { + Self::Fixed => None, + Self::Resizable { min_size, .. } => *min_size, + } + } + + pub fn max_size(&self) -> Option { + match self { + Self::Fixed => None, + Self::Resizable { max_size, .. } => *max_size, + } + } +} + +impl Default for SizingStrategy { + fn default() -> Self { + Self::Resizable { min_size: None, max_size: None } + } +} diff --git a/src/window.rs b/src/window.rs index 4cc976b4..d41a370c 100644 --- a/src/window.rs +++ b/src/window.rs @@ -159,6 +159,22 @@ impl Window { self.inner.is_resizable() } + /// Returns the minimum size of the window, if it has one. + /// + /// This is set by the [`WindowSettings::min_size`] field. + #[inline] + pub fn min_size(&self) -> Option { + self.inner.min_size() + } + + /// Returns the minimum size of the window, if it has one. + /// + /// This is set by the [`WindowSettings::max_size`] field. + #[inline] + pub fn max_size(&self) -> Option { + self.inner.max_size() + } + /// Performs the work the window thread had scheduled for the main thread. /// /// This must be called back on the main thread, as a response to [`HostMainThreadCaller::call_main_thread`](host::HostMainThreadCaller::call_main_thread). From fce70b893b33477acafef7097d85577857659832 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Sun, 9 Aug 2026 16:17:27 +0200 Subject: [PATCH 3/7] macOS impl --- examples/plugin_clack/src/gui.rs | 19 ++++++++++++++++-- src/platform/macos/view.rs | 32 ++++++++++++++++++++++++++++++- src/platform/macos/window.rs | 17 ++++++++++++---- src/platform/x11/window_shared.rs | 2 +- src/utils.rs | 22 ++++++++++----------- src/window.rs | 4 ++-- 6 files changed, 75 insertions(+), 21 deletions(-) diff --git a/examples/plugin_clack/src/gui.rs b/examples/plugin_clack/src/gui.rs index 4c5e8caa..2f98ebb3 100644 --- a/examples/plugin_clack/src/gui.rs +++ b/examples/plugin_clack/src/gui.rs @@ -95,15 +95,16 @@ impl PluginGuiImpl for ExamplePluginMainThread<'_> { fn adjust_size(&mut self, mut size: GuiSize) -> Option { let Some(gui) = &self.gui else { return None }; + let scale_factor = gui.handle.size().scale_factor; if let Some(max_size) = gui.handle.max_size() { - let max_size = window_size_to_gui_size(max_size); + let max_size = size_to_gui_size(max_size, scale_factor); size.width = size.width.min(max_size.width); size.height = size.height.min(max_size.height); } if let Some(min_size) = gui.handle.min_size() { - let min_size = window_size_to_gui_size(min_size); + let min_size = size_to_gui_size(min_size, scale_factor); size.width = size.width.max(min_size.width); size.height = size.height.max(min_size.height); } @@ -164,6 +165,20 @@ impl PluginGuiImpl for ExamplePluginMainThread<'_> { } } +fn size_to_gui_size(size: Size, scale_factor: f64) -> GuiSize { + #[cfg(target_os = "macos")] + { + let size = size.to_logical(scale_factor); + GuiSize { width: size.width, height: size.height } + } + + #[cfg(not(target_os = "macos"))] + { + let size = size.to_physical(scale_factor); + GuiSize { width: size.width, height: size.height } + } +} + fn window_size_to_gui_size(size: WindowSize) -> GuiSize { #[cfg(target_os = "macos")] { diff --git a/src/platform/macos/view.rs b/src/platform/macos/view.rs index 9c143270..686498bf 100644 --- a/src/platform/macos/view.rs +++ b/src/platform/macos/view.rs @@ -5,6 +5,7 @@ use super::window::WindowSharedState; use crate::host::Host; use crate::platform::*; use crate::tracing::warn; +use crate::utils::SizingStrategy; use crate::window::WindowInitializer; use crate::wrappers::appkit::*; use crate::MouseEvent::{ButtonPressed, ButtonReleased}; @@ -23,6 +24,7 @@ use objc2_app_kit::{ }; use objc2_foundation::{NSArray, NSNotification, NSPoint, NSRect, NSSize, NSString}; use std::cell::{Cell, RefCell}; +use std::cmp::min; use std::rc::Rc; pub enum ViewParentingType { @@ -85,7 +87,11 @@ impl BaseviewView { let view_rect = NSRect::new(NSPoint::ZERO, NSSize::new(final_size.width, final_size.height)); - let state = Rc::new(WindowSharedState::new(final_size, 1.0, init.settings.resizable)); + let state = Rc::new(WindowSharedState::new( + final_size, + 1.0, + SizingStrategy::from_settings(&init.settings), + )); let inner = BaseviewView { mtm, @@ -111,6 +117,8 @@ impl BaseviewView { view.state.scale_factor.set(view.view.backing_scale_factor()); view.state.size.set(view.view.size()); + Self::apply_size_constraints(view); + #[cfg(feature = "opengl")] if let Some(gl_config) = init.settings.gl_config { let gl_context = super::gl::GlContext::create(view.view, gl_config, view.mtm)?; @@ -244,6 +252,24 @@ impl BaseviewView { Self::close(this, false); } } + + fn apply_size_constraints(this: ViewRef) { + let ViewParentingType::Windowed { owned_window } = &*this.parenting.borrow() else { + return; + }; + let Some(window) = owned_window.load() else { return }; + let scale_factor = window.backingScaleFactor(); + + if let Some(min_size) = this.state.sizing_strategy.min_size() { + let min_size = min_size.to_logical(scale_factor); + window.setContentMinSize(NSSize::new(min_size.width, min_size.height)); + } + + if let Some(max_size) = this.state.sizing_strategy.max_size() { + let max_size = max_size.to_logical(scale_factor); + window.setContentMaxSize(NSSize::new(max_size.width, max_size.height)); + } + } } impl Drop for BaseviewView { @@ -290,6 +316,10 @@ impl ViewImpl for BaseviewView { let current_size = this.view.size(); let current_scale_factor = this.view.backing_scale_factor(); + if this.state.scale_factor.get() != current_scale_factor { + Self::apply_size_constraints(this); + } + // Only send the event when the window's size has actually changed to be in line with the // other platform implementations if this.state.scale_factor.get() != current_scale_factor diff --git a/src/platform/macos/window.rs b/src/platform/macos/window.rs index 808c8b43..954d7847 100644 --- a/src/platform/macos/window.rs +++ b/src/platform/macos/window.rs @@ -9,6 +9,7 @@ use std::rc::Rc; use crate::platform::macos::view::{BaseviewView, ViewParentingType}; use crate::platform::ParentWindowHandle; use crate::platform::Result; +use crate::utils::SizingStrategy; use crate::wrappers::appkit::{create_window, View}; use crate::*; @@ -96,7 +97,15 @@ impl WindowHandle { } pub fn is_resizable(&self) -> bool { - self.state.resizable + self.state.sizing_strategy.is_resizable() + } + + pub fn min_size(&self) -> Option { + self.state.sizing_strategy.min_size() + } + + pub fn max_size(&self) -> Option { + self.state.sizing_strategy.max_size() } #[inline] @@ -170,16 +179,16 @@ pub(crate) struct WindowSharedState { pub closed: Cell, pub size: Cell>, pub scale_factor: Cell, - pub resizable: bool, + pub sizing_strategy: SizingStrategy, } impl WindowSharedState { - pub fn new(size: LogicalSize, scale_factor: f64, resizable: bool) -> Self { + pub fn new(size: LogicalSize, scale_factor: f64, sizing_strategy: SizingStrategy) -> Self { Self { closed: false.into(), size: size.into(), scale_factor: scale_factor.into(), - resizable, + sizing_strategy, } } } diff --git a/src/platform/x11/window_shared.rs b/src/platform/x11/window_shared.rs index b04d67b9..a96876e3 100644 --- a/src/platform/x11/window_shared.rs +++ b/src/platform/x11/window_shared.rs @@ -77,7 +77,7 @@ impl WindowInner { let physical_size = options.size.to_physical(initial_scale_factor); - let sizing_strategy = SizingStrategy::from_settings(&options, initial_scale_factor); + let sizing_strategy = SizingStrategy::from_settings(&options); let size_hints = get_size_hints(&sizing_strategy, physical_size); diff --git a/src/utils.rs b/src/utils.rs index e12f4e60..f4712021 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,39 +1,39 @@ use crate::WindowSettings; -use dpi::PhysicalSize; - -#[cfg(target_os = "linux")] -type NativeSize = PhysicalSize; +use dpi::Size; #[derive(Copy, Clone)] pub(crate) enum SizingStrategy { Fixed, - Resizable { min_size: Option, max_size: Option }, + Resizable { min_size: Option, max_size: Option }, } impl SizingStrategy { - pub fn from_settings(settings: &WindowSettings, scale_factor: f64) -> Self { + pub fn from_settings(settings: &WindowSettings) -> Self { if !settings.resizable { return Self::Fixed; } - Self::Resizable { - min_size: settings.min_size.map(|s| s.to_physical(scale_factor)), - max_size: settings.max_size.map(|s| s.to_physical(scale_factor)), + if let (Some(min_size), Some(max_size)) = (settings.min_size, settings.max_size) { + if min_size == max_size { + return Self::Fixed; + } } + + Self::Resizable { min_size: settings.min_size, max_size: settings.max_size } } pub fn is_resizable(&self) -> bool { matches!(self, Self::Resizable { .. }) } - pub fn min_size(&self) -> Option { + pub fn min_size(&self) -> Option { match self { Self::Fixed => None, Self::Resizable { min_size, .. } => *min_size, } } - pub fn max_size(&self) -> Option { + pub fn max_size(&self) -> Option { match self { Self::Fixed => None, Self::Resizable { max_size, .. } => *max_size, diff --git a/src/window.rs b/src/window.rs index d41a370c..ebb51a9c 100644 --- a/src/window.rs +++ b/src/window.rs @@ -163,7 +163,7 @@ impl Window { /// /// This is set by the [`WindowSettings::min_size`] field. #[inline] - pub fn min_size(&self) -> Option { + pub fn min_size(&self) -> Option { self.inner.min_size() } @@ -171,7 +171,7 @@ impl Window { /// /// This is set by the [`WindowSettings::max_size`] field. #[inline] - pub fn max_size(&self) -> Option { + pub fn max_size(&self) -> Option { self.inner.max_size() } From 3acdd4559b047bb2904b6aba714c9c2281c128e5 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Mon, 10 Aug 2026 15:20:25 +0200 Subject: [PATCH 4/7] Win32 impl --- src/platform/win/window.rs | 46 ++++++++++++++++++++++++-------- src/platform/win/window_state.rs | 5 ++-- 2 files changed, 38 insertions(+), 13 deletions(-) diff --git a/src/platform/win/window.rs b/src/platform/win/window.rs index 1867139e..b2458ed1 100644 --- a/src/platform/win/window.rs +++ b/src/platform/win/window.rs @@ -1,22 +1,14 @@ use windows_core::{ComObject, HSTRING}; use windows_sys::Win32::{ Foundation::{LPARAM, LRESULT, RECT, WPARAM}, - UI::{ - Controls::WM_MOUSELEAVE, - WindowsAndMessaging::{ - HTCLIENT, WHEEL_DELTA, WM_CHAR, WM_CLOSE, WM_DPICHANGED, WM_INPUTLANGCHANGE, - WM_KEYDOWN, WM_KEYUP, WM_KILLFOCUS, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MBUTTONDOWN, - WM_MBUTTONUP, WM_MOUSEHWHEEL, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_RBUTTONDOWN, - WM_RBUTTONUP, WM_SETCURSOR, WM_SETFOCUS, WM_SIZE, WM_SYSCHAR, WM_SYSKEYDOWN, - WM_SYSKEYUP, WM_TIMER, WM_USER, WM_XBUTTONDOWN, WM_XBUTTONUP, - }, - }, + UI::{Controls::WM_MOUSELEAVE, WindowsAndMessaging::*}, }; use crate::{warn, HandlerError}; use dpi::{PhysicalPosition, PhysicalSize, Size}; use std::cell::Cell; use std::num::NonZeroUsize; +use windows_sys::Win32::Foundation::POINT; pub(crate) const BV_WINDOW_MUST_CLOSE: u32 = WM_USER + 1; @@ -69,7 +61,15 @@ impl WindowHandle { } pub fn is_resizable(&self) -> bool { - self.state.resizable + self.state.sizing_strategy.is_resizable() + } + + pub fn min_size(&self) -> Option { + self.state.sizing_strategy.min_size() + } + + pub fn max_size(&self) -> Option { + self.state.sizing_strategy.max_size() } pub fn size(&self) -> WindowSize { @@ -627,6 +627,30 @@ unsafe fn wnd_proc_inner( None } } + WM_GETMINMAXINFO => { + let sizing = window_state.shared.sizing_strategy; + + // Only implement this message if we actually need to specify a min/max size + if let (None, None) = (sizing.min_size(), sizing.max_size()) { + return None; + } + + let info = lparam as *mut MINMAXINFO; + + if let Some(size) = sizing.min_size() { + let size = size.to_physical(window_state.shared.scale_factor()); + let pt = POINT { x: size.width, y: size.height }; + (&raw mut (*info).ptMinTrackSize).write(pt); + } + + if let Some(size) = sizing.max_size() { + let size = size.to_physical(window_state.shared.scale_factor()); + let pt = POINT { x: size.width, y: size.height }; + (&raw mut (*info).ptMaxTrackSize).write(pt); + } + + Some(0) + } // NOTE: `WM_NCDESTROY` is handled in the outer function because this deallocates the window // state BV_WINDOW_MUST_CLOSE => { diff --git a/src/platform/win/window_state.rs b/src/platform/win/window_state.rs index 9207c962..75e00247 100644 --- a/src/platform/win/window_state.rs +++ b/src/platform/win/window_state.rs @@ -1,5 +1,6 @@ use crate::platform::win::keyboard::KeyboardState; use crate::platform::PlatformHandle; +use crate::utils::SizingStrategy; use crate::wrappers::win32::cursor::SystemCursor; use crate::wrappers::win32::h_instance::HInstance; use crate::wrappers::win32::window::HWnd; @@ -151,7 +152,7 @@ pub struct WindowSharedState { pub destroy_host_originated: Cell, pub user32: ExtendedUser32, - pub resizable: bool, + pub sizing_strategy: SizingStrategy, } impl WindowSharedState { @@ -164,7 +165,7 @@ impl WindowSharedState { fallback_scale_factor: settings.fallback_scale_factor.into(), resize_host_originated: false.into(), destroy_host_originated: false.into(), - resizable: settings.resizable, + sizing_strategy: SizingStrategy::from_settings(settings), user32, } .into() From 894e72ba56cadff2d55a0c5f0af6b57aaf60804d Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Mon, 10 Aug 2026 15:39:01 +0200 Subject: [PATCH 5/7] Win32 fixes --- src/platform/win/window.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/platform/win/window.rs b/src/platform/win/window.rs index b2458ed1..afe0519b 100644 --- a/src/platform/win/window.rs +++ b/src/platform/win/window.rs @@ -637,14 +637,22 @@ unsafe fn wnd_proc_inner( let info = lparam as *mut MINMAXINFO; + let ctx = DpiAwarenessContext::new(&window_state.user32).unwrap(); + let style = window.get_style().unwrap(); + let dpi = window_state.shared.current_dpi.get(); + if let Some(size) = sizing.min_size() { let size = size.to_physical(window_state.shared.scale_factor()); + let size = + ctx.client_area_to_nc_area(size.into(), style, dpi).unwrap().size().cast(); let pt = POINT { x: size.width, y: size.height }; (&raw mut (*info).ptMinTrackSize).write(pt); } if let Some(size) = sizing.max_size() { let size = size.to_physical(window_state.shared.scale_factor()); + let size = + ctx.client_area_to_nc_area(size.into(), style, dpi).unwrap().size().cast(); let pt = POINT { x: size.width, y: size.height }; (&raw mut (*info).ptMaxTrackSize).write(pt); } From 6d5b9bd1e2a324fc8d35515c0f96824738be5bc0 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Mon, 10 Aug 2026 15:47:37 +0200 Subject: [PATCH 6/7] X11 fixes --- src/platform/x11/window_shared.rs | 8 ++++---- src/platform/x11/window_thread.rs | 14 ++++---------- src/platform/x11/xcb_connection/size_hints.rs | 8 +++++--- 3 files changed, 13 insertions(+), 17 deletions(-) diff --git a/src/platform/x11/window_shared.rs b/src/platform/x11/window_shared.rs index a96876e3..81582180 100644 --- a/src/platform/x11/window_shared.rs +++ b/src/platform/x11/window_shared.rs @@ -12,7 +12,6 @@ use raw_window_handle::{DisplayHandle, XlibWindowHandle}; use std::cell::Cell; use std::rc::Rc; use std::sync::Arc; -use x11rb::properties::WmSizeHints; use x11rb::protocol::xproto::{ChangeWindowAttributesAux, ConnectionExt, InputFocus, Visualid}; use x11rb::CURRENT_TIME; @@ -79,7 +78,7 @@ impl WindowInner { let sizing_strategy = SizingStrategy::from_settings(&options); - let size_hints = get_size_hints(&sizing_strategy, physical_size); + let size_hints = get_size_hints(&sizing_strategy, physical_size, initial_scale_factor); #[cfg(feature = "opengl")] let visual_info = @@ -197,7 +196,8 @@ impl WindowInner { self.xcb_window.resize(new_physical_size)?.check()?; if !self.sizing_strategy.is_resizable() { - let size_hints = get_size_hints(&self.sizing_strategy, new_physical_size); + let size_hints = + get_size_hints(&self.sizing_strategy, new_physical_size, self.scale_factor()); self.xcb_window.set_size_hints(size_hints)?.check()?; } @@ -226,7 +226,7 @@ impl WindowInner { self.xcb_window.resize(new_size.cast())?.check()?; // Will not call handler, as size is the same as above. if !self.sizing_strategy.is_resizable() { - let size_hints = get_size_hints(&self.sizing_strategy, new_size); + let size_hints = get_size_hints(&self.sizing_strategy, new_size, self.scale_factor()); self.xcb_window.set_size_hints(size_hints)?.check()?; } diff --git a/src/platform/x11/window_thread.rs b/src/platform/x11/window_thread.rs index 1445f487..1e545851 100644 --- a/src/platform/x11/window_thread.rs +++ b/src/platform/x11/window_thread.rs @@ -213,18 +213,12 @@ impl WindowThreadHandle { self.shared.sizing_strategy().is_resizable() } - pub fn min_size(&self) -> Option { - let min_size = self.shared.sizing_strategy().min_size()?; - let scale_factor = self.shared.get_scaling_factor(); - - Some(WindowSize::from_physical(min_size.cast(), scale_factor)) + pub fn min_size(&self) -> Option { + self.shared.sizing_strategy().min_size() } - pub fn max_size(&self) -> Option { - let max_size = self.shared.sizing_strategy().max_size()?; - let scale_factor = self.shared.get_scaling_factor(); - - Some(WindowSize::from_physical(max_size.cast(), scale_factor)) + pub fn max_size(&self) -> Option { + self.shared.sizing_strategy().max_size() } pub fn handle_main_thread_callback(&self) { diff --git a/src/platform/x11/xcb_connection/size_hints.rs b/src/platform/x11/xcb_connection/size_hints.rs index 2dd516c6..eb11cde4 100644 --- a/src/platform/x11/xcb_connection/size_hints.rs +++ b/src/platform/x11/xcb_connection/size_hints.rs @@ -3,7 +3,7 @@ use dpi::{PhysicalSize, Pixel}; use x11rb::properties::WmSizeHints; pub fn get_size_hints( - strategy: &SizingStrategy, current_size: PhysicalSize, + strategy: &SizingStrategy, current_size: PhysicalSize, scale_factor: f64, ) -> WmSizeHints { let mut size_hints = WmSizeHints::default(); @@ -13,8 +13,10 @@ pub fn get_size_hints( size_hints.max_size = size_hints.min_size; } SizingStrategy::Resizable { min_size, max_size } => { - size_hints.min_size = min_size.map(to_size_hint); - size_hints.max_size = max_size.map(to_size_hint); + size_hints.min_size = + min_size.map(|s| to_size_hint(s.to_physical::(scale_factor))); + size_hints.max_size = + max_size.map(|s| to_size_hint(s.to_physical::(scale_factor))); } } From 60495addc46ff3c1819a0d3967cd15b2d134e87c Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Mon, 10 Aug 2026 15:54:05 +0200 Subject: [PATCH 7/7] lint fix --- src/platform/macos/view.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/platform/macos/view.rs b/src/platform/macos/view.rs index 686498bf..3b484889 100644 --- a/src/platform/macos/view.rs +++ b/src/platform/macos/view.rs @@ -24,7 +24,6 @@ use objc2_app_kit::{ }; use objc2_foundation::{NSArray, NSNotification, NSPoint, NSRect, NSSize, NSString}; use std::cell::{Cell, RefCell}; -use std::cmp::min; use std::rc::Rc; pub enum ViewParentingType {