diff --git a/crates/bevy_render/src/render_graph/nodes/window_texture_node.rs b/crates/bevy_render/src/render_graph/nodes/window_texture_node.rs index d287002ab4e295..0513bbce98cfc0 100644 --- a/crates/bevy_render/src/render_graph/nodes/window_texture_node.rs +++ b/crates/bevy_render/src/render_graph/nodes/window_texture_node.rs @@ -68,8 +68,8 @@ impl Node for WindowTextureNode { render_resource_context.remove_texture(old_texture); } - self.descriptor.size.width = window.physical_width(); - self.descriptor.size.height = window.physical_height(); + self.descriptor.size.width = window.physical_width().unwrap(); + self.descriptor.size.height = window.physical_height().unwrap(); let texture_resource = render_resource_context.create_texture(self.descriptor); output.set(WINDOW_TEXTURE, RenderResourceId::Texture(texture_resource)); } diff --git a/crates/bevy_wgpu/src/wgpu_type_converter.rs b/crates/bevy_wgpu/src/wgpu_type_converter.rs index e6e176263e0454..ea03516681ca2b 100644 --- a/crates/bevy_wgpu/src/wgpu_type_converter.rs +++ b/crates/bevy_wgpu/src/wgpu_type_converter.rs @@ -564,8 +564,8 @@ impl WgpuFrom<&Window> for wgpu::SwapChainDescriptor { wgpu::SwapChainDescriptor { usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT, format: TextureFormat::default().wgpu_into(), - width: window.physical_width(), - height: window.physical_height(), + width: window.physical_width().unwrap(), + height: window.physical_height().unwrap(), present_mode: if window.vsync() { wgpu::PresentMode::Mailbox } else { diff --git a/crates/bevy_window/src/window.rs b/crates/bevy_window/src/window.rs index a296792917a73f..b56e729000a785 100644 --- a/crates/bevy_window/src/window.rs +++ b/crates/bevy_window/src/window.rs @@ -32,11 +32,57 @@ impl Default for WindowId { } } +/// The requested size of the window client area in logical pixels +#[derive(Debug, Clone, Copy)] +struct RequestedSize { + width: f32, + height: f32, +} + +/// The actual size of the window client area in physical pixels, and the +/// scaling factory needed to convert to logical pixels +#[derive(Debug, Clone, Copy)] +struct ActualSize { + physical_width: u32, + physical_height: u32, + scale_factor: f64, +} + +impl ActualSize { + fn logical_width(&self) -> f32 { + (self.physical_width as f64 / self.scale_factor) as f32 + } + + fn logical_height(&self) -> f32 { + (self.physical_height as f64 / self.scale_factor) as f32 + } +} + +/// An operating system window that can present content and receive user input. +/// +/// ## Window Sizes +/// +/// There are three sizes associated with a window. The physical size which is +/// the height and width in physical pixels on the monitor. The logical size +/// which is the physical size scaled by an operating system provided factor to +/// account for monitors with differing pixel densities or user preference. And +/// the requested size, measured in logical pixels, which is the value submitted +/// to the API when creating the window, or requesting that it be resized. +/// +/// The actual size, in logical pixels, of the window may not match the +/// requested size due to operating system limits on the window size, or the +/// quantization of the logical size when converting the physical size to the +/// logical size through the scaling factor. +/// +/// Prior to window creation, the actual size of the window cannot be known. If +/// an application requires exact values at startup, it should process the +/// [WindowResized](crate::WindowResized) event. The first one of which will be +/// delivered during the first frame. #[derive(Debug)] pub struct Window { id: WindowId, - physical_width: u32, - physical_height: u32, + requested_size: RequestedSize, + actual_size: Option, title: String, vsync: bool, resizable: bool, @@ -48,7 +94,6 @@ pub struct Window { #[cfg(target_arch = "wasm32")] pub canvas: Option, command_queue: Vec, - scale_factor: f64, } #[derive(Debug)] @@ -61,8 +106,7 @@ pub enum WindowCommand { title: String, }, SetResolution { - physical_width: u32, - physical_height: u32, + resolution: (f32, f32), }, SetVsync { vsync: bool, @@ -103,8 +147,11 @@ impl Window { pub fn new(id: WindowId, window_descriptor: &WindowDescriptor) -> Self { Window { id, - physical_height: window_descriptor.height, - physical_width: window_descriptor.width, + requested_size: RequestedSize { + width: window_descriptor.width, + height: window_descriptor.height, + }, + actual_size: None, title: window_descriptor.title.clone(), vsync: window_descriptor.vsync, resizable: window_descriptor.resizable, @@ -116,7 +163,6 @@ impl Window { #[cfg(target_arch = "wasm32")] canvas: window_descriptor.canvas.clone(), command_queue: Vec::new(), - scale_factor: 1.0, } } @@ -125,24 +171,68 @@ impl Window { self.id } + /// The current logical width of the window's client area. + /// + /// If the window has not been created yet, this will return + /// the [requested width](Window::requested_width) instead of + /// the [actual width](Window::logical_width). #[inline] pub fn width(&self) -> f32 { - (self.physical_width as f64 / self.scale_factor) as f32 + self.logical_width().unwrap_or(self.requested_width()) } + /// The current logical height of the window's client area. + /// + /// If the window has not been created yet, this will return + /// the [requested height](Window::requested_height) instead of + /// the [actual height](Window::logical_height). #[inline] pub fn height(&self) -> f32 { - (self.physical_height as f64 / self.scale_factor) as f32 + self.logical_height().unwrap_or(self.requested_height()) + } + + /// The requested window client area width in logical pixels from window + /// creation or the last call to [set_resolution](Window::set_resolution). + /// + /// This may differ from the actual width depending on OS size limits and + /// the scaling factor for high DPI monitors. + #[inline] + pub fn requested_width(&self) -> f32 { + self.requested_size.width + } + + /// The requested window client area height in logical pixels from window + /// creation or the last call to [set_resolution](Window::set_resolution). + /// + /// This may differ from the actual width depending on OS size limits and + /// the scaling factor for high DPI monitors. + #[inline] + pub fn requested_height(&self) -> f32 { + self.requested_size.height } + /// The window's client area width in logical pixels if it has been created. #[inline] - pub fn physical_width(&self) -> u32 { - self.physical_width + pub fn logical_width(&self) -> Option { + self.actual_size.map(|s| s.logical_width()) } + /// The window's client area width in logical pixels if it has been created. #[inline] - pub fn physical_height(&self) -> u32 { - self.physical_height + pub fn logical_height(&self) -> Option { + self.actual_size.map(|s| s.logical_height()) + } + + /// The window's client area width in physical pixels if it has been created. + #[inline] + pub fn physical_width(&self) -> Option { + self.actual_size.map(|s| s.physical_width) + } + + /// The window's client area height in physical pixels if it has been created. + #[inline] + pub fn physical_height(&self) -> Option { + self.actual_size.map(|s| s.physical_height) } #[inline] @@ -151,31 +241,51 @@ impl Window { .push(WindowCommand::SetMaximized { maximized }); } + /// Request the OS to resize the window such the the client area matches the + /// specified width and height. pub fn set_resolution(&mut self, width: f32, height: f32) { - self.physical_width = (width as f64 * self.scale_factor) as u32; - self.physical_height = (height as f64 * self.scale_factor) as u32; + self.requested_size = RequestedSize { width, height }; self.command_queue.push(WindowCommand::SetResolution { - physical_width: self.physical_width, - physical_height: self.physical_height, + resolution: (self.requested_size.width, self.requested_size.height), }); } #[allow(missing_docs)] #[inline] - pub fn update_physical_size_from_backend(&mut self, width: u32, height: u32) { - self.physical_width = width; - self.physical_height = height; + pub fn update_actual_size_and_scale_from_backend( + &mut self, + width: u32, + height: u32, + scale: f64, + ) { + self.actual_size = Some(ActualSize { + physical_width: width, + physical_height: height, + scale_factor: scale, + }); } #[allow(missing_docs)] #[inline] - pub fn update_scale_factor_from_backend(&mut self, scale_factor: f64) { - self.scale_factor = scale_factor; - } - + pub fn update_actual_size_from_backend( + &mut self, + width: u32, + height: u32, + ) -> Option<(f32, f32)> { + self.actual_size.as_mut().map(|size| { + size.physical_width = width; + size.physical_height = height; + + (size.logical_width(), size.logical_height()) + }) + } + + /// The ratio of physical pixels to logical pixels if window has been created. + /// + /// `physical_pixels = logical_pixels * scale_factor` #[inline] - pub fn scale_factor(&self) -> f64 { - self.scale_factor + pub fn scale_factor(&self) -> Option { + self.actual_size.map(|s| s.scale_factor) } #[inline] @@ -267,9 +377,11 @@ impl Window { pub fn set_mode(&mut self, mode: WindowMode) { self.mode = mode; + let width = self.requested_size.width as u32; + let height = self.requested_size.height as u32; self.command_queue.push(WindowCommand::SetWindowMode { mode, - resolution: (self.physical_width, self.physical_height), + resolution: (width, height), }); } @@ -281,8 +393,8 @@ impl Window { #[derive(Debug, Clone)] pub struct WindowDescriptor { - pub width: u32, - pub height: u32, + pub width: f32, + pub height: f32, pub title: String, pub vsync: bool, pub resizable: bool, @@ -298,8 +410,8 @@ impl Default for WindowDescriptor { fn default() -> Self { WindowDescriptor { title: "bevy".to_string(), - width: 1280, - height: 720, + width: 1280., + height: 720., vsync: true, resizable: true, decorations: true, diff --git a/crates/bevy_winit/src/lib.rs b/crates/bevy_winit/src/lib.rs index 673e608d621740..d05d36c45a1995 100644 --- a/crates/bevy_winit/src/lib.rs +++ b/crates/bevy_winit/src/lib.rs @@ -68,13 +68,12 @@ fn change_window(_: &mut World, resources: &mut Resources) { window.set_title(&title); } bevy_window::WindowCommand::SetResolution { - physical_width, - physical_height, + resolution: (logical_width, logical_height), } => { let window = winit_windows.get_window(id).unwrap(); - window.set_inner_size(winit::dpi::PhysicalSize::new( - physical_width, - physical_height, + window.set_inner_size(winit::dpi::LogicalSize::new( + logical_width, + logical_height, )); } bevy_window::WindowCommand::SetVsync { .. } => (), @@ -204,15 +203,21 @@ pub fn winit_runner(mut app: App) { let mut windows = app.resources.get_mut::().unwrap(); let window_id = winit_windows.get_window_id(winit_window_id).unwrap(); let window = windows.get_mut(window_id).unwrap(); - window.update_physical_size_from_backend(size.width, size.height); - - let mut resize_events = - app.resources.get_mut::>().unwrap(); - resize_events.send(WindowResized { - id: window_id, - height: window.height(), - width: window.width(), - }); + if let Some((width, height)) = + window.update_actual_size_from_backend(size.width, size.height) + { + let mut resize_events = + app.resources.get_mut::>().unwrap(); + resize_events.send(WindowResized { + id: window_id, + width, + height, + }); + } else { + // this should not occur as we shouldn't receive resize + // events prior to window creation completing and + // learning of the scale factor + } } WindowEvent::CloseRequested => { let mut window_close_requested_events = app @@ -309,7 +314,8 @@ pub fn winit_runner(mut app: App) { // FIXME?: On Android window start is top while on PC/Linux/OSX on bottom if cfg!(target_os = "android") { - let window_height = windows.get_primary().unwrap().height(); + let window_height = + windows.get_primary().unwrap().logical_height().unwrap(); location.y = window_height - location.y; } touch_input_events.send(converters::convert_touch_input(touch, location)); @@ -336,11 +342,13 @@ pub fn winit_runner(mut app: App) { let mut windows = app.resources.get_mut::().unwrap(); let window_id = winit_windows.get_window_id(winit_window_id).unwrap(); let window = windows.get_mut(window_id).unwrap(); - window.update_physical_size_from_backend( + window.update_actual_size_and_scale_from_backend( new_inner_size.width, new_inner_size.height, + scale_factor, ); - window.update_scale_factor_from_backend(scale_factor); + // should we send a resize event to indicate the change in + // logical size? } WindowEvent::Focused(focused) => { let mut focused_events = diff --git a/crates/bevy_winit/src/winit_windows.rs b/crates/bevy_winit/src/winit_windows.rs index 75240017b00936..10d9f446f191c6 100644 --- a/crates/bevy_winit/src/winit_windows.rs +++ b/crates/bevy_winit/src/winit_windows.rs @@ -31,16 +31,16 @@ impl WinitWindows { winit::window::Fullscreen::Exclusive(match use_size { true => get_fitting_videomode( &event_loop.primary_monitor().unwrap(), - window.physical_width(), - window.physical_height(), + window.requested_width() as u32, + window.requested_height() as u32, ), false => get_best_videomode(&event_loop.primary_monitor().unwrap()), }), )), _ => winit_window_builder .with_inner_size(winit::dpi::LogicalSize::new( - window.width(), - window.height(), + window.requested_width(), + window.requested_height(), )) .with_resizable(window.resizable()) .with_decorations(window.decorations()), @@ -101,8 +101,12 @@ impl WinitWindows { } let inner_size = winit_window.inner_size(); - window.update_physical_size_from_backend(inner_size.width, inner_size.height); - window.update_scale_factor_from_backend(winit_window.scale_factor()); + let scale_factor = winit_window.scale_factor(); + window.update_actual_size_and_scale_from_backend( + inner_size.width, + inner_size.height, + scale_factor, + ); self.windows.insert(winit_window.id(), winit_window); } diff --git a/examples/tools/bevymark.rs b/examples/tools/bevymark.rs index 475d7224b9be83..fde32c40cbacc3 100644 --- a/examples/tools/bevymark.rs +++ b/examples/tools/bevymark.rs @@ -30,8 +30,8 @@ fn main() { App::build() .add_resource(WindowDescriptor { title: "BevyMark".to_string(), - width: 800, - height: 600, + width: 800., + height: 600., vsync: true, resizable: false, ..Default::default() @@ -85,8 +85,8 @@ fn mouse_handler( ) { if mouse_button_input.pressed(MouseButton::Left) { let spawn_count = (BIRDS_PER_SECOND as f32 * time.delta_seconds()) as u128; - let bird_x = (window.width as i32 / -2) as f32 + HALF_BIRD_SIZE; - let bird_y = (window.height / 2) as f32 - HALF_BIRD_SIZE; + let bird_x = (window.width / -2.) + HALF_BIRD_SIZE; + let bird_y = (window.height / 2.) - HALF_BIRD_SIZE; for count in 0..spawn_count { let bird_position = Vec3::new(bird_x, bird_y, (counter.count + count) as f32 * 0.00001); diff --git a/examples/wasm/winit_wasm.rs b/examples/wasm/winit_wasm.rs index c2c3ce8cd00866..14b48b1de2f29f 100644 --- a/examples/wasm/winit_wasm.rs +++ b/examples/wasm/winit_wasm.rs @@ -9,8 +9,8 @@ use bevy::{ fn main() { App::build() .add_resource(WindowDescriptor { - width: 300, - height: 300, + width: 300., + height: 300., ..Default::default() }) .add_plugins(DefaultPlugins) diff --git a/examples/window/multiple_windows.rs b/examples/window/multiple_windows.rs index e86cbd55e832cd..5eca81b4dc405a 100644 --- a/examples/window/multiple_windows.rs +++ b/examples/window/multiple_windows.rs @@ -35,8 +35,8 @@ fn setup( create_window_events.send(CreateWindow { id: window_id, descriptor: WindowDescriptor { - width: 800, - height: 600, + width: 800., + height: 600., vsync: false, title: "second window".to_string(), ..Default::default() diff --git a/examples/window/window_settings.rs b/examples/window/window_settings.rs index 17fefd6f7b52fa..0dc4fbd67ecfcf 100644 --- a/examples/window/window_settings.rs +++ b/examples/window/window_settings.rs @@ -5,8 +5,8 @@ fn main() { App::build() .add_resource(WindowDescriptor { title: "I am a window!".to_string(), - width: 500, - height: 300, + width: 500., + height: 300., vsync: true, resizable: false, ..Default::default()