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