Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
137 changes: 112 additions & 25 deletions src/web.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -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<u32>,
Expand All @@ -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<Self, SoftBufferError> {
let canvas: HtmlCanvasElement = display
Expand All @@ -64,25 +75,46 @@ impl WebImpl {
}

fn from_canvas(canvas: HtmlCanvasElement) -> Result<Self, SoftBufferError> {
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<Self, SoftBufferError> {
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<T: JsCast>(
result: Option<Option<Object>>,
name: &str,
) -> Result<T, SoftBufferError> {
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,
Expand Down Expand Up @@ -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" {
Expand All @@ -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(),
)
Expand All @@ -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.
Expand All @@ -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<Self, SoftBufferError>;

/// 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<Self, SoftBufferError>;
}

impl SurfaceExtWeb for crate::Surface {
Expand All @@ -206,6 +241,58 @@ impl SurfaceExtWeb for crate::Surface {
_marker: PhantomData,
})
}

fn from_offscreen_canvas(offscreen_canvas: OffscreenCanvas) -> Result<Self, SoftBufferError> {
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<ImageData, JsValue> {
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> {
Expand Down