mirror of
https://github.com/italicsjenga/winit-sonoma-fix.git
synced 2024-12-23 22:01:31 +11:00
On Web, implement WindowEvent::Occluded
(#2940)
This commit is contained in:
parent
bd890e69aa
commit
5e0e1e96bc
|
@ -11,6 +11,7 @@ And please only add new entries to the top of this list, right below the `# Unre
|
||||||
- On X11, fix false positive flagging of key repeats when pressing different keys with no release
|
- On X11, fix false positive flagging of key repeats when pressing different keys with no release
|
||||||
between presses.
|
between presses.
|
||||||
- Implement `PartialOrd` and `Ord` for `KeyCode` and `NativeKeyCode`.
|
- Implement `PartialOrd` and `Ord` for `KeyCode` and `NativeKeyCode`.
|
||||||
|
- On Web, implement `WindowEvent::Occluded`.
|
||||||
|
|
||||||
# 0.29.0-beta.0
|
# 0.29.0-beta.0
|
||||||
|
|
||||||
|
|
|
@ -145,6 +145,8 @@ features = [
|
||||||
'FocusEvent',
|
'FocusEvent',
|
||||||
'HtmlCanvasElement',
|
'HtmlCanvasElement',
|
||||||
'HtmlElement',
|
'HtmlElement',
|
||||||
|
'IntersectionObserver',
|
||||||
|
'IntersectionObserverEntry',
|
||||||
'KeyboardEvent',
|
'KeyboardEvent',
|
||||||
'MediaQueryList',
|
'MediaQueryList',
|
||||||
'Node',
|
'Node',
|
||||||
|
@ -155,6 +157,7 @@ features = [
|
||||||
'ResizeObserverEntry',
|
'ResizeObserverEntry',
|
||||||
'ResizeObserverOptions',
|
'ResizeObserverOptions',
|
||||||
'ResizeObserverSize',
|
'ResizeObserverSize',
|
||||||
|
'VisibilityState',
|
||||||
'Window',
|
'Window',
|
||||||
'WheelEvent'
|
'WheelEvent'
|
||||||
]
|
]
|
||||||
|
|
|
@ -58,6 +58,9 @@ pub fn main() {
|
||||||
|
|
||||||
#[cfg(wasm_platform)]
|
#[cfg(wasm_platform)]
|
||||||
mod wasm {
|
mod wasm {
|
||||||
|
use std::num::NonZeroU32;
|
||||||
|
|
||||||
|
use softbuffer::{Surface, SurfaceExtWeb};
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
use winit::{event::Event, window::Window};
|
use winit::{event::Event, window::Window};
|
||||||
|
|
||||||
|
@ -73,16 +76,26 @@ mod wasm {
|
||||||
use winit::platform::web::WindowExtWebSys;
|
use winit::platform::web::WindowExtWebSys;
|
||||||
|
|
||||||
let canvas = window.canvas().unwrap();
|
let canvas = window.canvas().unwrap();
|
||||||
|
let mut surface = Surface::from_canvas(canvas.clone()).unwrap();
|
||||||
|
surface
|
||||||
|
.resize(
|
||||||
|
NonZeroU32::new(canvas.width()).unwrap(),
|
||||||
|
NonZeroU32::new(canvas.height()).unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let mut buffer = surface.buffer_mut().unwrap();
|
||||||
|
buffer.fill(0xFFF0000);
|
||||||
|
buffer.present().unwrap();
|
||||||
|
|
||||||
let window = web_sys::window().unwrap();
|
let window = web_sys::window().unwrap();
|
||||||
let document = window.document().unwrap();
|
let document = window.document().unwrap();
|
||||||
let body = document.body().unwrap();
|
let body = document.body().unwrap();
|
||||||
|
|
||||||
// Set a background color for the canvas to make it easier to tell where the canvas is for debugging purposes.
|
let style = &canvas.style();
|
||||||
canvas
|
style.set_property("margin", "50px").unwrap();
|
||||||
.style()
|
// Use to test interactions with border and padding.
|
||||||
.set_property("background-color", "crimson")
|
//style.set_property("border", "50px solid black").unwrap();
|
||||||
.unwrap();
|
//style.set_property("padding", "50px").unwrap();
|
||||||
body.append_child(&canvas).unwrap();
|
body.append_child(&canvas).unwrap();
|
||||||
|
|
||||||
let log_header = document.create_element("h2").unwrap();
|
let log_header = document.create_element("h2").unwrap();
|
||||||
|
@ -100,11 +113,25 @@ mod wasm {
|
||||||
// Getting access to browser logs requires a lot of setup on mobile devices.
|
// Getting access to browser logs requires a lot of setup on mobile devices.
|
||||||
// So we implement this basic logging system into the page to give developers an easy alternative.
|
// So we implement this basic logging system into the page to give developers an easy alternative.
|
||||||
// As a bonus its also kind of handy on desktop.
|
// As a bonus its also kind of handy on desktop.
|
||||||
if let Event::WindowEvent { event, .. } = &event {
|
let event = match event {
|
||||||
|
Event::WindowEvent { event, .. } => Some(format!("{event:?}")),
|
||||||
|
Event::Resumed | Event::Suspended => Some(format!("{event:?}")),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
if let Some(event) = event {
|
||||||
let window = web_sys::window().unwrap();
|
let window = web_sys::window().unwrap();
|
||||||
let document = window.document().unwrap();
|
let document = window.document().unwrap();
|
||||||
let log = document.create_element("li").unwrap();
|
let log = document.create_element("li").unwrap();
|
||||||
log.set_text_content(Some(&format!("{event:?}")));
|
|
||||||
|
let date = js_sys::Date::new_0();
|
||||||
|
log.set_text_content(Some(&format!(
|
||||||
|
"{:02}:{:02}:{:02}.{:03}: {event}",
|
||||||
|
date.get_hours(),
|
||||||
|
date.get_minutes(),
|
||||||
|
date.get_seconds(),
|
||||||
|
date.get_milliseconds(),
|
||||||
|
)));
|
||||||
|
|
||||||
log_list
|
log_list
|
||||||
.insert_before(&log, log_list.first_child().as_ref())
|
.insert_before(&log, log_list.first_child().as_ref())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
|
@ -565,7 +565,12 @@ pub enum WindowEvent<'a> {
|
||||||
/// minimised, set invisible, or fully occluded by another window.
|
/// minimised, set invisible, or fully occluded by another window.
|
||||||
///
|
///
|
||||||
/// Platform-specific behavior:
|
/// Platform-specific behavior:
|
||||||
/// - **iOS / Android / Web / Wayland / Windows / Orbital:** Unsupported.
|
///
|
||||||
|
/// - **Web:** Doesn't take into account CSS [`border`] or [`padding`].
|
||||||
|
/// - **iOS / Android / Wayland / Windows / Orbital:** Unsupported.
|
||||||
|
///
|
||||||
|
/// [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border
|
||||||
|
/// [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding
|
||||||
Occluded(bool),
|
Occluded(bool),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ use super::{backend, state::State};
|
||||||
use crate::dpi::PhysicalSize;
|
use crate::dpi::PhysicalSize;
|
||||||
use crate::event::{
|
use crate::event::{
|
||||||
DeviceEvent, DeviceId as RootDeviceId, ElementState, Event, RawKeyEvent, StartCause,
|
DeviceEvent, DeviceId as RootDeviceId, ElementState, Event, RawKeyEvent, StartCause,
|
||||||
|
WindowEvent,
|
||||||
};
|
};
|
||||||
use crate::event_loop::{ControlFlow, DeviceEvents};
|
use crate::event_loop::{ControlFlow, DeviceEvents};
|
||||||
use crate::platform_impl::platform::backend::EventListenerHandle;
|
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> {
|
pub struct Execution<T: 'static> {
|
||||||
runner: RefCell<RunnerEnum<T>>,
|
runner: RefCell<RunnerEnum<T>>,
|
||||||
|
suspended: Cell<bool>,
|
||||||
event_loop_recreation: Cell<bool>,
|
event_loop_recreation: Cell<bool>,
|
||||||
events: RefCell<VecDeque<EventWrapper<T>>>,
|
events: RefCell<VecDeque<EventWrapper<T>>>,
|
||||||
id: RefCell<u32>,
|
id: RefCell<u32>,
|
||||||
|
@ -50,6 +52,7 @@ pub struct Execution<T: 'static> {
|
||||||
on_mouse_release: OnEventHandle<PointerEvent>,
|
on_mouse_release: OnEventHandle<PointerEvent>,
|
||||||
on_key_press: OnEventHandle<KeyboardEvent>,
|
on_key_press: OnEventHandle<KeyboardEvent>,
|
||||||
on_key_release: OnEventHandle<KeyboardEvent>,
|
on_key_release: OnEventHandle<KeyboardEvent>,
|
||||||
|
on_visibility_change: OnEventHandle<web_sys::Event>,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum RunnerEnum<T: 'static> {
|
enum RunnerEnum<T: 'static> {
|
||||||
|
@ -140,6 +143,7 @@ impl<T: 'static> Shared<T> {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Shared(Rc::new(Execution {
|
Shared(Rc::new(Execution {
|
||||||
runner: RefCell::new(RunnerEnum::Pending),
|
runner: RefCell::new(RunnerEnum::Pending),
|
||||||
|
suspended: Cell::new(false),
|
||||||
event_loop_recreation: Cell::new(false),
|
event_loop_recreation: Cell::new(false),
|
||||||
events: RefCell::new(VecDeque::new()),
|
events: RefCell::new(VecDeque::new()),
|
||||||
#[allow(clippy::disallowed_methods)]
|
#[allow(clippy::disallowed_methods)]
|
||||||
|
@ -156,6 +160,7 @@ impl<T: 'static> Shared<T> {
|
||||||
on_mouse_release: RefCell::new(None),
|
on_mouse_release: RefCell::new(None),
|
||||||
on_key_press: RefCell::new(None),
|
on_key_press: RefCell::new(None),
|
||||||
on_key_release: 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();
|
let runner = self.clone();
|
||||||
move |event: PageTransitionEvent| {
|
move |event: PageTransitionEvent| {
|
||||||
if event.persisted() {
|
if event.persisted() {
|
||||||
|
runner.0.suspended.set(false);
|
||||||
runner.send_event(Event::Resumed);
|
runner.send_event(Event::Resumed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -198,6 +204,7 @@ impl<T: 'static> Shared<T> {
|
||||||
{
|
{
|
||||||
let runner = self.clone();
|
let runner = self.clone();
|
||||||
move |event: PageTransitionEvent| {
|
move |event: PageTransitionEvent| {
|
||||||
|
runner.0.suspended.set(true);
|
||||||
if event.persisted() {
|
if event.persisted() {
|
||||||
runner.send_event(Event::Suspended);
|
runner.send_event(Event::Suspended);
|
||||||
} else {
|
} 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
|
// 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_mouse_release.borrow_mut() = None;
|
||||||
*self.0.on_key_press.borrow_mut() = None;
|
*self.0.on_key_press.borrow_mut() = None;
|
||||||
*self.0.on_key_release.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
|
// Dropping the `Runner` drops the event handler closure, which will in
|
||||||
// turn drop all `Window`s moved into the closure.
|
// turn drop all `Window`s moved into the closure.
|
||||||
*self.0.runner.borrow_mut() = RunnerEnum::Destroyed;
|
*self.0.runner.borrow_mut() = RunnerEnum::Destroyed;
|
||||||
|
|
|
@ -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> {
|
pub fn available_monitors(&self) -> VecDequeIter<MonitorHandle> {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use super::super::WindowId;
|
use super::super::WindowId;
|
||||||
use super::event_handle::EventListenerHandle;
|
use super::event_handle::EventListenerHandle;
|
||||||
|
use super::intersection_handle::IntersectionObserverHandle;
|
||||||
use super::media_query_handle::MediaQueryListHandle;
|
use super::media_query_handle::MediaQueryListHandle;
|
||||||
use super::pointer::PointerHandler;
|
use super::pointer::PointerHandler;
|
||||||
use super::{event, ButtonsState, ResizeScaleHandle};
|
use super::{event, ButtonsState, ResizeScaleHandle};
|
||||||
|
@ -37,6 +38,7 @@ pub struct Canvas {
|
||||||
on_dark_mode: Option<MediaQueryListHandle>,
|
on_dark_mode: Option<MediaQueryListHandle>,
|
||||||
pointer_handler: PointerHandler,
|
pointer_handler: PointerHandler,
|
||||||
on_resize_scale: Option<ResizeScaleHandle>,
|
on_resize_scale: Option<ResizeScaleHandle>,
|
||||||
|
on_intersect: Option<IntersectionObserverHandle>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Common {
|
pub struct Common {
|
||||||
|
@ -105,6 +107,7 @@ impl Canvas {
|
||||||
on_dark_mode: None,
|
on_dark_mode: None,
|
||||||
pointer_handler: PointerHandler::new(),
|
pointer_handler: PointerHandler::new(),
|
||||||
on_resize_scale: None,
|
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) {
|
pub fn request_fullscreen(&self) {
|
||||||
self.common.request_fullscreen()
|
self.common.request_fullscreen()
|
||||||
}
|
}
|
||||||
|
@ -421,6 +431,7 @@ impl Canvas {
|
||||||
self.on_dark_mode = None;
|
self.on_dark_mode = None;
|
||||||
self.pointer_handler.remove_listeners();
|
self.pointer_handler.remove_listeners();
|
||||||
self.on_resize_scale = None;
|
self.on_resize_scale = None;
|
||||||
|
self.on_intersect = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
45
src/platform_impl/web/web_sys/intersection_handle.rs
Normal file
45
src/platform_impl/web/web_sys/intersection_handle.rs
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
mod canvas;
|
mod canvas;
|
||||||
pub mod event;
|
pub mod event;
|
||||||
mod event_handle;
|
mod event_handle;
|
||||||
|
mod intersection_handle;
|
||||||
mod media_query_handle;
|
mod media_query_handle;
|
||||||
mod pointer;
|
mod pointer;
|
||||||
mod resize_scaling;
|
mod resize_scaling;
|
||||||
|
@ -16,7 +17,9 @@ use crate::dpi::LogicalSize;
|
||||||
use crate::platform::web::WindowExtWebSys;
|
use crate::platform::web::WindowExtWebSys;
|
||||||
use crate::window::Window;
|
use crate::window::Window;
|
||||||
use wasm_bindgen::closure::Closure;
|
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) {
|
pub fn throw(msg: &str) {
|
||||||
wasm_bindgen::throw_str(msg);
|
wasm_bindgen::throw_str(msg);
|
||||||
|
@ -136,4 +139,25 @@ pub fn is_dark_mode(window: &web_sys::Window) -> Option<bool> {
|
||||||
.map(|media| media.matches())
|
.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;
|
pub type RawCanvasType = HtmlCanvasElement;
|
||||||
|
|
Loading…
Reference in a new issue