From 2c15de7cf9dc57af5d63ec7c69053c74f07fa709 Mon Sep 17 00:00:00 2001 From: daxpedda Date: Fri, 22 Dec 2023 22:20:41 +0100 Subject: [PATCH] Allow custom cursor caching (#3276) --- Cargo.toml | 1 + examples/custom_cursors.rs | 12 +- src/cursor.rs | 77 ++- src/platform/web.rs | 16 +- src/platform_impl/android/mod.rs | 4 +- src/platform_impl/ios/mod.rs | 1 + src/platform_impl/ios/window.rs | 6 +- src/platform_impl/linux/mod.rs | 4 +- src/platform_impl/linux/wayland/window/mod.rs | 9 +- .../linux/wayland/window/state.rs | 6 +- src/platform_impl/linux/x11/window.rs | 10 +- src/platform_impl/macos/mod.rs | 1 + src/platform_impl/macos/window.rs | 8 +- src/platform_impl/orbital/mod.rs | 1 + src/platform_impl/orbital/window.rs | 7 +- src/platform_impl/web/async/channel.rs | 66 +- src/platform_impl/web/async/dispatcher.rs | 6 - src/platform_impl/web/async/mod.rs | 2 +- src/platform_impl/web/cursor.rs | 627 ++++++++++++------ src/platform_impl/web/mod.rs | 3 +- src/platform_impl/web/window.rs | 42 +- src/platform_impl/windows/mod.rs | 1 + src/platform_impl/windows/window.rs | 8 +- src/window.rs | 6 +- tests/send_objects.rs | 1 + tests/sync_object.rs | 1 + 26 files changed, 579 insertions(+), 347 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index dbd3c00512..60c9ffd833 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -191,6 +191,7 @@ features = [ 'FocusEvent', 'HtmlCanvasElement', 'HtmlElement', + 'HtmlImageElement', 'ImageBitmap', 'ImageBitmapOptions', 'ImageBitmapRenderingContext', diff --git a/examples/custom_cursors.rs b/examples/custom_cursors.rs index c685cd7540..c29dd863f2 100644 --- a/examples/custom_cursors.rs +++ b/examples/custom_cursors.rs @@ -4,17 +4,19 @@ use simple_logger::SimpleLogger; use winit::{ event::{ElementState, Event, KeyEvent, WindowEvent}, - event_loop::EventLoop, + event_loop::{EventLoop, EventLoopWindowTarget}, keyboard::Key, window::{CustomCursor, WindowBuilder}, }; -fn decode_cursor(bytes: &[u8]) -> CustomCursor { +fn decode_cursor(bytes: &[u8], window_target: &EventLoopWindowTarget) -> CustomCursor { let img = image::load_from_memory(bytes).unwrap().to_rgba8(); let samples = img.into_flat_samples(); let (_, w, h) = samples.extents(); let (w, h) = (w as u16, h as u16); - CustomCursor::from_rgba(samples.samples, w, h, w / 2, h / 2).unwrap() + let builder = CustomCursor::from_rgba(samples.samples, w, h, w / 2, h / 2).unwrap(); + + builder.build(window_target) } #[cfg(not(wasm_platform))] @@ -43,8 +45,8 @@ fn main() -> Result<(), impl std::error::Error> { let mut cursor_visible = true; let custom_cursors = [ - decode_cursor(include_bytes!("data/cross.png")), - decode_cursor(include_bytes!("data/cross2.png")), + decode_cursor(include_bytes!("data/cross.png"), &event_loop), + decode_cursor(include_bytes!("data/cross2.png"), &event_loop), ]; event_loop.run(move |event, _elwt| match event { diff --git a/src/cursor.rs b/src/cursor.rs index ca55a61f14..d53af7a52d 100644 --- a/src/cursor.rs +++ b/src/cursor.rs @@ -1,7 +1,10 @@ use core::fmt; -use std::{error::Error, sync::Arc}; +use std::hash::Hasher; +use std::sync::Arc; +use std::{error::Error, hash::Hash}; -use crate::platform_impl::PlatformCustomCursor; +use crate::event_loop::EventLoopWindowTarget; +use crate::platform_impl::{self, PlatformCustomCursor, PlatformCustomCursorBuilder}; /// The maximum width and height for a cursor when using [`CustomCursor::from_rgba`]. pub const MAX_CURSOR_SIZE: u16 = 2048; @@ -16,25 +19,52 @@ const PIXEL_SIZE: usize = 4; /// /// # Example /// -/// ``` -/// use winit::window::CustomCursor; +/// ```no_run +/// use winit::{ +/// event::{Event, WindowEvent}, +/// event_loop::{ControlFlow, EventLoop}, +/// window::{CustomCursor, Window}, +/// }; +/// +/// let mut event_loop = EventLoop::new().unwrap(); /// /// let w = 10; /// let h = 10; /// let rgba = vec![255; (w * h * 4) as usize]; -/// let custom_cursor = CustomCursor::from_rgba(rgba, w, h, w / 2, h / 2).unwrap(); +/// +/// #[cfg(not(target_family = "wasm"))] +/// let builder = CustomCursor::from_rgba(rgba, w, h, w / 2, h / 2).unwrap(); /// /// #[cfg(target_family = "wasm")] -/// let custom_cursor_url = { +/// let builder = { /// use winit::platform::web::CustomCursorExtWebSys; -/// CustomCursor::from_url("http://localhost:3000/cursor.png", 0, 0).unwrap() +/// CustomCursor::from_url(String::from("http://localhost:3000/cursor.png"), 0, 0) /// }; +/// +/// let custom_cursor = builder.build(&event_loop); +/// +/// let window = Window::new(&event_loop).unwrap(); +/// window.set_custom_cursor(&custom_cursor); /// ``` -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Clone, Debug)] pub struct CustomCursor { pub(crate) inner: Arc, } +impl Hash for CustomCursor { + fn hash(&self, state: &mut H) { + Arc::as_ptr(&self.inner).hash(state); + } +} + +impl PartialEq for CustomCursor { + fn eq(&self, other: &Self) -> bool { + Arc::ptr_eq(&self.inner, &other.inner) + } +} + +impl Eq for CustomCursor {} + impl CustomCursor { /// Creates a new cursor from an rgba buffer. /// @@ -48,20 +78,35 @@ impl CustomCursor { height: u16, hotspot_x: u16, hotspot_y: u16, - ) -> Result { - Ok(Self { + ) -> Result { + Ok(CustomCursorBuilder { inner: PlatformCustomCursor::from_rgba( rgba.into(), width, height, hotspot_x, hotspot_y, - )? - .into(), + )?, }) } } +/// Builds a [`CustomCursor`]. +/// +/// See [`CustomCursor`] for more details. +#[derive(Debug)] +pub struct CustomCursorBuilder { + pub(crate) inner: PlatformCustomCursorBuilder, +} + +impl CustomCursorBuilder { + pub fn build(self, window_target: &EventLoopWindowTarget) -> CustomCursor { + CustomCursor { + inner: self.inner.build(&window_target.p), + } + } +} + /// An error produced when using [`CustomCursor::from_rgba`] with invalid arguments. #[derive(Debug, Clone)] pub enum BadImage { @@ -177,6 +222,10 @@ impl CursorImage { hotspot_y, }) } + + fn build(self, _: &platform_impl::EventLoopWindowTarget) -> Arc { + Arc::new(self) + } } // Platforms that don't support cursors will export this as `PlatformCustomCursor`. @@ -195,4 +244,8 @@ impl NoCustomCursor { CursorImage::from_rgba(rgba, width, height, hotspot_x, hotspot_y)?; Ok(Self) } + + fn build(self, _: &platform_impl::EventLoopWindowTarget) -> Arc { + Arc::new(self) + } } diff --git a/src/platform/web.rs b/src/platform/web.rs index c01308e972..2d79914924 100644 --- a/src/platform/web.rs +++ b/src/platform/web.rs @@ -27,11 +27,12 @@ //! [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border //! [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding -use crate::cursor::CustomCursor; +use crate::cursor::CustomCursorBuilder; use crate::event::Event; use crate::event_loop::EventLoop; use crate::event_loop::EventLoopWindowTarget; -use crate::platform_impl::PlatformCustomCursor; +use crate::platform_impl::PlatformCustomCursorBuilder; +use crate::window::CustomCursor; use crate::window::{Window, WindowBuilder}; use crate::SendSyncWrapper; @@ -209,18 +210,17 @@ pub trait CustomCursorExtWebSys { /// but browser support for image formats is inconsistent. Using [PNG] is recommended. /// /// [PNG]: https://en.wikipedia.org/wiki/PNG - fn from_url(url: String, hotspot_x: u16, hotspot_y: u16) -> Self; + fn from_url(url: String, hotspot_x: u16, hotspot_y: u16) -> CustomCursorBuilder; } impl CustomCursorExtWebSys for CustomCursor { - fn from_url(url: String, hotspot_x: u16, hotspot_y: u16) -> Self { - Self { - inner: PlatformCustomCursor::Url { + fn from_url(url: String, hotspot_x: u16, hotspot_y: u16) -> CustomCursorBuilder { + CustomCursorBuilder { + inner: PlatformCustomCursorBuilder::Url { url, hotspot_x, hotspot_y, - } - .into(), + }, } } } diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index 68be1a7534..eee0fb0ac1 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -18,7 +18,6 @@ use android_activity::{ use once_cell::sync::Lazy; use crate::{ - cursor::CustomCursor, dpi::{PhysicalPosition, PhysicalSize, Position, Size}, error, event::{self, Force, InnerSizeWriter, StartCause}, @@ -907,7 +906,7 @@ impl Window { pub fn set_cursor_icon(&self, _: window::CursorIcon) {} - pub fn set_custom_cursor(&self, _: CustomCursor) {} + pub(crate) fn set_custom_cursor(&self, _: Arc) {} pub fn set_cursor_position(&self, _: Position) -> Result<(), error::ExternalError> { Err(error::ExternalError::NotSupported( @@ -1035,6 +1034,7 @@ impl Display for OsError { } pub(crate) use crate::cursor::NoCustomCursor as PlatformCustomCursor; +pub(crate) use crate::cursor::NoCustomCursor as PlatformCustomCursorBuilder; pub(crate) use crate::icon::NoIcon as PlatformIcon; #[derive(Clone, Debug, PartialEq, Eq, Hash)] diff --git a/src/platform_impl/ios/mod.rs b/src/platform_impl/ios/mod.rs index 5fc9d7431d..d4d5a77f39 100644 --- a/src/platform_impl/ios/mod.rs +++ b/src/platform_impl/ios/mod.rs @@ -78,6 +78,7 @@ pub(crate) use self::{ use self::uikit::UIScreen; pub(crate) use crate::cursor::NoCustomCursor as PlatformCustomCursor; +pub(crate) use crate::cursor::NoCustomCursor as PlatformCustomCursorBuilder; pub(crate) use crate::icon::NoIcon as PlatformIcon; pub(crate) use crate::platform_impl::Fullscreen; diff --git a/src/platform_impl/ios/window.rs b/src/platform_impl/ios/window.rs index e70d843f60..765dea503e 100644 --- a/src/platform_impl/ios/window.rs +++ b/src/platform_impl/ios/window.rs @@ -1,6 +1,7 @@ #![allow(clippy::unnecessary_cast)] use std::collections::VecDeque; +use std::sync::Arc; use icrate::Foundation::{CGFloat, CGPoint, CGRect, CGSize, MainThreadBound, MainThreadMarker}; use objc2::rc::Id; @@ -11,14 +12,13 @@ use super::app_state::EventWrapper; use super::uikit::{UIApplication, UIScreen, UIScreenOverscanCompensation}; use super::view::{WinitUIWindow, WinitView, WinitViewController}; use crate::{ - cursor::CustomCursor, dpi::{self, LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size}, error::{ExternalError, NotSupportedError, OsError as RootOsError}, event::{Event, WindowEvent}, icon::Icon, platform::ios::{ScreenEdge, StatusBarStyle, ValidOrientations}, platform_impl::platform::{ - app_state, monitor, EventLoopWindowTarget, Fullscreen, MonitorHandle, + app_state, monitor, EventLoopWindowTarget, Fullscreen, MonitorHandle, PlatformCustomCursor, }, window::{ CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType, @@ -178,7 +178,7 @@ impl Inner { debug!("`Window::set_cursor_icon` ignored on iOS") } - pub fn set_custom_cursor(&self, _: CustomCursor) { + pub(crate) fn set_custom_cursor(&self, _: Arc) { debug!("`Window::set_custom_cursor` ignored on iOS") } diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index 6b6186cf44..0979b700f6 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -14,7 +14,6 @@ use std::{ffi::CStr, mem::MaybeUninit, os::raw::*, sync::Mutex}; use once_cell::sync::Lazy; use smol_str::SmolStr; -use crate::cursor::CustomCursor; #[cfg(x11_platform)] use crate::platform::x11::XlibErrorHook; use crate::{ @@ -41,6 +40,7 @@ pub use x11::XNotSupported; #[cfg(x11_platform)] use x11::{util::WindowType as XWindowType, X11Error, XConnection, XError}; +pub(crate) use crate::cursor::CursorImage as PlatformCustomCursorBuilder; pub(crate) use crate::cursor::CursorImage as PlatformCustomCursor; pub(crate) use crate::icon::RgbaIcon as PlatformIcon; pub(crate) use crate::platform_impl::Fullscreen; @@ -427,7 +427,7 @@ impl Window { } #[inline] - pub fn set_custom_cursor(&self, cursor: CustomCursor) { + pub(crate) fn set_custom_cursor(&self, cursor: Arc) { x11_or_wayland!(match self; Window(w) => w.set_custom_cursor(cursor)) } diff --git a/src/platform_impl/linux/wayland/window/mod.rs b/src/platform_impl/linux/wayland/window/mod.rs index 13d74c201c..d2dbeb3611 100644 --- a/src/platform_impl/linux/wayland/window/mod.rs +++ b/src/platform_impl/linux/wayland/window/mod.rs @@ -15,14 +15,13 @@ use sctk::shell::xdg::window::Window as SctkWindow; use sctk::shell::xdg::window::WindowDecorations; use sctk::shell::WaylandSurface; -use crate::cursor::CustomCursor; use crate::dpi::{LogicalSize, PhysicalPosition, PhysicalSize, Position, Size}; use crate::error::{ExternalError, NotSupportedError, OsError as RootOsError}; use crate::event::{Ime, WindowEvent}; use crate::event_loop::AsyncRequestSerial; use crate::platform_impl::{ - Fullscreen, MonitorHandle as PlatformMonitorHandle, OsError, PlatformIcon, - PlatformSpecificWindowBuilderAttributes as PlatformAttributes, + Fullscreen, MonitorHandle as PlatformMonitorHandle, OsError, PlatformCustomCursor, + PlatformIcon, PlatformSpecificWindowBuilderAttributes as PlatformAttributes, }; use crate::window::{ CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType, @@ -508,8 +507,8 @@ impl Window { } #[inline] - pub fn set_custom_cursor(&self, cursor: CustomCursor) { - self.window_state.lock().unwrap().set_custom_cursor(cursor); + pub(crate) fn set_custom_cursor(&self, cursor: Arc) { + self.window_state.lock().unwrap().set_custom_cursor(&cursor); } #[inline] diff --git a/src/platform_impl/linux/wayland/window/state.rs b/src/platform_impl/linux/wayland/window/state.rs index 4d3512859b..10bb246c4b 100644 --- a/src/platform_impl/linux/wayland/window/state.rs +++ b/src/platform_impl/linux/wayland/window/state.rs @@ -28,7 +28,7 @@ use sctk::shm::Shm; use sctk::subcompositor::SubcompositorState; use wayland_protocols_plasma::blur::client::org_kde_kwin_blur::OrgKdeKwinBlur; -use crate::cursor::CustomCursor as RootCustomCursor; +use crate::cursor::CursorImage; use crate::dpi::{LogicalPosition, LogicalSize, PhysicalSize, Size}; use crate::error::{ExternalError, NotSupportedError}; use crate::event::WindowEvent; @@ -726,10 +726,10 @@ impl WindowState { } /// Set the custom cursor icon. - pub fn set_custom_cursor(&mut self, cursor: RootCustomCursor) { + pub fn set_custom_cursor(&mut self, cursor: &CursorImage) { let cursor = { let mut pool = self.custom_cursor_pool.lock().unwrap(); - CustomCursor::new(&mut pool, &cursor.inner) + CustomCursor::new(&mut pool, cursor) }; if self.cursor_visible { diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index 2d4314c4fb..f438616d15 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -7,8 +7,6 @@ use std::{ sync::{Arc, Mutex, MutexGuard}, }; -use crate::cursor::CustomCursor as RootCustomCursor; - use cursor_icon::CursorIcon; use x11rb::{ connection::Connection, @@ -32,8 +30,8 @@ use crate::{ atoms::*, xinput_fp1616_to_float, MonitorHandle as X11MonitorHandle, WakeSender, X11Error, }, - Fullscreen, MonitorHandle as PlatformMonitorHandle, OsError, PlatformIcon, - PlatformSpecificWindowBuilderAttributes, VideoMode as PlatformVideoMode, + Fullscreen, MonitorHandle as PlatformMonitorHandle, OsError, PlatformCustomCursor, + PlatformIcon, PlatformSpecificWindowBuilderAttributes, VideoMode as PlatformVideoMode, }, window::{ CursorGrabMode, ImePurpose, ResizeDirection, Theme, UserAttentionType, WindowAttributes, @@ -1552,8 +1550,8 @@ impl UnownedWindow { } #[inline] - pub fn set_custom_cursor(&self, cursor: RootCustomCursor) { - let new_cursor = unsafe { CustomCursor::new(&self.xconn, &cursor.inner) }; + pub(crate) fn set_custom_cursor(&self, cursor: Arc) { + let new_cursor = unsafe { CustomCursor::new(&self.xconn, &cursor) }; #[allow(clippy::mutex_atomic)] if *self.cursor_visible.lock().unwrap() { diff --git a/src/platform_impl/macos/mod.rs b/src/platform_impl/macos/mod.rs index 31bee1f03e..1f813b19fa 100644 --- a/src/platform_impl/macos/mod.rs +++ b/src/platform_impl/macos/mod.rs @@ -29,6 +29,7 @@ use crate::event::DeviceId as RootDeviceId; pub(crate) use self::window::Window; pub(crate) use crate::cursor::CursorImage as PlatformCustomCursor; +pub(crate) use crate::cursor::CursorImage as PlatformCustomCursorBuilder; pub(crate) use crate::icon::NoIcon as PlatformIcon; pub(crate) use crate::platform_impl::Fullscreen; diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index c98db38fba..3ca959d647 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -5,9 +5,9 @@ use std::f64; use std::ops; use std::os::raw::c_void; use std::ptr::NonNull; +use std::sync::Arc; use std::sync::{Mutex, MutexGuard}; -use crate::cursor::CustomCursor; use crate::{ dpi::{ LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size, Size::Logical, @@ -25,7 +25,7 @@ use crate::{ util, view::WinitView, window_delegate::WinitWindowDelegate, - Fullscreen, OsError, + Fullscreen, OsError, PlatformCustomCursor, }, window::{ CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType, @@ -836,9 +836,9 @@ impl WinitWindow { } #[inline] - pub fn set_custom_cursor(&self, cursor: CustomCursor) { + pub(crate) fn set_custom_cursor(&self, cursor: Arc) { let view = self.view(); - view.set_cursor_icon(NSCursor::from_image(&cursor.inner)); + view.set_cursor_icon(NSCursor::from_image(&cursor)); self.invalidateCursorRectsForView(&view); } diff --git a/src/platform_impl/orbital/mod.rs b/src/platform_impl/orbital/mod.rs index 121d020380..e2ad1ce08c 100644 --- a/src/platform_impl/orbital/mod.rs +++ b/src/platform_impl/orbital/mod.rs @@ -194,6 +194,7 @@ impl Display for OsError { } pub(crate) use crate::cursor::NoCustomCursor as PlatformCustomCursor; +pub(crate) use crate::cursor::NoCustomCursor as PlatformCustomCursorBuilder; pub(crate) use crate::icon::NoIcon as PlatformIcon; #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] diff --git a/src/platform_impl/orbital/window.rs b/src/platform_impl/orbital/window.rs index 9d800af7cf..b3edcdbf29 100644 --- a/src/platform_impl/orbital/window.rs +++ b/src/platform_impl/orbital/window.rs @@ -4,7 +4,6 @@ use std::{ }; use crate::{ - cursor::CustomCursor, dpi::{PhysicalPosition, PhysicalSize, Position, Size}, error, platform_impl::Fullscreen, @@ -13,8 +12,8 @@ use crate::{ }; use super::{ - EventLoopWindowTarget, MonitorHandle, PlatformSpecificWindowBuilderAttributes, RedoxSocket, - TimeSocket, WindowId, WindowProperties, + EventLoopWindowTarget, MonitorHandle, PlatformCustomCursor, + PlatformSpecificWindowBuilderAttributes, RedoxSocket, TimeSocket, WindowId, WindowProperties, }; // These values match the values uses in the `window_new` function in orbital: @@ -353,7 +352,7 @@ impl Window { #[inline] pub fn set_cursor_icon(&self, _: window::CursorIcon) {} - pub fn set_custom_cursor(&self, _: CustomCursor) {} + pub(crate) fn set_custom_cursor(&self, _: Arc) {} #[inline] pub fn set_cursor_position(&self, _: Position) -> Result<(), error::ExternalError> { diff --git a/src/platform_impl/web/async/channel.rs b/src/platform_impl/web/async/channel.rs index 3f9c72a156..0592a20822 100644 --- a/src/platform_impl/web/async/channel.rs +++ b/src/platform_impl/web/async/channel.rs @@ -6,65 +6,67 @@ use std::sync::mpsc::{self, Receiver, RecvError, SendError, Sender, TryRecvError use std::sync::{Arc, Mutex}; use std::task::Poll; -// NOTE: This channel doesn't wake up when all senders or receivers are -// dropped. This is acceptable as long as it's only used in `Dispatcher`, which -// has it's own `Drop` behavior. - pub fn channel() -> (AsyncSender, AsyncReceiver) { let (sender, receiver) = mpsc::channel(); - let sender = Arc::new(Mutex::new(sender)); - let inner = Arc::new(Inner { + let shared = Arc::new(Shared { closed: AtomicBool::new(false), waker: AtomicWaker::new(), }); - let sender = AsyncSender { - sender, - inner: Arc::clone(&inner), - }; + let sender = AsyncSender(Arc::new(SenderInner { + sender: Mutex::new(sender), + shared: Arc::clone(&shared), + })); let receiver = AsyncReceiver { receiver: Rc::new(receiver), - inner, + shared, }; (sender, receiver) } -pub struct AsyncSender { +pub struct AsyncSender(Arc>); + +struct SenderInner { // We need to wrap it into a `Mutex` to make it `Sync`. So the sender can't // be accessed on the main thread, as it could block. Additionally we need - // to wrap it in an `Arc` to make it clonable on the main thread without + // to wrap `Sender` in an `Arc` to make it clonable on the main thread without // having to block. - sender: Arc>>, - inner: Arc, + sender: Mutex>, + shared: Arc, } impl AsyncSender { pub fn send(&self, event: T) -> Result<(), SendError> { - self.sender.lock().unwrap().send(event)?; - self.inner.waker.wake(); + self.0.sender.lock().unwrap().send(event)?; + self.0.shared.waker.wake(); Ok(()) } +} - pub fn close(&self) { - self.inner.closed.store(true, Ordering::Relaxed); - self.inner.waker.wake() +impl SenderInner { + fn close(&self) { + self.shared.closed.store(true, Ordering::Relaxed); + self.shared.waker.wake(); } } impl Clone for AsyncSender { fn clone(&self) -> Self { - Self { - sender: Arc::clone(&self.sender), - inner: Arc::clone(&self.inner), - } + Self(Arc::clone(&self.0)) + } +} + +impl Drop for SenderInner { + fn drop(&mut self) { + self.close(); } } pub struct AsyncReceiver { receiver: Rc>, - inner: Arc, + shared: Arc, } impl AsyncReceiver { @@ -72,12 +74,12 @@ impl AsyncReceiver { future::poll_fn(|cx| match self.receiver.try_recv() { Ok(event) => Poll::Ready(Ok(event)), Err(TryRecvError::Empty) => { - self.inner.waker.register(cx.waker()); + self.shared.waker.register(cx.waker()); match self.receiver.try_recv() { Ok(event) => Poll::Ready(Ok(event)), Err(TryRecvError::Empty) => { - if self.inner.closed.load(Ordering::Relaxed) { + if self.shared.closed.load(Ordering::Relaxed) { Poll::Ready(Err(RecvError)) } else { Poll::Pending @@ -104,12 +106,18 @@ impl Clone for AsyncReceiver { fn clone(&self) -> Self { Self { receiver: Rc::clone(&self.receiver), - inner: Arc::clone(&self.inner), + shared: Arc::clone(&self.shared), } } } -struct Inner { +impl Drop for AsyncReceiver { + fn drop(&mut self) { + self.shared.closed.store(true, Ordering::Relaxed); + } +} + +struct Shared { closed: AtomicBool, waker: AtomicWaker, } diff --git a/src/platform_impl/web/async/dispatcher.rs b/src/platform_impl/web/async/dispatcher.rs index daa7702558..5fb63d5456 100644 --- a/src/platform_impl/web/async/dispatcher.rs +++ b/src/platform_impl/web/async/dispatcher.rs @@ -82,12 +82,6 @@ impl Dispatcher { } } -impl Drop for Dispatcher { - fn drop(&mut self) { - self.0.with_sender_data(|sender| sender.close()) - } -} - pub struct DispatchRunner { wrapper: Wrapper>, Closure>, receiver: AsyncReceiver>, diff --git a/src/platform_impl/web/async/mod.rs b/src/platform_impl/web/async/mod.rs index f1317f6e39..b4fe698daf 100644 --- a/src/platform_impl/web/async/mod.rs +++ b/src/platform_impl/web/async/mod.rs @@ -3,7 +3,7 @@ mod dispatcher; mod waker; mod wrapper; -use self::channel::{channel, AsyncReceiver, AsyncSender}; +pub use self::channel::{channel, AsyncReceiver, AsyncSender}; pub use self::dispatcher::{DispatchRunner, Dispatcher}; pub use self::waker::{Waker, WakerSpawner}; use self::wrapper::Wrapper; diff --git a/src/platform_impl/web/cursor.rs b/src/platform_impl/web/cursor.rs index e2a265a0dc..04c566631a 100644 --- a/src/platform_impl/web/cursor.rs +++ b/src/platform_impl/web/cursor.rs @@ -1,22 +1,31 @@ use std::{ - cell::{Cell, RefCell}, - ops::Deref, - rc::{Rc, Weak}, + cell::RefCell, + future, mem, + ops::DerefMut, + rc::{self, Rc}, + sync::{self, Arc}, + task::{Poll, Waker}, }; -use crate::cursor::{BadImage, CursorImage}; +use crate::{ + cursor::{BadImage, CursorImage}, + platform_impl::platform::r#async, +}; use cursor_icon::CursorIcon; +use once_cell::sync::Lazy; use wasm_bindgen::{closure::Closure, JsCast}; use wasm_bindgen_futures::JsFuture; use web_sys::{ - Blob, Document, HtmlCanvasElement, ImageBitmap, ImageBitmapOptions, + Blob, Document, HtmlCanvasElement, HtmlImageElement, ImageBitmap, ImageBitmapOptions, ImageBitmapRenderingContext, ImageData, PremultiplyAlpha, Url, Window, }; -use super::backend::Style; +use self::thread_safe::ThreadSafe; + +use super::{backend::Style, r#async::AsyncSender, EventLoopWindowTarget}; -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum WebCustomCursor { +#[derive(Debug)] +pub enum CustomCursorBuilder { Image(CursorImage), Url { url: String, @@ -25,72 +34,163 @@ pub enum WebCustomCursor { }, } -impl WebCustomCursor { +impl CustomCursorBuilder { + pub fn build(self, window_target: &EventLoopWindowTarget) -> Arc { + Lazy::force(&DROP_HANDLER); + + match self { + Self::Image(image) => ImageState::from_rgba( + window_target.runner.window(), + window_target.runner.document().clone(), + &image, + ), + Self::Url { + url, + hotspot_x, + hotspot_y, + } => ImageState::from_url(url, hotspot_x, hotspot_y), + } + } +} + +#[derive(Debug)] +pub struct CustomCursor(Option>>); + +static DROP_HANDLER: Lazy>>> = Lazy::new(|| { + let (sender, receiver) = r#async::channel(); + wasm_bindgen_futures::spawn_local(async move { while receiver.next().await.is_ok() {} }); + + sender +}); + +impl CustomCursor { pub fn from_rgba( rgba: Vec, width: u16, height: u16, hotspot_x: u16, hotspot_y: u16, - ) -> Result { - Ok(Self::Image(CursorImage::from_rgba( + ) -> Result { + Ok(CustomCursorBuilder::Image(CursorImage::from_rgba( rgba, width, height, hotspot_x, hotspot_y, )?)) } - pub(super) fn build( - &self, - window: &Window, - document: &Document, - style: &Style, - previous: SelectedCursor, - cursor_visible: Rc>, - ) -> SelectedCursor { - let previous = previous.into(); + fn new() -> Arc { + Arc::new(Self(Some(ThreadSafe::new(RefCell::new( + ImageState::Loading(None), + ))))) + } - match self { - WebCustomCursor::Image(image) => SelectedCursor::Image(CursorImageState::from_image( - window, - document.clone(), - style.clone(), - image, - previous, - cursor_visible, - )), - WebCustomCursor::Url { - url, - hotspot_x, - hotspot_y, - } => { - let value = previous.style_with_url(url, *hotspot_x, *hotspot_y); + fn get(&self) -> &RefCell { + self.0 + .as_ref() + .expect("value has accidently already been dropped") + .get() + } +} - if cursor_visible.get() { - style.set("cursor", &value); - } +impl Drop for CustomCursor { + fn drop(&mut self) { + let value = self + .0 + .take() + .expect("value has accidently already been dropped"); + + if !value.in_origin_thread() { + DROP_HANDLER + .send(value) + .expect("sender dropped in main thread") + } + } +} - SelectedCursor::Url { - style: value, - previous, - url: url.clone(), - hotspot_x: *hotspot_x, - hotspot_y: *hotspot_y, - } +#[derive(Debug)] +pub struct CursorState(Rc>); + +impl CursorState { + pub fn new(style: Style) -> Self { + Self(Rc::new(RefCell::new(State { + style, + visible: true, + cursor: SelectedCursor::default(), + }))) + } + + pub fn set_cursor_icon(&self, cursor: CursorIcon) { + let mut this = self.0.borrow_mut(); + + if let SelectedCursor::ImageLoading { state, .. } = &this.cursor { + if let ImageState::Loading(state) = state.get().borrow_mut().deref_mut() { + state.take(); + } + } + + this.cursor = SelectedCursor::Named(cursor); + this.set_style(); + } + + pub fn set_custom_cursor(&self, cursor: Arc) { + let mut this = self.0.borrow_mut(); + + match cursor.get().borrow_mut().deref_mut() { + ImageState::Loading(state) => { + this.cursor = SelectedCursor::ImageLoading { + state: cursor.clone(), + previous: mem::take(&mut this.cursor).into(), + }; + *state = Some(Rc::downgrade(&self.0)); } + ImageState::Failed => log::error!("tried to load invalid cursor"), + ImageState::Ready(image) => { + this.cursor = SelectedCursor::ImageReady(image.clone()); + this.set_style(); + } + } + } + + pub fn set_cursor_visible(&self, visible: bool) { + let mut state = self.0.borrow_mut(); + + if !visible && state.visible { + state.visible = false; + state.style.set("cursor", "none"); + } else if visible && !state.visible { + state.visible = true; + state.set_style(); + } + } +} + +#[derive(Debug)] +struct State { + style: Style, + visible: bool, + cursor: SelectedCursor, +} + +impl State { + pub fn set_style(&self) { + if self.visible { + let value = match &self.cursor { + SelectedCursor::Named(icon) => icon.name(), + SelectedCursor::ImageLoading { previous, .. } => previous.style(), + SelectedCursor::ImageReady(image) => &image.style, + }; + + self.style.set("cursor", value); } } } #[derive(Debug)] -pub enum SelectedCursor { +enum SelectedCursor { Named(CursorIcon), - Url { - style: String, + ImageLoading { + state: Arc, previous: Previous, - url: String, - hotspot_x: u16, - hotspot_y: u16, }, - Image(Rc>>), + ImageReady(Rc), } impl Default for SelectedCursor { @@ -99,71 +199,26 @@ impl Default for SelectedCursor { } } -impl SelectedCursor { - pub fn set_style(&self, style: &Style) { - let value = match self { - SelectedCursor::Named(icon) => icon.name(), - SelectedCursor::Url { style, .. } => style, - SelectedCursor::Image(image) => { - let image = image.borrow(); - let value = match image.deref().as_ref().unwrap() { - CursorImageState::Loading { previous, .. } => previous.style(), - CursorImageState::Failed(previous) => previous.style(), - CursorImageState::Ready { style, .. } => style, - }; - return style.set("cursor", value); - } - }; - - style.set("cursor", value); +impl From for SelectedCursor { + fn from(previous: Previous) -> Self { + match previous { + Previous::Named(icon) => Self::Named(icon), + Previous::Image(image) => Self::ImageReady(image), + } } } #[derive(Debug)] pub enum Previous { Named(CursorIcon), - Url { - style: String, - url: String, - hotspot_x: u16, - hotspot_y: u16, - }, - Image { - style: String, - image: WebCursorImage, - }, + Image(Rc), } impl Previous { fn style(&self) -> &str { match self { Previous::Named(icon) => icon.name(), - Previous::Url { style: url, .. } => url, - Previous::Image { style, .. } => style, - } - } - - fn style_with_url(&self, new_url: &str, new_hotspot_x: u16, new_hotspot_y: u16) -> String { - match self { - Previous::Named(icon) => format!("url({new_url}) {new_hotspot_x} {new_hotspot_y}, {}", icon.name()), - Previous::Url { - url, - hotspot_x, - hotspot_y, - .. - } - | Previous::Image { - image: - WebCursorImage { - data_url: url, - hotspot_x, - hotspot_y, - .. - }, - .. - } => format!( - "url({new_url}) {new_hotspot_x} {new_hotspot_y}, url({url}) {hotspot_x} {hotspot_y}, auto", - ), + Previous::Image(image) => &image.style, } } } @@ -172,66 +227,34 @@ impl From for Previous { fn from(value: SelectedCursor) -> Self { match value { SelectedCursor::Named(icon) => Self::Named(icon), - SelectedCursor::Url { - style, - url, - hotspot_x, - hotspot_y, - .. - } => Self::Url { - style, - url, - hotspot_x, - hotspot_y, - }, - SelectedCursor::Image(image) => { - match Rc::try_unwrap(image).unwrap().into_inner().unwrap() { - CursorImageState::Loading { previous, .. } => previous, - CursorImageState::Failed(previous) => previous, - CursorImageState::Ready { - style, - image: current, - .. - } => Self::Image { - style, - image: current, - }, - } - } + SelectedCursor::ImageLoading { previous, .. } => previous, + SelectedCursor::ImageReady(image) => Self::Image(image), } } } #[derive(Debug)] -pub enum CursorImageState { - Loading { - style: Style, - cursor_visible: Rc>, - previous: Previous, - hotspot_x: u16, - hotspot_y: u16, - }, - Failed(Previous), - Ready { - style: String, - image: WebCursorImage, - previous: Previous, - }, +enum ImageState { + Loading(Option>>), + Failed, + Ready(Rc), } -impl CursorImageState { - fn from_image( - window: &Window, - document: Document, - style: Style, - image: &CursorImage, - previous: Previous, - cursor_visible: Rc>, - ) -> Rc>> { - // Can't create array directly when backed by SharedArrayBuffer. +impl ImageState { + fn from_rgba(window: &Window, document: Document, image: &CursorImage) -> Arc { + // 1. Create an `ImageData` from the RGBA data. + // 2. Create an `ImageBitmap` from the `ImageData`. + // 3. Draw `ImageBitmap` on an `HTMLCanvasElement`. + // 4. Create a `Blob` from the `HTMLCanvasElement`. + // 5. Create an object URL from the `Blob`. + // 6. Decode the image on an `HTMLImageElement` from the URL. + // 7. Change the `CursorState` if queued. + + // 1. Create an `ImageData` from the RGBA data. // Adapted from https://github.com/rust-windowing/softbuffer/blob/ab7688e2ed2e2eca51b3c4e1863a5bd7fe85800e/src/web.rs#L196-L223 #[cfg(target_feature = "atomics")] - let image_data = { + // Can't share `SharedArrayBuffer` with `ImageData`. + let result = { use js_sys::{Uint8Array, Uint8ClampedArray}; use wasm_bindgen::prelude::wasm_bindgen; use wasm_bindgen::JsValue; @@ -250,115 +273,283 @@ impl CursorImageState { ImageDataExt::new(array, image.width as u32) .map(JsValue::from) .map(ImageData::unchecked_from_js) - .unwrap() }; #[cfg(not(target_feature = "atomics"))] - let image_data = ImageData::new_with_u8_clamped_array( + let result = ImageData::new_with_u8_clamped_array( wasm_bindgen::Clamped(&image.rgba), image.width as u32, - ) - .unwrap(); + ); + let image_data = result.expect("found wrong image size"); + // 2. Create an `ImageBitmap` from the `ImageData`. + // + // We call `createImageBitmap()` before spawning the future, + // to not have to clone the image buffer. let mut options = ImageBitmapOptions::new(); options.premultiply_alpha(PremultiplyAlpha::None); let bitmap = JsFuture::from( window .create_image_bitmap_with_image_data_and_image_bitmap_options(&image_data, &options) - .unwrap(), + .expect("unexpected exception in `createImageBitmap()`"), ); - let state = Rc::new(RefCell::new(Some(Self::Loading { - style, - cursor_visible, - previous, - hotspot_x: image.hotspot_x, - hotspot_y: image.hotspot_y, - }))); + let this = CustomCursor::new(); wasm_bindgen_futures::spawn_local({ - let weak = Rc::downgrade(&state); - let CursorImage { width, height, .. } = *image; + let weak = Arc::downgrade(&this); + let CursorImage { + width, + height, + hotspot_x, + hotspot_y, + .. + } = *image; async move { + // Keep checking if all references are dropped between every `await` call. if weak.strong_count() == 0 { return; } - let bitmap: ImageBitmap = bitmap.await.unwrap().unchecked_into(); + let bitmap: ImageBitmap = bitmap + .await + .expect("found invalid state in `ImageData`") + .unchecked_into(); if weak.strong_count() == 0 { return; } - let canvas: HtmlCanvasElement = - document.create_element("canvas").unwrap().unchecked_into(); + let canvas: HtmlCanvasElement = document + .create_element("canvas") + .expect("invalid tag name") + .unchecked_into(); #[allow(clippy::disallowed_methods)] canvas.set_width(width as u32); #[allow(clippy::disallowed_methods)] canvas.set_height(height as u32); + // 3. Draw `ImageBitmap` on an `HTMLCanvasElement`. let context: ImageBitmapRenderingContext = canvas .get_context("bitmaprenderer") - .unwrap() - .unwrap() + .expect("unexpected exception in `HTMLCanvasElement.getContext()`") + .expect("`bitmaprenderer` context unsupported") .unchecked_into(); context.transfer_from_image_bitmap(&bitmap); - thread_local! { - static CURRENT_STATE: RefCell>>>> = RefCell::new(None); - // `HTMLCanvasElement.toBlob()` can't be interrupted. So we have to use a - // `Closure` that doesn't need to be garbage-collected. - static CALLBACK: Closure)> = Closure::new(|blob| { - CURRENT_STATE.with(|weak| { - let Some(state) = weak.borrow_mut().take().and_then(|weak| weak.upgrade()) else { - return; - }; + // 4. Create a `Blob` from the `HTMLCanvasElement`. + // + // To keep the `Closure` alive until `HTMLCanvasElement.toBlob()` is done, + // we do the whole `Waker` strategy. Commonly on `Drop` the callback is aborted, + // but it would increase complexity and isn't possible in this case. + // Keep in mind that `HTMLCanvasElement.toBlob()` can call the callback immediately. + let value = Rc::new(RefCell::new(None)); + let waker = Rc::new(RefCell::>::new(None)); + let callback = Closure::once({ + let value = value.clone(); + let waker = waker.clone(); + move |blob: Option| { + *value.borrow_mut() = Some(blob); + if let Some(waker) = waker.borrow_mut().take() { + waker.wake(); + } + } + }); + canvas + .to_blob(callback.as_ref().unchecked_ref()) + .expect("failed with `SecurityError` despite only source coming from memory"); + let blob = future::poll_fn(|cx| { + if let Some(blob) = value.borrow_mut().take() { + Poll::Ready(blob) + } else { + *waker.borrow_mut() = Some(cx.waker().clone()); + Poll::Pending + } + }) + .await; + + let url = { + let Some(this) = weak.upgrade() else { + return; + }; + let mut this = this.get().borrow_mut(); + + let Some(blob) = blob else { + log::error!("creating custom cursor failed"); + let ImageState::Loading(state) = this.deref_mut() else { + unreachable!("found invalid state"); + }; + let state = state.take(); + *this = ImageState::Failed; + + if let Some(state) = state.and_then(|weak| weak.upgrade()) { let mut state = state.borrow_mut(); - // Extract old state. - let CursorImageState::Loading { style, cursor_visible, previous, hotspot_x, hotspot_y, .. } = state.take().unwrap() else { - unreachable!("found invalid state") + let SelectedCursor::ImageLoading { previous, .. } = + mem::take(&mut state.cursor) + else { + unreachable!("found invalid state"); }; + state.cursor = previous.into(); + } - let Some(blob) = blob else { - *state = Some(CursorImageState::Failed(previous)); - return; - }; - let data_url = Url::create_object_url_with_blob(&blob).unwrap(); - - let value = previous.style_with_url(&data_url, hotspot_x, hotspot_y); - - if cursor_visible.get() { - style.set("cursor", &value); - } - - *state = Some( - CursorImageState::Ready { - style: value, - image: WebCursorImage{ data_url, hotspot_x, hotspot_y }, - previous, - }); - }); - }); - } + return; + }; + + // 5. Create an object URL from the `Blob`. + Url::create_object_url_with_blob(&blob) + .expect("unexpected exception in `URL.createObjectURL()`") + }; - CURRENT_STATE.with(|state| *state.borrow_mut() = Some(weak)); - CALLBACK - .with(|callback| canvas.to_blob(callback.as_ref().unchecked_ref()).unwrap()); + Self::decode(weak, url, true, hotspot_x, hotspot_y).await; } }); - state + this + } + + fn from_url(url: String, hotspot_x: u16, hotspot_y: u16) -> Arc { + let this = CustomCursor::new(); + wasm_bindgen_futures::spawn_local(Self::decode( + Arc::downgrade(&this), + url, + false, + hotspot_x, + hotspot_y, + )); + + this + } + + async fn decode( + weak: sync::Weak, + url: String, + object: bool, + hotspot_x: u16, + hotspot_y: u16, + ) { + if weak.strong_count() == 0 { + return; + } + + // 6. Decode the image on an `HTMLImageElement` from the URL. + let image = + HtmlImageElement::new().expect("unexpected exception in `new HtmlImageElement`"); + image.set_src(&url); + let result = JsFuture::from(image.decode()).await; + + let Some(this) = weak.upgrade() else { + return; + }; + let mut this = this.get().borrow_mut(); + + let ImageState::Loading(state) = this.deref_mut() else { + unreachable!("found invalid state"); + }; + let state = state.take(); + + if let Err(error) = result { + log::error!("creating custom cursor failed: {error:?}"); + *this = ImageState::Failed; + + if let Some(state) = state.and_then(|weak| weak.upgrade()) { + let mut state = state.borrow_mut(); + let SelectedCursor::ImageLoading { previous, .. } = mem::take(&mut state.cursor) + else { + unreachable!("found invalid state"); + }; + state.cursor = previous.into(); + } + + return; + } + + let image = Image::new(url, object, image, hotspot_x, hotspot_y); + + // 7. Change the `CursorState` if queued. + if let Some(state) = state.and_then(|weak| weak.upgrade()) { + let mut state = state.borrow_mut(); + state.cursor = SelectedCursor::ImageReady(image.clone()); + state.set_style(); + } + + *this = ImageState::Ready(image); } } #[derive(Debug)] -pub struct WebCursorImage { - data_url: String, - hotspot_x: u16, - hotspot_y: u16, +pub struct Image { + style: String, + url: String, + object: bool, + _image: HtmlImageElement, } -impl Drop for WebCursorImage { +impl Drop for Image { fn drop(&mut self) { - Url::revoke_object_url(&self.data_url).unwrap(); + if self.object { + Url::revoke_object_url(&self.url) + .expect("unexpected exception in `URL.revokeObjectURL()`"); + } + } +} + +impl Image { + fn new( + url: String, + object: bool, + image: HtmlImageElement, + hotspot_x: u16, + hotspot_y: u16, + ) -> Rc { + let style = format!("url({url}) {hotspot_x} {hotspot_y}, auto"); + + Rc::new(Self { + style, + url, + object, + _image: image, + }) + } +} + +mod thread_safe { + use std::mem; + use std::thread::{self, ThreadId}; + + #[derive(Debug)] + pub struct ThreadSafe { + origin_thread: ThreadId, + value: T, + } + + impl ThreadSafe { + pub fn new(value: T) -> Self { + Self { + origin_thread: thread::current().id(), + value, + } + } + + pub fn get(&self) -> &T { + if self.origin_thread == thread::current().id() { + &self.value + } else { + panic!("value not accessible outside its origin thread") + } + } + + pub fn in_origin_thread(&self) -> bool { + self.origin_thread == thread::current().id() + } } + + impl Drop for ThreadSafe { + fn drop(&mut self) { + if mem::needs_drop::() && self.origin_thread != thread::current().id() { + panic!("value can't be dropped outside its origin thread") + } + } + } + + unsafe impl Send for ThreadSafe {} + unsafe impl Sync for ThreadSafe {} } diff --git a/src/platform_impl/web/mod.rs b/src/platform_impl/web/mod.rs index 433a4e8bed..56ca0eaf47 100644 --- a/src/platform_impl/web/mod.rs +++ b/src/platform_impl/web/mod.rs @@ -40,4 +40,5 @@ pub use self::window::{PlatformSpecificWindowBuilderAttributes, Window, WindowId pub(crate) use self::keyboard::KeyEventExtra; pub(crate) use crate::icon::NoIcon as PlatformIcon; pub(crate) use crate::platform_impl::Fullscreen; -pub(crate) use cursor::WebCustomCursor as PlatformCustomCursor; +pub(crate) use cursor::CustomCursor as PlatformCustomCursor; +pub(crate) use cursor::CustomCursorBuilder as PlatformCustomCursorBuilder; diff --git a/src/platform_impl/web/window.rs b/src/platform_impl/web/window.rs index 03990e926f..658ae65941 100644 --- a/src/platform_impl/web/window.rs +++ b/src/platform_impl/web/window.rs @@ -1,4 +1,3 @@ -use crate::cursor::CustomCursor; use crate::dpi::{PhysicalPosition, PhysicalSize, Position, Size}; use crate::error::{ExternalError, NotSupportedError, OsError as RootOE}; use crate::icon::Icon; @@ -8,14 +7,16 @@ use crate::window::{ }; use crate::SendSyncWrapper; -use super::cursor::SelectedCursor; +use super::cursor::CursorState; use super::r#async::Dispatcher; +use super::PlatformCustomCursor; use super::{backend, monitor::MonitorHandle, EventLoopWindowTarget, Fullscreen}; use web_sys::HtmlCanvasElement; -use std::cell::{Cell, RefCell}; +use std::cell::RefCell; use std::collections::VecDeque; use std::rc::Rc; +use std::sync::Arc; pub struct Window { inner: Dispatcher, @@ -25,8 +26,7 @@ pub struct Inner { id: WindowId, pub window: web_sys::Window, canvas: Rc>, - selected_cursor: RefCell, - cursor_visible: Rc>, + cursor: CursorState, destroy_fn: Option>, } @@ -45,6 +45,7 @@ impl Window { let canvas = backend::Canvas::create(id, window.clone(), document.clone(), &attr, platform_attr)?; let canvas = Rc::new(RefCell::new(canvas)); + let cursor = CursorState::new(canvas.borrow().style().clone()); target.register(&canvas, id, prevent_default); @@ -55,8 +56,7 @@ impl Window { id, window: window.clone(), canvas, - selected_cursor: Default::default(), - cursor_visible: Rc::new(Cell::new(true)), + cursor, destroy_fn: Some(destroy_fn), }; @@ -198,24 +198,12 @@ impl Inner { #[inline] pub fn set_cursor_icon(&self, cursor: CursorIcon) { - *self.selected_cursor.borrow_mut() = SelectedCursor::Named(cursor); - - if self.cursor_visible.get() { - self.canvas.borrow().style().set("cursor", cursor.name()); - } + self.cursor.set_cursor_icon(cursor) } #[inline] - pub fn set_custom_cursor(&self, cursor: CustomCursor) { - let canvas = self.canvas.borrow(); - let new_cursor = cursor.inner.build( - canvas.window(), - canvas.document(), - canvas.style(), - self.selected_cursor.take(), - self.cursor_visible.clone(), - ); - *self.selected_cursor.borrow_mut() = new_cursor; + pub(crate) fn set_custom_cursor(&self, cursor: Arc) { + self.cursor.set_custom_cursor(cursor) } #[inline] @@ -241,15 +229,7 @@ impl Inner { #[inline] pub fn set_cursor_visible(&self, visible: bool) { - if !visible && self.cursor_visible.get() { - self.canvas.borrow().style().set("cursor", "none"); - self.cursor_visible.set(false); - } else if visible && !self.cursor_visible.get() { - self.selected_cursor - .borrow() - .set_style(self.canvas.borrow().style()); - self.cursor_visible.set(true); - } + self.cursor.set_cursor_visible(visible) } #[inline] diff --git a/src/platform_impl/windows/mod.rs b/src/platform_impl/windows/mod.rs index b378494264..3d2253bcec 100644 --- a/src/platform_impl/windows/mod.rs +++ b/src/platform_impl/windows/mod.rs @@ -17,6 +17,7 @@ pub(crate) use self::{ pub use self::icon::WinIcon as PlatformIcon; pub(crate) use crate::cursor::CursorImage as PlatformCustomCursor; +pub(crate) use crate::cursor::CursorImage as PlatformCustomCursorBuilder; use crate::platform_impl::Fullscreen; use crate::event::DeviceId as RootDeviceId; diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index 144129b892..ae1fb07d95 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -55,7 +55,6 @@ use windows_sys::Win32::{ }; use crate::{ - cursor::CustomCursor, dpi::{PhysicalPosition, PhysicalSize, Position, Size}, error::{ExternalError, NotSupportedError, OsError as RootOsError}, icon::Icon, @@ -73,7 +72,8 @@ use crate::{ monitor::{self, MonitorHandle}, util, window_state::{CursorFlags, SavedWindow, WindowFlags, WindowState}, - Fullscreen, PlatformSpecificWindowBuilderAttributes, SelectedCursor, WindowId, + Fullscreen, PlatformCustomCursor, PlatformSpecificWindowBuilderAttributes, SelectedCursor, + WindowId, }, window::{ CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType, @@ -405,8 +405,8 @@ impl Window { } #[inline] - pub fn set_custom_cursor(&self, cursor: CustomCursor) { - let new_cursor = match WinCursor::new(&cursor.inner) { + pub(crate) fn set_custom_cursor(&self, cursor: Arc) { + let new_cursor = match WinCursor::new(&cursor) { Ok(cursor) => cursor, Err(err) => { warn!("Failed to create custom cursor: {err}"); diff --git a/src/window.rs b/src/window.rs index 2826219f2e..e5e22c187d 100644 --- a/src/window.rs +++ b/src/window.rs @@ -1,5 +1,5 @@ //! The [`Window`] struct and associated types. -use std::fmt; +use std::{fmt, sync::Arc}; use crate::{ dpi::{PhysicalPosition, PhysicalSize, Position, Size}, @@ -9,7 +9,7 @@ use crate::{ platform_impl, SendSyncWrapper, }; -pub use crate::cursor::{BadImage, CustomCursor, MAX_CURSOR_SIZE}; +pub use crate::cursor::{BadImage, CustomCursor, CustomCursorBuilder, MAX_CURSOR_SIZE}; pub use crate::icon::{BadIcon, Icon}; #[doc(inline)] @@ -1355,7 +1355,7 @@ impl Window { /// - **iOS / Android / Orbital:** Unsupported. #[inline] pub fn set_custom_cursor(&self, cursor: &CustomCursor) { - let cursor = cursor.clone(); + let cursor = Arc::clone(&cursor.inner); self.window .maybe_queue_on_main(move |w| w.set_custom_cursor(cursor)) } diff --git a/tests/send_objects.rs b/tests/send_objects.rs index 0288c6c3c4..0a5d1a9eb3 100644 --- a/tests/send_objects.rs +++ b/tests/send_objects.rs @@ -31,5 +31,6 @@ fn ids_send() { #[test] fn custom_cursor_send() { + needs_send::(); needs_send::(); } diff --git a/tests/sync_object.rs b/tests/sync_object.rs index 96b3b7df3b..8500ac5fa7 100644 --- a/tests/sync_object.rs +++ b/tests/sync_object.rs @@ -14,5 +14,6 @@ fn window_builder_sync() { #[test] fn custom_cursor_sync() { + needs_sync::(); needs_sync::(); }