From c4d70d75c1de71b0142f3144de9497d116452ae0 Mon Sep 17 00:00:00 2001 From: daxpedda Date: Mon, 10 Jul 2023 23:55:43 +0200 Subject: [PATCH] Increase accuracy of various Web APIs (#2946) --- CHANGELOG.md | 1 + src/platform/web.rs | 2 ++ src/platform_impl/web/event_loop/runner.rs | 13 ++++++---- .../web/event_loop/window_target.rs | 10 ++++++-- src/platform_impl/web/web_sys/canvas.rs | 24 +++++++++++++++++-- .../web/web_sys/intersection_handle.rs | 12 +--------- src/platform_impl/web/web_sys/mod.rs | 22 ++++------------- src/platform_impl/web/window.rs | 22 ++++++++++++++++- src/window.rs | 5 +++- 9 files changed, 73 insertions(+), 38 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7fe9a583..6fb2a16f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ And please only add new entries to the top of this list, right below the `# Unre - Implement `PartialOrd` and `Ord` for `KeyCode` and `NativeKeyCode`. - On Web, implement `WindowEvent::Occluded`. - On Web, fix touch location to be as accurate as mouse position. +- On Web, account for CSS `padding`, `border`, and `margin` when getting or setting the canvas position. # 0.29.0-beta.0 diff --git a/src/platform/web.rs b/src/platform/web.rs index b6f6b519..b353be82 100644 --- a/src/platform/web.rs +++ b/src/platform/web.rs @@ -13,6 +13,7 @@ //! - [`WindowEvent::Occluded`] //! - [`WindowEvent::CursorMoved`], [`WindowEvent::CursorEntered`], [`WindowEvent::CursorLeft`], //! and [`WindowEvent::Touch`]. +//! - [`Window::set_outer_position()`] //! //! [`WindowEvent::Resized`]: crate::event::WindowEvent::Resized //! [`Window::(set_)inner_size()`]: crate::window::Window::inner_size() @@ -21,6 +22,7 @@ //! [`WindowEvent::CursorEntered`]: crate::event::WindowEvent::CursorEntered //! [`WindowEvent::CursorLeft`]: crate::event::WindowEvent::CursorLeft //! [`WindowEvent::Touch`]: crate::event::WindowEvent::Touch +//! [`Window::set_outer_position()`]: crate::window::Window::set_outer_position() //! [`transform`]: https://developer.mozilla.org/en-US/docs/Web/CSS/transform //! [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border //! [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding diff --git a/src/platform_impl/web/event_loop/runner.rs b/src/platform_impl/web/event_loop/runner.rs index ac72dc6e..4fd68ade 100644 --- a/src/platform_impl/web/event_loop/runner.rs +++ b/src/platform_impl/web/event_loop/runner.rs @@ -400,12 +400,17 @@ impl Shared { if !runner.0.suspended.get() { for (id, canvas) in &*runner.0.all_canvases.borrow() { if let Some(canvas) = canvas.upgrade() { - if backend::is_intersecting(runner.window(), canvas.borrow().raw()) { + let is_visible = backend::is_visible(runner.window()); + // only fire if: + // - not visible and intersects + // - 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) + { runner.send_event(Event::WindowEvent { window_id: *id, - event: WindowEvent::Occluded(!backend::is_visible( - runner.window(), - )), + event: WindowEvent::Occluded(!is_visible), }); } } diff --git a/src/platform_impl/web/event_loop/window_target.rs b/src/platform_impl/web/event_loop/window_target.rs index fbb686d4..faf2403a 100644 --- a/src/platform_impl/web/event_loop/window_target.rs +++ b/src/platform_impl/web/event_loop/window_target.rs @@ -705,9 +705,10 @@ impl EventLoopWindowTarget { }, { let runner = self.runner.clone(); + let canvas = canvas_clone.clone(); move |new_size| { - let canvas = RefCell::borrow(&canvas_clone); + let canvas = canvas.borrow(); canvas.set_current_size(new_size); if canvas.old_size() != new_size { canvas.set_old_size(new_size); @@ -723,12 +724,17 @@ impl EventLoopWindowTarget { let runner = self.runner.clone(); canvas.on_intersection(move |is_intersecting| { - if backend::is_visible(runner.window()) { + // only fire if visible while skipping the first event if it's intersecting + if backend::is_visible(runner.window()) + && !(is_intersecting && canvas_clone.borrow().is_intersecting.is_none()) + { runner.send_event(Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::Occluded(!is_intersecting), }); } + + canvas_clone.borrow_mut().is_intersecting = Some(is_intersecting); }) } diff --git a/src/platform_impl/web/web_sys/canvas.rs b/src/platform_impl/web/web_sys/canvas.rs index 4b8a2200..8ef4f38f 100644 --- a/src/platform_impl/web/web_sys/canvas.rs +++ b/src/platform_impl/web/web_sys/canvas.rs @@ -28,6 +28,7 @@ pub struct Canvas { common: Common, id: WindowId, pub has_focus: Arc, + pub is_intersecting: Option, on_touch_start: Option>, on_touch_end: Option>, on_focus: Option>, @@ -97,6 +98,7 @@ impl Canvas { }, id, has_focus: Arc::new(AtomicBool::new(false)), + is_intersecting: None, on_touch_start: None, on_touch_end: None, on_blur: None, @@ -134,11 +136,29 @@ impl Canvas { pub fn position(&self) -> LogicalPosition { let bounds = self.common.raw.get_bounding_client_rect(); - - LogicalPosition { + let mut position = LogicalPosition { x: bounds.x(), y: bounds.y(), + }; + + let document = self.window().document().expect("Failed to obtain document"); + + if document.contains(Some(self.raw())) { + let style = self + .window() + .get_computed_style(self.raw()) + .expect("Failed to obtain computed style") + // this can't fail: we aren't using a pseudo-element + .expect("Invalid pseudo-element"); + if style.get_property_value("display").unwrap() != "none" { + position.x += super::style_size_property(&style, "border-left-width") + + super::style_size_property(&style, "padding-left"); + position.y += super::style_size_property(&style, "border-top-width") + + super::style_size_property(&style, "padding-top"); + } } + + position } pub fn old_size(&self) -> PhysicalSize { diff --git a/src/platform_impl/web/web_sys/intersection_handle.rs b/src/platform_impl/web/web_sys/intersection_handle.rs index 2b694a56..cae8eeb7 100644 --- a/src/platform_impl/web/web_sys/intersection_handle.rs +++ b/src/platform_impl/web/web_sys/intersection_handle.rs @@ -12,19 +12,9 @@ impl IntersectionObserverHandle { where F: 'static + FnMut(bool), { - let mut skip = true; let closure = Closure::new(move |entries: Array| { let entry: IntersectionObserverEntry = entries.get(0).unchecked_into(); - - let is_intersecting = entry.is_intersecting(); - - // skip first intersection - if skip && is_intersecting { - skip = false; - return; - } - - callback(is_intersecting); + callback(entry.is_intersecting()); }); let observer = IntersectionObserver::new(closure.as_ref().unchecked_ref()) // we don't provide any `options` diff --git a/src/platform_impl/web/web_sys/mod.rs b/src/platform_impl/web/web_sys/mod.rs index 882ff095..273c57ca 100644 --- a/src/platform_impl/web/web_sys/mod.rs +++ b/src/platform_impl/web/web_sys/mod.rs @@ -75,13 +75,17 @@ pub fn set_canvas_size( ) { let document = window.document().expect("Failed to obtain document"); + if !document.contains(Some(raw)) { + return; + } + let style = window .get_computed_style(raw) .expect("Failed to obtain computed style") // this can't fail: we aren't using a pseudo-element .expect("Invalid pseudo-element"); - if !document.contains(Some(raw)) || style.get_property_value("display").unwrap() == "none" { + if style.get_property_value("display").unwrap() == "none" { return; } @@ -144,20 +148,4 @@ pub fn is_visible(window: &web_sys::Window) -> bool { document.visibility_state() == VisibilityState::Visible } -pub fn is_intersecting(window: &web_sys::Window, canvas: &HtmlCanvasElement) -> bool { - let rect = canvas.get_bounding_client_rect(); - // This should never panic. - let window_width = window.inner_width().unwrap().as_f64().unwrap() as i32; - let window_height = window.inner_height().unwrap().as_f64().unwrap() as i32; - let left = rect.left() as i32; - let width = rect.width() as i32; - let top = rect.top() as i32; - let height = rect.height() as i32; - - let horizontal = left <= window_width && left + width >= 0; - let vertical = top <= window_height && top + height >= 0; - - horizontal && vertical -} - pub type RawCanvasType = HtmlCanvasElement; diff --git a/src/platform_impl/web/window.rs b/src/platform_impl/web/window.rs index da5c0a02..eb7ad858 100644 --- a/src/platform_impl/web/window.rs +++ b/src/platform_impl/web/window.rs @@ -127,9 +127,29 @@ impl Window { pub fn set_outer_position(&self, position: Position) { self.inner.dispatch(move |inner| { - let position = position.to_logical::(inner.scale_factor()); + let mut position = position.to_logical::(inner.scale_factor()); let canvas = inner.canvas.borrow(); + let document = inner.window.document().expect("Failed to obtain document"); + + if document.contains(Some(canvas.raw())) { + let style = inner + .window + .get_computed_style(canvas.raw()) + .expect("Failed to obtain computed style") + // this can't fail: we aren't using a pseudo-element + .expect("Invalid pseudo-element"); + + if style.get_property_value("display").unwrap() != "none" { + position.x -= backend::style_size_property(&style, "margin-left") + + backend::style_size_property(&style, "border-left-width") + + backend::style_size_property(&style, "padding-left"); + position.y -= backend::style_size_property(&style, "margin-top") + + backend::style_size_property(&style, "border-top-width") + + backend::style_size_property(&style, "padding-top"); + } + } + canvas.set_attribute("position", "fixed"); canvas.set_attribute("left", &position.x.to_string()); canvas.set_attribute("top", &position.y.to_string()); diff --git a/src/window.rs b/src/window.rs index b0066ed9..49859c19 100644 --- a/src/window.rs +++ b/src/window.rs @@ -629,8 +629,11 @@ impl Window { /// /// - **iOS:** Can only be called on the main thread. Sets the top left coordinates of the /// window in the screen space coordinate system. - /// - **Web:** Sets the top-left coordinates relative to the viewport. + /// - **Web:** Sets the top-left coordinates relative to the viewport. Doesn't account for CSS + /// [`transform`]. /// - **Android / Wayland:** Unsupported. + /// + /// [`transform`]: https://developer.mozilla.org/en-US/docs/Web/CSS/transform #[inline] pub fn set_outer_position>(&self, position: P) { self.window.set_outer_position(position.into())