Web: Implement MonitorHandle (#3801)

Requires getting permission from the user to get "detailed" support.
Also enables users to go fullscreen on specific monitors.
Exposes platform-specific orientation API.

Most functionality depends on browser support, currently only Chromium.
This commit is contained in:
daxpedda 2024-07-23 20:33:10 +02:00 committed by GitHub
parent 2e97ab3d4f
commit a0bc3e5dc8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 1493 additions and 120 deletions

View file

@ -7,7 +7,7 @@ use smol_str::SmolStr;
use wasm_bindgen::closure::Closure;
use wasm_bindgen::JsCast;
use web_sys::{
CssStyleDeclaration, Document, Event, FocusEvent, HtmlCanvasElement, KeyboardEvent,
CssStyleDeclaration, Document, Event, FocusEvent, HtmlCanvasElement, KeyboardEvent, Navigator,
PointerEvent, WheelEvent,
};
@ -24,7 +24,7 @@ use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize};
use crate::error::OsError as RootOE;
use crate::event::{Force, InnerSizeWriter, MouseButton, MouseScrollDelta};
use crate::keyboard::{Key, KeyLocation, ModifiersState, PhysicalKey};
use crate::platform_impl::OsError;
use crate::platform_impl::{Fullscreen, OsError};
use crate::window::{WindowAttributes, WindowId as RootWindowId};
#[allow(dead_code)]
@ -57,6 +57,7 @@ struct Handlers {
pub struct Common {
pub window: web_sys::Window,
navigator: Navigator,
pub document: Document,
/// Note: resizing the HTMLCanvasElement should go through `backend::set_canvas_size` to ensure
/// the DPI factor is maintained. Note: this is read-only because we use a pointer to this
@ -78,6 +79,7 @@ impl Canvas {
main_thread: MainThreadMarker,
id: WindowId,
window: web_sys::Window,
navigator: Navigator,
document: Document,
attr: WindowAttributes,
) -> Result<Self, RootOE> {
@ -116,6 +118,7 @@ impl Canvas {
let common = Common {
window: window.clone(),
document: document.clone(),
navigator,
raw: Rc::new(canvas.clone()),
style,
old_size: Rc::default(),
@ -142,8 +145,14 @@ impl Canvas {
super::set_canvas_position(&common.document, &common.raw, &common.style, position);
}
if attr.fullscreen.is_some() {
fullscreen::request_fullscreen(&document, &canvas);
if let Some(fullscreen) = attr.fullscreen {
fullscreen::request_fullscreen(
main_thread,
&window,
&document,
&canvas,
fullscreen.into(),
);
}
if attr.active {
@ -222,6 +231,11 @@ impl Canvas {
&self.common.window
}
#[inline]
pub fn navigator(&self) -> &Navigator {
&self.common.navigator
}
#[inline]
pub fn document(&self) -> &Document {
&self.common.document
@ -445,8 +459,14 @@ impl Canvas {
}));
}
pub fn request_fullscreen(&self) {
fullscreen::request_fullscreen(self.document(), self.raw());
pub(crate) fn request_fullscreen(&self, fullscreen: Fullscreen) {
fullscreen::request_fullscreen(
self.main_thread,
self.window(),
self.document(),
self.raw(),
fullscreen,
);
}
pub fn exit_fullscreen(&self) {

View file

@ -4,7 +4,7 @@ use dpi::{LogicalPosition, PhysicalPosition, Position};
use smol_str::SmolStr;
use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen::{JsCast, JsValue};
use web_sys::{KeyboardEvent, MouseEvent, PointerEvent, WheelEvent};
use web_sys::{KeyboardEvent, MouseEvent, Navigator, PointerEvent, WheelEvent};
use super::Engine;
use crate::event::{MouseButton, MouseScrollDelta};
@ -108,8 +108,8 @@ pub enum MouseDelta {
}
impl MouseDelta {
pub fn init(window: &web_sys::Window, event: &PointerEvent) -> Self {
match super::engine(window) {
pub fn init(navigator: &Navigator, event: &PointerEvent) -> Self {
match super::engine(navigator) {
Some(Engine::Chromium) => Self::Chromium,
// Firefox has wrong movement values in coalesced events.
Some(Engine::Gecko) if has_coalesced_events_support(event) => Self::Gecko {

View file

@ -1,16 +1,23 @@
use std::cell::OnceCell;
use js_sys::Promise;
use js_sys::{Object, Promise};
use tracing::error;
use wasm_bindgen::closure::Closure;
use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen::{JsCast, JsValue};
use web_sys::{Document, Element, HtmlCanvasElement};
use web_sys::{console, Document, Element, HtmlCanvasElement, Window};
pub fn request_fullscreen(document: &Document, canvas: &HtmlCanvasElement) {
if is_fullscreen(document, canvas) {
return;
}
use super::super::main_thread::MainThreadMarker;
use super::super::monitor::{self, ScreenDetailed};
use crate::platform_impl::Fullscreen;
pub(crate) fn request_fullscreen(
main_thread: MainThreadMarker,
window: &Window,
document: &Document,
canvas: &HtmlCanvasElement,
fullscreen: Fullscreen,
) {
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(extends = HtmlCanvasElement)]
@ -19,21 +26,66 @@ pub fn request_fullscreen(document: &Document, canvas: &HtmlCanvasElement) {
#[wasm_bindgen(method, js_name = requestFullscreen)]
fn request_fullscreen(this: &RequestFullscreen) -> Promise;
#[wasm_bindgen(method, js_name = requestFullscreen)]
fn request_fullscreen_with_options(
this: &RequestFullscreen,
options: &FullscreenOptions,
) -> Promise;
#[wasm_bindgen(method, js_name = webkitRequestFullscreen)]
fn webkit_request_fullscreen(this: &RequestFullscreen);
type FullscreenOptions;
#[wasm_bindgen(method, setter, js_name = screen)]
fn set_screen(this: &FullscreenOptions, screen: &ScreenDetailed);
}
thread_local! {
static REJECT_HANDLER: Closure<dyn FnMut(JsValue)> = Closure::new(|error| {
console::error_1(&error);
error!("Failed to transition to full screen mode")
});
}
if is_fullscreen(document, canvas) {
return;
}
let canvas: &RequestFullscreen = canvas.unchecked_ref();
if has_fullscreen_api_support(canvas) {
thread_local! {
static REJECT_HANDLER: Closure<dyn FnMut(JsValue)> = Closure::new(|_| ());
}
REJECT_HANDLER.with(|handler| {
let _ = canvas.request_fullscreen().catch(handler);
});
} else {
canvas.webkit_request_fullscreen();
match fullscreen {
Fullscreen::Exclusive(_) => error!("Exclusive full screen mode is not supported"),
Fullscreen::Borderless(Some(monitor)) => {
if !monitor::has_screen_details_support(window) {
error!(
"Fullscreen mode selecting a specific screen is not supported by this browser"
);
return;
}
if let Some(monitor) = monitor.detailed(main_thread) {
let options: FullscreenOptions = Object::new().unchecked_into();
options.set_screen(&monitor);
REJECT_HANDLER.with(|handler| {
let _ = canvas.request_fullscreen_with_options(&options).catch(handler);
});
} else {
error!(
"Selecting a specific screen for fullscreen mode requires a detailed screen. \
See `MonitorHandleExtWeb::is_detailed()`."
)
}
},
Fullscreen::Borderless(None) => {
if has_fullscreen_api_support(canvas) {
REJECT_HANDLER.with(|handler| {
let _ = canvas.request_fullscreen().catch(handler);
});
} else {
canvas.webkit_request_fullscreen();
}
},
}
}

View file

@ -15,9 +15,7 @@ use js_sys::Array;
use wasm_bindgen::closure::Closure;
use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen::JsCast;
use web_sys::{
Document, HtmlCanvasElement, Navigator, PageTransitionEvent, VisibilityState, Window,
};
use web_sys::{Document, HtmlCanvasElement, Navigator, PageTransitionEvent, VisibilityState};
pub use self::canvas::{Canvas, Style};
pub use self::event::ButtonsState;
@ -182,15 +180,15 @@ thread_local! {
static USER_AGENT_DATA: OnceCell<UserAgentData> = const { OnceCell::new() };
}
pub fn chrome_linux(window: &Window) -> bool {
USER_AGENT_DATA.with(|data| data.get_or_init(|| user_agent(window)).chrome_linux)
pub fn chrome_linux(navigator: &Navigator) -> bool {
USER_AGENT_DATA.with(|data| data.get_or_init(|| user_agent(navigator)).chrome_linux)
}
pub fn engine(window: &Window) -> Option<Engine> {
USER_AGENT_DATA.with(|data| data.get_or_init(|| user_agent(window)).engine)
pub fn engine(navigator: &Navigator) -> Option<Engine> {
USER_AGENT_DATA.with(|data| data.get_or_init(|| user_agent(navigator)).engine)
}
fn user_agent(window: &Window) -> UserAgentData {
fn user_agent(navigator: &Navigator) -> UserAgentData {
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(extends = Navigator)]
@ -213,7 +211,7 @@ fn user_agent(window: &Window) -> UserAgentData {
fn brand(this: &NavigatorUaBrandVersion) -> String;
}
let navigator: NavigatorExt = window.navigator().unchecked_into();
let navigator: &NavigatorExt = navigator.unchecked_ref();
if let Some(data) = navigator.user_agent_data() {
let engine = 'engine: {