Skip to content

Commit

Permalink
Implemented the ability to specify a canvas directly on the wasm targ…
Browse files Browse the repository at this point in the history
…et for winit-based applications.
  • Loading branch information
anlumo committed Oct 18, 2022
1 parent a86f771 commit d9609c4
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 15 deletions.
44 changes: 43 additions & 1 deletion crates/bevy_window/src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ pub enum AbstractWindowHandle {
/// [`ExtractedWindow`](https://docs.rs/bevy/*/bevy/render/view/struct.ExtractedWindow.html).
Virtual,
#[cfg(target_arch = "wasm32")]
HtmlCanvas(web_sys::HtmlCanvasElement),
HtmlCanvas(HtmlCanvasElement),
#[cfg(target_arch = "wasm32")]
OffscreenCanvas(web_sys::OffscreenCanvas),
}
Expand Down Expand Up @@ -290,6 +290,7 @@ pub struct Window {
window_handle: AbstractWindowHandle,
focused: bool,
mode: WindowMode,
#[cfg(target_arch = "wasm32")]
fit_canvas_to_parent: bool,
command_queue: Vec<WindowCommand>,
}
Expand Down Expand Up @@ -420,6 +421,7 @@ impl Window {
)),
focused: true,
mode: window_descriptor.mode,
#[cfg(target_arch = "wasm32")]
fit_canvas_to_parent: window_descriptor.fit_canvas_to_parent,
command_queue: Vec::new(),
}
Expand Down Expand Up @@ -457,6 +459,7 @@ impl Window {
window_handle: AbstractWindowHandle::Virtual,
focused: true,
mode: window_descriptor.mode,
#[cfg(target_arch = "wasm32")]
fit_canvas_to_parent: window_descriptor.fit_canvas_to_parent,
command_queue: Vec::new(),
}
Expand Down Expand Up @@ -948,6 +951,7 @@ impl Window {
/// 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
Expand Down Expand Up @@ -986,6 +990,15 @@ pub enum MonitorSelection {
Index(usize),
}

#[cfg(target_arch = "wasm32")]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SendSyncCanvas(pub HtmlCanvasElement);

#[cfg(target_arch = "wasm32")]
unsafe impl Send for SendSyncCanvas {}
#[cfg(target_arch = "wasm32")]
unsafe impl Sync for SendSyncCanvas {}

/// Describes the information needed for creating a window.
///
/// This should be set up before adding the [`WindowPlugin`](crate::WindowPlugin).
Expand Down Expand Up @@ -1056,13 +1069,16 @@ pub struct WindowDescriptor {
/// macOS X transparent works with winit out of the box, so this issue might be related to: <https://github.com/gfx-rs/wgpu/issues/687>
/// Windows 11 is related to <https://github.com/rust-windowing/winit/issues/2082>
pub transparent: bool,
#[cfg(target_arch = "wasm32")]
pub canvas: Option<SendSyncCanvas>,
/// 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,
}

Expand All @@ -1083,7 +1099,33 @@ impl Default for WindowDescriptor {
cursor_visible: true,
mode: WindowMode::Windowed,
transparent: false,
#[cfg(target_arch = "wasm32")]
canvas: None,
#[cfg(target_arch = "wasm32")]
fit_canvas_to_parent: false,
}
}
}

#[cfg(target_arch = "wasm32")]
impl WindowDescriptor {
pub fn set_canvas_from_selector(
&mut self,
selector: &str,
) -> Result<bool, wasm_bindgen::JsValue> {
Ok(web_sys::window()
.unwrap()
.document()
.unwrap()
.query_selector(selector)?
.and_then(|element| element.dyn_into().ok())
.map(|canvas| {
self.canvas = Some(SendSyncCanvas(canvas));
true
})
.unwrap_or(false))
}
pub fn set_canvas(&mut self, canvas: HtmlCanvasElement) {
self.canvas = Some(SendSyncCanvas(canvas));
}
}
26 changes: 22 additions & 4 deletions crates/bevy_winit/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,12 @@ use bevy_utils::{
tracing::{error, info, trace, warn},
Instant,
};
#[cfg(target_arch = "wasm32")]
use bevy_window::SendSyncCanvas;
use bevy_window::{
AbstractWindowHandle, CreateWindow, CursorEntered, CursorLeft, CursorMoved, FileDragAndDrop,
ModifiesWindows, ReceivedCharacter, RequestRedraw, WindowBackendScaleFactorChanged,
WindowCloseRequested, WindowClosed, WindowCreated, WindowFocused, WindowMoved, WindowResized,
CreateWindow, CursorEntered, CursorLeft, CursorMoved, FileDragAndDrop, ModifiesWindows,
ReceivedCharacter, RequestRedraw, WindowBackendScaleFactorChanged, WindowCloseRequested,
WindowClosed, WindowCreated, WindowFocused, WindowMoved, WindowResized,
WindowScaleFactorChanged, Windows,
};

