From d491071bdba854e8db77a5f675a2fefec45919e1 Mon Sep 17 00:00:00 2001 From: Andreas Monitzer Date: Mon, 3 Oct 2022 02:02:20 +0200 Subject: [PATCH] Simplified API by removing the redundant selector-based canvas resolver in the web target. --- crates/bevy_render/src/lib.rs | 2 +- crates/bevy_render/src/view/window.rs | 2 +- crates/bevy_window/Cargo.toml | 1 + crates/bevy_window/src/window.rs | 77 +++++++++++++++----------- crates/bevy_winit/src/lib.rs | 17 ++++-- crates/bevy_winit/src/web_resize.rs | 32 +++++++++++ crates/bevy_winit/src/winit_windows.rs | 20 +++---- 7 files changed, 103 insertions(+), 48 deletions(-) diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 0cd77ea856aa62..1a84eebe092324 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -148,7 +148,7 @@ impl Plugin for RenderPlugin { let windows = app.world.resource_mut::(); let raw_handle = windows.get_primary().map(|window| unsafe { #[cfg(target_arch = "wasm32")] - if let Some(canvas) = &window.canvas_element { + if let Some(canvas) = window.canvas() { return match canvas { bevy_window::Canvas::HtmlCanvas(canvas) => { instance.create_surface_from_canvas(canvas) diff --git a/crates/bevy_render/src/view/window.rs b/crates/bevy_render/src/view/window.rs index 1488d51a90848c..e07ca9d4e67810 100644 --- a/crates/bevy_render/src/view/window.rs +++ b/crates/bevy_render/src/view/window.rs @@ -95,7 +95,7 @@ fn extract_windows( size_changed: false, present_mode_changed: false, #[cfg(target_arch = "wasm32")] - canvas: window.canvas_element.clone(), + canvas: window.canvas().cloned(), }); // NOTE: Drop the swap chain frame here diff --git a/crates/bevy_window/Cargo.toml b/crates/bevy_window/Cargo.toml index f1402447072de8..f90b282d66bfc0 100644 --- a/crates/bevy_window/Cargo.toml +++ b/crates/bevy_window/Cargo.toml @@ -28,3 +28,4 @@ serde = { version = "1.0", features = ["derive"], optional = true } [target.'cfg(target_arch = "wasm32")'.dependencies] web-sys = "0.3" +wasm-bindgen = "0.2" \ No newline at end of file diff --git a/crates/bevy_window/src/window.rs b/crates/bevy_window/src/window.rs index 4de575e44cb11f..5101389cfa1ec3 100644 --- a/crates/bevy_window/src/window.rs +++ b/crates/bevy_window/src/window.rs @@ -3,6 +3,7 @@ use bevy_math::{DVec2, IVec2, UVec2, Vec2}; use bevy_reflect::{FromReflect, Reflect}; use bevy_utils::{tracing::warn, Uuid}; use raw_window_handle::RawWindowHandle; +use wasm_bindgen::JsCast; #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Reflect, FromReflect)] #[reflect_value(PartialEq, Hash)] @@ -209,9 +210,9 @@ pub struct Window { raw_window_handle: RawWindowHandleWrapper, focused: bool, mode: WindowMode, - canvas: Option, #[cfg(target_arch = "wasm32")] - pub canvas_element: Option, + canvas: Option, + #[cfg(target_arch = "wasm32")] fit_canvas_to_parent: bool, command_queue: Vec, } @@ -340,9 +341,9 @@ 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(), #[cfg(target_arch = "wasm32")] - canvas_element: window_descriptor.canvas_element.clone(), fit_canvas_to_parent: window_descriptor.fit_canvas_to_parent, command_queue: Vec::new(), } @@ -698,12 +699,12 @@ impl Window { }); } /// 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). + /// open, the [app will exit](bevy_app::AppExit). /// To disable this behaviour, set `exit_on_all_closed` on the [`WindowPlugin`] /// to `false` /// @@ -728,16 +729,13 @@ impl Window { self.raw_window_handle.clone() } - /// The "html canvas" element selector. + /// The canvas element. /// - /// 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. + /// If set, this canvas element will be used, rather than creating a new one. + #[cfg(target_arch = "wasm32")] #[inline] - pub fn canvas(&self) -> Option<&str> { - self.canvas.as_deref() + pub fn canvas(&self) -> Option<&Canvas> { + self.canvas.as_ref() } /// Whether or not to fit the canvas element's size to its parent element's size. @@ -745,8 +743,7 @@ impl Window { /// **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. + #[cfg(target_arch = "wasm32")] #[inline] pub fn fit_canvas_to_parent(&self) -> bool { self.fit_canvas_to_parent @@ -788,7 +785,9 @@ pub enum MonitorSelection { #[cfg(target_arch = "wasm32")] #[derive(Clone, Debug, PartialEq, Eq)] pub enum Canvas { + /// A canvas referenced by a regular HTML element. Does not have to be mounted to the DOM. HtmlCanvas(web_sys::HtmlCanvasElement), + /// An OffscreenCanvas element, for example as produced by `HtmlCanvasElement::transfer_control_to_offscreen()`. OffscreenCanvas(web_sys::OffscreenCanvas), } #[cfg(target_arch = "wasm32")] @@ -866,31 +865,20 @@ 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. + /// The canvas element. /// - /// 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. - pub canvas: Option, + /// If set, this canvas element will be used, rather than creating a new one. #[cfg(target_arch = "wasm32")] - pub canvas_element: Option, + pub canvas: Option, /// 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. + #[cfg(target_arch = "wasm32")] pub fit_canvas_to_parent: bool, } -#[cfg(target_arch = "wasm32")] -unsafe impl Send for WindowDescriptor {} -#[cfg(target_arch = "wasm32")] -unsafe impl Sync for WindowDescriptor {} - impl Default for WindowDescriptor { fn default() -> Self { WindowDescriptor { @@ -908,10 +896,37 @@ impl Default for WindowDescriptor { cursor_visible: true, mode: WindowMode::Windowed, transparent: false, + #[cfg(target_arch = "wasm32")] canvas: None, #[cfg(target_arch = "wasm32")] - canvas_element: None, fit_canvas_to_parent: false, } } } + +#[cfg(target_arch = "wasm32")] +impl WindowDescriptor { + /// Convenience function for setting the canvas to be used by this WindowDescriptor. + /// + /// The selector format used is a [CSS selector](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors). + /// It uses the first element matching the selector. + /// + /// Returns an `Err` if the selector format is invalid. Panics if it is run from a web worker. + /// + /// Returns `Ok(true)` if the canvas was found and `Ok(false)` otherwise. Note that matching a non-canvas element + /// also returns `Ok(false)`. + pub fn set_canvas_from_selector( + &mut self, + selector: &str, + ) -> Result { + let canvas = web_sys::window() + .unwrap() + .document() + .unwrap() + .query_selector(selector)?; + self.canvas = canvas + .and_then(|canvas| canvas.dyn_into().ok()) + .map(Canvas::HtmlCanvas); + Ok(self.canvas.is_some()) + } +} diff --git a/crates/bevy_winit/src/lib.rs b/crates/bevy_winit/src/lib.rs index 66804c5a426b42..71ae5939367198 100644 --- a/crates/bevy_winit/src/lib.rs +++ b/crates/bevy_winit/src/lib.rs @@ -717,12 +717,21 @@ fn handle_create_window_events( { 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 + if let Some(canvas) = &create_window_event.descriptor.canvas { + match canvas { + bevy_window::Canvas::HtmlCanvas(canvas) => { + channel.listen_to_element(create_window_event.id, canvas.clone()); + } + bevy_window::Canvas::OffscreenCanvas(_) => { + panic!("OffscreenCanvas not supported by winit!"); + } + } } else { - web_resize::WINIT_CANVAS_SELECTOR + channel.listen_to_selector( + create_window_event.id, + 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 index 7e289afdfc6752..9613b2c59cac3b 100644 --- a/crates/bevy_winit/src/web_resize.rs +++ b/crates/bevy_winit/src/web_resize.rs @@ -4,6 +4,7 @@ use bevy_ecs::prelude::*; use bevy_window::WindowId; use crossbeam_channel::{Receiver, Sender}; use wasm_bindgen::JsCast; +use web_sys::HtmlCanvasElement; use winit::dpi::LogicalSize; pub(crate) struct CanvasParentResizePlugin; @@ -49,6 +50,15 @@ fn get_size(selector: &str) -> Option> { )); } +fn get_size_element(element: &HtmlCanvasElement) -> Option> { + 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]"; impl Default for CanvasParentResizeEventChannel { @@ -81,4 +91,26 @@ impl CanvasParentResizeEventChannel { .unwrap(); closure.forget(); } + + pub(crate) fn listen_to_element(&self, window_id: WindowId, element: HtmlCanvasElement) { + let sender = self.sender.clone(); + let resize = move || { + if let Some(size) = get_size_element(&element) { + 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(); + } } diff --git a/crates/bevy_winit/src/winit_windows.rs b/crates/bevy_winit/src/winit_windows.rs index 4467574874c611..4d4cd4bea1550f 100644 --- a/crates/bevy_winit/src/winit_windows.rs +++ b/crates/bevy_winit/src/winit_windows.rs @@ -98,17 +98,15 @@ impl WinitWindows { use wasm_bindgen::JsCast; use winit::platform::web::WindowBuilderExtWebSys; - if let Some(selector) = &window_descriptor.canvas { - let window = web_sys::window().unwrap(); - let document = window.document().unwrap(); - let canvas = document - .query_selector(&selector) - .expect("Cannot query for canvas element."); - if let Some(canvas) = canvas { - let canvas = canvas.dyn_into::().ok(); - winit_window_builder = winit_window_builder.with_canvas(canvas); - } else { - panic!("Cannot find element: {}.", selector); + if let Some(canvas) = &window_descriptor.canvas { + match canvas { + bevy_window::Canvas::HtmlCanvas(canvas) => { + let canvas = canvas.dyn_ref::().cloned(); + winit_window_builder = winit_window_builder.with_canvas(canvas); + } + bevy_window::Canvas::OffscreenCanvas(_) => { + panic!("winit does not support OffscreenCanvas.") + } } } }