Implement ResizeObserver (#2859)
Co-authored-by: Liam Murphy <43807659+Liamolucko@users.noreply.github.com>
This commit is contained in:
parent
7ce86c3d2a
commit
9a9c9b15ba
12 changed files with 584 additions and 364 deletions
|
|
@ -1,13 +1,14 @@
|
|||
use super::super::WindowId;
|
||||
use super::event_handle::EventListenerHandle;
|
||||
use super::media_query_handle::MediaQueryListHandle;
|
||||
use super::pointer::PointerHandler;
|
||||
use super::{event, ButtonsState};
|
||||
use crate::dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize};
|
||||
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;
|
||||
|
|
@ -22,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<EventListenerHandle<dyn FnMut(Event)>>,
|
||||
on_touch_end: Option<EventListenerHandle<dyn FnMut(Event)>>,
|
||||
on_focus: Option<EventListenerHandle<dyn FnMut(FocusEvent)>>,
|
||||
|
|
@ -29,21 +31,23 @@ pub struct Canvas {
|
|||
on_keyboard_release: Option<EventListenerHandle<dyn FnMut(KeyboardEvent)>>,
|
||||
on_keyboard_press: Option<EventListenerHandle<dyn FnMut(KeyboardEvent)>>,
|
||||
on_mouse_wheel: Option<EventListenerHandle<dyn FnMut(WheelEvent)>>,
|
||||
on_fullscreen_change: Option<EventListenerHandle<dyn FnMut(Event)>>,
|
||||
on_dark_mode: Option<MediaQueryListHandle>,
|
||||
pointer_handler: PointerHandler,
|
||||
on_resize_scale: Option<ResizeScaleHandle>,
|
||||
}
|
||||
|
||||
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<Cell<PhysicalSize<u32>>>,
|
||||
old_size: Rc<Cell<PhysicalSize<u32>>>,
|
||||
current_size: Rc<Cell<PhysicalSize<u32>>>,
|
||||
wants_fullscreen: Rc<RefCell<bool>>,
|
||||
}
|
||||
|
||||
impl Canvas {
|
||||
pub fn create(
|
||||
id: WindowId,
|
||||
window: web_sys::Window,
|
||||
attr: &WindowAttributes,
|
||||
platform_attr: PlatformSpecificWindowBuilderAttributes,
|
||||
|
|
@ -73,24 +77,20 @@ 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(&window, &canvas, size);
|
||||
}
|
||||
|
||||
let canvas = Canvas {
|
||||
Ok(Canvas {
|
||||
common: Common {
|
||||
window,
|
||||
raw: canvas,
|
||||
size: Rc::new(Cell::new(size)),
|
||||
old_size: Rc::default(),
|
||||
current_size: Rc::default(),
|
||||
wants_fullscreen: Rc::new(RefCell::new(false)),
|
||||
},
|
||||
id,
|
||||
on_touch_start: None,
|
||||
on_touch_end: None,
|
||||
on_blur: None,
|
||||
|
|
@ -98,14 +98,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_scale: None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_cursor_lock(&self, lock: bool) -> Result<(), RootOE> {
|
||||
|
|
@ -138,12 +134,24 @@ impl Canvas {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn window(&self) -> &web_sys::Window {
|
||||
&self.common.window
|
||||
pub fn old_size(&self) -> PhysicalSize<u32> {
|
||||
self.common.old_size.get()
|
||||
}
|
||||
|
||||
pub fn size(&self) -> &Rc<Cell<PhysicalSize<u32>>> {
|
||||
&self.common.size
|
||||
pub fn inner_size(&self) -> PhysicalSize<u32> {
|
||||
self.common.current_size.get()
|
||||
}
|
||||
|
||||
pub fn set_old_size(&self, size: PhysicalSize<u32>) {
|
||||
self.common.old_size.set(size)
|
||||
}
|
||||
|
||||
pub fn set_current_size(&self, size: PhysicalSize<u32>) {
|
||||
self.common.current_size.set(size)
|
||||
}
|
||||
|
||||
pub fn window(&self) -> &web_sys::Window {
|
||||
&self.common.window
|
||||
}
|
||||
|
||||
pub fn raw(&self) -> &HtmlCanvasElement {
|
||||
|
|
@ -329,16 +337,6 @@ impl Canvas {
|
|||
}));
|
||||
}
|
||||
|
||||
pub fn on_fullscreen_change<F>(&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<F>(&mut self, mut handler: F)
|
||||
where
|
||||
F: 'static + FnMut(bool),
|
||||
|
|
@ -350,6 +348,19 @@ impl Canvas {
|
|||
));
|
||||
}
|
||||
|
||||
pub(crate) fn on_resize_scale<S, R>(&mut self, scale_handler: S, size_handler: R)
|
||||
where
|
||||
S: 'static + FnMut(PhysicalSize<u32>, f64),
|
||||
R: 'static + FnMut(PhysicalSize<u32>),
|
||||
{
|
||||
self.on_resize_scale = Some(ResizeScaleHandle::new(
|
||||
self.window().clone(),
|
||||
self.raw().clone(),
|
||||
scale_handler,
|
||||
size_handler,
|
||||
));
|
||||
}
|
||||
|
||||
pub fn request_fullscreen(&self) {
|
||||
self.common.request_fullscreen()
|
||||
}
|
||||
|
|
@ -358,15 +369,54 @@ impl Canvas {
|
|||
self.common.is_fullscreen()
|
||||
}
|
||||
|
||||
pub(crate) fn handle_scale_change<T: 'static>(
|
||||
&self,
|
||||
runner: &super::super::event_loop::runner::Shared<T>,
|
||||
event_handler: impl FnOnce(crate::event::Event<'_, T>),
|
||||
current_size: PhysicalSize<u32>,
|
||||
scale: f64,
|
||||
) {
|
||||
// First, we send the `ScaleFactorChanged` event:
|
||||
self.set_current_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")
|
||||
.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),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_listeners(&mut self) {
|
||||
self.on_focus = None;
|
||||
self.on_blur = None;
|
||||
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_scale = None;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,19 +3,19 @@ mod event;
|
|||
mod event_handle;
|
||||
mod media_query_handle;
|
||||
mod pointer;
|
||||
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, Size};
|
||||
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);
|
||||
|
|
@ -52,38 +52,52 @@ impl WindowExtWebSys for Window {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn window_size(window: &web_sys::Window) -> LogicalSize<f64> {
|
||||
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());
|
||||
pub fn set_canvas_size(
|
||||
window: &web_sys::Window,
|
||||
raw: &HtmlCanvasElement,
|
||||
mut new_size: LogicalSize<f64>,
|
||||
) {
|
||||
let document = window.document().expect("Failed to obtain document");
|
||||
|
||||
let physical_size = new_size.to_physical(scale_factor);
|
||||
canvas.size().set(physical_size);
|
||||
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");
|
||||
|
||||
let logical_size = new_size.to_logical::<f64>(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),
|
||||
);
|
||||
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")
|
||||
+ 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));
|
||||
}
|
||||
|
||||
/// 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) {
|
||||
|
|
|
|||
321
src/platform_impl/web/web_sys/resize_scaling.rs
Normal file
321
src/platform_impl/web/web_sys/resize_scaling.rs
Normal file
|
|
@ -0,0 +1,321 @@
|
|||
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;
|
||||
|
||||
pub struct ResizeScaleHandle(Rc<RefCell<ResizeScaleInternal>>);
|
||||
|
||||
impl ResizeScaleHandle {
|
||||
pub(crate) fn new<S, R>(
|
||||
window: Window,
|
||||
canvas: HtmlCanvasElement,
|
||||
scale_handler: S,
|
||||
resize_handler: R,
|
||||
) -> Self
|
||||
where
|
||||
S: 'static + FnMut(PhysicalSize<u32>, f64),
|
||||
R: 'static + FnMut(PhysicalSize<u32>),
|
||||
{
|
||||
Self(ResizeScaleInternal::new(
|
||||
window,
|
||||
canvas,
|
||||
scale_handler,
|
||||
resize_handler,
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) fn notify_resize(&self) {
|
||||
self.0.borrow_mut().notify()
|
||||
}
|
||||
}
|
||||
|
||||
/// 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<dyn FnMut(Array, ResizeObserver)>,
|
||||
scale_handler: Box<dyn FnMut(PhysicalSize<u32>, f64)>,
|
||||
resize_handler: Box<dyn FnMut(PhysicalSize<u32>)>,
|
||||
notify_scale: Cell<bool>,
|
||||
}
|
||||
|
||||
impl ResizeScaleInternal {
|
||||
fn new<S, R>(
|
||||
window: Window,
|
||||
canvas: HtmlCanvasElement,
|
||||
scale_handler: S,
|
||||
resize_handler: R,
|
||||
) -> Rc<RefCell<Self>>
|
||||
where
|
||||
S: 'static + FnMut(PhysicalSize<u32>, f64),
|
||||
R: 'static + FnMut(PhysicalSize<u32>),
|
||||
{
|
||||
Rc::<RefCell<ResizeScaleInternal>>::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();
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
});
|
||||
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),
|
||||
notify_scale: Cell::new(false),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn create_mql<F>(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 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))
|
||||
|| style.get_property_value("display").unwrap() == "none"
|
||||
{
|
||||
let size = PhysicalSize::new(0, 0);
|
||||
|
||||
if self.notify_scale.replace(false) {
|
||||
let scale = backend::scale_factor(&self.window);
|
||||
(self.scale_handler)(size, scale)
|
||||
} else {
|
||||
(self.resize_handler)(size)
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Safari doesn't support `devicePixelContentBoxSize`
|
||||
if has_device_pixel_support() {
|
||||
self.observer.unobserve(&self.canvas);
|
||||
self.observer.observe(&self.canvas);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
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<RefCell<Self>>, 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:
|
||||
// <https://github.com/rust-windowing/winit/issues/2597>.
|
||||
// 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, move |mql| {
|
||||
if let Some(rc_self) = weak_self.upgrade() {
|
||||
Self::handle_scale(rc_self, mql);
|
||||
}
|
||||
});
|
||||
this.mql = new_mql;
|
||||
|
||||
this.notify_scale.set(true);
|
||||
this.notify();
|
||||
}
|
||||
|
||||
fn process_entry(
|
||||
window: &Window,
|
||||
canvas: &HtmlCanvasElement,
|
||||
entries: Array,
|
||||
) -> PhysicalSize<u32> {
|
||||
let entry: ResizeObserverEntry = entries.get(0).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 entry: ResizeObserverSize = entry
|
||||
.device_pixel_content_box_size()
|
||||
.get(0)
|
||||
.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 <https://bugs.webkit.org/show_bug.cgi?id=219005>.
|
||||
pub fn has_device_pixel_support() -> bool {
|
||||
thread_local! {
|
||||
static DEVICE_PIXEL_SUPPORT: Lazy<bool> = 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)
|
||||
}
|
||||
|
|
@ -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<RefCell<ScaleChangeDetectorInternal>>);
|
||||
|
||||
impl ScaleChangeDetector {
|
||||
pub(crate) fn new<F>(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<dyn FnMut(ScaleChangeArgs)>,
|
||||
mql: MediaQueryListHandle,
|
||||
last_scale: f64,
|
||||
}
|
||||
|
||||
impl ScaleChangeDetectorInternal {
|
||||
fn new<F>(window: web_sys::Window, handler: F) -> Rc<RefCell<Self>>
|
||||
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<F>(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<RefCell<Self>>, 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:
|
||||
// <https://github.com/rust-windowing/winit/issues/2597>.
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue