From 9c1d0f5d4d768280f606580ed5efc445608a35da Mon Sep 17 00:00:00 2001 From: jprochazk <1665677+jprochazk@users.noreply.github.com> Date: Fri, 24 May 2024 13:18:42 +0200 Subject: [PATCH 01/14] use ResizeObserver instead of `resize` event --- crates/eframe/Cargo.toml | 3 ++ crates/eframe/src/web/events.rs | 43 ++++++++++++++++++++++++++++- crates/eframe/src/web/web_runner.rs | 25 +++++++++++++++++ 3 files changed, 70 insertions(+), 1 deletion(-) diff --git a/crates/eframe/Cargo.toml b/crates/eframe/Cargo.toml index f992c5c66a2..33199a84cdc 100644 --- a/crates/eframe/Cargo.toml +++ b/crates/eframe/Cargo.toml @@ -237,6 +237,9 @@ web-sys = { workspace = true, features = [ "MouseEvent", "Navigator", "Performance", + "ResizeObserver", + "ResizeObserverBoxOptions", + "ResizeObserverOptions", "Storage", "Touch", "TouchEvent", diff --git a/crates/eframe/src/web/events.rs b/crates/eframe/src/web/events.rs index 56d3fdf071a..2aacdb7b470 100644 --- a/crates/eframe/src/web/events.rs +++ b/crates/eframe/src/web/events.rs @@ -247,7 +247,8 @@ pub(crate) fn install_window_events(runner_ref: &WebRunner) -> Result<(), JsValu runner.save(); })?; - for event_name in &["load", "pagehide", "pageshow", "resize"] { + // NOTE: resize is handled by `ResizeObserver` below + for event_name in &["load", "pagehide", "pageshow"] { runner_ref.add_event_listener(&window, event_name, move |_: web_sys::Event, runner| { // log::debug!("{event_name:?}"); runner.needs_repaint.repaint_asap(); @@ -590,3 +591,43 @@ pub(crate) fn install_canvas_events(runner_ref: &WebRunner) -> Result<(), JsValu Ok(()) } + +trait JsValueExt { + fn get(&self, key: &str) -> Result; + fn at(&self, idx: usize) -> Result; + fn has(&self, key: &str) -> Result; +} + +impl JsValueExt for JsValue { + fn get(&self, key: &str) -> Result { + js_sys::Reflect::get(self, &JsValue::from_str(key)) + } + + fn at(&self, idx: usize) -> Result { + js_sys::Reflect::get(self, &JsValue::from_f64(idx as f64)) + } + + fn has(&self, key: &str) -> Result { + js_sys::Reflect::has(self, &JsValue::from_str(key)) + } +} + +pub(crate) fn install_resize_observer(runner_ref: &WebRunner) -> Result<(), JsValue> { + let closure = Closure::wrap(Box::new({ + let runner_ref = runner_ref.clone(); + move |entries: js_sys::Array| { + // Only call the wrapped closure if the egui code has not panicked + if let Some(mut runner_lock) = runner_ref.try_lock() { + runner_lock.needs_repaint.repaint_asap(); + } + } + }) as Box); + + let observer = web_sys::ResizeObserver::new(closure.as_ref().unchecked_ref())?; + let mut options = web_sys::ResizeObserverOptions::new(); + options.box_(web_sys::ResizeObserverBoxOptions::ContentBox); + observer.observe_with_options(runner_ref.try_lock().unwrap().canvas(), &options); + runner_ref.set_resize_observer(observer, closure); + + Ok(()) +} diff --git a/crates/eframe/src/web/web_runner.rs b/crates/eframe/src/web/web_runner.rs index f39fc0dcace..f6e3e0c4ba7 100644 --- a/crates/eframe/src/web/web_runner.rs +++ b/crates/eframe/src/web/web_runner.rs @@ -30,6 +30,8 @@ pub struct WebRunner { /// Used in `destroy` to cancel a pending frame. request_animation_frame_id: Cell>, + + resize_observer: Rc>>, } impl WebRunner { @@ -48,6 +50,7 @@ impl WebRunner { runner: Rc::new(RefCell::new(None)), events_to_unsubscribe: Rc::new(RefCell::new(Default::default())), request_animation_frame_id: Cell::new(None), + resize_observer: Default::default(), } } @@ -78,6 +81,8 @@ impl WebRunner { events::install_color_scheme_change_event(self)?; } + events::install_resize_observer(self)?; + self.request_animation_frame()?; } @@ -109,6 +114,11 @@ impl WebRunner { } } } + + if let Some(context) = self.resize_observer.take() { + context.observer.disconnect(); + drop(context.closure); + } } /// Shut down eframe and clean up resources. @@ -203,10 +213,25 @@ impl WebRunner { closure.forget(); // We must forget it, or else the callback is canceled on drop Ok(()) } + + pub(crate) fn set_resize_observer( + &self, + observer: web_sys::ResizeObserver, + closure: Closure, + ) { + self.resize_observer + .borrow_mut() + .replace(ResizeObserverContext { observer, closure }); + } } // ---------------------------------------------------------------------------- +struct ResizeObserverContext { + observer: web_sys::ResizeObserver, + closure: Closure, +} + struct TargetEvent { target: web_sys::EventTarget, event_name: String, From 0b814ddd648b69078be41b6489618da374b5d288 Mon Sep 17 00:00:00 2001 From: jprochazk <1665677+jprochazk@users.noreply.github.com> Date: Fri, 24 May 2024 13:27:38 +0200 Subject: [PATCH 02/14] remove unused code --- crates/eframe/src/web/events.rs | 24 ++---------------------- 1 file changed, 2 insertions(+), 22 deletions(-) diff --git a/crates/eframe/src/web/events.rs b/crates/eframe/src/web/events.rs index 2aacdb7b470..5d5dd2efca9 100644 --- a/crates/eframe/src/web/events.rs +++ b/crates/eframe/src/web/events.rs @@ -592,36 +592,16 @@ pub(crate) fn install_canvas_events(runner_ref: &WebRunner) -> Result<(), JsValu Ok(()) } -trait JsValueExt { - fn get(&self, key: &str) -> Result; - fn at(&self, idx: usize) -> Result; - fn has(&self, key: &str) -> Result; -} - -impl JsValueExt for JsValue { - fn get(&self, key: &str) -> Result { - js_sys::Reflect::get(self, &JsValue::from_str(key)) - } - - fn at(&self, idx: usize) -> Result { - js_sys::Reflect::get(self, &JsValue::from_f64(idx as f64)) - } - - fn has(&self, key: &str) -> Result { - js_sys::Reflect::has(self, &JsValue::from_str(key)) - } -} - pub(crate) fn install_resize_observer(runner_ref: &WebRunner) -> Result<(), JsValue> { let closure = Closure::wrap(Box::new({ let runner_ref = runner_ref.clone(); - move |entries: js_sys::Array| { + move |_: js_sys::JsValue| { // Only call the wrapped closure if the egui code has not panicked if let Some(mut runner_lock) = runner_ref.try_lock() { runner_lock.needs_repaint.repaint_asap(); } } - }) as Box); + }) as Box); let observer = web_sys::ResizeObserver::new(closure.as_ref().unchecked_ref())?; let mut options = web_sys::ResizeObserverOptions::new(); From c0247ea0737468ef1757420c349042c4875c5a46 Mon Sep 17 00:00:00 2001 From: jprochazk <1665677+jprochazk@users.noreply.github.com> Date: Fri, 24 May 2024 14:04:08 +0200 Subject: [PATCH 03/14] fix compile error --- crates/eframe/src/web/events.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/eframe/src/web/events.rs b/crates/eframe/src/web/events.rs index 5d5dd2efca9..4399108cf97 100644 --- a/crates/eframe/src/web/events.rs +++ b/crates/eframe/src/web/events.rs @@ -595,13 +595,13 @@ pub(crate) fn install_canvas_events(runner_ref: &WebRunner) -> Result<(), JsValu pub(crate) fn install_resize_observer(runner_ref: &WebRunner) -> Result<(), JsValue> { let closure = Closure::wrap(Box::new({ let runner_ref = runner_ref.clone(); - move |_: js_sys::JsValue| { + move |_: js_sys::Array| { // Only call the wrapped closure if the egui code has not panicked if let Some(mut runner_lock) = runner_ref.try_lock() { runner_lock.needs_repaint.repaint_asap(); } } - }) as Box); + }) as Box); let observer = web_sys::ResizeObserver::new(closure.as_ref().unchecked_ref())?; let mut options = web_sys::ResizeObserverOptions::new(); From 224e2342076789ec2710e1f3098c4f6a951c80ed Mon Sep 17 00:00:00 2001 From: jprochazk <1665677+jprochazk@users.noreply.github.com> Date: Fri, 24 May 2024 14:09:56 +0200 Subject: [PATCH 04/14] observe parent element --- crates/eframe/src/web/events.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/crates/eframe/src/web/events.rs b/crates/eframe/src/web/events.rs index 4399108cf97..3caa46506e1 100644 --- a/crates/eframe/src/web/events.rs +++ b/crates/eframe/src/web/events.rs @@ -606,7 +606,13 @@ pub(crate) fn install_resize_observer(runner_ref: &WebRunner) -> Result<(), JsVa let observer = web_sys::ResizeObserver::new(closure.as_ref().unchecked_ref())?; let mut options = web_sys::ResizeObserverOptions::new(); options.box_(web_sys::ResizeObserverBoxOptions::ContentBox); - observer.observe_with_options(runner_ref.try_lock().unwrap().canvas(), &options); + let parent_element = runner_ref + .try_lock() + .unwrap() + .canvas() + .parent_element() + .unwrap(); + observer.observe_with_options(&parent_element, &options); runner_ref.set_resize_observer(observer, closure); Ok(()) From a14bc86b9fc49ecdb61ab0608f023a16f724eede Mon Sep 17 00:00:00 2001 From: jprochazk <1665677+jprochazk@users.noreply.github.com> Date: Fri, 24 May 2024 17:56:30 +0200 Subject: [PATCH 05/14] redraw on resize --- crates/eframe/Cargo.toml | 4 ++ crates/eframe/src/web/app_runner.rs | 2 +- crates/eframe/src/web/events.rs | 71 ++++++++++++++++++++--------- crates/eframe/src/web/web_runner.rs | 59 +++++++++++++++++++----- web_demo/index.html | 2 + 5 files changed, 103 insertions(+), 35 deletions(-) diff --git a/crates/eframe/Cargo.toml b/crates/eframe/Cargo.toml index 33199a84cdc..8220b9e5306 100644 --- a/crates/eframe/Cargo.toml +++ b/crates/eframe/Cargo.toml @@ -236,10 +236,14 @@ web-sys = { workspace = true, features = [ "MediaQueryListEvent", "MouseEvent", "Navigator", + "Node", + "NodeList", "Performance", "ResizeObserver", + "ResizeObserverEntry", "ResizeObserverBoxOptions", "ResizeObserverOptions", + "ResizeObserverSize", "Storage", "Touch", "TouchEvent", diff --git a/crates/eframe/src/web/app_runner.rs b/crates/eframe/src/web/app_runner.rs index 620fe86fd62..ec81ff726c5 100644 --- a/crates/eframe/src/web/app_runner.rs +++ b/crates/eframe/src/web/app_runner.rs @@ -184,7 +184,7 @@ impl AppRunner { /// /// The result can be painted later with a call to [`Self::run_and_paint`] or [`Self::paint`]. pub fn logic(&mut self) { - super::resize_canvas_to_screen_size(self.canvas(), self.web_options.max_size_points); + // super::resize_canvas_to_screen_size(self.canvas(), self.web_options.max_size_points); let canvas_size = super::canvas_size_in_points(self.canvas(), self.egui_ctx()); let raw_input = self.input.new_frame(canvas_size); diff --git a/crates/eframe/src/web/events.rs b/crates/eframe/src/web/events.rs index 3caa46506e1..9c9c99173d7 100644 --- a/crates/eframe/src/web/events.rs +++ b/crates/eframe/src/web/events.rs @@ -2,20 +2,7 @@ use super::*; // ------------------------------------------------------------------------ -/// Calls `request_animation_frame` to schedule repaint. -/// -/// It will only paint if needed, but will always call `request_animation_frame` immediately. -pub(crate) fn paint_and_schedule(runner_ref: &WebRunner) -> Result<(), JsValue> { - // Only paint and schedule if there has been no panic - if let Some(mut runner_lock) = runner_ref.try_lock() { - paint_if_needed(&mut runner_lock); - drop(runner_lock); - runner_ref.request_animation_frame()?; - } - Ok(()) -} - -fn paint_if_needed(runner: &mut AppRunner) { +pub(crate) fn paint_if_needed(runner: &mut AppRunner) { if runner.needs_repaint.needs_repaint() { if runner.has_outstanding_paint_data() { // We have already run the logic, e.g. in an on-click event, @@ -595,10 +582,18 @@ pub(crate) fn install_canvas_events(runner_ref: &WebRunner) -> Result<(), JsValu pub(crate) fn install_resize_observer(runner_ref: &WebRunner) -> Result<(), JsValue> { let closure = Closure::wrap(Box::new({ let runner_ref = runner_ref.clone(); - move |_: js_sys::Array| { + move |entries: js_sys::Array| { // Only call the wrapped closure if the egui code has not panicked if let Some(mut runner_lock) = runner_ref.try_lock() { runner_lock.needs_repaint.repaint_asap(); + let canvas = runner_lock.canvas(); + let (width, height) = get_display_size(entries).unwrap(); + canvas.set_width(width); + canvas.set_height(height); + + // force an immediate repaint + runner_lock.needs_repaint.repaint_asap(); + paint_if_needed(&mut runner_lock); } } }) as Box); @@ -606,14 +601,46 @@ pub(crate) fn install_resize_observer(runner_ref: &WebRunner) -> Result<(), JsVa let observer = web_sys::ResizeObserver::new(closure.as_ref().unchecked_ref())?; let mut options = web_sys::ResizeObserverOptions::new(); options.box_(web_sys::ResizeObserverBoxOptions::ContentBox); - let parent_element = runner_ref - .try_lock() - .unwrap() - .canvas() - .parent_element() - .unwrap(); - observer.observe_with_options(&parent_element, &options); + observer.observe_with_options(runner_ref.try_lock().unwrap().canvas(), &options); runner_ref.set_resize_observer(observer, closure); Ok(()) } + +fn get_display_size(resize_observer_entries: js_sys::Array) -> Result<(u32, u32), JsValue> { + let width; + let height; + let mut dpr = web_sys::window().unwrap().device_pixel_ratio(); + + let entry: web_sys::ResizeObserverEntry = resize_observer_entries.at(0).dyn_into()?; + if JsValue::from_str("devicePixelContentBoxSize").js_in(entry.as_ref()) { + // NOTE: Only this path gives the correct answer for most browsers. + // Unfortunately this doesn't work perfectly everywhere. + let size: web_sys::ResizeObserverSize = + entry.device_pixel_content_box_size().at(0).dyn_into()?; + width = size.inline_size(); + height = size.block_size(); + dpr = 1.0; // no need to apply + } else if JsValue::from_str("contentBoxSize").js_in(entry.as_ref()) { + let content_box_size = entry.content_box_size(); + let idx0 = content_box_size.at(0); + if !idx0.is_undefined() { + let size: web_sys::ResizeObserverSize = idx0.dyn_into()?; + width = size.inline_size(); + height = size.block_size(); + } else { + // legacy + let size = JsValue::clone(content_box_size.as_ref()); + let size: web_sys::ResizeObserverSize = size.dyn_into()?; + width = size.inline_size(); + height = size.block_size(); + } + } else { + // legacy + let content_rect = entry.content_rect(); + width = content_rect.width(); + height = content_rect.height(); + } + + Ok(((width.round() * dpr) as u32, (height.round() * dpr) as u32)) +} diff --git a/crates/eframe/src/web/web_runner.rs b/crates/eframe/src/web/web_runner.rs index f6e3e0c4ba7..0cedaf68dea 100644 --- a/crates/eframe/src/web/web_runner.rs +++ b/crates/eframe/src/web/web_runner.rs @@ -28,8 +28,8 @@ pub struct WebRunner { /// the panic handler, since they aren't `Send`. events_to_unsubscribe: Rc>>, - /// Used in `destroy` to cancel a pending frame. - request_animation_frame_id: Cell>, + /// Current animation frame in flight. + frame: Rc>>, resize_observer: Rc>>, } @@ -49,7 +49,7 @@ impl WebRunner { panic_handler, runner: Rc::new(RefCell::new(None)), events_to_unsubscribe: Rc::new(RefCell::new(Default::default())), - request_animation_frame_id: Cell::new(None), + frame: Default::default(), resize_observer: Default::default(), } } @@ -116,7 +116,7 @@ impl WebRunner { } if let Some(context) = self.resize_observer.take() { - context.observer.disconnect(); + context.resize_observer.disconnect(); drop(context.closure); } } @@ -125,9 +125,9 @@ impl WebRunner { pub fn destroy(&self) { self.unsubscribe_from_all_events(); - if let Some(id) = self.request_animation_frame_id.get() { + if let Some(frame) = self.frame.take() { let window = web_sys::window().unwrap(); - window.cancel_animation_frame(id).ok(); + window.cancel_animation_frame(frame.id).ok(); } if let Some(runner) = self.runner.replace(None) { @@ -203,32 +203,67 @@ impl WebRunner { } pub(crate) fn request_animation_frame(&self) -> Result<(), wasm_bindgen::JsValue> { + if self.frame.borrow().is_some() { + // there is already an animation frame in flight + return Ok(()); + } + let window = web_sys::window().unwrap(); let closure = Closure::once({ let runner_ref = self.clone(); - move || events::paint_and_schedule(&runner_ref) + move || { + // animation frame is running and can't be cancelled anymore + let _ = runner_ref.frame.take(); + + // if there hasn't been a panic, then paint and schedule. + if let Some(mut runner_lock) = runner_ref.try_lock() { + events::paint_if_needed(&mut runner_lock); + drop(runner_lock); + runner_ref.request_animation_frame()?; + } + + Ok(()) + } }); + let id = window.request_animation_frame(closure.as_ref().unchecked_ref())?; - self.request_animation_frame_id.set(Some(id)); - closure.forget(); // We must forget it, or else the callback is canceled on drop + self.frame + .borrow_mut() + .replace(AnimationFrameRequest { id, closure }); + Ok(()) } pub(crate) fn set_resize_observer( &self, - observer: web_sys::ResizeObserver, + resize_observer: web_sys::ResizeObserver, closure: Closure, ) { self.resize_observer .borrow_mut() - .replace(ResizeObserverContext { observer, closure }); + .replace(ResizeObserverContext { + resize_observer, + closure, + }); } } // ---------------------------------------------------------------------------- +struct AnimationFrameRequest { + /// Represents the ID of a frame in flight. + /// + /// This is only set between a call to `request_animation_frame` and the invocation of its callback, + /// which means that repeated calls to `request_animation_frame` will be ignored. + id: i32, + + /// The callback given to `request_animation_frame`, stored here both to prevent it + /// from being canceled, and from having to `.forget()` it. + closure: Closure Result<(), JsValue>>, +} + struct ResizeObserverContext { - observer: web_sys::ResizeObserver, + resize_observer: web_sys::ResizeObserver, closure: Closure, } diff --git a/web_demo/index.html b/web_demo/index.html index 142355b48f8..bdc337b5c31 100644 --- a/web_demo/index.html +++ b/web_demo/index.html @@ -50,6 +50,8 @@ position: absolute; top: 0%; left: 50%; + width: 100%; + height: 100%; transform: translate(-50%, 0%); } From 28c970de3e05461e4444b8a247908673560f5102 Mon Sep 17 00:00:00 2001 From: jprochazk <1665677+jprochazk@users.noreply.github.com> Date: Fri, 24 May 2024 20:40:47 +0200 Subject: [PATCH 06/14] revert unrelated changes + fix lints --- crates/eframe/src/web/app_runner.rs | 2 +- crates/eframe/src/web/events.rs | 15 ++++++++- crates/eframe/src/web/mod.rs | 51 ----------------------------- crates/eframe/src/web/web_runner.rs | 20 ++--------- 4 files changed, 18 insertions(+), 70 deletions(-) diff --git a/crates/eframe/src/web/app_runner.rs b/crates/eframe/src/web/app_runner.rs index ec81ff726c5..872921591ac 100644 --- a/crates/eframe/src/web/app_runner.rs +++ b/crates/eframe/src/web/app_runner.rs @@ -5,6 +5,7 @@ use crate::{epi, App}; use super::{now_sec, web_painter::WebPainter, NeedRepaint}; pub struct AppRunner { + #[allow(dead_code)] web_options: crate::WebOptions, pub(crate) frame: epi::Frame, egui_ctx: egui::Context, @@ -184,7 +185,6 @@ impl AppRunner { /// /// The result can be painted later with a call to [`Self::run_and_paint`] or [`Self::paint`]. pub fn logic(&mut self) { - // super::resize_canvas_to_screen_size(self.canvas(), self.web_options.max_size_points); let canvas_size = super::canvas_size_in_points(self.canvas(), self.egui_ctx()); let raw_input = self.input.new_frame(canvas_size); diff --git a/crates/eframe/src/web/events.rs b/crates/eframe/src/web/events.rs index 9c9c99173d7..0c3806907be 100644 --- a/crates/eframe/src/web/events.rs +++ b/crates/eframe/src/web/events.rs @@ -2,7 +2,20 @@ use super::*; // ------------------------------------------------------------------------ -pub(crate) fn paint_if_needed(runner: &mut AppRunner) { +/// Calls `request_animation_frame` to schedule repaint. +/// +/// It will only paint if needed, but will always call `request_animation_frame` immediately. +pub(crate) fn paint_and_schedule(runner_ref: &WebRunner) -> Result<(), JsValue> { + // Only paint and schedule if there has been no panic + if let Some(mut runner_lock) = runner_ref.try_lock() { + paint_if_needed(&mut runner_lock); + drop(runner_lock); + runner_ref.request_animation_frame()?; + } + Ok(()) +} + +fn paint_if_needed(runner: &mut AppRunner) { if runner.needs_repaint.needs_repaint() { if runner.has_outstanding_paint_data() { // We have already run the logic, e.g. in an on-click event, diff --git a/crates/eframe/src/web/mod.rs b/crates/eframe/src/web/mod.rs index 566c9b03c8d..f98bbc1ed01 100644 --- a/crates/eframe/src/web/mod.rs +++ b/crates/eframe/src/web/mod.rs @@ -40,7 +40,6 @@ pub(crate) type ActiveWebPainter = web_painter_wgpu::WebPainterWgpu; pub use backend::*; -use egui::Vec2; use wasm_bindgen::prelude::*; use web_sys::MediaQueryList; @@ -124,56 +123,6 @@ fn canvas_size_in_points(canvas: &web_sys::HtmlCanvasElement, ctx: &egui::Contex ) } -fn resize_canvas_to_screen_size( - canvas: &web_sys::HtmlCanvasElement, - max_size_points: egui::Vec2, -) -> Option<()> { - let parent = canvas.parent_element()?; - - // In this function we use "pixel" to mean physical pixel, - // and "point" to mean "logical CSS pixel". - let pixels_per_point = native_pixels_per_point(); - - // Prefer the client width and height so that if the parent - // element is resized that the egui canvas resizes appropriately. - let parent_size_points = Vec2 { - x: parent.client_width() as f32, - y: parent.client_height() as f32, - }; - - if parent_size_points.x <= 0.0 || parent_size_points.y <= 0.0 { - log::error!("The parent element of the egui canvas is {}x{}. Try adding `html, body {{ height: 100%; width: 100% }}` to your CSS!", parent_size_points.x, parent_size_points.y); - } - - // We take great care here to ensure the rendered canvas aligns - // perfectly to the physical pixel grid, lest we get blurry text. - // At the time of writing, we get pixel perfection on Chromium and Firefox on Mac, - // but Desktop Safari will be blurry on most zoom levels. - // See https://github.com/emilk/egui/issues/4241 for more. - - let canvas_size_pixels = pixels_per_point * parent_size_points.min(max_size_points); - - // Make sure that the size is always an even number of pixels, - // otherwise, the page renders blurry on some platforms. - // See https://github.com/emilk/egui/issues/103 - let canvas_size_pixels = (canvas_size_pixels / 2.0).round() * 2.0; - - let canvas_size_points = canvas_size_pixels / pixels_per_point; - - canvas - .style() - .set_property("width", &format!("{}px", canvas_size_points.x)) - .ok()?; - canvas - .style() - .set_property("height", &format!("{}px", canvas_size_points.y)) - .ok()?; - canvas.set_width(canvas_size_pixels.x as u32); - canvas.set_height(canvas_size_pixels.y as u32); - - Some(()) -} - // ---------------------------------------------------------------------------- /// Set the cursor icon. diff --git a/crates/eframe/src/web/web_runner.rs b/crates/eframe/src/web/web_runner.rs index 0cedaf68dea..17119fe8eb8 100644 --- a/crates/eframe/src/web/web_runner.rs +++ b/crates/eframe/src/web/web_runner.rs @@ -1,7 +1,4 @@ -use std::{ - cell::{Cell, RefCell}, - rc::Rc, -}; +use std::{cell::RefCell, rc::Rc}; use wasm_bindgen::prelude::*; @@ -128,6 +125,7 @@ impl WebRunner { if let Some(frame) = self.frame.take() { let window = web_sys::window().unwrap(); window.cancel_animation_frame(frame.id).ok(); + drop(frame.closure); } if let Some(runner) = self.runner.replace(None) { @@ -211,19 +209,7 @@ impl WebRunner { let window = web_sys::window().unwrap(); let closure = Closure::once({ let runner_ref = self.clone(); - move || { - // animation frame is running and can't be cancelled anymore - let _ = runner_ref.frame.take(); - - // if there hasn't been a panic, then paint and schedule. - if let Some(mut runner_lock) = runner_ref.try_lock() { - events::paint_if_needed(&mut runner_lock); - drop(runner_lock); - runner_ref.request_animation_frame()?; - } - - Ok(()) - } + move || events::paint_and_schedule(&runner_ref) }); let id = window.request_animation_frame(closure.as_ref().unchecked_ref())?; From 880d4b8a16eea42c2f0604cd23a43148048580f7 Mon Sep 17 00:00:00 2001 From: jprochazk <1665677+jprochazk@users.noreply.github.com> Date: Fri, 24 May 2024 20:42:00 +0200 Subject: [PATCH 07/14] link to canvas resize article --- crates/eframe/src/web/events.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/eframe/src/web/events.rs b/crates/eframe/src/web/events.rs index 0c3806907be..8151d301c5b 100644 --- a/crates/eframe/src/web/events.rs +++ b/crates/eframe/src/web/events.rs @@ -620,6 +620,8 @@ pub(crate) fn install_resize_observer(runner_ref: &WebRunner) -> Result<(), JsVa Ok(()) } +// Code ported to Rust from: +// https://webglfundamentals.org/webgl/lessons/webgl-resizing-the-canvas.html fn get_display_size(resize_observer_entries: js_sys::Array) -> Result<(u32, u32), JsValue> { let width; let height; From 0f8728fc159fc23f325e6eca1b58bf205f7237ff Mon Sep 17 00:00:00 2001 From: jprochazk <1665677+jprochazk@users.noreply.github.com> Date: Fri, 24 May 2024 22:18:32 +0200 Subject: [PATCH 08/14] fix lint --- crates/eframe/src/web/events.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/eframe/src/web/events.rs b/crates/eframe/src/web/events.rs index 8151d301c5b..bdc55f59f04 100644 --- a/crates/eframe/src/web/events.rs +++ b/crates/eframe/src/web/events.rs @@ -600,7 +600,7 @@ pub(crate) fn install_resize_observer(runner_ref: &WebRunner) -> Result<(), JsVa if let Some(mut runner_lock) = runner_ref.try_lock() { runner_lock.needs_repaint.repaint_asap(); let canvas = runner_lock.canvas(); - let (width, height) = get_display_size(entries).unwrap(); + let (width, height) = get_display_size(&entries).unwrap(); canvas.set_width(width); canvas.set_height(height); @@ -622,7 +622,7 @@ pub(crate) fn install_resize_observer(runner_ref: &WebRunner) -> Result<(), JsVa // Code ported to Rust from: // https://webglfundamentals.org/webgl/lessons/webgl-resizing-the-canvas.html -fn get_display_size(resize_observer_entries: js_sys::Array) -> Result<(u32, u32), JsValue> { +fn get_display_size(resize_observer_entries: &js_sys::Array) -> Result<(u32, u32), JsValue> { let width; let height; let mut dpr = web_sys::window().unwrap().device_pixel_ratio(); From e693b108e7244c2604a2438a38185ef19ac5d31d Mon Sep 17 00:00:00 2001 From: jprochazk <1665677+jprochazk@users.noreply.github.com> Date: Mon, 27 May 2024 17:20:18 +0200 Subject: [PATCH 09/14] remove `max_size_points` --- crates/eframe/src/epi.rs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/crates/eframe/src/epi.rs b/crates/eframe/src/epi.rs index db39a08b15b..feb401c269f 100644 --- a/crates/eframe/src/epi.rs +++ b/crates/eframe/src/epi.rs @@ -455,11 +455,6 @@ pub struct WebOptions { /// Configures wgpu instance/device/adapter/surface creation and renderloop. #[cfg(feature = "wgpu")] pub wgpu_options: egui_wgpu::WgpuConfiguration, - - /// The size limit of the web app canvas. - /// - /// By default the max size is [`egui::Vec2::INFINITY`], i.e. unlimited. - pub max_size_points: egui::Vec2, } #[cfg(target_arch = "wasm32")] @@ -475,8 +470,6 @@ impl Default for WebOptions { #[cfg(feature = "wgpu")] wgpu_options: egui_wgpu::WgpuConfiguration::default(), - - max_size_points: egui::Vec2::INFINITY, } } } From 0570abf9de7cdc2384299d4d3350a1a407432073 Mon Sep 17 00:00:00 2001 From: jprochazk <1665677+jprochazk@users.noreply.github.com> Date: Mon, 27 May 2024 17:22:14 +0200 Subject: [PATCH 10/14] dont use `transform` --- web_demo/index.html | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/web_demo/index.html b/web_demo/index.html index bdc337b5c31..c4b3bac0095 100644 --- a/web_demo/index.html +++ b/web_demo/index.html @@ -48,11 +48,10 @@ margin-left: auto; display: block; position: absolute; - top: 0%; - left: 50%; + top: 0; + left: 0; width: 100%; height: 100%; - transform: translate(-50%, 0%); } .centered { From da20bfa481d314892aa861040c813683cee83bee Mon Sep 17 00:00:00 2001 From: jprochazk <1665677+jprochazk@users.noreply.github.com> Date: Mon, 27 May 2024 17:54:02 +0200 Subject: [PATCH 11/14] fix repaint --- crates/eframe/src/web/events.rs | 1 - crates/eframe/src/web/web_runner.rs | 8 +++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/crates/eframe/src/web/events.rs b/crates/eframe/src/web/events.rs index bdc55f59f04..5a6d54f90cc 100644 --- a/crates/eframe/src/web/events.rs +++ b/crates/eframe/src/web/events.rs @@ -598,7 +598,6 @@ pub(crate) fn install_resize_observer(runner_ref: &WebRunner) -> Result<(), JsVa move |entries: js_sys::Array| { // Only call the wrapped closure if the egui code has not panicked if let Some(mut runner_lock) = runner_ref.try_lock() { - runner_lock.needs_repaint.repaint_asap(); let canvas = runner_lock.canvas(); let (width, height) = get_display_size(&entries).unwrap(); canvas.set_width(width); diff --git a/crates/eframe/src/web/web_runner.rs b/crates/eframe/src/web/web_runner.rs index 17119fe8eb8..9b12deee3b8 100644 --- a/crates/eframe/src/web/web_runner.rs +++ b/crates/eframe/src/web/web_runner.rs @@ -209,7 +209,13 @@ impl WebRunner { let window = web_sys::window().unwrap(); let closure = Closure::once({ let runner_ref = self.clone(); - move || events::paint_and_schedule(&runner_ref) + move || { + // we can paint now, so clear the animation frame + // this drop the `closure` and allows another + // animation frame to be scheduled + let _ = runner_ref.frame.take(); + events::paint_and_schedule(&runner_ref) + } }); let id = window.request_animation_frame(closure.as_ref().unchecked_ref())?; From 7a116001a064a52dcbd8e34ac576e1d48f53a5c1 Mon Sep 17 00:00:00 2001 From: jprochazk <1665677+jprochazk@users.noreply.github.com> Date: Mon, 27 May 2024 18:07:03 +0200 Subject: [PATCH 12/14] don't unwrap --- crates/eframe/src/web/events.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/crates/eframe/src/web/events.rs b/crates/eframe/src/web/events.rs index 5a6d54f90cc..33d36c356d7 100644 --- a/crates/eframe/src/web/events.rs +++ b/crates/eframe/src/web/events.rs @@ -599,7 +599,13 @@ pub(crate) fn install_resize_observer(runner_ref: &WebRunner) -> Result<(), JsVa // Only call the wrapped closure if the egui code has not panicked if let Some(mut runner_lock) = runner_ref.try_lock() { let canvas = runner_lock.canvas(); - let (width, height) = get_display_size(&entries).unwrap(); + let (width, height) = match get_display_size(&entries) { + Ok(v) => v, + Err(err) => { + log::error!("{}", super::string_from_js_value(&err)); + return; + } + }; canvas.set_width(width); canvas.set_height(height); @@ -613,8 +619,11 @@ pub(crate) fn install_resize_observer(runner_ref: &WebRunner) -> Result<(), JsVa let observer = web_sys::ResizeObserver::new(closure.as_ref().unchecked_ref())?; let mut options = web_sys::ResizeObserverOptions::new(); options.box_(web_sys::ResizeObserverBoxOptions::ContentBox); - observer.observe_with_options(runner_ref.try_lock().unwrap().canvas(), &options); - runner_ref.set_resize_observer(observer, closure); + if let Some(runner_lock) = runner_ref.try_lock() { + observer.observe_with_options(runner_lock.canvas(), &options); + drop(runner_lock); + runner_ref.set_resize_observer(observer, closure); + } Ok(()) } From 9b9602f67567385c364ed14e16ad0f38f144159e Mon Sep 17 00:00:00 2001 From: jprochazk <1665677+jprochazk@users.noreply.github.com> Date: Mon, 27 May 2024 20:01:28 +0200 Subject: [PATCH 13/14] revert --- crates/eframe/src/web/web_runner.rs | 45 +++++++---------------------- 1 file changed, 11 insertions(+), 34 deletions(-) diff --git a/crates/eframe/src/web/web_runner.rs b/crates/eframe/src/web/web_runner.rs index 9b12deee3b8..bfb96d9c710 100644 --- a/crates/eframe/src/web/web_runner.rs +++ b/crates/eframe/src/web/web_runner.rs @@ -1,4 +1,7 @@ -use std::{cell::RefCell, rc::Rc}; +use std::{ + cell::{Cell, RefCell}, + rc::Rc, +}; use wasm_bindgen::prelude::*; @@ -26,7 +29,7 @@ pub struct WebRunner { events_to_unsubscribe: Rc>>, /// Current animation frame in flight. - frame: Rc>>, + request_animation_frame_id: Cell>, resize_observer: Rc>>, } @@ -46,7 +49,7 @@ impl WebRunner { panic_handler, runner: Rc::new(RefCell::new(None)), events_to_unsubscribe: Rc::new(RefCell::new(Default::default())), - frame: Default::default(), + request_animation_frame_id: Cell::new(None), resize_observer: Default::default(), } } @@ -122,10 +125,9 @@ impl WebRunner { pub fn destroy(&self) { self.unsubscribe_from_all_events(); - if let Some(frame) = self.frame.take() { + if let Some(id) = self.request_animation_frame_id.get() { let window = web_sys::window().unwrap(); - window.cancel_animation_frame(frame.id).ok(); - drop(frame.closure); + window.cancel_animation_frame(id).ok(); } if let Some(runner) = self.runner.replace(None) { @@ -201,27 +203,14 @@ impl WebRunner { } pub(crate) fn request_animation_frame(&self) -> Result<(), wasm_bindgen::JsValue> { - if self.frame.borrow().is_some() { - // there is already an animation frame in flight - return Ok(()); - } - let window = web_sys::window().unwrap(); let closure = Closure::once({ let runner_ref = self.clone(); - move || { - // we can paint now, so clear the animation frame - // this drop the `closure` and allows another - // animation frame to be scheduled - let _ = runner_ref.frame.take(); - events::paint_and_schedule(&runner_ref) - } + move || events::paint_and_schedule(&runner_ref) }); - let id = window.request_animation_frame(closure.as_ref().unchecked_ref())?; - self.frame - .borrow_mut() - .replace(AnimationFrameRequest { id, closure }); + self.request_animation_frame_id.set(Some(id)); + closure.forget(); // We must forget it, or else the callback is canceled on drop Ok(()) } @@ -242,18 +231,6 @@ impl WebRunner { // ---------------------------------------------------------------------------- -struct AnimationFrameRequest { - /// Represents the ID of a frame in flight. - /// - /// This is only set between a call to `request_animation_frame` and the invocation of its callback, - /// which means that repeated calls to `request_animation_frame` will be ignored. - id: i32, - - /// The callback given to `request_animation_frame`, stored here both to prevent it - /// from being canceled, and from having to `.forget()` it. - closure: Closure Result<(), JsValue>>, -} - struct ResizeObserverContext { resize_observer: web_sys::ResizeObserver, closure: Closure, From 44be76ef026eef035356908879f4de2a9b96667d Mon Sep 17 00:00:00 2001 From: jprochazk <1665677+jprochazk@users.noreply.github.com> Date: Mon, 27 May 2024 20:09:59 +0200 Subject: [PATCH 14/14] fix --- crates/eframe/src/web/web_runner.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/eframe/src/web/web_runner.rs b/crates/eframe/src/web/web_runner.rs index bfb96d9c710..35bb4be334b 100644 --- a/crates/eframe/src/web/web_runner.rs +++ b/crates/eframe/src/web/web_runner.rs @@ -209,6 +209,7 @@ impl WebRunner { move || events::paint_and_schedule(&runner_ref) }); + let id = window.request_animation_frame(closure.as_ref().unchecked_ref())?; self.request_animation_frame_id.set(Some(id)); closure.forget(); // We must forget it, or else the callback is canceled on drop