diff --git a/Cargo.toml b/Cargo.toml index ddffed0c..80ca553f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -115,6 +115,6 @@ optional = true [target.'cfg(target_arch = "wasm32")'.dependencies.std_web] package = "stdweb" -version = "0.4.18" +version = "=0.4.20" optional = true - +features = ["experimental_features_which_may_break_on_minor_version_bumps"] diff --git a/src/platform_impl/web/event_loop/window_target.rs b/src/platform_impl/web/event_loop/window_target.rs index 60c917d8..d302edb8 100644 --- a/src/platform_impl/web/event_loop/window_target.rs +++ b/src/platform_impl/web/event_loop/window_target.rs @@ -1,4 +1,5 @@ use super::{backend, device, proxy::Proxy, runner, window}; +use crate::dpi::LogicalSize; use crate::event::{DeviceId, ElementState, Event, KeyboardInput, TouchPhase, WindowEvent}; use crate::event_loop::ControlFlow; use crate::window::WindowId; @@ -37,7 +38,6 @@ impl WindowTarget { pub fn register(&self, canvas: &mut backend::Canvas, id: window::Id) { let runner = self.runner.clone(); - canvas.set_attribute("data-raw-handle", &id.0.to_string()); canvas.on_blur(move || { @@ -165,5 +165,33 @@ impl WindowTarget { }, }); }); + + let runner = self.runner.clone(); + let raw = canvas.raw().clone(); + let mut intended_size = LogicalSize { + width: raw.width() as f64, + height: raw.height() as f64, + }; + canvas.on_fullscreen_change(move || { + // 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 = LogicalSize { + width: raw.width() as f64, + height: raw.height() as f64, + }; + + backend::window_size() + } else { + intended_size + }; + raw.set_width(new_size.width as u32); + raw.set_height(new_size.height as u32); + runner.send_event(Event::WindowEvent { + window_id: WindowId(id), + event: WindowEvent::Resized(new_size), + }); + runner.request_redraw(WindowId(id)); + }); } } diff --git a/src/platform_impl/web/stdweb/canvas.rs b/src/platform_impl/web/stdweb/canvas.rs index 92e46bac..a0ff5af2 100644 --- a/src/platform_impl/web/stdweb/canvas.rs +++ b/src/platform_impl/web/stdweb/canvas.rs @@ -4,11 +4,14 @@ use crate::error::OsError as RootOE; use crate::event::{ModifiersState, MouseButton, MouseScrollDelta, ScanCode, VirtualKeyCode}; use crate::platform_impl::OsError; +use std::cell::RefCell; +use std::rc::Rc; use stdweb::traits::IPointerEvent; use stdweb::unstable::TryInto; use stdweb::web::event::{ - BlurEvent, ConcreteEvent, FocusEvent, KeyDownEvent, KeyPressEvent, KeyUpEvent, MouseWheelEvent, - PointerDownEvent, PointerMoveEvent, PointerOutEvent, PointerOverEvent, PointerUpEvent, + BlurEvent, ConcreteEvent, FocusEvent, FullscreenChangeEvent, KeyDownEvent, KeyPressEvent, + KeyUpEvent, MouseWheelEvent, PointerDownEvent, PointerMoveEvent, PointerOutEvent, + PointerOverEvent, PointerUpEvent, }; use stdweb::web::html_element::CanvasElement; use stdweb::web::{ @@ -28,6 +31,8 @@ pub struct Canvas { on_mouse_press: Option, on_mouse_release: Option, on_mouse_wheel: Option, + on_fullscreen_change: Option, + wants_fullscreen: Rc>, } impl Drop for Canvas { @@ -66,6 +71,8 @@ impl Canvas { on_mouse_release: None, on_mouse_press: None, on_mouse_wheel: None, + on_fullscreen_change: None, + wants_fullscreen: Rc::new(RefCell::new(false)), }) } @@ -120,7 +127,7 @@ impl Canvas { where F: 'static + FnMut(ScanCode, Option, ModifiersState), { - self.on_keyboard_release = Some(self.add_event(move |event: KeyUpEvent| { + self.on_keyboard_release = Some(self.add_user_event(move |event: KeyUpEvent| { handler( event::scan_code(&event), event::virtual_key_code(&event), @@ -133,7 +140,7 @@ impl Canvas { where F: 'static + FnMut(ScanCode, Option, ModifiersState), { - self.on_keyboard_press = Some(self.add_event(move |event: KeyDownEvent| { + self.on_keyboard_press = Some(self.add_user_event(move |event: KeyDownEvent| { handler( event::scan_code(&event), event::virtual_key_code(&event), @@ -151,7 +158,7 @@ impl Canvas { // The `keypress` event is deprecated, but there does not seem to be a // viable/compatible alternative as of now. `beforeinput` is still widely // unsupported. - self.on_received_character = Some(self.add_event(move |event: KeyPressEvent| { + self.on_received_character = Some(self.add_user_event(move |event: KeyPressEvent| { handler(event::codepoint(&event)); })); } @@ -178,7 +185,7 @@ impl Canvas { where F: 'static + FnMut(i32, MouseButton, ModifiersState), { - self.on_mouse_release = Some(self.add_event(move |event: PointerUpEvent| { + self.on_mouse_release = Some(self.add_user_event(move |event: PointerUpEvent| { handler( event.pointer_id(), event::mouse_button(&event), @@ -191,7 +198,7 @@ impl Canvas { where F: 'static + FnMut(i32, MouseButton, ModifiersState), { - self.on_mouse_press = Some(self.add_event(move |event: PointerDownEvent| { + self.on_mouse_press = Some(self.add_user_event(move |event: PointerDownEvent| { handler( event.pointer_id(), event::mouse_button(&event), @@ -224,6 +231,13 @@ impl Canvas { })); } + pub fn on_fullscreen_change(&mut self, mut handler: F) + where + F: 'static + FnMut(), + { + self.on_fullscreen_change = Some(self.add_event(move |_: FullscreenChangeEvent| handler())); + } + fn add_event(&self, mut handler: F) -> EventListenerHandle where E: ConcreteEvent, @@ -236,4 +250,33 @@ impl Canvas { handler(event); }) } + + // The difference between add_event and add_user_event is that the latter has a special meaning + // for browser security. A user event is a deliberate action by the user (like a mouse or key + // press) and is the only time things like a fullscreen request may be successfully completed.) + fn add_user_event(&self, mut handler: F) -> EventListenerHandle + where + E: ConcreteEvent, + F: 'static + FnMut(E), + { + let wants_fullscreen = self.wants_fullscreen.clone(); + let canvas = self.raw.clone(); + + self.add_event(move |event: E| { + handler(event); + + if *wants_fullscreen.borrow() { + canvas.request_fullscreen(); + *wants_fullscreen.borrow_mut() = false; + } + }) + } + + pub fn request_fullscreen(&self) { + *self.wants_fullscreen.borrow_mut() = true; + } + + pub fn is_fullscreen(&self) -> bool { + super::is_fullscreen(&self.raw) + } } diff --git a/src/platform_impl/web/stdweb/mod.rs b/src/platform_impl/web/stdweb/mod.rs index f7caa232..d49dbf02 100644 --- a/src/platform_impl/web/stdweb/mod.rs +++ b/src/platform_impl/web/stdweb/mod.rs @@ -5,19 +5,24 @@ mod timeout; pub use self::canvas::Canvas; pub use self::timeout::Timeout; +use crate::dpi::LogicalSize; use crate::platform::web::WindowExtStdweb; use crate::window::Window; use stdweb::js; use stdweb::web::event::BeforeUnloadEvent; -use stdweb::web::html_element::CanvasElement; use stdweb::web::window; use stdweb::web::IEventTarget; +use stdweb::web::{document, html_element::CanvasElement, Element}; pub fn throw(msg: &str) { js! { throw @{msg} } } +pub fn exit_fullscreen() { + document().exit_fullscreen(); +} + pub fn on_unload(mut handler: impl FnMut() + 'static) { window().add_event_listener(move |_: BeforeUnloadEvent| handler()); } @@ -27,3 +32,21 @@ impl WindowExtStdweb for Window { self.window.canvas().raw().clone() } } + +pub fn window_size() -> LogicalSize { + let window = window(); + let width = window.inner_width() as f64; + let height = window.inner_height() as f64; + + LogicalSize { width, height } +} + +pub fn is_fullscreen(canvas: &CanvasElement) -> bool { + match document().fullscreen_element() { + Some(elem) => { + let raw: Element = canvas.clone().into(); + raw == elem + } + None => false, + } +} diff --git a/src/platform_impl/web/web_sys/canvas.rs b/src/platform_impl/web/web_sys/canvas.rs index bcf6ce4a..0543055e 100644 --- a/src/platform_impl/web/web_sys/canvas.rs +++ b/src/platform_impl/web/web_sys/canvas.rs @@ -4,8 +4,11 @@ use crate::error::OsError as RootOE; use crate::event::{ModifiersState, MouseButton, MouseScrollDelta, ScanCode, VirtualKeyCode}; use crate::platform_impl::OsError; +use std::cell::RefCell; +use std::rc::Rc; + use wasm_bindgen::{closure::Closure, JsCast}; -use web_sys::{FocusEvent, HtmlCanvasElement, KeyboardEvent, PointerEvent, WheelEvent}; +use web_sys::{Event, FocusEvent, HtmlCanvasElement, KeyboardEvent, PointerEvent, WheelEvent}; pub struct Canvas { raw: HtmlCanvasElement, @@ -20,6 +23,8 @@ pub struct Canvas { on_mouse_press: Option>, on_mouse_release: Option>, on_mouse_wheel: Option>, + on_fullscreen_change: Option>, + wants_fullscreen: Rc>, } impl Drop for Canvas { @@ -64,6 +69,8 @@ impl Canvas { on_mouse_release: None, on_mouse_press: None, on_mouse_wheel: None, + on_fullscreen_change: None, + wants_fullscreen: Rc::new(RefCell::new(false)), }) } @@ -118,26 +125,28 @@ impl Canvas { where F: 'static + FnMut(ScanCode, Option, ModifiersState), { - self.on_keyboard_release = Some(self.add_event("keyup", move |event: KeyboardEvent| { - handler( - event::scan_code(&event), - event::virtual_key_code(&event), - event::keyboard_modifiers(&event), - ); - })); + self.on_keyboard_release = + Some(self.add_user_event("keyup", move |event: KeyboardEvent| { + handler( + event::scan_code(&event), + event::virtual_key_code(&event), + event::keyboard_modifiers(&event), + ); + })); } pub fn on_keyboard_press(&mut self, mut handler: F) where F: 'static + FnMut(ScanCode, Option, ModifiersState), { - self.on_keyboard_press = Some(self.add_event("keydown", move |event: KeyboardEvent| { - handler( - event::scan_code(&event), - event::virtual_key_code(&event), - event::keyboard_modifiers(&event), - ); - })); + self.on_keyboard_press = + Some(self.add_user_event("keydown", move |event: KeyboardEvent| { + handler( + event::scan_code(&event), + event::virtual_key_code(&event), + event::keyboard_modifiers(&event), + ); + })); } pub fn on_received_character(&mut self, mut handler: F) @@ -149,10 +158,12 @@ impl Canvas { // The `keypress` event is deprecated, but there does not seem to be a // viable/compatible alternative as of now. `beforeinput` is still widely // unsupported. - self.on_received_character = - Some(self.add_event("keypress", move |event: KeyboardEvent| { + self.on_received_character = Some(self.add_user_event( + "keypress", + move |event: KeyboardEvent| { handler(event::codepoint(&event)); - })); + }, + )); } pub fn on_cursor_leave(&mut self, mut handler: F) @@ -177,26 +188,32 @@ impl Canvas { where F: 'static + FnMut(i32, MouseButton, ModifiersState), { - self.on_mouse_release = Some(self.add_event("pointerup", move |event: PointerEvent| { - handler( - event.pointer_id(), - event::mouse_button(&event), - event::mouse_modifiers(&event), - ); - })); + self.on_mouse_release = Some(self.add_user_event( + "pointerup", + move |event: PointerEvent| { + handler( + event.pointer_id(), + event::mouse_button(&event), + event::mouse_modifiers(&event), + ); + }, + )); } pub fn on_mouse_press(&mut self, mut handler: F) where F: 'static + FnMut(i32, MouseButton, ModifiersState), { - self.on_mouse_press = Some(self.add_event("pointerdown", move |event: PointerEvent| { - handler( - event.pointer_id(), - event::mouse_button(&event), - event::mouse_modifiers(&event), - ); - })); + self.on_mouse_press = Some(self.add_user_event( + "pointerdown", + move |event: PointerEvent| { + handler( + event.pointer_id(), + event::mouse_button(&event), + event::mouse_modifiers(&event), + ); + }, + )); } pub fn on_cursor_move(&mut self, mut handler: F) @@ -223,6 +240,14 @@ impl Canvas { })); } + pub fn on_fullscreen_change(&mut self, mut handler: F) + where + F: 'static + FnMut(), + { + self.on_fullscreen_change = + Some(self.add_event("fullscreenchange", move |_: Event| handler())); + } + fn add_event(&self, event_name: &str, mut handler: F) -> Closure where E: 'static + AsRef + wasm_bindgen::convert::FromWasmAbi, @@ -244,4 +269,35 @@ impl Canvas { closure } + + // The difference between add_event and add_user_event is that the latter has a special meaning + // for browser security. A user event is a deliberate action by the user (like a mouse or key + // press) and is the only time things like a fullscreen request may be successfully completed.) + fn add_user_event(&self, event_name: &str, mut handler: F) -> Closure + where + E: 'static + AsRef + wasm_bindgen::convert::FromWasmAbi, + F: 'static + FnMut(E), + { + let wants_fullscreen = self.wants_fullscreen.clone(); + let canvas = self.raw.clone(); + + self.add_event(event_name, move |event: E| { + handler(event); + + if *wants_fullscreen.borrow() { + canvas + .request_fullscreen() + .expect("Failed to enter fullscreen"); + *wants_fullscreen.borrow_mut() = false; + } + }) + } + + pub fn request_fullscreen(&self) { + *self.wants_fullscreen.borrow_mut() = true; + } + + pub fn is_fullscreen(&self) -> bool { + super::is_fullscreen(&self.raw) + } } diff --git a/src/platform_impl/web/web_sys/mod.rs b/src/platform_impl/web/web_sys/mod.rs index 811b6f68..205519d1 100644 --- a/src/platform_impl/web/web_sys/mod.rs +++ b/src/platform_impl/web/web_sys/mod.rs @@ -5,15 +5,23 @@ mod timeout; pub use self::canvas::Canvas; pub use self::timeout::Timeout; +use crate::dpi::LogicalSize; use crate::platform::web::WindowExtWebSys; use crate::window::Window; use wasm_bindgen::{closure::Closure, JsCast}; -use web_sys::{BeforeUnloadEvent, HtmlCanvasElement}; +use web_sys::{window, BeforeUnloadEvent, Element, HtmlCanvasElement}; pub fn throw(msg: &str) { wasm_bindgen::throw_str(msg); } +pub fn exit_fullscreen() { + let window = web_sys::window().expect("Failed to obtain window"); + let document = window.document().expect("Failed to obtain document"); + + document.exit_fullscreen(); +} + pub fn on_unload(mut handler: impl FnMut() + 'static) { let window = web_sys::window().expect("Failed to obtain window"); @@ -31,3 +39,32 @@ impl WindowExtWebSys for Window { self.window.canvas().raw().clone() } } + +pub fn window_size() -> LogicalSize { + let window = web_sys::window().expect("Failed to obtain window"); + 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 is_fullscreen(canvas: &HtmlCanvasElement) -> bool { + let window = window().expect("Failed to obtain window"); + let document = window.document().expect("Failed to obtain document"); + + match document.fullscreen_element() { + Some(elem) => { + let raw: Element = canvas.clone().into(); + raw == elem + } + None => false, + } +} diff --git a/src/platform_impl/web/window.rs b/src/platform_impl/web/window.rs index 43c0511d..8752d8b6 100644 --- a/src/platform_impl/web/window.rs +++ b/src/platform_impl/web/window.rs @@ -206,13 +206,20 @@ impl Window { #[inline] pub fn fullscreen(&self) -> Option { - // TODO: should there be a maximization / fullscreen API? - None + if self.canvas.is_fullscreen() { + Some(Fullscreen::Borderless(self.current_monitor())) + } else { + None + } } #[inline] - pub fn set_fullscreen(&self, _monitor: Option) { - // TODO: should there be a maximization / fullscreen API? + pub fn set_fullscreen(&self, monitor: Option) { + if monitor.is_some() { + self.canvas.request_fullscreen(); + } else if self.canvas.is_fullscreen() { + backend::exit_fullscreen(); + } } #[inline]