From e1fbc4bf9fc8cc55f3a21d110d19c2ab6b9f03eb Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Sun, 13 Sep 2026 08:41:08 +0200 Subject: [PATCH 1/6] wip --- examples/plugin_clack/src/gui.rs | 17 ++-------- src/dpi.rs | 10 ++++++ src/window.rs | 56 ++++++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 14 deletions(-) diff --git a/examples/plugin_clack/src/gui.rs b/examples/plugin_clack/src/gui.rs index 204a4f0e..26c24f49 100644 --- a/examples/plugin_clack/src/gui.rs +++ b/examples/plugin_clack/src/gui.rs @@ -90,23 +90,12 @@ impl PluginGuiImpl for ExamplePluginMainThread<'_> { }) } - fn adjust_size(&mut self, mut size: GuiSize) -> Option { + fn adjust_size(&mut self, 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 = NativeSize::from_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 = NativeSize::from_size(min_size, scale_factor); - size.width = size.width.max(min_size.width); - size.height = size.height.max(min_size.height); - } + let size = gui.handle.adjust_size(NativeSize::new(size.width, size.height)); - Some(size) + Some(GuiSize { width: size.width, height: size.height }) } fn set_size(&mut self, size: GuiSize) -> Result<(), PluginError> { diff --git a/src/dpi.rs b/src/dpi.rs index b9ce05d8..9d3e9e53 100644 --- a/src/dpi.rs +++ b/src/dpi.rs @@ -10,11 +10,21 @@ pub struct NativeSize

{ pub height: P, } +#[cfg(target_os = "macos")] +const NATIVE_IS_LOGICAL: bool = true; +#[cfg(not(target_os = "macos"))] +const NATIVE_IS_LOGICAL: bool = false; + impl

NativeSize

