Skip to content

Commit

Permalink
Put the ResizeObserver functionality behind the css-size feature
Browse files Browse the repository at this point in the history
  • Loading branch information
Liamolucko committed Feb 25, 2022
1 parent a4c631f commit caaa91b
Show file tree
Hide file tree
Showing 9 changed files with 155 additions and 31 deletions.
2 changes: 0 additions & 2 deletions .cargo/config.toml

This file was deleted.

16 changes: 10 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ default = ["x11", "wayland", "wayland-dlopen"]
x11 = ["x11-dl", "mio", "percent-encoding", "parking_lot"]
wayland = ["wayland-client", "wayland-protocols", "sctk"]
wayland-dlopen = ["sctk/dlopen", "wayland-client/dlopen"]
css-size = [
"js-sys",
"web_sys/ResizeObserver",
"web_sys/ResizeObserverBoxOptions",
"web_sys/ResizeObserverEntry",
"web_sys/ResizeObserverOptions",
"web_sys/ResizeObserverSize",
]

[dependencies]
instant = { version = "0.1", features = ["wasm-bindgen"] }
Expand Down Expand Up @@ -107,11 +115,6 @@ features = [
'Element',
'Event',
'EventTarget',
"ResizeObserver",
"ResizeObserverBoxOptions",
"ResizeObserverEntry",
"ResizeObserverOptions",
"ResizeObserverSize",
'FocusEvent',
'HtmlCanvasElement',
'HtmlElement',
Expand All @@ -129,7 +132,8 @@ features = [
version = "0.2.45"

[target.'cfg(target_arch = "wasm32")'.dependencies.js-sys]
version = "0.3.22"
version = "0.3.56"
optional = true

[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
console_log = "0.2"
1 change: 1 addition & 0 deletions src/platform_impl/web/event_loop/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod proxy;
#[cfg(feature = "css-size")]
mod resize;
mod runner;
mod state;
Expand Down
52 changes: 42 additions & 10 deletions src/platform_impl/web/event_loop/runner.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#[cfg(feature = "css-size")]
use super::resize::ResizeState;
use super::{backend, state::State};
use crate::dpi::PhysicalSize;
Expand All @@ -7,6 +8,8 @@ use crate::window::WindowId;

use instant::{Duration, Instant};
use std::cell::Cell;
#[cfg(feature = "css-size")]
use std::cell::Ref;
use std::{
cell::RefCell,
clone::Clone,
Expand All @@ -33,7 +36,10 @@ pub struct Execution<T: 'static> {
redraw_pending: RefCell<HashSet<WindowId>>,
destroy_pending: RefCell<VecDeque<(WindowId, HtmlCanvasElement)>>,
/// This is initially `None`, because it requires a handle on the runner.
#[cfg(feature = "css-size")]
resize_state: RefCell<Option<ResizeState>>,
#[cfg(not(feature = "css-size"))]
scale_change_detector: RefCell<Option<backend::ScaleChangeDetector>>,
last_scale: Cell<f64>,
unload_event_handle: RefCell<Option<backend::UnloadEventHandle>>,
}
Expand Down Expand Up @@ -110,12 +116,26 @@ impl<T: 'static> Shared<T> {
all_canvases: RefCell::new(Vec::new()),
redraw_pending: RefCell::new(HashSet::new()),
destroy_pending: RefCell::new(VecDeque::new()),
#[cfg(feature = "css-size")]
resize_state: RefCell::new(None),
#[cfg(not(feature = "css-size"))]
scale_change_detector: RefCell::new(None),
last_scale: Cell::new(backend::scale_factor()),
unload_event_handle: RefCell::new(None),
}));

*this.0.resize_state.borrow_mut() = Some(ResizeState::new(this.clone()));
#[cfg(feature = "css-size")]
{
*this.0.resize_state.borrow_mut() = Some(ResizeState::new(this.clone()));
}
#[cfg(not(feature = "css-size"))]
{
let runner = this.clone();
*this.0.scale_change_detector.borrow_mut() =
Some(backend::ScaleChangeDetector::new(move || {
runner.handle_scale_changed(false)
}))
}

this
}
Expand All @@ -133,9 +153,13 @@ impl<T: 'static> Shared<T> {
.borrow_mut()
.push((id, Rc::downgrade(canvas)));

let resize_state_ref = self.0.resize_state.borrow();
let resize_state = resize_state_ref.as_ref().unwrap();
resize_state.observe(canvas.borrow().raw());
#[cfg(feature = "css-size")]
{
let resize_state = Ref::map(self.0.resize_state.borrow(), |resize_state| {
resize_state.as_ref().unwrap()
});
resize_state.observe(canvas.borrow().raw());
}
}

