diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 5913b59bc68dd1..c7f1510d9e4677 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -154,6 +154,14 @@ impl Plugin for RenderPlugin { Some(instance.create_surface(&handle.get_handle())) } AbstractWindowHandle::Virtual => None, + #[cfg(target_arch = "wasm32")] + AbstractWindowHandle::HtmlCanvas(canvas) => { + Some(instance.create_surface_from_canvas(&canvas)) + } + #[cfg(target_arch = "wasm32")] + AbstractWindowHandle::OffscreenCanvas(canvas) => { + Some(instance.create_surface_from_offscreen_canvas(&canvas)) + } } }); raw_handle diff --git a/crates/bevy_render/src/view/window.rs b/crates/bevy_render/src/view/window.rs index f8f8a3254e8f43..90c50502a5181d 100644 --- a/crates/bevy_render/src/view/window.rs +++ b/crates/bevy_render/src/view/window.rs @@ -173,6 +173,16 @@ pub fn prepare_windows( // NOTE: On some OSes this MUST be called from the main thread. render_instance.create_surface(&handle.get_handle()) }), + #[cfg(target_arch = "wasm32")] + AbstractWindowHandle::HtmlCanvas(canvas) => window_surfaces + .surfaces + .entry(window.id) + .or_insert_with(|| render_instance.create_surface_from_canvas(canvas)), + #[cfg(target_arch = "wasm32")] + AbstractWindowHandle::OffscreenCanvas(canvas) => window_surfaces + .surfaces + .entry(window.id) + .or_insert_with(|| render_instance.create_surface_from_offscreen_canvas(canvas)), AbstractWindowHandle::Virtual => continue, }; 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 8022d3d13dcc22..a9235d39cf11a9 100644 --- a/crates/bevy_window/src/window.rs +++ b/crates/bevy_window/src/window.rs @@ -3,6 +3,10 @@ use bevy_math::{DVec2, IVec2, UVec2, Vec2}; use bevy_reflect::{FromReflect, Reflect}; use bevy_utils::{tracing::warn, Uuid}; use raw_window_handle::RawWindowHandle; +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::JsCast; +#[cfg(target_arch = "wasm32")] +use web_sys::{HtmlCanvasElement, OffscreenCanvas}; #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Reflect, FromReflect)] #[reflect_value(PartialEq, Hash)] @@ -164,8 +168,17 @@ pub enum AbstractWindowHandle { /// for creating and presenting surface textures and inserting them into /// [`ExtractedWindow`](https://docs.rs/bevy/*/bevy/render/view/struct.ExtractedWindow.html). Virtual, + #[cfg(target_arch = "wasm32")] + HtmlCanvas(web_sys::HtmlCanvasElement), + #[cfg(target_arch = "wasm32")] + OffscreenCanvas(web_sys::OffscreenCanvas), } +#[cfg(target_arch = "wasm32")] +unsafe impl Send for AbstractWindowHandle {} +#[cfg(target_arch = "wasm32")] +unsafe impl Sync for AbstractWindowHandle {} + /// An operating system or virtual window that can present content and receive user input. /// /// To create a window, use a [`EventWriter`](`crate::CreateWindow`). @@ -224,7 +237,7 @@ pub struct Window { window_handle: AbstractWindowHandle, focused: bool, mode: WindowMode, - canvas: Option, + #[allow(dead_code)] fit_canvas_to_parent: bool, command_queue: Vec, } @@ -355,7 +368,6 @@ impl Window { )), 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(), } @@ -393,7 +405,103 @@ impl Window { window_handle: AbstractWindowHandle::Virtual, 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(), + } + } + + /// Creates a new [`Window`] from a canvas. + /// + /// See [`AbstractWindowHandle::HtmlCanvas`]. + #[cfg(target_arch = "wasm32")] + pub fn new_canvas( + id: WindowId, + window_descriptor: &WindowDescriptor, + canvas: HtmlCanvasElement, + ) -> Self { + let size = canvas.get_bounding_client_rect(); + Window { + id, + requested_width: window_descriptor.width, + requested_height: window_descriptor.height, + position: None, + physical_width: size.width() as _, + physical_height: size.height() as _, + resize_constraints: window_descriptor.resize_constraints, + scale_factor_override: window_descriptor.scale_factor_override, + backend_scale_factor: web_sys::window().unwrap().device_pixel_ratio(), + 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, + window_handle: AbstractWindowHandle::HtmlCanvas(canvas), + focused: true, + mode: window_descriptor.mode, + fit_canvas_to_parent: window_descriptor.fit_canvas_to_parent, + command_queue: Vec::new(), + } + } + + /// Creates a new [`Window`] from a selector to a canvas. + /// + /// 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(None) when the element could not be found with the selector. + /// + /// See [`AbstractWindowHandle::HtmlCanvas`]. + #[cfg(target_arch = "wasm32")] + pub fn new_canvas_selector( + id: WindowId, + window_descriptor: &WindowDescriptor, + selector: &str, + ) -> Result, wasm_bindgen::JsValue> { + Ok(web_sys::window() + .unwrap() + .document() + .unwrap() + .query_selector(selector)? + .and_then(|element| element.dyn_into().ok()) + .map(|canvas| Self::new_canvas(id, window_descriptor, canvas))) + } + + /// Creates a new [`Window`] from an offscreen canvas. + /// + /// See [`AbstractWindowHandle::OffscreenCanvas`]. + #[cfg(target_arch = "wasm32")] + pub fn new_offscreen_canvas( + id: WindowId, + window_descriptor: &WindowDescriptor, + canvas: OffscreenCanvas, + scale_factor: f64, + ) -> Self { + Window { + id, + requested_width: window_descriptor.width, + requested_height: window_descriptor.height, + position: None, + physical_width: canvas.width() as _, + physical_height: canvas.height() as _, + 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, + window_handle: AbstractWindowHandle::OffscreenCanvas(canvas), + focused: true, + mode: window_descriptor.mode, fit_canvas_to_parent: window_descriptor.fit_canvas_to_parent, command_queue: Vec::new(), } @@ -750,12 +858,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` /// @@ -780,18 +888,6 @@ impl Window { self.window_handle.clone() } - /// 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] - 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. /// /// **Warning**: this will not behave as expected for parents that set their size according to the size of their @@ -907,14 +1003,6 @@ 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, 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, /// 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 @@ -942,7 +1030,6 @@ impl Default for WindowDescriptor { cursor_visible: true, mode: WindowMode::Windowed, transparent: false, - canvas: None, fit_canvas_to_parent: false, } } diff --git a/crates/bevy_winit/src/winit_windows.rs b/crates/bevy_winit/src/winit_windows.rs index 4467574874c611..b5e31d61c9ce41 100644 --- a/crates/bevy_winit/src/winit_windows.rs +++ b/crates/bevy_winit/src/winit_windows.rs @@ -93,26 +93,6 @@ impl WinitWindows { #[allow(unused_mut)] let mut winit_window_builder = winit_window_builder.with_title(&window_descriptor.title); - #[cfg(target_arch = "wasm32")] - { - 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); - } - } - } - let winit_window = winit_window_builder.build(event_loop).unwrap(); if window_descriptor.mode == WindowMode::Windowed { @@ -174,16 +154,14 @@ impl WinitWindows { { use winit::platform::web::WindowExtWebSys; - if window_descriptor.canvas.is_none() { - let canvas = winit_window.canvas(); + let canvas = winit_window.canvas(); - let window = web_sys::window().unwrap(); - let document = window.document().unwrap(); - let body = document.body().unwrap(); + let window = web_sys::window().unwrap(); + let document = window.document().unwrap(); + let body = document.body().unwrap(); - body.append_child(&canvas) - .expect("Append canvas to HTML body."); - } + body.append_child(&canvas) + .expect("Append canvas to HTML body."); } let position = winit_window