diff --git a/Cargo.toml b/Cargo.toml index a42033c0efb..77cc2e7ba5d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ members = [ "crates/epaint", "examples/*", + "tests/*", "xtask", ] diff --git a/crates/eframe/Cargo.toml b/crates/eframe/Cargo.toml index 4a5a9918a12..acd7600f6e6 100644 --- a/crates/eframe/Cargo.toml +++ b/crates/eframe/Cargo.toml @@ -237,7 +237,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/epi.rs b/crates/eframe/src/epi.rs index 6d15c39e9e8..7b75811ccbc 100644 --- a/crates/eframe/src/epi.rs +++ b/crates/eframe/src/epi.rs @@ -464,11 +464,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")] @@ -484,8 +479,6 @@ impl Default for WebOptions { #[cfg(feature = "wgpu")] wgpu_options: egui_wgpu::WgpuConfiguration::default(), - - max_size_points: egui::Vec2::INFINITY, } } } diff --git a/crates/eframe/src/web/app_runner.rs b/crates/eframe/src/web/app_runner.rs index 620fe86fd62..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 56d3fdf071a..33d36c356d7 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,79 @@ pub(crate) fn install_canvas_events(runner_ref: &WebRunner) -> Result<(), JsValu Ok(()) } + +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() { + let canvas = runner_lock.canvas(); + 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); + + // force an immediate repaint + runner_lock.needs_repaint.repaint_asap(); + paint_if_needed(&mut runner_lock); + } + } + }) 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); + 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(()) +} + +// 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; + 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/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 383572ce3f7..8bfc899479e 100644 --- a/crates/eframe/src/web/web_runner.rs +++ b/crates/eframe/src/web/web_runner.rs @@ -27,6 +27,8 @@ pub struct WebRunner { /// Current animation frame in flight. frame: Rc>>, + + resize_observer: Rc>>, } impl WebRunner { @@ -45,6 +47,7 @@ impl WebRunner { runner: Rc::new(RefCell::new(None)), events_to_unsubscribe: Rc::new(RefCell::new(Default::default())), frame: Default::default(), + resize_observer: Default::default(), } } @@ -75,6 +78,8 @@ impl WebRunner { events::install_color_scheme_change_event(self)?; } + events::install_resize_observer(self)?; + self.request_animation_frame()?; } @@ -106,6 +111,11 @@ impl WebRunner { } } } + + if let Some(context) = self.resize_observer.take() { + context.resize_observer.disconnect(); + drop(context.closure); + } } /// Shut down eframe and clean up resources. @@ -215,6 +225,19 @@ impl WebRunner { Ok(()) } + + pub(crate) fn set_resize_observer( + &self, + resize_observer: web_sys::ResizeObserver, + closure: Closure, + ) { + self.resize_observer + .borrow_mut() + .replace(ResizeObserverContext { + resize_observer, + closure, + }); + } } // ---------------------------------------------------------------------------- @@ -232,6 +255,11 @@ struct AnimationFrameRequest { _closure: Closure Result<(), JsValue>>, } +struct ResizeObserverContext { + resize_observer: web_sys::ResizeObserver, + closure: Closure, +} + struct TargetEvent { target: web_sys::EventTarget, event_name: String, diff --git a/examples/multiple_viewports/README.md b/examples/multiple_viewports/README.md index 1ce0a2b98b0..3fa67d8abe7 100644 --- a/examples/multiple_viewports/README.md +++ b/examples/multiple_viewports/README.md @@ -4,4 +4,4 @@ Example how to show multiple viewports (native windows) can be created in `egui` cargo run -p multiple_viewports ``` -For a more advanced example, see [../test_viewports]. +For a more advanced example, see [../../tests/test_viewports]. diff --git a/examples/test_viewports/screenshot.png b/examples/test_viewports/screenshot.png deleted file mode 100644 index 5e8680bbcb0..00000000000 Binary files a/examples/test_viewports/screenshot.png and /dev/null differ diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 00000000000..ac4d8a18f4c --- /dev/null +++ b/tests/README.md @@ -0,0 +1,4 @@ +## Test apps +Some application to tests various parts of egui and eframe. + +At some point it would be nice to have automatic screenshot regression tests for these. diff --git a/examples/test_inline_glow_paint/Cargo.toml b/tests/test_inline_glow_paint/Cargo.toml similarity index 100% rename from examples/test_inline_glow_paint/Cargo.toml rename to tests/test_inline_glow_paint/Cargo.toml diff --git a/examples/test_inline_glow_paint/src/main.rs b/tests/test_inline_glow_paint/src/main.rs similarity index 100% rename from examples/test_inline_glow_paint/src/main.rs rename to tests/test_inline_glow_paint/src/main.rs diff --git a/examples/test_viewports/Cargo.toml b/tests/test_viewports/Cargo.toml similarity index 100% rename from examples/test_viewports/Cargo.toml rename to tests/test_viewports/Cargo.toml diff --git a/examples/test_viewports/README.md b/tests/test_viewports/README.md similarity index 52% rename from examples/test_viewports/README.md rename to tests/test_viewports/README.md index 280ffe2325d..2fa38c802aa 100644 --- a/examples/test_viewports/README.md +++ b/tests/test_viewports/README.md @@ -1,5 +1,3 @@ This is a test of the viewports feature of eframe and egui, where we show off using multiple windows. -For a simple example, see [`multiple_viewports`](../multiple_viewports). - -![](screenshot.png) +For a simple example, see [`multiple_viewports`](../../examples/multiple_viewports). diff --git a/examples/test_viewports/src/main.rs b/tests/test_viewports/src/main.rs similarity index 100% rename from examples/test_viewports/src/main.rs rename to tests/test_viewports/src/main.rs diff --git a/web_demo/index.html b/web_demo/index.html index 142355b48f8..c4b3bac0095 100644 --- a/web_demo/index.html +++ b/web_demo/index.html @@ -48,9 +48,10 @@ margin-left: auto; display: block; position: absolute; - top: 0%; - left: 50%; - transform: translate(-50%, 0%); + top: 0; + left: 0; + width: 100%; + height: 100%; } .centered {