On Web, implement WindowEvent::Occluded (#2940)

This commit is contained in:
daxpedda 2023-07-10 02:02:38 +02:00 committed by GitHub
parent bd890e69aa
commit 5e0e1e96bc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 165 additions and 9 deletions

View file

@ -3,6 +3,7 @@ use super::{backend, state::State};
use crate::dpi::PhysicalSize;
use crate::event::{
DeviceEvent, DeviceId as RootDeviceId, ElementState, Event, RawKeyEvent, StartCause,
WindowEvent,
};
use crate::event_loop::{ControlFlow, DeviceEvents};
use crate::platform_impl::platform::backend::EventListenerHandle;
@ -35,6 +36,7 @@ type OnEventHandle<T> = RefCell<Option<EventListenerHandle<dyn FnMut(T)>>>;
pub struct Execution<T: 'static> {
runner: RefCell<RunnerEnum<T>>,
suspended: Cell<bool>,
event_loop_recreation: Cell<bool>,
events: RefCell<VecDeque<EventWrapper<T>>>,
id: RefCell<u32>,
@ -50,6 +52,7 @@ pub struct Execution<T: 'static> {
on_mouse_release: OnEventHandle<PointerEvent>,
on_key_press: OnEventHandle<KeyboardEvent>,
on_key_release: OnEventHandle<KeyboardEvent>,
on_visibility_change: OnEventHandle<web_sys::Event>,
}
enum RunnerEnum<T: 'static> {
@ -140,6 +143,7 @@ impl<T: 'static> Shared<T> {
pub fn new() -> Self {
Shared(Rc::new(Execution {
runner: RefCell::new(RunnerEnum::Pending),
suspended: Cell::new(false),
event_loop_recreation: Cell::new(false),
events: RefCell::new(VecDeque::new()),
#[allow(clippy::disallowed_methods)]
@ -156,6 +160,7 @@ impl<T: 'static> Shared<T> {
on_mouse_release: RefCell::new(None),
on_key_press: RefCell::new(None),
on_key_release: RefCell::new(None),
on_visibility_change: RefCell::new(None),
}))
}
@ -191,6 +196,7 @@ impl<T: 'static> Shared<T> {
let runner = self.clone();
move |event: PageTransitionEvent| {
if event.persisted() {
runner.0.suspended.set(false);
runner.send_event(Event::Resumed);
}
}
@ -198,6 +204,7 @@ impl<T: 'static> Shared<T> {
{
let runner = self.clone();
move |event: PageTransitionEvent| {
runner.0.suspended.set(true);
if event.persisted() {
runner.send_event(Event::Suspended);
} else {
@ -384,6 +391,28 @@ impl<T: 'static> Shared<T> {
});
}),
));
let runner = self.clone();
*self.0.on_visibility_change.borrow_mut() = Some(EventListenerHandle::new(
// Safari <14 doesn't support the `visibilitychange` event on `Window`.
&self.window().document().expect("Failed to obtain document"),
"visibilitychange",
Closure::new(move |_| {
if !runner.0.suspended.get() {
for (id, canvas) in &*runner.0.all_canvases.borrow() {
if let Some(canvas) = canvas.upgrade() {
if backend::is_intersecting(runner.window(), canvas.borrow().raw()) {
runner.send_event(Event::WindowEvent {
window_id: *id,
event: WindowEvent::Occluded(!backend::is_visible(
runner.window(),
)),
});
}
}
}
}
}),
));
}
// Generate a strictly increasing ID
@ -630,6 +659,7 @@ impl<T: 'static> Shared<T> {
*self.0.on_mouse_release.borrow_mut() = None;
*self.0.on_key_press.borrow_mut() = None;
*self.0.on_key_release.borrow_mut() = None;
*self.0.on_visibility_change.borrow_mut() = None;
// Dropping the `Runner` drops the event handler closure, which will in
// turn drop all `Window`s moved into the closure.
*self.0.runner.borrow_mut() = RunnerEnum::Destroyed;

View file

@ -720,6 +720,16 @@ impl<T> EventLoopWindowTarget<T> {
}
},
);
let runner = self.runner.clone();
canvas.on_intersection(move |is_intersecting| {
if backend::is_visible(runner.window()) {
runner.send_event(Event::WindowEvent {
window_id: RootWindowId(id),
event: WindowEvent::Occluded(!is_intersecting),
});
}
})
}
pub fn available_monitors(&self) -> VecDequeIter<MonitorHandle> {

View file

@ -1,5 +1,6 @@
use super::super::WindowId;
use super::event_handle::EventListenerHandle;
use super::intersection_handle::IntersectionObserverHandle;
use super::media_query_handle::MediaQueryListHandle;
use super::pointer::PointerHandler;
use super::{event, ButtonsState, ResizeScaleHandle};
@ -37,6 +38,7 @@ pub struct Canvas {
on_dark_mode: Option<MediaQueryListHandle>,
pointer_handler: PointerHandler,
on_resize_scale: Option<ResizeScaleHandle>,
on_intersect: Option<IntersectionObserverHandle>,
}
pub struct Common {
@ -105,6 +107,7 @@ impl Canvas {
on_dark_mode: None,
pointer_handler: PointerHandler::new(),
on_resize_scale: None,
on_intersect: None,
})
}
@ -365,6 +368,13 @@ impl Canvas {
));
}
pub(crate) fn on_intersection<F>(&mut self, handler: F)
where
F: 'static + FnMut(bool),
{
self.on_intersect = Some(IntersectionObserverHandle::new(self.raw(), handler));
}
pub fn request_fullscreen(&self) {
self.common.request_fullscreen()
}
@ -421,6 +431,7 @@ impl Canvas {
self.on_dark_mode = None;
self.pointer_handler.remove_listeners();
self.on_resize_scale = None;
self.on_intersect = None;
}
}

View file

@ -0,0 +1,45 @@
use js_sys::Array;
use wasm_bindgen::{prelude::Closure, JsCast};
use web_sys::{Element, IntersectionObserver, IntersectionObserverEntry};
pub(super) struct IntersectionObserverHandle {
observer: IntersectionObserver,
_closure: Closure<dyn FnMut(Array)>,
}
impl IntersectionObserverHandle {
pub fn new<F>(element: &Element, mut callback: F) -> Self
where
F: 'static + FnMut(bool),
{
let mut skip = true;
let closure = Closure::new(move |entries: Array| {
let entry: IntersectionObserverEntry = entries.get(0).unchecked_into();
let is_intersecting = entry.is_intersecting();
// skip first intersection
if skip && is_intersecting {
skip = false;
return;
}
callback(is_intersecting);
});
let observer = IntersectionObserver::new(closure.as_ref().unchecked_ref())
// we don't provide any `options`
.expect("Invalid `options`");
observer.observe(element);
Self {
observer,
_closure: closure,
}
}
}
impl Drop for IntersectionObserverHandle {
fn drop(&mut self) {
self.observer.disconnect()
}
}

View file

@ -1,6 +1,7 @@
mod canvas;
pub mod event;
mod event_handle;
mod intersection_handle;
mod media_query_handle;
mod pointer;
mod resize_scaling;
@ -16,7 +17,9 @@ use crate::dpi::LogicalSize;
use crate::platform::web::WindowExtWebSys;
use crate::window::Window;
use wasm_bindgen::closure::Closure;
use web_sys::{CssStyleDeclaration, Element, HtmlCanvasElement, PageTransitionEvent};
use web_sys::{
CssStyleDeclaration, Element, HtmlCanvasElement, PageTransitionEvent, VisibilityState,
};
pub fn throw(msg: &str) {
wasm_bindgen::throw_str(msg);
@ -136,4 +139,25 @@ pub fn is_dark_mode(window: &web_sys::Window) -> Option<bool> {
.map(|media| media.matches())
}
pub fn is_visible(window: &web_sys::Window) -> bool {
let document = window.document().expect("Failed to obtain document");
document.visibility_state() == VisibilityState::Visible
}
pub fn is_intersecting(window: &web_sys::Window, canvas: &HtmlCanvasElement) -> bool {
let rect = canvas.get_bounding_client_rect();
// This should never panic.
let window_width = window.inner_width().unwrap().as_f64().unwrap() as i32;
let window_height = window.inner_height().unwrap().as_f64().unwrap() as i32;
let left = rect.left() as i32;
let width = rect.width() as i32;
let top = rect.top() as i32;
let height = rect.height() as i32;
let horizontal = left <= window_width && left + width >= 0;
let vertical = top <= window_height && top + height >= 0;
horizontal && vertical
}
pub type RawCanvasType = HtmlCanvasElement;