diff --git a/Cargo.toml b/Cargo.toml index ab6c48e8..6de08a7f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,12 +51,21 @@ foreign-types = "0.3.0" objc = "0.2.7" [target.'cfg(target_arch = "wasm32")'.dependencies] -js-sys = "0.3.55" -wasm-bindgen = "0.2.78" +js-sys = "0.3.63" +wasm-bindgen = "0.2.86" [target.'cfg(target_arch = "wasm32")'.dependencies.web-sys] version = "0.3.55" -features = ["CanvasRenderingContext2d", "Document", "Element", "HtmlCanvasElement", "ImageData", "Window"] +features = [ + "CanvasRenderingContext2d", + "Document", + "Element", + "HtmlCanvasElement", + "ImageData", + "OffscreenCanvas", + "OffscreenCanvasRenderingContext2d", + "Window", +] [target.'cfg(target_os = "redox")'.dependencies] redox_syscall = "0.3" diff --git a/src/web.rs b/src/web.rs index 96b9f038..bbd1e719 100644 --- a/src/web.rs +++ b/src/web.rs @@ -2,11 +2,12 @@ #![allow(clippy::uninlined_format_args)] +use js_sys::Object; use raw_window_handle::WebWindowHandle; -use wasm_bindgen::JsCast; -use web_sys::CanvasRenderingContext2d; -use web_sys::HtmlCanvasElement; +use wasm_bindgen::{JsCast, JsValue}; use web_sys::ImageData; +use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement}; +use web_sys::{OffscreenCanvas, OffscreenCanvasRenderingContext2d}; use crate::error::SwResultExt; use crate::{Rect, SoftBufferError}; @@ -33,11 +34,8 @@ impl WebDisplayImpl { } pub struct WebImpl { - /// The handle to the canvas that we're drawing to. - canvas: HtmlCanvasElement, - - /// The 2D rendering context for the canvas. - ctx: CanvasRenderingContext2d, + /// The handle and context to the canvas that we're drawing to. + canvas: Canvas, /// The buffer that we're drawing to. buffer: Vec, @@ -49,6 +47,19 @@ pub struct WebImpl { size: Option<(NonZeroU32, NonZeroU32)>, } +/// Holding canvas and context for [`HtmlCanvasElement`] or [`OffscreenCanvas`], +/// since they have different types. +enum Canvas { + Canvas { + canvas: HtmlCanvasElement, + ctx: CanvasRenderingContext2d, + }, + OffscreenCanvas { + canvas: OffscreenCanvas, + ctx: OffscreenCanvasRenderingContext2d, + }, +} + impl WebImpl { pub fn new(display: &WebDisplayImpl, handle: WebWindowHandle) -> Result { let canvas: HtmlCanvasElement = display @@ -64,25 +75,46 @@ impl WebImpl { } fn from_canvas(canvas: HtmlCanvasElement) -> Result { - let ctx = canvas - .get_context("2d") - .ok() - .swbuf_err("Canvas already controlled using `OffscreenCanvas`")? - .swbuf_err( - "A canvas context other than `CanvasRenderingContext2d` was already created", - )? - .dyn_into() - .expect("`getContext(\"2d\") didn't return a `CanvasRenderingContext2d`"); + let ctx = Self::resolve_ctx(canvas.get_context("2d").ok(), "CanvasRenderingContext2d")?; + + Ok(Self { + canvas: Canvas::Canvas { canvas, ctx }, + buffer: Vec::new(), + buffer_presented: false, + size: None, + }) + } + + fn from_offscreen_canvas(canvas: OffscreenCanvas) -> Result { + let ctx = Self::resolve_ctx( + canvas.get_context("2d").ok(), + "OffscreenCanvasRenderingContext2d", + )?; Ok(Self { - canvas, - ctx, + canvas: Canvas::OffscreenCanvas { canvas, ctx }, buffer: Vec::new(), buffer_presented: false, size: None, }) } + /// De-duplicates the error handling between `HtmlCanvasElement` and `OffscreenCanvas`. + fn resolve_ctx( + result: Option>, + name: &str, + ) -> Result { + let ctx = result + .swbuf_err("Canvas already controlled using `OffscreenCanvas`")? + .swbuf_err(format!( + "A canvas context other than `{name}` was already created" + ))? + .dyn_into() + .unwrap_or_else(|_| panic!("`getContext(\"2d\") didn't return a `{name}`")); + + Ok(ctx) + } + /// Resize the canvas to the given dimensions. pub(crate) fn resize( &mut self, @@ -121,7 +153,6 @@ impl WebImpl { let result = { use js_sys::{Uint8Array, Uint8ClampedArray}; use wasm_bindgen::prelude::wasm_bindgen; - use wasm_bindgen::JsValue; #[wasm_bindgen] extern "C" { @@ -147,13 +178,11 @@ impl WebImpl { for rect in damage { // This can only throw an error if `data` is detached, which is impossible. - self.ctx - .put_image_data_with_dirty_x_and_dirty_y_and_dirty_width_and_dirty_height( + self.canvas + .put_image_data( &image_data, rect.x.into(), rect.y.into(), - rect.x.into(), - rect.y.into(), rect.width.get().into(), rect.height.get().into(), ) @@ -172,7 +201,7 @@ impl WebImpl { .expect("Must set size of surface before calling `fetch()`"); let image_data = self - .ctx + .canvas .get_image_data(0., 0., width.get().into(), height.get().into()) .ok() // TODO: Can also error if width or height are 0. @@ -195,6 +224,12 @@ pub trait SurfaceExtWeb: Sized { /// - If the canvas was already controlled by an `OffscreenCanvas`. /// - If a another context then "2d" was already created for this canvas. fn from_canvas(canvas: HtmlCanvasElement) -> Result; + + /// Creates a new instance of this struct, using the provided [`HtmlCanvasElement`]. + /// + /// # Errors + /// If a another context then "2d" was already created for this canvas. + fn from_offscreen_canvas(offscreen_canvas: OffscreenCanvas) -> Result; } impl SurfaceExtWeb for crate::Surface { @@ -206,6 +241,58 @@ impl SurfaceExtWeb for crate::Surface { _marker: PhantomData, }) } + + fn from_offscreen_canvas(offscreen_canvas: OffscreenCanvas) -> Result { + let imple = crate::SurfaceDispatch::Web(WebImpl::from_offscreen_canvas(offscreen_canvas)?); + + Ok(Self { + surface_impl: Box::new(imple), + _marker: PhantomData, + }) + } +} + +impl Canvas { + fn set_width(&self, width: u32) { + match self { + Self::Canvas { canvas, .. } => canvas.set_width(width), + Self::OffscreenCanvas { canvas, .. } => canvas.set_width(width), + } + } + + fn set_height(&self, height: u32) { + match self { + Self::Canvas { canvas, .. } => canvas.set_height(height), + Self::OffscreenCanvas { canvas, .. } => canvas.set_height(height), + } + } + + fn get_image_data(&self, sx: f64, sy: f64, sw: f64, sh: f64) -> Result { + match self { + Canvas::Canvas { ctx, .. } => ctx.get_image_data(sx, sy, sw, sh), + Canvas::OffscreenCanvas { ctx, .. } => ctx.get_image_data(sx, sy, sw, sh), + } + } + + fn put_image_data( + &self, + imagedata: &ImageData, + dx: f64, + dy: f64, + widht: f64, + height: f64, + ) -> Result<(), JsValue> { + match self { + Self::Canvas { ctx, .. } => ctx + .put_image_data_with_dirty_x_and_dirty_y_and_dirty_width_and_dirty_height( + imagedata, dx, dy, dx, dy, widht, height, + ), + Self::OffscreenCanvas { ctx, .. } => ctx + .put_image_data_with_dirty_x_and_dirty_y_and_dirty_width_and_dirty_height( + imagedata, dx, dy, dx, dy, widht, height, + ), + } + } } pub struct BufferImpl<'a> {