pub fn notify_destroy_window(&self, id: WindowId, canvas: HtmlCanvasElement) {
Expand Down Expand Up @@ -260,10 +284,15 @@ impl<T: 'static> Shared<T> {
// `run_until_cleared` and `handle_scale_changed`, somewhere between emitting
// `NewEvents` and `MainEventsCleared`.
fn process_destroy_pending_windows(&self, control: &mut root::ControlFlow) {
let resize_state_ref = self.0.resize_state.borrow();
let resize_state = resize_state_ref.as_ref().unwrap();
#[cfg(feature = "css-size")]
let resize_state = Ref::map(self.0.resize_state.borrow(), |resize_state| {
resize_state.as_ref().unwrap()
});

// `canvas` isn't used when we aren't using `ResizeObserver`.
#[cfg_attr(not(feature = "css-size"), allow(unused_variables))]
while let Some((id, canvas)) = self.0.destroy_pending.borrow_mut().pop_front() {
#[cfg(feature = "css-size")]
resize_state.unobserve(&canvas);
self.0
.all_canvases
Expand Down Expand Up @@ -333,7 +362,7 @@ impl<T: 'static> Shared<T> {
let canvas = canvas.borrow();

// If the canvas isn't in the DOM, we don't need to handle the scale factor change for it.
let content_size = backend::content_size(canvas.raw())?;
let content_size = backend::inner_size(canvas.raw())?;

if canvas.size() != content_size.to_physical(old_scale) {
observer_will_run = true;
Expand Down Expand Up @@ -427,7 +456,7 @@ impl<T: 'static> Shared<T> {

if mut_size != *size {
// Treat a change through this route the same way as `set_inner_size`, setting the CSS `width` and `height`.
backend::set_content_size(canvas, mut_size.into());
backend::set_inner_size(canvas, mut_size.into());
}
} else if size.width == canvas.width() && size.height == canvas.height() {
// If the canvas is already the correct size, don't send any resize events.
Expand Down Expand Up @@ -580,7 +609,10 @@ impl<T: 'static> Shared<T> {
fn handle_loop_destroyed(&self, control: &mut root::ControlFlow) {
self.handle_event(Event::LoopDestroyed, control);
let all_canvases = std::mem::take(&mut *self.0.all_canvases.borrow_mut());
*self.0.resize_state.borrow_mut() = None;
#[cfg(feature = "css-size")]
{
*self.0.resize_state.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.
Expand All @@ -593,7 +625,6 @@ impl<T: 'static> Shared<T> {
canvas.remove_listeners();
}
}

// At this point, the `self.0` `Rc` should only be strongly referenced
// by the following:
// * `self`, i.e. the item which triggered this event loop wakeup, which
Expand Down Expand Up @@ -646,6 +677,7 @@ impl<T: 'static> Shared<T> {
.count()
}

#[cfg(feature = "css-size")]
/// Whether the scale factor has changed since the last `ScaleFactorChanged` event.
pub fn scale_factor_changed(&self) -> bool {
self.0.last_scale.get() != backend::scale_factor()
Expand Down
41 changes: 41 additions & 0 deletions src/platform_impl/web/event_loop/window_target.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use super::{super::monitor, backend, device, proxy::Proxy, runner, window};
use crate::dpi::PhysicalSize;
use crate::event::{
DeviceEvent, DeviceId, ElementState, Event, KeyboardInput, TouchPhase, WindowEvent,
};
Expand Down Expand Up @@ -196,6 +197,46 @@ impl<T> WindowTarget<T> {
});
});

if cfg!(not(feature = "css-size")) {
let runner = self.runner.clone();
let raw = canvas.raw().clone();

// The size to restore to after exiting fullscreen.
let mut intended_size = PhysicalSize {
width: raw.width() as u32,
height: raw.height() as u32,
};
canvas.on_fullscreen_change(move || {
let old_size = PhysicalSize {
width: raw.width() as u32,
height: raw.height() as u32,
};

// 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(&raw) {
intended_size = old_size;

backend::inner_size(&raw)
// I don't think it's possible for an element to become fullscreen whilst not being in the DOM.
.unwrap()
.to_physical(backend::scale_factor())
} else {
intended_size
};

if old_size != new_size {
raw.set_width(new_size.width);
raw.set_height(new_size.height);
runner.send_event(Event::WindowEvent {
window_id: WindowId(id),
event: WindowEvent::Resized(new_size),
});
runner.request_redraw(WindowId(id));
}
});
}