Expand Down Expand Up @@ -686,6 +688,22 @@ fn handle_create_window_events(
#[cfg(not(any(target_os = "windows", target_feature = "x11")))]
let mut window_resized_events = world.resource_mut::<Events<WindowResized>>();
for create_window_event in create_window_event_reader.iter(&create_window_events) {
#[cfg(target_arch = "wasm32")]
let window = if let Some(SendSyncCanvas(canvas)) = &create_window_event.descriptor.canvas {
winit_windows.create_window_with_canvas(
event_loop,
create_window_event.id,
&create_window_event.descriptor,
canvas.clone(),
)
} else {
winit_windows.create_window(
event_loop,
create_window_event.id,
&create_window_event.descriptor,
)
};
#[cfg(not(target_arch = "wasm32"))]
let window = winit_windows.create_window(
event_loop,
create_window_event.id,
Expand All @@ -709,7 +727,7 @@ fn handle_create_window_events(
{
let channel = world.resource_mut::<web_resize::CanvasParentResizeEventChannel>();
if create_window_event.descriptor.fit_canvas_to_parent {
if let AbstractWindowHandle::HtmlCanvas(canvas) = window.window_handle {
if let Some(SendSyncCanvas(canvas)) = &create_window_event.descriptor.canvas {
// PROBLEM: this path is unreachable, because we're always creating the window
// based on the raw window handle above.
channel.listen_to_element(create_window_event.id, canvas.clone());
Expand Down
8 changes: 6 additions & 2 deletions crates/bevy_winit/src/web_resize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ impl Default for CanvasParentResizeEventChannel {
}
}

fn get_size_element(element: &HtmlCanvasElement) -> Option<LogicalSize<f32>> {
fn get_size_element(element: &web_sys::HtmlCanvasElement) -> Option<LogicalSize<f32>> {
let parent_element = element.parent_element()?;
let rect = parent_element.get_bounding_client_rect();
return Some(winit::dpi::LogicalSize::new(
Expand Down Expand Up @@ -91,7 +91,11 @@ impl CanvasParentResizeEventChannel {
closure.forget();
}

pub(crate) fn listen_to_element(&self, window_id: WindowId, element: HtmlCanvasElement) {
pub(crate) fn listen_to_element(
&self,
window_id: WindowId,
element: web_sys::HtmlCanvasElement,
) {
let sender = self.sender.clone();
let resize = move || {
if let Some(size) = get_size_element(&element) {
Expand Down
23 changes: 15 additions & 8 deletions crates/bevy_winit/src/winit_windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,26 @@ impl WinitWindows {
window_id: WindowId,
window_descriptor: &WindowDescriptor,
) -> Window {
Self::create_window_internal(event_loop, window_id, window_descriptor, None)
#[cfg(target_arch = "wasm32")]
{
self.create_window_internal(event_loop, window_id, window_descriptor, None)
}
#[cfg(not(target_arch = "wasm32"))]
{
self.create_window_internal(event_loop, window_id, window_descriptor)
}
}
#[cfg(target_wasm = "wasm32")]
#[cfg(target_arch = "wasm32")]
pub fn create_window_with_canvas(
&mut self,
event_loop: &winit::event_loop::EventLoopWindowTarget<()>,
window_id: WindowId,
window_descriptor: &WindowDescriptor,
canvas: web_sys::HtmlCanvasElement,
) -> Window {
Self::create_window_internal(event_loop, window_id, window_descriptor, Some(canvas))
self.create_window_internal(event_loop, window_id, window_descriptor, Some(canvas))
}
#[cfg(target_wasm = "wasm32")]
#[cfg(target_arch = "wasm32")]
pub fn create_window_with_selector(
&mut self,
event_loop: &winit::event_loop::EventLoopWindowTarget<()>,
Expand All @@ -52,15 +59,15 @@ impl WinitWindows {
let canvas = document.query_selector(selector)?;
Ok(canvas.map(|canvas| {
let canvas = canvas.dyn_into::<web_sys::HtmlCanvasElement>().ok();
Self::create_window_internal(event_loop, window_id, window_descriptor, canvas)
self.create_window_internal(event_loop, window_id, window_descriptor, canvas)
}))
}
fn create_window_internal(
&mut self,
event_loop: &winit::event_loop::EventLoopWindowTarget<()>,
window_id: WindowId,
window_descriptor: &WindowDescriptor,
#[cfg(target_wasm = "wasm32")] canvas: Option<web_sys::HtmlCanvasElement>,
#[cfg(target_arch = "wasm32")] canvas: Option<web_sys::HtmlCanvasElement>,
) -> Window {
let mut winit_window_builder = winit::window::WindowBuilder::new();

Expand Down Expand Up @@ -130,10 +137,10 @@ impl WinitWindows {
#[allow(unused_mut)]
let mut winit_window_builder = winit_window_builder.with_title(&window_descriptor.title);

#[cfg(target_wasm = "wasm32")]
#[cfg(target_arch = "wasm32")]
if let Some(canvas) = canvas {
use winit::platform::web::WindowBuilderExtWebSys;
winit_window_builder = winit_window_builder.with_canvas(canvas);
winit_window_builder = winit_window_builder.with_canvas(Some(canvas));
}

let winit_window = winit_window_builder.build(event_loop).unwrap();
Expand Down

0 comments on commit d9609c4

Please sign in to comment.