diff --git a/src/custom_cursor.rs b/src/custom_cursor.rs index 7fefcc5902..cefca7264b 100644 --- a/src/custom_cursor.rs +++ b/src/custom_cursor.rs @@ -122,6 +122,7 @@ impl CustomCursor { #[derive(Debug, Clone)] pub(crate) struct NoCustomCursor; +#[allow(dead_code)] impl NoCustomCursor { pub fn from_rgba( rgba: Vec, diff --git a/src/platform_impl/web/custom_cursor.rs b/src/platform_impl/web/custom_cursor.rs new file mode 100644 index 0000000000..0553a93b60 --- /dev/null +++ b/src/platform_impl/web/custom_cursor.rs @@ -0,0 +1,56 @@ +use std::sync::Arc; + +use wasm_bindgen::{Clamped, JsCast}; +use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement}; + +use crate::custom_cursor::BadCursor; + +#[derive(Debug, Clone)] +pub struct WebCustomCursor { + pub(crate) inner: Arc, +} + +impl WebCustomCursor { + pub fn from_rgba( + rgba: Vec, + width: u32, + height: u32, + hotspot_x: u32, + hotspot_y: u32, + ) -> Result { + #[allow(clippy::disallowed_methods)] + let cursor_icon_canvas = web_sys::window() + .unwrap() + .document() + .unwrap() + .create_element("canvas") + .unwrap() + .dyn_into::() + .unwrap(); + + #[allow(clippy::disallowed_methods)] + cursor_icon_canvas.set_width(width); + #[allow(clippy::disallowed_methods)] + cursor_icon_canvas.set_height(height); + + let context = cursor_icon_canvas + .get_context("2d") + .unwrap() + .unwrap() + .dyn_into::() + .unwrap(); + + let image_data = + web_sys::ImageData::new_with_u8_clamped_array_and_sh(Clamped(&rgba), width, height); + + context + .put_image_data(&image_data.unwrap(), 0.0, 0.0) + .unwrap(); + + let data_url = cursor_icon_canvas.to_data_url().unwrap(); + + Ok(Self { + inner: format!("url({data_url}) {hotspot_x} {hotspot_y}, auto").into(), + }) + } +} diff --git a/src/platform_impl/web/mod.rs b/src/platform_impl/web/mod.rs index 3abd268414..7b97d012f3 100644 --- a/src/platform_impl/web/mod.rs +++ b/src/platform_impl/web/mod.rs @@ -18,6 +18,7 @@ // compliant way. mod r#async; +mod custom_cursor; mod device; mod error; mod event_loop; @@ -36,6 +37,7 @@ pub(crate) use self::event_loop::{ pub use self::monitor::{MonitorHandle, VideoMode}; pub use self::window::{PlatformSpecificWindowBuilderAttributes, Window, WindowId}; +pub(crate) use self::custom_cursor::WebCustomCursor as PlatformCustomCursor; pub(crate) use self::keyboard::KeyEventExtra; pub(crate) use crate::icon::NoIcon as PlatformIcon; pub(crate) use crate::platform_impl::Fullscreen; diff --git a/src/platform_impl/web/window.rs b/src/platform_impl/web/window.rs index 9759c2125f..8f8ed39a6f 100644 --- a/src/platform_impl/web/window.rs +++ b/src/platform_impl/web/window.rs @@ -1,4 +1,3 @@ -use crate::cursor_image::CursorImage; use crate::dpi::{PhysicalPosition, PhysicalSize, Position, Size}; use crate::error::{ExternalError, NotSupportedError, OsError as RootOE}; use crate::icon::Icon; @@ -8,24 +7,24 @@ use crate::window::{ }; use crate::SendSyncWrapper; -use wasm_bindgen::{Clamped, JsCast}; -use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement}; +use web_sys::HtmlCanvasElement; +use super::custom_cursor::WebCustomCursor; use super::r#async::Dispatcher; use super::{backend, monitor::MonitorHandle, EventLoopWindowTarget, Fullscreen}; use std::cell::RefCell; -use std::collections::{HashMap, VecDeque}; +use std::collections::VecDeque; use std::rc::Rc; pub struct Window { inner: Dispatcher, } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone)] pub enum SelectedCursor { BuiltIn(CursorIcon), - Custom(u64), + Custom(WebCustomCursor), } impl Default for SelectedCursor { @@ -39,7 +38,6 @@ pub struct Inner { pub window: web_sys::Window, canvas: Rc>, selected_cursor: RefCell, - custom_cursors: RefCell>, destroy_fn: Option>, } @@ -69,7 +67,6 @@ impl Window { window: window.clone(), canvas, selected_cursor: Default::default(), - custom_cursors: Default::default(), destroy_fn: Some(destroy_fn), }; @@ -221,63 +218,14 @@ impl Inner { } #[inline] - pub fn register_custom_cursor_icon(&self, key: u64, image: CursorImage) { - let cursor_icon_canvas = self - .canvas - .borrow() - .document() - .create_element("canvas") - .unwrap() - .dyn_into::() - .unwrap(); - - #[allow(clippy::disallowed_methods)] - cursor_icon_canvas.set_width(image.width); - #[allow(clippy::disallowed_methods)] - cursor_icon_canvas.set_height(image.height); - - let context = cursor_icon_canvas - .get_context("2d") - .unwrap() - .unwrap() - .dyn_into::() - .unwrap(); - - let image_data = web_sys::ImageData::new_with_u8_clamped_array_and_sh( - Clamped(&image.rgba), - image.width, - image.height, - ); - - context - .put_image_data(&image_data.unwrap(), 0.0, 0.0) - .unwrap(); - - let data_url = cursor_icon_canvas.to_data_url().unwrap(); - - let pointer = format!( - "url({data_url}) {} {}, auto", - image.hotspot_x, image.hotspot_y, - ); - self.custom_cursors.borrow_mut().insert(key, pointer); - let selected = *self.selected_cursor.borrow(); - if let SelectedCursor::Custom(key) = selected { - self.set_custom_cursor_icon(key); - } - } - - #[inline] - pub fn set_custom_cursor_icon(&self, key: u64) { - let Some(pointer) = self.custom_cursors.borrow().get(&key).cloned() else { - return; - }; - *self.selected_cursor.borrow_mut() = SelectedCursor::Custom(key); + pub fn set_custom_cursor(&self, cursor: WebCustomCursor) { + *self.selected_cursor.borrow_mut() = SelectedCursor::Custom(cursor.clone()); if let Some(s) = backend::get_canvas_style_property(self.canvas.borrow().raw(), "cursor") { if s == "none" { return; } } - backend::set_canvas_style_property(self.canvas.borrow().raw(), "cursor", &pointer); + backend::set_canvas_style_property(self.canvas.borrow().raw(), "cursor", &cursor.inner); } #[inline] @@ -306,16 +254,10 @@ impl Inner { if !visible { backend::set_canvas_style_property(self.canvas.borrow().raw(), "cursor", "none"); } else { - let custom_cursors = self.custom_cursors.borrow(); - let name = match *self.selected_cursor.borrow() { + let cursor = self.selected_cursor.borrow(); + let name = match &*cursor { SelectedCursor::BuiltIn(cursor) => cursor.name(), - SelectedCursor::Custom(key) => { - if let Some(data) = custom_cursors.get(&key) { - data - } else { - CursorIcon::default().name() - } - } + SelectedCursor::Custom(cursor) => &cursor.inner, }; backend::set_canvas_style_property(self.canvas.borrow().raw(), "cursor", name); }