diff --git a/examples/plugin_clack/src/gui.rs b/examples/plugin_clack/src/gui.rs index 204a4f0e..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() } @@ -90,23 +93,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/platform/macos/context.rs b/src/platform/macos/context.rs index a53170cb..4266a242 100644 --- a/src/platform/macos/context.rs +++ b/src/platform/macos/context.rs @@ -60,12 +60,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..bc9f8043 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::*; @@ -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()); @@ -311,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) { @@ -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( 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)); 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..934ad460 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,49 @@ 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; + } + + 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), + } + } } impl Default for SizingStrategy { diff --git a/src/window.rs b/src/window.rs index f88b04f4..6a912335 100644 --- a/src/window.rs +++ b/src/window.rs @@ -219,6 +219,12 @@ impl Window { self.inner.hide()?; Ok(()) } + + /// Adjusts the given size to the window's size constraints. + #[inline] + pub fn adjust_size + Into>(&self, size: S) -> S { + self.inner.sizing_strategy().adjust_size(size.into(), self.size()).into() + } } pub(crate) struct WindowInitializer { @@ -282,3 +288,10 @@ impl From for LogicalSize

{ size.logical.cast() } } + +impl From for Size { + #[inline] + fn from(value: WindowSize) -> Self { + value.to_native_size::().into() + } +}