diff --git a/crates/bevy_render/src/camera/camera.rs b/crates/bevy_render/src/camera/camera.rs index 8c0f3c814fcf7..9a873a9d31289 100644 --- a/crates/bevy_render/src/camera/camera.rs +++ b/crates/bevy_render/src/camera/camera.rs @@ -74,8 +74,8 @@ pub fn camera_system( } for (entity, mut camera, mut camera_projection) in &mut query.iter() { if let Some(window) = windows.get(camera.window) { - if changed_window_ids.contains(&window.id) || added_cameras.contains(&entity) { - camera_projection.update(window.width as usize, window.height as usize); + if changed_window_ids.contains(&window.id()) || added_cameras.contains(&entity) { + camera_projection.update(window.width() as usize, window.height() as usize); camera.projection_matrix = camera_projection.get_projection_matrix(); camera.depth_calculation = camera_projection.depth_calculation(); } diff --git a/crates/bevy_render/src/render_graph/nodes/window_swapchain_node.rs b/crates/bevy_render/src/render_graph/nodes/window_swapchain_node.rs index a1401e5a68457..1c700c4d8c818 100644 --- a/crates/bevy_render/src/render_graph/nodes/window_swapchain_node.rs +++ b/crates/bevy_render/src/render_graph/nodes/window_swapchain_node.rs @@ -56,11 +56,11 @@ impl Node for WindowSwapChainNode { // create window swapchain when window is resized or created if self .window_created_event_reader - .find_latest(&window_created_events, |e| e.id == window.id) + .find_latest(&window_created_events, |e| e.id == window.id()) .is_some() || self .window_resized_event_reader - .find_latest(&window_resized_events, |e| e.id == window.id) + .find_latest(&window_resized_events, |e| e.id == window.id()) .is_some() { render_resource_context.create_swap_chain(window); 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 ba0ae467ff261..dc6ee403f3ed8 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 @@ -56,11 +56,11 @@ impl Node for WindowTextureNode { if self .window_created_event_reader - .find_latest(&window_created_events, |e| e.id == window.id) + .find_latest(&window_created_events, |e| e.id == window.id()) .is_some() || self .window_resized_event_reader - .find_latest(&window_resized_events, |e| e.id == window.id) + .find_latest(&window_resized_events, |e| e.id == window.id()) .is_some() { let render_resource_context = render_context.resources_mut(); @@ -68,8 +68,8 @@ impl Node for WindowTextureNode { render_resource_context.remove_texture(old_texture); } - self.descriptor.size.width = window.width; - self.descriptor.size.height = window.height; + self.descriptor.size.width = window.width(); + self.descriptor.size.height = window.height(); let texture_resource = render_resource_context.create_texture(self.descriptor); output.set(WINDOW_TEXTURE, RenderResourceId::Texture(texture_resource)); } diff --git a/crates/bevy_ui/src/flex/mod.rs b/crates/bevy_ui/src/flex/mod.rs index ee7967929ec0f..760fad6c690ae 100644 --- a/crates/bevy_ui/src/flex/mod.rs +++ b/crates/bevy_ui/src/flex/mod.rs @@ -105,7 +105,7 @@ impl FlexSurface { pub fn update_window(&mut self, window: &Window) { let stretch = &mut self.stretch; - let node = self.window_nodes.entry(window.id).or_insert_with(|| { + let node = self.window_nodes.entry(window.id()).or_insert_with(|| { stretch .new_node(stretch::style::Style::default(), Vec::new()) .unwrap() @@ -116,8 +116,8 @@ impl FlexSurface { *node, stretch::style::Style { size: stretch::geometry::Size { - width: stretch::style::Dimension::Points(window.width as f32), - height: stretch::style::Dimension::Points(window.height as f32), + width: stretch::style::Dimension::Points(window.width() as f32), + height: stretch::style::Dimension::Points(window.height() as f32), }, ..Default::default() }, @@ -189,7 +189,7 @@ pub fn flex_node_system( // update window children (for now assuming all Nodes live in the primary window) if let Some(primary_window) = windows.get_primary() { - flex_surface.set_window_children(primary_window.id, root_node_query.iter().iter()); + flex_surface.set_window_children(primary_window.id(), root_node_query.iter().iter()); } // update children diff --git a/crates/bevy_wgpu/src/renderer/wgpu_render_resource_context.rs b/crates/bevy_wgpu/src/renderer/wgpu_render_resource_context.rs index 0f89affbe8b74..d6b1f70d176d5 100644 --- a/crates/bevy_wgpu/src/renderer/wgpu_render_resource_context.rs +++ b/crates/bevy_wgpu/src/renderer/wgpu_render_resource_context.rs @@ -274,22 +274,25 @@ impl RenderResourceContext for WgpuRenderResourceContext { let swap_chain_descriptor: wgpu::SwapChainDescriptor = window.wgpu_into(); let surface = surfaces - .get(&window.id) + .get(&window.id()) .expect("No surface found for window"); let swap_chain = self .device .create_swap_chain(surface, &swap_chain_descriptor); - window_swap_chains.insert(window.id, swap_chain); + window_swap_chains.insert(window.id(), swap_chain); } fn next_swap_chain_texture(&self, window: &bevy_window::Window) -> TextureId { - if let Some(texture_id) = self.try_next_swap_chain_texture(window.id) { + if let Some(texture_id) = self.try_next_swap_chain_texture(window.id()) { texture_id } else { - self.resources.window_swap_chains.write().remove(&window.id); + self.resources + .window_swap_chains + .write() + .remove(&window.id()); self.create_swap_chain(window); - self.try_next_swap_chain_texture(window.id) + self.try_next_swap_chain_texture(window.id()) .expect("Failed to acquire next swap chain texture!") } } diff --git a/crates/bevy_wgpu/src/wgpu_renderer.rs b/crates/bevy_wgpu/src/wgpu_renderer.rs index 49e6663d33d24..31803de4b21f5 100644 --- a/crates/bevy_wgpu/src/wgpu_renderer.rs +++ b/crates/bevy_wgpu/src/wgpu_renderer.rs @@ -74,9 +74,9 @@ impl WgpuRenderer { #[cfg(feature = "bevy_winit")] { let winit_windows = resources.get::().unwrap(); - let winit_window = winit_windows.get_window(window.id).unwrap(); + let winit_window = winit_windows.get_window(window.id()).unwrap(); let surface = unsafe { self.instance.create_surface(winit_window.deref()) }; - render_resource_context.set_window_surface(window.id, surface); + render_resource_context.set_window_surface(window.id(), surface); } } } diff --git a/crates/bevy_wgpu/src/wgpu_type_converter.rs b/crates/bevy_wgpu/src/wgpu_type_converter.rs index 7a27620eadc34..077e50dc7fd1b 100644 --- a/crates/bevy_wgpu/src/wgpu_type_converter.rs +++ b/crates/bevy_wgpu/src/wgpu_type_converter.rs @@ -563,9 +563,9 @@ impl WgpuFrom<&Window> for wgpu::SwapChainDescriptor { wgpu::SwapChainDescriptor { usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT, format: wgpu::TextureFormat::Bgra8UnormSrgb, - width: window.width, - height: window.height, - present_mode: if window.vsync { + width: window.width(), + height: window.height(), + present_mode: if window.vsync() { wgpu::PresentMode::Fifo } else { wgpu::PresentMode::Immediate diff --git a/crates/bevy_window/src/window.rs b/crates/bevy_window/src/window.rs index 45c82f61c8267..fba0b806c9f8d 100644 --- a/crates/bevy_window/src/window.rs +++ b/crates/bevy_window/src/window.rs @@ -33,16 +33,41 @@ impl Default for WindowId { #[derive(Debug)] pub struct Window { - pub id: WindowId, - pub width: u32, - pub height: u32, - pub title: String, - pub vsync: bool, - pub resizable: bool, - pub decorations: bool, - pub mode: WindowMode, + id: WindowId, + width: u32, + height: u32, + title: String, + vsync: bool, + resizable: bool, + decorations: bool, + mode: WindowMode, #[cfg(target_arch = "wasm32")] pub canvas: Option, + command_queue: Vec, +} + +#[derive(Debug)] +pub enum WindowCommand { + SetWindowMode { + mode: WindowMode, + resolution: (u32, u32), + }, + SetTitle { + title: String, + }, + SetResolution { + width: u32, + height: u32, + }, + SetVsync { + vsync: bool, + }, + SetResizable { + resizable: bool, + }, + SetDecorations { + decorations: bool, + }, } /// Defines the way a window is displayed @@ -70,8 +95,91 @@ impl Window { mode: window_descriptor.mode, #[cfg(target_arch = "wasm32")] canvas: window_descriptor.canvas.clone(), + command_queue: Vec::new(), } } + + #[inline] + pub fn id(&self) -> WindowId { + self.id + } + + #[inline] + pub fn width(&self) -> u32 { + self.width + } + + #[inline] + pub fn height(&self) -> u32 { + self.height + } + + pub fn set_resolution(&mut self, width: u32, height: u32) { + self.width = width; + self.height = height; + self.command_queue + .push(WindowCommand::SetResolution { width, height }); + } + + #[doc(hidden)] + pub fn update_resolution_from_backend(&mut self, width: u32, height: u32) { + self.width = width; + self.height = height; + } + + pub fn title(&self) -> &str { + &self.title + } + + pub fn set_title(&mut self, title: String) { + self.title = title.to_string(); + self.command_queue.push(WindowCommand::SetTitle { title }); + } + + pub fn vsync(&self) -> bool { + self.vsync + } + + pub fn set_vsync(&mut self, vsync: bool) { + self.vsync = vsync; + self.command_queue.push(WindowCommand::SetVsync { vsync }); + } + + pub fn resizable(&self) -> bool { + self.resizable + } + + pub fn set_resizable(&mut self, resizable: bool) { + self.resizable = resizable; + self.command_queue + .push(WindowCommand::SetResizable { resizable }); + } + + pub fn decorations(&self) -> bool { + self.decorations + } + + pub fn set_decorations(&mut self, decorations: bool) { + self.decorations = decorations; + self.command_queue + .push(WindowCommand::SetDecorations { decorations }); + } + + pub fn mode(&self) -> WindowMode { + self.mode + } + + pub fn set_mode(&mut self, mode: WindowMode) { + self.mode = mode; + self.command_queue.push(WindowCommand::SetWindowMode { + mode, + resolution: (self.width, self.height), + }); + } + + pub fn drain_commands<'a>(&'a mut self) -> impl Iterator + 'a { + self.command_queue.drain(..) + } } #[derive(Debug, Clone)] diff --git a/crates/bevy_window/src/windows.rs b/crates/bevy_window/src/windows.rs index 8345a14362bd9..a669799b572f9 100644 --- a/crates/bevy_window/src/windows.rs +++ b/crates/bevy_window/src/windows.rs @@ -8,7 +8,7 @@ pub struct Windows { impl Windows { pub fn add(&mut self, window: Window) { - self.windows.insert(window.id, window); + self.windows.insert(window.id(), window); } pub fn get(&self, id: WindowId) -> Option<&Window> { @@ -23,7 +23,15 @@ impl Windows { self.get(WindowId::primary()) } + pub fn get_primary_mut(&mut self) -> Option<&mut Window> { + self.get_mut(WindowId::primary()) + } + pub fn iter(&self) -> impl Iterator { self.windows.values() } + + pub fn iter_mut(&mut self) -> impl Iterator { + self.windows.values_mut() + } } diff --git a/crates/bevy_winit/src/lib.rs b/crates/bevy_winit/src/lib.rs index f95544cdcd949..fc43772be9841 100644 --- a/crates/bevy_winit/src/lib.rs +++ b/crates/bevy_winit/src/lib.rs @@ -9,7 +9,7 @@ pub use winit_config::*; pub use winit_windows::*; use bevy_app::{prelude::*, AppExit}; -use bevy_ecs::Resources; +use bevy_ecs::{IntoThreadLocalSystem, Resources, World}; use bevy_math::Vec2; use bevy_window::{ CreateWindow, CursorMoved, Window, WindowCloseRequested, WindowCreated, WindowResized, Windows, @@ -29,7 +29,60 @@ impl Plugin for WinitPlugin { // stopping us. there are plans to remove the lifetime: https://github.com/rust-windowing/winit/pull/1456 // .add_event::() .init_resource::() - .set_runner(winit_runner); + .set_runner(winit_runner) + .add_system(change_window.thread_local_system()); + } +} + +fn change_window(_: &mut World, resources: &mut Resources) { + let winit_windows = resources.get::().unwrap(); + let mut windows = resources.get_mut::().unwrap(); + + for bevy_window in windows.iter_mut() { + let id = bevy_window.id(); + for command in bevy_window.drain_commands() { + match command { + bevy_window::WindowCommand::SetWindowMode { + mode, + resolution: (width, height), + } => { + let window = winit_windows.get_window(id).unwrap(); + match mode { + bevy_window::WindowMode::BorderlessFullscreen => { + window.set_fullscreen(Some(winit::window::Fullscreen::Borderless(None))) + } + bevy_window::WindowMode::Fullscreen { use_size } => window.set_fullscreen( + Some(winit::window::Fullscreen::Exclusive(match use_size { + true => get_fitting_videomode( + &window.current_monitor().unwrap(), + width, + height, + ), + false => get_best_videomode(&window.current_monitor().unwrap()), + })), + ), + bevy_window::WindowMode::Windowed => window.set_fullscreen(None), + } + } + bevy_window::WindowCommand::SetTitle { title } => { + let window = winit_windows.get_window(id).unwrap(); + window.set_title(&title); + } + bevy_window::WindowCommand::SetResolution { width, height } => { + let window = winit_windows.get_window(id).unwrap(); + window.set_inner_size(winit::dpi::PhysicalSize::new(width, height)); + } + bevy_window::WindowCommand::SetVsync { .. } => (), + bevy_window::WindowCommand::SetResizable { resizable } => { + let window = winit_windows.get_window(id).unwrap(); + window.set_resizable(resizable); + } + bevy_window::WindowCommand::SetDecorations { decorations } => { + let window = winit_windows.get_window(id).unwrap(); + window.set_decorations(decorations); + } + } + } } } @@ -118,15 +171,14 @@ pub fn winit_runner(mut app: App) { let winit_windows = app.resources.get_mut::().unwrap(); let mut windows = app.resources.get_mut::().unwrap(); let window_id = winit_windows.get_window_id(winit_window_id).unwrap(); - let mut window = windows.get_mut(window_id).unwrap(); - window.width = size.width; - window.height = size.height; + let window = windows.get_mut(window_id).unwrap(); + window.update_resolution_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 as usize, - width: window.width as usize, + height: window.height() as usize, + width: window.width() as usize, }); } event::Event::WindowEvent { @@ -231,7 +283,7 @@ fn handle_create_window_events( for create_window_event in create_window_event_reader.iter(&create_window_events) { let window = Window::new(create_window_event.id, &create_window_event.descriptor); winit_windows.create_window(event_loop, &window); - let window_id = window.id; + let window_id = window.id(); windows.add(window); window_created_events.send(WindowCreated { id: window_id }); } diff --git a/crates/bevy_winit/src/winit_windows.rs b/crates/bevy_winit/src/winit_windows.rs index e73a98aa797b8..2ee217a99dfea 100644 --- a/crates/bevy_winit/src/winit_windows.rs +++ b/crates/bevy_winit/src/winit_windows.rs @@ -23,24 +23,31 @@ impl WinitWindows { #[cfg(not(target_os = "windows"))] let mut winit_window_builder = winit::window::WindowBuilder::new(); - winit_window_builder = match window.mode { + winit_window_builder = match window.mode() { WindowMode::BorderlessFullscreen => winit_window_builder.with_fullscreen(Some( winit::window::Fullscreen::Borderless(event_loop.primary_monitor()), )), WindowMode::Fullscreen { use_size } => winit_window_builder.with_fullscreen(Some( winit::window::Fullscreen::Exclusive(match use_size { - true => get_fitting_videomode(&event_loop.primary_monitor().unwrap(), &window), + true => get_fitting_videomode( + &event_loop.primary_monitor().unwrap(), + window.width(), + window.height(), + ), false => get_best_videomode(&event_loop.primary_monitor().unwrap()), }), )), _ => winit_window_builder - .with_inner_size(winit::dpi::PhysicalSize::new(window.width, window.height)) - .with_resizable(window.resizable) - .with_decorations(window.decorations), + .with_inner_size(winit::dpi::PhysicalSize::new( + window.width(), + window.height(), + )) + .with_resizable(window.resizable()) + .with_decorations(window.decorations()), }; #[allow(unused_mut)] - let mut winit_window_builder = winit_window_builder.with_title(&window.title); + let mut winit_window_builder = winit_window_builder.with_title(window.title()); #[cfg(target_arch = "wasm32")] { @@ -64,8 +71,10 @@ impl WinitWindows { let winit_window = winit_window_builder.build(&event_loop).unwrap(); - self.window_id_to_winit.insert(window.id, winit_window.id()); - self.winit_to_window_id.insert(winit_window.id(), window.id); + self.window_id_to_winit + .insert(window.id(), winit_window.id()); + self.winit_to_window_id + .insert(winit_window.id(), window.id()); #[cfg(target_arch = "wasm32")] { @@ -96,9 +105,10 @@ impl WinitWindows { self.winit_to_window_id.get(&id).cloned() } } -fn get_fitting_videomode( +pub fn get_fitting_videomode( monitor: &winit::monitor::MonitorHandle, - window: &Window, + width: u32, + height: u32, ) -> winit::monitor::VideoMode { let mut modes = monitor.video_modes().collect::>(); @@ -111,11 +121,9 @@ fn get_fitting_videomode( modes.sort_by(|a, b| { use std::cmp::Ordering::*; - match abs_diff(a.size().width, window.width).cmp(&abs_diff(b.size().width, window.width)) { + match abs_diff(a.size().width, width).cmp(&abs_diff(b.size().width, width)) { Equal => { - match abs_diff(a.size().height, window.height) - .cmp(&abs_diff(b.size().height, window.height)) - { + match abs_diff(a.size().height, height).cmp(&abs_diff(b.size().height, height)) { Equal => b.refresh_rate().cmp(&a.refresh_rate()), default => default, } @@ -127,7 +135,7 @@ fn get_fitting_videomode( modes.first().unwrap().clone() } -fn get_best_videomode(monitor: &winit::monitor::MonitorHandle) -> winit::monitor::VideoMode { +pub fn get_best_videomode(monitor: &winit::monitor::MonitorHandle) -> winit::monitor::VideoMode { let mut modes = monitor.video_modes().collect::>(); modes.sort_by(|a, b| { use std::cmp::Ordering::*; diff --git a/examples/ecs/parallel_query.rs b/examples/ecs/parallel_query.rs index 65981a18c8875..a00b91e156dda 100644 --- a/examples/ecs/parallel_query.rs +++ b/examples/ecs/parallel_query.rs @@ -48,11 +48,13 @@ fn bounce_system( windows: Res, mut sprites: Query<(&Transform, &mut Velocity)>, ) { - let Window { width, height, .. } = windows.get_primary().expect("No primary window"); - let left = *width as f32 / -2.0; - let right = *width as f32 / 2.0; - let bottom = *height as f32 / -2.0; - let top = *height as f32 / 2.0; + let window = windows.get_primary().expect("No primary window"); + let width = window.width(); + let height = window.height(); + let left = width as f32 / -2.0; + let right = width as f32 / 2.0; + let bottom = height as f32 / -2.0; + let top = height as f32 / 2.0; sprites .iter() // Batch size of 32 is chosen to limit the overhead of diff --git a/examples/window/window_settings.rs b/examples/window/window_settings.rs index f1df55cf8c7ae..2930c0081d530 100644 --- a/examples/window/window_settings.rs +++ b/examples/window/window_settings.rs @@ -5,12 +5,22 @@ fn main() { App::build() .add_resource(WindowDescriptor { title: "I am a window!".to_string(), - width: 300, + width: 500, height: 300, vsync: true, resizable: false, ..Default::default() }) .add_default_plugins() + .add_system(change_title.system()) .run(); } + +/// This system will then change the title during execution +fn change_title(time: Res