let runner = self.runner.clone();
canvas.on_dark_mode(move |is_dark_mode| {
let theme = if is_dark_mode {
Expand Down
3 changes: 3 additions & 0 deletions src/platform_impl/web/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ mod window;
#[path = "web_sys/mod.rs"]
mod backend;

#[cfg(all(feature = "css-size", not(web_sys_unstable_apis)))]
compile_error!("`web_sys_unstable_apis` must be enabled to use the `css-size` feature");

pub use self::device::Id as DeviceId;
pub use self::error::OsError;
pub(crate) use self::event_loop::{
Expand Down
21 changes: 19 additions & 2 deletions src/platform_impl/web/web_sys/canvas.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ use std::rc::Rc;

use wasm_bindgen::{closure::Closure, JsCast};
use web_sys::{
AddEventListenerOptions, FocusEvent, HtmlCanvasElement, KeyboardEvent, MediaQueryListEvent,
MouseEvent, WheelEvent,
AddEventListenerOptions, Event, FocusEvent, HtmlCanvasElement, KeyboardEvent,
MediaQueryListEvent, MouseEvent, WheelEvent,
};

mod mouse_handler;
Expand All @@ -27,6 +27,7 @@ pub struct Canvas {
on_keyboard_press: Option<EventListenerHandle<dyn FnMut(KeyboardEvent)>>,
on_received_character: 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>,
mouse_state: MouseState,
}
Expand Down Expand Up @@ -56,6 +57,10 @@ impl Canvas {
}
};

#[cfg(not(feature = "css-size"))]
// Try to avoid this changing from under us when we don't have `ResizeObserver`.
super::set_canvas_style_property(&canvas, "box-sizing", "content-box");

// A tabindex is needed in order to capture local keyboard events.
// A "0" value means that the element should be focusable in
// sequential keyboard navigation, but its order is defined by the
Expand All @@ -82,6 +87,7 @@ impl Canvas {
on_keyboard_press: None,
on_received_character: None,
on_mouse_wheel: None,
on_fullscreen_change: None,
on_dark_mode: None,
mouse_state,
})
Expand Down Expand Up @@ -271,6 +277,16 @@ 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),
Expand Down Expand Up @@ -298,6 +314,7 @@ impl Canvas {
self.on_keyboard_press = None;
self.on_received_character = None;
self.on_mouse_wheel = None;
self.on_fullscreen_change = None;
self.on_dark_mode = None;
match &mut self.mouse_state {
MouseState::HasPointerEvent(h) => h.remove_listeners(),
Expand Down
19 changes: 14 additions & 5 deletions src/platform_impl/web/web_sys/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@ pub use self::timeout::{AnimationFrameRequest, Timeout};
use crate::dpi::{LogicalSize, Size};
use crate::platform::web::WindowExtWebSys;
use crate::window::Window;
use js_sys::Object;
use wasm_bindgen::prelude::*;
use web_sys::{window, BeforeUnloadEvent, Element, HtmlCanvasElement, HtmlElement};
use web_sys::{window, BeforeUnloadEvent, Element, HtmlCanvasElement};

pub fn throw(msg: &str) {
wasm_bindgen::throw_str(msg);
Expand Down Expand Up @@ -69,7 +68,7 @@ pub fn scale_factor() -> f64 {
/// Gets the size of the content box of `element` based on CSS.
///
/// Returns `None` if the element isn't in the DOM.
pub fn content_size(element: &HtmlElement) -> Option<LogicalSize<f64>> {
pub fn inner_size(element: &HtmlCanvasElement) -> Option<LogicalSize<f64>> {
let window = web_sys::window().unwrap();
let document = window.document().unwrap();
if !document.contains(Some(element)) {
Expand Down Expand Up @@ -103,11 +102,17 @@ pub fn content_size(element: &HtmlElement) -> Option<LogicalSize<f64>> {
})
}

pub fn set_content_size(element: &HtmlElement, size: Size) {
pub fn set_inner_size(element: &HtmlCanvasElement, size: Size) {
let scale_factor = scale_factor();

let mut logical_size = size.to_logical::<f64>(scale_factor);

if cfg!(not(feature = "css-size")) {
let physical_size = size.to_physical(scale_factor);
element.set_width(physical_size.width);
element.set_height(physical_size.height);
}

let window = web_sys::window().unwrap();
let style = window
.get_computed_style(element)
Expand Down Expand Up @@ -137,7 +142,7 @@ pub fn set_content_size(element: &HtmlElement, size: Size) {
set_canvas_style_property(element, "height", &format!("{}px", logical_size.height));
}

pub fn set_canvas_style_property(raw: &HtmlElement, property: &str, value: &str) {
pub fn set_canvas_style_property(raw: &HtmlCanvasElement, property: &str, value: &str) {
let style = raw.style();
style
.set_property(property, value)
Expand All @@ -158,6 +163,7 @@ pub fn is_fullscreen(canvas: &HtmlCanvasElement) -> bool {
}

// A slight hack to get at the prototype of `ResizeObserverEntry`, so that we can check for `device-pixel-content-box` support.
#[cfg(feature = "css-size")]
mod prototype {
use js_sys::Object;
use wasm_bindgen::prelude::*;
Expand All @@ -172,7 +178,10 @@ mod prototype {
}
}

#[cfg(feature = "css-size")]
pub fn supports_device_pixel_content_size() -> bool {
use js_sys::Object;

let proto = prototype::ResizeObserverEntry::prototype();
let desc = Object::get_own_property_descriptor(
&proto,
Expand Down
Loading

0 comments on commit caaa91b

Please sign in to comment.