Skip to content

Commit

Permalink
Web: Fullscreen Overhaul (#3063)
Browse files Browse the repository at this point in the history
  • Loading branch information
daxpedda authored and kchibisov committed Oct 21, 2023
1 parent 2428224 commit 4b30f9c
Show file tree
Hide file tree
Showing 10 changed files with 219 additions and 142 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ And please only add new entries to the top of this list, right below the `# Unre
- **Breaking:** Move `Event::RedrawRequested` to `WindowEvent::RedrawRequested`.
- On macOS, fix crash in `window.set_minimized(false)`.
- On Web, enable event propagation and let `DeviceEvent`s appear after `WindowEvent`s.
- On Web, take all transient activations on the canvas and window into account to queue a fullscreen request.
- On Web, remove any fullscreen requests from the queue when an external fullscreen activation was detected.

# 0.29.1-beta

Expand Down
9 changes: 8 additions & 1 deletion examples/web.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,10 @@ mod wasm {

use softbuffer::{Surface, SurfaceExtWeb};
use wasm_bindgen::prelude::*;
use winit::{event::Event, window::Window};
use winit::{
event::{Event, WindowEvent},
window::Window,
};

#[wasm_bindgen(start)]
pub fn run() {
Expand Down Expand Up @@ -116,6 +119,10 @@ mod wasm {
// 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.
let event = match event {
Event::WindowEvent {
event: WindowEvent::RedrawRequested,
..
} => None,
Event::WindowEvent { event, .. } => Some(format!("{event:?}")),
Event::Resumed | Event::Suspended => Some(format!("{event:?}")),
_ => None,
Expand Down
26 changes: 25 additions & 1 deletion src/platform_impl/web/event_loop/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ pub struct Execution<T: 'static> {
on_key_press: OnEventHandle<KeyboardEvent>,
on_key_release: OnEventHandle<KeyboardEvent>,
on_visibility_change: OnEventHandle<web_sys::Event>,
on_touch_end: OnEventHandle<web_sys::Event>,
}

enum RunnerEnum<T: 'static> {
Expand Down Expand Up @@ -167,6 +168,7 @@ impl<T: 'static> Shared<T> {
on_key_press: RefCell::new(None),
on_key_release: RefCell::new(None),
on_visibility_change: RefCell::new(None),
on_touch_end: RefCell::new(None),
}))
}

Expand Down Expand Up @@ -324,6 +326,8 @@ impl<T: 'static> Shared<T> {
self.window().clone(),
"pointerdown",
Closure::new(move |event: PointerEvent| {
runner.transient_activation();

if !runner.device_events() {
return;
}
Expand All @@ -347,6 +351,8 @@ impl<T: 'static> Shared<T> {
self.window().clone(),
"pointerup",
Closure::new(move |event: PointerEvent| {
runner.transient_activation();

if !runner.device_events() {
return;
}
Expand All @@ -370,6 +376,8 @@ impl<T: 'static> Shared<T> {
self.window().clone(),
"keydown",
Closure::new(move |event: KeyboardEvent| {
runner.transient_activation();

if !runner.device_events() {
return;
}
Expand Down Expand Up @@ -428,6 +436,14 @@ impl<T: 'static> Shared<T> {
}
}),
));
let runner = self.clone();
*self.0.on_touch_end.borrow_mut() = Some(EventListenerHandle::new(
self.window().clone(),
"touchend",
Closure::new(move |_| {
runner.transient_activation();
}),
));
}

// Generate a strictly increasing ID
Expand Down Expand Up @@ -736,7 +752,7 @@ impl<T: 'static> Shared<T> {
self.0.device_events.set(allowed)
}

pub fn device_events(&self) -> bool {
fn device_events(&self) -> bool {
match self.0.device_events.get() {
DeviceEvents::Always => true,
DeviceEvents::WhenFocused => self.0.all_canvases.borrow().iter().any(|(_, canvas)| {
Expand All @@ -750,6 +766,14 @@ impl<T: 'static> Shared<T> {
}
}

fn transient_activation(&self) {
self.0.all_canvases.borrow().iter().for_each(|(_, canvas)| {
if let Some(canvas) = canvas.upgrade() {
canvas.borrow().transient_activation();
}
});
}

pub fn event_loop_recreation(&self, allow: bool) {
self.0.event_loop_recreation.set(allow)
}
Expand Down
2 changes: 2 additions & 0 deletions src/platform_impl/web/event_loop/window_target.rs
Original file line number Diff line number Diff line change
Expand Up @@ -660,6 +660,8 @@ impl<T> EventLoopWindowTarget<T> {

let runner = self.runner.clone();
canvas.on_animation_frame(move || runner.request_redraw(RootWindowId(id)));

canvas.on_touch_end();
}

pub fn available_monitors(&self) -> VecDequeIter<MonitorHandle> {
Expand Down
8 changes: 8 additions & 0 deletions src/platform_impl/web/web_sys/animation_frame.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,14 @@ impl AnimationFrameHandler {

self.handle.set(Some(handle));
}

pub fn cancel(&mut self) {
if let Some(handle) = self.handle.take() {
self.window
.cancel_animation_frame(handle)
.expect("Failed to cancel animation frame");
}
}
}

impl Drop for AnimationFrameHandler {
Expand Down
84 changes: 37 additions & 47 deletions src/platform_impl/web/web_sys/canvas.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
use std::cell::{Cell, RefCell};
use std::rc::Rc;
use std::cell::Cell;
use std::rc::{Rc, Weak};
use std::sync::atomic::AtomicBool;
use std::sync::{Arc, Mutex};

use js_sys::Promise;
use smol_str::SmolStr;
use wasm_bindgen::{closure::Closure, JsCast};
use wasm_bindgen_futures::JsFuture;
use web_sys::{
CssStyleDeclaration, Document, Event, FocusEvent, HtmlCanvasElement, KeyboardEvent, WheelEvent,
};
Expand All @@ -21,10 +19,11 @@ use crate::window::{WindowAttributes, WindowId as RootWindowId};
use super::super::WindowId;
use super::animation_frame::AnimationFrameHandler;
use super::event_handle::EventListenerHandle;
use super::fullscreen::FullscreenHandler;
use super::intersection_handle::IntersectionObserverHandle;
use super::media_query_handle::MediaQueryListHandle;
use super::pointer::PointerHandler;
use super::{event, fullscreen, ButtonsState, ResizeScaleHandle};
use super::{event, ButtonsState, ResizeScaleHandle};

#[allow(dead_code)]
pub struct Canvas {
Expand All @@ -33,7 +32,6 @@ pub struct Canvas {
pub has_focus: Arc<AtomicBool>,
pub is_intersecting: Option<bool>,
on_touch_start: Option<EventListenerHandle<dyn FnMut(Event)>>,
on_touch_end: Option<EventListenerHandle<dyn FnMut(Event)>>,
on_focus: Option<EventListenerHandle<dyn FnMut(FocusEvent)>>,
on_blur: Option<EventListenerHandle<dyn FnMut(FocusEvent)>>,
on_keyboard_release: Option<EventListenerHandle<dyn FnMut(KeyboardEvent)>>,
Expand All @@ -44,6 +42,7 @@ pub struct Canvas {
on_resize_scale: Option<ResizeScaleHandle>,
on_intersect: Option<IntersectionObserverHandle>,
animation_frame_handler: AnimationFrameHandler,
on_touch_end: Option<EventListenerHandle<dyn FnMut(Event)>>,
}

pub struct Common {
Expand All @@ -54,7 +53,7 @@ pub struct Common {
style: CssStyleDeclaration,
old_size: Rc<Cell<PhysicalSize<u32>>>,
current_size: Rc<Cell<PhysicalSize<u32>>>,
wants_fullscreen: Rc<RefCell<bool>>,
fullscreen_handler: Rc<FullscreenHandler>,
}

impl Canvas {
Expand Down Expand Up @@ -101,12 +100,12 @@ impl Canvas {

let common = Common {
window: window.clone(),
document,
raw: canvas,
document: document.clone(),
raw: canvas.clone(),
style,
old_size: Rc::default(),
current_size: Rc::default(),
wants_fullscreen: Rc::new(RefCell::new(false)),
fullscreen_handler: Rc::new(FullscreenHandler::new(document.clone(), canvas.clone())),
};

if let Some(size) = attr.inner_size {
Expand All @@ -130,7 +129,7 @@ impl Canvas {
}

if attr.fullscreen.is_some() {
common.request_fullscreen();
common.fullscreen_handler.request_fullscreen();
}

if attr.active {
Expand All @@ -143,7 +142,6 @@ impl Canvas {
has_focus: Arc::new(AtomicBool::new(false)),
is_intersecting: None,
on_touch_start: None,
on_touch_end: None,
on_blur: None,
on_focus: None,
on_keyboard_release: None,
Expand All @@ -154,6 +152,7 @@ impl Canvas {
on_resize_scale: None,
on_intersect: None,
animation_frame_handler: AnimationFrameHandler::new(window),
on_touch_end: None,
})
}

Expand Down Expand Up @@ -262,9 +261,8 @@ impl Canvas {
where
F: 'static + FnMut(KeyCode, Key, Option<SmolStr>, KeyLocation, bool, ModifiersState),
{
self.on_keyboard_release = Some(self.common.add_user_event(
"keyup",
move |event: KeyboardEvent| {
self.on_keyboard_release =
Some(self.common.add_event("keyup", move |event: KeyboardEvent| {
if prevent_default {
event.prevent_default();
}
Expand All @@ -278,15 +276,14 @@ impl Canvas {
event.repeat(),
modifiers,
);
},
));
}));
}

pub fn on_keyboard_press<F>(&mut self, mut handler: F, prevent_default: bool)
where
F: 'static + FnMut(KeyCode, Key, Option<SmolStr>, KeyLocation, bool, ModifiersState),
{
self.on_keyboard_press = Some(self.common.add_user_event(
self.on_keyboard_press = Some(self.common.add_transient_event(
"keydown",
move |event: KeyboardEvent| {
if prevent_default {
Expand Down Expand Up @@ -446,12 +443,20 @@ impl Canvas {
self.animation_frame_handler.on_animation_frame(f)
}

pub(crate) fn on_touch_end(&mut self) {
self.on_touch_end = Some(self.common.add_transient_event("touchend", |_| {}));
}

pub fn request_fullscreen(&self) {
self.common.request_fullscreen()
self.common.fullscreen_handler.request_fullscreen()
}

pub fn exit_fullscreen(&self) {
self.common.fullscreen_handler.exit_fullscreen()
}

pub fn is_fullscreen(&self) -> bool {
self.common.is_fullscreen()
self.common.fullscreen_handler.is_fullscreen()
}

pub fn request_animation_frame(&self) {
Expand Down Expand Up @@ -502,7 +507,12 @@ impl Canvas {
}
}

pub(crate) fn transient_activation(&self) {
self.common.fullscreen_handler.transient_activation()
}

pub fn remove_listeners(&mut self) {
self.on_touch_start = None;
self.on_focus = None;
self.on_blur = None;
self.on_keyboard_release = None;
Expand All @@ -512,6 +522,9 @@ impl Canvas {
self.pointer_handler.remove_listeners();
self.on_resize_scale = None;
self.on_intersect = None;
self.animation_frame_handler.cancel();
self.on_touch_end = None;
self.common.fullscreen_handler.cancel();
}
}

Expand All @@ -531,7 +544,7 @@ impl Common {
// 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.)
pub fn add_user_event<E, F>(
pub fn add_transient_event<E, F>(
&self,
event_name: &'static str,
mut handler: F,
Expand All @@ -540,37 +553,14 @@ impl Common {
E: 'static + AsRef<web_sys::Event> + wasm_bindgen::convert::FromWasmAbi,
F: 'static + FnMut(E),
{
let wants_fullscreen = self.wants_fullscreen.clone();
let canvas = self.raw.clone();
let fullscreen_handler = Rc::downgrade(&self.fullscreen_handler);

self.add_event(event_name, move |event: E| {
handler(event);

if *wants_fullscreen.borrow() {
fullscreen::request_fullscreen(&canvas).expect("Failed to enter fullscreen");
*wants_fullscreen.borrow_mut() = false;
if let Some(fullscreen_handler) = Weak::upgrade(&fullscreen_handler) {
fullscreen_handler.transient_activation()
}
})
}

pub fn request_fullscreen(&self) {
// This should return a `Promise`, but Safari v<16.4 is not up-to-date with the spec.
match fullscreen::request_fullscreen(&self.raw) {
Ok(value) if !value.is_undefined() => {
let promise: Promise = value.unchecked_into();
let wants_fullscreen = self.wants_fullscreen.clone();
wasm_bindgen_futures::spawn_local(async move {
if JsFuture::from(promise).await.is_err() {
*wants_fullscreen.borrow_mut() = true
}
});
}
// We are on Safari v<16.4, let's try again on the next transient activation.
_ => *self.wants_fullscreen.borrow_mut() = true,
}
}

pub fn is_fullscreen(&self) -> bool {
super::is_fullscreen(&self.document, &self.raw)
}
}
Loading

0 comments on commit 4b30f9c

Please sign in to comment.