Web: improve custom cursor loading (#3321)
This commit is contained in:
parent
e0fea25b06
commit
25d6a1d46d
4 changed files with 248 additions and 191 deletions
|
|
@ -1,16 +1,21 @@
|
||||||
|
use super::backend::Style;
|
||||||
|
use super::event_loop::runner::{EventWrapper, WeakShared};
|
||||||
|
use super::main_thread::{MainThreadMarker, MainThreadSafe};
|
||||||
|
use super::EventLoopWindowTarget;
|
||||||
|
use crate::cursor::{BadImage, Cursor, CursorImage};
|
||||||
|
use cursor_icon::CursorIcon;
|
||||||
|
use std::ops::Deref;
|
||||||
|
use std::sync::Weak;
|
||||||
use std::{
|
use std::{
|
||||||
cell::RefCell,
|
cell::RefCell,
|
||||||
future,
|
future,
|
||||||
hash::{Hash, Hasher},
|
hash::{Hash, Hasher},
|
||||||
mem,
|
mem,
|
||||||
ops::DerefMut,
|
ops::DerefMut,
|
||||||
rc::{self, Rc},
|
rc::Rc,
|
||||||
sync::{self, Arc},
|
sync::Arc,
|
||||||
task::{Poll, Waker},
|
task::{Poll, Waker},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::cursor::{BadImage, Cursor, CursorImage};
|
|
||||||
use cursor_icon::CursorIcon;
|
|
||||||
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::{
|
||||||
|
|
@ -18,10 +23,6 @@ use web_sys::{
|
||||||
ImageBitmapRenderingContext, ImageData, PremultiplyAlpha, Url, Window,
|
ImageBitmapRenderingContext, ImageData, PremultiplyAlpha, Url, Window,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::backend::Style;
|
|
||||||
use super::main_thread::{MainThreadMarker, MainThreadSafe};
|
|
||||||
use super::EventLoopWindowTarget;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) enum CustomCursorBuilder {
|
pub(crate) enum CustomCursorBuilder {
|
||||||
Image(CursorImage),
|
Image(CursorImage),
|
||||||
|
|
@ -94,136 +95,176 @@ impl CustomCursor {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct CursorState(Rc<RefCell<State>>);
|
pub struct CursorHandler {
|
||||||
|
|
||||||
impl CursorState {
|
|
||||||
pub fn new(main_thread: MainThreadMarker, style: Style) -> Self {
|
|
||||||
Self(Rc::new(RefCell::new(State {
|
|
||||||
main_thread,
|
|
||||||
style,
|
|
||||||
visible: true,
|
|
||||||
cursor: SelectedCursor::default(),
|
|
||||||
})))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_cursor(&self, cursor: Cursor) {
|
|
||||||
let mut this = self.0.borrow_mut();
|
|
||||||
|
|
||||||
match cursor {
|
|
||||||
Cursor::Icon(icon) => {
|
|
||||||
if let SelectedCursor::ImageLoading { state, .. } = &this.cursor {
|
|
||||||
if let ImageState::Loading(state) =
|
|
||||||
state.0.get(this.main_thread).borrow_mut().deref_mut()
|
|
||||||
{
|
|
||||||
state.take();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.cursor = SelectedCursor::Named(icon);
|
|
||||||
this.set_style();
|
|
||||||
}
|
|
||||||
Cursor::Custom(cursor) => match cursor
|
|
||||||
.inner
|
|
||||||
.0
|
|
||||||
.get(this.main_thread)
|
|
||||||
.borrow_mut()
|
|
||||||
.deref_mut()
|
|
||||||
{
|
|
||||||
ImageState::Loading(state) => {
|
|
||||||
this.cursor = SelectedCursor::ImageLoading {
|
|
||||||
state: cursor.inner.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 {
|
|
||||||
main_thread: MainThreadMarker,
|
main_thread: MainThreadMarker,
|
||||||
|
runner: WeakShared,
|
||||||
style: Style,
|
style: Style,
|
||||||
visible: bool,
|
visible: bool,
|
||||||
cursor: SelectedCursor,
|
cursor: SelectedCursor,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl State {
|
impl CursorHandler {
|
||||||
pub fn set_style(&self) {
|
pub(crate) fn new(main_thread: MainThreadMarker, runner: WeakShared, style: Style) -> Self {
|
||||||
if self.visible {
|
Self {
|
||||||
let value = match &self.cursor {
|
main_thread,
|
||||||
SelectedCursor::Named(icon) => icon.name(),
|
runner,
|
||||||
SelectedCursor::ImageLoading { previous, .. } => previous.style(),
|
style,
|
||||||
SelectedCursor::ImageReady(image) => &image.style,
|
visible: true,
|
||||||
|
cursor: SelectedCursor::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_cursor(&mut self, cursor: Cursor) {
|
||||||
|
match cursor {
|
||||||
|
Cursor::Icon(icon) => {
|
||||||
|
if let SelectedCursor::Icon(old_icon)
|
||||||
|
| SelectedCursor::ImageLoading {
|
||||||
|
previous: Previous::Icon(old_icon),
|
||||||
|
..
|
||||||
|
} = &self.cursor
|
||||||
|
{
|
||||||
|
if *old_icon == icon {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.cursor = SelectedCursor::Icon(icon);
|
||||||
|
self.set_style();
|
||||||
|
}
|
||||||
|
Cursor::Custom(cursor) => {
|
||||||
|
let cursor = cursor.inner;
|
||||||
|
|
||||||
|
if let SelectedCursor::ImageLoading {
|
||||||
|
cursor: old_cursor, ..
|
||||||
|
}
|
||||||
|
| SelectedCursor::ImageReady(old_cursor) = &self.cursor
|
||||||
|
{
|
||||||
|
if *old_cursor == cursor {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut image = cursor.0.get(self.main_thread).borrow_mut();
|
||||||
|
match image.deref_mut() {
|
||||||
|
ImageState::Loading(state) => {
|
||||||
|
*state = Some(self.runner.clone());
|
||||||
|
drop(image);
|
||||||
|
self.cursor = SelectedCursor::ImageLoading {
|
||||||
|
cursor,
|
||||||
|
previous: mem::take(&mut self.cursor).into(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
ImageState::Failed => log::error!("tried to load invalid cursor"),
|
||||||
|
ImageState::Ready { .. } => {
|
||||||
|
drop(image);
|
||||||
|
self.cursor = SelectedCursor::ImageReady(cursor);
|
||||||
|
self.set_style();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_cursor_visible(&mut self, visible: bool) {
|
||||||
|
if !visible && self.visible {
|
||||||
|
self.visible = false;
|
||||||
|
self.style.set("cursor", "none");
|
||||||
|
} else if visible && !self.visible {
|
||||||
|
self.visible = true;
|
||||||
|
self.set_style();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_cursor_ready(&mut self, result: Result<CustomCursorHandle, CustomCursorHandle>) {
|
||||||
|
if let SelectedCursor::ImageLoading {
|
||||||
|
cursor: current_cursor,
|
||||||
|
..
|
||||||
|
} = &self.cursor
|
||||||
|
{
|
||||||
|
let current_cursor = Arc::downgrade(¤t_cursor.0);
|
||||||
|
|
||||||
|
let (Ok(new_cursor) | Err(new_cursor)) = &result;
|
||||||
|
|
||||||
|
if !new_cursor.0.ptr_eq(¤t_cursor) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let SelectedCursor::ImageLoading { cursor, previous } = mem::take(&mut self.cursor)
|
||||||
|
else {
|
||||||
|
unreachable!("found wrong state")
|
||||||
};
|
};
|
||||||
|
|
||||||
self.style.set("cursor", value);
|
match result {
|
||||||
|
Ok(_) => {
|
||||||
|
self.cursor = SelectedCursor::ImageReady(cursor);
|
||||||
|
self.set_style();
|
||||||
|
}
|
||||||
|
Err(_) => self.cursor = previous.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_style(&self) {
|
||||||
|
if self.visible {
|
||||||
|
match &self.cursor {
|
||||||
|
SelectedCursor::Icon(icon)
|
||||||
|
| SelectedCursor::ImageLoading {
|
||||||
|
previous: Previous::Icon(icon),
|
||||||
|
..
|
||||||
|
} => self.style.set("cursor", icon.name()),
|
||||||
|
SelectedCursor::ImageLoading {
|
||||||
|
previous: Previous::Image(cursor),
|
||||||
|
..
|
||||||
|
}
|
||||||
|
| SelectedCursor::ImageReady(cursor) => {
|
||||||
|
if let ImageState::Ready { style, .. } =
|
||||||
|
cursor.0.get(self.main_thread).borrow().deref()
|
||||||
|
{
|
||||||
|
self.style.set("cursor", style)
|
||||||
|
} else {
|
||||||
|
unreachable!("found invalid saved state")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
enum SelectedCursor {
|
enum SelectedCursor {
|
||||||
Named(CursorIcon),
|
Icon(CursorIcon),
|
||||||
ImageLoading {
|
ImageLoading {
|
||||||
state: CustomCursor,
|
cursor: CustomCursor,
|
||||||
previous: Previous,
|
previous: Previous,
|
||||||
},
|
},
|
||||||
ImageReady(Rc<Image>),
|
ImageReady(CustomCursor),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for SelectedCursor {
|
impl Default for SelectedCursor {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::Named(Default::default())
|
Self::Icon(Default::default())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Previous> for SelectedCursor {
|
impl From<Previous> for SelectedCursor {
|
||||||
fn from(previous: Previous) -> Self {
|
fn from(previous: Previous) -> Self {
|
||||||
match previous {
|
match previous {
|
||||||
Previous::Named(icon) => Self::Named(icon),
|
Previous::Icon(icon) => Self::Icon(icon),
|
||||||
Previous::Image(image) => Self::ImageReady(image),
|
Previous::Image(cursor) => Self::ImageReady(cursor),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Previous {
|
pub enum Previous {
|
||||||
Named(CursorIcon),
|
Icon(CursorIcon),
|
||||||
Image(Rc<Image>),
|
Image(CustomCursor),
|
||||||
}
|
|
||||||
|
|
||||||
impl Previous {
|
|
||||||
fn style(&self) -> &str {
|
|
||||||
match self {
|
|
||||||
Previous::Named(icon) => icon.name(),
|
|
||||||
Previous::Image(image) => &image.style,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<SelectedCursor> for Previous {
|
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::Icon(icon) => Self::Icon(icon),
|
||||||
SelectedCursor::ImageLoading { previous, .. } => previous,
|
SelectedCursor::ImageLoading { previous, .. } => previous,
|
||||||
SelectedCursor::ImageReady(image) => Self::Image(image),
|
SelectedCursor::ImageReady(image) => Self::Image(image),
|
||||||
}
|
}
|
||||||
|
|
@ -232,9 +273,13 @@ impl From<SelectedCursor> for Previous {
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
enum ImageState {
|
enum ImageState {
|
||||||
Loading(Option<rc::Weak<RefCell<State>>>),
|
Loading(Option<WeakShared>),
|
||||||
Failed,
|
Failed,
|
||||||
Ready(Rc<Image>),
|
Ready {
|
||||||
|
style: String,
|
||||||
|
_object_url: Option<ObjectUrl>,
|
||||||
|
_image: HtmlImageElement,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ImageState {
|
impl ImageState {
|
||||||
|
|
@ -250,7 +295,7 @@ impl ImageState {
|
||||||
// 4. Create a `Blob` from the `HTMLCanvasElement`.
|
// 4. Create a `Blob` from the `HTMLCanvasElement`.
|
||||||
// 5. Create an object URL from the `Blob`.
|
// 5. Create an object URL from the `Blob`.
|
||||||
// 6. Decode the image on an `HTMLImageElement` from the URL.
|
// 6. Decode the image on an `HTMLImageElement` from the URL.
|
||||||
// 7. Change the `CursorState` if queued.
|
// 7. Notify event loop if one is registered.
|
||||||
|
|
||||||
// 1. Create an `ImageData` from the RGBA data.
|
// 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
|
||||||
|
|
@ -295,7 +340,6 @@ impl ImageState {
|
||||||
.expect("unexpected exception in `createImageBitmap()`"),
|
.expect("unexpected exception in `createImageBitmap()`"),
|
||||||
);
|
);
|
||||||
|
|
||||||
#[allow(clippy::arc_with_non_send_sync)]
|
|
||||||
let this = CustomCursor::new(main_thread);
|
let this = CustomCursor::new(main_thread);
|
||||||
|
|
||||||
wasm_bindgen_futures::spawn_local({
|
wasm_bindgen_futures::spawn_local({
|
||||||
|
|
@ -338,6 +382,8 @@ impl ImageState {
|
||||||
.expect("`bitmaprenderer` context unsupported")
|
.expect("`bitmaprenderer` context unsupported")
|
||||||
.unchecked_into();
|
.unchecked_into();
|
||||||
context.transfer_from_image_bitmap(&bitmap);
|
context.transfer_from_image_bitmap(&bitmap);
|
||||||
|
drop(bitmap);
|
||||||
|
drop(context);
|
||||||
|
|
||||||
// 4. Create a `Blob` from the `HTMLCanvasElement`.
|
// 4. Create a `Blob` from the `HTMLCanvasElement`.
|
||||||
//
|
//
|
||||||
|
|
@ -369,40 +415,37 @@ impl ImageState {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
drop(canvas);
|
||||||
|
|
||||||
let url = {
|
if weak.strong_count() == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(blob) = blob else {
|
||||||
|
log::error!("creating object URL from custom cursor failed");
|
||||||
let Some(this) = weak.upgrade() else {
|
let Some(this) = weak.upgrade() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let mut this = this.get(main_thread).borrow_mut();
|
let mut this = this.get(main_thread).borrow_mut();
|
||||||
|
let ImageState::Loading(runner) = this.deref_mut() else {
|
||||||
let Some(blob) = blob else {
|
unreachable!("found invalid state");
|
||||||
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 SelectedCursor::ImageLoading { previous, .. } =
|
|
||||||
mem::take(&mut state.cursor)
|
|
||||||
else {
|
|
||||||
unreachable!("found invalid state");
|
|
||||||
};
|
|
||||||
state.cursor = previous.into();
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
};
|
};
|
||||||
|
let runner = runner.take();
|
||||||
|
*this = ImageState::Failed;
|
||||||
|
|
||||||
// 5. Create an object URL from the `Blob`.
|
if let Some(runner) = runner.and_then(|weak| weak.upgrade()) {
|
||||||
Url::create_object_url_with_blob(&blob)
|
runner.send_event(EventWrapper::CursorReady(Err(CustomCursorHandle(weak))));
|
||||||
.expect("unexpected exception in `URL.createObjectURL()`")
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
Self::decode(main_thread, weak, url, true, hotspot_x, hotspot_y).await;
|
// 5. Create an object URL from the `Blob`.
|
||||||
|
let url = Url::create_object_url_with_blob(&blob)
|
||||||
|
.expect("unexpected exception in `URL.createObjectURL()`");
|
||||||
|
let url = UrlType::Object(ObjectUrl(url));
|
||||||
|
|
||||||
|
Self::decode(main_thread, weak, url, hotspot_x, hotspot_y).await;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -415,13 +458,11 @@ impl ImageState {
|
||||||
hotspot_x: u16,
|
hotspot_x: u16,
|
||||||
hotspot_y: u16,
|
hotspot_y: u16,
|
||||||
) -> CustomCursor {
|
) -> CustomCursor {
|
||||||
#[allow(clippy::arc_with_non_send_sync)]
|
|
||||||
let this = CustomCursor::new(main_thread);
|
let this = CustomCursor::new(main_thread);
|
||||||
wasm_bindgen_futures::spawn_local(Self::decode(
|
wasm_bindgen_futures::spawn_local(Self::decode(
|
||||||
main_thread,
|
main_thread,
|
||||||
Arc::downgrade(&this.0),
|
Arc::downgrade(&this.0),
|
||||||
url,
|
UrlType::Plain(url),
|
||||||
false,
|
|
||||||
hotspot_x,
|
hotspot_x,
|
||||||
hotspot_y,
|
hotspot_y,
|
||||||
));
|
));
|
||||||
|
|
@ -431,9 +472,8 @@ impl ImageState {
|
||||||
|
|
||||||
async fn decode(
|
async fn decode(
|
||||||
main_thread: MainThreadMarker,
|
main_thread: MainThreadMarker,
|
||||||
weak: sync::Weak<MainThreadSafe<RefCell<ImageState>>>,
|
weak: Weak<MainThreadSafe<RefCell<ImageState>>>,
|
||||||
url: String,
|
url: UrlType,
|
||||||
object: bool,
|
|
||||||
hotspot_x: u16,
|
hotspot_x: u16,
|
||||||
hotspot_y: u16,
|
hotspot_y: u16,
|
||||||
) {
|
) {
|
||||||
|
|
@ -444,7 +484,7 @@ impl ImageState {
|
||||||
// 6. Decode the image on an `HTMLImageElement` from the URL.
|
// 6. Decode the image on an `HTMLImageElement` from the URL.
|
||||||
let image =
|
let image =
|
||||||
HtmlImageElement::new().expect("unexpected exception in `new HtmlImageElement`");
|
HtmlImageElement::new().expect("unexpected exception in `new HtmlImageElement`");
|
||||||
image.set_src(&url);
|
image.set_src(url.url());
|
||||||
let result = JsFuture::from(image.decode()).await;
|
let result = JsFuture::from(image.decode()).await;
|
||||||
|
|
||||||
let Some(this) = weak.upgrade() else {
|
let Some(this) = weak.upgrade() else {
|
||||||
|
|
@ -452,72 +492,60 @@ impl ImageState {
|
||||||
};
|
};
|
||||||
let mut this = this.get(main_thread).borrow_mut();
|
let mut this = this.get(main_thread).borrow_mut();
|
||||||
|
|
||||||
let ImageState::Loading(state) = this.deref_mut() else {
|
let ImageState::Loading(runner) = this.deref_mut() else {
|
||||||
unreachable!("found invalid state");
|
unreachable!("found invalid state");
|
||||||
};
|
};
|
||||||
let state = state.take();
|
let runner = runner.take();
|
||||||
|
|
||||||
if let Err(error) = result {
|
if let Err(error) = result {
|
||||||
log::error!("creating custom cursor failed: {error:?}");
|
log::error!("decoding custom cursor failed: {error:?}");
|
||||||
*this = ImageState::Failed;
|
*this = ImageState::Failed;
|
||||||
|
|
||||||
if let Some(state) = state.and_then(|weak| weak.upgrade()) {
|
if let Some(runner) = runner.and_then(|weak| weak.upgrade()) {
|
||||||
let mut state = state.borrow_mut();
|
runner.send_event(EventWrapper::CursorReady(Err(CustomCursorHandle(weak))));
|
||||||
let SelectedCursor::ImageLoading { previous, .. } = mem::take(&mut state.cursor)
|
|
||||||
else {
|
|
||||||
unreachable!("found invalid state");
|
|
||||||
};
|
|
||||||
state.cursor = previous.into();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let image = Image::new(url, object, image, hotspot_x, hotspot_y);
|
*this = ImageState::Ready {
|
||||||
|
style: format!("url({}) {hotspot_x} {hotspot_y}, auto", url.url()),
|
||||||
|
_object_url: match url {
|
||||||
|
UrlType::Plain(_) => None,
|
||||||
|
UrlType::Object(object_url) => Some(object_url),
|
||||||
|
},
|
||||||
|
_image: image,
|
||||||
|
};
|
||||||
|
|
||||||
// 7. Change the `CursorState` if queued.
|
// 7. Notify event loop if one is registered.
|
||||||
if let Some(state) = state.and_then(|weak| weak.upgrade()) {
|
if let Some(runner) = runner.and_then(|weak| weak.upgrade()) {
|
||||||
let mut state = state.borrow_mut();
|
runner.send_event(EventWrapper::CursorReady(Ok(CustomCursorHandle(weak))));
|
||||||
state.cursor = SelectedCursor::ImageReady(image.clone());
|
|
||||||
state.set_style();
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
*this = ImageState::Ready(image);
|
#[derive(Clone)]
|
||||||
|
pub struct CustomCursorHandle(Weak<MainThreadSafe<RefCell<ImageState>>>);
|
||||||
|
|
||||||
|
enum UrlType {
|
||||||
|
Plain(String),
|
||||||
|
Object(ObjectUrl),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UrlType {
|
||||||
|
fn url(&self) -> &str {
|
||||||
|
match &self {
|
||||||
|
UrlType::Plain(url) => url,
|
||||||
|
UrlType::Object(object_url) => &object_url.0,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Image {
|
struct ObjectUrl(String);
|
||||||
style: String,
|
|
||||||
url: String,
|
|
||||||
object: bool,
|
|
||||||
_image: HtmlImageElement,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for Image {
|
impl Drop for ObjectUrl {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
if self.object {
|
Url::revoke_object_url(&self.0).expect("unexpected exception in `URL.revokeObjectURL()`");
|
||||||
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,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
use super::super::cursor::CustomCursorHandle;
|
||||||
use super::super::main_thread::MainThreadMarker;
|
use super::super::main_thread::MainThreadMarker;
|
||||||
use super::super::DeviceId;
|
use super::super::DeviceId;
|
||||||
use super::{backend, state::State};
|
use super::{backend, state::State};
|
||||||
|
|
@ -139,6 +140,16 @@ impl Runner {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
EventWrapper::CursorReady(result) => {
|
||||||
|
for (_, canvas, _) in runner.0.all_canvases.borrow().deref() {
|
||||||
|
if let Some(canvas) = canvas.upgrade() {
|
||||||
|
canvas
|
||||||
|
.borrow_mut()
|
||||||
|
.cursor
|
||||||
|
.handle_cursor_ready(result.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -811,6 +822,19 @@ impl Shared {
|
||||||
pub(crate) fn waker(&self) -> Waker<Weak<Execution>> {
|
pub(crate) fn waker(&self) -> Waker<Weak<Execution>> {
|
||||||
self.0.proxy_spawner.waker()
|
self.0.proxy_spawner.waker()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn weak(&self) -> WeakShared {
|
||||||
|
WeakShared(Rc::downgrade(&self.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub(crate) struct WeakShared(Weak<Execution>);
|
||||||
|
|
||||||
|
impl WeakShared {
|
||||||
|
pub(crate) fn upgrade(&self) -> Option<Shared> {
|
||||||
|
self.0.upgrade().map(Shared)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) enum EventWrapper {
|
pub(crate) enum EventWrapper {
|
||||||
|
|
@ -820,6 +844,7 @@ pub(crate) enum EventWrapper {
|
||||||
size: PhysicalSize<u32>,
|
size: PhysicalSize<u32>,
|
||||||
scale: f64,
|
scale: f64,
|
||||||
},
|
},
|
||||||
|
CursorReady(Result<CustomCursorHandle, CustomCursorHandle>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Event<()>> for EventWrapper {
|
impl From<Event<()>> for EventWrapper {
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,8 @@ use crate::keyboard::{Key, KeyLocation, ModifiersState, PhysicalKey};
|
||||||
use crate::platform_impl::{OsError, PlatformSpecificWindowBuilderAttributes};
|
use crate::platform_impl::{OsError, PlatformSpecificWindowBuilderAttributes};
|
||||||
use crate::window::{WindowAttributes, WindowId as RootWindowId};
|
use crate::window::{WindowAttributes, WindowId as RootWindowId};
|
||||||
|
|
||||||
|
use super::super::cursor::CursorHandler;
|
||||||
|
use super::super::event_loop::runner::WeakShared;
|
||||||
use super::super::main_thread::MainThreadMarker;
|
use super::super::main_thread::MainThreadMarker;
|
||||||
use super::super::WindowId;
|
use super::super::WindowId;
|
||||||
use super::animation_frame::AnimationFrameHandler;
|
use super::animation_frame::AnimationFrameHandler;
|
||||||
|
|
@ -46,6 +48,7 @@ pub struct Canvas {
|
||||||
animation_frame_handler: AnimationFrameHandler,
|
animation_frame_handler: AnimationFrameHandler,
|
||||||
on_touch_end: Option<EventListenerHandle<dyn FnMut(Event)>>,
|
on_touch_end: Option<EventListenerHandle<dyn FnMut(Event)>>,
|
||||||
on_context_menu: Option<EventListenerHandle<dyn FnMut(PointerEvent)>>,
|
on_context_menu: Option<EventListenerHandle<dyn FnMut(PointerEvent)>>,
|
||||||
|
pub cursor: CursorHandler,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Common {
|
pub struct Common {
|
||||||
|
|
@ -66,8 +69,9 @@ pub struct Style {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Canvas {
|
impl Canvas {
|
||||||
pub fn create(
|
pub(crate) fn create(
|
||||||
main_thread: MainThreadMarker,
|
main_thread: MainThreadMarker,
|
||||||
|
runner: WeakShared,
|
||||||
id: WindowId,
|
id: WindowId,
|
||||||
window: web_sys::Window,
|
window: web_sys::Window,
|
||||||
document: Document,
|
document: Document,
|
||||||
|
|
@ -107,6 +111,8 @@ impl Canvas {
|
||||||
|
|
||||||
let style = Style::new(&window, &canvas);
|
let style = Style::new(&window, &canvas);
|
||||||
|
|
||||||
|
let cursor = CursorHandler::new(main_thread, runner, style.clone());
|
||||||
|
|
||||||
let common = Common {
|
let common = Common {
|
||||||
window: window.clone(),
|
window: window.clone(),
|
||||||
document: document.clone(),
|
document: document.clone(),
|
||||||
|
|
@ -163,6 +169,7 @@ impl Canvas {
|
||||||
animation_frame_handler: AnimationFrameHandler::new(window),
|
animation_frame_handler: AnimationFrameHandler::new(window),
|
||||||
on_touch_end: None,
|
on_touch_end: None,
|
||||||
on_context_menu: None,
|
on_context_menu: None,
|
||||||
|
cursor,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ use crate::window::{
|
||||||
WindowAttributes, WindowButtons, WindowId as RootWI, WindowLevel,
|
WindowAttributes, WindowButtons, WindowId as RootWI, WindowLevel,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::cursor::CursorState;
|
|
||||||
use super::main_thread::{MainThreadMarker, MainThreadSafe};
|
use super::main_thread::{MainThreadMarker, MainThreadSafe};
|
||||||
use super::r#async::Dispatcher;
|
use super::r#async::Dispatcher;
|
||||||
use super::{backend, monitor::MonitorHandle, EventLoopWindowTarget, Fullscreen};
|
use super::{backend, monitor::MonitorHandle, EventLoopWindowTarget, Fullscreen};
|
||||||
|
|
@ -25,7 +24,6 @@ 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>>,
|
||||||
cursor: CursorState,
|
|
||||||
destroy_fn: Option<Box<dyn FnOnce()>>,
|
destroy_fn: Option<Box<dyn FnOnce()>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -41,6 +39,7 @@ impl Window {
|
||||||
let document = target.runner.document();
|
let document = target.runner.document();
|
||||||
let canvas = backend::Canvas::create(
|
let canvas = backend::Canvas::create(
|
||||||
target.runner.main_thread(),
|
target.runner.main_thread(),
|
||||||
|
target.runner.weak(),
|
||||||
id,
|
id,
|
||||||
window.clone(),
|
window.clone(),
|
||||||
document.clone(),
|
document.clone(),
|
||||||
|
|
@ -48,7 +47,6 @@ impl Window {
|
||||||
platform_attr,
|
platform_attr,
|
||||||
)?;
|
)?;
|
||||||
let canvas = Rc::new(RefCell::new(canvas));
|
let canvas = Rc::new(RefCell::new(canvas));
|
||||||
let cursor = CursorState::new(target.runner.main_thread(), canvas.borrow().style().clone());
|
|
||||||
|
|
||||||
target.register(&canvas, id);
|
target.register(&canvas, id);
|
||||||
|
|
||||||
|
|
@ -59,7 +57,6 @@ impl Window {
|
||||||
id,
|
id,
|
||||||
window: window.clone(),
|
window: window.clone(),
|
||||||
canvas,
|
canvas,
|
||||||
cursor,
|
|
||||||
destroy_fn: Some(destroy_fn),
|
destroy_fn: Some(destroy_fn),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -227,7 +224,7 @@ impl Inner {
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn set_cursor(&self, cursor: Cursor) {
|
pub fn set_cursor(&self, cursor: Cursor) {
|
||||||
self.cursor.set_cursor(cursor)
|
self.canvas.borrow_mut().cursor.set_cursor(cursor)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|
@ -253,7 +250,7 @@ impl Inner {
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn set_cursor_visible(&self, visible: bool) {
|
pub fn set_cursor_visible(&self, visible: bool) {
|
||||||
self.cursor.set_cursor_visible(visible)
|
self.canvas.borrow_mut().cursor.set_cursor_visible(visible)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue