diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index bf40e13fb98b1..8f45cd26204ae 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -62,6 +62,12 @@ impl<'w, 's> Commands<'w, 's> { Self { queue, entities } } + // TODO: Can this be solved in any other way? + /// Returns true if this commands has the entity present + pub fn has_entity(&self, entity: Entity) -> bool { + return self.entities.contains(entity); + } + /// Creates a new empty [`Entity`] and returns an [`EntityCommands`] builder for it. /// /// To directly spawn an entity with a [`Bundle`] included, you can use diff --git a/crates/bevy_render/src/camera/camera.rs b/crates/bevy_render/src/camera/camera.rs index 83f2a75a0ec07..8a792a1da86dc 100644 --- a/crates/bevy_render/src/camera/camera.rs +++ b/crates/bevy_render/src/camera/camera.rs @@ -12,6 +12,7 @@ use bevy_ecs::{ component::Component, entity::Entity, event::EventReader, + prelude::With, query::Added, reflect::ReflectComponent, system::{Commands, ParamSet, Query, Res}, @@ -20,7 +21,7 @@ use bevy_math::{Mat4, UVec2, Vec2, Vec3}; use bevy_reflect::prelude::*; use bevy_transform::components::GlobalTransform; use bevy_utils::HashSet; -use bevy_window::{WindowCreated, WindowId, WindowResized, Windows}; +use bevy_window::{Window, WindowCreated, WindowResized, WindowResolution}; use serde::{Deserialize, Serialize}; use std::{borrow::Cow, ops::Range}; use wgpu::Extent3d; @@ -97,7 +98,7 @@ impl Default for Camera { priority: 0, viewport: None, computed: Default::default(), - target: Default::default(), + target: RenderTarget::PrimaryWindow, depth_calculation: Default::default(), } } @@ -220,15 +221,17 @@ impl CameraRenderGraph { /// swapchain or an [`Image`]. #[derive(Debug, Clone, Reflect, PartialEq, Eq, Hash, PartialOrd, Ord)] pub enum RenderTarget { + /// Renders the camera's view to the current primary window + PrimaryWindow, /// Window to which the camera's view is rendered. - Window(WindowId), + Window(Entity), /// Image to which the camera's view is rendered. Image(Handle), } impl Default for RenderTarget { fn default() -> Self { - Self::Window(Default::default()) + Self::PrimaryWindow } } @@ -245,20 +248,28 @@ impl RenderTarget { RenderTarget::Image(image_handle) => { images.get(image_handle).map(|image| &image.texture_view) } + RenderTarget::PrimaryWindow => todo!(), } } pub fn get_render_target_info( &self, - windows: &Windows, + windows: &Query<&WindowResolution, With>, // TODO: Maybe this could just be a Vec? images: &Assets, ) -> Option { Some(match self { RenderTarget::Window(window_id) => { - let window = windows.get(*window_id)?; - RenderTargetInfo { - physical_size: UVec2::new(window.physical_width(), window.physical_height()), - scale_factor: window.scale_factor(), + if let Ok(resolution) = windows.get(*window_id) { + RenderTargetInfo { + physical_size: UVec2::new( + resolution.physical_width(), + resolution.physical_height(), + ), + scale_factor: resolution.scale_factor(), + } + } else { + // TODO: Helpful panic comment + panic!("Render target does not point to a valid window"); } } RenderTarget::Image(image_handle) => { @@ -269,17 +280,20 @@ impl RenderTarget { scale_factor: 1.0, } } + RenderTarget::PrimaryWindow => todo!(), }) } + // Check if this render target is contained in the given changed windows or images. fn is_changed( &self, - changed_window_ids: &[WindowId], + changed_window_ids: &[Entity], changed_image_handles: &HashSet<&Handle>, ) -> bool { match self { RenderTarget::Window(window_id) => changed_window_ids.contains(window_id), RenderTarget::Image(image_handle) => changed_image_handles.contains(&image_handle), + RenderTarget::PrimaryWindow => todo!(), } } } @@ -303,32 +317,32 @@ pub fn camera_system( mut window_resized_events: EventReader, mut window_created_events: EventReader, mut image_asset_events: EventReader>, - windows: Res, + windows: Query<&WindowResolution, With>, images: Res>, mut queries: ParamSet<( Query<(Entity, &mut Camera, &mut T)>, Query>, )>, ) { - let mut changed_window_ids = Vec::new(); + let mut changed_window_ids: Vec = Vec::new(); // handle resize events. latest events are handled first because we only want to resize each // window once for event in window_resized_events.iter().rev() { - if changed_window_ids.contains(&event.id) { + if changed_window_ids.contains(&event.entity) { continue; } - changed_window_ids.push(event.id); + changed_window_ids.push(event.entity); } // handle resize events. latest events are handled first because we only want to resize each // window once for event in window_created_events.iter().rev() { - if changed_window_ids.contains(&event.id) { + if changed_window_ids.contains(&event.entity) { continue; } - changed_window_ids.push(event.id); + changed_window_ids.push(event.entity); } let changed_image_handles: HashSet<&Handle> = image_asset_events diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 888d0d6865b96..8a5c0ffebb0c0 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -27,7 +27,9 @@ pub mod prelude { }; } +use bevy_window::{PrimaryWindow, Window, WindowHandle}; pub use once_cell; +use settings::WgpuSettings; use crate::{ camera::CameraPlugin, @@ -42,8 +44,9 @@ use crate::{ }; use bevy_app::{App, AppLabel, Plugin}; use bevy_asset::{AddAsset, AssetServer}; -use bevy_ecs::prelude::*; +use bevy_ecs::{prelude::*, system::SystemState}; use bevy_utils::tracing::debug; +use core::panic; use std::ops::{Deref, DerefMut}; /// Contains the default Bevy rendering backend based on wgpu. @@ -114,27 +117,58 @@ struct ScratchRenderWorld(World); impl Plugin for RenderPlugin { /// Initializes the renderer, sets up the [`RenderStage`](RenderStage) and creates the rendering sub-app. fn build(&self, app: &mut App) { + app.add_asset::() + .add_debug_asset::() + .init_asset_loader::() + .init_debug_asset_loader::() + .register_type::(); + let options = app .world .get_resource::() .cloned() .unwrap_or_default(); - app.add_asset::() - .add_debug_asset::() - .init_asset_loader::() - .init_debug_asset_loader::() - .register_type::(); + bevy_utils::tracing::info!("Tryin to request primarywindow"); + + let mut system_state: SystemState<( + Query<&WindowHandle, With>, + Res, + // Res, + )> = SystemState::new(&mut app.world); + let ( + window_query, + primary_window, + // options, // This was .clone().unwrap_or_default(). Will this work the same? + ) = system_state.get(&mut app.world); + + bevy_utils::tracing::info!("Should have resource here"); if let Some(backends) = options.backends { let instance = wgpu::Instance::new(backends); let surface = { - let windows = app.world.resource_mut::(); - let raw_handle = windows.get_primary().map(|window| unsafe { - let handle = window.raw_window_handle().get_handle(); - instance.create_surface(&handle) - }); - raw_handle + // TODO: This can probably be cleaner + if let Ok(handle_component) = window_query.get( + primary_window + .window + .expect("There should be a primary window"), + ) { + // TODO: Make sure this is ok + unsafe { + let handle = handle_component.raw_window_handle().get_handle(); + Some(instance.create_surface(&handle)) + } + } else { + // TODO: Helpful panic comment + panic!("No WindowHandle component on primary window"); + } + // let handle_component = + // // let windows = app.world.resource_mut::(); + // let raw_handle = windows.get_primary().map(|window| unsafe { + // let handle = window.raw_window_handle().get_handle(); + // instance.create_surface(&handle) + // }); + // raw_handle }; let request_adapter_options = wgpu::RequestAdapterOptions { power_preference: options.power_preference, diff --git a/crates/bevy_render/src/view/window.rs b/crates/bevy_render/src/view/window.rs index 305a56ddd115b..b93082de3dd86 100644 --- a/crates/bevy_render/src/view/window.rs +++ b/crates/bevy_render/src/view/window.rs @@ -7,7 +7,10 @@ use crate::{ use bevy_app::{App, Plugin}; use bevy_ecs::prelude::*; use bevy_utils::{tracing::debug, HashMap, HashSet}; -use bevy_window::{PresentMode, RawWindowHandleWrapper, WindowClosed, WindowId, Windows}; +use bevy_window::{ + PresentMode, RawWindowHandleWrapper, Window, WindowClosed, WindowHandle, WindowPresentation, + WindowResolution, +}; use std::ops::{Deref, DerefMut}; use wgpu::TextureFormat; @@ -39,7 +42,7 @@ impl Plugin for WindowRenderPlugin { } pub struct ExtractedWindow { - pub id: WindowId, + pub id: Entity, pub handle: RawWindowHandleWrapper, pub physical_width: u32, pub physical_height: u32, @@ -50,11 +53,11 @@ pub struct ExtractedWindow { #[derive(Default)] pub struct ExtractedWindows { - pub windows: HashMap, + pub windows: HashMap, } impl Deref for ExtractedWindows { - type Target = HashMap; + type Target = HashMap; fn deref(&self) -> &Self::Target { &self.windows @@ -70,27 +73,32 @@ impl DerefMut for ExtractedWindows { fn extract_windows( mut render_world: ResMut, mut closed: EventReader, - windows: Res, + windows: Query< + ( + Entity, + &WindowResolution, + &WindowHandle, + &WindowPresentation, + ), + With, + >, ) { let mut extracted_windows = render_world.get_resource_mut::().unwrap(); - for window in windows.iter() { + for (entity, resolution, handle, presentation) in windows.iter() { let (new_width, new_height) = ( - window.physical_width().max(1), - window.physical_height().max(1), + resolution.physical_width().max(1), + resolution.physical_height().max(1), ); - let mut extracted_window = - extracted_windows - .entry(window.id()) - .or_insert(ExtractedWindow { - id: window.id(), - handle: window.raw_window_handle(), - physical_width: new_width, - physical_height: new_height, - present_mode: window.present_mode(), - swap_chain_texture: None, - size_changed: false, - }); + let mut extracted_window = extracted_windows.entry(entity).or_insert(ExtractedWindow { + id: entity, + handle: handle.raw_window_handle(), + physical_width: new_width, + physical_height: new_height, + present_mode: presentation.present_mode(), + swap_chain_texture: None, + size_changed: false, + }); // NOTE: Drop the swap chain frame here extracted_window.swap_chain_texture = None; @@ -110,15 +118,15 @@ fn extract_windows( } } for closed_window in closed.iter() { - extracted_windows.remove(&closed_window.id); + extracted_windows.remove(&closed_window.entity); } } #[derive(Default)] pub struct WindowSurfaces { - surfaces: HashMap, + surfaces: HashMap, /// List of windows that we have already called the initial `configure_surface` for - configured_windows: HashSet, + configured_windows: HashSet, } pub fn prepare_windows( diff --git a/crates/bevy_text/src/text2d.rs b/crates/bevy_text/src/text2d.rs index d0bebbad3609a..368c0fb2407da 100644 --- a/crates/bevy_text/src/text2d.rs +++ b/crates/bevy_text/src/text2d.rs @@ -4,6 +4,7 @@ use bevy_ecs::{ component::Component, entity::Entity, event::EventReader, + prelude::With, query::Changed, reflect::ReflectComponent, system::{Local, Query, Res, ResMut}, @@ -14,7 +15,7 @@ use bevy_render::{texture::Image, view::Visibility, RenderWorld}; use bevy_sprite::{Anchor, ExtractedSprite, ExtractedSprites, TextureAtlas}; use bevy_transform::prelude::{GlobalTransform, Transform}; use bevy_utils::HashSet; -use bevy_window::{WindowId, WindowScaleFactorChanged, Windows}; +use bevy_window::{PrimaryWindow, Window, WindowResolution, WindowScaleFactorChanged}; use crate::{ DefaultTextPipeline, Font, FontAtlasSet, HorizontalAlign, Text, TextError, VerticalAlign, @@ -64,12 +65,16 @@ pub fn extract_text2d_sprite( mut render_world: ResMut, texture_atlases: Res>, text_pipeline: Res, - windows: Res, + primary_window: Res, + windows: Query<&WindowResolution, With>, text2d_query: Query<(Entity, &Visibility, &Text, &GlobalTransform, &Text2dSize)>, ) { let mut extracted_sprites = render_world.resource_mut::(); - let scale_factor = windows.scale_factor(WindowId::primary()) as f32; + let resolution = windows + .get(primary_window.window.expect("Primary window should exist")) + .expect("Primary windows should have a valid WindowResolution component"); + let scale_factor = resolution.scale_factor() as f32; for (entity, visibility, text, transform, calculated_size) in text2d_query.iter() { if !visibility.is_visible { @@ -133,7 +138,8 @@ pub fn update_text2d_layout( mut queue: Local>, mut textures: ResMut>, fonts: Res>, - windows: Res, + primary_window: Res, + windows: Query<&WindowResolution, With>, mut scale_factor_changed: EventReader, mut texture_atlases: ResMut>, mut font_atlas_set_storage: ResMut>, @@ -148,7 +154,11 @@ pub fn update_text2d_layout( ) { // We need to consume the entire iterator, hence `last` let factor_changed = scale_factor_changed.iter().last().is_some(); - let scale_factor = windows.scale_factor(WindowId::primary()); + + let resolution = windows + .get(primary_window.window.expect("Primary window should exist")) + .expect("Primary windows should have a valid WindowResolution component"); + let scale_factor = resolution.scale_factor(); for (entity, text_changed, text, maybe_bounds, mut calculated_size) in text_query.iter_mut() { if factor_changed || text_changed || queue.remove(&entity) { diff --git a/crates/bevy_ui/src/flex/mod.rs b/crates/bevy_ui/src/flex/mod.rs index 607daaacbc26a..c02c02c58c4ce 100644 --- a/crates/bevy_ui/src/flex/mod.rs +++ b/crates/bevy_ui/src/flex/mod.rs @@ -12,13 +12,13 @@ use bevy_log::warn; use bevy_math::Vec2; use bevy_transform::components::Transform; use bevy_utils::HashMap; -use bevy_window::{Window, WindowId, WindowScaleFactorChanged, Windows}; +use bevy_window::{PrimaryWindow, Window, WindowResolution, WindowScaleFactorChanged}; use std::fmt; use stretch::{number::Number, Stretch}; pub struct FlexSurface { entity_to_stretch: HashMap, - window_nodes: HashMap, + window_nodes: HashMap, stretch: Stretch, } @@ -31,7 +31,7 @@ unsafe impl Sync for FlexSurface {} fn _assert_send_sync_flex_surface_impl_safe() { fn _assert_send_sync() {} _assert_send_sync::>(); - _assert_send_sync::>(); + _assert_send_sync::>(); // FIXME https://github.com/vislyhq/stretch/issues/69 // _assert_send_sync::(); } @@ -133,9 +133,9 @@ without UI components as a child of an entity with UI components, results may be .unwrap(); } - pub fn update_window(&mut self, window: &Window) { + pub fn update_window(&mut self, window_id: Entity, window_resolution: &WindowResolution) { 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() @@ -146,8 +146,12 @@ without UI components as a child of an entity with UI components, results may be *node, stretch::style::Style { size: stretch::geometry::Size { - width: stretch::style::Dimension::Points(window.physical_width() as f32), - height: stretch::style::Dimension::Points(window.physical_height() as f32), + width: stretch::style::Dimension::Points( + window_resolution.physical_width() as f32, + ), + height: stretch::style::Dimension::Points( + window_resolution.physical_height() as f32, + ), }, ..Default::default() }, @@ -157,7 +161,7 @@ without UI components as a child of an entity with UI components, results may be pub fn set_window_children( &mut self, - window_id: WindowId, + window_id: Entity, children: impl Iterator, ) { let stretch_node = self.window_nodes.get(&window_id).unwrap(); @@ -200,7 +204,8 @@ pub enum FlexError { #[allow(clippy::too_many_arguments)] pub fn flex_node_system( - windows: Res, + primary_window: Res, + windows: Query<(Entity, &WindowResolution), With>, mut scale_factor_events: EventReader, mut flex_surface: ResMut, root_node_query: Query, Without)>, @@ -214,12 +219,15 @@ pub fn flex_node_system( mut node_transform_query: Query<(Entity, &mut Node, &mut Transform, Option<&Parent>)>, ) { // update window root nodes - for window in windows.iter() { - flex_surface.update_window(window); + for (window_id, window_resolution) in windows.iter() { + flex_surface.update_window(window_id, window_resolution); } // assume one window for time being... - let logical_to_physical_factor = windows.scale_factor(WindowId::primary()); + let (_, primary_resolution) = windows + .get(primary_window.window.expect("Primary window should exist")) + .expect("Primary windows should have a valid WindowResolution component"); + let logical_to_physical_factor = primary_resolution.scale_factor(); if scale_factor_events.iter().next_back().is_some() { update_changed( @@ -254,8 +262,8 @@ pub fn flex_node_system( // TODO: handle removed nodes // 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()); + if let Some(primary_window_id) = primary_window.window { + flex_surface.set_window_children(primary_window_id, root_node_query.iter()); } // update children diff --git a/crates/bevy_ui/src/focus.rs b/crates/bevy_ui/src/focus.rs index 03a269c690dca..1b22c9c8e0805 100644 --- a/crates/bevy_ui/src/focus.rs +++ b/crates/bevy_ui/src/focus.rs @@ -1,7 +1,7 @@ use crate::{CalculatedClip, Node}; use bevy_ecs::{ entity::Entity, - prelude::Component, + prelude::{Component, With}, reflect::ReflectComponent, system::{Local, Query, Res}, }; @@ -10,7 +10,7 @@ use bevy_math::Vec2; use bevy_reflect::{Reflect, ReflectDeserialize}; use bevy_transform::components::GlobalTransform; use bevy_utils::FloatOrd; -use bevy_window::Windows; +use bevy_window::{PrimaryWindow, Window, WindowCursorPosition}; use serde::{Deserialize, Serialize}; use smallvec::SmallVec; @@ -59,7 +59,8 @@ pub struct State { /// The system that sets Interaction for all UI elements based on the mouse cursor activity pub fn ui_focus_system( mut state: Local, - windows: Res, + primary_window: Res, + cursor_positions: Query<&WindowCursorPosition, With>, mouse_button_input: Res>, touches_input: Res, mut node_query: Query<( @@ -71,9 +72,12 @@ pub fn ui_focus_system( Option<&CalculatedClip>, )>, ) { - let cursor_position = windows - .get_primary() - .and_then(|window| window.cursor_position()); + let primary_window_id = primary_window.window.expect("Primary window should exist"); + // Cursor position of primary window + let cursor_position = cursor_positions + .get(primary_window_id) + .expect("Primary window should have a valid WindowCursorPosition component") + .physical_cursor_position(); // reset entities that were both clicked and released in the last frame for entity in state.entities_to_reset.drain(..) { @@ -115,8 +119,8 @@ pub fn ui_focus_system( // if the current cursor position is within the bounds of the node, consider it for // clicking let contains_cursor = if let Some(cursor_position) = cursor_position { - (min.x..max.x).contains(&cursor_position.x) - && (min.y..max.y).contains(&cursor_position.y) + (min.x..max.x).contains(&(cursor_position.x as f32)) + && (min.y..max.y).contains(&(cursor_position.y as f32)) } else { false }; diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index b4bcb94e0a0c8..5b65d4e80bdf1 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -2,6 +2,7 @@ mod pipeline; mod render_pass; use bevy_core_pipeline::{core_2d::Camera2d, core_3d::Camera3d}; +use bevy_window::{PrimaryWindow, Window, WindowResolution}; pub use pipeline::*; pub use render_pass::*; @@ -28,7 +29,6 @@ use bevy_text::{DefaultTextPipeline, Text}; use bevy_transform::components::GlobalTransform; use bevy_utils::FloatOrd; use bevy_utils::HashMap; -use bevy_window::{WindowId, Windows}; use bytemuck::{Pod, Zeroable}; use std::ops::Range; @@ -274,7 +274,8 @@ pub fn extract_text_uinodes( mut render_world: ResMut, texture_atlases: Res>, text_pipeline: Res, - windows: Res, + primary_window: Res, + windows: Query<&WindowResolution, With>, uinode_query: Query<( Entity, &Node, @@ -286,7 +287,10 @@ pub fn extract_text_uinodes( ) { let mut extracted_uinodes = render_world.resource_mut::(); - let scale_factor = windows.scale_factor(WindowId::primary()) as f32; + let resolution = windows + .get(primary_window.window.expect("Primary window should exist")) + .expect("Primary windows should have a valid WindowResolution component"); + let scale_factor = resolution.scale_factor() as f32; for (entity, uinode, transform, text, visibility, clip) in uinode_query.iter() { if !visibility.is_visible { diff --git a/crates/bevy_ui/src/widget/text.rs b/crates/bevy_ui/src/widget/text.rs index 895c4a6fdee48..a6ea304a98611 100644 --- a/crates/bevy_ui/src/widget/text.rs +++ b/crates/bevy_ui/src/widget/text.rs @@ -9,7 +9,7 @@ use bevy_math::Vec2; use bevy_render::texture::Image; use bevy_sprite::TextureAtlas; use bevy_text::{DefaultTextPipeline, Font, FontAtlasSet, Text, TextError}; -use bevy_window::{WindowId, Windows}; +use bevy_window::{PrimaryWindow, Window, WindowResolution}; #[derive(Debug, Default)] pub struct QueuedText { @@ -42,7 +42,8 @@ pub fn text_system( mut last_scale_factor: Local, mut textures: ResMut>, fonts: Res>, - windows: Res, + primary_window: Res, + windows: Query<&WindowResolution, With>, mut texture_atlases: ResMut>, mut font_atlas_set_storage: ResMut>, mut text_pipeline: ResMut, @@ -52,7 +53,10 @@ pub fn text_system( Query<(&Text, &Style, &mut CalculatedSize)>, )>, ) { - let scale_factor = windows.scale_factor(WindowId::primary()); + let resolution = windows + .get(primary_window.window.expect("Primary window should exist")) + .expect("Primary windows should have a valid WindowResolution component"); + let scale_factor = resolution.scale_factor(); let inv_scale_factor = 1. / scale_factor; diff --git a/crates/bevy_window/src/event.rs b/crates/bevy_window/src/event.rs index 8452c482035ca..5bfd299a46158 100644 --- a/crates/bevy_window/src/event.rs +++ b/crates/bevy_window/src/event.rs @@ -1,12 +1,12 @@ use std::path::PathBuf; -use super::{WindowDescriptor, WindowId}; +use bevy_ecs::entity::Entity; use bevy_math::{IVec2, Vec2}; /// A window event that is sent whenever a windows logical size has changed #[derive(Debug, Clone)] pub struct WindowResized { - pub id: WindowId, + pub entity: Entity, /// The new logical width of the window pub width: f32, /// The new logical height of the window @@ -14,12 +14,13 @@ pub struct WindowResized { } /// An event that indicates that a new window should be created. -#[derive(Debug, Clone)] -pub struct CreateWindow { - pub id: WindowId, - pub descriptor: WindowDescriptor, -} +// #[derive(Debug, Clone)] +// pub struct CreateWindow { +// pub entity: Entity, +// pub descriptor: WindowDescriptor, +// } +// TODO: This would redraw all windows ? If yes, update docs to reflect this /// An event that indicates the window should redraw, even if its control flow is set to `Wait` and /// there have been no window events. #[derive(Debug, Clone)] @@ -31,7 +32,7 @@ pub struct RequestRedraw; /// event will be sent in the handler for that event. #[derive(Debug, Clone)] pub struct WindowCreated { - pub id: WindowId, + pub entity: Entity, } /// An event that is sent whenever the operating systems requests that a window @@ -47,7 +48,7 @@ pub struct WindowCreated { /// [closing]: crate::Window::close #[derive(Debug, Clone)] pub struct WindowCloseRequested { - pub id: WindowId, + pub entity: Entity, } /// An event that is sent whenever a window is closed. This will be sent by the @@ -56,65 +57,65 @@ pub struct WindowCloseRequested { /// [`Window::close`]: crate::Window::close #[derive(Debug, Clone)] pub struct WindowClosed { - pub id: WindowId, + pub entity: Entity, } #[derive(Debug, Clone)] pub struct CursorMoved { - pub id: WindowId, + pub entity: Entity, pub position: Vec2, } #[derive(Debug, Clone)] pub struct CursorEntered { - pub id: WindowId, + pub entity: Entity, } #[derive(Debug, Clone)] pub struct CursorLeft { - pub id: WindowId, + pub entity: Entity, } /// An event that is sent whenever a window receives a character from the OS or underlying system. #[derive(Debug, Clone)] pub struct ReceivedCharacter { - pub id: WindowId, + pub entity: Entity, pub char: char, } /// An event that indicates a window has received or lost focus. #[derive(Debug, Clone)] pub struct WindowFocused { - pub id: WindowId, + pub entity: Entity, pub focused: bool, } /// An event that indicates a window's scale factor has changed. #[derive(Debug, Clone)] pub struct WindowScaleFactorChanged { - pub id: WindowId, + pub entity: Entity, pub scale_factor: f64, } /// An event that indicates a window's OS-reported scale factor has changed. #[derive(Debug, Clone)] pub struct WindowBackendScaleFactorChanged { - pub id: WindowId, + pub entity: Entity, pub scale_factor: f64, } /// Events related to files being dragged and dropped on a window. #[derive(Debug, Clone)] pub enum FileDragAndDrop { - DroppedFile { id: WindowId, path_buf: PathBuf }, + DroppedFile { entity: Entity, path_buf: PathBuf }, - HoveredFile { id: WindowId, path_buf: PathBuf }, + HoveredFile { entity: Entity, path_buf: PathBuf }, - HoveredFileCancelled { id: WindowId }, + HoveredFileCancelled { entity: Entity }, } /// An event that is sent when a window is repositioned in physical pixels. #[derive(Debug, Clone)] pub struct WindowMoved { - pub id: WindowId, + pub entity: Entity, pub position: IVec2, } diff --git a/crates/bevy_window/src/lib.rs b/crates/bevy_window/src/lib.rs index 6e5b2abd87680..efe33faf9eea8 100644 --- a/crates/bevy_window/src/lib.rs +++ b/crates/bevy_window/src/lib.rs @@ -3,25 +3,25 @@ mod event; mod raw_window_handle; mod system; mod window; -mod windows; +mod window_commands; pub use crate::raw_window_handle::*; pub use cursor::*; pub use event::*; pub use system::*; pub use window::*; -pub use windows::*; +pub use window_commands::*; pub mod prelude { #[doc(hidden)] pub use crate::{ CursorEntered, CursorIcon, CursorLeft, CursorMoved, FileDragAndDrop, ReceivedCharacter, - Window, WindowDescriptor, WindowMoved, Windows, + Window, WindowCommands, WindowCommandsExtension, WindowDescriptor, WindowMoved, }; } use bevy_app::prelude::*; -use bevy_ecs::{event::Events, schedule::SystemLabel}; +use bevy_ecs::{entity::Entity, event::Events, schedule::{SystemLabel, SystemStage}, system::{SystemState, Commands, ResMut, Command}}; pub struct WindowPlugin { /// Whether to create a window when added. @@ -36,7 +36,8 @@ pub struct WindowPlugin { /// surprise your users. It is recommended to leave this setting as `true`. /// /// If true, this plugin will add [`exit_on_all_closed`] to [`CoreStage::Update`]. - pub exit_on_all_closed: bool, + // TODO: Update documentation here + pub exit_condition: ExitCondition, /// Whether to close windows when they are requested to be closed (i.e. /// when the close button is pressed) /// @@ -50,7 +51,7 @@ impl Default for WindowPlugin { fn default() -> Self { WindowPlugin { add_primary_window: true, - exit_on_all_closed: true, + exit_condition: ExitCondition::OnAllClosed, close_when_requested: true, } } @@ -59,7 +60,8 @@ impl Default for WindowPlugin { impl Plugin for WindowPlugin { fn build(&self, app: &mut App) { app.add_event::() - .add_event::() + // TODO: This is now moved to a command and no longer needed + // .add_event::() .add_event::() .add_event::() .add_event::() @@ -73,29 +75,112 @@ impl Plugin for WindowPlugin { .add_event::() .add_event::() .add_event::() - .init_resource::(); + // Command events + .add_event::() + .add_event::() + .add_event::() + .add_event::() + .add_event::() + .add_event::() + .add_event::() + .add_event::() + .add_event::() + .add_event::() + .add_event::() + .add_event::() + .add_event::() + .add_event::() + .add_event::() + .add_event::() + .add_event::() + // Resources + .init_resource::(); + + bevy_utils::tracing::info!("Hello"); if self.add_primary_window { + // TODO: Creating primary window should ideally be done through commands instead of the old way + // however, commands aren't executed until the end of the "build-stage" + // which means the primary-window does not exist until just before startup-systems starts running (?) + // which means bevy_render does not have a window to use as attach to during plugin build. + + // Wishlist item; for this to work: + // app.add_startup_system(create_primary_window); + // or this: + // app.add_build_system(create_primary_window) + + // TODO: The unwrap_or_default is necessary for the user to setup ahead of time what the window should be + // if not we'll regress on this let window_descriptor = app .world .get_resource::() .map(|descriptor| (*descriptor).clone()) .unwrap_or_default(); - let mut create_window_event = app.world.resource_mut::>(); - create_window_event.send(CreateWindow { - id: WindowId::primary(), - descriptor: window_descriptor, - }); + + let window_id = app.world.spawn().id(); + + let mut system_state: SystemState<(Commands, ResMut)> = SystemState::new(&mut app.world); + let (mut commands, mut primary_window) = system_state.get_mut(&mut app.world); + primary_window.window = Some(window_id); + // create_primary_window(commands, primary_window); + + + let command = CreateWindowCommand { entity: window_id, descriptor: window_descriptor }; + // Apply the command directly on the world + // I wonder if this causes timing issue: this will trigger a CreateWindowCommand event, but will bevy_winit exist in time to listen to the event? + command.write(&mut app.world); + + // let mut create_window_event = app.world.resource_mut::>(); + + // // TODO: Replace with commands + // create_window_event.send(CreateWindow { + // entity: WindowId::primary(), + // descriptor: window_descriptor, + // }); } - if self.exit_on_all_closed { - app.add_system(exit_on_all_closed); + match self.exit_condition { + ExitCondition::OnPrimaryClosed => { + app.add_system(exit_on_primary_closed); + } + ExitCondition::OnAllClosed => { + app.add_system(exit_on_all_closed); + } + ExitCondition::DontExit => {} } + if self.close_when_requested { app.add_system(close_when_requested); } } } +/// System Label marking when changes are applied to windows #[derive(Debug, Hash, PartialEq, Eq, Clone, SystemLabel)] pub struct ModifiesWindows; + +pub enum ExitCondition { + /// Close application when the primary window is closed + OnPrimaryClosed, + /// Close application when all windows are closed + OnAllClosed, + /// Keep application running headless even after closing all windows + DontExit, +} + +/// Resource containing the Entity that is currently considered the primary window +pub struct PrimaryWindow { + // TODO: + // Should this be Option? + // should this be allowed to change? + // If yes, what should be responsible for updating it? + pub window: Option, +} + +impl Default for PrimaryWindow { + fn default() -> Self { + Self { + window: Option::None, + } + } +} diff --git a/crates/bevy_window/src/raw_window_handle.rs b/crates/bevy_window/src/raw_window_handle.rs index 5bc122558d78f..42d1316c06203 100644 --- a/crates/bevy_window/src/raw_window_handle.rs +++ b/crates/bevy_window/src/raw_window_handle.rs @@ -9,7 +9,7 @@ use raw_window_handle::{HasRawWindowHandle, RawWindowHandle}; pub struct RawWindowHandleWrapper(RawWindowHandle); impl RawWindowHandleWrapper { - pub(crate) fn new(handle: RawWindowHandle) -> Self { + pub fn new(handle: RawWindowHandle) -> Self { Self(handle) } diff --git a/crates/bevy_window/src/system.rs b/crates/bevy_window/src/system.rs index 45a374fe2e09d..c718e1e481038 100644 --- a/crates/bevy_window/src/system.rs +++ b/crates/bevy_window/src/system.rs @@ -1,9 +1,25 @@ -use crate::{Window, WindowCloseRequested, WindowFocused, WindowId, Windows}; +use crate::{ + PrimaryWindow, Window, WindowCloseRequested, WindowClosed, WindowCommandsExtension, + WindowCurrentlyFocused, WindowDescriptor, +}; use bevy_app::AppExit; use bevy_ecs::prelude::*; use bevy_input::{keyboard::KeyCode, Input}; +pub fn create_primary_window(mut commands: Commands, mut primary: ResMut) { + + bevy_utils::tracing::info!("Creating primary window"); + let entity = commands.spawn().id(); + + commands + .window(entity) + .create_window(WindowDescriptor::default()); + + // TODO: Maybe this should be controlled by window backend + primary.window = Some(entity); +} + /// Exit the application when there are no open windows. /// /// This system is added by the [`WindowPlugin`] in the default configuration. @@ -11,12 +27,31 @@ use bevy_input::{keyboard::KeyCode, Input}; /// Ensure that you read the caveats documented on that field if doing so. /// /// [`WindowPlugin`]: crate::WindowPlugin -pub fn exit_on_all_closed(mut app_exit_events: EventWriter, windows: Res) { +pub fn exit_on_all_closed(mut app_exit_events: EventWriter, windows: Query<&Window>) { if windows.iter().count() == 0 { app_exit_events.send(AppExit); } } +/// Exit the application when the primary window has been closed +/// +/// This system is added by the [`WindowPlugin`] +// TODO: More docs +pub fn exit_on_primary_closed( + mut app_exit_events: EventWriter, + primary: Res, + mut window_close: EventReader, +) { + for window in window_close.iter() { + if let Some(primary_window) = primary.window { + if primary_window == window.entity { + // Primary window has been closed + app_exit_events.send(AppExit); + } + } + } +} + /// Close windows in response to [`WindowCloseRequested`] (e.g. when the close button is pressed). /// /// This system is added by the [`WindowPlugin`] in the default configuration. @@ -24,12 +59,9 @@ pub fn exit_on_all_closed(mut app_exit_events: EventWriter, windows: Re /// Ensure that you read the caveats documented on that field if doing so. /// /// [`WindowPlugin`]: crate::WindowPlugin -pub fn close_when_requested( - mut windows: ResMut, - mut closed: EventReader, -) { +pub fn close_when_requested(mut commands: Commands, mut closed: EventReader) { for event in closed.iter() { - windows.get_mut(event.id).map(Window::close); + commands.window(event.entity).close(); } } @@ -37,21 +69,20 @@ pub fn close_when_requested( /// /// This is useful for examples or prototyping. pub fn close_on_esc( - mut focused: Local>, - mut focused_events: EventReader, - mut windows: ResMut, + mut commands: Commands, + focused_windows: Query>, + // mut focused: Local>, input: Res>, ) { + // TODO: Not quite sure what this is about // TODO: Track this in e.g. a resource to ensure consistent behaviour across similar systems - for event in focused_events.iter() { - *focused = event.focused.then(|| event.id); - } + // for event in focused_events.iter() { + // *focused = event.focused.then(|| event.id); + // } - if let Some(focused) = &*focused { + for focused_window in focused_windows.iter() { if input.just_pressed(KeyCode::Escape) { - if let Some(window) = windows.get_mut(*focused) { - window.close(); - } + commands.window(focused_window).close(); } } } diff --git a/crates/bevy_window/src/window.rs b/crates/bevy_window/src/window.rs index 19aa6cbbb8267..c58fb4a93ec44 100644 --- a/crates/bevy_window/src/window.rs +++ b/crates/bevy_window/src/window.rs @@ -1,9 +1,14 @@ +use bevy_ecs::{ + entity::Entity, + prelude::{Bundle, Component}, + system::{Command, Commands}, +}; use bevy_math::{DVec2, IVec2, Vec2}; use bevy_utils::{tracing::warn, Uuid}; use raw_window_handle::RawWindowHandle; -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct WindowId(Uuid); +use crate::CursorIcon; +use crate::{raw_window_handle::RawWindowHandleWrapper, WindowFocused}; /// Presentation mode for a window. /// @@ -38,35 +43,36 @@ pub enum PresentMode { Fifo = 2, // NOTE: The explicit ordinal values mirror wgpu and the vulkan spec. } -impl WindowId { - pub fn new() -> Self { - WindowId(Uuid::new_v4()) - } - - pub fn primary() -> Self { - WindowId(Uuid::from_u128(0)) - } - - pub fn is_primary(&self) -> bool { - *self == WindowId::primary() - } -} - -use crate::CursorIcon; -use std::fmt; - -use crate::raw_window_handle::RawWindowHandleWrapper; - -impl fmt::Display for WindowId { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.as_simple().fmt(f) - } +/// Defines the way a window is displayed +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum WindowMode { + /// Creates a window that uses the given size + Windowed, + /// Creates a borderless window that uses the full size of the screen + BorderlessFullscreen, + /// Creates a fullscreen window that will render at desktop resolution. The app will use the closest supported size + /// from the given size and scale it to fit the screen. + SizedFullscreen, + /// Creates a fullscreen window that uses the maximum supported size + Fullscreen, } -impl Default for WindowId { - fn default() -> Self { - WindowId::primary() - } +// This should only be used by the window backend, so maybe it should not be a bundle for those reasons +// The window backend is responsible for spawning the correct components that together define a whole window +#[derive(Bundle)] +pub struct WindowBundle { + pub window: Window, + pub cursor: WindowCursor, + pub cursor_position: WindowCursorPosition, + pub handle: WindowHandle, + pub presentation: WindowPresentation, + pub mode: WindowModeComponent, + pub position: WindowPosition, + pub resolution: WindowResolution, + pub title: WindowTitle, + pub canvas: WindowCanvas, + pub resize_constraints: WindowResizeConstraints, + pub focused: WindowCurrentlyFocused, } /// The size limits on a window. @@ -75,7 +81,7 @@ impl Default for WindowId { /// 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)] +#[derive(Debug, Clone, Copy, Component)] pub struct WindowResizeConstraints { pub min_width: f32, pub min_height: f32, @@ -126,301 +132,166 @@ impl WindowResizeConstraints { max_height, } } + + // /// The window's client resize constraint in logical pixels. + // #[inline] + // pub fn resize_constraints(&self) -> WindowResizeConstraints { + // self.resize_constraints + // } } -/// 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. -#[derive(Debug)] -pub struct Window { - id: WindowId, - requested_width: f32, - requested_height: f32, - physical_width: u32, - physical_height: u32, - resize_constraints: WindowResizeConstraints, - position: Option, - scale_factor_override: Option, - backend_scale_factor: f64, - title: String, - present_mode: PresentMode, - resizable: bool, - decorations: bool, +/// A marker component on an entity containing a window +#[derive(Debug, Component)] +pub struct Window; + +#[derive(Component)] +pub struct WindowCursor { cursor_icon: CursorIcon, cursor_visible: bool, cursor_locked: bool, - physical_cursor_position: Option, - raw_window_handle: RawWindowHandleWrapper, - focused: bool, - mode: WindowMode, - canvas: Option, - fit_canvas_to_parent: bool, - command_queue: Vec, -} - -#[derive(Debug)] -pub enum WindowCommand { - SetWindowMode { - mode: WindowMode, - resolution: (u32, u32), - }, - SetTitle { - title: String, - }, - SetScaleFactor { - scale_factor: f64, - }, - SetResolution { - logical_resolution: (f32, f32), - scale_factor: f64, - }, - SetPresentMode { - present_mode: PresentMode, - }, - SetResizable { - resizable: bool, - }, - SetDecorations { - decorations: bool, - }, - SetCursorLockMode { - locked: bool, - }, - SetCursorIcon { - icon: CursorIcon, - }, - SetCursorVisibility { - visible: bool, - }, - SetCursorPosition { - position: Vec2, - }, - SetMaximized { - maximized: bool, - }, - SetMinimized { - minimized: bool, - }, - SetPosition { - position: IVec2, - }, - SetResizeConstraints { - resize_constraints: WindowResizeConstraints, - }, - Close, } -/// Defines the way a window is displayed -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum WindowMode { - /// Creates a window that uses the given size - Windowed, - /// Creates a borderless window that uses the full size of the screen - BorderlessFullscreen, - /// Creates a fullscreen window that will render at desktop resolution. The app will use the closest supported size - /// from the given size and scale it to fit the screen. - SizedFullscreen, - /// Creates a fullscreen window that uses the maximum supported size - Fullscreen, -} - -impl Window { - pub fn new( - id: WindowId, - window_descriptor: &WindowDescriptor, - physical_width: u32, - physical_height: u32, - scale_factor: f64, - position: Option, - raw_window_handle: RawWindowHandle, - ) -> Self { - Window { - id, - requested_width: window_descriptor.width, - requested_height: window_descriptor.height, - 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(), - present_mode: window_descriptor.present_mode, - resizable: window_descriptor.resizable, - decorations: window_descriptor.decorations, - cursor_visible: window_descriptor.cursor_visible, - cursor_locked: window_descriptor.cursor_locked, - cursor_icon: CursorIcon::Default, - physical_cursor_position: None, - raw_window_handle: RawWindowHandleWrapper::new(raw_window_handle), - focused: true, - mode: window_descriptor.mode, - canvas: window_descriptor.canvas.clone(), - fit_canvas_to_parent: window_descriptor.fit_canvas_to_parent, - command_queue: Vec::new(), +impl WindowCursor { + pub fn new(cursor_icon: CursorIcon, cursor_visible: bool, cursor_locked: bool) -> Self { + Self { + cursor_icon, + cursor_visible, + cursor_locked, } } #[inline] - pub fn id(&self) -> WindowId { - self.id + pub fn cursor_icon(&self) -> CursorIcon { + self.cursor_icon } - /// The current logical width of the window's client area. #[inline] - pub fn width(&self) -> f32 { - (self.physical_width as f64 / self.scale_factor()) as f32 + pub fn cursor_visible(&self) -> bool { + self.cursor_visible } - /// The current logical height of the window's client area. #[inline] - pub fn height(&self) -> f32 { - (self.physical_height as f64 / self.scale_factor()) as f32 + pub fn cursor_locked(&self) -> bool { + self.cursor_locked } - /// 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_width + pub fn set_icon_from_backend(&mut self, icon: CursorIcon) { + self.cursor_icon = icon; } - /// 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_height + pub fn set_visible_from_backend(&mut self, visible: bool) { + self.cursor_visible = visible; } - /// The window's client area width in physical pixels. - #[inline] - pub fn physical_width(&self) -> u32 { - self.physical_width + pub fn set_locked_from_backend(&mut self, locked: bool) { + self.cursor_locked = locked; } +} - /// The window's client area height in physical pixels. - #[inline] - pub fn physical_height(&self) -> u32 { - self.physical_height +#[derive(Component)] +pub struct WindowCursorPosition { + // TODO: Docs + /// This is None if the cursor has left the window + physical_cursor_position: Option, +} + +impl WindowCursorPosition { + pub fn new(physical_cursor_position: Option) -> Self { + Self { + physical_cursor_position, + } } - /// The window's client resize constraint in logical pixels. + /// The current mouse position, in physical pixels. #[inline] - pub fn resize_constraints(&self) -> WindowResizeConstraints { - self.resize_constraints + pub fn physical_cursor_position(&self) -> Option { + self.physical_cursor_position } - /// The window's client position in physical pixels. - #[inline] - pub fn position(&self) -> Option { - self.position + // TODO: Docs + pub fn update_position_from_backend(&mut self, position: Option) { + // TODO: Fix type inconsitencies + self.physical_cursor_position = position; } +} - #[inline] - pub fn set_maximized(&mut self, maximized: bool) { - self.command_queue - .push(WindowCommand::SetMaximized { maximized }); +// TODO: Figure out how this connects to everything +#[derive(Component)] +pub struct WindowHandle { + // TODo: What should be creating and setting this? + raw_window_handle: RawWindowHandleWrapper, +} + +impl WindowHandle { + pub fn new(raw_window_handle: RawWindowHandle) -> Self { + Self { + raw_window_handle: RawWindowHandleWrapper::new(raw_window_handle), + } } - /// Sets the window to minimized or back. - /// - /// # Platform-specific - /// - iOS / Android / Web: Unsupported. - /// - Wayland: Un-minimize is unsupported. - #[inline] - pub fn set_minimized(&mut self, minimized: bool) { - self.command_queue - .push(WindowCommand::SetMinimized { minimized }); + pub fn raw_window_handle(&self) -> RawWindowHandleWrapper { + self.raw_window_handle.clone() } +} - /// Modifies the position of the window in physical pixels. - /// - /// Note that the top-left hand corner of the desktop is not necessarily the same as the screen. - /// If the user uses a desktop with multiple monitors, the top-left hand corner of the - /// desktop is the top-left hand corner of the monitor at the top-left of the desktop. This - /// automatically un-maximizes the window if it's maximized. - /// - /// # Platform-specific - /// - /// - iOS: Can only be called on the main thread. Sets the top left coordinates of the window in - /// the screen space coordinate system. - /// - Web: Sets the top-left coordinates relative to the viewport. - /// - Android / Wayland: Unsupported. - #[inline] - pub fn set_position(&mut self, position: IVec2) { - self.command_queue - .push(WindowCommand::SetPosition { position }); +// TODO: Find better name +#[derive(Component)] +pub struct WindowPresentation { + present_mode: PresentMode, +} + +impl WindowPresentation { + pub fn new(present_mode: PresentMode) -> Self { + Self { present_mode } } - /// 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 }); + #[doc(alias = "vsync")] + pub fn present_mode(&self) -> PresentMode { + self.present_mode } - /// Request the OS to resize the window such the the client area matches the - /// specified width and height. - #[allow(clippy::float_cmp)] - pub fn set_resolution(&mut self, width: f32, height: f32) { - if self.requested_width == width && self.requested_height == height { - return; - } - - self.requested_width = width; - self.requested_height = height; - self.command_queue.push(WindowCommand::SetResolution { - logical_resolution: (self.requested_width, self.requested_height), - scale_factor: self.scale_factor(), - }); + pub fn update_present_mode_from_backend(&mut self, present_mode: PresentMode) { + self.present_mode = present_mode; } +} - /// Override the os-reported scaling factor - #[allow(clippy::float_cmp)] - pub fn set_scale_factor_override(&mut self, scale_factor: Option) { - if self.scale_factor_override == scale_factor { - return; - } +// TODO: Find better name +#[derive(Component)] +pub struct WindowModeComponent { + mode: WindowMode, +} - self.scale_factor_override = scale_factor; - self.command_queue.push(WindowCommand::SetScaleFactor { - scale_factor: self.scale_factor(), - }); - self.command_queue.push(WindowCommand::SetResolution { - logical_resolution: (self.requested_width, self.requested_height), - scale_factor: self.scale_factor(), - }); +impl WindowModeComponent { + pub fn new(mode: WindowMode) -> Self { + Self { mode } } - #[allow(missing_docs)] #[inline] - pub fn update_scale_factor_from_backend(&mut self, scale_factor: f64) { - self.backend_scale_factor = scale_factor; + pub fn mode(&self) -> WindowMode { + self.mode } - #[allow(missing_docs)] + pub fn update_mode_from_backend(&mut self, mode: WindowMode) { + self.mode = mode; + } +} + +#[derive(Component)] +pub struct WindowPosition { + // TODO: Document why this must be option + position: Option, +} + +impl WindowPosition { + pub fn new(position: Option) -> Self { + Self { position } + } + + /// The window's client position in physical pixels. #[inline] - pub fn update_actual_size_from_backend(&mut self, physical_width: u32, physical_height: u32) { - self.physical_width = physical_width; - self.physical_height = physical_height; + pub fn position(&self) -> Option { + self.position } #[allow(missing_docs)] @@ -428,6 +299,50 @@ impl Window { pub fn update_actual_position_from_backend(&mut self, position: IVec2) { self.position = Some(position); } +} + +/// ## 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. +// TODO: Make sure this is used correctly +#[derive(Component)] +pub struct WindowResolution { + requested_width: f32, + requested_height: f32, + physical_width: u32, + physical_height: u32, + scale_factor_override: Option, + backend_scale_factor: f64, +} + +impl WindowResolution { + pub fn new( + requested_width: f32, + requested_height: f32, + physical_width: u32, + physical_height: u32, + scale_factor_override: Option, + backend_scale_factor: f64, + ) -> Self { + Self { + requested_width, + requested_height, + physical_width, + physical_height, + scale_factor_override, + backend_scale_factor, + } + } /// The ratio of physical pixels to logical pixels /// @@ -449,156 +364,120 @@ impl Window { self.scale_factor_override } + /// The current logical width of the window's client area. #[inline] - 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 width(&self) -> f32 { + (self.physical_width as f64 / self.scale_factor()) as f32 } + /// The current logical height of the window's client area. #[inline] - #[doc(alias = "vsync")] - pub fn present_mode(&self) -> PresentMode { - self.present_mode + pub fn height(&self) -> f32 { + (self.physical_height as f64 / self.scale_factor()) as f32 } + /// 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. + // TODO: This is never set #[inline] - #[doc(alias = "set_vsync")] - pub fn set_present_mode(&mut self, present_mode: PresentMode) { - self.present_mode = present_mode; - self.command_queue - .push(WindowCommand::SetPresentMode { present_mode }); + pub fn requested_width(&self) -> f32 { + self.requested_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. + // TODO: This is never set #[inline] - 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 requested_height(&self) -> f32 { + self.requested_height } + /// The window's client area width in physical pixels. #[inline] - 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 physical_width(&self) -> u32 { + self.physical_width } + /// The window's client area height in physical pixels. #[inline] - pub fn cursor_locked(&self) -> bool { - self.cursor_locked - } - - pub fn set_cursor_lock_mode(&mut self, lock_mode: bool) { - self.cursor_locked = lock_mode; - self.command_queue - .push(WindowCommand::SetCursorLockMode { locked: lock_mode }); + pub fn physical_height(&self) -> u32 { + self.physical_height } + #[allow(missing_docs)] #[inline] - pub fn cursor_visible(&self) -> bool { - self.cursor_visible + pub fn update_scale_factor_from_backend(&mut self, scale_factor: f64) { + self.backend_scale_factor = scale_factor; } - pub fn set_cursor_visibility(&mut self, visibile_mode: bool) { - self.cursor_visible = visibile_mode; - self.command_queue.push(WindowCommand::SetCursorVisibility { - visible: visibile_mode, - }); + pub fn update_scale_factor_override(&mut self, scale_factor_override: Option) { + self.scale_factor_override = scale_factor_override; } + #[allow(missing_docs)] #[inline] - pub fn cursor_icon(&self) -> CursorIcon { - self.cursor_icon + pub fn update_actual_size_from_backend(&mut self, physical_width: u32, physical_height: u32) { + self.physical_width = physical_width; + self.physical_height = physical_height; } +} - pub fn set_cursor_icon(&mut self, icon: CursorIcon) { - self.command_queue - .push(WindowCommand::SetCursorIcon { icon }); - } +#[derive(Component)] +pub struct WindowTitle { + title: String, +} - /// The current mouse position, in physical pixels. - #[inline] - pub fn physical_cursor_position(&self) -> Option { - self.physical_cursor_position +impl WindowTitle { + pub fn new(title: String) -> Self { + Self { title } } - /// The current mouse position, in logical pixels, taking into account the screen scale factor. #[inline] - #[doc(alias = "mouse position")] - pub fn cursor_position(&self) -> Option { - self.physical_cursor_position - .map(|p| (p / self.scale_factor()).as_vec2()) + pub fn title(&self) -> &str { + &self.title } - pub fn set_cursor_position(&mut self, position: Vec2) { - self.command_queue - .push(WindowCommand::SetCursorPosition { position }); + pub fn update_title_from_backend(&mut self, title: String) { + self.title = title; } +} - #[allow(missing_docs)] - #[inline] - pub fn update_focused_status_from_backend(&mut self, focused: bool) { - self.focused = focused; - } +#[derive(Component)] +pub struct WindowDecorated; - #[allow(missing_docs)] - #[inline] - pub fn update_cursor_physical_position_from_backend(&mut self, cursor_position: Option) { - self.physical_cursor_position = cursor_position; - } +#[derive(Component)] +pub struct WindowCurrentlyFocused; - #[inline] - pub fn mode(&self) -> WindowMode { - self.mode - } +#[derive(Component)] +pub struct WindowResizable; - pub fn set_mode(&mut self, mode: WindowMode) { - self.mode = mode; - self.command_queue.push(WindowCommand::SetWindowMode { - mode, - resolution: (self.physical_width, self.physical_height), - }); - } +#[derive(Component)] +pub struct WindowTransparent; - /// Close the operating system window corresponding to this [`Window`]. - /// This will also lead to this [`Window`] being removed from the - /// [`Windows`] resource. - /// - /// If the default [`WindowPlugin`] is used, when no windows are - /// open, the [app will exit](bevy_app::AppExit). - /// To disable this behaviour, set `exit_on_all_closed` on the [`WindowPlugin`] - /// to `false` - /// - /// [`Windows`]: crate::Windows - /// [`WindowPlugin`]: crate::WindowPlugin - pub fn close(&mut self) { - self.command_queue.push(WindowCommand::Close); - } +#[derive(Component)] +pub struct WindowMinimized; - #[inline] - pub fn drain_commands(&mut self) -> impl Iterator + '_ { - self.command_queue.drain(..) - } +#[derive(Component)] +pub struct WindowMaximized; - #[inline] - pub fn is_focused(&self) -> bool { - self.focused - } +#[derive(Component)] +pub struct WindowCanvas { + canvas: Option, + fit_canvas_to_parent: bool, +} - pub fn raw_window_handle(&self) -> RawWindowHandleWrapper { - self.raw_window_handle.clone() +impl WindowCanvas { + pub fn new(canvas: Option, fit_canvas_to_parent: bool) -> Self { + Self { + canvas, + fit_canvas_to_parent, + } } /// The "html canvas" element selector. If set, this selector will be used to find a matching html canvas element, @@ -624,6 +503,9 @@ impl Window { } } +// /// Request the OS to resize the window such the the client area matches the +// /// specified width and height. + /// Describes the information needed for creating a window. /// /// This should be set up before adding the [`WindowPlugin`](crate::WindowPlugin). diff --git a/crates/bevy_window/src/window_commands.rs b/crates/bevy_window/src/window_commands.rs new file mode 100644 index 0000000000000..537863c070681 --- /dev/null +++ b/crates/bevy_window/src/window_commands.rs @@ -0,0 +1,623 @@ +use bevy_ecs::{ + entity::Entity, + event::Events, + prelude::World, + system::{Command, Commands}, +}; +use bevy_math::{DVec2, IVec2, Vec2}; + +use crate::{ + CursorIcon, PresentMode, RawWindowHandleWrapper, Window, + WindowDescriptor, WindowMode, WindowResizeConstraints, +}; + +// TODO: Docs +pub trait WindowCommandsExtension<'w, 's> { + // TODO: Docs + fn window<'a>(&'a mut self, entity: Entity) -> WindowCommands<'w, 's, 'a>; + // TODO: Docs + fn spawn_window<'a>(&'a mut self, descriptor: WindowDescriptor) -> WindowCommands<'w, 's, 'a>; +} + +impl<'w, 's> WindowCommandsExtension<'w, 's> for Commands<'w, 's> { + // TODO: Docs + /// Gives you windowcommands for an entity + fn window<'a>(&'a mut self, entity: Entity) -> WindowCommands<'w, 's, 'a> { + assert!( + self.has_entity(entity), + "Attempting to create an WindowCommands for entity {:?}, which doesn't exist.", + entity + ); + + WindowCommands { + entity, + commands: self, + } + } + + // TODO: Docs + /// Spawns and entity, then gives you window-commands for that entity + fn spawn_window<'a>(&'a mut self, descriptor: WindowDescriptor) -> WindowCommands<'w, 's, 'a> { + let entity = self.spawn().id(); + + self.add(CreateWindowCommand { entity, descriptor }); + + WindowCommands { + entity, + commands: self, + } + } +} + +// TODO: Docs +#[derive(Debug)] +pub struct CreateWindowCommand { + pub entity: Entity, + pub descriptor: WindowDescriptor, +} + +impl Command for CreateWindowCommand { + fn write(self, world: &mut World) { + // Make sure we only create new windows on entities that has none + if let None = world.get::(self.entity) { + let mut event = world.resource_mut::>(); + event.send(self); + } else { + panic!("Can't create a window on an entity that already has a Window"); + } + + // match world.resource_mut::>() { + // mut create_window_event => { + // create_window_event.send(self); + // } + // _ => { + // panic!( + // "Could not send CreateWindow event as the Event has not been created" + // ); + // } + // } + } +} + +// TODO: Docs +#[derive(Debug)] +pub struct SetWindowModeCommand { + pub entity: Entity, + pub mode: WindowMode, + pub resolution: (u32, u32), +} + +impl Command for SetWindowModeCommand { + fn write(self, world: &mut World) { + if let Some(_) = world.get::(self.entity) { + let mut event = world.resource_mut::>(); + event.send(self); + } else { + panic!("Trying to enact window commands on an entity without a window-component"); + } + } +} + +// TODO: Docs +#[derive(Debug)] +pub struct SetTitleCommand { + pub entity: Entity, + pub title: String, +} + +impl Command for SetTitleCommand { + fn write(self, world: &mut World) { + if let Some(_) = world.get::(self.entity) { + let mut event = world.resource_mut::>(); + event.send(self); + } else { + panic!("Trying to enact window commands on an entity without a window-component"); + } + } +} + +// TODO: Docs +#[derive(Debug)] +pub struct SetScaleFactorOverrideCommand { + pub entity: Entity, + pub scale_factor: Option, +} + +impl Command for SetScaleFactorOverrideCommand { + fn write(self, world: &mut World) { + if let Some(_) = world.get::(self.entity) { + let mut event = world.resource_mut::>(); + event.send(self); + } else { + panic!("Trying to enact window commands on an entity without a window-component"); + } + } +} + +// TODO: Docs +#[derive(Debug)] +pub struct SetResolutionCommand { + pub entity: Entity, + pub logical_resolution: (f32, f32), + pub scale_factor: f64, +} + +impl Command for SetResolutionCommand { + fn write(self, world: &mut World) { + if let Some(_) = world.get::(self.entity) { + let mut event = world.resource_mut::>(); + event.send(self); + } else { + panic!("Trying to enact window commands on an entity without a window-component"); + } + } +} + +// TODO: Docs +#[derive(Debug)] +pub struct SetPresentModeCommand { + pub entity: Entity, + pub present_mode: PresentMode, +} + +impl Command for SetPresentModeCommand { + fn write(self, world: &mut World) { + if let Some(_) = world.get::(self.entity) { + let mut event = world.resource_mut::>(); + event.send(self); + } else { + panic!("Trying to enact window commands on an entity without a window-component"); + } + } +} + +// TODO: Docs +#[derive(Debug)] +pub struct SetResizableCommand { + pub entity: Entity, + pub resizable: bool, +} + +impl Command for SetResizableCommand { + fn write(self, world: &mut World) { + if let Some(_) = world.get::(self.entity) { + let mut event = world.resource_mut::>(); + event.send(self); + } else { + panic!("Trying to enact window commands on an entity without a window-component"); + } + } +} + +// TODO: Docs +#[derive(Debug)] +pub struct SetDecorationsCommand { + pub entity: Entity, + pub decorations: bool, +} + +impl Command for SetDecorationsCommand { + fn write(self, world: &mut World) { + if let Some(_) = world.get::(self.entity) { + let mut event = world.resource_mut::>(); + event.send(self); + } else { + panic!("Trying to enact window commands on an entity without a window-component"); + } + } +} + +// TODO: Docs +#[derive(Debug)] +pub struct SetCursorLockModeCommand { + pub entity: Entity, + pub locked: bool, +} + +impl Command for SetCursorLockModeCommand { + fn write(self, world: &mut World) { + if let Some(_) = world.get::(self.entity) { + let mut event = world.resource_mut::>(); + event.send(self); + } else { + panic!("Trying to enact window commands on an entity without a window-component"); + } + } +} + +// TODO: Docs +#[derive(Debug)] +pub struct SetCursorIconCommand { + pub entity: Entity, + pub icon: CursorIcon, +} + +impl Command for SetCursorIconCommand { + fn write(self, world: &mut World) { + if let Some(_) = world.get::(self.entity) { + let mut event = world.resource_mut::>(); + event.send(self); + } else { + panic!("Trying to enact window commands on an entity without a window-component"); + } + } +} + +// TODO: Docs +#[derive(Debug)] +pub struct SetCursorVisibilityCommand { + pub entity: Entity, + pub visible: bool, +} + +impl Command for SetCursorVisibilityCommand { + fn write(self, world: &mut World) { + if let Some(_) = world.get::(self.entity) { + let mut event = world.resource_mut::>(); + event.send(self); + } else { + panic!("Trying to enact window commands on an entity without a window-component"); + } + } +} + +// TODO: Docs +#[derive(Debug)] +pub struct SetCursorPositionCommand { + pub entity: Entity, + pub position: DVec2, +} + +impl Command for SetCursorPositionCommand { + fn write(self, world: &mut World) { + if let Some(_) = world.get::(self.entity) { + let mut event = world.resource_mut::>(); + event.send(self); + } else { + panic!("Trying to enact window commands on an entity without a window-component"); + } + } +} + +// TODO: Docs +#[derive(Debug)] +pub struct SetMaximizedCommand { + pub entity: Entity, + pub maximized: bool, +} + +impl Command for SetMaximizedCommand { + fn write(self, world: &mut World) { + if let Some(_) = world.get::(self.entity) { + let mut event = world.resource_mut::>(); + event.send(self); + } else { + panic!("Trying to enact window commands on an entity without a window-component"); + } + } +} + +// TODO: Docs +#[derive(Debug)] +pub struct SetMinimizedCommand { + pub entity: Entity, + pub minimized: bool, +} + +impl Command for SetMinimizedCommand { + fn write(self, world: &mut World) { + if let Some(_) = world.get::(self.entity) { + let mut event = world.resource_mut::>(); + event.send(self); + } else { + panic!("Trying to enact window commands on an entity without a window-component"); + } + } +} + +// TODO: Docs +#[derive(Debug)] +pub struct SetPositionCommand { + pub entity: Entity, + pub position: IVec2, +} + +impl Command for SetPositionCommand { + fn write(self, world: &mut World) { + if let Some(_) = world.get::(self.entity) { + let mut event = world.resource_mut::>(); + event.send(self); + } else { + panic!("Trying to enact window commands on an entity without a window-component"); + } + } +} + +// TODO: Docs +#[derive(Debug)] +pub struct SetResizeConstraintsCommand { + pub entity: Entity, + pub resize_constraints: WindowResizeConstraints, +} + +impl Command for SetResizeConstraintsCommand { + fn write(self, world: &mut World) { + if let Some(_) = world.get::(self.entity) { + let mut event = world.resource_mut::>(); + event.send(self); + } else { + panic!("Trying to enact window commands on an entity without a window-component"); + } + } +} + +// TODO: Docs +#[derive(Debug)] +pub struct CloseWindowCommand { + pub entity: Entity, +} + +impl Command for CloseWindowCommand { + fn write(self, world: &mut World) { + if let Some(_) = world.get::(self.entity) { + let mut event = world.resource_mut::>(); + event.send(self); + } else { + panic!("Trying to enact window commands on an entity without a window-component"); + } + } +} + +// TODO: Docs +pub struct WindowCommands<'w, 's, 'a> { + entity: Entity, + commands: &'a mut Commands<'w, 's>, +} + +impl<'w, 's, 'a> WindowCommands<'w, 's, 'a> { + // TODO: Update documentation + /// Returns the [`Entity`] id of the entity. + /// + /// # Example + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// # + /// fn my_system(mut commands: Commands) { + /// let entity_id = commands.spawn().id(); + /// } + /// # bevy_ecs::system::assert_is_system(my_system); + /// ``` + #[inline] + #[must_use = "Omit the .id() call if you do not need to store the `Entity` identifier."] + pub fn id(&self) -> Entity { + self.entity + } + + pub fn create_window(&mut self, window_desciptor: WindowDescriptor) -> &mut Self { + self.commands.add(CreateWindowCommand { + entity: self.entity, + descriptor: window_desciptor, + }); + self + } + + #[inline] + pub fn set_maximized(&mut self, maximized: bool) -> &mut Self { + self.commands.add(SetMaximizedCommand { + entity: self.entity, + maximized, + }); + self + } + + /// Sets the window to minimized or back. + /// + /// # Platform-specific + /// - iOS / Android / Web: Unsupported. + /// - Wayland: Un-minimize is unsupported. + #[inline] + pub fn set_minimized(&mut self, minimized: bool) -> &mut Self { + self.commands.add(SetMinimizedCommand { + entity: self.entity, + minimized, + }); + self + } + + /// Modifies the position of the window in physical pixels. + /// + /// Note that the top-left hand corner of the desktop is not necessarily the same as the screen. + /// If the user uses a desktop with multiple monitors, the top-left hand corner of the + /// desktop is the top-left hand corner of the monitor at the top-left of the desktop. This + /// automatically un-maximizes the window if it's maximized. + /// + /// # Platform-specific + /// + /// - iOS: Can only be called on the main thread. Sets the top left coordinates of the window in + /// the screen space coordinate system. + /// - Web: Sets the top-left coordinates relative to the viewport. + /// - Android / Wayland: Unsupported. + #[inline] + pub fn set_position(&mut self, position: IVec2) -> &mut Self { + self.commands.add(SetPositionCommand { + entity: self.entity, + position, + }); + self + } + + /// Modifies the minimum and maximum window bounds for resizing in logical pixels. + #[inline] + pub fn set_resize_constraints( + &mut self, + resize_constraints: WindowResizeConstraints, + ) -> &mut Self { + self.commands.add(SetResizeConstraintsCommand { + entity: self.entity, + resize_constraints, + }); + self + } + + /// Close the operating system window corresponding to this [`Window`]. + /// This will also lead to this [`Window`] being removed from the + /// [`Windows`] resource. + /// + /// If the default [`WindowPlugin`] is used, when no windows are + /// open, the [app will exit](bevy_app::AppExit). + /// To disable this behaviour, set `exit_on_all_closed` on the [`WindowPlugin`] + /// to `false` + /// + /// [`Windows`]: crate::Windows + /// [`WindowPlugin`]: crate::WindowPlugin + pub fn close(&mut self) -> &mut Self { + self.commands.add(CloseWindowCommand { + entity: self.entity, + }); + self + } + + pub fn set_title(&mut self, title: String) -> &mut Self { + self.commands.add(SetTitleCommand { + entity: self.entity, + title, + }); + self + } + + #[allow(clippy::float_cmp)] + pub fn set_resolution(&mut self, width: f32, height: f32, scale_factor: f64) -> &mut Self { + // TODO: Should not send the command if new is the same as old? + // if self.requested_width == width && self.requested_height == height { + // return; + // // } + self.commands.add(SetResolutionCommand { + entity: self.entity, + logical_resolution: (width, height), + scale_factor, + }); + + self + } + + /// Override the os-reported scaling factor + #[allow(clippy::float_cmp)] + pub fn set_scale_factor_override(&mut self, scale_factor: Option) -> &mut Self { + // TODO: Not do anything if new is same as old? + // if self.scale_factor_override == scale_factor { + // return; + // } + + // self.scale_factor_override = scale_factor; + self.commands.add(SetScaleFactorOverrideCommand { + entity: self.entity, + scale_factor, + }); + + self + + // TODO: Sending scale-factor event should also update the resolution + // self.commands.add(WindowCommand::SetResolution { + // logical_resolution: (self.requested_width, self.requested_height), + // scale_factor: self.scale_factor(), + // }); + } + + #[inline] + #[doc(alias = "set_vsync")] + pub fn set_present_mode(&mut self, present_mode: PresentMode) -> &mut Self { + self.commands.add(SetPresentModeCommand { + entity: self.entity, + present_mode, + }); + + self + } + + pub fn set_resizable(&mut self, resizable: bool) -> &mut Self { + self.commands.add(SetResizableCommand { + entity: self.entity, + resizable, + }); + self + } + + pub fn set_decorations(&mut self, decorations: bool) -> &mut Self { + self.commands.add(SetDecorationsCommand { + entity: self.entity, + decorations, + }); + self + } + + pub fn set_cursor_lock_mode(&mut self, lock_mode: bool) -> &mut Self { + self.commands.add(SetCursorLockModeCommand { + entity: self.entity, + locked: lock_mode, + }); + self + } + + pub fn set_cursor_visibility(&mut self, visibile_mode: bool) -> &mut Self { + self.commands.add(SetCursorVisibilityCommand { + entity: self.entity, + visible: visibile_mode, + }); + self + } + + pub fn set_cursor_icon(&mut self, icon: CursorIcon) -> &mut Self { + self.commands.add(SetCursorIconCommand { + entity: self.entity, + icon, + }); + self + } + + // TODO: This should be a resource that calculates this? + /// The current mouse position, in logical pixels, taking into account the screen scale factor. + // #[inline] + // #[doc(alias = "mouse position")] + // pub fn cursor_position(&self) -> Option { + // self.physical_cursor_position + // .map(|p| (p / self.scale_factor()).as_vec2()) + // } + + pub fn set_cursor_position(&mut self, position: DVec2) -> &mut Self { + self.commands.add(SetCursorPositionCommand { + entity: self.entity, + position, + }); + self + } + + // TODO: Backend should add or remove WindowFocused marker component + // #[allow(missing_docs)] + // #[inline] + // pub fn update_focused_status_from_backend(&mut self, focused: bool) { + // self.focused = focused; + // } + + // TODO: What to do with this? + // #[allow(missing_docs)] + // #[inline] + // pub fn update_cursor_physical_position_from_backend(&mut self, cursor_position: Option) { + // self.physical_cursor_position = cursor_position; + // } + + pub fn set_mode(&mut self, mode: WindowMode, resolution: (u32, u32)) -> &mut Self { + self.commands.add(SetWindowModeCommand { + entity: self.entity, + mode, + resolution, + }); + + self + } + + // TODO: What to do with this? + // pub fn raw_window_handle(&self) -> RawWindowHandleWrapper { + // self.raw_window_handle.clone() + // } +} diff --git a/crates/bevy_window/src/windows.rs b/crates/bevy_window/src/windows.rs deleted file mode 100644 index df20688018133..0000000000000 --- a/crates/bevy_window/src/windows.rs +++ /dev/null @@ -1,77 +0,0 @@ -use super::{Window, WindowId}; -use bevy_utils::HashMap; - -/// A collection of [`Window`]s with unique [`WindowId`]s. -#[derive(Debug, Default)] -pub struct Windows { - windows: HashMap, -} - -impl Windows { - /// Add the provided [`Window`] to the [`Windows`] resource. - pub fn add(&mut self, window: Window) { - self.windows.insert(window.id(), window); - } - - /// Get a reference to the [`Window`] of `id` - pub fn get(&self, id: WindowId) -> Option<&Window> { - self.windows.get(&id) - } - - /// Get a mutable reference to the provided [`WindowId`]. - pub fn get_mut(&mut self, id: WindowId) -> Option<&mut Window> { - self.windows.get_mut(&id) - } - - /// Get a reference to the primary [`Window`]. - pub fn get_primary(&self) -> Option<&Window> { - self.get(WindowId::primary()) - } - - /// Get a reference to the primary [`Window`]. - /// - /// # Panics - /// - /// Panics if the primary window does not exist in [`Windows`] - pub fn primary(&self) -> &Window { - self.get_primary().expect("Primary window does not exist") - } - - /// Get a mutable reference to the primary [`Window`]. - pub fn get_primary_mut(&mut self) -> Option<&mut Window> { - self.get_mut(WindowId::primary()) - } - - /// Get a mutable reference to the primary [`Window`]. - /// - /// # Panics - /// - /// Panics if the primary window does not exist in [`Windows`] - pub fn primary_mut(&mut self) -> &mut Window { - self.get_primary_mut() - .expect("Primary window does not exist") - } - - /// Returns the scale factor for the [`Window`] of `id`, or `1.0` if the window does not exist. - pub fn scale_factor(&self, id: WindowId) -> f64 { - if let Some(window) = self.get(id) { - window.scale_factor() - } else { - 1.0 - } - } - - /// An iterator over all registered [`Window`]s - pub fn iter(&self) -> impl Iterator { - self.windows.values() - } - - /// A mutable iterator over all registered [`Window`]s - pub fn iter_mut(&mut self) -> impl Iterator { - self.windows.values_mut() - } - - pub fn remove(&mut self, id: WindowId) -> Option { - self.windows.remove(&id) - } -} diff --git a/crates/bevy_winit/src/lib.rs b/crates/bevy_winit/src/lib.rs index 40534dd8098eb..5d760820784ea 100644 --- a/crates/bevy_winit/src/lib.rs +++ b/crates/bevy_winit/src/lib.rs @@ -1,37 +1,48 @@ mod converters; +mod system; #[cfg(target_arch = "wasm32")] mod web_resize; mod winit_config; mod winit_windows; +use core::panic; + +use bevy_ecs::system::{SystemParam, SystemState, Insert, InsertBundle, Command}; +use raw_window_handle::HasRawWindowHandle; +use system::{ + create_window_system, destroy_windows, update_cursor_icon, update_cursor_lock_mode, + update_cursor_position, update_cursor_visibility, update_decorations, update_maximized, + update_minimized, update_position, update_present_mode, update_resizable, + update_resize_contraints, update_resolution, update_scale_factor_override, update_title, + update_window_mode, window_destroyed, create_window_direct, +}; + +use winit::event_loop; pub use winit_config::*; pub use winit_windows::*; use bevy_app::{App, AppExit, CoreStage, Plugin}; +use bevy_ecs::event::{Events, ManualEventReader}; use bevy_ecs::prelude::*; -use bevy_ecs::{ - event::{Events, ManualEventReader}, - world::World, -}; use bevy_input::{ keyboard::KeyboardInput, mouse::{MouseButtonInput, MouseMotion, MouseScrollUnit, MouseWheel}, touch::TouchInput, }; -use bevy_math::{ivec2, DVec2, Vec2}; +use bevy_math::{ivec2, DVec2, Vec2, IVec2}; use bevy_utils::{ tracing::{error, info, trace, warn}, Instant, }; use bevy_window::{ - CreateWindow, CursorEntered, CursorLeft, CursorMoved, FileDragAndDrop, ModifiesWindows, - ReceivedCharacter, RequestRedraw, WindowBackendScaleFactorChanged, WindowCloseRequested, - WindowClosed, WindowCreated, WindowFocused, WindowMoved, WindowResized, - WindowScaleFactorChanged, Windows, + CursorEntered, CursorLeft, CursorMoved, FileDragAndDrop, ModifiesWindows, + PrimaryWindow, ReceivedCharacter, RequestRedraw, Window, WindowBackendScaleFactorChanged, + WindowCloseRequested, WindowCreated, WindowCurrentlyFocused, WindowCursorPosition, + WindowFocused, WindowMoved, WindowPosition, WindowResized, WindowResolution, + WindowScaleFactorChanged, CreateWindowCommand, WindowDecorated, WindowTransparent, WindowTitle, WindowCursor, CursorIcon, WindowBundle, WindowHandle, WindowPresentation, WindowModeComponent, WindowResizable, WindowCanvas, }; use winit::{ - dpi::{LogicalSize, PhysicalPosition}, event::{self, DeviceEvent, Event, StartCause, WindowEvent}, event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget}, }; @@ -44,155 +55,55 @@ impl Plugin for WinitPlugin { app.init_non_send_resource::() .init_resource::() .set_runner(winit_runner) - .add_system_to_stage(CoreStage::PostUpdate, change_window.label(ModifiesWindows)); + // TODO: Verify that this actually works and does not cause any race-conditions or strange ordering issues + .add_system_set_to_stage( + CoreStage::PostUpdate, + SystemSet::new() + .label(ModifiesWindows) + .with_system(update_title) + .with_system(update_window_mode) + .with_system(update_decorations) + .with_system(update_scale_factor_override) + .with_system(update_resizable) + .with_system(update_position) + .with_system(update_minimized) + .with_system(update_maximized) + .with_system(update_resolution) + .with_system(update_cursor_icon) + .with_system(update_cursor_lock_mode) + .with_system(update_cursor_visibility) + .with_system(update_cursor_position) + .with_system(update_resize_contraints) + .with_system(update_present_mode) + .with_system(destroy_windows), // TODO: This should probably go last? + // .with_system(window_destroyed) // TODO: Unsure if this is the correct approach + ); #[cfg(target_arch = "wasm32")] app.add_plugin(web_resize::CanvasParentResizePlugin); - let event_loop = EventLoop::new(); - let mut create_window_reader = WinitCreateWindowReader::default(); - // Note that we create a window here "early" because WASM/WebGL requires the window to exist prior to initializing - // the renderer. - handle_create_window_events(&mut app.world, &event_loop, &mut create_window_reader.0); - app.insert_resource(create_window_reader) - .insert_non_send_resource(event_loop); - } -} -fn change_window( - mut winit_windows: NonSendMut, - mut windows: ResMut, - mut window_dpi_changed_events: EventWriter, - mut window_close_events: EventWriter, -) { - let mut removed_windows = vec![]; - 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 => { - window.set_fullscreen(Some(winit::window::Fullscreen::Exclusive( - get_best_videomode(&window.current_monitor().unwrap()), - ))); - } - bevy_window::WindowMode::SizedFullscreen => window.set_fullscreen(Some( - winit::window::Fullscreen::Exclusive(get_fitting_videomode( - &window.current_monitor().unwrap(), - width, - height, - )), - )), - 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::SetScaleFactor { scale_factor } => { - window_dpi_changed_events.send(WindowScaleFactorChanged { id, scale_factor }); - } - bevy_window::WindowCommand::SetResolution { - logical_resolution: (width, height), - scale_factor, - } => { - let window = winit_windows.get_window(id).unwrap(); - window.set_inner_size( - winit::dpi::LogicalSize::new(width, height) - .to_physical::(scale_factor), - ); - } - bevy_window::WindowCommand::SetPresentMode { .. } => (), - 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); - } - bevy_window::WindowCommand::SetCursorIcon { icon } => { - let window = winit_windows.get_window(id).unwrap(); - window.set_cursor_icon(converters::convert_cursor_icon(icon)); - } - bevy_window::WindowCommand::SetCursorLockMode { locked } => { - let window = winit_windows.get_window(id).unwrap(); - window - .set_cursor_grab(locked) - .unwrap_or_else(|e| error!("Unable to un/grab cursor: {}", e)); - } - bevy_window::WindowCommand::SetCursorVisibility { visible } => { - let window = winit_windows.get_window(id).unwrap(); - window.set_cursor_visible(visible); - } - bevy_window::WindowCommand::SetCursorPosition { position } => { - let window = winit_windows.get_window(id).unwrap(); - let inner_size = window.inner_size().to_logical::(window.scale_factor()); - window - .set_cursor_position(winit::dpi::LogicalPosition::new( - position.x, - inner_size.height - position.y, - )) - .unwrap_or_else(|e| error!("Unable to set cursor position: {}", e)); - } - bevy_window::WindowCommand::SetMaximized { maximized } => { - let window = winit_windows.get_window(id).unwrap(); - window.set_maximized(maximized); - } - bevy_window::WindowCommand::SetMinimized { minimized } => { - let window = winit_windows.get_window(id).unwrap(); - window.set_minimized(minimized); - } - bevy_window::WindowCommand::SetPosition { position } => { - let window = winit_windows.get_window(id).unwrap(); - window.set_outer_position(PhysicalPosition { - x: position[0], - 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, - }; + let mut system_state: SystemState<( + Commands, + EventReader, + EventWriter, + NonSendMut, + NonSendMut>, + )> = SystemState::new(&mut app.world); + let ( + mut commands, + mut create_window_commands, + mut window_created_events, + mut winit_windows, + mut event_loop, + ) = system_state.get_mut(&mut app.world); - window.set_min_inner_size(Some(min_inner_size)); - if constraints.max_width.is_finite() && constraints.max_height.is_finite() { - window.set_max_inner_size(Some(max_inner_size)); - } - } - bevy_window::WindowCommand::Close => { - // Since we have borrowed `windows` to iterate through them, we can't remove the window from it. - // Add the removal requests to a queue to solve this - removed_windows.push(id); - // No need to run any further commands - this drops the rest of the commands, although the `bevy_window::Window` will be dropped later anyway - break; - } - } - } - } - if !removed_windows.is_empty() { - for id in removed_windows { - // Close the OS window. (The `Drop` impl actually closes the window) - let _ = winit_windows.remove_window(id); - // Clean up our own data structures - windows.remove(id); - window_close_events.send(WindowClosed { id }); - } + // Here we need to create a winit-window and give it a WindowHandle which the renderer can use. + // It needs to be spawned before the start of the startup-stage, so we cannot use a regular system. + // Instead we need to create the window and spawn it using direct world access + create_window_system(commands, event_loop, create_window_commands, window_created_events, winit_windows); + + system_state.apply(&mut app.world); + + app.insert_non_send_resource(event_loop); } } @@ -243,6 +154,33 @@ pub fn winit_runner(app: App) { winit_runner_with(app); } +#[derive(SystemParam)] +struct WindowEvents<'w, 's> { + window_resized: EventWriter<'w, 's, WindowResized>, + window_close_requested: EventWriter<'w, 's, WindowCloseRequested>, + window_scale_factor_changed: EventWriter<'w, 's, WindowScaleFactorChanged>, + window_backend_scale_factor_changed: EventWriter<'w, 's, WindowBackendScaleFactorChanged>, + window_focused: EventWriter<'w, 's, WindowFocused>, + window_moved: EventWriter<'w, 's, WindowMoved>, +} + +#[derive(SystemParam)] +struct InputEvents<'w, 's> { + keyboard_input: EventWriter<'w, 's, KeyboardInput>, + character_input: EventWriter<'w, 's, ReceivedCharacter>, + mouse_button_input: EventWriter<'w, 's, MouseButtonInput>, + mouse_wheel_input: EventWriter<'w, 's, MouseWheel>, + touch_input: EventWriter<'w, 's, TouchInput>, + // mouse_motion: EventWriter<'w, 's, MouseMotion>, +} + +#[derive(SystemParam)] +struct CursorEvents<'w, 's> { + cursor_moved: EventWriter<'w, 's, CursorMoved>, + cursor_entered: EventWriter<'w, 's, CursorEntered>, + cursor_left: EventWriter<'w, 's, CursorLeft>, +} + // #[cfg(any( // target_os = "linux", // target_os = "dragonfly", @@ -279,19 +217,21 @@ impl Default for WinitPersistentState { } } -#[derive(Default)] -struct WinitCreateWindowReader(ManualEventReader); +// #[derive(Default)] +// struct WinitCreateWindowReader(ManualEventReader); +// TODO: Refactor this to work with new pattern pub fn winit_runner_with(mut app: App) { + // TODO: Understand what removing and adding this does let mut event_loop = app .world .remove_non_send_resource::>() .unwrap(); - let mut create_window_event_reader = app - .world - .remove_resource::() - .unwrap() - .0; + // let mut create_window_event_reader = app + // .world + // .remove_resource::() + // .unwrap() + // .0; let mut app_exit_event_reader = ManualEventReader::::default(); let mut redraw_event_reader = ManualEventReader::::default(); let mut winit_state = WinitPersistentState::default(); @@ -305,18 +245,27 @@ pub fn winit_runner_with(mut app: App) { let event_handler = move |event: Event<()>, event_loop: &EventLoopWindowTarget<()>, control_flow: &mut ControlFlow| { + // TODO move all system state fetch up here? + match event { event::Event::NewEvents(start) => { - let winit_config = app.world.resource::(); - let windows = app.world.resource::(); - let focused = windows.iter().any(|w| w.is_focused()); + // Fetch from the world + let mut system_state: SystemState<( + Res, + Query, With)>, + )> = SystemState::new(&mut app.world); + + let (winit_config, window_focused_query) = system_state.get(&mut app.world); + + let any_window_focused = !window_focused_query.is_empty(); + // Check if either the `WaitUntil` timeout was triggered by winit, or that same // amount of time has elapsed since the last app update. This manual check is needed // because we don't know if the criteria for an app update were met until the end of // the frame. let auto_timeout_reached = matches!(start, StartCause::ResumeTimeReached { .. }); let now = Instant::now(); - let manual_timeout_reached = match winit_config.update_mode(focused) { + let manual_timeout_reached = match winit_config.update_mode(any_window_focused) { UpdateMode::Continuous => false, UpdateMode::Reactive { max_wait } | UpdateMode::ReactiveLowPower { max_wait } => { @@ -332,13 +281,42 @@ pub fn winit_runner_with(mut app: App) { window_id: winit_window_id, .. } => { - let world = app.world.cell(); - let winit_windows = world.non_send_resource_mut::(); - let mut windows = world.resource_mut::(); - let window_id = - if let Some(window_id) = winit_windows.get_window_id(winit_window_id) { - window_id + // Fetch and prepare details from the world + let mut system_state: SystemState<( + Commands, + NonSend, + Query< + ( + Entity, + &mut WindowResolution, + &mut WindowCursorPosition, + &mut WindowPosition, + ), + With, + >, + Res, + WindowEvents, + InputEvents, + CursorEvents, + EventWriter, + )> = SystemState::new(&mut app.world); + let ( + mut commands, + winit_windows, + mut window_query, + primary_window, + mut window_events, + mut input_events, + mut cursor_events, + mut file_drag_and_drop_events, + ) = system_state.get_mut(&mut app.world); + + // Entity of this window + let window_entity = + if let Some(entity) = winit_windows.get_window_entity(winit_window_id) { + entity } else { + // TODO: This seems like it can cause problems now warn!( "Skipped event for unknown winit Window Id {:?}", winit_window_id @@ -346,84 +324,108 @@ pub fn winit_runner_with(mut app: App) { return; }; - let window = if let Some(window) = windows.get_mut(window_id) { - window - } else { - // If we're here, this window was previously opened - info!("Skipped event for closed window: {:?}", window_id); - return; - }; + // Reference to the Winit-window + let winit_window = winit_windows.get_window(window_entity).unwrap(); + + // TODO: Is there an edge-case introduced by removing this? + // let window = if let Some(window) = windows.get_mut(window_entity) { + // window + // } else { + // // If we're here, this window was previously opened + // info!("Skipped event for closed window: {:?}", window_entity); + // return; + // }; winit_state.low_power_event = true; match event { WindowEvent::Resized(size) => { - window.update_actual_size_from_backend(size.width, size.height); - let mut resize_events = world.resource_mut::>(); - resize_events.send(WindowResized { - id: window_id, - width: window.width(), - height: window.height(), - }); + if let Ok((_, mut resolution_component, _, _)) = + window_query.get_mut(window_entity) + { + // Update component + resolution_component + .update_actual_size_from_backend(size.width, size.height); + + // Send event to notify change + window_events.window_resized.send(WindowResized { + entity: window_entity, + width: resolution_component.width(), + height: resolution_component.height(), + }); + } else { + // TODO: Helpful panic comment + panic!("Window does not have a valid WindowResolution component"); + } } WindowEvent::CloseRequested => { - let mut window_close_requested_events = - world.resource_mut::>(); - window_close_requested_events.send(WindowCloseRequested { id: window_id }); + window_events + .window_close_requested + .send(WindowCloseRequested { + entity: window_entity, + }); } WindowEvent::KeyboardInput { ref input, .. } => { - let mut keyboard_input_events = - world.resource_mut::>(); - keyboard_input_events.send(converters::convert_keyboard_input(input)); + input_events + .keyboard_input + .send(converters::convert_keyboard_input(input)); } WindowEvent::CursorMoved { position, .. } => { - let mut cursor_moved_events = world.resource_mut::>(); - let winit_window = winit_windows.get_window(window_id).unwrap(); + // let winit_window = winit_windows.get_window(window_entity).unwrap(); let inner_size = winit_window.inner_size(); + // Components + let (_, window_resolution, mut window_cursor_position, _) = + window_query.get_mut(window_entity).expect("msg"); + + // TODO: Why is this necessary? Improve comment as to why // move origin to bottom left let y_position = inner_size.height as f64 - position.y; let physical_position = DVec2::new(position.x, y_position); - window - .update_cursor_physical_position_from_backend(Some(physical_position)); - cursor_moved_events.send(CursorMoved { - id: window_id, - position: (physical_position / window.scale_factor()).as_vec2(), + window_cursor_position + .update_position_from_backend(Some(physical_position)); + + // Event + cursor_events.cursor_moved.send(CursorMoved { + entity: window_entity, + position: (physical_position / window_resolution.scale_factor()) + .as_vec2(), }); } WindowEvent::CursorEntered { .. } => { - let mut cursor_entered_events = - world.resource_mut::>(); - cursor_entered_events.send(CursorEntered { id: window_id }); + cursor_events.cursor_entered.send(CursorEntered { + entity: window_entity, + }); } WindowEvent::CursorLeft { .. } => { - let mut cursor_left_events = world.resource_mut::>(); - window.update_cursor_physical_position_from_backend(None); - cursor_left_events.send(CursorLeft { id: window_id }); + // Component + let (_, _, mut window_cursor_position, _) = window_query + .get_mut(window_entity) + .expect("Window should have a WindowCursorComponent component"); + window_cursor_position.update_position_from_backend(None); + + // Event + cursor_events.cursor_left.send(CursorLeft { + entity: window_entity, + }); } WindowEvent::MouseInput { state, button, .. } => { - let mut mouse_button_input_events = - world.resource_mut::>(); - mouse_button_input_events.send(MouseButtonInput { + input_events.mouse_button_input.send(MouseButtonInput { button: converters::convert_mouse_button(button), state: converters::convert_element_state(state), }); } WindowEvent::MouseWheel { delta, .. } => match delta { event::MouseScrollDelta::LineDelta(x, y) => { - let mut mouse_wheel_input_events = - world.resource_mut::>(); - mouse_wheel_input_events.send(MouseWheel { + input_events.mouse_wheel_input.send(MouseWheel { unit: MouseScrollUnit::Line, x, y, }); } event::MouseScrollDelta::PixelDelta(p) => { - let mut mouse_wheel_input_events = - world.resource_mut::>(); - mouse_wheel_input_events.send(MouseWheel { + input_events.mouse_wheel_input.send(MouseWheel { unit: MouseScrollUnit::Pixel, x: p.x as f32, y: p.y as f32, @@ -431,24 +433,34 @@ pub fn winit_runner_with(mut app: App) { } }, WindowEvent::Touch(touch) => { - let mut touch_input_events = world.resource_mut::>(); + let (_, window_resolution, _, _) = window_query + .get(window_entity) + .expect("Window should have a WindowResolution component"); - let mut location = touch.location.to_logical(window.scale_factor()); + let mut location = + touch.location.to_logical(window_resolution.scale_factor()); // On a mobile window, the start is from the top while on PC/Linux/OSX from // bottom if cfg!(target_os = "android") || cfg!(target_os = "ios") { - let window_height = windows.primary().height(); - location.y = window_height - location.y; + // Get windows_resolution of the entity currently set as primary window + let primary_window_id = + primary_window.window.expect("Primary window should exist"); + let (_, primary_window_resolution, _, _) = + window_query.get(primary_window_id).expect( + "Primary window should have a valid WindowResolution component", + ); + location.y = primary_window_resolution.height() - location.y; } - touch_input_events.send(converters::convert_touch_input(touch, location)); + + // Event + input_events + .touch_input + .send(converters::convert_touch_input(touch, location)); } WindowEvent::ReceivedCharacter(c) => { - let mut char_input_events = - world.resource_mut::>(); - - char_input_events.send(ReceivedCharacter { - id: window_id, + input_events.character_input.send(ReceivedCharacter { + entity: window_entity, char: c, }); } @@ -456,84 +468,109 @@ pub fn winit_runner_with(mut app: App) { scale_factor, new_inner_size, } => { - let mut backend_scale_factor_change_events = - world.resource_mut::>(); - backend_scale_factor_change_events.send(WindowBackendScaleFactorChanged { - id: window_id, - scale_factor, - }); - let prior_factor = window.scale_factor(); - window.update_scale_factor_from_backend(scale_factor); - let new_factor = window.scale_factor(); - if let Some(forced_factor) = window.scale_factor_override() { + window_events.window_backend_scale_factor_changed.send( + WindowBackendScaleFactorChanged { + entity: window_entity, + scale_factor, + }, + ); + + // Components + let (_, mut window_resolution, _, _) = window_query + .get_mut(window_entity) + .expect("Window should have a WindowResolution component"); + + let prior_factor = window_resolution.scale_factor(); + window_resolution.update_scale_factor_from_backend(scale_factor); + let new_factor = window_resolution.scale_factor(); + + if let Some(forced_factor) = window_resolution.scale_factor_override() { // If there is a scale factor override, then force that to be used // Otherwise, use the OS suggested size // We have already told the OS about our resize constraints, so // the new_inner_size should take those into account *new_inner_size = winit::dpi::LogicalSize::new( - window.requested_width(), - window.requested_height(), + window_resolution.requested_width(), + window_resolution.requested_height(), ) .to_physical::(forced_factor); + // TODO: Should this not trigger a WindowsScaleFactorChanged? } else if approx::relative_ne!(new_factor, prior_factor) { - let mut scale_factor_change_events = - world.resource_mut::>(); - - scale_factor_change_events.send(WindowScaleFactorChanged { - id: window_id, - scale_factor, - }); + // Trigger a change event if they are approximately different + window_events.window_scale_factor_changed.send( + WindowScaleFactorChanged { + entity: window_entity, + scale_factor, + }, + ); } let new_logical_width = new_inner_size.width as f64 / new_factor; let new_logical_height = new_inner_size.height as f64 / new_factor; - if approx::relative_ne!(window.width() as f64, new_logical_width) - || approx::relative_ne!(window.height() as f64, new_logical_height) + if approx::relative_ne!(window_resolution.width() as f64, new_logical_width) + || approx::relative_ne!( + window_resolution.height() as f64, + new_logical_height + ) { - let mut resize_events = world.resource_mut::>(); - resize_events.send(WindowResized { - id: window_id, + window_events.window_resized.send(WindowResized { + entity: window_entity, width: new_logical_width as f32, height: new_logical_height as f32, }); } - window.update_actual_size_from_backend( + window_resolution.update_actual_size_from_backend( new_inner_size.width, new_inner_size.height, ); } WindowEvent::Focused(focused) => { - window.update_focused_status_from_backend(focused); - let mut focused_events = world.resource_mut::>(); - focused_events.send(WindowFocused { - id: window_id, + // Component + if focused { + commands + .entity(window_entity) + .insert(WindowCurrentlyFocused); + } else { + commands + .entity(window_entity) + .remove::(); + } + + // Event + window_events.window_focused.send(WindowFocused { + entity: window_entity, focused, }); } WindowEvent::DroppedFile(path_buf) => { - let mut events = world.resource_mut::>(); - events.send(FileDragAndDrop::DroppedFile { - id: window_id, + file_drag_and_drop_events.send(FileDragAndDrop::DroppedFile { + entity: window_entity, path_buf, }); } WindowEvent::HoveredFile(path_buf) => { - let mut events = world.resource_mut::>(); - events.send(FileDragAndDrop::HoveredFile { - id: window_id, + file_drag_and_drop_events.send(FileDragAndDrop::HoveredFile { + entity: window_entity, path_buf, }); } WindowEvent::HoveredFileCancelled => { - let mut events = world.resource_mut::>(); - events.send(FileDragAndDrop::HoveredFileCancelled { id: window_id }); + file_drag_and_drop_events.send(FileDragAndDrop::HoveredFileCancelled { + entity: window_entity, + }); } WindowEvent::Moved(position) => { let position = ivec2(position.x, position.y); - window.update_actual_position_from_backend(position); - let mut events = world.resource_mut::>(); - events.send(WindowMoved { - id: window_id, + + // Component + let (_, _, _, mut window_position) = window_query + .get_mut(window_entity) + .expect("Window should have a WindowPosition component"); + window_position.update_actual_position_from_backend(position); + + // Event + window_events.window_moved.send(WindowMoved { + entity: window_entity, position, }); } @@ -544,8 +581,11 @@ pub fn winit_runner_with(mut app: App) { event: DeviceEvent::MouseMotion { delta }, .. } => { - let mut mouse_motion_events = app.world.resource_mut::>(); - mouse_motion_events.send(MouseMotion { + let mut system_state: SystemState> = + SystemState::new(&mut app.world); + let mut mouse_motion = system_state.get_mut(&mut app.world); + + mouse_motion.send(MouseMotion { delta: Vec2::new(delta.0 as f32, delta.1 as f32), }); } @@ -556,15 +596,38 @@ pub fn winit_runner_with(mut app: App) { winit_state.active = true; } event::Event::MainEventsCleared => { - handle_create_window_events( - &mut app.world, + let mut system_state: SystemState<( + Commands, + EventReader, + EventWriter, + NonSendMut, + NonSendMut>, + Res, + Query, With)>, + )> = SystemState::new(&mut app.world); + let ( + mut commands, + mut create_window_commands, + mut window_created_events, + mut winit_windows, + mut event_loop, + winit_config, + window_focused_query, + ) = system_state.get_mut(&mut app.world); + + // Responsible for creating new windows + create_window_system( + commands, event_loop, - &mut create_window_event_reader, + create_window_commands, + window_created_events, + winit_windows, ); - let winit_config = app.world.resource::(); + let update = if winit_state.active { - let windows = app.world.resource::(); - let focused = windows.iter().any(|w| w.is_focused()); + // True if _any_ windows are currently being focused + // TODO: Do we need to fetch windows again since new ones might have been created and they might be focused? + let focused = !window_focused_query.is_empty(); match winit_config.update_mode(focused) { UpdateMode::Continuous | UpdateMode::Reactive { .. } => true, UpdateMode::ReactiveLowPower { .. } => { @@ -583,9 +646,17 @@ pub fn winit_runner_with(mut app: App) { } Event::RedrawEventsCleared => { { - let winit_config = app.world.resource::(); - let windows = app.world.resource::(); - let focused = windows.iter().any(|w| w.is_focused()); + // Fetch from world + let mut system_state: SystemState<( + Res, + Query, With)>, + )> = SystemState::new(&mut app.world); + + let (winit_config, window_focused_query) = system_state.get(&mut app.world); + + // True if _any_ windows are currently being focused + let focused = !window_focused_query.is_empty(); + let now = Instant::now(); use UpdateMode::*; *control_flow = match winit_config.update_mode(focused) { @@ -599,6 +670,7 @@ pub fn winit_runner_with(mut app: App) { } }; } + // This block needs to run after `app.update()` in `MainEventsCleared`. Otherwise, // we won't be able to see redraw requests until the next event, defeating the // purpose of a redraw request! @@ -620,45 +692,10 @@ pub fn winit_runner_with(mut app: App) { } }; + // If true, returns control from Winit back to the main Bevy loop if return_from_run { run_return(&mut event_loop, event_handler); } else { run(event_loop, event_handler); } } - -fn handle_create_window_events( - world: &mut World, - event_loop: &EventLoopWindowTarget<()>, - create_window_event_reader: &mut ManualEventReader, -) { - let world = world.cell(); - let mut winit_windows = world.non_send_resource_mut::(); - let mut windows = world.resource_mut::(); - let create_window_events = world.resource::>(); - let mut window_created_events = world.resource_mut::>(); - for create_window_event in create_window_event_reader.iter(&create_window_events) { - let window = winit_windows.create_window( - event_loop, - create_window_event.id, - &create_window_event.descriptor, - ); - windows.add(window); - window_created_events.send(WindowCreated { - id: create_window_event.id, - }); - - #[cfg(target_arch = "wasm32")] - { - let channel = world.resource_mut::(); - if create_window_event.descriptor.fit_canvas_to_parent { - let selector = if let Some(selector) = &create_window_event.descriptor.canvas { - selector - } else { - web_resize::WINIT_CANVAS_SELECTOR - }; - channel.listen_to_selector(create_window_event.id, selector); - } - } - } -} diff --git a/crates/bevy_winit/src/system.rs b/crates/bevy_winit/src/system.rs new file mode 100644 index 0000000000000..581bd975c2bff --- /dev/null +++ b/crates/bevy_winit/src/system.rs @@ -0,0 +1,534 @@ +use bevy_ecs::{ + entity::Entity, + event::{EventReader, EventWriter}, + prelude::{Added, With, World, Component}, + system::{Commands, NonSendMut, Query, RemovedComponents, InsertBundle, Command, Insert, SystemState}, component::TableStorage, +}; +use bevy_math::IVec2; +use bevy_utils::tracing::{error, info}; +use bevy_window::{ + CloseWindowCommand, CursorIcon, SetCursorIconCommand, SetCursorLockModeCommand, + SetCursorPositionCommand, SetCursorVisibilityCommand, SetDecorationsCommand, + SetMaximizedCommand, SetMinimizedCommand, SetPositionCommand, SetPresentModeCommand, + SetResizableCommand, SetResizeConstraintsCommand, SetResolutionCommand, + SetScaleFactorOverrideCommand, SetTitleCommand, SetWindowModeCommand, Window, WindowBundle, + WindowCanvas, WindowClosed, WindowCreated, WindowCurrentlyFocused, WindowCursor, + WindowCursorPosition, WindowDecorated, WindowHandle, WindowMaximized, WindowMinimized, + WindowModeComponent, WindowPosition, WindowPresentation, WindowResizable, WindowResolution, + WindowScaleFactorChanged, WindowTitle, WindowTransparent, CreateWindowCommand, +}; +use raw_window_handle::HasRawWindowHandle; +use winit::{ + dpi::{LogicalSize, PhysicalPosition}, + event_loop::EventLoop, +}; + +use crate::{converters, get_best_videomode, get_fitting_videomode, WinitWindows}; + +// TODO: Docs +/// System responsible for creating new windows whenever the Event has been sent +pub(crate) fn create_window_system( + mut commands: Commands, + mut event_loop: NonSendMut>, // &EventLoopWindowTarget<()>, // TODO: Not sure how this would work + mut create_window_commands: EventReader, + mut window_created_events: EventWriter, + mut winit_windows: NonSendMut, +) { + println!("entry"); + info!("Creating primary window"); + for event in create_window_commands.iter() { + info!("Creating window event"); + // TODO: This should be about spawning the WinitWindow that corresponds + let winit_window = + winit_windows.create_window(&event_loop, event.entity, &event.descriptor); + + let mut entity_commands = commands.entity(event.entity); + + // Prepare data + let position = winit_window + .outer_position() + .ok() + .map(|position| IVec2::new(position.x, position.y)); + let inner_size = winit_window.inner_size(); + + entity_commands.insert_bundle(WindowBundle { + window: Window, + handle: WindowHandle::new(winit_window.raw_window_handle()), + presentation: WindowPresentation::new(event.descriptor.present_mode), + mode: WindowModeComponent::new(event.descriptor.mode), + position: WindowPosition::new(position), + resolution: WindowResolution::new( + event.descriptor.width, + event.descriptor.height, + inner_size.width, + inner_size.height, + event.descriptor.scale_factor_override, + winit_window.scale_factor(), + ), + title: WindowTitle::new(event.descriptor.title.clone()), + cursor_position: WindowCursorPosition::new(None), + cursor: WindowCursor::new( + CursorIcon::Default, + event.descriptor.cursor_visible, + event.descriptor.cursor_locked, + ), + canvas: WindowCanvas::new( + event.descriptor.canvas.clone(), + event.descriptor.fit_canvas_to_parent, + ), + resize_constraints: event.descriptor.resize_constraints, + // TODO: Are newly created windows considered focused by default? + focused: WindowCurrentlyFocused, + }); + + // Optional marker components + if event.descriptor.resizable { + entity_commands.insert(WindowResizable); + } + + if event.descriptor.decorations { + entity_commands.insert(WindowDecorated); + } + + if event.descriptor.transparent { + entity_commands.insert(WindowTransparent); + } + + // TODO: Replace with separete `window_added`-system? See below + window_created_events.send(WindowCreated { + entity: event.entity, + }); + + // TODO: Fix this + #[cfg(target_arch = "wasm32")] + { + let channel = world.resource_mut::(); + if create_window_event.descriptor.fit_canvas_to_parent { + let selector = if let Some(selector) = &create_window_event.descriptor.canvas { + selector + } else { + web_resize::WINIT_CANVAS_SELECTOR + }; + channel.listen_to_selector(create_window_event.entity, selector); + } + } + } +} + +// TODO: Docs +/// System that sends a [`WindowCreated`] event once a new [`Window`] component has been created +pub(crate) fn window_added( + q: Query>, + mut writer: EventWriter, +) { + for entity in q.iter() { + writer.send(WindowCreated { entity }); + } +} + +// TODO: Docs +/// System responsible for destroying windows from commands +pub(crate) fn destroy_windows( + mut commands: Commands, + mut close_window_writer: EventWriter, + mut winit_windows: NonSendMut, + mut command_reader: EventReader, +) { + for event in command_reader.iter() { + // Close the OS window. (The `Drop` impl actually closes the window) + let _ = winit_windows.remove_window(event.entity); + + // Despawn the entity from the world + commands.entity(event.entity).despawn(); + + // Send event that the window has been closed + // TODO: Consider using the system below instead + close_window_writer.send(WindowClosed { + entity: event.entity, + }); + } +} + +// TODO: Docs +// TODO: Not sure if this is correct / better +/// System that detect that a window has been destroyed and sends an event as a result +pub(crate) fn window_destroyed( + removed: RemovedComponents, + mut writer: EventWriter, +) { + for entity in removed.iter() { + writer.send(WindowClosed { entity }); + } +} + +// TODO: Docs +pub(crate) fn update_title( + mut titles: Query<&mut WindowTitle, With>, + winit_windows: NonSendMut, + mut reader: EventReader, +) { + for event in reader.iter() { + let winit_window = winit_windows.get_window(event.entity).unwrap(); + // Set the winit title + winit_window.set_title(&event.title); + // Set the title in the component + if let Ok(mut window_title) = titles.get_mut(event.entity) { + // TODO: Remove the clone and somehow appease the borrow-checker instead + window_title.update_title_from_backend(event.title.clone()); + } else { + panic!("No WindowTitle on the entity in question"); + } + } +} + +// TODO: Docs +pub(crate) fn update_window_mode( + mut window_modes: Query<&mut WindowModeComponent, With>, + winit_windows: NonSendMut, + mut command_reader: EventReader, +) { + for event in command_reader.iter() { + let winit_window = winit_windows.get_window(event.entity).unwrap(); + + // Update Winit Window + match event.mode { + bevy_window::WindowMode::BorderlessFullscreen => { + winit_window.set_fullscreen(Some(winit::window::Fullscreen::Borderless(None))); + } + bevy_window::WindowMode::Fullscreen => { + winit_window.set_fullscreen(Some(winit::window::Fullscreen::Exclusive( + get_best_videomode(&winit_window.current_monitor().unwrap()), + ))); + } + bevy_window::WindowMode::SizedFullscreen => { + let (width, height) = event.resolution; + winit_window.set_fullscreen(Some(winit::window::Fullscreen::Exclusive( + get_fitting_videomode(&winit_window.current_monitor().unwrap(), width, height), + ))) + } + bevy_window::WindowMode::Windowed => winit_window.set_fullscreen(None), + } + + // Update components correspondinly + // TODO: Should also update resolution? + if let Ok(mut window_mode) = window_modes.get_mut(event.entity) { + window_mode.update_mode_from_backend(event.mode); + } + } +} + +// TODO: Docs +pub(crate) fn update_resolution( + mut components: Query<&mut WindowResolution, With>, + winit_windows: NonSendMut, + mut command_reader: EventReader, +) { + for event in command_reader.iter() { + let winit_window = winit_windows.get_window(event.entity).unwrap(); + + let (width, height) = event.logical_resolution; + + let physical_size = + winit::dpi::LogicalSize::new(width, height).to_physical::(event.scale_factor); + + // Update Winit + winit_window.set_inner_size(physical_size); + + // Update components + if let Ok(mut window_resolution) = components.get_mut(event.entity) { + // TODO: Is this casting f64 -> u32 correct / ok? + window_resolution.update_actual_size_from_backend( + physical_size.width as u32, + physical_size.height as u32, + ); + } else { + // TODO: helpful panic comment + panic!(); + } + } +} + +// TODO: Docs +pub(crate) fn update_cursor_position( + mut components: Query<&mut WindowCursorPosition, With>, + winit_windows: NonSendMut, + mut command_reader: EventReader, +) { + for event in command_reader.iter() { + let winit_window = winit_windows.get_window(event.entity).unwrap(); + + // Update Winit + // TODO: Fix type inconsitencies + let inner_size = winit_window + .inner_size() + .to_logical::(winit_window.scale_factor()); + + // This can take either a physical position (physical pixels ) + // or logical position (logical pixels) + winit_window + .set_cursor_position(winit::dpi::LogicalPosition::new( + event.position.x, + inner_size.height - event.position.y, + )) + .unwrap_or_else(|e| error!("Unable to set cursor position: {}", e)); + + // Update components + if let Ok(mut cursor_position) = components.get_mut(event.entity) { + cursor_position.update_position_from_backend(Some(event.position)); + } else { + // TODO: helpful panic comment + panic!(); + } + } +} + +// TODO: Docs +// Does this need to be a command? +// TODO: Check where this is actually being used +pub(crate) fn update_resize_contraints( + winit_windows: NonSendMut, + mut command_reader: EventReader, +) { + for event in command_reader.iter() { + let winit_window = winit_windows.get_window(event.entity).unwrap(); + + // Update Winit + let constraints = event.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, + }; + + winit_window.set_min_inner_size(Some(min_inner_size)); + if constraints.max_width.is_finite() && constraints.max_height.is_finite() { + winit_window.set_max_inner_size(Some(max_inner_size)); + } + + // Update components + } +} + +// TODO: Docs +pub(crate) fn update_cursor_icon( + mut components: Query<&mut WindowCursor, With>, + winit_windows: NonSendMut, + mut command_reader: EventReader, +) { + for event in command_reader.iter() { + let winit_window = winit_windows.get_window(event.entity).unwrap(); + + // Update Winit + winit_window.set_cursor_icon(converters::convert_cursor_icon(event.icon)); + + // Update components + if let Ok(mut window_cursor) = components.get_mut(event.entity) { + window_cursor.set_icon_from_backend(event.icon); + } else { + // TODO: helpful panic comment + panic!(); + } + } +} + +// TODO: Docs +pub(crate) fn update_cursor_lock_mode( + mut components: Query<&mut WindowCursor, With>, + winit_windows: NonSendMut, + mut command_reader: EventReader, +) { + for event in command_reader.iter() { + let winit_window = winit_windows.get_window(event.entity).unwrap(); + + // Update Winit + winit_window + .set_cursor_grab(event.locked) + .unwrap_or_else(|e| error!("Unable to un/grab cursor: {}", e)); + + // Update components + if let Ok(mut window_cursor) = components.get_mut(event.entity) { + window_cursor.set_locked_from_backend(event.locked); + } else { + // TODO: helpful panic comment + panic!(); + } + } +} + +// TODO: Docs +pub(crate) fn update_cursor_visibility( + mut components: Query<&mut WindowCursor, With>, + winit_windows: NonSendMut, + mut command_reader: EventReader, +) { + for event in command_reader.iter() { + let winit_window = winit_windows.get_window(event.entity).unwrap(); + + // Update Winit + winit_window.set_cursor_visible(event.visible); + + // Update components + if let Ok(mut window_cursor) = components.get_mut(event.entity) { + window_cursor.set_visible_from_backend(event.visible); + } else { + // TODO: helpful panic comment + panic!(); + } + } +} + +// TODO: Docs +pub(crate) fn update_present_mode( + mut components: Query<&mut WindowPresentation, With>, + mut command_reader: EventReader, +) { + for event in command_reader.iter() { + // Update Winit + // Present mode is only relevant for the renderer, so no need to do anything to Winit at this point + + // Update components + if let Ok(mut window_presentation) = components.get_mut(event.entity) { + window_presentation.update_present_mode_from_backend(event.present_mode); + } else { + // TODO: helpful panic comment + panic!(); + } + } +} + +// TODO: Docs +pub(crate) fn update_scale_factor_override( + mut components: Query<&mut WindowResolution, With>, + mut window_dpi_changed_events: EventWriter, + mut command_reader: EventReader, +) { + for event in command_reader.iter() { + // TODO: Implement and verify behaviour here + // window_dpi_changed_events.send(WindowScaleFactorChanged { + // entity: event.entity, + // scale_factor: event.scale_factor, + // }); + + if let Ok(mut window_resolution) = components.get_mut(event.entity) { + // TODO: Should this be scale_factor_override instead? + window_resolution.update_scale_factor_override(event.scale_factor); + } else { + // TODO: Helpful panic comment + panic!(); + } + } +} + +// TODO: Docs +// TODO: What happens if you try to apply decorations on something that already has decorations, or vice versa? +pub(crate) fn update_decorations( + mut commands: Commands, + winit_windows: NonSendMut, + mut command_reader: EventReader, +) { + for event in command_reader.iter() { + let winit_window = winit_windows.get_window(event.entity).unwrap(); + winit_window.set_decorations(event.decorations); + + if event.decorations { + // Add decoratiosn marker + commands.entity(event.entity).insert(WindowDecorated); + } else { + // remove decoration marker + commands.entity(event.entity).remove::(); + } + } +} + +// TODO: Docs +// TODO: What happens if you try to apply resizable on something that already is resizable, or vice versa? +pub(crate) fn update_resizable( + mut commands: Commands, + winit_windows: NonSendMut, + mut command_reader: EventReader, +) { + for event in command_reader.iter() { + let winit_window = winit_windows.get_window(event.entity).unwrap(); + + winit_window.set_resizable(event.resizable); + + if event.resizable { + // Add marker + commands.entity(event.entity).insert(WindowResizable); + } else { + // remove marker + commands.entity(event.entity).remove::(); + } + } +} + +// TODO: Docs +pub(crate) fn update_position( + mut components: Query<&mut WindowPosition, With>, + winit_windows: NonSendMut, + mut command_reader: EventReader, +) { + for event in command_reader.iter() { + let winit_window = winit_windows.get_window(event.entity).unwrap(); + + winit_window.set_outer_position(PhysicalPosition { + x: event.position[0], + y: event.position[1], + }); + + // TODO: When will position be Option<> ? + if let Ok(mut comp) = components.get_mut(event.entity) { + comp.update_actual_position_from_backend(event.position); + } else { + // TODO: helpful panic comment + panic!() + } + } +} + +// TODO: Docs +// TODO: What happens if you try to apply minimize on something that already is minimized, or vice versa? +pub(crate) fn update_minimized( + mut commands: Commands, + winit_windows: NonSendMut, + mut command_reader: EventReader, +) { + for event in command_reader.iter() { + let winit_window = winit_windows.get_window(event.entity).unwrap(); + + winit_window.set_minimized(event.minimized); + + if event.minimized { + // Add marker + commands.entity(event.entity).insert(WindowMinimized); + } else { + // remove marker + commands.entity(event.entity).remove::(); + } + } +} + +// TODO: Docs +// TODO: What happens if you try to apply maximize on something that already is maximized, or vice versa? +pub(crate) fn update_maximized( + mut commands: Commands, + winit_windows: NonSendMut, + mut command_reader: EventReader, +) { + for event in command_reader.iter() { + let winit_window = winit_windows.get_window(event.entity).unwrap(); + + winit_window.set_maximized(event.maximized); + + if event.maximized { + // Add marker + commands.entity(event.entity).insert(WindowMaximized); + } else { + // remove marker + commands.entity(event.entity).remove::(); + } + } +} diff --git a/crates/bevy_winit/src/winit_windows.rs b/crates/bevy_winit/src/winit_windows.rs index e5565092701b7..346b920572aa1 100644 --- a/crates/bevy_winit/src/winit_windows.rs +++ b/crates/bevy_winit/src/winit_windows.rs @@ -1,14 +1,13 @@ -use bevy_math::IVec2; +use bevy_ecs::entity::Entity; use bevy_utils::HashMap; -use bevy_window::{Window, WindowDescriptor, WindowId, WindowMode}; -use raw_window_handle::HasRawWindowHandle; +use bevy_window::{WindowDescriptor, WindowMode}; use winit::dpi::LogicalSize; #[derive(Debug, Default)] pub struct WinitWindows { pub windows: HashMap, - pub window_id_to_winit: HashMap, - pub winit_to_window_id: HashMap, + pub window_id_to_winit: HashMap, + pub winit_to_window_id: HashMap, // Some winit functions, such as `set_window_icon` can only be used from the main thread. If // they are used in another thread, the app will hang. This marker ensures `WinitWindows` is // only ever accessed with bevy's non-send functions and in NonSend systems. @@ -19,9 +18,9 @@ impl WinitWindows { pub fn create_window( &mut self, event_loop: &winit::event_loop::EventLoopWindowTarget<()>, - window_id: WindowId, + entity: Entity, window_descriptor: &WindowDescriptor, - ) -> Window { + ) -> &winit::window::Window { let mut winit_window_builder = winit::window::WindowBuilder::new(); winit_window_builder = match window_descriptor.mode { @@ -133,8 +132,8 @@ impl WinitWindows { winit_window.set_cursor_visible(window_descriptor.cursor_visible); - 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(entity, winit_window.id()); + self.winit_to_window_id.insert(winit_window.id(), entity); #[cfg(target_arch = "wasm32")] { @@ -152,37 +151,32 @@ impl WinitWindows { } } - let position = winit_window - .outer_position() - .ok() - .map(|position| IVec2::new(position.x, position.y)); - let inner_size = winit_window.inner_size(); - let scale_factor = winit_window.scale_factor(); - let raw_window_handle = winit_window.raw_window_handle(); + // TODO: Might be more elegant ways to get return the reference of the winit-window + let id = winit_window.id(); self.windows.insert(winit_window.id(), winit_window); - Window::new( - window_id, - window_descriptor, - inner_size.width, - inner_size.height, - scale_factor, - position, - raw_window_handle, - ) + let created_window = self + .windows + .get(&id) + .expect("Winit should alway have the window it just created"); + + created_window } - pub fn get_window(&self, id: WindowId) -> Option<&winit::window::Window> { + // TODO: Docs + pub fn get_window(&self, entity: Entity) -> Option<&winit::window::Window> { self.window_id_to_winit - .get(&id) - .and_then(|id| self.windows.get(id)) + .get(&entity) + .and_then(|winit_id| self.windows.get(winit_id)) } - pub fn get_window_id(&self, id: winit::window::WindowId) -> Option { - self.winit_to_window_id.get(&id).cloned() + // TODO: Docs + pub fn get_window_entity(&self, winit_id: winit::window::WindowId) -> Option { + self.winit_to_window_id.get(&winit_id).cloned() } - pub fn remove_window(&mut self, id: WindowId) -> Option { - let winit_id = self.window_id_to_winit.remove(&id)?; + // TODO: Docs + pub fn remove_window(&mut self, entity: Entity) -> Option { + let winit_id = self.window_id_to_winit.remove(&entity)?; // Don't remove from winit_to_window_id, to track that we used to know about this winit window self.windows.remove(&winit_id) } diff --git a/examples/3d/split_screen.rs b/examples/3d/split_screen.rs index b296f170ddb48..2c770cd854c62 100644 --- a/examples/3d/split_screen.rs +++ b/examples/3d/split_screen.rs @@ -4,7 +4,7 @@ use bevy::{ core_pipeline::clear_color::ClearColorConfig, prelude::*, render::camera::Viewport, - window::{WindowId, WindowResized}, + window::{PrimaryWindow, WindowResized, WindowResolution}, }; fn main() { @@ -79,17 +79,24 @@ struct LeftCamera; struct RightCamera; fn set_camera_viewports( - windows: Res, + primary_window: Res, + resolutions: Query<&WindowResolution, With>, mut resize_events: EventReader, mut left_camera: Query<&mut Camera, (With, Without)>, mut right_camera: Query<&mut Camera, With>, ) { + let primary_window_id = primary_window + .window + .expect("Should have a valid Primary window"); + // We need to dynamically resize the camera's viewports whenever the window size changes // so then each camera always takes up half the screen. // A resize_event is sent when the window is first created, allowing us to reuse this system for initial setup. for resize_event in resize_events.iter() { - if resize_event.id == WindowId::primary() { - let window = windows.primary(); + if resize_event.entity == primary_window_id { + let window = resolutions + .get(primary_window_id) + .expect("Primary window should have valid resolution"); let mut left_camera = left_camera.single_mut(); left_camera.viewport = Some(Viewport { physical_position: UVec2::new(0, 0), diff --git a/examples/ecs/parallel_query.rs b/examples/ecs/parallel_query.rs index 2ecebd5e2a31c..0e195284cc608 100644 --- a/examples/ecs/parallel_query.rs +++ b/examples/ecs/parallel_query.rs @@ -1,6 +1,9 @@ //! Illustrates parallel queries with `ParallelIterator`. -use bevy::prelude::*; +use bevy::{ + prelude::*, + window::{PrimaryWindow, WindowResolution}, +}; use rand::random; #[derive(Component, Deref)] @@ -38,10 +41,16 @@ fn move_system(mut sprites: Query<(&mut Transform, &Velocity)>) { } // Bounce sprites outside the window -fn bounce_system(windows: Res, mut sprites: Query<(&Transform, &mut Velocity)>) { - let window = windows.primary(); - let width = window.width(); - let height = window.height(); +fn bounce_system( + primary_window: Res, + resolutions: Query<&WindowResolution, With>, + mut sprites: Query<(&Transform, &mut Velocity)>, +) { + let resolution = resolutions + .get(primary_window.window.expect("Primary window should exist")) + .expect("Primary windows should have a valid WindowResolution component"); + let width = resolution.width(); + let height = resolution.height(); let left = width / -2.0; let right = width / 2.0; let bottom = height / -2.0; diff --git a/examples/games/contributors.rs b/examples/games/contributors.rs index ca0d513146235..e618ca5123dbd 100644 --- a/examples/games/contributors.rs +++ b/examples/games/contributors.rs @@ -1,6 +1,10 @@ //! This example displays each contributor to the bevy source code as a bouncing bevy-ball. -use bevy::{prelude::*, utils::HashSet}; +use bevy::{ + prelude::*, + utils::HashSet, + window::{PrimaryWindow, WindowResolution}, +}; use rand::{prelude::SliceRandom, Rng}; use std::{ env::VarError, @@ -253,18 +257,25 @@ fn velocity_system(time: Res