diff --git a/CHANGELOG.md b/CHANGELOG.md index 32926a69bb2..22ff710423d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ And please only add new entries to the top of this list, right below the `# Unre # Unreleased +- On Web, use `Window.requestAnimationFrame()` to throttle `RedrawRequested` events. - On Wayland, use frame callbacks to throttle `RedrawRequested` events so redraws will align with compositor. - Add `Window::pre_present_notify` to notify winit before presenting to the windowing system. - **Breaking:** `ActivationTokenDone` event which could be requested with the new `startup_notify` module, see its docs for more. diff --git a/src/platform_impl/web/event_loop/window_target.rs b/src/platform_impl/web/event_loop/window_target.rs index 72893f067a8..f38b4804bf6 100644 --- a/src/platform_impl/web/event_loop/window_target.rs +++ b/src/platform_impl/web/event_loop/window_target.rs @@ -735,7 +735,10 @@ impl EventLoopWindowTarget { } canvas_clone.borrow_mut().is_intersecting = Some(is_intersecting); - }) + }); + + let runner = self.runner.clone(); + canvas.on_animation_frame(move || runner.request_redraw(RootWindowId(id))); } pub fn available_monitors(&self) -> VecDequeIter { diff --git a/src/platform_impl/web/web_sys/animation_frame.rs b/src/platform_impl/web/web_sys/animation_frame.rs new file mode 100644 index 00000000000..75a09551387 --- /dev/null +++ b/src/platform_impl/web/web_sys/animation_frame.rs @@ -0,0 +1,62 @@ +use std::cell::Cell; +use std::rc::Rc; +use wasm_bindgen::closure::Closure; +use wasm_bindgen::JsCast; + +pub struct AnimationFrameHandler { + window: web_sys::Window, + closure: Closure, + handle: Rc>>, +} + +impl AnimationFrameHandler { + pub fn new(window: web_sys::Window) -> Self { + let handle = Rc::new(Cell::new(None)); + let closure = Closure::new({ + let handle = handle.clone(); + move || handle.set(None) + }); + + Self { + window, + closure, + handle, + } + } + + pub fn on_animation_frame(&mut self, mut f: F) + where + F: 'static + FnMut(), + { + let handle = self.handle.clone(); + self.closure = Closure::new(move || { + handle.set(None); + f(); + }) + } + + pub fn request(&self) { + if let Some(handle) = self.handle.take() { + self.window + .cancel_animation_frame(handle) + .expect("Failed to cancel animation frame"); + } + + let handle = self + .window + .request_animation_frame(self.closure.as_ref().unchecked_ref()) + .expect("Failed to request animation frame"); + + self.handle.set(Some(handle)); + } +} + +impl Drop for AnimationFrameHandler { + fn drop(&mut self) { + if let Some(handle) = self.handle.take() { + self.window + .cancel_animation_frame(handle) + .expect("Failed to cancel animation frame"); + } + } +} diff --git a/src/platform_impl/web/web_sys/canvas.rs b/src/platform_impl/web/web_sys/canvas.rs index 0c42c59b8d0..d30044ba40b 100644 --- a/src/platform_impl/web/web_sys/canvas.rs +++ b/src/platform_impl/web/web_sys/canvas.rs @@ -1,4 +1,5 @@ use super::super::WindowId; +use super::animation_frame::AnimationFrameHandler; use super::event_handle::EventListenerHandle; use super::intersection_handle::IntersectionObserverHandle; use super::media_query_handle::MediaQueryListHandle; @@ -41,6 +42,7 @@ pub struct Canvas { pointer_handler: PointerHandler, on_resize_scale: Option, on_intersect: Option, + animation_frame_handler: AnimationFrameHandler, } pub struct Common { @@ -97,7 +99,7 @@ impl Canvas { .expect("Invalid pseudo-element"); let common = Common { - window, + window: window.clone(), document, raw: canvas, style, @@ -150,6 +152,7 @@ impl Canvas { pointer_handler: PointerHandler::new(), on_resize_scale: None, on_intersect: None, + animation_frame_handler: AnimationFrameHandler::new(window), }) } @@ -440,6 +443,13 @@ impl Canvas { self.on_intersect = Some(IntersectionObserverHandle::new(self.raw(), handler)); } + pub(crate) fn on_animation_frame(&mut self, f: F) + where + F: 'static + FnMut(), + { + self.animation_frame_handler.on_animation_frame(f) + } + pub fn request_fullscreen(&self) { self.common.request_fullscreen() } @@ -448,6 +458,10 @@ impl Canvas { self.common.is_fullscreen() } + pub fn request_animation_frame(&self) { + self.animation_frame_handler.request(); + } + pub(crate) fn handle_scale_change( &self, runner: &super::super::event_loop::runner::Shared, diff --git a/src/platform_impl/web/web_sys/mod.rs b/src/platform_impl/web/web_sys/mod.rs index 06526975b92..7f2abc05e9f 100644 --- a/src/platform_impl/web/web_sys/mod.rs +++ b/src/platform_impl/web/web_sys/mod.rs @@ -1,3 +1,4 @@ +mod animation_frame; mod canvas; pub mod event; mod event_handle; diff --git a/src/platform_impl/web/window.rs b/src/platform_impl/web/window.rs index 8347860684c..bbaaa466046 100644 --- a/src/platform_impl/web/window.rs +++ b/src/platform_impl/web/window.rs @@ -30,7 +30,6 @@ pub struct Inner { document: Document, canvas: Rc>, previous_pointer: RefCell<&'static str>, - register_redraw_request: Box, destroy_fn: Option>, } @@ -40,8 +39,6 @@ impl Window { attr: WindowAttributes, platform_attr: PlatformSpecificWindowBuilderAttributes, ) -> Result { - let runner = target.runner.clone(); - let id = target.generate_id(); let prevent_default = platform_attr.prevent_default; @@ -52,8 +49,6 @@ impl Window { backend::Canvas::create(id, window.clone(), document.clone(), &attr, platform_attr)?; let canvas = Rc::new(RefCell::new(canvas)); - let register_redraw_request = Box::new(move || runner.request_redraw(RootWI(id))); - target.register(&canvas, id, prevent_default); let runner = target.runner.clone(); @@ -68,7 +63,6 @@ impl Window { document: document.clone(), canvas, previous_pointer: RefCell::new("auto"), - register_redraw_request, destroy_fn: Some(destroy_fn), }) .unwrap(), @@ -110,8 +104,9 @@ impl Window { } pub fn request_redraw(&self) { - self.inner - .dispatch(|inner| (inner.register_redraw_request)()); + self.inner.dispatch(move |inner| { + inner.canvas.borrow().request_animation_frame(); + }); } pub fn pre_present_notify(&self) {} diff --git a/src/window.rs b/src/window.rs index b8a1f5ccbe5..be515a95464 100644 --- a/src/window.rs +++ b/src/window.rs @@ -547,6 +547,7 @@ impl Window { /// - **iOS:** Can only be called on the main thread. /// - **Wayland:** The events are aligned with the frame callbacks when [`Window::pre_present_notify`] /// is used. + /// - **Web:** [`Event::RedrawRequested`] will be aligned with the `requestAnimationFrame`. /// /// [`Event::RedrawRequested`]: crate::event::Event::RedrawRequested #[inline]