From ea14c6b104cecf131ab5e7ba320bc52373efa3d1 Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Tue, 11 Jul 2023 16:55:36 +0300 Subject: [PATCH] feat: split min/max size constraints (#759) * feat: split min/max size constraints, closes #138 * windows impl * linux impl * fix linux impl * cleanup linux impl * macOS impl * imports * ios build * macos build * unsafe * merge `set_min/max_width/height` into a single function * fix macos build * macos again * use macros to generate DPI types ref: https://github.com/rust-windowing/winit/pull/2148 * fix windows impl * fmt --- examples/min_max_size.rs | 57 ++- src/dpi.rs | 580 +++++++++------------- src/platform_impl/android/mod.rs | 4 +- src/platform_impl/ios/window.rs | 15 +- src/platform_impl/linux/event_loop.rs | 31 +- src/platform_impl/linux/util.rs | 41 +- src/platform_impl/linux/window.rs | 109 ++-- src/platform_impl/macos/window.rs | 66 ++- src/platform_impl/windows/event_loop.rs | 72 ++- src/platform_impl/windows/system_tray.rs | 2 +- src/platform_impl/windows/window.rs | 36 +- src/platform_impl/windows/window_state.rs | 10 +- src/window.rs | 162 +++++- 13 files changed, 624 insertions(+), 561 deletions(-) diff --git a/examples/min_max_size.rs b/examples/min_max_size.rs index 03c3004a4..527519727 100644 --- a/examples/min_max_size.rs +++ b/examples/min_max_size.rs @@ -3,10 +3,11 @@ // SPDX-License-Identifier: Apache-2.0 use tao::{ - dpi::LogicalSize, - event::{Event, WindowEvent}, + dpi::LogicalPixel, + event::{ElementState, Event, KeyEvent, WindowEvent}, event_loop::{ControlFlow, EventLoop}, - window::WindowBuilder, + keyboard::Key, + window::{WindowBuilder, WindowSizeConstraints}, }; #[allow(clippy::single_match)] @@ -14,20 +15,64 @@ fn main() { env_logger::init(); let event_loop = EventLoop::new(); + let min_width = 400.0; + let max_width = 800.0; + let min_height = 200.0; + let max_height = 400.0; + let mut size_constraints = WindowSizeConstraints::default(); + let window = WindowBuilder::new().build(&event_loop).unwrap(); - window.set_min_inner_size(Some(LogicalSize::new(400.0, 200.0))); - window.set_max_inner_size(Some(LogicalSize::new(800.0, 400.0))); + eprintln!("constraint keys:"); + eprintln!(" (E) Toggle the min width"); + eprintln!(" (F) Toggle the max width"); + eprintln!(" (P) Toggle the min height"); + eprintln!(" (V) Toggle the max height"); event_loop.run(move |event, _, control_flow| { *control_flow = ControlFlow::Wait; - println!("{:?}", event); match event { Event::WindowEvent { event: WindowEvent::CloseRequested, .. } => *control_flow = ControlFlow::Exit, + + Event::WindowEvent { + event: + WindowEvent::KeyboardInput { + event: + KeyEvent { + logical_key: Key::Character(key_str), + state: ElementState::Released, + .. + }, + .. + }, + .. + } => match key_str { + "e" => { + size_constraints.min_width = + (!size_constraints.min_width.is_some()).then_some(LogicalPixel::new(min_width).into()); + window.set_inner_size_constraints(size_constraints); + } + "f" => { + size_constraints.max_width = + (!size_constraints.max_width.is_some()).then_some(LogicalPixel::new(max_width).into()); + window.set_inner_size_constraints(size_constraints); + } + "p" => { + size_constraints.min_height = (!size_constraints.min_height.is_some()) + .then_some(LogicalPixel::new(min_height).into()); + window.set_inner_size_constraints(size_constraints); + } + "v" => { + size_constraints.max_height = (!size_constraints.max_height.is_some()) + .then_some(LogicalPixel::new(max_height).into()); + window.set_inner_size_constraints(size_constraints); + } + _ => {} + }, _ => (), } }); diff --git a/src/dpi.rs b/src/dpi.rs index f86030433..3b4fb4ae1 100644 --- a/src/dpi.rs +++ b/src/dpi.rs @@ -98,47 +98,65 @@ pub trait Pixel: Copy + Into { } } -impl Pixel for u8 { - fn from_f64(f: f64) -> Self { - f.round() as u8 - } -} -impl Pixel for u16 { - fn from_f64(f: f64) -> Self { - f.round() as u16 - } -} -impl Pixel for u32 { - fn from_f64(f: f64) -> Self { - f.round() as u32 - } -} -impl Pixel for i8 { - fn from_f64(f: f64) -> Self { - f.round() as i8 - } -} -impl Pixel for i16 { - fn from_f64(f: f64) -> Self { - f.round() as i16 - } -} -impl Pixel for i32 { - fn from_f64(f: f64) -> Self { - f.round() as i32 - } +macro_rules! pixel_int_impl { + ($($t:ty),*) => {$( + impl Pixel for $t { + fn from_f64(f: f64) -> Self { + f.round() as $t + } + } + )*} } + +pixel_int_impl!(u8, u16, u32, i8, i16, i32); + impl Pixel for f32 { fn from_f64(f: f64) -> Self { f as f32 } } + impl Pixel for f64 { fn from_f64(f: f64) -> Self { f } } +macro_rules! from_impls { + ($t:ident, $a:ident, $(,)? ) => { + impl From

for $t

{ + fn from($a: P) -> Self { + Self::new($a.cast()) + } + } + }; + ($t:ident, $a:ident, $b:ident$(,)? ) => { + impl From<(X, X)> for $t

{ + fn from(($a, $b): (X, X)) -> Self { + Self::new($a.cast(), $b.cast()) + } + } + + impl From<$t

> for (X, X) { + fn from(p: $t

) -> Self { + (p.$a.cast(), p.$b.cast()) + } + } + + impl From<[X; 2]> for $t

{ + fn from([$a, $b]: [X; 2]) -> Self { + Self::new($a.cast(), $b.cast()) + } + } + + impl From<$t

> for [X; 2] { + fn from(p: $t

) -> Self { + [p.$a.cast(), p.$b.cast()] + } + } + }; +} + /// Checks that the scale factor is a normal positive `f64`. /// /// All functions that take a scale factor assert that this will return `true`. If you're sourcing scale factors from @@ -149,369 +167,233 @@ pub fn validate_scale_factor(scale_factor: f64) -> bool { scale_factor.is_sign_positive() && scale_factor.is_normal() } -/// A position represented in logical pixels. -/// -/// The position is stored as floats, so please be careful. Casting floats to integers truncates the -/// fractional part, which can cause noticeable issues. To help with that, an `Into<(i32, i32)>` -/// implementation is provided which does the rounding for you. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct LogicalPosition

