diff --git a/crates/bevy_window/src/window.rs b/crates/bevy_window/src/window.rs index fc9d800cdaeff..488dcfe5ec3dd 100644 --- a/crates/bevy_window/src/window.rs +++ b/crates/bevy_window/src/window.rs @@ -1,5 +1,5 @@ use bevy_math::{IVec2, Vec2}; -use bevy_utils::Uuid; +use bevy_utils::{tracing::warn, Uuid}; #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub struct WindowId(Uuid); @@ -32,6 +32,64 @@ impl Default for WindowId { } } +/// The size limits on a window. +/// These values are measured in logical pixels, so the user's +/// scale factor does affect the size limits on the window. +/// Please note that if the window is resizable, then when the window is +/// maximized it may have a size outside of these limits. The functionality +/// required to disable maximizing is not yet exposed by winit. +#[derive(Debug, Clone, Copy)] +pub struct WindowResizeConstraints { + pub min_width: f32, + pub min_height: f32, + pub max_width: f32, + pub max_height: f32, +} + +impl Default for WindowResizeConstraints { + fn default() -> Self { + Self { + min_width: 180., + min_height: 120., + max_width: f32::INFINITY, + max_height: f32::INFINITY, + } + } +} + +impl WindowResizeConstraints { + pub fn check_constraints(&self) -> WindowResizeConstraints { + let WindowResizeConstraints { + mut min_width, + mut min_height, + mut max_width, + mut max_height, + } = self; + min_width = min_width.max(1.); + min_height = min_height.max(1.); + if max_width < min_width { + warn!( + "The given maximum width {} is smaller than the minimum width {}", + max_width, min_width + ); + max_width = min_width; + } + if max_height < min_height { + warn!( + "The given maximum height {} is smaller than the minimum height {}", + max_height, min_height + ); + max_height = min_height; + } + WindowResizeConstraints { + min_width, + min_height, + max_width, + max_height, + } + } +} + /// An operating system window that can present content and receive user input. /// /// ## Window Sizes @@ -54,6 +112,7 @@ pub struct Window { requested_height: f32, physical_width: u32, physical_height: u32, + resize_constraints: WindowResizeConstraints, position: Option, scale_factor_override: Option, backend_scale_factor: f64, @@ -113,6 +172,9 @@ pub enum WindowCommand { SetPosition { position: IVec2, }, + SetResizeConstraints { + resize_constraints: WindowResizeConstraints, + }, } /// Defines the way a window is displayed @@ -143,6 +205,7 @@ impl Window { position, physical_width, physical_height, + resize_constraints: window_descriptor.resize_constraints, scale_factor_override: window_descriptor.scale_factor_override, backend_scale_factor: scale_factor, title: window_descriptor.title.clone(), @@ -208,6 +271,12 @@ impl Window { self.physical_height } + /// The window's client resize constraint in logical pixels. + #[inline] + pub fn resize_constraints(&self) -> WindowResizeConstraints { + self.resize_constraints + } + /// The window's client position in physical pixels. #[inline] pub fn position(&self) -> Option { @@ -248,6 +317,13 @@ impl Window { .push(WindowCommand::SetPosition { position }) } + /// Modifies the minimum and maximum window bounds for resizing in logical pixels. + #[inline] + pub fn set_resize_constraints(&mut self, resize_constraints: WindowResizeConstraints) { + self.command_queue + .push(WindowCommand::SetResizeConstraints { resize_constraints }); + } + /// Request the OS to resize the window such the the client area matches the /// specified width and height. #[allow(clippy::float_cmp)] @@ -255,6 +331,7 @@ impl Window { if self.requested_width == width && self.requested_height == height { return; } + self.requested_width = width; self.requested_height = height; self.command_queue.push(WindowCommand::SetResolution { @@ -424,6 +501,7 @@ impl Window { pub struct WindowDescriptor { pub width: f32, pub height: f32, + pub resize_constraints: WindowResizeConstraints, pub scale_factor_override: Option, pub title: String, pub vsync: bool, @@ -442,6 +520,7 @@ impl Default for WindowDescriptor { title: "bevy".to_string(), width: 1280., height: 720., + resize_constraints: WindowResizeConstraints::default(), scale_factor_override: None, vsync: true, resizable: true, diff --git a/crates/bevy_winit/Cargo.toml b/crates/bevy_winit/Cargo.toml index f9ef5394d147a..18a0b9c6a4899 100644 --- a/crates/bevy_winit/Cargo.toml +++ b/crates/bevy_winit/Cargo.toml @@ -21,6 +21,7 @@ x11 = ["winit/x11"] bevy_app = { path = "../bevy_app", version = "0.4.0" } bevy_ecs = { path = "../bevy_ecs", version = "0.4.0" } bevy_input = { path = "../bevy_input", version = "0.4.0" } +bevy_log = { path = "../bevy_log", version = "0.4.0" } bevy_math = { path = "../bevy_math", version = "0.4.0" } bevy_window = { path = "../bevy_window", version = "0.4.0" } bevy_utils = { path = "../bevy_utils", version = "0.4.0" } diff --git a/crates/bevy_winit/src/lib.rs b/crates/bevy_winit/src/lib.rs index 0f5d082a3739a..3afe0900ee549 100644 --- a/crates/bevy_winit/src/lib.rs +++ b/crates/bevy_winit/src/lib.rs @@ -25,6 +25,7 @@ use winit::{ event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget}, }; +use winit::dpi::LogicalSize; #[cfg(any( target_os = "linux", target_os = "dragonfly", @@ -139,6 +140,24 @@ fn change_window(_: &mut World, resources: &mut Resources) { y: position[1], }); } + bevy_window::WindowCommand::SetResizeConstraints { resize_constraints } => { + let window = winit_windows.get_window(id).unwrap(); + let constraints = resize_constraints.check_constraints(); + let min_inner_size = LogicalSize { + width: constraints.min_width, + height: constraints.min_height, + }; + let max_inner_size = LogicalSize { + width: constraints.max_width, + height: constraints.max_height, + }; + if constraints.max_width.is_finite() && constraints.max_height.is_finite() { + window.set_min_inner_size(Some(min_inner_size)); + window.set_max_inner_size(Some(max_inner_size)); + } else { + window.set_min_inner_size(Some(min_inner_size)); + } + } } } } diff --git a/crates/bevy_winit/src/winit_windows.rs b/crates/bevy_winit/src/winit_windows.rs index 736b98e762305..e3a3c518c0421 100644 --- a/crates/bevy_winit/src/winit_windows.rs +++ b/crates/bevy_winit/src/winit_windows.rs @@ -1,6 +1,7 @@ use bevy_math::IVec2; use bevy_utils::HashMap; use bevy_window::{Window, WindowDescriptor, WindowId, WindowMode}; +use winit::dpi::LogicalSize; #[derive(Debug, Default)] pub struct WinitWindows { @@ -59,6 +60,25 @@ impl WinitWindows { .with_decorations(window_descriptor.decorations), }; + let constraints = window_descriptor.resize_constraints.check_constraints(); + let min_inner_size = LogicalSize { + width: constraints.min_width, + height: constraints.min_height, + }; + let max_inner_size = LogicalSize { + width: constraints.max_width, + height: constraints.max_height, + }; + + let winit_window_builder = + if constraints.max_width.is_finite() && constraints.max_height.is_finite() { + winit_window_builder + .with_min_inner_size(min_inner_size) + .with_max_inner_size(max_inner_size) + } else { + winit_window_builder.with_min_inner_size(min_inner_size) + }; + #[allow(unused_mut)] let mut winit_window_builder = winit_window_builder.with_title(&window_descriptor.title);