{ #[inline] pub const fn new(width: P, height: P) -> Self { NativeSize { width, height } } + + /// This is `true` if the platform's native size is represented in logical pixels, and `false` otherwise. + /// + /// This is `true` on macOS, and `false` on Windows and Linux. + pub const IS_LOGICAL: bool = NATIVE_IS_LOGICAL; } impl NativeSize

{ diff --git a/src/window.rs b/src/window.rs index f88b04f4..ce9702d9 100644 --- a/src/window.rs +++ b/src/window.rs @@ -219,6 +219,55 @@ impl Window { self.inner.hide()?; Ok(()) } + + fn adjust_size_physical(&self, mut size: PhysicalSize) -> WindowSize { + let scale_factor = self.size().scale_factor; + + if let Some(max_size) = self.max_size() { + let max_size = max_size.to_physical::(scale_factor); + size.width = max_size.width.min(size.width); + size.height = max_size.height.min(size.height); + } + + if let Some(min_size) = self.min_size() { + let min_size = min_size.to_physical::(scale_factor); + size.width = min_size.width.max(size.width); + size.height = min_size.height.max(size.height); + } + + WindowSize::from_physical(size, scale_factor) + } + + fn adjust_size_logical(&self, mut size: LogicalSize) -> WindowSize { + let scale_factor = self.size().scale_factor; + + if let Some(max_size) = self.max_size() { + let max_size = max_size.to_logical::(scale_factor); + size.width = max_size.width.min(size.width); + size.height = max_size.height.min(size.height); + } + + if let Some(min_size) = self.min_size() { + let min_size = min_size.to_logical::(scale_factor); + size.width = min_size.width.max(size.width); + size.height = min_size.height.max(size.height); + } + + WindowSize::from_logical(size, scale_factor) + } + + pub fn adjust_size + Into>(&self, size: S) -> S { + if !self.is_resizable() { + return self.size().into(); + } + + let adjusted = match size.into() { + Size::Physical(size) => self.adjust_size_physical(size), + Size::Logical(size) => self.adjust_size_logical(size), + }; + + adjusted.into() + } } pub(crate) struct WindowInitializer { @@ -282,3 +331,10 @@ impl From for LogicalSize

{ size.logical.cast() } } + +impl From for Size { + #[inline] + fn from(value: WindowSize) -> Self { + value.to_native_size::().into() + } +} From e37a4d44ce710e53d17c4d392fcf70b988f047d0 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Sun, 13 Sep 2026 10:17:09 +0200 Subject: [PATCH 2/6] wip --- examples/plugin_clack/src/gui.rs | 7 +++-- src/platform/x11/window_shared.rs | 12 +++++--- src/platform/x11/window_thread.rs | 4 +++ src/utils.rs | 48 +++++++++++++++++++++++++++++- src/window.rs | 49 ++----------------------------- 5 files changed, 67 insertions(+), 53 deletions(-) diff --git a/examples/plugin_clack/src/gui.rs b/examples/plugin_clack/src/gui.rs index 26c24f49..9b7a6883 100644 --- a/examples/plugin_clack/src/gui.rs +++ b/examples/plugin_clack/src/gui.rs @@ -28,8 +28,11 @@ impl PluginGuiImpl for ExamplePluginMainThread<'_> { } fn create(&mut self, _configuration: GuiConfiguration) -> Result<(), PluginError> { - let options = - WindowSettings::new().wait_for_parent().with_size(PhysicalSize::new(400, 200)); + let options = WindowSettings::new() + .wait_for_parent() + .with_size(PhysicalSize::new(400, 200)) + .with_min_size(LogicalSize::new(200.0, 100.0)) + .with_max_size(LogicalSize::new(600.0, 400.0)); let mut host = Host::new().with_main_thread(unsafe { MainThreadHandler { host: self.host.shared().with_arbitrary_lifetime() } diff --git a/src/platform/x11/window_shared.rs b/src/platform/x11/window_shared.rs index 203f911d..035aecbc 100644 --- a/src/platform/x11/window_shared.rs +++ b/src/platform/x11/window_shared.rs @@ -213,12 +213,16 @@ impl WindowInner { } pub fn resize(&self, size: Size) -> Result<()> { - let new_physical_size = size.to_physical(self.scaling_factor.get()); - self.xcb_window.resize(new_physical_size)?.check()?; + let new_size = self.sizing_strategy.adjust_size(size, self.size()).physical; + + if new_size == self.window_size.get().cast() { + return Ok(()); + } + + self.xcb_window.resize(new_size)?.check()?; if !self.sizing_strategy.is_resizable() { - let size_hints = - get_size_hints(&self.sizing_strategy, new_physical_size, self.scale_factor()); + 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 764452d7..54c62565 100644 --- a/src/platform/x11/window_thread.rs +++ b/src/platform/x11/window_thread.rs @@ -179,6 +179,10 @@ impl WindowThreadHandle { result.map_err(|e| RequestFailed::Response(e).into()) } + pub fn sizing_strategy(&self) -> SizingStrategy { + self.shared.sizing_strategy.get().copied().unwrap_or_default() + } + pub fn run_until_closed(&self) -> Result<()> { if !self.shared.stopped.load(Ordering::Relaxed) { self.request(WindowThreadRequest::Show)?; diff --git a/src/utils.rs b/src/utils.rs index 1aa88287..e01c1eee 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,5 +1,6 @@ use crate::dpi::Size; -use crate::WindowSettings; +use crate::{WindowSettings, WindowSize}; +use dpi::{LogicalSize, PhysicalSize}; #[derive(Copy, Clone)] pub(crate) enum SizingStrategy { @@ -39,6 +40,51 @@ impl SizingStrategy { Self::Resizable { max_size, .. } => *max_size, } } + + fn adjust_size_physical(&self, mut size: PhysicalSize, scale_factor: f64) -> WindowSize { + if let Some(max_size) = self.max_size() { + let max_size = max_size.to_physical::(scale_factor); + size.width = max_size.width.min(size.width); + size.height = max_size.height.min(size.height); + } + + if let Some(min_size) = self.min_size() { + let min_size = min_size.to_physical::(scale_factor); + size.width = min_size.width.max(size.width); + size.height = min_size.height.max(size.height); + } + + WindowSize::from_physical(size, scale_factor) + } + + fn adjust_size_logical(&self, mut size: LogicalSize, scale_factor: f64) -> WindowSize { + if let Some(max_size) = self.max_size() { + let max_size = max_size.to_logical::(scale_factor); + size.width = max_size.width.min(size.width); + size.height = max_size.height.min(size.height); + } + + if let Some(min_size) = self.min_size() { + let min_size = min_size.to_logical::(scale_factor); + size.width = min_size.width.max(size.width); + size.height = min_size.height.max(size.height); + } + + WindowSize::from_logical(size, scale_factor) + } + + pub fn adjust_size(&self, size: Size, window_size: WindowSize) -> WindowSize { + if !self.is_resizable() { + return window_size; + } + + let adjusted = match size.into() { + Size::Physical(size) => self.adjust_size_physical(size, window_size.scale_factor), + Size::Logical(size) => self.adjust_size_logical(size, window_size.scale_factor), + }; + + adjusted + } } impl Default for SizingStrategy { diff --git a/src/window.rs b/src/window.rs index ce9702d9..6a912335 100644 --- a/src/window.rs +++ b/src/window.rs @@ -220,53 +220,10 @@ impl Window { Ok(()) } - fn adjust_size_physical(&self, mut size: PhysicalSize) -> WindowSize { - let scale_factor = self.size().scale_factor; - - if let Some(max_size) = self.max_size() { - let max_size = max_size.to_physical::(scale_factor); - size.width = max_size.width.min(size.width); - size.height = max_size.height.min(size.height); - } - - if let Some(min_size) = self.min_size() { - let min_size = min_size.to_physical::(scale_factor); - size.width = min_size.width.max(size.width); - size.height = min_size.height.max(size.height); - } - - WindowSize::from_physical(size, scale_factor) - } - - fn adjust_size_logical(&self, mut size: LogicalSize) -> WindowSize { - let scale_factor = self.size().scale_factor; - - if let Some(max_size) = self.max_size() { - let max_size = max_size.to_logical::(scale_factor); - size.width = max_size.width.min(size.width); - size.height = max_size.height.min(size.height); - } - - if let Some(min_size) = self.min_size() { - let min_size = min_size.to_logical::(scale_factor); - size.width = min_size.width.max(size.width); - size.height = min_size.height.max(size.height); - } - - WindowSize::from_logical(size, scale_factor) - } - + /// Adjusts the given size to the window's size constraints. + #[inline] pub fn adjust_size + Into>(&self, size: S) -> S { - if !self.is_resizable() { - return self.size().into(); - } - - let adjusted = match size.into() { - Size::Physical(size) => self.adjust_size_physical(size), - Size::Logical(size) => self.adjust_size_logical(size), - }; - - adjusted.into() + self.inner.sizing_strategy().adjust_size(size.into(), self.size()).into() } } From 5b5faaad71c484528deff4a513421e4158cdd695 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Sun, 13 Sep 2026 10:22:38 +0200 Subject: [PATCH 3/6] wip --- src/platform/macos/context.rs | 10 +++++++--- src/platform/macos/view.rs | 13 +++++++++---- src/platform/macos/window.rs | 6 +++++- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/platform/macos/context.rs b/src/platform/macos/context.rs index a53170cb..c868863b 100644 --- a/src/platform/macos/context.rs +++ b/src/platform/macos/context.rs @@ -2,6 +2,7 @@ use crate::dpi::Size; use crate::platform::macos::view::BaseviewView; use crate::platform::Result; use crate::platform::{PlatformHandle, WindowSharedState}; +use crate::utils::SizingStrategy; use crate::wrappers::appkit::{View, ViewRef}; use crate::*; use dispatch2::MainThreadBound; @@ -60,12 +61,15 @@ impl WindowContext { } pub fn resize(&self, size: Size) -> Result<()> { - let Some(view) = self.view.load() else { return Ok(()) }; - let Some(view) = view.inner_ref() else { return Ok(()) }; - if view.inner.state.closed.get() { + if self.state.closed.get() { return Ok(()); } + let Some(view) = self.view.load() else { return Ok(()) }; + let Some(view) = view.inner_ref() else { return Ok(()) }; + + let size = self.state.sizing_strategy.adjust_size(size, self.size()).logical; + BaseviewView::resize(view, size, true, false); Ok(()) diff --git a/src/platform/macos/view.rs b/src/platform/macos/view.rs index 53060b7d..a5765fe5 100644 --- a/src/platform/macos/view.rs +++ b/src/platform/macos/view.rs @@ -215,8 +215,13 @@ impl BaseviewView { this.parenting.replace(parenting); } - pub fn resize(this: ViewRef, size: Size, notify_host: bool, from_window: bool) { - let size = size.to_logical::(this.view.backing_scale_factor()); + pub fn resize( + this: ViewRef, size: LogicalSize, notify_host: bool, from_window: bool, + ) { + if size == this.inner.state.size.get() { + return; + } + // NOTE: macOS gives you a personal rave if you pass in fractional pixels here. Even // though the size is in fractional pixels. let size = NSSize::new(size.width.round(), size.height.round()); @@ -337,7 +342,7 @@ impl ViewImpl for BaseviewView { warn!("Window Handler failed to resize: {}", e); this.state.size.set(previous); - Self::resize(this, previous.into(), false, false); + Self::resize(this, previous, false, false); return; } @@ -345,7 +350,7 @@ impl ViewImpl for BaseviewView { if let Err(e) = this.host.request_resize(new_size) { warn!("Host failed to resize parent view: {}", e); - Self::resize(this, previous.into(), false, false); + Self::resize(this, previous, false, false); } } } diff --git a/src/platform/macos/window.rs b/src/platform/macos/window.rs index 2bf16282..dcb09bf8 100644 --- a/src/platform/macos/window.rs +++ b/src/platform/macos/window.rs @@ -126,7 +126,7 @@ impl WindowHandle { let Some(view) = self.view.load() else { return Ok(()) }; let Some(view) = view.inner_ref() else { return Ok(()) }; - BaseviewView::resize(view, size, false, false); + BaseviewView::resize(view, size.to_logical(self.state.scale_factor.get()), false, false); Ok(()) } @@ -160,6 +160,10 @@ impl WindowHandle { BaseviewView::hide(view); Ok(()) } + + pub fn sizing_strategy(&self) -> SizingStrategy { + self.state.sizing_strategy + } } fn create_window_with_options( From 6fc48285a899424c2a5ea940434d88810d29454c Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Sun, 13 Sep 2026 10:27:14 +0200 Subject: [PATCH 4/6] wip --- src/platform/win/window.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/platform/win/window.rs b/src/platform/win/window.rs index 867902e0..c777a502 100644 --- a/src/platform/win/window.rs +++ b/src/platform/win/window.rs @@ -18,6 +18,7 @@ use crate::handler::WindowHandlerBuilder; use crate::host::Host; use crate::platform::win::window_state::{WindowSharedState, WindowState}; use crate::platform::PlatformError; +use crate::utils::SizingStrategy; use crate::window::WindowInitializer; use crate::wrappers::win32::cursor::SystemCursor; use crate::wrappers::win32::window::*; @@ -75,7 +76,12 @@ impl WindowHandle { } pub fn resize(&self, new_size: Size) -> Result<()> { - let new_size = new_size.to_physical(self.state.scale_factor()); + let new_size = self.state.sizing_strategy.adjust_size(new_size, self.size()).physical; + + if new_size == self.state.current_size.get() { + return Ok(()); + } + let hwnd = match self.hwnd.get() { Some(hwnd) => hwnd, None => { @@ -96,6 +102,10 @@ impl WindowHandle { } } + pub fn sizing_strategy(&self) -> SizingStrategy { + self.state.sizing_strategy + } + pub fn suggest_scale_factor(&self, scale_factor: f64) -> Result<()> { let current_scale_factor = self.state.scale_factor(); self.state.fallback_scale_factor.set(Some(scale_factor)); From 5f28770cd2200ad9c7ba682d873312e2b0541359 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Sun, 13 Sep 2026 10:29:27 +0200 Subject: [PATCH 5/6] clippy fix --- src/platform/macos/context.rs | 1 - src/platform/macos/view.rs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/platform/macos/context.rs b/src/platform/macos/context.rs index c868863b..4266a242 100644 --- a/src/platform/macos/context.rs +++ b/src/platform/macos/context.rs @@ -2,7 +2,6 @@ use crate::dpi::Size; use crate::platform::macos::view::BaseviewView; use crate::platform::Result; use crate::platform::{PlatformHandle, WindowSharedState}; -use crate::utils::SizingStrategy; use crate::wrappers::appkit::{View, ViewRef}; use crate::*; use dispatch2::MainThreadBound; diff --git a/src/platform/macos/view.rs b/src/platform/macos/view.rs index a5765fe5..9e5df9e5 100644 --- a/src/platform/macos/view.rs +++ b/src/platform/macos/view.rs @@ -2,7 +2,7 @@ use super::keyboard::{make_modifiers, KeyboardState}; use super::window::WindowSharedState; -use crate::dpi::{LogicalPosition, LogicalSize, Size}; +use crate::dpi::{LogicalPosition, LogicalSize}; use crate::host::Host; use crate::platform::macos::cursor::CursorManager; use crate::platform::*; From 15d7c5f8665cabab80d1d18662d9b8bb7e308aa5 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Sun, 13 Sep 2026 10:41:35 +0200 Subject: [PATCH 6/6] clippy fix --- src/platform/macos/view.rs | 2 +- src/utils.rs | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/platform/macos/view.rs b/src/platform/macos/view.rs index 9e5df9e5..bc9f8043 100644 --- a/src/platform/macos/view.rs +++ b/src/platform/macos/view.rs @@ -316,7 +316,7 @@ impl ViewImpl for BaseviewView { let size = window.contentRectForFrameRect(window.frame()).size; let size = LogicalSize::new(size.width, size.height); - BaseviewView::resize(this, size.into(), true, true); + BaseviewView::resize(this, size, true, true); } fn view_did_change_backing_properties(this: ViewRef, notify_host: bool) { diff --git a/src/utils.rs b/src/utils.rs index e01c1eee..934ad460 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -78,12 +78,10 @@ impl SizingStrategy { return window_size; } - let adjusted = match size.into() { + match size { Size::Physical(size) => self.adjust_size_physical(size, window_size.scale_factor), Size::Logical(size) => self.adjust_size_logical(size, window_size.scale_factor), - }; - - adjusted + } } }