{ - pub x: P, - pub y: P, -} +macro_rules! dpi_type { + ( + $(let $a:ident;)* -impl

LogicalPosition

{ - #[inline] - pub const fn new(x: P, y: P) -> Self { - LogicalPosition { x, y } - } -} - -impl LogicalPosition

{ - #[inline] - pub fn from_physical>, X: Pixel>( - physical: T, - scale_factor: f64, - ) -> Self { - physical.into().to_logical(scale_factor) - } - - #[inline] - pub fn to_physical(&self, scale_factor: f64) -> PhysicalPosition { - assert!(validate_scale_factor(scale_factor)); - let x = self.x.into() * scale_factor; - let y = self.y.into() * scale_factor; - PhysicalPosition::new(x, y).cast() - } - - #[inline] - pub fn cast(&self) -> LogicalPosition { - LogicalPosition { - x: self.x.cast(), - y: self.y.cast(), - } - } -} - -impl From<(X, X)> for LogicalPosition

{ - fn from((x, y): (X, X)) -> LogicalPosition

{ - LogicalPosition::new(x.cast(), y.cast()) - } -} - -impl Into<(X, X)> for LogicalPosition

{ - fn into(self) -> (X, X) { - (self.x.cast(), self.y.cast()) - } -} - -impl From<[X; 2]> for LogicalPosition

{ - fn from([x, y]: [X; 2]) -> LogicalPosition

{ - LogicalPosition::new(x.cast(), y.cast()) - } -} - -impl Into<[X; 2]> for LogicalPosition

{ - fn into(self) -> [X; 2] { - [self.x.cast(), self.y.cast()] - } -} - -/// A position represented in physical pixels. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct PhysicalPosition

{ - pub x: P, - pub y: P, -} - -impl

PhysicalPosition

{ - #[inline] - pub const fn new(x: P, y: P) -> Self { - PhysicalPosition { x, y } - } -} - -impl PhysicalPosition

{ - #[inline] - pub fn from_logical>, X: Pixel>( - logical: T, - scale_factor: f64, - ) -> Self { - logical.into().to_physical(scale_factor) - } - - #[inline] - pub fn to_logical(&self, scale_factor: f64) -> LogicalPosition { - assert!(validate_scale_factor(scale_factor)); - let x = self.x.into() / scale_factor; - let y = self.y.into() / scale_factor; - LogicalPosition::new(x, y).cast() - } - - #[inline] - pub fn cast(&self) -> PhysicalPosition { - PhysicalPosition { - x: self.x.cast(), - y: self.y.cast(), - } - } -} + $(#[$logical_meta:meta])* + pub struct $LogicalType:ident; + $(#[$physical_meta:meta])* + pub struct $PhysicalType:ident; + $(#[$unified_meta:meta])* + pub enum $UnifiedType:ident { + Physical($unified_physical:ty), + Logical($unified_logical:ty), + } + ) => { + $(#[$logical_meta])* + #[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Hash, PartialOrd, Ord)] + #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] + pub struct $LogicalType

{ + $(pub $a: P,)* + } -impl From<(X, X)> for PhysicalPosition

{ - fn from((x, y): (X, X)) -> PhysicalPosition

{ - PhysicalPosition::new(x.cast(), y.cast()) - } -} + impl

$LogicalType

{ + #[inline] + pub const fn new($($a: P,)*) -> Self { + $LogicalType { $($a,)* } + } + } -impl Into<(X, X)> for PhysicalPosition

{ - fn into(self) -> (X, X) { - (self.x.cast(), self.y.cast()) - } -} + impl $LogicalType

{ + #[inline] + pub fn from_physical>, X: Pixel>( + physical: T, + scale_factor: f64, + ) -> Self { + physical.into().to_logical(scale_factor) + } + + #[inline] + pub fn to_physical(&self, scale_factor: f64) -> $PhysicalType { + assert!(validate_scale_factor(scale_factor)); + $(let $a = self.$a.into() * scale_factor;)* + $PhysicalType::new($($a,)*).cast() + } + + #[inline] + pub fn cast(&self) -> $LogicalType { + $LogicalType { + $($a: self.$a.cast(),)* + } + } + } -impl From<[X; 2]> for PhysicalPosition

{ - fn from([x, y]: [X; 2]) -> PhysicalPosition

{ - PhysicalPosition::new(x.cast(), y.cast()) - } -} + from_impls!($LogicalType, $($a,)*); -impl Into<[X; 2]> for PhysicalPosition

{ - fn into(self) -> [X; 2] { - [self.x.cast(), self.y.cast()] - } -} + $(#[$physical_meta])* + #[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Hash, PartialOrd, Ord)] + #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] + pub struct $PhysicalType

{ + $(pub $a: P,)* + } -/// A size represented in logical pixels. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct LogicalSize

{ - pub width: P, - pub height: P, -} + impl

$PhysicalType

{ + #[inline] + pub const fn new($($a: P,)*) -> Self { + $PhysicalType { $($a,)* } + } + } -impl

LogicalSize

{ - #[inline] - pub const fn new(width: P, height: P) -> Self { - LogicalSize { width, height } - } -} + impl $PhysicalType

{ + #[inline] + pub fn from_logical>, X: Pixel>( + logical: T, + scale_factor: f64, + ) -> Self { + logical.into().to_physical(scale_factor) + } + + #[inline] + pub fn to_logical(&self, scale_factor: f64) -> $LogicalType { + assert!(validate_scale_factor(scale_factor)); + $(let $a = self.$a.into() / scale_factor;)* + $LogicalType::new($($a,)*).cast() + } + + #[inline] + pub fn cast(&self) -> $PhysicalType { + $PhysicalType { + $($a: self.$a.cast(),)* + } + } + } -impl LogicalSize

{ - #[inline] - pub fn from_physical>, X: Pixel>(physical: T, scale_factor: f64) -> Self { - physical.into().to_logical(scale_factor) - } + from_impls!($PhysicalType, $($a,)*); - #[inline] - pub fn to_physical(&self, scale_factor: f64) -> PhysicalSize { - assert!(validate_scale_factor(scale_factor)); - let width = self.width.into() * scale_factor; - let height = self.height.into() * scale_factor; - PhysicalSize::new(width, height).cast() - } + $(#[$unified_meta])* + #[derive(Debug, Copy, Clone, PartialEq, PartialOrd)] + #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] + pub enum $UnifiedType { + Physical($unified_physical), + Logical($unified_logical), + } - #[inline] - pub fn cast(&self) -> LogicalSize { - LogicalSize { - width: self.width.cast(), - height: self.height.cast(), - } - } -} + impl $UnifiedType { + pub fn new>(val: S) -> $UnifiedType { + val.into() + } + + pub fn to_logical(&self, scale_factor: f64) -> $LogicalType

{ + match *self { + $UnifiedType::Physical(val) => val.to_logical(scale_factor), + $UnifiedType::Logical(val) => val.cast(), + } + } + + pub fn to_physical(&self, scale_factor: f64) -> $PhysicalType

{ + match *self { + $UnifiedType::Physical(val) => val.cast(), + $UnifiedType::Logical(val) => val.to_physical(scale_factor), + } + } + + $(pub fn $a(&self) -> PixelUnit { + match *self { + $UnifiedType::Physical(any) => PixelUnit::Physical(any.$a.into()), + $UnifiedType::Logical(any) => PixelUnit::Logical(any.$a.into()), + } + })* + } -impl From<(X, X)> for LogicalSize

{ - fn from((x, y): (X, X)) -> LogicalSize

{ - LogicalSize::new(x.cast(), y.cast()) - } -} + impl From<$PhysicalType

> for $UnifiedType { + #[inline] + fn from(val: $PhysicalType

) -> $UnifiedType { + $UnifiedType::Physical(val.cast()) + } + } -impl Into<(X, X)> for LogicalSize

{ - fn into(self: LogicalSize

) -> (X, X) { - (self.width.cast(), self.height.cast()) - } + impl From<$LogicalType

> for $UnifiedType { + #[inline] + fn from(val: $LogicalType

) -> $UnifiedType { + $UnifiedType::Logical(val.cast()) + } + } + }; } -impl From<[X; 2]> for LogicalSize

{ - fn from([x, y]: [X; 2]) -> LogicalSize

{ - LogicalSize::new(x.cast(), y.cast()) - } -} +dpi_type! { + let value; -impl Into<[X; 2]> for LogicalSize

{ - fn into(self) -> [X; 2] { - [self.width.cast(), self.height.cast()] + /// A logical pixel. + pub struct LogicalPixel; + /// A physical pixel. + pub struct PhysicalPixel; + /// A pixel that's either physical or logical. + pub enum PixelUnit { + Physical(PhysicalPixel), + Logical(LogicalPixel), } } -/// A size represented in physical pixels. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct PhysicalSize

{ - pub width: P, - pub height: P, +impl PixelUnit { + /// Represents a minimum logical unit of `0` + pub const MIN: PixelUnit = PixelUnit::Logical(LogicalPixel::new(0.0)); + /// Represents a maximum logical unit that is equal to [`f64::MAX`] + pub const MAX: PixelUnit = PixelUnit::Logical(LogicalPixel::new(f64::MAX)); } -impl

PhysicalSize

{ - #[inline] - pub const fn new(width: P, height: P) -> Self { - PhysicalSize { width, height } +impl From for PhysicalPixel { + fn from(value: u32) -> Self { + Self::new(value.cast()) } } -impl PhysicalSize

{ - #[inline] - pub fn from_logical>, X: Pixel>(logical: T, scale_factor: f64) -> Self { - logical.into().to_physical(scale_factor) - } +dpi_type! { + let x; + let y; - #[inline] - pub fn to_logical(&self, scale_factor: f64) -> LogicalSize { - assert!(validate_scale_factor(scale_factor)); - let width = self.width.into() / scale_factor; - let height = self.height.into() / scale_factor; - LogicalSize::new(width, height).cast() - } - - #[inline] - pub fn cast(&self) -> PhysicalSize { - PhysicalSize { - width: self.width.cast(), - height: self.height.cast(), - } + /// A position represented in logical pixels. + /// + /// The position is stored as floats, so please be careful. Casting floats to integers truncates the + /// fractional part, which can cause noticable issues. To help with that, an `Into<(i32, i32)>` + /// implementation is provided which does the rounding for you. + pub struct LogicalPosition; + /// A position represented in physical pixels. + pub struct PhysicalPosition; + /// A position that's either physical or logical. + pub enum Position { + Physical(PhysicalPosition), + Logical(LogicalPosition), } } -impl From<(X, X)> for PhysicalSize

{ - fn from((x, y): (X, X)) -> PhysicalSize

{ - PhysicalSize::new(x.cast(), y.cast()) - } -} +dpi_type! { + let width; + let height; -impl Into<(X, X)> for PhysicalSize

{ - fn into(self) -> (X, X) { - (self.width.cast(), self.height.cast()) + /// A size represented in logical pixels. + pub struct LogicalSize; + /// A size represented in physical pixels. + pub struct PhysicalSize; + /// A size that's either physical or logical. + pub enum Size { + Physical(PhysicalSize), + Logical(LogicalSize), } } -impl From<[X; 2]> for PhysicalSize

{ - fn from([x, y]: [X; 2]) -> PhysicalSize

{ - PhysicalSize::new(x.cast(), y.cast()) - } -} - -impl Into<[X; 2]> for PhysicalSize

{ - fn into(self) -> [X; 2] { - [self.width.cast(), self.height.cast()] - } -} - -/// A size that's either physical or logical. -#[non_exhaustive] -#[derive(Debug, Copy, Clone, PartialEq)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub enum Size { - Physical(PhysicalSize), - Logical(LogicalSize), -} - impl Size { - pub fn new>(size: S) -> Size { - size.into() - } - - pub fn to_logical(&self, scale_factor: f64) -> LogicalSize

{ - match *self { - Size::Physical(size) => size.to_logical(scale_factor), - Size::Logical(size) => size.cast(), - } - } - - pub fn to_physical(&self, scale_factor: f64) -> PhysicalSize

{ - match *self { - Size::Physical(size) => size.cast(), - Size::Logical(size) => size.to_physical(scale_factor), - } - } - - pub fn clamp>(input: S, min: S, max: S, scale_factor: f64) -> Size { - let (input, min, max) = ( - input.into().to_physical::(scale_factor), + pub fn clamp>(desired_size: S, min: S, max: S, scale_factor: f64) -> Size { + let (desired_size, min, max) = ( + desired_size.into().to_physical::(scale_factor), min.into().to_physical::(scale_factor), max.into().to_physical::(scale_factor), ); - let clamp = |input: f64, min: f64, max: f64| { - if input < min { + let clamp = |desired_size: f64, min: f64, max: f64| { + if desired_size < min { min - } else if input > max { + } else if desired_size > max { max } else { - input + desired_size } }; - let width = clamp(input.width, min.width, max.width); - let height = clamp(input.height, min.height, max.height); + let width = clamp(desired_size.width, min.width, max.width); + let height = clamp(desired_size.height, min.height, max.height); PhysicalSize::new(width, height).into() } } - -impl From> for Size { - #[inline] - fn from(size: PhysicalSize

) -> Size { - Size::Physical(size.cast()) - } -} - -impl From> for Size { - #[inline] - fn from(size: LogicalSize

) -> Size { - Size::Logical(size.cast()) - } -} - -/// A position that's either physical or logical. -#[non_exhaustive] -#[derive(Debug, Copy, Clone, PartialEq)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub enum Position { - Physical(PhysicalPosition), - Logical(LogicalPosition), -} - -impl Position { - pub fn new>(position: S) -> Position { - position.into() - } - - pub fn to_logical(&self, scale_factor: f64) -> LogicalPosition

{ - match *self { - Position::Physical(position) => position.to_logical(scale_factor), - Position::Logical(position) => position.cast(), - } - } - - pub fn to_physical(&self, scale_factor: f64) -> PhysicalPosition

{ - match *self { - Position::Physical(position) => position.cast(), - Position::Logical(position) => position.to_physical(scale_factor), - } - } -} - -impl From> for Position { - #[inline] - fn from(position: PhysicalPosition

) -> Position { - Position::Physical(position.cast()) - } -} - -impl From> for Position { - #[inline] - fn from(position: LogicalPosition

) -> Position { - Position::Logical(position.cast()) - } -} diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index fc6b5f849..62fe5a186 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -12,7 +12,7 @@ use crate::{ keyboard::{Key, KeyCode, KeyLocation, NativeKeyCode}, menu::{CustomMenuItem, MenuId, MenuItem, MenuType}, monitor, - window::{self, Theme}, + window::{self, Theme, WindowSizeConstraints}, }; use ndk::{ configuration::Configuration, @@ -602,8 +602,8 @@ impl Window { } pub fn set_min_inner_size(&self, _: Option) {} - pub fn set_max_inner_size(&self, _: Option) {} + pub fn set_inner_size_constraints(&self, _: WindowSizeConstraints) {} pub fn set_title(&self, _title: &str) {} pub fn title(&self) -> String { diff --git a/src/platform_impl/ios/window.rs b/src/platform_impl/ios/window.rs index 736ae592f..ebbf28312 100644 --- a/src/platform_impl/ios/window.rs +++ b/src/platform_impl/ios/window.rs @@ -28,6 +28,7 @@ use crate::{ }, window::{ CursorIcon, Fullscreen, Theme, UserAttentionType, WindowAttributes, WindowId as RootWindowId, + WindowSizeConstraints, }, }; @@ -169,13 +170,15 @@ impl Inner { warn!("not clear what `Window::set_inner_size` means on iOS"); } - pub fn set_min_inner_size(&self, _dimensions: Option) { + pub fn set_min_inner_size(&self, _: Option) { warn!("`Window::set_min_inner_size` is ignored on iOS") } - - pub fn set_max_inner_size(&self, _dimensions: Option) { + pub fn set_max_inner_size(&self, _: Option) { warn!("`Window::set_max_inner_size` is ignored on iOS") } + pub fn set_inner_size_constraints(&self, _: WindowSizeConstraints) { + warn!("`Window::set_inner_size_constraints` is ignored on iOS") + } pub fn set_resizable(&self, _resizable: bool) { warn!("`Window::set_resizable` is ignored on iOS") @@ -461,12 +464,6 @@ impl Window { window_attributes: WindowAttributes, platform_attributes: PlatformSpecificWindowBuilderAttributes, ) -> Result { - if let Some(_) = window_attributes.min_inner_size { - warn!("`WindowAttributes::min_inner_size` is ignored on iOS"); - } - if let Some(_) = window_attributes.max_inner_size { - warn!("`WindowAttributes::max_inner_size` is ignored on iOS"); - } if window_attributes.always_on_top { warn!("`WindowAttributes::always_on_top` is unsupported on iOS"); } diff --git a/src/platform_impl/linux/event_loop.rs b/src/platform_impl/linux/event_loop.rs index 7af4cc9f4..f141b8112 100644 --- a/src/platform_impl/linux/event_loop.rs +++ b/src/platform_impl/linux/event_loop.rs @@ -223,35 +223,8 @@ impl EventLoop { WindowRequest::Title(title) => window.set_title(&title), WindowRequest::Position((x, y)) => window.move_(x, y), WindowRequest::Size((w, h)) => window.resize(w, h), - WindowRequest::SizeConstraint { min, max } => { - let geom_mask = min - .map(|_| gdk::WindowHints::MIN_SIZE) - .unwrap_or(gdk::WindowHints::empty()) - | max - .map(|_| gdk::WindowHints::MAX_SIZE) - .unwrap_or(gdk::WindowHints::empty()); - - let (min_width, min_height) = min.map(Into::into).unwrap_or_default(); - let (max_width, max_height) = max.map(Into::into).unwrap_or_default(); - - let picky_none: Option<>k::Window> = None; - window.set_geometry_hints( - picky_none, - Some(&gdk::Geometry::new( - min_width, - min_height, - max_width, - max_height, - 0, - 0, - 0, - 0, - 0f64, - 0f64, - gdk::Gravity::Center, - )), - geom_mask, - ) + WindowRequest::SizeConstraints(constraints) => { + util::set_size_constraints(&window, constraints); } WindowRequest::Visible(visible) => { if visible { diff --git a/src/platform_impl/linux/util.rs b/src/platform_impl/linux/util.rs index 180b130d7..a221e609b 100644 --- a/src/platform_impl/linux/util.rs +++ b/src/platform_impl/linux/util.rs @@ -1,8 +1,10 @@ use gdk::Display; +use gtk::traits::{GtkWindowExt, WidgetExt}; use crate::{ - dpi::{LogicalPosition, PhysicalPosition}, + dpi::{LogicalPosition, LogicalSize, PhysicalPosition}, error::ExternalError, + window::WindowSizeConstraints, }; #[inline] @@ -28,6 +30,43 @@ pub fn cursor_position(is_wayland: bool) -> Result, Extern } } +pub fn set_size_constraints( + window: &W, + constraints: WindowSizeConstraints, +) { + let mut geom_mask = gdk::WindowHints::empty(); + if constraints.has_min() { + geom_mask |= gdk::WindowHints::MIN_SIZE; + } + if constraints.has_max() { + geom_mask |= gdk::WindowHints::MAX_SIZE; + } + + let scale_factor = window.scale_factor() as f64; + + let min_size: LogicalSize = constraints.min_size_logical(scale_factor); + let max_size: LogicalSize = constraints.max_size_logical(scale_factor); + + let picky_none: Option<>k::Window> = None; + window.set_geometry_hints( + picky_none, + Some(&gdk::Geometry::new( + min_size.width, + min_size.height, + max_size.width, + max_size.height, + 0, + 0, + 0, + 0, + 0f64, + 0f64, + gdk::Gravity::Center, + )), + geom_mask, + ) +} + pub fn is_unity() -> bool { std::env::var("XDG_CURRENT_DESKTOP") .map(|d| { diff --git a/src/platform_impl/linux/window.rs b/src/platform_impl/linux/window.rs index 002d8a5c7..1c9b66ca0 100644 --- a/src/platform_impl/linux/window.rs +++ b/src/platform_impl/linux/window.rs @@ -28,7 +28,7 @@ use crate::{ monitor::MonitorHandle as RootMonitorHandle, window::{ CursorIcon, Fullscreen, ProgressBarState, Theme, UserAttentionType, WindowAttributes, - BORDERLESS_RESIZE_INSET, + WindowSizeConstraints, BORDERLESS_RESIZE_INSET, }, }; @@ -69,8 +69,7 @@ pub struct Window { maximized: Rc, minimized: Rc, fullscreen: RefCell>, - min_inner_size: RefCell>, - max_inner_size: RefCell>, + inner_size_constraints: RefCell, /// Draw event Sender draw_tx: crossbeam_channel::Sender, } @@ -114,40 +113,7 @@ impl Window { window.set_deletable(attributes.closable); // Set Min/Max Size - let geom_mask = attributes - .min_inner_size - .map(|_| gdk::WindowHints::MIN_SIZE) - .unwrap_or(gdk::WindowHints::empty()) - | attributes - .max_inner_size - .map(|_| gdk::WindowHints::MAX_SIZE) - .unwrap_or(gdk::WindowHints::empty()); - let (min_width, min_height) = attributes - .min_inner_size - .map(|size| size.to_logical::(win_scale_factor as f64).into()) - .unwrap_or_default(); - let (max_width, max_height) = attributes - .max_inner_size - .map(|size| size.to_logical::(win_scale_factor as f64).into()) - .unwrap_or_default(); - let picky_none: Option<>k::Window> = None; - window.set_geometry_hints( - picky_none, - Some(&gdk::Geometry::new( - min_width, - min_height, - max_width, - max_height, - 0, - 0, - 0, - 0, - 0f64, - 0f64, - gdk::Gravity::Center, - )), - geom_mask, - ); + util::set_size_constraints(&window, attributes.inner_size_constraints); // Set Position if let Some(position) = attributes.position { @@ -313,12 +279,12 @@ impl Window { let maximized: Rc = Rc::new(w_max.into()); let max_clone = maximized.clone(); let minimized = Rc::new(AtomicBool::new(false)); - let min_clone = minimized.clone(); + let minimized_clone = minimized.clone(); window.connect_window_state_event(move |_, event| { let state = event.new_window_state(); max_clone.store(state.contains(WindowState::MAXIMIZED), Ordering::Release); - min_clone.store(state.contains(WindowState::ICONIFIED), Ordering::Release); + minimized_clone.store(state.contains(WindowState::ICONIFIED), Ordering::Release); Inhibit(false) }); @@ -361,8 +327,7 @@ impl Window { maximized, minimized, fullscreen: RefCell::new(attributes.fullscreen), - min_inner_size: RefCell::new(attributes.min_inner_size), - max_inner_size: RefCell::new(attributes.max_inner_size), + inner_size_constraints: RefCell::new(attributes.inner_size_constraints), }; win.set_skip_taskbar(pl_attribs.skip_taskbar); @@ -445,47 +410,32 @@ impl Window { .to_physical(self.scale_factor.load(Ordering::Acquire) as f64) } - pub fn set_min_inner_size>(&self, min_size: Option) { - *self.min_inner_size.borrow_mut() = min_size.map(Into::into); - - let scale_factor = self.scale_factor(); - - let min = self - .min_inner_size - .borrow() - .map(|s| s.to_logical::(scale_factor)); - let max = self - .max_inner_size - .borrow() - .map(|s| s.to_logical::(scale_factor)); - - if let Err(e) = self - .window_requests_tx - .send((self.window_id, WindowRequest::SizeConstraint { min, max })) - { - log::warn!("Fail to send min size request: {}", e); + fn set_size_constraints(&self) { + if let Err(e) = self.window_requests_tx.send(( + self.window_id, + WindowRequest::SizeConstraints(*self.inner_size_constraints.borrow()), + )) { + log::warn!("Fail to send size constraint request: {}", e); } } - pub fn set_max_inner_size>(&self, max_size: Option) { - *self.max_inner_size.borrow_mut() = max_size.map(Into::into); - let scale_factor = self.scale_factor(); + pub fn set_min_inner_size(&self, size: Option) { + let mut size_constraints = self.inner_size_constraints.borrow_mut(); + size_constraints.min_width = size.map(|s| s.width()); + size_constraints.min_height = size.map(|s| s.height()); + self.set_size_constraints() + } - let min = self - .min_inner_size - .borrow() - .map(|s| s.to_logical::(scale_factor)); - let max = self - .max_inner_size - .borrow() - .map(|s| s.to_logical::(scale_factor)); + pub fn set_max_inner_size(&self, size: Option) { + let mut size_constraints = self.inner_size_constraints.borrow_mut(); + size_constraints.max_width = size.map(|s| s.width()); + size_constraints.max_height = size.map(|s| s.height()); + self.set_size_constraints() + } - if let Err(e) = self - .window_requests_tx - .send((self.window_id, WindowRequest::SizeConstraint { min, max })) - { - log::warn!("Fail to send max size request: {}", e); - } + pub fn set_inner_size_constraints(&self, constraints: WindowSizeConstraints) { + *self.inner_size_constraints.borrow_mut() = constraints; + self.set_size_constraints() } pub fn set_title(&self, title: &str) { @@ -892,10 +842,7 @@ pub enum WindowRequest { Title(String), Position((i32, i32)), Size((i32, i32)), - SizeConstraint { - min: Option>, - max: Option>, - }, + SizeConstraints(WindowSizeConstraints), Visible(bool), Focus, Resizable(bool), diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index 3db5044fd..8c000c16d 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -36,6 +36,7 @@ use crate::{ }, window::{ CursorIcon, Fullscreen, Theme, UserAttentionType, WindowAttributes, WindowId as RootWindowId, + WindowSizeConstraints, }, }; use cocoa::{ @@ -162,13 +163,14 @@ fn create_window( None => { let screen = NSScreen::mainScreen(nil); let scale_factor = NSScreen::backingScaleFactor(screen) as f64; - let (width, height) = match attrs.inner_size { - Some(size) => { - let logical = size.to_logical(scale_factor); - (logical.width, logical.height) - } - None => (800.0, 600.0), - }; + let desired_size = attrs + .inner_size + .unwrap_or_else(|| PhysicalSize::new(800, 600).into()); + let (width, height): (f64, f64) = attrs + .inner_size_constraints + .clamp(desired_size, scale_factor) + .to_logical::(scale_factor) + .into(); let (left, bottom) = match attrs.position { Some(position) => { let logical = util::window_position(position.to_logical(scale_factor)); @@ -495,14 +497,18 @@ impl UnownedWindow { ns_window.setBackgroundColor_(NSColor::clearColor(nil)); } - win_attribs.min_inner_size.map(|dim| { - let logical_dim = dim.to_logical(scale_factor); - set_min_inner_size(*ns_window, logical_dim) - }); - win_attribs.max_inner_size.map(|dim| { - let logical_dim = dim.to_logical(scale_factor); - set_max_inner_size(*ns_window, logical_dim) - }); + if win_attribs.inner_size_constraints.has_min() { + let min_size = win_attribs + .inner_size_constraints + .min_size_logical(scale_factor); + set_min_inner_size(*ns_window, min_size); + } + if win_attribs.inner_size_constraints.has_max() { + let max_size = win_attribs + .inner_size_constraints + .max_size_logical(scale_factor); + set_max_inner_size(*ns_window, max_size); + } // register for drag and drop operations. let () = msg_send![ @@ -697,27 +703,37 @@ impl UnownedWindow { } pub fn set_min_inner_size(&self, dimensions: Option) { + let dimensions = dimensions.unwrap_or(Logical(LogicalSize { + width: 0.0, + height: 0.0, + })); + let scale_factor = self.scale_factor(); unsafe { - let dimensions = dimensions.unwrap_or(Logical(LogicalSize { - width: 0.0, - height: 0.0, - })); - let scale_factor = self.scale_factor(); set_min_inner_size(*self.ns_window, dimensions.to_logical(scale_factor)); } } pub fn set_max_inner_size(&self, dimensions: Option) { + let dimensions = dimensions.unwrap_or(Logical(LogicalSize { + width: std::f32::MAX as f64, + height: std::f32::MAX as f64, + })); + let scale_factor = self.scale_factor(); unsafe { - let dimensions = dimensions.unwrap_or(Logical(LogicalSize { - width: std::f32::MAX as f64, - height: std::f32::MAX as f64, - })); - let scale_factor = self.scale_factor(); set_max_inner_size(*self.ns_window, dimensions.to_logical(scale_factor)); } } + pub fn set_inner_size_constraints(&self, constraints: WindowSizeConstraints) { + let scale_factor = self.scale_factor(); + unsafe { + let min_size = constraints.min_size_logical(scale_factor); + set_min_inner_size(*self.ns_window, min_size); + let max_size = constraints.max_size_logical(scale_factor); + set_max_inner_size(*self.ns_window, max_size); + } + } + #[inline] pub fn set_resizable(&self, resizable: bool) { let fullscreen = { diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 62217683a..596613c92 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -44,7 +44,7 @@ use windows::{ use crate::{ accelerator::AcceleratorId, - dpi::{PhysicalPosition, PhysicalSize}, + dpi::{PhysicalPosition, PhysicalSize, PixelUnit}, error::ExternalError, event::{DeviceEvent, Event, Force, RawKeyEvent, Touch, TouchPhase, WindowEvent}, event_loop::{ControlFlow, DeviceEventFilter, EventLoopClosed, EventLoopWindowTarget as RootELW}, @@ -1773,29 +1773,49 @@ unsafe fn public_window_callback_inner( let mmi = lparam.0 as *mut MINMAXINFO; let window_state = subclass_input.window_state.lock(); - - if window_state.min_size.is_some() || window_state.max_size.is_some() { - let is_decorated = window_state - .window_flags() - .contains(WindowFlags::MARKER_DECORATIONS); - if let Some(min_size) = window_state.min_size { - let min_size = min_size.to_physical(window_state.scale_factor); - let (width, height): (u32, u32) = - util::adjust_size(window, min_size, is_decorated).into(); - (*mmi).ptMinTrackSize = POINT { - x: width as i32, - y: height as i32, - }; - } - if let Some(max_size) = window_state.max_size { - let max_size = max_size.to_physical(window_state.scale_factor); - let (width, height): (u32, u32) = - util::adjust_size(window, max_size, is_decorated).into(); - (*mmi).ptMaxTrackSize = POINT { - x: width as i32, - y: height as i32, - }; - } + let is_decorated = window_state + .window_flags() + .contains(WindowFlags::MARKER_DECORATIONS); + + let size_constraints = window_state.size_constraints; + + if size_constraints.has_min() { + let min_size = PhysicalSize::new( + size_constraints + .min_width + .unwrap_or_else(|| PixelUnit::Physical(GetSystemMetrics(SM_CXMINTRACK).into())) + .to_physical(window_state.scale_factor) + .value, + size_constraints + .min_height + .unwrap_or_else(|| PixelUnit::Physical(GetSystemMetrics(SM_CYMINTRACK).into())) + .to_physical(window_state.scale_factor) + .value, + ); + let (width, height): (u32, u32) = util::adjust_size(window, min_size, is_decorated).into(); + (*mmi).ptMinTrackSize = POINT { + x: width as i32, + y: height as i32, + }; + } + if size_constraints.has_max() { + let max_size = PhysicalSize::new( + size_constraints + .max_width + .unwrap_or_else(|| PixelUnit::Physical(GetSystemMetrics(SM_CXMAXTRACK).into())) + .to_physical(window_state.scale_factor) + .value, + size_constraints + .max_height + .unwrap_or_else(|| PixelUnit::Physical(GetSystemMetrics(SM_CYMAXTRACK).into())) + .to_physical(window_state.scale_factor) + .value, + ); + let (width, height): (u32, u32) = util::adjust_size(window, max_size, is_decorated).into(); + (*mmi).ptMaxTrackSize = POINT { + x: width as i32, + y: height as i32, + }; } result = ProcResult::Value(LRESULT(0)); @@ -1891,7 +1911,7 @@ unsafe fn public_window_callback_inner( false => old_physical_inner_size, }; - let _ = subclass_input.send_event(Event::WindowEvent { + subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window.0)), event: ScaleFactorChanged { scale_factor: new_scale_factor, @@ -2028,7 +2048,7 @@ unsafe fn public_window_callback_inner( let preferred_theme = subclass_input.window_state.lock().preferred_theme; - if preferred_theme == None { + if preferred_theme.is_none() { let new_theme = try_theme(window, preferred_theme); let mut window_state = subclass_input.window_state.lock(); diff --git a/src/platform_impl/windows/system_tray.rs b/src/platform_impl/windows/system_tray.rs index f44c5184c..aa2a43d77 100644 --- a/src/platform_impl/windows/system_tray.rs +++ b/src/platform_impl/windows/system_tray.rs @@ -114,7 +114,7 @@ impl SystemTrayBuilder { ))); } - let system_tray = SystemTray { hwnd: hwnd.clone() }; + let system_tray = SystemTray { hwnd }; // system_tray event handler let event_loop_runner = window_target.p.runner_shared.clone(); diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index a56eafa2f..87b3e6b0c 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -54,7 +54,7 @@ use crate::{ }, window::{ CursorIcon, Fullscreen, ProgressBarState, ProgressState, Theme, UserAttentionType, - WindowAttributes, WindowId as RootWindowId, BORDERLESS_RESIZE_INSET, + WindowAttributes, WindowId as RootWindowId, WindowSizeConstraints, BORDERLESS_RESIZE_INSET, }, }; @@ -285,7 +285,11 @@ impl Window { #[inline] pub fn set_min_inner_size(&self, size: Option) { - self.window_state.lock().min_size = size; + { + let mut window_state = self.window_state.lock(); + window_state.size_constraints.min_width = size.map(|s| s.width()); + window_state.size_constraints.min_height = size.map(|s| s.height()); + } // Make windows re-check the window size bounds. let size = self.inner_size(); self.set_inner_size(size.into()); @@ -293,7 +297,19 @@ impl Window { #[inline] pub fn set_max_inner_size(&self, size: Option) { - self.window_state.lock().max_size = size; + { + let mut window_state = self.window_state.lock(); + window_state.size_constraints.max_width = size.map(|s| s.width()); + window_state.size_constraints.max_height = size.map(|s| s.height()); + } + // Make windows re-check the window size bounds. + let size = self.inner_size(); + self.set_inner_size(size.into()); + } + + #[inline] + pub fn set_inner_size_constraints(&self, constraints: WindowSizeConstraints) { + self.window_state.lock().size_constraints = constraints; // Make windows re-check the window size bounds. let size = self.inner_size(); self.set_inner_size(size.into()); @@ -1102,17 +1118,13 @@ unsafe fn init( win.set_fullscreen(attributes.fullscreen); force_window_active(win.window.0); } else { - let size = attributes + let desired_size = attributes .inner_size .unwrap_or_else(|| PhysicalSize::new(800, 600).into()); - let max_size = attributes - .max_inner_size - .unwrap_or_else(|| PhysicalSize::new(f64::MAX, f64::MAX).into()); - let min_size = attributes - .min_inner_size - .unwrap_or_else(|| PhysicalSize::new(0, 0).into()); - let clamped_size = Size::clamp(size, min_size, max_size, win.scale_factor()); - win.set_inner_size(clamped_size); + let size = attributes + .inner_size_constraints + .clamp(desired_size, win.scale_factor()); + win.set_inner_size(size); if attributes.maximized { // Need to set MAXIMIZED after setting `inner_size` as diff --git a/src/platform_impl/windows/window_state.rs b/src/platform_impl/windows/window_state.rs index d42433368..579add95e 100644 --- a/src/platform_impl/windows/window_state.rs +++ b/src/platform_impl/windows/window_state.rs @@ -3,11 +3,11 @@ // SPDX-License-Identifier: Apache-2.0 use crate::{ - dpi::{PhysicalPosition, Size}, + dpi::PhysicalPosition, icon::Icon, keyboard::ModifiersState, platform_impl::platform::{event_loop, minimal_ime::MinimalIme, util}, - window::{CursorIcon, Fullscreen, Theme, WindowAttributes}, + window::{CursorIcon, Fullscreen, Theme, WindowAttributes, WindowSizeConstraints}, }; use parking_lot::MutexGuard; use std::io; @@ -22,8 +22,7 @@ pub struct WindowState { pub mouse: MouseProperties, /// Used by `WM_GETMINMAXINFO`. - pub min_size: Option, - pub max_size: Option, + pub size_constraints: WindowSizeConstraints, pub window_icon: Option, pub taskbar_icon: Option, @@ -128,8 +127,7 @@ impl WindowState { last_position: None, }, - min_size: attributes.min_inner_size, - max_size: attributes.max_inner_size, + size_constraints: attributes.inner_size_constraints, window_icon: attributes.window_icon.clone(), taskbar_icon, diff --git a/src/window.rs b/src/window.rs index b44d33587..5b67da73e 100644 --- a/src/window.rs +++ b/src/window.rs @@ -8,7 +8,7 @@ use std::fmt; use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle, RawDisplayHandle}; use crate::{ - dpi::{PhysicalPosition, PhysicalSize, Position, Size}, + dpi::{LogicalSize, PhysicalPosition, PhysicalSize, Pixel, PixelUnit, Position, Size}, error::{ExternalError, NotSupportedError, OsError}, event_loop::EventLoopWindowTarget, menu::MenuBar, @@ -136,15 +136,8 @@ pub struct WindowAttributes { /// The default is `None`. pub inner_size: Option, - /// The minimum dimensions a window can be, If this is `None`, the window will have no minimum dimensions (aside from reserved). - /// - /// The default is `None`. - pub min_inner_size: Option, - - /// The maximum dimensions a window can be, If this is `None`, the maximum will have no maximum or will be set to the primary monitor's dimensions by the platform. - /// - /// The default is `None`. - pub max_inner_size: Option, + /// The window size constraints + pub inner_size_constraints: WindowSizeConstraints, /// The desired position of the window. If this is `None`, some platform-specific position /// will be chosen. @@ -278,8 +271,7 @@ impl Default for WindowAttributes { fn default() -> WindowAttributes { WindowAttributes { inner_size: None, - min_inner_size: None, - max_inner_size: None, + inner_size_constraints: Default::default(), position: None, resizable: true, minimizable: true, @@ -328,7 +320,9 @@ impl WindowBuilder { /// [`Window::set_min_inner_size`]: crate::window::Window::set_min_inner_size #[inline] pub fn with_min_inner_size>(mut self, min_size: S) -> Self { - self.window.min_inner_size = Some(min_size.into()); + let size = min_size.into(); + self.window.inner_size_constraints.min_width = Some(size.width()); + self.window.inner_size_constraints.min_height = Some(size.height()); self } @@ -339,7 +333,20 @@ impl WindowBuilder { /// [`Window::set_max_inner_size`]: crate::window::Window::set_max_inner_size #[inline] pub fn with_max_inner_size>(mut self, max_size: S) -> Self { - self.window.max_inner_size = Some(max_size.into()); + let size = max_size.into(); + self.window.inner_size_constraints.max_width = Some(size.width()); + self.window.inner_size_constraints.max_height = Some(size.height()); + self + } + + /// Sets inner size constraints for the window. + /// + /// See [`Window::set_inner_size_constraints`] for details. + /// + /// [`Window::set_inner_size_constraints`]: crate::window::Window::set_inner_size_constraints + #[inline] + pub fn with_inner_size_constraints(mut self, constraints: WindowSizeConstraints) -> Self { + self.window.inner_size_constraints = constraints; self } @@ -742,6 +749,16 @@ impl Window { pub fn set_max_inner_size>(&self, max_size: Option) { self.window.set_max_inner_size(max_size.map(|s| s.into())) } + + /// Sets inner size constraints for the window. + /// + /// ## Platform-specific + /// + /// - **iOS / Android:** Unsupported. + #[inline] + pub fn set_inner_size_constraints(&self, constraints: WindowSizeConstraints) { + self.window.set_inner_size_constraints(constraints) + } } /// Misc. attribute functions. @@ -1436,6 +1453,123 @@ impl Default for UserAttentionType { } } +/// Window size constraints +#[derive(Clone, Copy, PartialEq, PartialOrd, Debug, Default)] +pub struct WindowSizeConstraints { + /// The minimum width a window can be, If this is `None`, the window will have no minimum width (aside from reserved). + /// + /// The default is `None`. + pub min_width: Option, + /// The minimum height a window can be, If this is `None`, the window will have no minimum height (aside from reserved). + /// + /// The default is `None`. + pub min_height: Option, + /// The maximum width a window can be, If this is `None`, the window will have no maximum width (aside from reserved). + /// + /// The default is `None`. + pub max_width: Option, + /// The maximum height a window can be, If this is `None`, the window will have no maximum height (aside from reserved). + /// + /// The default is `None`. + pub max_height: Option, +} + +impl WindowSizeConstraints { + pub fn new( + min_width: Option, + min_height: Option, + max_width: Option, + max_height: Option, + ) -> Self { + Self { + min_width, + min_height, + max_width, + max_height, + } + } + + /// Returns true if `min_width` or `min_height` is set. + pub fn has_min(&self) -> bool { + self.min_width.is_some() || self.min_height.is_some() + } + /// Returns true if `max_width` or `max_height` is set. + pub fn has_max(&self) -> bool { + self.max_width.is_some() || self.max_height.is_some() + } + + /// Returns a physical size that represents the minimum constraints set and fallbacks to [`PixelUnit::MIN`] for unset values + pub fn min_size_physical(&self, scale_factor: f64) -> PhysicalSize { + PhysicalSize::new( + self + .min_width + .unwrap_or(PixelUnit::MIN) + .to_physical(scale_factor) + .value, + self + .min_height + .unwrap_or(PixelUnit::MIN) + .to_physical(scale_factor) + .value, + ) + } + + /// Returns a logical size that represents the minimum constraints set and fallbacks to [`PixelUnit::MIN`] for unset values + pub fn min_size_logical(&self, scale_factor: f64) -> LogicalSize { + LogicalSize::new( + self + .min_width + .unwrap_or(PixelUnit::MIN) + .to_logical(scale_factor) + .value, + self + .min_height + .unwrap_or(PixelUnit::MIN) + .to_logical(scale_factor) + .value, + ) + } + + /// Returns a physical size that represents the maximum constraints set and fallbacks to [`PixelUnit::MAX`] for unset values + pub fn max_size_physical(&self, scale_factor: f64) -> PhysicalSize { + PhysicalSize::new( + self + .max_width + .unwrap_or(PixelUnit::MAX) + .to_physical(scale_factor) + .value, + self + .max_height + .unwrap_or(PixelUnit::MAX) + .to_physical(scale_factor) + .value, + ) + } + + /// Returns a logical size that represents the maximum constraints set and fallbacks to [`PixelUnit::MAX`] for unset values + pub fn max_size_logical(&self, scale_factor: f64) -> LogicalSize { + LogicalSize::new( + self + .max_width + .unwrap_or(PixelUnit::MAX) + .to_logical(scale_factor) + .value, + self + .max_height + .unwrap_or(PixelUnit::MAX) + .to_logical(scale_factor) + .value, + ) + } + + /// Clamps the desired size based on the constraints set + pub fn clamp(&self, desired_size: Size, scale_factor: f64) -> Size { + let min_size: PhysicalSize = self.min_size_physical(scale_factor); + let max_size: PhysicalSize = self.max_size_physical(scale_factor); + Size::clamp(desired_size, min_size.into(), max_size.into(), scale_factor) + } +} + /// A constant used to determine how much inside the window, the resize handler should appear (only used in Linux(gtk) and Windows). /// You probably need to scale it by the scale_factor of the window. pub const BORDERLESS_RESIZE_INSET: i32 = 5;