From e791cd53948a24b2a3d2a1258bf31a9986c93590 Mon Sep 17 00:00:00 2001 From: daxpedda Date: Sat, 20 Jul 2024 18:29:10 +0200 Subject: [PATCH] Web: async improvements --- src/changelog/unreleased.md | 1 + src/platform/web.rs | 9 +- src/platform_impl/web/async/dispatcher.rs | 28 ++- src/platform_impl/web/async/notifier.rs | 25 ++- src/platform_impl/web/async/waker.rs | 9 +- src/platform_impl/web/async/wrapper.rs | 15 +- src/platform_impl/web/cursor.rs | 4 +- src/platform_impl/web/event_loop/proxy.rs | 8 +- src/platform_impl/web/event_loop/runner.rs | 42 ++-- .../web/event_loop/window_target.rs | 16 +- src/platform_impl/web/web_sys/canvas.rs | 199 ++++++++++-------- src/platform_impl/web/window.rs | 97 +++++---- 12 files changed, 236 insertions(+), 217 deletions(-) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 96a13ba13b..02a091c76f 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -70,6 +70,7 @@ changelog entry. - Change signature of `EventLoop::run_app`, `EventLoopExtPumpEvents::pump_app_events` and `EventLoopExtRunOnDemand::run_app_on_demand` to accept a `impl ApplicationHandler` directly, instead of requiring a `&mut` reference to it. +- On Web, `Window::canvas()` now returns a reference. ### Removed diff --git a/src/platform/web.rs b/src/platform/web.rs index c3e570c3ed..f737ba3058 100644 --- a/src/platform/web.rs +++ b/src/platform/web.rs @@ -42,6 +42,7 @@ //! [`WindowEvent::Touch`]: crate::event::WindowEvent::Touch //! [`Window::set_outer_position()`]: crate::window::Window::set_outer_position +use std::cell::Ref; use std::error::Error; use std::fmt::{self, Display, Formatter}; use std::future::Future; @@ -67,7 +68,7 @@ pub struct HtmlCanvasElement; pub trait WindowExtWeb { /// Only returns the canvas if called from inside the window context (the /// main thread). - fn canvas(&self) -> Option; + fn canvas(&self) -> Option>; /// Returns [`true`] if calling `event.preventDefault()` is enabled. /// @@ -87,7 +88,7 @@ pub trait WindowExtWeb { impl WindowExtWeb for Window { #[inline] - fn canvas(&self) -> Option { + fn canvas(&self) -> Option> { self.window.canvas() } @@ -175,7 +176,7 @@ pub trait EventLoopExtWeb { not(all(web_platform, target_feature = "exception-handling")), doc = "[`run_app()`]: EventLoop::run_app()" )] - /// [^1]: `run_app()` is _not_ available on WASM when the target supports `exception-handling`. + /// [^1]: `run_app()` is _not_ available on Wasm when the target supports `exception-handling`. fn spawn_app(self, app: A); /// Sets the strategy for [`ControlFlow::Poll`]. @@ -398,7 +399,7 @@ impl fmt::Display for BadAnimation { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Empty => write!(f, "No cursors supplied"), - Self::Animation => write!(f, "A supplied cursor is an animtion"), + Self::Animation => write!(f, "A supplied cursor is an animation"), } } } diff --git a/src/platform_impl/web/async/dispatcher.rs b/src/platform_impl/web/async/dispatcher.rs index b024042223..beac64873a 100644 --- a/src/platform_impl/web/async/dispatcher.rs +++ b/src/platform_impl/web/async/dispatcher.rs @@ -10,13 +10,12 @@ pub struct Dispatcher(Wrapper>>, Closure struct Closure(Box); impl Dispatcher { - #[track_caller] - pub fn new(main_thread: MainThreadMarker, value: T) -> Option<(Self, DispatchRunner)> { + pub fn new(main_thread: MainThreadMarker, value: T) -> (Self, DispatchRunner) { let (sender, receiver) = channel::>(); let sender = Arc::new(sender); let receiver = Rc::new(receiver); - Wrapper::new( + let wrapper = Wrapper::new( main_thread, value, |value, Closure(closure)| { @@ -29,8 +28,7 @@ impl Dispatcher { move |value| async move { while let Ok(Closure(closure)) = receiver.next().await { // SAFETY: The given `Closure` here isn't really `'static`, so we shouldn't - // do anything funny with it here. See - // `Self::queue()`. + // do anything funny with it here. See `Self::queue()`. closure(value.borrow().as_ref().unwrap()) } } @@ -41,25 +39,25 @@ impl Dispatcher { // anything funny with it here. See `Self::queue()`. sender.send(closure).unwrap() }, - ) - .map(|wrapper| (Self(wrapper.clone()), DispatchRunner { wrapper, receiver })) + ); + (Self(wrapper.clone()), DispatchRunner { wrapper, receiver }) } - pub fn value(&self) -> Option> { - self.0.value() + pub fn value(&self, main_thread: MainThreadMarker) -> Ref<'_, T> { + self.0.value(main_thread) } pub fn dispatch(&self, f: impl 'static + FnOnce(&T) + Send) { - if let Some(value) = self.0.value() { - f(&value) + if let Some(main_thread) = MainThreadMarker::new() { + f(&self.0.value(main_thread)) } else { self.0.send(Closure(Box::new(f))) } } pub fn queue(&self, f: impl FnOnce(&T) -> R + Send) -> R { - if let Some(value) = self.0.value() { - f(&value) + if let Some(main_thread) = MainThreadMarker::new() { + f(&self.0.value(main_thread)) } else { let pair = Arc::new((Mutex::new(None), Condvar::new())); let closure = Box::new({ @@ -98,13 +96,13 @@ pub struct DispatchRunner { } impl DispatchRunner { - pub fn run(&self) { + pub fn run(&self, main_thread: MainThreadMarker) { while let Some(Closure(closure)) = self.receiver.try_recv().expect("should only be closed when `Dispatcher` is dropped") { // SAFETY: The given `Closure` here isn't really `'static`, so we shouldn't do anything // funny with it here. See `Self::queue()`. - closure(&self.wrapper.value().expect("don't call this outside the main thread")) + closure(&self.wrapper.value(main_thread)) } } } diff --git a/src/platform_impl/web/async/notifier.rs b/src/platform_impl/web/async/notifier.rs index 81655d1849..c92d9e4a6b 100644 --- a/src/platform_impl/web/async/notifier.rs +++ b/src/platform_impl/web/async/notifier.rs @@ -17,24 +17,28 @@ impl Notifier { if self.0.value.set(value).is_err() { unreachable!("value set before") } + } + + pub fn notified(&self) -> Notified { + Notified(Some(Arc::clone(&self.0))) + } +} +impl Drop for Notifier { + fn drop(&mut self) { self.0.queue.close(); while let Ok(waker) = self.0.queue.pop() { waker.wake() } } - - pub fn notified(&self) -> Notified { - Notified(Some(Arc::clone(&self.0))) - } } #[derive(Clone, Debug)] pub struct Notified(Option>>); impl Future for Notified { - type Output = T; + type Output = Option; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = self.0.take().expect("`Receiver` polled after completion"); @@ -54,14 +58,13 @@ impl Future for Notified { } } - let (Ok(Some(value)) | Err(Some(value))) = Arc::try_unwrap(this) + match Arc::try_unwrap(this) .map(|mut inner| inner.value.take()) .map_err(|this| this.value.get().cloned()) - else { - unreachable!("found no value despite being ready") - }; - - Poll::Ready(value) + { + Ok(Some(value)) | Err(Some(value)) => Poll::Ready(Some(value)), + _ => Poll::Ready(None), + } } } diff --git a/src/platform_impl/web/async/waker.rs b/src/platform_impl/web/async/waker.rs index a1a1515fc1..33387363a8 100644 --- a/src/platform_impl/web/async/waker.rs +++ b/src/platform_impl/web/async/waker.rs @@ -19,8 +19,7 @@ struct Handler { struct Sender(Arc); impl WakerSpawner { - #[track_caller] - pub fn new(main_thread: MainThreadMarker, value: T, handler: fn(&T, bool)) -> Option { + pub fn new(main_thread: MainThreadMarker, value: T, handler: fn(&T, bool)) -> Self { let inner = Arc::new(Inner { awoken: AtomicBool::new(false), waker: AtomicWaker::new(), @@ -31,7 +30,7 @@ impl WakerSpawner { let sender = Sender(Arc::clone(&inner)); - let wrapper = Wrapper::new( + Self(Wrapper::new( main_thread, handler, |handler, _| { @@ -73,9 +72,7 @@ impl WakerSpawner { inner.0.awoken.store(true, Ordering::Relaxed); inner.0.waker.wake(); }, - )?; - - Some(Self(wrapper)) + )) } pub fn waker(&self) -> Waker { diff --git a/src/platform_impl/web/async/wrapper.rs b/src/platform_impl/web/async/wrapper.rs index 8d6de2f659..35524555f8 100644 --- a/src/platform_impl/web/async/wrapper.rs +++ b/src/platform_impl/web/async/wrapper.rs @@ -33,7 +33,6 @@ unsafe impl Send for Value {} unsafe impl Sync for Value {} impl Wrapper { - #[track_caller] pub fn new>( _: MainThreadMarker, value: V, @@ -41,7 +40,7 @@ impl Wrapper { receiver: impl 'static + FnOnce(Arc>>) -> R, sender_data: S, sender_handler: fn(&S, E), - ) -> Option { + ) -> Self { let value = Arc::new(RefCell::new(Some(value))); wasm_bindgen_futures::spawn_local({ @@ -52,12 +51,7 @@ impl Wrapper { } }); - Some(Self { - value: Value { value, local: PhantomData }, - handler, - sender_data, - sender_handler, - }) + Self { value: Value { value, local: PhantomData }, handler, sender_data, sender_handler } } pub fn send(&self, event: E) { @@ -68,9 +62,8 @@ impl Wrapper { } } - pub fn value(&self) -> Option> { - MainThreadMarker::new() - .map(|_| Ref::map(self.value.value.borrow(), |value| value.as_ref().unwrap())) + pub fn value(&self, _: MainThreadMarker) -> Ref<'_, V> { + Ref::map(self.value.value.borrow(), |value| value.as_ref().unwrap()) } pub fn with_sender_data(&self, f: impl FnOnce(&S) -> T) -> T { diff --git a/src/platform_impl/web/cursor.rs b/src/platform_impl/web/cursor.rs index 0da977af95..7fcb850665 100644 --- a/src/platform_impl/web/cursor.rs +++ b/src/platform_impl/web/cursor.rs @@ -178,7 +178,7 @@ impl Future for CustomCursorFuture { panic!("`CustomCursorFuture` polled after completion") } - let result = ready!(Pin::new(&mut self.notified).poll(cx)); + let result = ready!(Pin::new(&mut self.notified).poll(cx)).unwrap(); let state = self.state.take().expect("`CustomCursorFuture` polled after completion"); Poll::Ready(result.map(|_| CustomCursor { animation: self.animation, state })) @@ -662,7 +662,7 @@ async fn from_animation( ImageState::Loading { notifier, .. } => { let notified = notifier.notified(); drop(state); - notified.await?; + notified.await.unwrap()?; }, ImageState::Failed(error) => return Err(error.clone()), ImageState::Image(_) => drop(state), diff --git a/src/platform_impl/web/event_loop/proxy.rs b/src/platform_impl/web/event_loop/proxy.rs index 07cf10f128..0973b6c83d 100644 --- a/src/platform_impl/web/event_loop/proxy.rs +++ b/src/platform_impl/web/event_loop/proxy.rs @@ -1,15 +1,13 @@ -use std::rc::Weak; - -use super::runner::Execution; +use super::runner::WeakShared; use crate::platform_impl::platform::r#async::Waker; #[derive(Clone)] pub struct EventLoopProxy { - runner: Waker>, + runner: Waker, } impl EventLoopProxy { - pub fn new(runner: Waker>) -> Self { + pub fn new(runner: Waker) -> Self { Self { runner } } diff --git a/src/platform_impl/web/event_loop/runner.rs b/src/platform_impl/web/event_loop/runner.rs index 5fdba0e720..2739ced89c 100644 --- a/src/platform_impl/web/event_loop/runner.rs +++ b/src/platform_impl/web/event_loop/runner.rs @@ -38,9 +38,9 @@ impl Clone for Shared { type OnEventHandle = RefCell>>; -pub struct Execution { +struct Execution { main_thread: MainThreadMarker, - proxy_spawner: WakerSpawner>, + proxy_spawner: WakerSpawner, control_flow: Cell, poll_strategy: Cell, wait_until_strategy: Cell, @@ -53,7 +53,7 @@ pub struct Execution { window: web_sys::Window, document: Document, #[allow(clippy::type_complexity)] - all_canvases: RefCell>, DispatchRunner)>>, + all_canvases: RefCell, DispatchRunner)>>, redraw_pending: RefCell>, destroy_pending: RefCell>, page_transition_event_handle: RefCell>, @@ -116,7 +116,7 @@ impl Runner { EventWrapper::Event(event) => (self.event_handler)(event), EventWrapper::ScaleChange { canvas, size, scale } => { if let Some(canvas) = canvas.upgrade() { - canvas.borrow().handle_scale_change( + canvas.handle_scale_change( runner, |event| (self.event_handler)(event), size, @@ -137,12 +137,12 @@ impl Shared { let document = window.document().expect("Failed to obtain document"); Shared(Rc::::new_cyclic(|weak| { - let proxy_spawner = WakerSpawner::new(main_thread, weak.clone(), |runner, local| { - if let Some(runner) = runner.upgrade() { - Shared(runner).send_proxy_wake_up(local); - } - }) - .expect("`EventLoop` has to be created in the main thread"); + let proxy_spawner = + WakerSpawner::new(main_thread, WeakShared(weak.clone()), |runner, local| { + if let Some(runner) = runner.upgrade() { + runner.send_proxy_wake_up(local); + } + }); Execution { main_thread, @@ -189,7 +189,7 @@ impl Shared { pub fn add_canvas( &self, id: WindowId, - canvas: Weak>, + canvas: Weak, runner: DispatchRunner, ) { self.0.all_canvases.borrow_mut().push((id, canvas, runner)); @@ -391,7 +391,7 @@ impl Shared { // - not visible and we don't know if it intersects yet // - visible and intersects if let (false, Some(true) | None) | (true, Some(true)) = - (is_visible, canvas.borrow().is_intersecting) + (is_visible, canvas.is_intersecting.get()) { runner.send_event(Event::WindowEvent { window_id: *id, @@ -616,7 +616,7 @@ impl Shared { // and potentially block other threads in the meantime. for (_, window, runner) in self.0.all_canvases.borrow().iter() { if let Some(window) = window.upgrade() { - runner.run(); + runner.run(self.main_thread()); drop(window) } } @@ -705,7 +705,6 @@ impl Shared { // In case any remaining `Window`s are still not dropped, we will need // to explicitly remove the event handlers associated with their canvases. if let Some(canvas) = canvas.upgrade() { - let mut canvas = canvas.borrow_mut(); canvas.remove_listeners(); } } @@ -747,7 +746,7 @@ impl Shared { DeviceEvents::WhenFocused => { self.0.all_canvases.borrow().iter().any(|(_, canvas, _)| { if let Some(canvas) = canvas.upgrade() { - canvas.borrow().has_focus.get() + canvas.has_focus.get() } else { false } @@ -793,14 +792,23 @@ impl Shared { self.0.wait_until_strategy.get() } - pub(crate) fn waker(&self) -> Waker> { + pub(crate) fn waker(&self) -> Waker { self.0.proxy_spawner.waker() } } +#[derive(Clone, Debug)] +pub struct WeakShared(Weak); + +impl WeakShared { + pub fn upgrade(&self) -> Option { + self.0.upgrade().map(Shared) + } +} + pub(crate) enum EventWrapper { Event(Event), - ScaleChange { canvas: Weak>, size: PhysicalSize, scale: f64 }, + ScaleChange { canvas: Weak, size: PhysicalSize, scale: f64 }, } impl From for EventWrapper { diff --git a/src/platform_impl/web/event_loop/window_target.rs b/src/platform_impl/web/event_loop/window_target.rs index 144143f92f..ba3b4f4a5a 100644 --- a/src/platform_impl/web/event_loop/window_target.rs +++ b/src/platform_impl/web/event_loop/window_target.rs @@ -1,16 +1,16 @@ -use std::cell::{Cell, RefCell}; +use std::cell::Cell; use std::clone::Clone; use std::collections::vec_deque::IntoIter as VecDequeIter; use std::collections::VecDeque; use std::iter; -use std::rc::{Rc, Weak}; +use std::rc::Rc; use web_sys::Element; use super::super::monitor::MonitorHandle; use super::super::KeyEventExtra; use super::device::DeviceId; -use super::runner::{EventWrapper, Execution}; +use super::runner::{EventWrapper, WeakShared}; use super::window::WindowId; use super::{backend, runner, EventLoopProxy}; use crate::event::{ @@ -80,9 +80,8 @@ impl ActiveEventLoop { CustomCursorFuture(CustomCursor::new_async(self, source.inner)) } - pub fn register(&self, canvas: &Rc>, id: WindowId) { + pub fn register(&self, canvas: &Rc, id: WindowId) { let canvas_clone = canvas.clone(); - let mut canvas = canvas.borrow_mut(); #[cfg(any(feature = "rwh_04", feature = "rwh_05"))] canvas.set_attribute("data-raw-handle", &id.0.to_string()); @@ -561,7 +560,6 @@ impl ActiveEventLoop { let canvas = canvas_clone.clone(); move |new_size| { - let canvas = canvas.borrow(); canvas.set_current_size(new_size); if canvas.old_size() != new_size { canvas.set_old_size(new_size); @@ -579,7 +577,7 @@ impl ActiveEventLoop { canvas.on_intersection(move |is_intersecting| { // only fire if visible while skipping the first event if it's intersecting if backend::is_visible(runner.document()) - && !(is_intersecting && canvas_clone.borrow().is_intersecting.is_none()) + && !(is_intersecting && canvas_clone.is_intersecting.get().is_none()) { runner.send_event(Event::WindowEvent { window_id: RootWindowId(id), @@ -587,7 +585,7 @@ impl ActiveEventLoop { }); } - canvas_clone.borrow_mut().is_intersecting = Some(is_intersecting); + canvas_clone.is_intersecting.set(Some(is_intersecting)); }); let runner = self.runner.clone(); @@ -654,7 +652,7 @@ impl ActiveEventLoop { self.runner.wait_until_strategy() } - pub(crate) fn waker(&self) -> Waker> { + pub(crate) fn waker(&self) -> Waker { self.runner.waker() } diff --git a/src/platform_impl/web/web_sys/canvas.rs b/src/platform_impl/web/web_sys/canvas.rs index ef075aa02b..7a046a6205 100644 --- a/src/platform_impl/web/web_sys/canvas.rs +++ b/src/platform_impl/web/web_sys/canvas.rs @@ -1,4 +1,4 @@ -use std::cell::Cell; +use std::cell::{Cell, RefCell}; use std::ops::Deref; use std::rc::Rc; use std::sync::{Arc, Mutex}; @@ -29,11 +29,18 @@ use crate::window::{WindowAttributes, WindowId as RootWindowId}; #[allow(dead_code)] pub struct Canvas { + main_thread: MainThreadMarker, common: Common, id: WindowId, pub has_focus: Rc>, pub prevent_default: Rc>, - pub is_intersecting: Option, + pub is_intersecting: Cell>, + pub cursor: CursorHandler, + handlers: RefCell, +} + +struct Handlers { + animation_frame_handler: AnimationFrameHandler, on_touch_start: Option>, on_focus: Option>, on_blur: Option>, @@ -44,10 +51,8 @@ pub struct Canvas { pointer_handler: PointerHandler, on_resize_scale: Option, on_intersect: Option, - animation_frame_handler: AnimationFrameHandler, on_touch_end: Option>, on_context_menu: Option>, - pub cursor: CursorHandler, } pub struct Common { @@ -74,14 +79,11 @@ impl Canvas { id: WindowId, window: web_sys::Window, document: Document, - attr: &mut WindowAttributes, + attr: WindowAttributes, ) -> Result { - let canvas = match attr.platform_specific.canvas.take().map(|canvas| { - Arc::try_unwrap(canvas) - .map(|canvas| canvas.into_inner(main_thread)) - .unwrap_or_else(|canvas| canvas.get(main_thread).clone()) - }) { - Some(canvas) => canvas, + let canvas = match attr.platform_specific.canvas.map(Arc::try_unwrap) { + Some(Ok(canvas)) => canvas.into_inner(main_thread), + Some(Err(canvas)) => canvas.get(main_thread).clone(), None => document .create_element("canvas") .map_err(|_| os_error!(OsError("Failed to create canvas element".to_owned())))? @@ -149,25 +151,28 @@ impl Canvas { } Ok(Canvas { + main_thread, common, id, has_focus: Rc::new(Cell::new(false)), prevent_default: Rc::new(Cell::new(attr.platform_specific.prevent_default)), - is_intersecting: None, - on_touch_start: None, - on_blur: None, - on_focus: None, - on_keyboard_release: None, - on_keyboard_press: None, - on_mouse_wheel: None, - on_dark_mode: None, - pointer_handler: PointerHandler::new(), - on_resize_scale: None, - on_intersect: None, - animation_frame_handler: AnimationFrameHandler::new(window), - on_touch_end: None, - on_context_menu: None, + is_intersecting: Cell::new(None), cursor, + handlers: RefCell::new(Handlers { + animation_frame_handler: AnimationFrameHandler::new(window), + on_touch_start: None, + on_blur: None, + on_focus: None, + on_keyboard_release: None, + on_keyboard_press: None, + on_mouse_wheel: None, + on_dark_mode: None, + pointer_handler: PointerHandler::new(), + on_resize_scale: None, + on_intersect: None, + on_touch_end: None, + on_context_menu: None, + }), }) } @@ -241,39 +246,42 @@ impl Canvas { &self.common.style } - pub fn on_touch_start(&mut self) { + pub fn on_touch_start(&self) { let prevent_default = Rc::clone(&self.prevent_default); - self.on_touch_start = Some(self.common.add_event("touchstart", move |event: Event| { - if prevent_default.get() { - event.prevent_default(); - } - })); + self.handlers.borrow_mut().on_touch_start = + Some(self.common.add_event("touchstart", move |event: Event| { + if prevent_default.get() { + event.prevent_default(); + } + })); } - pub fn on_blur(&mut self, mut handler: F) + pub fn on_blur(&self, mut handler: F) where F: 'static + FnMut(), { - self.on_blur = Some(self.common.add_event("blur", move |_: FocusEvent| { - handler(); - })); + self.handlers.borrow_mut().on_blur = + Some(self.common.add_event("blur", move |_: FocusEvent| { + handler(); + })); } - pub fn on_focus(&mut self, mut handler: F) + pub fn on_focus(&self, mut handler: F) where F: 'static + FnMut(), { - self.on_focus = Some(self.common.add_event("focus", move |_: FocusEvent| { - handler(); - })); + self.handlers.borrow_mut().on_focus = + Some(self.common.add_event("focus", move |_: FocusEvent| { + handler(); + })); } - pub fn on_keyboard_release(&mut self, mut handler: F) + pub fn on_keyboard_release(&self, mut handler: F) where F: 'static + FnMut(PhysicalKey, Key, Option, KeyLocation, bool, ModifiersState), { let prevent_default = Rc::clone(&self.prevent_default); - self.on_keyboard_release = + self.handlers.borrow_mut().on_keyboard_release = Some(self.common.add_event("keyup", move |event: KeyboardEvent| { if prevent_default.get() { event.prevent_default(); @@ -291,12 +299,12 @@ impl Canvas { })); } - pub fn on_keyboard_press(&mut self, mut handler: F) + pub fn on_keyboard_press(&self, mut handler: F) where F: 'static + FnMut(PhysicalKey, Key, Option, KeyLocation, bool, ModifiersState), { let prevent_default = Rc::clone(&self.prevent_default); - self.on_keyboard_press = + self.handlers.borrow_mut().on_keyboard_press = Some(self.common.add_event("keydown", move |event: KeyboardEvent| { if prevent_default.get() { event.prevent_default(); @@ -314,34 +322,38 @@ impl Canvas { })); } - pub fn on_cursor_leave(&mut self, handler: F) + pub fn on_cursor_leave(&self, handler: F) where F: 'static + FnMut(ModifiersState, Option), { - self.pointer_handler.on_cursor_leave(&self.common, handler) + self.handlers.borrow_mut().pointer_handler.on_cursor_leave(&self.common, handler) } - pub fn on_cursor_enter(&mut self, handler: F) + pub fn on_cursor_enter(&self, handler: F) where F: 'static + FnMut(ModifiersState, Option), { - self.pointer_handler.on_cursor_enter(&self.common, handler) + self.handlers.borrow_mut().pointer_handler.on_cursor_enter(&self.common, handler) } - pub fn on_mouse_release(&mut self, mouse_handler: M, touch_handler: T) + pub fn on_mouse_release(&self, mouse_handler: M, touch_handler: T) where M: 'static + FnMut(ModifiersState, i32, PhysicalPosition, MouseButton), T: 'static + FnMut(ModifiersState, i32, PhysicalPosition, Force), { - self.pointer_handler.on_mouse_release(&self.common, mouse_handler, touch_handler) + self.handlers.borrow_mut().pointer_handler.on_mouse_release( + &self.common, + mouse_handler, + touch_handler, + ) } - pub fn on_mouse_press(&mut self, mouse_handler: M, touch_handler: T) + pub fn on_mouse_press(&self, mouse_handler: M, touch_handler: T) where M: 'static + FnMut(ModifiersState, i32, PhysicalPosition, MouseButton), T: 'static + FnMut(ModifiersState, i32, PhysicalPosition, Force), { - self.pointer_handler.on_mouse_press( + self.handlers.borrow_mut().pointer_handler.on_mouse_press( &self.common, mouse_handler, touch_handler, @@ -349,14 +361,14 @@ impl Canvas { ) } - pub fn on_cursor_move(&mut self, mouse_handler: M, touch_handler: T, button_handler: B) + pub fn on_cursor_move(&self, mouse_handler: M, touch_handler: T, button_handler: B) where M: 'static + FnMut(ModifiersState, i32, &mut dyn Iterator>), T: 'static + FnMut(ModifiersState, i32, &mut dyn Iterator, Force)>), B: 'static + FnMut(ModifiersState, i32, PhysicalPosition, ButtonsState, MouseButton), { - self.pointer_handler.on_cursor_move( + self.handlers.borrow_mut().pointer_handler.on_cursor_move( &self.common, mouse_handler, touch_handler, @@ -365,48 +377,49 @@ impl Canvas { ) } - pub fn on_touch_cancel(&mut self, handler: F) + pub fn on_touch_cancel(&self, handler: F) where F: 'static + FnMut(i32, PhysicalPosition, Force), { - self.pointer_handler.on_touch_cancel(&self.common, handler) + self.handlers.borrow_mut().pointer_handler.on_touch_cancel(&self.common, handler) } - pub fn on_mouse_wheel(&mut self, mut handler: F) + pub fn on_mouse_wheel(&self, mut handler: F) where F: 'static + FnMut(i32, MouseScrollDelta, ModifiersState), { let window = self.common.window.clone(); let prevent_default = Rc::clone(&self.prevent_default); - self.on_mouse_wheel = Some(self.common.add_event("wheel", move |event: WheelEvent| { - if prevent_default.get() { - event.prevent_default(); - } + self.handlers.borrow_mut().on_mouse_wheel = + Some(self.common.add_event("wheel", move |event: WheelEvent| { + if prevent_default.get() { + event.prevent_default(); + } - if let Some(delta) = event::mouse_scroll_delta(&window, &event) { - let modifiers = event::mouse_modifiers(&event); - handler(0, delta, modifiers); - } - })); + if let Some(delta) = event::mouse_scroll_delta(&window, &event) { + let modifiers = event::mouse_modifiers(&event); + handler(0, delta, modifiers); + } + })); } - pub fn on_dark_mode(&mut self, mut handler: F) + pub fn on_dark_mode(&self, mut handler: F) where F: 'static + FnMut(bool), { - self.on_dark_mode = Some(MediaQueryListHandle::new( + self.handlers.borrow_mut().on_dark_mode = Some(MediaQueryListHandle::new( &self.common.window, "(prefers-color-scheme: dark)", move |mql| handler(mql.matches()), )); } - pub(crate) fn on_resize_scale(&mut self, scale_handler: S, size_handler: R) + pub(crate) fn on_resize_scale(&self, scale_handler: S, size_handler: R) where S: 'static + Fn(PhysicalSize, f64), R: 'static + Fn(PhysicalSize), { - self.on_resize_scale = Some(ResizeScaleHandle::new( + self.handlers.borrow_mut().on_resize_scale = Some(ResizeScaleHandle::new( self.window().clone(), self.document().clone(), self.raw().clone(), @@ -416,23 +429,24 @@ impl Canvas { )); } - pub(crate) fn on_intersection(&mut self, handler: F) + pub(crate) fn on_intersection(&self, handler: F) where F: 'static + FnMut(bool), { - self.on_intersect = Some(IntersectionObserverHandle::new(self.raw(), handler)); + self.handlers.borrow_mut().on_intersect = + Some(IntersectionObserverHandle::new(self.raw(), handler)); } - pub(crate) fn on_animation_frame(&mut self, f: F) + pub(crate) fn on_animation_frame(&self, f: F) where F: 'static + FnMut(), { - self.animation_frame_handler.on_animation_frame(f) + self.handlers.borrow_mut().animation_frame_handler.on_animation_frame(f) } - pub(crate) fn on_context_menu(&mut self) { + pub(crate) fn on_context_menu(&self) { let prevent_default = Rc::clone(&self.prevent_default); - self.on_context_menu = + self.handlers.borrow_mut().on_context_menu = Some(self.common.add_event("contextmenu", move |event: PointerEvent| { if prevent_default.get() { event.prevent_default(); @@ -453,7 +467,7 @@ impl Canvas { } pub fn request_animation_frame(&self) { - self.animation_frame_handler.request(); + self.handlers.borrow().animation_frame_handler.request(); } pub(crate) fn handle_scale_change( @@ -486,7 +500,9 @@ impl Canvas { super::set_canvas_size(self.document(), self.raw(), self.style(), new_size); // Set the size might not trigger the event because the calculation is inaccurate. - self.on_resize_scale + self.handlers + .borrow() + .on_resize_scale .as_ref() .expect("expected Window to still be active") .notify_resize(); @@ -500,20 +516,21 @@ impl Canvas { } } - pub fn remove_listeners(&mut self) { - self.on_touch_start = None; - self.on_focus = None; - self.on_blur = None; - self.on_keyboard_release = None; - self.on_keyboard_press = None; - self.on_mouse_wheel = None; - self.on_dark_mode = None; - self.pointer_handler.remove_listeners(); - self.on_resize_scale = None; - self.on_intersect = None; - self.animation_frame_handler.cancel(); - self.on_touch_end = None; - self.on_context_menu = None; + pub fn remove_listeners(&self) { + let mut handlers = self.handlers.borrow_mut(); + handlers.on_touch_start.take(); + handlers.on_focus.take(); + handlers.on_blur.take(); + handlers.on_keyboard_release.take(); + handlers.on_keyboard_press.take(); + handlers.on_mouse_wheel.take(); + handlers.on_dark_mode.take(); + handlers.pointer_handler.remove_listeners(); + handlers.on_resize_scale = None; + handlers.on_intersect = None; + handlers.animation_frame_handler.cancel(); + handlers.on_touch_end = None; + handlers.on_context_menu = None; } } diff --git a/src/platform_impl/web/window.rs b/src/platform_impl/web/window.rs index 29d854047f..f29386c1fc 100644 --- a/src/platform_impl/web/window.rs +++ b/src/platform_impl/web/window.rs @@ -1,4 +1,4 @@ -use std::cell::RefCell; +use std::cell::Ref; use std::collections::VecDeque; use std::rc::Rc; use std::sync::Arc; @@ -24,15 +24,12 @@ pub struct Window { pub struct Inner { id: WindowId, pub window: web_sys::Window, - canvas: Rc>, + canvas: Rc, destroy_fn: Option>, } impl Window { - pub(crate) fn new( - target: &ActiveEventLoop, - mut attr: WindowAttributes, - ) -> Result { + pub(crate) fn new(target: &ActiveEventLoop, attr: WindowAttributes) -> Result { let id = target.generate_id(); let window = target.runner.window(); @@ -42,9 +39,9 @@ impl Window { id, window.clone(), document.clone(), - &mut attr, + attr, )?; - let canvas = Rc::new(RefCell::new(canvas)); + let canvas = Rc::new(canvas); target.register(&canvas, id); @@ -53,14 +50,8 @@ impl Window { let inner = Inner { id, window: window.clone(), canvas, destroy_fn: Some(destroy_fn) }; - inner.set_title(&attr.title); - inner.set_maximized(attr.maximized); - inner.set_visible(attr.visible); - inner.set_window_icon(attr.window_icon); - inner.set_cursor(attr.cursor); - let canvas = Rc::downgrade(&inner.canvas); - let (dispatcher, runner) = Dispatcher::new(target.runner.main_thread(), inner).unwrap(); + let (dispatcher, runner) = Dispatcher::new(target.runner.main_thread(), inner); target.runner.add_canvas(RootWI(id), canvas, runner); Ok(Window { inner: dispatcher }) @@ -74,27 +65,27 @@ impl Window { self.inner.queue(f) } - pub fn canvas(&self) -> Option { - self.inner.value().map(|inner| inner.canvas.borrow().raw().clone()) + pub fn canvas(&self) -> Option> { + MainThreadMarker::new() + .map(|main_thread| Ref::map(self.inner.value(main_thread), |inner| inner.canvas.raw())) } pub(crate) fn prevent_default(&self) -> bool { - self.inner.queue(|inner| inner.canvas.borrow().prevent_default.get()) + self.inner.queue(|inner| inner.canvas.prevent_default.get()) } pub(crate) fn set_prevent_default(&self, prevent_default: bool) { - self.inner.dispatch(move |inner| inner.canvas.borrow().prevent_default.set(prevent_default)) + self.inner.dispatch(move |inner| inner.canvas.prevent_default.set(prevent_default)) } #[cfg(feature = "rwh_06")] #[inline] pub fn raw_window_handle_rwh_06(&self) -> Result { - self.inner - .value() - .map(|inner| { - let canvas = inner.canvas.borrow(); + MainThreadMarker::new() + .map(|main_thread| { + let inner = self.inner.value(main_thread); // SAFETY: This will only work if the reference to `HtmlCanvasElement` stays valid. - let canvas: &wasm_bindgen::JsValue = canvas.raw(); + let canvas: &wasm_bindgen::JsValue = inner.canvas.raw(); let window_handle = rwh_06::WebCanvasWindowHandle::new(std::ptr::NonNull::from(canvas).cast()); rwh_06::RawWindowHandle::WebCanvas(window_handle) @@ -113,7 +104,7 @@ impl Window { impl Inner { pub fn set_title(&self, title: &str) { - self.canvas.borrow().set_attribute("alt", title) + self.canvas.set_attribute("alt", title) } pub fn set_transparent(&self, _transparent: bool) {} @@ -130,13 +121,13 @@ impl Inner { } pub fn request_redraw(&self) { - self.canvas.borrow().request_animation_frame(); + self.canvas.request_animation_frame(); } pub fn pre_present_notify(&self) {} pub fn outer_position(&self) -> Result, NotSupportedError> { - Ok(self.canvas.borrow().position().to_physical(self.scale_factor())) + Ok(self.canvas.position().to_physical(self.scale_factor())) } pub fn inner_position(&self) -> Result, NotSupportedError> { @@ -145,15 +136,19 @@ impl Inner { } pub fn set_outer_position(&self, position: Position) { - let canvas = self.canvas.borrow(); let position = position.to_logical::(self.scale_factor()); - backend::set_canvas_position(canvas.document(), canvas.raw(), canvas.style(), position) + backend::set_canvas_position( + self.canvas.document(), + self.canvas.raw(), + self.canvas.style(), + position, + ) } #[inline] pub fn inner_size(&self) -> PhysicalSize { - self.canvas.borrow().inner_size() + self.canvas.inner_size() } #[inline] @@ -165,23 +160,35 @@ impl Inner { #[inline] pub fn request_inner_size(&self, size: Size) -> Option> { let size = size.to_logical(self.scale_factor()); - let canvas = self.canvas.borrow(); - backend::set_canvas_size(canvas.document(), canvas.raw(), canvas.style(), size); + backend::set_canvas_size( + self.canvas.document(), + self.canvas.raw(), + self.canvas.style(), + size, + ); None } #[inline] pub fn set_min_inner_size(&self, dimensions: Option) { let dimensions = dimensions.map(|dimensions| dimensions.to_logical(self.scale_factor())); - let canvas = self.canvas.borrow(); - backend::set_canvas_min_size(canvas.document(), canvas.raw(), canvas.style(), dimensions) + backend::set_canvas_min_size( + self.canvas.document(), + self.canvas.raw(), + self.canvas.style(), + dimensions, + ) } #[inline] pub fn set_max_inner_size(&self, dimensions: Option) { let dimensions = dimensions.map(|dimensions| dimensions.to_logical(self.scale_factor())); - let canvas = self.canvas.borrow(); - backend::set_canvas_max_size(canvas.document(), canvas.raw(), canvas.style(), dimensions) + backend::set_canvas_max_size( + self.canvas.document(), + self.canvas.raw(), + self.canvas.style(), + dimensions, + ) } #[inline] @@ -218,7 +225,7 @@ impl Inner { #[inline] pub fn set_cursor(&self, cursor: Cursor) { - self.canvas.borrow_mut().cursor.set_cursor(cursor) + self.canvas.cursor.set_cursor(cursor) } #[inline] @@ -236,12 +243,12 @@ impl Inner { }, }; - self.canvas.borrow().set_cursor_lock(lock).map_err(ExternalError::Os) + self.canvas.set_cursor_lock(lock).map_err(ExternalError::Os) } #[inline] pub fn set_cursor_visible(&self, visible: bool) { - self.canvas.borrow_mut().cursor.set_cursor_visible(visible) + self.canvas.cursor.set_cursor_visible(visible) } #[inline] @@ -286,7 +293,7 @@ impl Inner { #[inline] pub(crate) fn fullscreen(&self) -> Option { - if self.canvas.borrow().is_fullscreen() { + if self.canvas.is_fullscreen() { Some(Fullscreen::Borderless(None)) } else { None @@ -295,12 +302,10 @@ impl Inner { #[inline] pub(crate) fn set_fullscreen(&self, fullscreen: Option) { - let canvas = &self.canvas.borrow(); - if fullscreen.is_some() { - canvas.request_fullscreen(); + self.canvas.request_fullscreen(); } else { - canvas.exit_fullscreen() + self.canvas.exit_fullscreen() } } @@ -340,7 +345,7 @@ impl Inner { #[inline] pub fn focus_window(&self) { - let _ = self.canvas.borrow().raw().focus(); + let _ = self.canvas.raw().focus(); } #[inline] @@ -408,7 +413,7 @@ impl Inner { #[inline] pub fn has_focus(&self) -> bool { - self.canvas.borrow().has_focus.get() + self.canvas.has_focus.get() } pub fn title(&self) -> String {