Increase accuracy of various Web APIs (#2946)

This commit is contained in:
daxpedda 2023-07-10 23:55:43 +02:00 committed by GitHub
parent db8de03142
commit c4d70d75c1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 73 additions and 38 deletions

View file

@ -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

View file

@ -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

View file

@ -400,12 +400,17 @@ impl<T: 'static> Shared<T> {
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),
});
}
}

View file

@ -705,9 +705,10 @@ impl<T> EventLoopWindowTarget<T> {
},
{
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<T> EventLoopWindowTarget<T> {
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);
})
}

View file

@ -28,6 +28,7 @@ pub struct Canvas {
common: Common,
id: WindowId,
pub has_focus: Arc<AtomicBool>,
pub is_intersecting: Option<bool>,
on_touch_start: Option<EventListenerHandle<dyn FnMut(Event)>>,
on_touch_end: Option<EventListenerHandle<dyn FnMut(Event)>>,
on_focus: Option<EventListenerHandle<dyn FnMut(FocusEvent)>>,
@ -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<f64> {
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<u32> {

View file

@ -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`

View file

@ -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;

View file

@ -127,9 +127,29 @@ impl Window {
pub fn set_outer_position(&self, position: Position) {
self.inner.dispatch(move |inner| {
let position = position.to_logical::<f64>(inner.scale_factor());
let mut position = position.to_logical::<f64>(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());

View file

@ -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<P: Into<Position>>(&self, position: P) {
self.window.set_outer_position(position.into())