From 550bba3a0518bf046d686eb772e2569a1ba3fe50 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Wed, 11 May 2022 15:15:15 -0700 Subject: [PATCH 01/11] Optionally resize window canvas element to fit parent element --- crates/bevy_window/src/window.rs | 29 ++++++++-- crates/bevy_winit/Cargo.toml | 1 + crates/bevy_winit/src/lib.rs | 52 ++++++++++-------- crates/bevy_winit/src/web_resize.rs | 83 +++++++++++++++++++++++++++++ 4 files changed, 139 insertions(+), 26 deletions(-) create mode 100644 crates/bevy_winit/src/web_resize.rs diff --git a/crates/bevy_window/src/window.rs b/crates/bevy_window/src/window.rs index 47c03d085ce7e..bfa584568f5cf 100644 --- a/crates/bevy_window/src/window.rs +++ b/crates/bevy_window/src/window.rs @@ -165,8 +165,8 @@ pub struct Window { raw_window_handle: RawWindowHandleWrapper, focused: bool, mode: WindowMode, - #[cfg(target_arch = "wasm32")] - pub canvas: Option, + canvas: Option, + fit_canvas_to_parent: bool, command_queue: Vec, } @@ -266,8 +266,8 @@ impl Window { raw_window_handle: RawWindowHandleWrapper::new(raw_window_handle), focused: true, mode: window_descriptor.mode, - #[cfg(target_arch = "wasm32")] canvas: window_descriptor.canvas.clone(), + fit_canvas_to_parent: window_descriptor.fit_canvas_to_parent, command_queue: Vec::new(), } } @@ -584,6 +584,20 @@ impl Window { pub fn raw_window_handle(&self) -> RawWindowHandleWrapper { self.raw_window_handle.clone() } + + /// The "html canvas" element selector, if it was configured for this window. + /// This value does not do anything on non-web platforms. + #[inline] + pub fn canvas(&self) -> Option<&str> { + self.canvas.as_deref() + } + + /// Whether or not to fit the canvas element's size to its parent element's size. + /// This value does not do anything on non-web platforms. + #[inline] + pub fn fit_canvas_to_parent(&self) -> bool { + self.fit_canvas_to_parent + } } #[derive(Debug, Clone)] @@ -609,8 +623,13 @@ pub struct WindowDescriptor { /// macOS X transparent works with winit out of the box, so this issue might be related to: /// Windows 11 is related to pub transparent: bool, - #[cfg(target_arch = "wasm32")] + /// The "html canvas" element selector. If set, the given selector will be used to find a matching html canvas element, + /// rather than creating a new one. + /// This value does not do anything on non-web platforms. pub canvas: Option, + /// Whether or not to fit the canvas element's size to its parent element's size. + /// This value does not do anything on non-web platforms. + pub fit_canvas_to_parent: bool, } impl Default for WindowDescriptor { @@ -629,8 +648,8 @@ impl Default for WindowDescriptor { cursor_visible: true, mode: WindowMode::Windowed, transparent: false, - #[cfg(target_arch = "wasm32")] canvas: None, + fit_canvas_to_parent: false, } } } diff --git a/crates/bevy_winit/Cargo.toml b/crates/bevy_winit/Cargo.toml index d4e4eb49d85eb..e896dd6c400be 100644 --- a/crates/bevy_winit/Cargo.toml +++ b/crates/bevy_winit/Cargo.toml @@ -30,6 +30,7 @@ raw-window-handle = "0.4.2" winit = { version = "0.26.0", default-features = false } wasm-bindgen = { version = "0.2" } web-sys = "0.3" +crossbeam-channel = "0.5" [package.metadata.docs.rs] features = ["x11"] diff --git a/crates/bevy_winit/src/lib.rs b/crates/bevy_winit/src/lib.rs index 438b722cba698..11beb19e464fe 100644 --- a/crates/bevy_winit/src/lib.rs +++ b/crates/bevy_winit/src/lib.rs @@ -1,4 +1,6 @@ mod converters; +#[cfg(target_arch = "wasm32")] +mod web_resize; mod winit_config; mod winit_windows; @@ -44,9 +46,15 @@ impl Plugin for WinitPlugin { .init_resource::() .set_runner(winit_runner) .add_system_to_stage(CoreStage::PostUpdate, change_window.label(ModifiesWindows)); + #[cfg(target_arch = "wasm32")] + { + app.add_plugin(web_resize::CanvasParentResizePlugin); + } let event_loop = EventLoop::new(); - handle_initial_window_events(&mut app.world, &event_loop); - app.insert_non_send_resource(event_loop); + let mut create_window_reader = WinitCreateWindowReader::default(); + 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); } } @@ -254,12 +262,19 @@ impl Default for WinitPersistentState { } } +#[derive(Default)] +struct WinitCreateWindowReader(ManualEventReader); + pub fn winit_runner_with(mut app: App) { let mut event_loop = app .world .remove_non_send_resource::>() .unwrap(); - let mut create_window_event_reader = ManualEventReader::::default(); + 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(); @@ -267,6 +282,7 @@ pub fn winit_runner_with(mut app: App) { .insert_non_send_resource(event_loop.create_proxy()); let return_from_run = app.world.resource::().return_from_run; + trace!("Entering winit event loop"); let event_handler = move |event: Event<()>, @@ -609,24 +625,18 @@ fn handle_create_window_events( window_created_events.send(WindowCreated { id: create_window_event.id, }); - } -} -fn handle_initial_window_events(world: &mut World, event_loop: &EventLoop<()>) { - let world = world.cell(); - let mut winit_windows = world.non_send_resource_mut::(); - let mut windows = world.resource_mut::(); - let mut create_window_events = world.resource_mut::>(); - let mut window_created_events = world.resource_mut::>(); - for create_window_event in create_window_events.drain() { - 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/web_resize.rs b/crates/bevy_winit/src/web_resize.rs new file mode 100644 index 0000000000000..da1d5a85ee708 --- /dev/null +++ b/crates/bevy_winit/src/web_resize.rs @@ -0,0 +1,83 @@ +use crate::WinitWindows; +use bevy_app::{App, Plugin}; +use bevy_ecs::prelude::*; +use bevy_window::WindowId; +use crossbeam_channel::{Receiver, Sender}; +use wasm_bindgen::JsCast; +use winit::dpi::LogicalSize; + +pub struct CanvasParentResizePlugin; + +impl Plugin for CanvasParentResizePlugin { + fn build(&self, app: &mut App) { + app.init_resource::() + .add_system(canvas_parent_resize_event_handler); + } +} + +struct ResizeEvent { + size: LogicalSize, + window_id: WindowId, +} + +pub(crate) struct CanvasParentResizeEventChannel { + sender: Sender, + receiver: Receiver, +} + +fn canvas_parent_resize_event_handler( + winit_windows: Res, + resize_events: Res, +) { + for event in resize_events.receiver.try_iter() { + if let Some(window) = winit_windows.get_window(event.window_id) { + window.set_inner_size(event.size); + } + } +} + +fn get_size(selector: &str) -> Option> { + let win = web_sys::window().unwrap(); + let doc = win.document().unwrap(); + let element = doc.query_selector(selector).ok()??; + let parent_element = element.parent_element()?; + let rect = parent_element.get_bounding_client_rect(); + return Some(winit::dpi::LogicalSize::new( + rect.width() as f32, + rect.height() as f32, + )); +} + +pub(crate) const WINIT_CANVAS_SELECTOR: &str = "canvas[data-raw-handle=\"1\"]"; + +impl Default for CanvasParentResizeEventChannel { + fn default() -> Self { + let (sender, receiver) = crossbeam_channel::unbounded(); + return Self { sender, receiver }; + } +} + +impl CanvasParentResizeEventChannel { + pub(crate) fn listen_to_selector(&self, window_id: WindowId, selector: &str) { + let sender = self.sender.clone(); + let owned_selector = selector.to_string(); + let resize = move || { + if let Some(size) = get_size(&owned_selector) { + sender.send(ResizeEvent { size, window_id }).unwrap(); + } + }; + + // ensure resize happens on startup + resize(); + + let closure = wasm_bindgen::closure::Closure::wrap(Box::new(move |_: web_sys::Event| { + resize(); + }) as Box); + let window = web_sys::window().unwrap(); + + window + .add_event_listener_with_callback("resize", closure.as_ref().unchecked_ref()) + .unwrap(); + closure.forget(); + } +} From 4c17893a54ee99eb3b06d55a6c9fe85f73f6f9bc Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Thu, 12 May 2022 12:54:24 -0700 Subject: [PATCH 02/11] Select any canvas with data-raw-handle attribute --- crates/bevy_winit/src/web_resize.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_winit/src/web_resize.rs b/crates/bevy_winit/src/web_resize.rs index da1d5a85ee708..de9ac2f085cd0 100644 --- a/crates/bevy_winit/src/web_resize.rs +++ b/crates/bevy_winit/src/web_resize.rs @@ -48,7 +48,7 @@ fn get_size(selector: &str) -> Option> { )); } -pub(crate) const WINIT_CANVAS_SELECTOR: &str = "canvas[data-raw-handle=\"1\"]"; +pub(crate) const WINIT_CANVAS_SELECTOR: &str = "canvas[data-raw-handle]"; impl Default for CanvasParentResizeEventChannel { fn default() -> Self { From 1ccae7e0fa31d764cfdcba1da5cd95219978d0b5 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Fri, 20 May 2022 15:26:01 -0700 Subject: [PATCH 03/11] Update crates/bevy_window/src/window.rs Co-authored-by: Alice Cecile --- crates/bevy_window/src/window.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/bevy_window/src/window.rs b/crates/bevy_window/src/window.rs index bfa584568f5cf..3acfc990f0d1f 100644 --- a/crates/bevy_window/src/window.rs +++ b/crates/bevy_window/src/window.rs @@ -586,7 +586,8 @@ impl Window { } /// The "html canvas" element selector, if it was configured for this window. - /// This value does not do anything on non-web platforms. + /// + /// This value has no effect on non-web platforms. #[inline] pub fn canvas(&self) -> Option<&str> { self.canvas.as_deref() From 2134a5571c83cd5569fe07326478aecad05a68f8 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Fri, 20 May 2022 15:26:07 -0700 Subject: [PATCH 04/11] Update crates/bevy_window/src/window.rs Co-authored-by: Alice Cecile --- crates/bevy_window/src/window.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/bevy_window/src/window.rs b/crates/bevy_window/src/window.rs index 3acfc990f0d1f..f52c4bb9d3187 100644 --- a/crates/bevy_window/src/window.rs +++ b/crates/bevy_window/src/window.rs @@ -594,7 +594,8 @@ impl Window { } /// Whether or not to fit the canvas element's size to its parent element's size. - /// This value does not do anything on non-web platforms. + /// + /// This value has no effect on non-web platforms. #[inline] pub fn fit_canvas_to_parent(&self) -> bool { self.fit_canvas_to_parent From 2e1dbf6d999870be1acdd73a918b3fc78577dd71 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Fri, 20 May 2022 15:26:21 -0700 Subject: [PATCH 05/11] Update crates/bevy_window/src/window.rs Co-authored-by: Alice Cecile --- crates/bevy_window/src/window.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_window/src/window.rs b/crates/bevy_window/src/window.rs index f52c4bb9d3187..1ddff6011e075 100644 --- a/crates/bevy_window/src/window.rs +++ b/crates/bevy_window/src/window.rs @@ -625,7 +625,7 @@ pub struct WindowDescriptor { /// macOS X transparent works with winit out of the box, so this issue might be related to: /// Windows 11 is related to pub transparent: bool, - /// The "html canvas" element selector. If set, the given selector will be used to find a matching html canvas element, + /// The "html canvas" element selector. If set, this selector will be used to find a matching html canvas element, /// rather than creating a new one. /// This value does not do anything on non-web platforms. pub canvas: Option, From 62d851128019a63dcf9580b192907c9d0fd26cee Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Fri, 20 May 2022 15:36:16 -0700 Subject: [PATCH 06/11] Add selector doc link --- crates/bevy_window/src/window.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/bevy_window/src/window.rs b/crates/bevy_window/src/window.rs index 1ddff6011e075..44a827903ed39 100644 --- a/crates/bevy_window/src/window.rs +++ b/crates/bevy_window/src/window.rs @@ -586,6 +586,7 @@ impl Window { } /// The "html canvas" element selector, if it was configured for this window. + /// Uses the CSS selector format: https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector. /// /// This value has no effect on non-web platforms. #[inline] @@ -594,7 +595,7 @@ impl Window { } /// Whether or not to fit the canvas element's size to its parent element's size. - /// + /// /// This value has no effect on non-web platforms. #[inline] pub fn fit_canvas_to_parent(&self) -> bool { @@ -627,6 +628,7 @@ pub struct WindowDescriptor { pub transparent: bool, /// The "html canvas" element selector. If set, this selector will be used to find a matching html canvas element, /// rather than creating a new one. + /// Uses the CSS selector format: https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector. /// This value does not do anything on non-web platforms. pub canvas: Option, /// Whether or not to fit the canvas element's size to its parent element's size. From 6425b71845ce990cb6cc1f6ff4ae66e459a62577 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Fri, 20 May 2022 15:36:23 -0700 Subject: [PATCH 07/11] remove braces --- crates/bevy_winit/src/lib.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/bevy_winit/src/lib.rs b/crates/bevy_winit/src/lib.rs index 11beb19e464fe..d686edb1fdbdb 100644 --- a/crates/bevy_winit/src/lib.rs +++ b/crates/bevy_winit/src/lib.rs @@ -47,9 +47,7 @@ impl Plugin for WinitPlugin { .set_runner(winit_runner) .add_system_to_stage(CoreStage::PostUpdate, change_window.label(ModifiesWindows)); #[cfg(target_arch = "wasm32")] - { - app.add_plugin(web_resize::CanvasParentResizePlugin); - } + app.add_plugin(web_resize::CanvasParentResizePlugin); let event_loop = EventLoop::new(); let mut create_window_reader = WinitCreateWindowReader::default(); handle_create_window_events(&mut app.world, &event_loop, &mut create_window_reader.0); From 0f982ff60cdfd1c837385c2144096bd67170d190 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Fri, 20 May 2022 15:36:30 -0700 Subject: [PATCH 08/11] pub(crate) --- crates/bevy_winit/src/web_resize.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_winit/src/web_resize.rs b/crates/bevy_winit/src/web_resize.rs index de9ac2f085cd0..637dc06e0eaed 100644 --- a/crates/bevy_winit/src/web_resize.rs +++ b/crates/bevy_winit/src/web_resize.rs @@ -6,7 +6,7 @@ use crossbeam_channel::{Receiver, Sender}; use wasm_bindgen::JsCast; use winit::dpi::LogicalSize; -pub struct CanvasParentResizePlugin; +pub(crate) struct CanvasParentResizePlugin; impl Plugin for CanvasParentResizePlugin { fn build(&self, app: &mut App) { From 813ca0b453a918c46e23ea88bd7323c218710053 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Fri, 20 May 2022 15:40:29 -0700 Subject: [PATCH 09/11] add window init comment --- crates/bevy_winit/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/bevy_winit/src/lib.rs b/crates/bevy_winit/src/lib.rs index d686edb1fdbdb..92013db9bd97c 100644 --- a/crates/bevy_winit/src/lib.rs +++ b/crates/bevy_winit/src/lib.rs @@ -50,6 +50,8 @@ impl Plugin for WinitPlugin { 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); From ec8930361f33d10e0f11a86289eb78634dd5f76a Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Fri, 20 May 2022 15:46:35 -0700 Subject: [PATCH 10/11] warn about size feedback loops --- crates/bevy_window/src/window.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/crates/bevy_window/src/window.rs b/crates/bevy_window/src/window.rs index 44a827903ed39..a8772f4ec52e4 100644 --- a/crates/bevy_window/src/window.rs +++ b/crates/bevy_window/src/window.rs @@ -596,6 +596,10 @@ impl Window { /// Whether or not to fit the canvas element's size to its parent element's size. /// + /// **Warning**: this will not behave as expected for parents that set their size according to the size of their + /// children. This creates a "feedback loop" that will result in the canvas growing on each resize. When using this + /// feature, ensure the parent's size is not affected by its children. + /// /// This value has no effect on non-web platforms. #[inline] pub fn fit_canvas_to_parent(&self) -> bool { @@ -632,7 +636,12 @@ pub struct WindowDescriptor { /// This value does not do anything on non-web platforms. pub canvas: Option, /// Whether or not to fit the canvas element's size to its parent element's size. - /// This value does not do anything on non-web platforms. + /// + /// **Warning**: this will not behave as expected for parents that set their size according to the size of their + /// children. This creates a "feedback loop" that will result in the canvas growing on each resize. When using this + /// feature, ensure the parent's size is not affected by its children. + /// + /// This value has no effect on non-web platforms. pub fit_canvas_to_parent: bool, } From bca882683e3fe0feceea53c0fa886953fdf20cd4 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Fri, 20 May 2022 15:53:47 -0700 Subject: [PATCH 11/11] link format / update doc --- crates/bevy_window/src/window.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/crates/bevy_window/src/window.rs b/crates/bevy_window/src/window.rs index a8772f4ec52e4..05b5388ec5027 100644 --- a/crates/bevy_window/src/window.rs +++ b/crates/bevy_window/src/window.rs @@ -585,8 +585,9 @@ impl Window { self.raw_window_handle.clone() } - /// The "html canvas" element selector, if it was configured for this window. - /// Uses the CSS selector format: https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector. + /// The "html canvas" element selector. If set, this selector will be used to find a matching html canvas element, + /// rather than creating a new one. + /// Uses the [CSS selector format](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector). /// /// This value has no effect on non-web platforms. #[inline] @@ -632,8 +633,9 @@ pub struct WindowDescriptor { pub transparent: bool, /// The "html canvas" element selector. If set, this selector will be used to find a matching html canvas element, /// rather than creating a new one. - /// Uses the CSS selector format: https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector. - /// This value does not do anything on non-web platforms. + /// Uses the [CSS selector format](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector). + /// + /// This value has no effect on non-web platforms. pub canvas: Option, /// Whether or not to fit the canvas element's size to its parent element's size. ///