From 0bee4cab4e3f0fb3e5c12d9b299470c7365efe60 Mon Sep 17 00:00:00 2001 From: dAxpeDDa Date: Wed, 7 Jun 2023 17:32:52 +0200 Subject: [PATCH 1/6] Implement `ResizeObserver` Co-Authored-By: Liam Murphy <43807659+Liamolucko@users.noreply.github.com> --- CHANGELOG.md | 4 +- Cargo.toml | 8 +- examples/web.rs | 24 ++- src/platform_impl/web/event_loop/runner.rs | 18 +-- .../web/event_loop/window_target.rs | 40 ++--- src/platform_impl/web/web_sys/canvas.rs | 68 ++++---- src/platform_impl/web/web_sys/mod.rs | 34 +--- src/platform_impl/web/web_sys/resize.rs | 146 ++++++++++++++++++ src/platform_impl/web/window.rs | 26 +--- 9 files changed, 234 insertions(+), 134 deletions(-) create mode 100644 src/platform_impl/web/web_sys/resize.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 578187a028..68fda1a8ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -67,7 +67,7 @@ And please only add new entries to the top of this list, right below the `# Unre - On Web, fix pen treated as mouse input. - On Web, send mouse position on button release as well. - On Web, fix touch input not gaining or loosing focus. -- **Breaking:** On Web, dropped support for Safari versions below 13. +- **Breaking:** On Web, dropped support for Safari versions below 13.1. - On Web, prevent clicks on the canvas to select text. - On Web, `EventLoopProxy` now implements `Send`. - On Web, `Window` now implements `Send` and `Sync`. @@ -79,6 +79,8 @@ And please only add new entries to the top of this list, right below the `# Unre - On Web, fix scale factor resize suggestion always overwriting the canvas size. - On macOS, fix crash when dropping `Window`. - On Web, use `Window.requestIdleCallback()` for `ControlFlow::Poll` when available. +- **Breaking:** On Web, the canvas size is not controlled by Winit anymore and external changes to + the canvas size will be reported through `WindowEvent::Resized`. # 0.28.6 diff --git a/Cargo.toml b/Cargo.toml index 9ff849e3be..a919867524 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -129,12 +129,13 @@ redox_syscall = "0.3" [target.'cfg(target_family = "wasm")'.dependencies.web_sys] package = "web-sys" -version = "0.3" +version = "0.3.64" features = [ 'console', 'CssStyleDeclaration', 'Document', 'DomRect', + 'DomRectReadOnly', 'Element', 'Event', 'EventTarget', @@ -145,6 +146,11 @@ features = [ 'MediaQueryList', 'Node', 'PointerEvent', + 'ResizeObserver', + 'ResizeObserverBoxOptions', + 'ResizeObserverEntry', + 'ResizeObserverOptions', + 'ResizeObserverSize', 'Window', 'WheelEvent' ] diff --git a/examples/web.rs b/examples/web.rs index ea32f06e32..c1df698773 100644 --- a/examples/web.rs +++ b/examples/web.rs @@ -1,9 +1,10 @@ #![allow(clippy::disallowed_methods, clippy::single_match)] use winit::{ - event::{Event, WindowEvent}, + event::{ElementState, Event, KeyEvent, WindowEvent}, event_loop::EventLoop, - window::WindowBuilder, + keyboard::KeyCode, + window::{Fullscreen, WindowBuilder}, }; pub fn main() { @@ -31,6 +32,25 @@ pub fn main() { Event::MainEventsCleared => { window.request_redraw(); } + Event::WindowEvent { + window_id, + event: + WindowEvent::KeyboardInput { + event: + KeyEvent { + physical_key: KeyCode::KeyF, + state: ElementState::Released, + .. + }, + .. + }, + } if window_id == window.id() => { + if window.fullscreen().is_some() { + window.set_fullscreen(None); + } else { + window.set_fullscreen(Some(Fullscreen::Borderless(None))); + } + } _ => (), } }); diff --git a/src/platform_impl/web/event_loop/runner.rs b/src/platform_impl/web/event_loop/runner.rs index 42494cfd4b..8af0a05823 100644 --- a/src/platform_impl/web/event_loop/runner.rs +++ b/src/platform_impl/web/event_loop/runner.rs @@ -327,14 +327,13 @@ impl Shared { // Now handle the `ScaleFactorChanged` events. for &(id, ref canvas) in &*self.0.all_canvases.borrow() { - let rc = match canvas.upgrade() { + let canvas = match canvas.upgrade() { Some(rc) => rc, // This shouldn't happen, but just in case... None => continue, }; - let canvas = rc.borrow(); // First, we send the `ScaleFactorChanged` event: - let current_size = canvas.size().get(); + let current_size = canvas.borrow().inner_size(); let logical_size = current_size.to_logical::(old_scale); let mut new_size = logical_size.to_physical(new_scale); self.handle_single_event_sync( @@ -348,16 +347,11 @@ impl Shared { &mut control, ); - // Then we resize the canvas to the new size and send a `Resized` event: + // Then we resize the canvas to the new size, a new + // `Resized` event will be sent by the `ResizeObserver`: if current_size != new_size { - backend::set_canvas_size(&canvas, crate::dpi::Size::Physical(new_size)); - self.handle_single_event_sync( - Event::WindowEvent { - window_id: id, - event: crate::event::WindowEvent::Resized(new_size), - }, - &mut control, - ); + let new_size = new_size.to_logical::(new_scale); + backend::set_canvas_size(canvas.borrow().raw(), new_size); } } diff --git a/src/platform_impl/web/event_loop/window_target.rs b/src/platform_impl/web/event_loop/window_target.rs index d39de98240..794a5d81a9 100644 --- a/src/platform_impl/web/event_loop/window_target.rs +++ b/src/platform_impl/web/event_loop/window_target.rs @@ -16,7 +16,6 @@ use super::{ runner, window::WindowId, }; -use crate::dpi::Size; use crate::event::{ DeviceEvent, DeviceId as RootDeviceId, ElementState, Event, KeyEvent, Touch, TouchPhase, WindowEvent, @@ -595,35 +594,6 @@ impl EventLoopWindowTarget { prevent_default, ); - // The size to restore to after exiting fullscreen. - let mut intended_size = canvas.size().get(); - - canvas.on_fullscreen_change({ - let window = self.runner.window().clone(); - let runner = self.runner.clone(); - - move || { - let canvas = canvas_clone.borrow(); - - // If the canvas is marked as fullscreen, it is moving *into* fullscreen - // If it is not, it is moving *out of* fullscreen - let new_size = if backend::is_fullscreen(&window, canvas.raw()) { - intended_size = canvas.size().get(); - - backend::window_size(&window).to_physical(backend::scale_factor(&window)) - } else { - intended_size - }; - - backend::set_canvas_size(&canvas, Size::Physical(new_size)); - runner.send_event(Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::Resized(new_size), - }); - runner.request_redraw(RootWindowId(id)); - } - }); - let runner = self.runner.clone(); canvas.on_touch_cancel(move |device_id, location, force| { runner.send_event(Event::WindowEvent { @@ -650,6 +620,16 @@ impl EventLoopWindowTarget { event: WindowEvent::ThemeChanged(theme), }); }); + + let runner = self.runner.clone(); + canvas.on_resize(move |size| { + canvas_clone.borrow().set_inner_size(size); + runner.send_event(Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::Resized(size), + }); + runner.request_redraw(RootWindowId(id)); + }); } pub fn available_monitors(&self) -> VecDequeIter { diff --git a/src/platform_impl/web/web_sys/canvas.rs b/src/platform_impl/web/web_sys/canvas.rs index 273c7c6edc..d15c141cf3 100644 --- a/src/platform_impl/web/web_sys/canvas.rs +++ b/src/platform_impl/web/web_sys/canvas.rs @@ -1,8 +1,9 @@ use super::event_handle::EventListenerHandle; use super::media_query_handle::MediaQueryListHandle; use super::pointer::PointerHandler; +use super::resize::ResizeHandle; use super::{event, ButtonsState}; -use crate::dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize}; +use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize}; use crate::error::OsError as RootOE; use crate::event::{Force, MouseButton, MouseScrollDelta}; use crate::keyboard::{Key, KeyCode, KeyLocation, ModifiersState}; @@ -29,9 +30,9 @@ pub struct Canvas { on_keyboard_release: Option>, on_keyboard_press: Option>, on_mouse_wheel: Option>, - on_fullscreen_change: Option>, on_dark_mode: Option, pointer_handler: PointerHandler, + on_resize: Option, } pub struct Common { @@ -73,22 +74,16 @@ impl Canvas { .map_err(|_| os_error!(OsError("Failed to set a tabindex".to_owned())))?; } - let size = attr - .inner_size - .unwrap_or( - LogicalSize { - width: 1024.0, - height: 768.0, - } - .into(), - ) - .to_physical(super::scale_factor(&window)); + if let Some(size) = attr.inner_size { + let size = size.to_logical(super::scale_factor(&window)); + super::set_canvas_size(&canvas, size); + } - let canvas = Canvas { + Ok(Canvas { common: Common { window, raw: canvas, - size: Rc::new(Cell::new(size)), + size: Rc::default(), wants_fullscreen: Rc::new(RefCell::new(false)), }, on_touch_start: None, @@ -98,14 +93,10 @@ impl Canvas { on_keyboard_release: None, on_keyboard_press: None, on_mouse_wheel: None, - on_fullscreen_change: None, on_dark_mode: None, pointer_handler: PointerHandler::new(), - }; - - super::set_canvas_size(&canvas, size.into()); - - Ok(canvas) + on_resize: None, + }) } pub fn set_cursor_lock(&self, lock: bool) -> Result<(), RootOE> { @@ -138,12 +129,16 @@ impl Canvas { } } - pub fn window(&self) -> &web_sys::Window { - &self.common.window + pub fn inner_size(&self) -> PhysicalSize { + self.common.size.get() } - pub fn size(&self) -> &Rc>> { - &self.common.size + pub fn set_inner_size(&self, size: PhysicalSize) { + self.common.size.set(size) + } + + pub fn window(&self) -> &web_sys::Window { + &self.common.window } pub fn raw(&self) -> &HtmlCanvasElement { @@ -329,16 +324,6 @@ impl Canvas { })); } - pub fn on_fullscreen_change(&mut self, mut handler: F) - where - F: 'static + FnMut(), - { - self.on_fullscreen_change = Some( - self.common - .add_event("fullscreenchange", move |_: Event| handler()), - ); - } - pub fn on_dark_mode(&mut self, mut handler: F) where F: 'static + FnMut(bool), @@ -350,6 +335,17 @@ impl Canvas { )); } + pub fn on_resize(&mut self, handler: F) + where + F: 'static + FnMut(PhysicalSize), + { + self.on_resize = Some(ResizeHandle::new( + self.window().clone(), + self.raw().clone(), + handler, + )); + } + pub fn request_fullscreen(&self) { self.common.request_fullscreen() } @@ -364,9 +360,9 @@ impl Canvas { self.on_keyboard_release = None; self.on_keyboard_press = None; self.on_mouse_wheel = None; - self.on_fullscreen_change = None; self.on_dark_mode = None; - self.pointer_handler.remove_listeners() + self.pointer_handler.remove_listeners(); + self.on_resize = None; } } diff --git a/src/platform_impl/web/web_sys/mod.rs b/src/platform_impl/web/web_sys/mod.rs index 4a47ebbc53..95717d7075 100644 --- a/src/platform_impl/web/web_sys/mod.rs +++ b/src/platform_impl/web/web_sys/mod.rs @@ -3,6 +3,7 @@ mod event; mod event_handle; mod media_query_handle; mod pointer; +mod resize; mod scaling; mod timeout; @@ -11,7 +12,7 @@ pub use self::event::ButtonsState; pub use self::scaling::ScaleChangeDetector; pub use self::timeout::{IdleCallback, Timeout}; -use crate::dpi::{LogicalSize, Size}; +use crate::dpi::LogicalSize; use crate::platform::web::WindowExtWebSys; use crate::window::Window; use wasm_bindgen::closure::Closure; @@ -52,38 +53,13 @@ impl WindowExtWebSys for Window { } } -pub fn window_size(window: &web_sys::Window) -> LogicalSize { - let width = window - .inner_width() - .expect("Failed to get width") - .as_f64() - .expect("Failed to get width as f64"); - let height = window - .inner_height() - .expect("Failed to get height") - .as_f64() - .expect("Failed to get height as f64"); - - LogicalSize { width, height } -} - pub fn scale_factor(window: &web_sys::Window) -> f64 { window.device_pixel_ratio() } -pub fn set_canvas_size(canvas: &Canvas, new_size: Size) { - let scale_factor = scale_factor(canvas.window()); - - let physical_size = new_size.to_physical(scale_factor); - canvas.size().set(physical_size); - - let logical_size = new_size.to_logical::(scale_factor); - set_canvas_style_property(canvas.raw(), "width", &format!("{}px", logical_size.width)); - set_canvas_style_property( - canvas.raw(), - "height", - &format!("{}px", logical_size.height), - ); +pub fn set_canvas_size(raw: &HtmlCanvasElement, new_size: LogicalSize) { + set_canvas_style_property(raw, "width", &format!("{}px", new_size.width)); + set_canvas_style_property(raw, "height", &format!("{}px", new_size.height)); } pub fn set_canvas_style_property(raw: &HtmlCanvasElement, property: &str, value: &str) { diff --git a/src/platform_impl/web/web_sys/resize.rs b/src/platform_impl/web/web_sys/resize.rs new file mode 100644 index 0000000000..ea669268dc --- /dev/null +++ b/src/platform_impl/web/web_sys/resize.rs @@ -0,0 +1,146 @@ +use js_sys::{Array, Object}; +use once_cell::unsync::Lazy; +use wasm_bindgen::prelude::{wasm_bindgen, Closure}; +use wasm_bindgen::{JsCast, JsValue}; +use web_sys::{ + HtmlCanvasElement, ResizeObserver, ResizeObserverBoxOptions, ResizeObserverEntry, + ResizeObserverOptions, ResizeObserverSize, Window, +}; + +use crate::dpi::{LogicalSize, PhysicalSize}; + +use super::super::backend; + +pub struct ResizeHandle { + observer: ResizeObserver, + _closure: Closure, +} + +impl ResizeHandle { + pub fn new(window: Window, canvas: HtmlCanvasElement, mut listener: F) -> Self + where + F: 'static + FnMut(PhysicalSize), + { + let closure = Closure::new({ + let canvas = canvas.clone(); + move |entries: Array| { + let size = Self::process_entry(&window, &canvas, entries); + + listener(size) + } + }); + let observer = ResizeObserver::new(closure.as_ref().unchecked_ref()) + .expect("Failed to create `ResizeObserver`"); + + // Safari doesn't support `devicePixelContentBoxSize` + if has_device_pixel_support() { + observer.observe_with_options( + &canvas, + ResizeObserverOptions::new().box_(ResizeObserverBoxOptions::DevicePixelContentBox), + ); + } else { + observer.observe(&canvas); + } + + Self { + observer, + _closure: closure, + } + } + + fn process_entry( + window: &Window, + canvas: &HtmlCanvasElement, + entries: Array, + ) -> PhysicalSize { + debug_assert_eq!(entries.length(), 1, "expected exactly one entry"); + let entry = entries.get(0); + debug_assert!(entry.has_type::()); + let entry: ResizeObserverEntry = entry.unchecked_into(); + + // Safari doesn't support `devicePixelContentBoxSize` + if !has_device_pixel_support() { + let rect = entry.content_rect(); + + return LogicalSize::new(rect.width(), rect.height()) + .to_physical(backend::scale_factor(window)); + } + + let entries = entry.device_pixel_content_box_size(); + debug_assert_eq!( + entries.length(), + 1, + "a canvas can't be split into multiple fragments" + ); + let entry = entries.get(0); + debug_assert!(entry.has_type::()); + let entry: ResizeObserverSize = entry.unchecked_into(); + + let style = window + .get_computed_style(canvas) + .expect("Failed to get computed style of canvas") + // this can only be empty if we provided an invalid `pseudoElt` + .expect("`getComputedStyle` can not be empty"); + + let writing_mode = style + .get_property_value("writing-mode") + .expect("`wirting-mode` is a valid CSS property"); + + // means the canvas is not inserted into the DOM + if writing_mode.is_empty() { + debug_assert_eq!(entry.inline_size(), 0.); + debug_assert_eq!(entry.block_size(), 0.); + + return PhysicalSize::new(0, 0); + } + + let horizontal = match writing_mode.as_str() { + _ if writing_mode.starts_with("horizontal") => true, + _ if writing_mode.starts_with("vertical") | writing_mode.starts_with("sideways") => { + false + } + // deprecated values + "lr" | "lr-tb" | "rl" => true, + "tb" | "tb-lr" | "tb-rl" => false, + _ => { + log::warn!("unrecognized `writing-mode`, assuming horizontal"); + true + } + }; + + if horizontal { + PhysicalSize::new(entry.inline_size() as u32, entry.block_size() as u32) + } else { + PhysicalSize::new(entry.block_size() as u32, entry.inline_size() as u32) + } + } +} + +impl Drop for ResizeHandle { + fn drop(&mut self) { + self.observer.disconnect(); + } +} + +fn has_device_pixel_support() -> bool { + thread_local! { + static DEVICE_PIXEL_SUPPORT: Lazy = Lazy::new(|| { + #[wasm_bindgen] + extern "C" { + type ResizeObserverEntryExt; + + #[wasm_bindgen(js_class = ResizeObserverEntry, static_method_of = ResizeObserverEntryExt, getter)] + fn prototype() -> Object; + } + + let prototype = ResizeObserverEntryExt::prototype(); + let descriptor = Object::get_own_property_descriptor( + &prototype, + &JsValue::from_str("devicePixelContentBoxSize"), + ); + !descriptor.is_undefined() + }); + } + + DEVICE_PIXEL_SUPPORT.with(|support| **support) +} diff --git a/src/platform_impl/web/window.rs b/src/platform_impl/web/window.rs index 9b1caefbbd..fbf45b61ec 100644 --- a/src/platform_impl/web/window.rs +++ b/src/platform_impl/web/window.rs @@ -1,6 +1,5 @@ use crate::dpi::{PhysicalPosition, PhysicalSize, Position, Size}; use crate::error::{ExternalError, NotSupportedError, OsError as RootOE}; -use crate::event; use crate::icon::Icon; use crate::window::{ CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType, @@ -31,7 +30,6 @@ pub struct Inner { canvas: Rc>, previous_pointer: RefCell<&'static str>, register_redraw_request: Box, - resize_notify_fn: Box)>, destroy_fn: Option>, } @@ -56,14 +54,6 @@ impl Window { let has_focus = Arc::new(AtomicBool::new(false)); target.register(&canvas, id, prevent_default, has_focus.clone()); - let runner = target.runner.clone(); - let resize_notify_fn = Box::new(move |new_size| { - runner.send_event(event::Event::WindowEvent { - window_id: RootWI(id), - event: event::WindowEvent::Resized(new_size), - }); - }); - let runner = target.runner.clone(); let destroy_fn = Box::new(move || runner.notify_destroy_window(RootWI(id))); @@ -75,7 +65,6 @@ impl Window { canvas, previous_pointer: RefCell::new("auto"), register_redraw_request, - resize_notify_fn, destroy_fn: Some(destroy_fn), }) .unwrap(), @@ -149,7 +138,7 @@ impl Window { #[inline] pub fn inner_size(&self) -> PhysicalSize { - self.inner.queue(|inner| inner.inner_size()) + self.inner.queue(|inner| inner.canvas.borrow().inner_size()) } #[inline] @@ -161,12 +150,8 @@ impl Window { #[inline] pub fn set_inner_size(&self, size: Size) { self.inner.dispatch(move |inner| { - let old_size = inner.inner_size(); - backend::set_canvas_size(&inner.canvas.borrow(), size); - let new_size = inner.inner_size(); - if old_size != new_size { - (inner.resize_notify_fn)(new_size); - } + let size = size.to_logical(inner.scale_factor()); + backend::set_canvas_size(inner.canvas.borrow().raw(), size); }); } @@ -442,11 +427,6 @@ impl Inner { pub fn scale_factor(&self) -> f64 { super::backend::scale_factor(&self.window) } - - #[inline] - pub fn inner_size(&self) -> PhysicalSize { - self.canvas.borrow().size().get() - } } #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] From a8dbf9637945e974686522a4c85df58c10d06ff0 Mon Sep 17 00:00:00 2001 From: dAxpeDDa Date: Fri, 9 Jun 2023 05:08:13 +0200 Subject: [PATCH 2/6] Account for `box-sizing: border-box` Co-Authored-By: Liam Murphy <43807659+Liamolucko@users.noreply.github.com> --- src/platform_impl/web/event_loop/runner.rs | 3 +- src/platform_impl/web/web_sys/canvas.rs | 2 +- src/platform_impl/web/web_sys/mod.rs | 41 ++++++++++++++++++++-- src/platform_impl/web/web_sys/resize.rs | 2 +- src/platform_impl/web/window.rs | 3 +- 5 files changed, 45 insertions(+), 6 deletions(-) diff --git a/src/platform_impl/web/event_loop/runner.rs b/src/platform_impl/web/event_loop/runner.rs index 8af0a05823..da2b841038 100644 --- a/src/platform_impl/web/event_loop/runner.rs +++ b/src/platform_impl/web/event_loop/runner.rs @@ -351,7 +351,8 @@ impl Shared { // `Resized` event will be sent by the `ResizeObserver`: if current_size != new_size { let new_size = new_size.to_logical::(new_scale); - backend::set_canvas_size(canvas.borrow().raw(), new_size); + let canvas = canvas.borrow(); + backend::set_canvas_size(canvas.window(), canvas.raw(), new_size); } } diff --git a/src/platform_impl/web/web_sys/canvas.rs b/src/platform_impl/web/web_sys/canvas.rs index d15c141cf3..59aaf054f8 100644 --- a/src/platform_impl/web/web_sys/canvas.rs +++ b/src/platform_impl/web/web_sys/canvas.rs @@ -76,7 +76,7 @@ impl Canvas { if let Some(size) = attr.inner_size { let size = size.to_logical(super::scale_factor(&window)); - super::set_canvas_size(&canvas, size); + super::set_canvas_size(&window, &canvas, size); } Ok(Canvas { diff --git a/src/platform_impl/web/web_sys/mod.rs b/src/platform_impl/web/web_sys/mod.rs index 95717d7075..0e864cabcf 100644 --- a/src/platform_impl/web/web_sys/mod.rs +++ b/src/platform_impl/web/web_sys/mod.rs @@ -16,7 +16,7 @@ use crate::dpi::LogicalSize; use crate::platform::web::WindowExtWebSys; use crate::window::Window; use wasm_bindgen::closure::Closure; -use web_sys::{Element, HtmlCanvasElement}; +use web_sys::{CssStyleDeclaration, Element, HtmlCanvasElement}; pub fn throw(msg: &str) { wasm_bindgen::throw_str(msg); @@ -57,7 +57,44 @@ pub fn scale_factor(window: &web_sys::Window) -> f64 { window.device_pixel_ratio() } -pub fn set_canvas_size(raw: &HtmlCanvasElement, new_size: LogicalSize) { +pub fn set_canvas_size( + window: &web_sys::Window, + raw: &HtmlCanvasElement, + mut new_size: LogicalSize, +) { + let document = window.document().expect("Failed to obtain document"); + + if !document.contains(Some(raw)) { + return; + } + + /// This function will panic if the element is not inserted in the DOM + /// or is not a CSS property that represents a size in pixel. + fn style_size_property(style: &CssStyleDeclaration, property: &str) -> f64 { + let prop = style + .get_property_value(property) + .expect("Found invalid property"); + prop.strip_suffix("px") + .expect("Element was not inserted into the DOM or is not a size in pixel") + .parse() + .expect("CSS property is not a size in pixel") + } + 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 style.get_property_value("box-sizing").unwrap() == "border-box" { + new_size.width += style_size_property(&style, "border-left-width") + + style_size_property(&style, "border-right-width") + + style_size_property(&style, "padding-left") + + style_size_property(&style, "padding-right"); + new_size.height += style_size_property(&style, "border-top-width") + + style_size_property(&style, "border-bottom-width") + + style_size_property(&style, "padding-top") + + style_size_property(&style, "padding-bottom"); + } + set_canvas_style_property(raw, "width", &format!("{}px", new_size.width)); set_canvas_style_property(raw, "height", &format!("{}px", new_size.height)); } diff --git a/src/platform_impl/web/web_sys/resize.rs b/src/platform_impl/web/web_sys/resize.rs index ea669268dc..fa291e8e35 100644 --- a/src/platform_impl/web/web_sys/resize.rs +++ b/src/platform_impl/web/web_sys/resize.rs @@ -103,7 +103,7 @@ impl ResizeHandle { "lr" | "lr-tb" | "rl" => true, "tb" | "tb-lr" | "tb-rl" => false, _ => { - log::warn!("unrecognized `writing-mode`, assuming horizontal"); + warn!("unrecognized `writing-mode`, assuming horizontal"); true } }; diff --git a/src/platform_impl/web/window.rs b/src/platform_impl/web/window.rs index fbf45b61ec..ee5a867b75 100644 --- a/src/platform_impl/web/window.rs +++ b/src/platform_impl/web/window.rs @@ -151,7 +151,8 @@ impl Window { pub fn set_inner_size(&self, size: Size) { self.inner.dispatch(move |inner| { let size = size.to_logical(inner.scale_factor()); - backend::set_canvas_size(inner.canvas.borrow().raw(), size); + let canvas = inner.canvas.borrow(); + backend::set_canvas_size(canvas.window(), canvas.raw(), size); }); } From 5a18ea64b1e1c7f38ee5ae534c8801e2712dd327 Mon Sep 17 00:00:00 2001 From: dAxpeDDa Date: Sat, 10 Jun 2023 00:18:13 +0200 Subject: [PATCH 3/6] Polish implementation and fix remaining bugs --- src/platform_impl/web/event_loop/mod.rs | 2 +- src/platform_impl/web/event_loop/runner.rs | 179 ++++------- .../web/event_loop/window_target.rs | 40 ++- src/platform_impl/web/mod.rs | 6 - src/platform_impl/web/web_sys/canvas.rs | 64 +++- src/platform_impl/web/web_sys/mod.rs | 5 +- src/platform_impl/web/web_sys/resize.rs | 146 --------- .../web/web_sys/resize_scaling.rs | 296 ++++++++++++++++++ src/platform_impl/web/web_sys/scaling.rs | 102 ------ src/platform_impl/web/window.rs | 2 +- 10 files changed, 441 insertions(+), 401 deletions(-) delete mode 100644 src/platform_impl/web/web_sys/resize.rs create mode 100644 src/platform_impl/web/web_sys/resize_scaling.rs delete mode 100644 src/platform_impl/web/web_sys/scaling.rs diff --git a/src/platform_impl/web/event_loop/mod.rs b/src/platform_impl/web/event_loop/mod.rs index 6bec70c972..e93a79daf2 100644 --- a/src/platform_impl/web/event_loop/mod.rs +++ b/src/platform_impl/web/event_loop/mod.rs @@ -1,5 +1,5 @@ mod proxy; -mod runner; +pub(crate) mod runner; mod state; mod window_target; diff --git a/src/platform_impl/web/event_loop/runner.rs b/src/platform_impl/web/event_loop/runner.rs index da2b841038..f255dc2f67 100644 --- a/src/platform_impl/web/event_loop/runner.rs +++ b/src/platform_impl/web/event_loop/runner.rs @@ -1,4 +1,5 @@ -use super::{super::ScaleChangeArgs, backend, state::State}; +use super::{backend, state::State}; +use crate::dpi::PhysicalSize; use crate::event::{Event, StartCause}; use crate::event_loop::ControlFlow; use crate::window::WindowId; @@ -25,13 +26,12 @@ impl Clone for Shared { pub struct Execution { runner: RefCell>, - events: RefCell>>, + events: RefCell>>, id: RefCell, window: web_sys::Window, all_canvases: RefCell>)>>, redraw_pending: RefCell>, destroy_pending: RefCell>, - scale_change_detector: RefCell>, unload_event_handle: RefCell>, } @@ -86,10 +86,31 @@ impl Runner { }) } - fn handle_single_event(&mut self, event: Event<'_, T>, control: &mut ControlFlow) { + fn handle_single_event( + &mut self, + runner: &Shared, + event: impl Into>, + control: &mut ControlFlow, + ) { let is_closed = matches!(*control, ControlFlow::ExitWithCode(_)); - (self.event_handler)(event, control); + match event.into() { + EventWrapper::Event(event) => (self.event_handler)(event, control), + EventWrapper::ScaleChange { + canvas, + size, + scale, + } => { + if let Some(canvas) = canvas.upgrade() { + canvas.borrow().handle_scale_change( + runner, + |event| (self.event_handler)(event, control), + size, + scale, + ) + } + } + } // Maintain closed state, even if the callback changes it if is_closed { @@ -109,7 +130,6 @@ impl Shared { all_canvases: RefCell::new(Vec::new()), redraw_pending: RefCell::new(HashSet::new()), destroy_pending: RefCell::new(VecDeque::new()), - scale_change_detector: RefCell::new(None), unload_event_handle: RefCell::new(None), })) } @@ -147,16 +167,6 @@ impl Shared { })); } - pub(crate) fn set_on_scale_change(&self, handler: F) - where - F: 'static + FnMut(ScaleChangeArgs), - { - *self.0.scale_change_detector.borrow_mut() = Some(backend::ScaleChangeDetector::new( - self.window().clone(), - handler, - )); - } - // Generate a strictly increasing ID // This is used to differentiate windows when handling events pub fn generate_id(&self) -> u32 { @@ -168,7 +178,7 @@ impl Shared { pub fn request_redraw(&self, id: WindowId) { self.0.redraw_pending.borrow_mut().insert(id); - self.send_events(iter::empty()); + self.send_events::>(iter::empty()); } pub fn init(&self) { @@ -196,14 +206,17 @@ impl Shared { // Add an event to the event loop runner, from the user or an event handler // // It will determine if the event should be immediately sent to the user or buffered for later - pub fn send_event(&self, event: Event<'static, T>) { + pub(crate) fn send_event>>(&self, event: E) { self.send_events(iter::once(event)); } // Add a series of events to the event loop runner // // It will determine if the event should be immediately sent to the user or buffered for later - pub fn send_events(&self, events: impl IntoIterator>) { + pub(crate) fn send_events>>( + &self, + events: impl IntoIterator, + ) { // If the event loop is closed, it should discard any new events if self.is_closed() { return; @@ -232,7 +245,10 @@ impl Shared { } if !process_immediately { // Queue these events to look at later - self.0.events.borrow_mut().extend(events); + self.0 + .events + .borrow_mut() + .extend(events.into_iter().map(Into::into)); return; } // At this point, we know this is a fresh set of events @@ -250,13 +266,13 @@ impl Shared { // Take the start event, then the events provided to this function, and run an iteration of // the event loop let start_event = Event::NewEvents(start_cause); - let events = iter::once(start_event).chain(events); + let events = + iter::once(EventWrapper::from(start_event)).chain(events.into_iter().map(Into::into)); self.run_until_cleared(events); } // Process the destroy-pending windows. This should only be called from - // `run_until_cleared` and `handle_scale_changed`, somewhere between emitting - // `NewEvents` and `MainEventsCleared`. + // `run_until_cleared`, somewhere between emitting `NewEvents` and `MainEventsCleared`. fn process_destroy_pending_windows(&self, control: &mut ControlFlow) { while let Some(id) = self.0.destroy_pending.borrow_mut().pop_front() { self.0 @@ -278,10 +294,10 @@ impl Shared { // cleared // // This will also process any events that have been queued or that are queued during processing - fn run_until_cleared(&self, events: impl Iterator>) { + fn run_until_cleared>>(&self, events: impl Iterator) { let mut control = self.current_control_flow(); for event in events { - self.handle_event(event, &mut control); + self.handle_event(event.into(), &mut control); } self.process_destroy_pending_windows(&mut control); self.handle_event(Event::MainEventsCleared, &mut control); @@ -301,80 +317,6 @@ impl Shared { } } - pub fn handle_scale_changed(&self, old_scale: f64, new_scale: f64) { - // If there aren't any windows, then there is nothing to do here. - if self.0.all_canvases.borrow().is_empty() { - return; - } - - let start_cause = match (self.0.runner.borrow().maybe_runner()) - .unwrap_or_else(|| unreachable!("`scale_changed` should not happen without a runner")) - .maybe_start_cause() - { - Some(c) => c, - // If we're in the exit state, don't do event processing - None => return, - }; - let mut control = self.current_control_flow(); - - // Handle the start event and all other events in the queue. - self.handle_event(Event::NewEvents(start_cause), &mut control); - - // It is possible for windows to be dropped before this point. We don't - // want to send `ScaleFactorChanged` for destroyed windows, so we process - // the destroy-pending windows here. - self.process_destroy_pending_windows(&mut control); - - // Now handle the `ScaleFactorChanged` events. - for &(id, ref canvas) in &*self.0.all_canvases.borrow() { - let canvas = match canvas.upgrade() { - Some(rc) => rc, - // This shouldn't happen, but just in case... - None => continue, - }; - // First, we send the `ScaleFactorChanged` event: - let current_size = canvas.borrow().inner_size(); - let logical_size = current_size.to_logical::(old_scale); - let mut new_size = logical_size.to_physical(new_scale); - self.handle_single_event_sync( - Event::WindowEvent { - window_id: id, - event: crate::event::WindowEvent::ScaleFactorChanged { - scale_factor: new_scale, - new_inner_size: &mut new_size, - }, - }, - &mut control, - ); - - // Then we resize the canvas to the new size, a new - // `Resized` event will be sent by the `ResizeObserver`: - if current_size != new_size { - let new_size = new_size.to_logical::(new_scale); - let canvas = canvas.borrow(); - backend::set_canvas_size(canvas.window(), canvas.raw(), new_size); - } - } - - // Process the destroy-pending windows again. - self.process_destroy_pending_windows(&mut control); - self.handle_event(Event::MainEventsCleared, &mut control); - - // Discard all the pending redraw as we shall just redraw all windows. - self.0.redraw_pending.borrow_mut().clear(); - for &(window_id, _) in &*self.0.all_canvases.borrow() { - self.handle_event(Event::RedrawRequested(window_id), &mut control); - } - self.handle_event(Event::RedrawEventsCleared, &mut control); - - self.apply_control_flow(control); - // If the event loop is closed, it has been closed this iteration and now the closing - // event should be emitted - if self.is_closed() { - self.handle_loop_destroyed(&mut control); - } - } - fn handle_unload(&self) { self.apply_control_flow(ControlFlow::Exit); let mut control = self.current_control_flow(); @@ -383,35 +325,20 @@ impl Shared { self.handle_event(Event::LoopDestroyed, &mut control); } - // handle_single_event_sync takes in an event and handles it synchronously. - // - // It should only ever be called from `scale_changed`. - fn handle_single_event_sync(&self, event: Event<'_, T>, control: &mut ControlFlow) { - if self.is_closed() { - *control = ControlFlow::Exit; - } - match *self.0.runner.borrow_mut() { - RunnerEnum::Running(ref mut runner) => { - runner.handle_single_event(event, control); - } - _ => panic!("Cannot handle event synchronously without a runner"), - } - } - // handle_event takes in events and either queues them or applies a callback // - // It should only ever be called from `run_until_cleared` and `scale_changed`. - fn handle_event(&self, event: Event<'static, T>, control: &mut ControlFlow) { + // It should only ever be called from `run_until_cleared`. + fn handle_event(&self, event: impl Into>, control: &mut ControlFlow) { if self.is_closed() { *control = ControlFlow::Exit; } match *self.0.runner.borrow_mut() { RunnerEnum::Running(ref mut runner) => { - runner.handle_single_event(event, control); + runner.handle_single_event(self, event, control); } // If an event is being handled without a runner somehow, add it to the event queue so // it will eventually be processed - RunnerEnum::Pending => self.0.events.borrow_mut().push_back(event), + RunnerEnum::Pending => self.0.events.borrow_mut().push_back(event.into()), // If the Runner has been destroyed, there is nothing to do. RunnerEnum::Destroyed => return, } @@ -477,7 +404,6 @@ impl Shared { fn handle_loop_destroyed(&self, control: &mut ControlFlow) { self.handle_event(Event::LoopDestroyed, control); let all_canvases = std::mem::take(&mut *self.0.all_canvases.borrow_mut()); - *self.0.scale_change_detector.borrow_mut() = None; *self.0.unload_event_handle.borrow_mut() = None; // Dropping the `Runner` drops the event handler closure, which will in // turn drop all `Window`s moved into the closure. @@ -525,3 +451,18 @@ impl Shared { } } } + +pub(crate) enum EventWrapper { + Event(Event<'static, T>), + ScaleChange { + canvas: Weak>, + size: PhysicalSize, + scale: f64, + }, +} + +impl From> for EventWrapper { + fn from(value: Event<'static, T>) -> Self { + Self::Event(value) + } +} diff --git a/src/platform_impl/web/event_loop/window_target.rs b/src/platform_impl/web/event_loop/window_target.rs index 794a5d81a9..c659a59396 100644 --- a/src/platform_impl/web/event_loop/window_target.rs +++ b/src/platform_impl/web/event_loop/window_target.rs @@ -8,6 +8,7 @@ use std::sync::Arc; use raw_window_handle::{RawDisplayHandle, WebDisplayHandle}; +use super::runner::EventWrapper; use super::{ super::{monitor::MonitorHandle, KeyEventExtra}, backend, @@ -70,10 +71,6 @@ impl EventLoopWindowTarget { pub fn run(&self, event_handler: Box>) { self.runner.set_listener(event_handler); - let runner = self.runner.clone(); - self.runner.set_on_scale_change(move |arg| { - runner.handle_scale_changed(arg.old_scale, arg.new_scale) - }); } pub fn generate_id(&self) -> WindowId { @@ -621,15 +618,32 @@ impl EventLoopWindowTarget { }); }); - let runner = self.runner.clone(); - canvas.on_resize(move |size| { - canvas_clone.borrow().set_inner_size(size); - runner.send_event(Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::Resized(size), - }); - runner.request_redraw(RootWindowId(id)); - }); + canvas.on_resize_scale( + { + let runner = self.runner.clone(); + let canvas = canvas_clone.clone(); + + move |size, scale| { + runner.send_event(EventWrapper::ScaleChange { + canvas: Rc::downgrade(&canvas), + size, + scale, + }) + } + }, + { + let runner = self.runner.clone(); + + move |size| { + RefCell::borrow(&canvas_clone).set_inner_size(size); + runner.send_event(Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::Resized(size), + }); + runner.request_redraw(RootWindowId(id)); + } + }, + ); } pub fn available_monitors(&self) -> VecDequeIter { diff --git a/src/platform_impl/web/mod.rs b/src/platform_impl/web/mod.rs index bea9f0cf1c..dc178179b4 100644 --- a/src/platform_impl/web/mod.rs +++ b/src/platform_impl/web/mod.rs @@ -39,9 +39,3 @@ pub use self::window::{PlatformSpecificWindowBuilderAttributes, Window, WindowId pub(crate) use self::keyboard::KeyEventExtra; pub(crate) use crate::icon::NoIcon as PlatformIcon; pub(self) use crate::platform_impl::Fullscreen; - -#[derive(Clone, Copy)] -pub(crate) struct ScaleChangeArgs { - old_scale: f64, - new_scale: f64, -} diff --git a/src/platform_impl/web/web_sys/canvas.rs b/src/platform_impl/web/web_sys/canvas.rs index 59aaf054f8..598e858af7 100644 --- a/src/platform_impl/web/web_sys/canvas.rs +++ b/src/platform_impl/web/web_sys/canvas.rs @@ -1,14 +1,14 @@ +use super::super::WindowId; use super::event_handle::EventListenerHandle; use super::media_query_handle::MediaQueryListHandle; use super::pointer::PointerHandler; -use super::resize::ResizeHandle; -use super::{event, ButtonsState}; +use super::{event, ButtonsState, ResizeScaleHandle}; use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize}; use crate::error::OsError as RootOE; use crate::event::{Force, MouseButton, MouseScrollDelta}; use crate::keyboard::{Key, KeyCode, KeyLocation, ModifiersState}; use crate::platform_impl::{OsError, PlatformSpecificWindowBuilderAttributes}; -use crate::window::WindowAttributes; +use crate::window::{WindowAttributes, WindowId as RootWindowId}; use std::cell::{Cell, RefCell}; use std::rc::Rc; @@ -23,6 +23,7 @@ use web_sys::{Event, FocusEvent, HtmlCanvasElement, KeyboardEvent, WheelEvent}; #[allow(dead_code)] pub struct Canvas { common: Common, + id: WindowId, on_touch_start: Option>, on_touch_end: Option>, on_focus: Option>, @@ -32,7 +33,7 @@ pub struct Canvas { on_mouse_wheel: Option>, on_dark_mode: Option, pointer_handler: PointerHandler, - on_resize: Option, + on_resize_scale: Option, } pub struct Common { @@ -45,6 +46,7 @@ pub struct Common { impl Canvas { pub fn create( + id: WindowId, window: web_sys::Window, attr: &WindowAttributes, platform_attr: PlatformSpecificWindowBuilderAttributes, @@ -86,6 +88,7 @@ impl Canvas { size: Rc::default(), wants_fullscreen: Rc::new(RefCell::new(false)), }, + id, on_touch_start: None, on_touch_end: None, on_blur: None, @@ -95,7 +98,7 @@ impl Canvas { on_mouse_wheel: None, on_dark_mode: None, pointer_handler: PointerHandler::new(), - on_resize: None, + on_resize_scale: None, }) } @@ -335,14 +338,16 @@ impl Canvas { )); } - pub fn on_resize(&mut self, handler: F) + pub(crate) fn on_resize_scale(&mut self, scale_handler: S, size_handler: R) where - F: 'static + FnMut(PhysicalSize), + S: 'static + FnMut(PhysicalSize, f64), + R: 'static + FnMut(PhysicalSize), { - self.on_resize = Some(ResizeHandle::new( + self.on_resize_scale = Some(ResizeScaleHandle::new( self.window().clone(), self.raw().clone(), - handler, + scale_handler, + size_handler, )); } @@ -354,6 +359,45 @@ impl Canvas { self.common.is_fullscreen() } + pub(crate) fn handle_scale_change( + &self, + runner: &super::super::event_loop::runner::Shared, + event_handler: impl FnOnce(crate::event::Event<'_, T>), + current_size: PhysicalSize, + scale: f64, + ) { + // First, we send the `ScaleFactorChanged` event: + let old_size = self.inner_size(); + self.set_inner_size(current_size); + let mut new_size = current_size; + event_handler(crate::event::Event::WindowEvent { + window_id: RootWindowId(self.id), + event: crate::event::WindowEvent::ScaleFactorChanged { + scale_factor: scale, + new_inner_size: &mut new_size, + }, + }); + + if current_size != new_size { + // Then we resize the canvas to the new size, a new + // `Resized` event will be sent by the `ResizeObserver`: + let new_size = new_size.to_logical(scale); + super::set_canvas_size(self.window(), self.raw(), new_size); + + // Set the size might not trigger the event because the calculation is inaccurate. + self.on_resize_scale + .as_ref() + .expect("expected Window to still be active") + .create_oneshot_resize(); + } else if old_size != new_size { + // Then we at least send a resized event. + runner.send_event(crate::event::Event::WindowEvent { + window_id: RootWindowId(self.id), + event: crate::event::WindowEvent::Resized(new_size), + }) + } + } + pub fn remove_listeners(&mut self) { self.on_focus = None; self.on_blur = None; @@ -362,7 +406,7 @@ impl Canvas { self.on_mouse_wheel = None; self.on_dark_mode = None; self.pointer_handler.remove_listeners(); - self.on_resize = None; + self.on_resize_scale = None; } } diff --git a/src/platform_impl/web/web_sys/mod.rs b/src/platform_impl/web/web_sys/mod.rs index 0e864cabcf..d188319063 100644 --- a/src/platform_impl/web/web_sys/mod.rs +++ b/src/platform_impl/web/web_sys/mod.rs @@ -3,13 +3,12 @@ mod event; mod event_handle; mod media_query_handle; mod pointer; -mod resize; -mod scaling; +mod resize_scaling; mod timeout; pub use self::canvas::Canvas; pub use self::event::ButtonsState; -pub use self::scaling::ScaleChangeDetector; +pub use self::resize_scaling::ResizeScaleHandle; pub use self::timeout::{IdleCallback, Timeout}; use crate::dpi::LogicalSize; diff --git a/src/platform_impl/web/web_sys/resize.rs b/src/platform_impl/web/web_sys/resize.rs deleted file mode 100644 index fa291e8e35..0000000000 --- a/src/platform_impl/web/web_sys/resize.rs +++ /dev/null @@ -1,146 +0,0 @@ -use js_sys::{Array, Object}; -use once_cell::unsync::Lazy; -use wasm_bindgen::prelude::{wasm_bindgen, Closure}; -use wasm_bindgen::{JsCast, JsValue}; -use web_sys::{ - HtmlCanvasElement, ResizeObserver, ResizeObserverBoxOptions, ResizeObserverEntry, - ResizeObserverOptions, ResizeObserverSize, Window, -}; - -use crate::dpi::{LogicalSize, PhysicalSize}; - -use super::super::backend; - -pub struct ResizeHandle { - observer: ResizeObserver, - _closure: Closure, -} - -impl ResizeHandle { - pub fn new(window: Window, canvas: HtmlCanvasElement, mut listener: F) -> Self - where - F: 'static + FnMut(PhysicalSize), - { - let closure = Closure::new({ - let canvas = canvas.clone(); - move |entries: Array| { - let size = Self::process_entry(&window, &canvas, entries); - - listener(size) - } - }); - let observer = ResizeObserver::new(closure.as_ref().unchecked_ref()) - .expect("Failed to create `ResizeObserver`"); - - // Safari doesn't support `devicePixelContentBoxSize` - if has_device_pixel_support() { - observer.observe_with_options( - &canvas, - ResizeObserverOptions::new().box_(ResizeObserverBoxOptions::DevicePixelContentBox), - ); - } else { - observer.observe(&canvas); - } - - Self { - observer, - _closure: closure, - } - } - - fn process_entry( - window: &Window, - canvas: &HtmlCanvasElement, - entries: Array, - ) -> PhysicalSize { - debug_assert_eq!(entries.length(), 1, "expected exactly one entry"); - let entry = entries.get(0); - debug_assert!(entry.has_type::()); - let entry: ResizeObserverEntry = entry.unchecked_into(); - - // Safari doesn't support `devicePixelContentBoxSize` - if !has_device_pixel_support() { - let rect = entry.content_rect(); - - return LogicalSize::new(rect.width(), rect.height()) - .to_physical(backend::scale_factor(window)); - } - - let entries = entry.device_pixel_content_box_size(); - debug_assert_eq!( - entries.length(), - 1, - "a canvas can't be split into multiple fragments" - ); - let entry = entries.get(0); - debug_assert!(entry.has_type::()); - let entry: ResizeObserverSize = entry.unchecked_into(); - - let style = window - .get_computed_style(canvas) - .expect("Failed to get computed style of canvas") - // this can only be empty if we provided an invalid `pseudoElt` - .expect("`getComputedStyle` can not be empty"); - - let writing_mode = style - .get_property_value("writing-mode") - .expect("`wirting-mode` is a valid CSS property"); - - // means the canvas is not inserted into the DOM - if writing_mode.is_empty() { - debug_assert_eq!(entry.inline_size(), 0.); - debug_assert_eq!(entry.block_size(), 0.); - - return PhysicalSize::new(0, 0); - } - - let horizontal = match writing_mode.as_str() { - _ if writing_mode.starts_with("horizontal") => true, - _ if writing_mode.starts_with("vertical") | writing_mode.starts_with("sideways") => { - false - } - // deprecated values - "lr" | "lr-tb" | "rl" => true, - "tb" | "tb-lr" | "tb-rl" => false, - _ => { - warn!("unrecognized `writing-mode`, assuming horizontal"); - true - } - }; - - if horizontal { - PhysicalSize::new(entry.inline_size() as u32, entry.block_size() as u32) - } else { - PhysicalSize::new(entry.block_size() as u32, entry.inline_size() as u32) - } - } -} - -impl Drop for ResizeHandle { - fn drop(&mut self) { - self.observer.disconnect(); - } -} - -fn has_device_pixel_support() -> bool { - thread_local! { - static DEVICE_PIXEL_SUPPORT: Lazy = Lazy::new(|| { - #[wasm_bindgen] - extern "C" { - type ResizeObserverEntryExt; - - #[wasm_bindgen(js_class = ResizeObserverEntry, static_method_of = ResizeObserverEntryExt, getter)] - fn prototype() -> Object; - } - - let prototype = ResizeObserverEntryExt::prototype(); - let descriptor = Object::get_own_property_descriptor( - &prototype, - &JsValue::from_str("devicePixelContentBoxSize"), - ); - !descriptor.is_undefined() - }); - } - - DEVICE_PIXEL_SUPPORT.with(|support| **support) -} diff --git a/src/platform_impl/web/web_sys/resize_scaling.rs b/src/platform_impl/web/web_sys/resize_scaling.rs new file mode 100644 index 0000000000..97851453c8 --- /dev/null +++ b/src/platform_impl/web/web_sys/resize_scaling.rs @@ -0,0 +1,296 @@ +use js_sys::{Array, Object}; +use once_cell::unsync::Lazy; +use wasm_bindgen::prelude::{wasm_bindgen, Closure}; +use wasm_bindgen::{JsCast, JsValue}; +use web_sys::{ + HtmlCanvasElement, MediaQueryList, ResizeObserver, ResizeObserverBoxOptions, + ResizeObserverEntry, ResizeObserverOptions, ResizeObserverSize, Window, +}; + +use crate::dpi::{LogicalSize, PhysicalSize}; + +use super::super::backend; +use super::media_query_handle::MediaQueryListHandle; + +use std::cell::{Cell, RefCell}; +use std::rc::{Rc, Weak}; + +pub struct ResizeScaleHandle(Rc>); + +impl ResizeScaleHandle { + pub(crate) fn new( + window: Window, + canvas: HtmlCanvasElement, + scale_handler: S, + resize_handler: R, + ) -> Self + where + S: 'static + FnMut(PhysicalSize, f64), + R: 'static + FnMut(PhysicalSize), + { + Self(ResizeScaleInternal::new( + window, + canvas, + scale_handler, + resize_handler, + )) + } + + pub(crate) fn create_oneshot_resize(&self) { + self.0 + .borrow() + .create_oneshot_observer(Rc::downgrade(&self.0), |this, size| { + (this.resize_handler)(size) + }) + } +} + +/// This is a helper type to help manage the `MediaQueryList` used for detecting +/// changes of the `devicePixelRatio`. +struct ResizeScaleInternal { + window: Window, + canvas: HtmlCanvasElement, + mql: MediaQueryListHandle, + observer: ResizeObserver, + _observer_closure: Closure, + scale_handler: Box, f64)>, + resize_handler: Box)>, + resize_enabled: Cell, +} + +impl ResizeScaleInternal { + fn new( + window: Window, + canvas: HtmlCanvasElement, + scale_handler: S, + resize_handler: R, + ) -> Rc> + where + S: 'static + FnMut(PhysicalSize, f64), + R: 'static + FnMut(PhysicalSize), + { + Rc::>::new_cyclic(|weak_self| { + let mql = Self::create_mql(&window, { + let weak_self = weak_self.clone(); + move |mql| { + if let Some(rc_self) = weak_self.upgrade() { + Self::handle_scale(rc_self, mql); + } + } + }); + + let weak_self = weak_self.clone(); + let observer_closure = Closure::new(move |entries: Array, _| { + if let Some(rc_self) = weak_self.upgrade() { + let mut this = rc_self.borrow_mut(); + + if this.resize_enabled.get() { + let size = Self::process_entry(&this.window, &this.canvas, entries); + (this.resize_handler)(size) + } + } + }); + let observer = Self::create_observer(&canvas, observer_closure.as_ref()); + + RefCell::new(Self { + window, + canvas, + mql, + observer, + _observer_closure: observer_closure, + scale_handler: Box::new(scale_handler), + resize_handler: Box::new(resize_handler), + resize_enabled: Cell::new(true), + }) + }) + } + + fn create_mql(window: &Window, closure: F) -> MediaQueryListHandle + where + F: 'static + FnMut(&MediaQueryList), + { + let current_scale = super::scale_factor(window); + // TODO: Remove `-webkit-device-pixel-ratio`. Requires Safari v16. + let media_query = format!( + "(resolution: {current_scale}dppx), + (-webkit-device-pixel-ratio: {current_scale})", + ); + let mql = MediaQueryListHandle::new(window, &media_query, closure); + assert!( + mql.mql().matches(), + "created media query doesn't match, {current_scale} != {}", + super::scale_factor(window) + ); + mql + } + + fn create_observer(canvas: &HtmlCanvasElement, closure: &JsValue) -> ResizeObserver { + let observer = ResizeObserver::new(closure.as_ref().unchecked_ref()) + .expect("Failed to create `ResizeObserver`"); + + // Safari doesn't support `devicePixelContentBoxSize` + if has_device_pixel_support() { + observer.observe_with_options( + canvas, + ResizeObserverOptions::new().box_(ResizeObserverBoxOptions::DevicePixelContentBox), + ); + } else { + observer.observe(canvas); + } + + observer + } + + fn create_oneshot_observer( + &self, + weak_self: Weak>, + handler: impl 'static + FnOnce(&mut Self, PhysicalSize), + ) { + self.resize_enabled.set(false); + + let observer_closure = + Closure::once_into_js(move |entries: Array, observer: ResizeObserver| { + if let Some(rc_self) = weak_self.upgrade() { + let mut this = rc_self.borrow_mut(); + let size = Self::process_entry(&this.window, &this.canvas, entries); + this.resize_enabled.set(true); + + handler(&mut this, size) + } + + observer.disconnect(); + }); + Self::create_observer(&self.canvas, &observer_closure); + } + + fn handle_scale(this: Rc>, mql: &MediaQueryList) { + let weak_self = Rc::downgrade(&this); + let mut this = this.borrow_mut(); + let scale = super::scale_factor(&this.window); + + // TODO: confirm/reproduce this problem, see: + // . + // This should never happen, but if it does then apparently the scale factor didn't change. + if mql.matches() { + warn!( + "media query tracking scale factor was triggered without a change:\n\ + Media Query: {}\n\ + Current Scale: {scale}", + mql.media(), + ); + return; + } + + let new_mql = Self::create_mql(&this.window, { + let weak_self = weak_self.clone(); + move |mql| { + if let Some(rc_self) = weak_self.upgrade() { + Self::handle_scale(rc_self, mql); + } + } + }); + this.mql = new_mql; + + this.create_oneshot_observer(weak_self, move |this, size| { + (this.scale_handler)(size, scale) + }); + } + + fn process_entry( + window: &Window, + canvas: &HtmlCanvasElement, + entries: Array, + ) -> PhysicalSize { + debug_assert_eq!(entries.length(), 1, "expected exactly one entry"); + let entry = entries.get(0); + debug_assert!(entry.has_type::()); + let entry: ResizeObserverEntry = entry.unchecked_into(); + + // Safari doesn't support `devicePixelContentBoxSize` + if !has_device_pixel_support() { + let rect = entry.content_rect(); + + return LogicalSize::new(rect.width(), rect.height()) + .to_physical(backend::scale_factor(window)); + } + + let entries = entry.device_pixel_content_box_size(); + debug_assert_eq!( + entries.length(), + 1, + "a canvas can't be split into multiple fragments" + ); + let entry = entries.get(0); + debug_assert!(entry.has_type::()); + let entry: ResizeObserverSize = entry.unchecked_into(); + + let style = window + .get_computed_style(canvas) + .expect("Failed to get computed style of canvas") + // this can only be empty if we provided an invalid `pseudoElt` + .expect("`getComputedStyle` can not be empty"); + + let writing_mode = style + .get_property_value("writing-mode") + .expect("`wirting-mode` is a valid CSS property"); + + // means the canvas is not inserted into the DOM + if writing_mode.is_empty() { + debug_assert_eq!(entry.inline_size(), 0.); + debug_assert_eq!(entry.block_size(), 0.); + + return PhysicalSize::new(0, 0); + } + + let horizontal = match writing_mode.as_str() { + _ if writing_mode.starts_with("horizontal") => true, + _ if writing_mode.starts_with("vertical") | writing_mode.starts_with("sideways") => { + false + } + // deprecated values + "lr" | "lr-tb" | "rl" => true, + "tb" | "tb-lr" | "tb-rl" => false, + _ => { + warn!("unrecognized `writing-mode`, assuming horizontal"); + true + } + }; + + if horizontal { + PhysicalSize::new(entry.inline_size() as u32, entry.block_size() as u32) + } else { + PhysicalSize::new(entry.block_size() as u32, entry.inline_size() as u32) + } + } +} + +impl Drop for ResizeScaleInternal { + fn drop(&mut self) { + self.observer.disconnect(); + } +} + +// TODO: Remove when Safari supports `devicePixelContentBoxSize`. +// See . +pub fn has_device_pixel_support() -> bool { + thread_local! { + static DEVICE_PIXEL_SUPPORT: Lazy = Lazy::new(|| { + #[wasm_bindgen] + extern "C" { + type ResizeObserverEntryExt; + + #[wasm_bindgen(js_class = ResizeObserverEntry, static_method_of = ResizeObserverEntryExt, getter)] + fn prototype() -> Object; + } + + let prototype = ResizeObserverEntryExt::prototype(); + let descriptor = Object::get_own_property_descriptor( + &prototype, + &JsValue::from_str("devicePixelContentBoxSize"), + ); + !descriptor.is_undefined() + }); + } + + DEVICE_PIXEL_SUPPORT.with(|support| **support) +} diff --git a/src/platform_impl/web/web_sys/scaling.rs b/src/platform_impl/web/web_sys/scaling.rs deleted file mode 100644 index 1c13e1b04e..0000000000 --- a/src/platform_impl/web/web_sys/scaling.rs +++ /dev/null @@ -1,102 +0,0 @@ -use web_sys::MediaQueryList; - -use super::super::ScaleChangeArgs; -use super::media_query_handle::MediaQueryListHandle; - -use std::{cell::RefCell, rc::Rc}; - -pub struct ScaleChangeDetector(Rc>); - -impl ScaleChangeDetector { - pub(crate) fn new(window: web_sys::Window, handler: F) -> Self - where - F: 'static + FnMut(ScaleChangeArgs), - { - Self(ScaleChangeDetectorInternal::new(window, handler)) - } -} - -/// This is a helper type to help manage the `MediaQueryList` used for detecting -/// changes of the `devicePixelRatio`. -struct ScaleChangeDetectorInternal { - window: web_sys::Window, - callback: Box, - mql: MediaQueryListHandle, - last_scale: f64, -} - -impl ScaleChangeDetectorInternal { - fn new(window: web_sys::Window, handler: F) -> Rc> - where - F: 'static + FnMut(ScaleChangeArgs), - { - let current_scale = super::scale_factor(&window); - Rc::new_cyclic(|weak_self| { - let weak_self = weak_self.clone(); - let mql = Self::create_mql(&window, move |mql| { - if let Some(rc_self) = weak_self.upgrade() { - Self::handler(rc_self, mql); - } - }); - - RefCell::new(Self { - window, - callback: Box::new(handler), - mql, - last_scale: current_scale, - }) - }) - } - - fn create_mql(window: &web_sys::Window, closure: F) -> MediaQueryListHandle - where - F: 'static + FnMut(&MediaQueryList), - { - let current_scale = super::scale_factor(window); - // TODO: Remove `-webkit-device-pixel-ratio`. Requires Safari v16. - let media_query = format!( - "(resolution: {current_scale}dppx), - (-webkit-device-pixel-ratio: {current_scale})", - ); - let mql = MediaQueryListHandle::new(window, &media_query, closure); - assert!( - mql.mql().matches(), - "created media query doesn't match, {current_scale} != {}", - super::scale_factor(window) - ); - mql - } - - fn handler(this: Rc>, mql: &MediaQueryList) { - let weak_self = Rc::downgrade(&this); - let mut this = this.borrow_mut(); - let old_scale = this.last_scale; - let new_scale = super::scale_factor(&this.window); - - // TODO: confirm/reproduce this problem, see: - // . - // This should never happen, but if it does then apparently the scale factor didn't change. - if mql.matches() { - warn!( - "media query tracking scale factor was triggered without a change:\n\ - Media Query: {}\n\ - Current Scale: {new_scale}", - mql.media(), - ); - return; - } - - (this.callback)(ScaleChangeArgs { - old_scale, - new_scale, - }); - - let new_mql = Self::create_mql(&this.window, move |mql| { - if let Some(rc_self) = weak_self.upgrade() { - Self::handler(rc_self, mql); - } - }); - this.mql = new_mql; - this.last_scale = new_scale; - } -} diff --git a/src/platform_impl/web/window.rs b/src/platform_impl/web/window.rs index ee5a867b75..2557054a55 100644 --- a/src/platform_impl/web/window.rs +++ b/src/platform_impl/web/window.rs @@ -46,7 +46,7 @@ impl Window { let prevent_default = platform_attr.prevent_default; let window = target.runner.window(); - let canvas = backend::Canvas::create(window.clone(), &attr, platform_attr)?; + let canvas = backend::Canvas::create(id, window.clone(), &attr, platform_attr)?; let canvas = Rc::new(RefCell::new(canvas)); let register_redraw_request = Box::new(move || runner.request_redraw(RootWI(id))); From 34b09ec5eed5d9b83905e79627ed74e4c7ac501b Mon Sep 17 00:00:00 2001 From: dAxpeDDa Date: Sat, 10 Jun 2023 00:20:59 +0200 Subject: [PATCH 4/6] Remove unnecessary `debug_assert`s --- .../web/web_sys/resize_scaling.rs | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/src/platform_impl/web/web_sys/resize_scaling.rs b/src/platform_impl/web/web_sys/resize_scaling.rs index 97851453c8..3522a7338d 100644 --- a/src/platform_impl/web/web_sys/resize_scaling.rs +++ b/src/platform_impl/web/web_sys/resize_scaling.rs @@ -201,10 +201,7 @@ impl ResizeScaleInternal { canvas: &HtmlCanvasElement, entries: Array, ) -> PhysicalSize { - debug_assert_eq!(entries.length(), 1, "expected exactly one entry"); - let entry = entries.get(0); - debug_assert!(entry.has_type::()); - let entry: ResizeObserverEntry = entry.unchecked_into(); + let entry: ResizeObserverEntry = entries.get(0).unchecked_into(); // Safari doesn't support `devicePixelContentBoxSize` if !has_device_pixel_support() { @@ -214,15 +211,10 @@ impl ResizeScaleInternal { .to_physical(backend::scale_factor(window)); } - let entries = entry.device_pixel_content_box_size(); - debug_assert_eq!( - entries.length(), - 1, - "a canvas can't be split into multiple fragments" - ); - let entry = entries.get(0); - debug_assert!(entry.has_type::()); - let entry: ResizeObserverSize = entry.unchecked_into(); + let entry: ResizeObserverSize = entry + .device_pixel_content_box_size() + .get(0) + .unchecked_into(); let style = window .get_computed_style(canvas) From 486fd455abcdf1fdf0d8e00c98866199900eddd5 Mon Sep 17 00:00:00 2001 From: dAxpeDDa Date: Sun, 11 Jun 2023 17:49:54 +0200 Subject: [PATCH 5/6] Account for no initial `ResizeObserver` event on Safari --- .../web/event_loop/window_target.rs | 18 +-- src/platform_impl/web/web_sys/canvas.rs | 28 +++-- src/platform_impl/web/web_sys/mod.rs | 23 ++-- .../web/web_sys/resize_scaling.rs | 105 ++++++++++++------ 4 files changed, 110 insertions(+), 64 deletions(-) diff --git a/src/platform_impl/web/event_loop/window_target.rs b/src/platform_impl/web/event_loop/window_target.rs index c659a59396..5f477d5c87 100644 --- a/src/platform_impl/web/event_loop/window_target.rs +++ b/src/platform_impl/web/event_loop/window_target.rs @@ -634,13 +634,17 @@ impl EventLoopWindowTarget { { let runner = self.runner.clone(); - move |size| { - RefCell::borrow(&canvas_clone).set_inner_size(size); - runner.send_event(Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::Resized(size), - }); - runner.request_redraw(RootWindowId(id)); + move |new_size| { + let canvas = RefCell::borrow(&canvas_clone); + canvas.set_current_size(new_size); + if canvas.old_size() != new_size { + canvas.set_old_size(new_size); + runner.send_event(Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::Resized(new_size), + }); + runner.request_redraw(RootWindowId(id)); + } } }, ); diff --git a/src/platform_impl/web/web_sys/canvas.rs b/src/platform_impl/web/web_sys/canvas.rs index 598e858af7..9138d4052c 100644 --- a/src/platform_impl/web/web_sys/canvas.rs +++ b/src/platform_impl/web/web_sys/canvas.rs @@ -40,7 +40,8 @@ pub struct Common { pub window: web_sys::Window, /// Note: resizing the HTMLCanvasElement should go through `backend::set_canvas_size` to ensure the DPI factor is maintained. pub raw: HtmlCanvasElement, - size: Rc>>, + old_size: Rc>>, + current_size: Rc>>, wants_fullscreen: Rc>, } @@ -85,7 +86,8 @@ impl Canvas { common: Common { window, raw: canvas, - size: Rc::default(), + old_size: Rc::default(), + current_size: Rc::default(), wants_fullscreen: Rc::new(RefCell::new(false)), }, id, @@ -132,12 +134,20 @@ impl Canvas { } } + pub fn old_size(&self) -> PhysicalSize { + self.common.old_size.get() + } + pub fn inner_size(&self) -> PhysicalSize { - self.common.size.get() + self.common.current_size.get() + } + + pub fn set_old_size(&self, size: PhysicalSize) { + self.common.old_size.set(size) } - pub fn set_inner_size(&self, size: PhysicalSize) { - self.common.size.set(size) + pub fn set_current_size(&self, size: PhysicalSize) { + self.common.current_size.set(size) } pub fn window(&self) -> &web_sys::Window { @@ -367,8 +377,7 @@ impl Canvas { scale: f64, ) { // First, we send the `ScaleFactorChanged` event: - let old_size = self.inner_size(); - self.set_inner_size(current_size); + self.set_current_size(current_size); let mut new_size = current_size; event_handler(crate::event::Event::WindowEvent { window_id: RootWindowId(self.id), @@ -388,9 +397,10 @@ impl Canvas { self.on_resize_scale .as_ref() .expect("expected Window to still be active") - .create_oneshot_resize(); - } else if old_size != new_size { + .notify_resize(); + } else if self.old_size() != new_size { // Then we at least send a resized event. + self.set_old_size(new_size); runner.send_event(crate::event::Event::WindowEvent { window_id: RootWindowId(self.id), event: crate::event::WindowEvent::Resized(new_size), diff --git a/src/platform_impl/web/web_sys/mod.rs b/src/platform_impl/web/web_sys/mod.rs index d188319063..cb928c27a7 100644 --- a/src/platform_impl/web/web_sys/mod.rs +++ b/src/platform_impl/web/web_sys/mod.rs @@ -67,17 +67,6 @@ pub fn set_canvas_size( return; } - /// This function will panic if the element is not inserted in the DOM - /// or is not a CSS property that represents a size in pixel. - fn style_size_property(style: &CssStyleDeclaration, property: &str) -> f64 { - let prop = style - .get_property_value(property) - .expect("Found invalid property"); - prop.strip_suffix("px") - .expect("Element was not inserted into the DOM or is not a size in pixel") - .parse() - .expect("CSS property is not a size in pixel") - } let style = window .get_computed_style(raw) .expect("Failed to obtain computed style") @@ -98,6 +87,18 @@ pub fn set_canvas_size( set_canvas_style_property(raw, "height", &format!("{}px", new_size.height)); } +/// This function will panic if the element is not inserted in the DOM +/// or is not a CSS property that represents a size in pixel. +pub fn style_size_property(style: &CssStyleDeclaration, property: &str) -> f64 { + let prop = style + .get_property_value(property) + .expect("Found invalid property"); + prop.strip_suffix("px") + .expect("Element was not inserted into the DOM or is not a size in pixel") + .parse() + .expect("CSS property is not a size in pixel") +} + pub fn set_canvas_style_property(raw: &HtmlCanvasElement, property: &str, value: &str) { let style = raw.style(); style diff --git a/src/platform_impl/web/web_sys/resize_scaling.rs b/src/platform_impl/web/web_sys/resize_scaling.rs index 3522a7338d..fdfe0645c9 100644 --- a/src/platform_impl/web/web_sys/resize_scaling.rs +++ b/src/platform_impl/web/web_sys/resize_scaling.rs @@ -13,7 +13,7 @@ use super::super::backend; use super::media_query_handle::MediaQueryListHandle; use std::cell::{Cell, RefCell}; -use std::rc::{Rc, Weak}; +use std::rc::Rc; pub struct ResizeScaleHandle(Rc>); @@ -36,12 +36,8 @@ impl ResizeScaleHandle { )) } - pub(crate) fn create_oneshot_resize(&self) { - self.0 - .borrow() - .create_oneshot_observer(Rc::downgrade(&self.0), |this, size| { - (this.resize_handler)(size) - }) + pub(crate) fn notify_resize(&self) { + self.0.borrow_mut().notify() } } @@ -55,7 +51,7 @@ struct ResizeScaleInternal { _observer_closure: Closure, scale_handler: Box, f64)>, resize_handler: Box)>, - resize_enabled: Cell, + notify_scale: Cell, } impl ResizeScaleInternal { @@ -84,8 +80,12 @@ impl ResizeScaleInternal { if let Some(rc_self) = weak_self.upgrade() { let mut this = rc_self.borrow_mut(); - if this.resize_enabled.get() { - let size = Self::process_entry(&this.window, &this.canvas, entries); + let size = Self::process_entry(&this.window, &this.canvas, entries); + + if this.notify_scale.replace(false) { + let scale = backend::scale_factor(&this.window); + (this.scale_handler)(size, scale) + } else { (this.resize_handler)(size) } } @@ -100,7 +100,7 @@ impl ResizeScaleInternal { _observer_closure: observer_closure, scale_handler: Box::new(scale_handler), resize_handler: Box::new(resize_handler), - resize_enabled: Cell::new(true), + notify_scale: Cell::new(false), }) }) } @@ -141,26 +141,61 @@ impl ResizeScaleInternal { observer } - fn create_oneshot_observer( - &self, - weak_self: Weak>, - handler: impl 'static + FnOnce(&mut Self, PhysicalSize), - ) { - self.resize_enabled.set(false); + fn notify(&mut self) { + let document = self.window.document().expect("Failed to obtain document"); - let observer_closure = - Closure::once_into_js(move |entries: Array, observer: ResizeObserver| { - if let Some(rc_self) = weak_self.upgrade() { - let mut this = rc_self.borrow_mut(); - let size = Self::process_entry(&this.window, &this.canvas, entries); - this.resize_enabled.set(true); + if !document.contains(Some(&self.canvas)) { + let size = PhysicalSize::new(0, 0); - handler(&mut this, size) - } + if self.notify_scale.replace(false) { + let scale = backend::scale_factor(&self.window); + (self.scale_handler)(size, scale) + } else { + (self.resize_handler)(size) + } - observer.disconnect(); - }); - Self::create_observer(&self.canvas, &observer_closure); + return; + } + + // Safari doesn't support `devicePixelContentBoxSize` + if has_device_pixel_support() { + self.observer.unobserve(&self.canvas); + self.observer.observe(&self.canvas); + + return; + } + + let style = self + .window + .get_computed_style(&self.canvas) + .expect("Failed to obtain computed style") + // this can't fail: we aren't using a pseudo-element + .expect("Invalid pseudo-element"); + + let mut size = LogicalSize::new( + backend::style_size_property(&style, "width"), + backend::style_size_property(&style, "height"), + ); + + if style.get_property_value("box-sizing").unwrap() == "border-box" { + size.width -= backend::style_size_property(&style, "border-left-width") + + backend::style_size_property(&style, "border-right-width") + + backend::style_size_property(&style, "padding-left") + + backend::style_size_property(&style, "padding-right"); + size.height -= backend::style_size_property(&style, "border-top-width") + + backend::style_size_property(&style, "border-bottom-width") + + backend::style_size_property(&style, "padding-top") + + backend::style_size_property(&style, "padding-bottom"); + } + + let size = size.to_physical(backend::scale_factor(&self.window)); + + if self.notify_scale.replace(false) { + let scale = backend::scale_factor(&self.window); + (self.scale_handler)(size, scale) + } else { + (self.resize_handler)(size) + } } fn handle_scale(this: Rc>, mql: &MediaQueryList) { @@ -181,19 +216,15 @@ impl ResizeScaleInternal { return; } - let new_mql = Self::create_mql(&this.window, { - let weak_self = weak_self.clone(); - move |mql| { - if let Some(rc_self) = weak_self.upgrade() { - Self::handle_scale(rc_self, mql); - } + let new_mql = Self::create_mql(&this.window, move |mql| { + if let Some(rc_self) = weak_self.upgrade() { + Self::handle_scale(rc_self, mql); } }); this.mql = new_mql; - this.create_oneshot_observer(weak_self, move |this, size| { - (this.scale_handler)(size, scale) - }); + this.notify_scale.set(true); + this.notify(); } fn process_entry( From e2004eb3b74ff41036a3ccde87d92c0f79087162 Mon Sep 17 00:00:00 2001 From: dAxpeDDa Date: Tue, 13 Jun 2023 10:41:57 +0200 Subject: [PATCH 6/6] Fix interaction with `display: none` --- src/platform_impl/web/web_sys/mod.rs | 9 +++++---- .../web/web_sys/resize_scaling.rs | 18 ++++++++++-------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/platform_impl/web/web_sys/mod.rs b/src/platform_impl/web/web_sys/mod.rs index cb928c27a7..8f6cf630da 100644 --- a/src/platform_impl/web/web_sys/mod.rs +++ b/src/platform_impl/web/web_sys/mod.rs @@ -63,15 +63,16 @@ 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" { + return; + } + if style.get_property_value("box-sizing").unwrap() == "border-box" { new_size.width += style_size_property(&style, "border-left-width") + style_size_property(&style, "border-right-width") diff --git a/src/platform_impl/web/web_sys/resize_scaling.rs b/src/platform_impl/web/web_sys/resize_scaling.rs index fdfe0645c9..43b0faa75c 100644 --- a/src/platform_impl/web/web_sys/resize_scaling.rs +++ b/src/platform_impl/web/web_sys/resize_scaling.rs @@ -142,9 +142,18 @@ impl ResizeScaleInternal { } fn notify(&mut self) { + let style = self + .window + .get_computed_style(&self.canvas) + .expect("Failed to obtain computed style") + // this can't fail: we aren't using a pseudo-element + .expect("Invalid pseudo-element"); + let document = self.window.document().expect("Failed to obtain document"); - if !document.contains(Some(&self.canvas)) { + if !document.contains(Some(&self.canvas)) + || style.get_property_value("display").unwrap() == "none" + { let size = PhysicalSize::new(0, 0); if self.notify_scale.replace(false) { @@ -165,13 +174,6 @@ impl ResizeScaleInternal { return; } - let style = self - .window - .get_computed_style(&self.canvas) - .expect("Failed to obtain computed style") - // this can't fail: we aren't using a pseudo-element - .expect("Invalid pseudo-element"); - let mut size = LogicalSize::new( backend::style_size_property(&style, "width"), backend::style_size_property(&style, "height"),