mirror of
https://github.com/italicsjenga/winit-sonoma-fix.git
synced 2024-12-23 13:51:30 +11:00
On Web, implement Send
and Sync
where appropriate (#2834)
This commit is contained in:
parent
eb2d3894ef
commit
8f7f3efc0d
|
@ -68,6 +68,9 @@ And please only add new entries to the top of this list, right below the `# Unre
|
||||||
- **Breaking:** On Web, dropped support for Safari versions below 13.
|
- **Breaking:** On Web, dropped support for Safari versions below 13.
|
||||||
- On Web, prevent clicks on the canvas to select text.
|
- On Web, prevent clicks on the canvas to select text.
|
||||||
- On Web, use high-frequency pointer input events when supported by the browser.
|
- On Web, use high-frequency pointer input events when supported by the browser.
|
||||||
|
- On Web, `EventLoopProxy` now implements `Send`.
|
||||||
|
- On Web, `Window` now implements `Send` and `Sync`.
|
||||||
|
- **Breaking:** `WindowExtWebSys::canvas()` now returns an `Option`.
|
||||||
|
|
||||||
# 0.28.6
|
# 0.28.6
|
||||||
|
|
||||||
|
|
|
@ -153,6 +153,7 @@ features = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[target.'cfg(target_family = "wasm")'.dependencies]
|
[target.'cfg(target_family = "wasm")'.dependencies]
|
||||||
|
atomic-waker = "1"
|
||||||
js-sys = "0.3"
|
js-sys = "0.3"
|
||||||
wasm-bindgen = "0.2.45"
|
wasm-bindgen = "0.2.45"
|
||||||
wasm-bindgen-futures = "0.4"
|
wasm-bindgen-futures = "0.4"
|
||||||
|
|
3
clippy.toml
Normal file
3
clippy.toml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
disallowed-methods = [
|
||||||
|
{ path = "web_sys::window", reason = "is not available in every context" },
|
||||||
|
]
|
|
@ -1,4 +1,4 @@
|
||||||
#![allow(clippy::single_match)]
|
#![allow(clippy::disallowed_methods, clippy::single_match)]
|
||||||
|
|
||||||
use winit::{
|
use winit::{
|
||||||
event::{Event, WindowEvent},
|
event::{Event, WindowEvent},
|
||||||
|
@ -52,7 +52,7 @@ mod wasm {
|
||||||
pub fn insert_canvas_and_create_log_list(window: &Window) -> web_sys::Element {
|
pub fn insert_canvas_and_create_log_list(window: &Window) -> web_sys::Element {
|
||||||
use winit::platform::web::WindowExtWebSys;
|
use winit::platform::web::WindowExtWebSys;
|
||||||
|
|
||||||
let canvas = window.canvas();
|
let canvas = window.canvas().unwrap();
|
||||||
|
|
||||||
let window = web_sys::window().unwrap();
|
let window = web_sys::window().unwrap();
|
||||||
let document = window.document().unwrap();
|
let document = window.document().unwrap();
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#![allow(clippy::disallowed_methods)]
|
||||||
|
|
||||||
pub fn main() {
|
pub fn main() {
|
||||||
println!("This example must be run with cargo run-wasm --example web_aspect_ratio")
|
println!("This example must be run with cargo run-wasm --example web_aspect_ratio")
|
||||||
}
|
}
|
||||||
|
@ -66,7 +68,7 @@ This example demonstrates the desired future functionality which will possibly b
|
||||||
let body = document.body().unwrap();
|
let body = document.body().unwrap();
|
||||||
|
|
||||||
// Set a background color for the canvas to make it easier to tell the where the canvas is for debugging purposes.
|
// Set a background color for the canvas to make it easier to tell the where the canvas is for debugging purposes.
|
||||||
let canvas = window.canvas();
|
let canvas = window.canvas().unwrap();
|
||||||
canvas
|
canvas
|
||||||
.style()
|
.style()
|
||||||
.set_css_text("display: block; background-color: crimson; margin: auto; width: 50%; aspect-ratio: 4 / 1;");
|
.set_css_text("display: block; background-color: crimson; margin: auto; width: 50%; aspect-ratio: 4 / 1;");
|
||||||
|
|
|
@ -12,7 +12,8 @@ use crate::window::WindowBuilder;
|
||||||
use web_sys::HtmlCanvasElement;
|
use web_sys::HtmlCanvasElement;
|
||||||
|
|
||||||
pub trait WindowExtWebSys {
|
pub trait WindowExtWebSys {
|
||||||
fn canvas(&self) -> HtmlCanvasElement;
|
/// Only returns the canvas if called from inside the window.
|
||||||
|
fn canvas(&self) -> Option<HtmlCanvasElement>;
|
||||||
|
|
||||||
/// Whether the browser reports the preferred color scheme to be "dark".
|
/// Whether the browser reports the preferred color scheme to be "dark".
|
||||||
fn is_dark_mode(&self) -> bool;
|
fn is_dark_mode(&self) -> bool;
|
||||||
|
|
314
src/platform_impl/web/async.rs
Normal file
314
src/platform_impl/web/async.rs
Normal file
|
@ -0,0 +1,314 @@
|
||||||
|
use atomic_waker::AtomicWaker;
|
||||||
|
use once_cell::unsync::Lazy;
|
||||||
|
use std::future;
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
use std::ops::Deref;
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
use std::sync::mpsc::{self, Receiver, RecvError, SendError, Sender, TryRecvError};
|
||||||
|
use std::sync::{Arc, Condvar, Mutex, RwLock};
|
||||||
|
use std::task::Poll;
|
||||||
|
use wasm_bindgen::prelude::wasm_bindgen;
|
||||||
|
use wasm_bindgen::{JsCast, JsValue};
|
||||||
|
|
||||||
|
// Unsafe wrapper type that allows us to use `T` when it's not `Send` from other threads.
|
||||||
|
// `value` **must** only be accessed on the main thread.
|
||||||
|
pub struct MainThreadSafe<const SYNC: bool, T: 'static, E: 'static> {
|
||||||
|
// We wrap this in an `Arc` to allow it to be safely cloned without accessing the value.
|
||||||
|
// The `RwLock` lets us safely drop in any thread.
|
||||||
|
// The `Option` lets us safely drop `T` only in the main thread, while letting other threads drop `None`.
|
||||||
|
value: Arc<RwLock<Option<T>>>,
|
||||||
|
handler: fn(&RwLock<Option<T>>, E),
|
||||||
|
sender: AsyncSender<E>,
|
||||||
|
// Prevent's `Send` or `Sync` to be automatically implemented.
|
||||||
|
local: PhantomData<*const ()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const SYNC: bool, T, E> MainThreadSafe<SYNC, T, E> {
|
||||||
|
thread_local! {
|
||||||
|
static MAIN_THREAD: Lazy<bool> = Lazy::new(|| {
|
||||||
|
#[wasm_bindgen]
|
||||||
|
extern "C" {
|
||||||
|
#[derive(Clone)]
|
||||||
|
type Global;
|
||||||
|
|
||||||
|
#[wasm_bindgen(method, getter, js_name = Window)]
|
||||||
|
fn window(this: &Global) -> JsValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let global: Global = js_sys::global().unchecked_into();
|
||||||
|
!global.window().is_undefined()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn new(value: T, handler: fn(&RwLock<Option<T>>, E)) -> Option<Self> {
|
||||||
|
Self::MAIN_THREAD.with(|safe| {
|
||||||
|
if !*safe.deref() {
|
||||||
|
panic!("only callable from inside the `Window`")
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let value = Arc::new(RwLock::new(Some(value)));
|
||||||
|
|
||||||
|
let (sender, receiver) = channel::<E>();
|
||||||
|
|
||||||
|
wasm_bindgen_futures::spawn_local({
|
||||||
|
let value = Arc::clone(&value);
|
||||||
|
async move {
|
||||||
|
while let Ok(event) = receiver.next().await {
|
||||||
|
handler(&value, event)
|
||||||
|
}
|
||||||
|
|
||||||
|
// An error was returned because the channel was closed, which
|
||||||
|
// happens when all senders are dropped.
|
||||||
|
value.write().unwrap().take().unwrap();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Some(Self {
|
||||||
|
value,
|
||||||
|
handler,
|
||||||
|
sender,
|
||||||
|
local: PhantomData,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send(&self, event: E) {
|
||||||
|
Self::MAIN_THREAD.with(|is_main_thread| {
|
||||||
|
if *is_main_thread.deref() {
|
||||||
|
(self.handler)(&self.value, event)
|
||||||
|
} else {
|
||||||
|
self.sender.send(event).unwrap()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_main_thread(&self) -> bool {
|
||||||
|
Self::MAIN_THREAD.with(|is_main_thread| *is_main_thread.deref())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with<R>(&self, f: impl FnOnce(&T) -> R) -> Option<R> {
|
||||||
|
Self::MAIN_THREAD.with(|is_main_thread| {
|
||||||
|
if *is_main_thread.deref() {
|
||||||
|
Some(f(self.value.read().unwrap().as_ref().unwrap()))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_mut<R>(&self, f: impl FnOnce(&mut T) -> R) -> Option<R> {
|
||||||
|
Self::MAIN_THREAD.with(|is_main_thread| {
|
||||||
|
if *is_main_thread.deref() {
|
||||||
|
Some(f(self.value.write().unwrap().as_mut().unwrap()))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const SYNC: bool, T, E> Clone for MainThreadSafe<SYNC, T, E> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
value: self.value.clone(),
|
||||||
|
handler: self.handler,
|
||||||
|
sender: self.sender.clone(),
|
||||||
|
local: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl<const SYNC: bool, T, E> Send for MainThreadSafe<SYNC, T, E> {}
|
||||||
|
unsafe impl<T, E> Sync for MainThreadSafe<true, T, E> {}
|
||||||
|
|
||||||
|
fn channel<T>() -> (AsyncSender<T>, AsyncReceiver<T>) {
|
||||||
|
let (sender, receiver) = mpsc::channel();
|
||||||
|
let sender = Arc::new(Mutex::new(sender));
|
||||||
|
let waker = Arc::new(AtomicWaker::new());
|
||||||
|
let closed = Arc::new(AtomicBool::new(false));
|
||||||
|
|
||||||
|
let sender = AsyncSender {
|
||||||
|
sender,
|
||||||
|
closed: closed.clone(),
|
||||||
|
waker: Arc::clone(&waker),
|
||||||
|
};
|
||||||
|
let receiver = AsyncReceiver {
|
||||||
|
receiver,
|
||||||
|
closed,
|
||||||
|
waker,
|
||||||
|
};
|
||||||
|
|
||||||
|
(sender, receiver)
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AsyncSender<T> {
|
||||||
|
// We need to wrap it into a `Mutex` to make it `Sync`. So the sender can't
|
||||||
|
// be accessed on the main thread, as it could block. Additionally we need
|
||||||
|
// to wrap it in an `Arc` to make it clonable on the main thread without
|
||||||
|
// having to block.
|
||||||
|
sender: Arc<Mutex<Sender<T>>>,
|
||||||
|
closed: Arc<AtomicBool>,
|
||||||
|
waker: Arc<AtomicWaker>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> AsyncSender<T> {
|
||||||
|
pub fn send(&self, event: T) -> Result<(), SendError<T>> {
|
||||||
|
self.sender.lock().unwrap().send(event)?;
|
||||||
|
self.waker.wake();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Clone for AsyncSender<T> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
sender: self.sender.clone(),
|
||||||
|
waker: self.waker.clone(),
|
||||||
|
closed: self.closed.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Drop for AsyncSender<T> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
// If it's the last + the one held by the receiver make sure to wake it
|
||||||
|
// up and tell it that all receiver have dropped.
|
||||||
|
if Arc::strong_count(&self.closed) == 2 {
|
||||||
|
self.closed.store(true, Ordering::Relaxed);
|
||||||
|
self.waker.wake()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AsyncReceiver<T> {
|
||||||
|
receiver: Receiver<T>,
|
||||||
|
closed: Arc<AtomicBool>,
|
||||||
|
waker: Arc<AtomicWaker>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> AsyncReceiver<T> {
|
||||||
|
pub async fn next(&self) -> Result<T, RecvError> {
|
||||||
|
future::poll_fn(|cx| match self.receiver.try_recv() {
|
||||||
|
Ok(event) => Poll::Ready(Ok(event)),
|
||||||
|
Err(TryRecvError::Empty) => {
|
||||||
|
if self.closed.load(Ordering::Relaxed) {
|
||||||
|
return Poll::Ready(Err(RecvError));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.waker.register(cx.waker());
|
||||||
|
|
||||||
|
match self.receiver.try_recv() {
|
||||||
|
Ok(event) => Poll::Ready(Ok(event)),
|
||||||
|
Err(TryRecvError::Empty) => {
|
||||||
|
if self.closed.load(Ordering::Relaxed) {
|
||||||
|
Poll::Ready(Err(RecvError))
|
||||||
|
} else {
|
||||||
|
Poll::Pending
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(TryRecvError::Disconnected) => Poll::Ready(Err(RecvError)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(TryRecvError::Disconnected) => Poll::Ready(Err(RecvError)),
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Dispatcher<T: 'static>(MainThreadSafe<true, T, Closure<T>>);
|
||||||
|
|
||||||
|
pub enum Closure<T> {
|
||||||
|
Ref(Box<dyn FnOnce(&T) + Send>),
|
||||||
|
RefMut(Box<dyn FnOnce(&mut T) + Send>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Dispatcher<T> {
|
||||||
|
#[track_caller]
|
||||||
|
pub fn new(value: T) -> Option<Self> {
|
||||||
|
MainThreadSafe::new(value, |value, closure| match closure {
|
||||||
|
Closure::Ref(f) => f(value.read().unwrap().as_ref().unwrap()),
|
||||||
|
Closure::RefMut(f) => f(value.write().unwrap().as_mut().unwrap()),
|
||||||
|
})
|
||||||
|
.map(Self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dispatch(&self, f: impl 'static + FnOnce(&T) + Send) {
|
||||||
|
if self.is_main_thread() {
|
||||||
|
self.0.with(f).unwrap()
|
||||||
|
} else {
|
||||||
|
self.0.send(Closure::Ref(Box::new(f)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dispatch_mut(&self, f: impl 'static + FnOnce(&mut T) + Send) {
|
||||||
|
if self.is_main_thread() {
|
||||||
|
self.0.with_mut(f).unwrap()
|
||||||
|
} else {
|
||||||
|
self.0.send(Closure::RefMut(Box::new(f)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn queue<R: 'static + Send>(&self, f: impl 'static + FnOnce(&T) -> R + Send) -> R {
|
||||||
|
if self.is_main_thread() {
|
||||||
|
self.0.with(f).unwrap()
|
||||||
|
} else {
|
||||||
|
let pair = Arc::new((Mutex::new(None), Condvar::new()));
|
||||||
|
let closure = Closure::Ref(Box::new({
|
||||||
|
let pair = pair.clone();
|
||||||
|
move |value| {
|
||||||
|
*pair.0.lock().unwrap() = Some(f(value));
|
||||||
|
pair.1.notify_one();
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
self.0.send(closure);
|
||||||
|
|
||||||
|
let mut started = pair.0.lock().unwrap();
|
||||||
|
|
||||||
|
while started.is_none() {
|
||||||
|
started = pair.1.wait(started).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
started.take().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Deref for Dispatcher<T> {
|
||||||
|
type Target = MainThreadSafe<true, T, Closure<T>>;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChannelValue<T, E> = MainThreadSafe<false, (T, fn(&T, E)), E>;
|
||||||
|
|
||||||
|
pub struct Channel<T: 'static, E: 'static>(ChannelValue<T, E>);
|
||||||
|
|
||||||
|
impl<T, E> Channel<T, E> {
|
||||||
|
pub fn new(value: T, handler: fn(&T, E)) -> Option<Self> {
|
||||||
|
MainThreadSafe::new((value, handler), |runner, event| {
|
||||||
|
let lock = runner.read().unwrap();
|
||||||
|
let (value, handler) = lock.as_ref().unwrap();
|
||||||
|
handler(value, event);
|
||||||
|
})
|
||||||
|
.map(Self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, E> Clone for Channel<T, E> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self(self.0.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, E> Deref for Channel<T, E> {
|
||||||
|
type Target = ChannelValue<T, E>;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,18 +1,24 @@
|
||||||
use super::runner;
|
use super::runner;
|
||||||
use crate::event::Event;
|
use crate::event::Event;
|
||||||
use crate::event_loop::EventLoopClosed;
|
use crate::event_loop::EventLoopClosed;
|
||||||
|
use crate::platform_impl::platform::r#async::Channel;
|
||||||
|
|
||||||
pub struct EventLoopProxy<T: 'static> {
|
pub struct EventLoopProxy<T: 'static> {
|
||||||
runner: runner::Shared<T>,
|
runner: Channel<runner::Shared<T>, T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: 'static> EventLoopProxy<T> {
|
impl<T: 'static> EventLoopProxy<T> {
|
||||||
pub fn new(runner: runner::Shared<T>) -> Self {
|
pub fn new(runner: runner::Shared<T>) -> Self {
|
||||||
Self { runner }
|
Self {
|
||||||
|
runner: Channel::new(runner, |runner, event| {
|
||||||
|
runner.send_event(Event::UserEvent(event))
|
||||||
|
})
|
||||||
|
.unwrap(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
|
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
|
||||||
self.runner.send_event(Event::UserEvent(event));
|
self.runner.send(event);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@ pub struct Execution<T: 'static> {
|
||||||
runner: RefCell<RunnerEnum<T>>,
|
runner: RefCell<RunnerEnum<T>>,
|
||||||
events: RefCell<VecDeque<Event<'static, T>>>,
|
events: RefCell<VecDeque<Event<'static, T>>>,
|
||||||
id: RefCell<u32>,
|
id: RefCell<u32>,
|
||||||
|
window: web_sys::Window,
|
||||||
all_canvases: RefCell<Vec<(WindowId, Weak<RefCell<backend::Canvas>>)>>,
|
all_canvases: RefCell<Vec<(WindowId, Weak<RefCell<backend::Canvas>>)>>,
|
||||||
redraw_pending: RefCell<HashSet<WindowId>>,
|
redraw_pending: RefCell<HashSet<WindowId>>,
|
||||||
destroy_pending: RefCell<VecDeque<WindowId>>,
|
destroy_pending: RefCell<VecDeque<WindowId>>,
|
||||||
|
@ -102,6 +103,8 @@ impl<T: 'static> Shared<T> {
|
||||||
Shared(Rc::new(Execution {
|
Shared(Rc::new(Execution {
|
||||||
runner: RefCell::new(RunnerEnum::Pending),
|
runner: RefCell::new(RunnerEnum::Pending),
|
||||||
events: RefCell::new(VecDeque::new()),
|
events: RefCell::new(VecDeque::new()),
|
||||||
|
#[allow(clippy::disallowed_methods)]
|
||||||
|
window: web_sys::window().expect("only callable from inside the `Window`"),
|
||||||
id: RefCell::new(0),
|
id: RefCell::new(0),
|
||||||
all_canvases: RefCell::new(Vec::new()),
|
all_canvases: RefCell::new(Vec::new()),
|
||||||
redraw_pending: RefCell::new(HashSet::new()),
|
redraw_pending: RefCell::new(HashSet::new()),
|
||||||
|
@ -111,6 +114,10 @@ impl<T: 'static> Shared<T> {
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn window(&self) -> &web_sys::Window {
|
||||||
|
&self.0.window
|
||||||
|
}
|
||||||
|
|
||||||
pub fn add_canvas(&self, id: WindowId, canvas: &Rc<RefCell<backend::Canvas>>) {
|
pub fn add_canvas(&self, id: WindowId, canvas: &Rc<RefCell<backend::Canvas>>) {
|
||||||
self.0
|
self.0
|
||||||
.all_canvases
|
.all_canvases
|
||||||
|
@ -135,15 +142,19 @@ impl<T: 'static> Shared<T> {
|
||||||
|
|
||||||
let close_instance = self.clone();
|
let close_instance = self.clone();
|
||||||
*self.0.unload_event_handle.borrow_mut() =
|
*self.0.unload_event_handle.borrow_mut() =
|
||||||
Some(backend::on_unload(move || close_instance.handle_unload()));
|
Some(backend::on_unload(self.window(), move || {
|
||||||
|
close_instance.handle_unload()
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn set_on_scale_change<F>(&self, handler: F)
|
pub(crate) fn set_on_scale_change<F>(&self, handler: F)
|
||||||
where
|
where
|
||||||
F: 'static + FnMut(ScaleChangeArgs),
|
F: 'static + FnMut(ScaleChangeArgs),
|
||||||
{
|
{
|
||||||
*self.0.scale_change_detector.borrow_mut() =
|
*self.0.scale_change_detector.borrow_mut() = Some(backend::ScaleChangeDetector::new(
|
||||||
Some(backend::ScaleChangeDetector::new(handler));
|
self.window().clone(),
|
||||||
|
handler,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate a strictly increasing ID
|
// Generate a strictly increasing ID
|
||||||
|
@ -340,7 +351,7 @@ impl<T: 'static> Shared<T> {
|
||||||
);
|
);
|
||||||
|
|
||||||
// Then we resize the canvas to the new size and send a `Resized` event:
|
// Then we resize the canvas to the new size and send a `Resized` event:
|
||||||
backend::set_canvas_size(&canvas, crate::dpi::Size::Physical(new_size));
|
backend::set_canvas_size(self.window(), &canvas, crate::dpi::Size::Physical(new_size));
|
||||||
self.handle_single_event_sync(
|
self.handle_single_event_sync(
|
||||||
Event::WindowEvent {
|
Event::WindowEvent {
|
||||||
window_id: id,
|
window_id: id,
|
||||||
|
@ -431,7 +442,10 @@ impl<T: 'static> Shared<T> {
|
||||||
ControlFlow::Poll => {
|
ControlFlow::Poll => {
|
||||||
let cloned = self.clone();
|
let cloned = self.clone();
|
||||||
State::Poll {
|
State::Poll {
|
||||||
request: backend::AnimationFrameRequest::new(move || cloned.poll()),
|
request: backend::AnimationFrameRequest::new(
|
||||||
|
self.window().clone(),
|
||||||
|
move || cloned.poll(),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ControlFlow::Wait => State::Wait {
|
ControlFlow::Wait => State::Wait {
|
||||||
|
@ -452,6 +466,7 @@ impl<T: 'static> Shared<T> {
|
||||||
start,
|
start,
|
||||||
end,
|
end,
|
||||||
timeout: backend::Timeout::new(
|
timeout: backend::Timeout::new(
|
||||||
|
self.window().clone(),
|
||||||
move || cloned.resume_time_reached(start, end),
|
move || cloned.resume_time_reached(start, end),
|
||||||
delay,
|
delay,
|
||||||
),
|
),
|
||||||
|
|
|
@ -3,6 +3,8 @@ use std::clone::Clone;
|
||||||
use std::collections::{vec_deque::IntoIter as VecDequeIter, VecDeque};
|
use std::collections::{vec_deque::IntoIter as VecDequeIter, VecDeque};
|
||||||
use std::iter;
|
use std::iter;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use raw_window_handle::{RawDisplayHandle, WebDisplayHandle};
|
use raw_window_handle::{RawDisplayHandle, WebDisplayHandle};
|
||||||
|
|
||||||
|
@ -84,7 +86,7 @@ impl<T> EventLoopWindowTarget<T> {
|
||||||
canvas: &Rc<RefCell<backend::Canvas>>,
|
canvas: &Rc<RefCell<backend::Canvas>>,
|
||||||
id: WindowId,
|
id: WindowId,
|
||||||
prevent_default: bool,
|
prevent_default: bool,
|
||||||
has_focus: Rc<Cell<bool>>,
|
has_focus: Arc<AtomicBool>,
|
||||||
) {
|
) {
|
||||||
self.runner.add_canvas(RootWindowId(id), canvas);
|
self.runner.add_canvas(RootWindowId(id), canvas);
|
||||||
let mut canvas = canvas.borrow_mut();
|
let mut canvas = canvas.borrow_mut();
|
||||||
|
@ -96,7 +98,7 @@ impl<T> EventLoopWindowTarget<T> {
|
||||||
let has_focus_clone = has_focus.clone();
|
let has_focus_clone = has_focus.clone();
|
||||||
let modifiers = self.modifiers.clone();
|
let modifiers = self.modifiers.clone();
|
||||||
canvas.on_blur(move || {
|
canvas.on_blur(move || {
|
||||||
has_focus_clone.set(false);
|
has_focus_clone.store(false, Ordering::Relaxed);
|
||||||
|
|
||||||
let clear_modifiers = (!modifiers.get().is_empty()).then(|| {
|
let clear_modifiers = (!modifiers.get().is_empty()).then(|| {
|
||||||
modifiers.set(ModifiersState::empty());
|
modifiers.set(ModifiersState::empty());
|
||||||
|
@ -119,7 +121,7 @@ impl<T> EventLoopWindowTarget<T> {
|
||||||
let runner = self.runner.clone();
|
let runner = self.runner.clone();
|
||||||
let has_focus_clone = has_focus.clone();
|
let has_focus_clone = has_focus.clone();
|
||||||
canvas.on_focus(move || {
|
canvas.on_focus(move || {
|
||||||
if !has_focus_clone.replace(true) {
|
if !has_focus_clone.swap(true, Ordering::Relaxed) {
|
||||||
runner.send_event(Event::WindowEvent {
|
runner.send_event(Event::WindowEvent {
|
||||||
window_id: RootWindowId(id),
|
window_id: RootWindowId(id),
|
||||||
event: WindowEvent::Focused(true),
|
event: WindowEvent::Focused(true),
|
||||||
|
@ -204,7 +206,7 @@ impl<T> EventLoopWindowTarget<T> {
|
||||||
let modifiers = self.modifiers.clone();
|
let modifiers = self.modifiers.clone();
|
||||||
|
|
||||||
move |active_modifiers| {
|
move |active_modifiers| {
|
||||||
if has_focus.get() && modifiers.get() != active_modifiers {
|
if has_focus.load(Ordering::Relaxed) && modifiers.get() != active_modifiers {
|
||||||
modifiers.set(active_modifiers);
|
modifiers.set(active_modifiers);
|
||||||
runner.send_event(Event::WindowEvent {
|
runner.send_event(Event::WindowEvent {
|
||||||
window_id: RootWindowId(id),
|
window_id: RootWindowId(id),
|
||||||
|
@ -234,7 +236,7 @@ impl<T> EventLoopWindowTarget<T> {
|
||||||
let modifiers = self.modifiers.clone();
|
let modifiers = self.modifiers.clone();
|
||||||
|
|
||||||
move |active_modifiers| {
|
move |active_modifiers| {
|
||||||
if has_focus.get() && modifiers.get() != active_modifiers {
|
if has_focus.load(Ordering::Relaxed) && modifiers.get() != active_modifiers {
|
||||||
modifiers.set(active_modifiers);
|
modifiers.set(active_modifiers);
|
||||||
runner.send_event(Event::WindowEvent {
|
runner.send_event(Event::WindowEvent {
|
||||||
window_id: RootWindowId(id),
|
window_id: RootWindowId(id),
|
||||||
|
@ -264,7 +266,7 @@ impl<T> EventLoopWindowTarget<T> {
|
||||||
let modifiers = self.modifiers.clone();
|
let modifiers = self.modifiers.clone();
|
||||||
|
|
||||||
move |active_modifiers| {
|
move |active_modifiers| {
|
||||||
if has_focus.get() && modifiers.get() != active_modifiers {
|
if has_focus.load(Ordering::Relaxed) && modifiers.get() != active_modifiers {
|
||||||
modifiers.set(active_modifiers);
|
modifiers.set(active_modifiers);
|
||||||
runner.send_event(Event::WindowEvent {
|
runner.send_event(Event::WindowEvent {
|
||||||
window_id: RootWindowId(id),
|
window_id: RootWindowId(id),
|
||||||
|
@ -419,7 +421,7 @@ impl<T> EventLoopWindowTarget<T> {
|
||||||
let modifiers = self.modifiers.clone();
|
let modifiers = self.modifiers.clone();
|
||||||
|
|
||||||
move |active_modifiers| {
|
move |active_modifiers| {
|
||||||
if has_focus.get() && modifiers.get() != active_modifiers {
|
if has_focus.load(Ordering::Relaxed) && modifiers.get() != active_modifiers {
|
||||||
modifiers.set(active_modifiers);
|
modifiers.set(active_modifiers);
|
||||||
runner.send_event(Event::WindowEvent {
|
runner.send_event(Event::WindowEvent {
|
||||||
window_id: RootWindowId(id),
|
window_id: RootWindowId(id),
|
||||||
|
@ -476,7 +478,8 @@ impl<T> EventLoopWindowTarget<T> {
|
||||||
let modifiers = self.modifiers.clone();
|
let modifiers = self.modifiers.clone();
|
||||||
canvas.on_mouse_wheel(
|
canvas.on_mouse_wheel(
|
||||||
move |pointer_id, delta, active_modifiers| {
|
move |pointer_id, delta, active_modifiers| {
|
||||||
let modifiers_changed = (has_focus.get() && modifiers.get() != active_modifiers)
|
let modifiers_changed = (has_focus.load(Ordering::Relaxed)
|
||||||
|
&& modifiers.get() != active_modifiers)
|
||||||
.then(|| {
|
.then(|| {
|
||||||
modifiers.set(active_modifiers);
|
modifiers.set(active_modifiers);
|
||||||
Event::WindowEvent {
|
Event::WindowEvent {
|
||||||
|
@ -507,26 +510,30 @@ impl<T> EventLoopWindowTarget<T> {
|
||||||
width: raw.width(),
|
width: raw.width(),
|
||||||
height: raw.height(),
|
height: raw.height(),
|
||||||
};
|
};
|
||||||
canvas.on_fullscreen_change(move || {
|
|
||||||
// If the canvas is marked as fullscreen, it is moving *into* fullscreen
|
canvas.on_fullscreen_change({
|
||||||
// If it is not, it is moving *out of* fullscreen
|
let window = self.runner.window().clone();
|
||||||
let new_size = if backend::is_fullscreen(&raw) {
|
move || {
|
||||||
intended_size = PhysicalSize {
|
// If the canvas is marked as fullscreen, it is moving *into* fullscreen
|
||||||
width: raw.width(),
|
// If it is not, it is moving *out of* fullscreen
|
||||||
height: raw.height(),
|
let new_size = if backend::is_fullscreen(&window, &raw) {
|
||||||
|
intended_size = PhysicalSize {
|
||||||
|
width: raw.width(),
|
||||||
|
height: raw.height(),
|
||||||
|
};
|
||||||
|
|
||||||
|
backend::window_size(&window).to_physical(backend::scale_factor(&window))
|
||||||
|
} else {
|
||||||
|
intended_size
|
||||||
};
|
};
|
||||||
|
|
||||||
backend::window_size().to_physical(backend::scale_factor())
|
backend::set_canvas_size(&window, &raw, Size::Physical(new_size));
|
||||||
} else {
|
runner.send_event(Event::WindowEvent {
|
||||||
intended_size
|
window_id: RootWindowId(id),
|
||||||
};
|
event: WindowEvent::Resized(new_size),
|
||||||
|
});
|
||||||
backend::set_canvas_size(&raw, Size::Physical(new_size));
|
runner.request_redraw(RootWindowId(id));
|
||||||
runner.send_event(Event::WindowEvent {
|
}
|
||||||
window_id: RootWindowId(id),
|
|
||||||
event: WindowEvent::Resized(new_size),
|
|
||||||
});
|
|
||||||
runner.request_redraw(RootWindowId(id));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let runner = self.runner.clone();
|
let runner = self.runner.clone();
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
// incoming events (from the registered handlers) and ensuring they are passed to the user in a
|
// incoming events (from the registered handlers) and ensuring they are passed to the user in a
|
||||||
// compliant way.
|
// compliant way.
|
||||||
|
|
||||||
|
mod r#async;
|
||||||
mod device;
|
mod device;
|
||||||
mod error;
|
mod error;
|
||||||
mod event_loop;
|
mod event_loop;
|
||||||
|
|
|
@ -36,19 +36,20 @@ pub struct Canvas {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Common {
|
pub struct Common {
|
||||||
|
pub window: web_sys::Window,
|
||||||
/// Note: resizing the HTMLCanvasElement should go through `backend::set_canvas_size` to ensure the DPI factor is maintained.
|
/// Note: resizing the HTMLCanvasElement should go through `backend::set_canvas_size` to ensure the DPI factor is maintained.
|
||||||
pub raw: HtmlCanvasElement,
|
pub raw: HtmlCanvasElement,
|
||||||
wants_fullscreen: Rc<RefCell<bool>>,
|
wants_fullscreen: Rc<RefCell<bool>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Canvas {
|
impl Canvas {
|
||||||
pub fn create(attr: PlatformSpecificWindowBuilderAttributes) -> Result<Self, RootOE> {
|
pub fn create(
|
||||||
|
window: web_sys::Window,
|
||||||
|
attr: PlatformSpecificWindowBuilderAttributes,
|
||||||
|
) -> Result<Self, RootOE> {
|
||||||
let canvas = match attr.canvas {
|
let canvas = match attr.canvas {
|
||||||
Some(canvas) => canvas,
|
Some(canvas) => canvas,
|
||||||
None => {
|
None => {
|
||||||
let window = web_sys::window()
|
|
||||||
.ok_or_else(|| os_error!(OsError("Failed to obtain window".to_owned())))?;
|
|
||||||
|
|
||||||
let document = window
|
let document = window
|
||||||
.document()
|
.document()
|
||||||
.ok_or_else(|| os_error!(OsError("Failed to obtain document".to_owned())))?;
|
.ok_or_else(|| os_error!(OsError("Failed to obtain document".to_owned())))?;
|
||||||
|
@ -73,6 +74,7 @@ impl Canvas {
|
||||||
|
|
||||||
Ok(Canvas {
|
Ok(Canvas {
|
||||||
common: Common {
|
common: Common {
|
||||||
|
window,
|
||||||
raw: canvas,
|
raw: canvas,
|
||||||
wants_fullscreen: Rc::new(RefCell::new(false)),
|
wants_fullscreen: Rc::new(RefCell::new(false)),
|
||||||
},
|
},
|
||||||
|
@ -93,9 +95,9 @@ impl Canvas {
|
||||||
if lock {
|
if lock {
|
||||||
self.raw().request_pointer_lock();
|
self.raw().request_pointer_lock();
|
||||||
} else {
|
} else {
|
||||||
let window = web_sys::window()
|
let document = self
|
||||||
.ok_or_else(|| os_error!(OsError("Failed to obtain window".to_owned())))?;
|
.common
|
||||||
let document = window
|
.window
|
||||||
.document()
|
.document()
|
||||||
.ok_or_else(|| os_error!(OsError("Failed to obtain document".to_owned())))?;
|
.ok_or_else(|| os_error!(OsError("Failed to obtain document".to_owned())))?;
|
||||||
document.exit_pointer_lock();
|
document.exit_pointer_lock();
|
||||||
|
@ -294,12 +296,13 @@ impl Canvas {
|
||||||
where
|
where
|
||||||
F: 'static + FnMut(i32, MouseScrollDelta, ModifiersState),
|
F: 'static + FnMut(i32, MouseScrollDelta, ModifiersState),
|
||||||
{
|
{
|
||||||
|
let window = self.common.window.clone();
|
||||||
self.on_mouse_wheel = Some(self.common.add_event("wheel", move |event: WheelEvent| {
|
self.on_mouse_wheel = Some(self.common.add_event("wheel", move |event: WheelEvent| {
|
||||||
if prevent_default {
|
if prevent_default {
|
||||||
event.prevent_default();
|
event.prevent_default();
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(delta) = event::mouse_scroll_delta(&event) {
|
if let Some(delta) = event::mouse_scroll_delta(&window, &event) {
|
||||||
let modifiers = event::mouse_modifiers(&event);
|
let modifiers = event::mouse_modifiers(&event);
|
||||||
handler(0, delta, modifiers);
|
handler(0, delta, modifiers);
|
||||||
}
|
}
|
||||||
|
@ -325,7 +328,8 @@ impl Canvas {
|
||||||
Box::new(move |event: MediaQueryListEvent| handler(event.matches()))
|
Box::new(move |event: MediaQueryListEvent| handler(event.matches()))
|
||||||
as Box<dyn FnMut(_)>,
|
as Box<dyn FnMut(_)>,
|
||||||
);
|
);
|
||||||
self.on_dark_mode = MediaQueryListHandle::new("(prefers-color-scheme: dark)", closure);
|
self.on_dark_mode =
|
||||||
|
MediaQueryListHandle::new(&self.common.window, "(prefers-color-scheme: dark)", closure);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn request_fullscreen(&self) {
|
pub fn request_fullscreen(&self) {
|
||||||
|
@ -426,6 +430,6 @@ impl Common {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_fullscreen(&self) -> bool {
|
pub fn is_fullscreen(&self) -> bool {
|
||||||
super::is_fullscreen(&self.raw)
|
super::is_fullscreen(&self.window, &self.raw)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,14 +79,17 @@ pub fn mouse_position_by_client(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn mouse_scroll_delta(event: &WheelEvent) -> Option<MouseScrollDelta> {
|
pub fn mouse_scroll_delta(
|
||||||
|
window: &web_sys::Window,
|
||||||
|
event: &WheelEvent,
|
||||||
|
) -> Option<MouseScrollDelta> {
|
||||||
let x = -event.delta_x();
|
let x = -event.delta_x();
|
||||||
let y = -event.delta_y();
|
let y = -event.delta_y();
|
||||||
|
|
||||||
match event.delta_mode() {
|
match event.delta_mode() {
|
||||||
WheelEvent::DOM_DELTA_LINE => Some(MouseScrollDelta::LineDelta(x as f32, y as f32)),
|
WheelEvent::DOM_DELTA_LINE => Some(MouseScrollDelta::LineDelta(x as f32, y as f32)),
|
||||||
WheelEvent::DOM_DELTA_PIXEL => {
|
WheelEvent::DOM_DELTA_PIXEL => {
|
||||||
let delta = LogicalPosition::new(x, y).to_physical(super::scale_factor());
|
let delta = LogicalPosition::new(x, y).to_physical(super::scale_factor(window));
|
||||||
Some(MouseScrollDelta::PixelDelta(delta))
|
Some(MouseScrollDelta::PixelDelta(delta))
|
||||||
}
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
|
|
|
@ -8,10 +8,10 @@ pub(super) struct MediaQueryListHandle {
|
||||||
|
|
||||||
impl MediaQueryListHandle {
|
impl MediaQueryListHandle {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
|
window: &web_sys::Window,
|
||||||
media_query: &str,
|
media_query: &str,
|
||||||
listener: Closure<dyn FnMut(MediaQueryListEvent)>,
|
listener: Closure<dyn FnMut(MediaQueryListEvent)>,
|
||||||
) -> Option<Self> {
|
) -> Option<Self> {
|
||||||
let window = web_sys::window().expect("Failed to obtain window");
|
|
||||||
let mql = window
|
let mql = window
|
||||||
.match_media(media_query)
|
.match_media(media_query)
|
||||||
.ok()
|
.ok()
|
||||||
|
|
|
@ -15,14 +15,13 @@ use crate::dpi::{LogicalSize, Size};
|
||||||
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::{window, BeforeUnloadEvent, Element, HtmlCanvasElement};
|
use web_sys::{BeforeUnloadEvent, Element, HtmlCanvasElement};
|
||||||
|
|
||||||
pub fn throw(msg: &str) {
|
pub fn throw(msg: &str) {
|
||||||
wasm_bindgen::throw_str(msg);
|
wasm_bindgen::throw_str(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn exit_fullscreen() {
|
pub fn exit_fullscreen(window: &web_sys::Window) {
|
||||||
let window = web_sys::window().expect("Failed to obtain window");
|
|
||||||
let document = window.document().expect("Failed to obtain document");
|
let document = window.document().expect("Failed to obtain document");
|
||||||
|
|
||||||
document.exit_fullscreen();
|
document.exit_fullscreen();
|
||||||
|
@ -32,38 +31,33 @@ pub struct UnloadEventHandle {
|
||||||
_listener: event_handle::EventListenerHandle<dyn FnMut(BeforeUnloadEvent)>,
|
_listener: event_handle::EventListenerHandle<dyn FnMut(BeforeUnloadEvent)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_unload(mut handler: impl FnMut() + 'static) -> UnloadEventHandle {
|
pub fn on_unload(
|
||||||
let window = web_sys::window().expect("Failed to obtain window");
|
window: &web_sys::Window,
|
||||||
|
mut handler: impl FnMut() + 'static,
|
||||||
|
) -> UnloadEventHandle {
|
||||||
let closure = Closure::wrap(
|
let closure = Closure::wrap(
|
||||||
Box::new(move |_: BeforeUnloadEvent| handler()) as Box<dyn FnMut(BeforeUnloadEvent)>
|
Box::new(move |_: BeforeUnloadEvent| handler()) as Box<dyn FnMut(BeforeUnloadEvent)>
|
||||||
);
|
);
|
||||||
|
|
||||||
let listener = event_handle::EventListenerHandle::new(&window, "beforeunload", closure);
|
let listener = event_handle::EventListenerHandle::new(window, "beforeunload", closure);
|
||||||
UnloadEventHandle {
|
UnloadEventHandle {
|
||||||
_listener: listener,
|
_listener: listener,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WindowExtWebSys for Window {
|
impl WindowExtWebSys for Window {
|
||||||
fn canvas(&self) -> HtmlCanvasElement {
|
fn canvas(&self) -> Option<HtmlCanvasElement> {
|
||||||
self.window.canvas().raw().clone()
|
self.window.canvas()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_dark_mode(&self) -> bool {
|
fn is_dark_mode(&self) -> bool {
|
||||||
let window = web_sys::window().expect("Failed to obtain window");
|
self.window
|
||||||
|
.inner
|
||||||
window
|
.queue(|inner| is_dark_mode(&inner.window).unwrap_or(false))
|
||||||
.match_media("(prefers-color-scheme: dark)")
|
|
||||||
.ok()
|
|
||||||
.flatten()
|
|
||||||
.map(|media| media.matches())
|
|
||||||
.unwrap_or(false)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn window_size() -> LogicalSize<f64> {
|
pub fn window_size(window: &web_sys::Window) -> LogicalSize<f64> {
|
||||||
let window = web_sys::window().expect("Failed to obtain window");
|
|
||||||
let width = window
|
let width = window
|
||||||
.inner_width()
|
.inner_width()
|
||||||
.expect("Failed to get width")
|
.expect("Failed to get width")
|
||||||
|
@ -78,13 +72,12 @@ pub fn window_size() -> LogicalSize<f64> {
|
||||||
LogicalSize { width, height }
|
LogicalSize { width, height }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn scale_factor() -> f64 {
|
pub fn scale_factor(window: &web_sys::Window) -> f64 {
|
||||||
let window = web_sys::window().expect("Failed to obtain window");
|
|
||||||
window.device_pixel_ratio()
|
window.device_pixel_ratio()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_canvas_size(raw: &HtmlCanvasElement, size: Size) {
|
pub fn set_canvas_size(window: &web_sys::Window, raw: &HtmlCanvasElement, size: Size) {
|
||||||
let scale_factor = scale_factor();
|
let scale_factor = scale_factor(window);
|
||||||
let logical_size = size.to_logical::<f64>(scale_factor);
|
let logical_size = size.to_logical::<f64>(scale_factor);
|
||||||
|
|
||||||
set_canvas_style_property(raw, "width", &format!("{}px", logical_size.width));
|
set_canvas_style_property(raw, "width", &format!("{}px", logical_size.width));
|
||||||
|
@ -98,8 +91,7 @@ pub fn set_canvas_style_property(raw: &HtmlCanvasElement, property: &str, value:
|
||||||
.unwrap_or_else(|err| panic!("error: {err:?}\nFailed to set {property}"))
|
.unwrap_or_else(|err| panic!("error: {err:?}\nFailed to set {property}"))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_fullscreen(canvas: &HtmlCanvasElement) -> bool {
|
pub fn is_fullscreen(window: &web_sys::Window, canvas: &HtmlCanvasElement) -> bool {
|
||||||
let window = window().expect("Failed to obtain window");
|
|
||||||
let document = window.document().expect("Failed to obtain document");
|
let document = window.document().expect("Failed to obtain document");
|
||||||
|
|
||||||
match document.fullscreen_element() {
|
match document.fullscreen_element() {
|
||||||
|
@ -111,4 +103,12 @@ pub fn is_fullscreen(canvas: &HtmlCanvasElement) -> bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_dark_mode(window: &web_sys::Window) -> Option<bool> {
|
||||||
|
window
|
||||||
|
.match_media("(prefers-color-scheme: dark)")
|
||||||
|
.ok()
|
||||||
|
.flatten()
|
||||||
|
.map(|media| media.matches())
|
||||||
|
}
|
||||||
|
|
||||||
pub type RawCanvasType = HtmlCanvasElement;
|
pub type RawCanvasType = HtmlCanvasElement;
|
||||||
|
|
|
@ -96,6 +96,7 @@ impl PointerHandler {
|
||||||
M: 'static + FnMut(i32, PhysicalPosition<f64>, MouseButton),
|
M: 'static + FnMut(i32, PhysicalPosition<f64>, MouseButton),
|
||||||
T: 'static + FnMut(i32, PhysicalPosition<f64>, Force),
|
T: 'static + FnMut(i32, PhysicalPosition<f64>, Force),
|
||||||
{
|
{
|
||||||
|
let window = canvas_common.window.clone();
|
||||||
let canvas = canvas_common.raw.clone();
|
let canvas = canvas_common.raw.clone();
|
||||||
self.on_pointer_release = Some(canvas_common.add_user_event(
|
self.on_pointer_release = Some(canvas_common.add_user_event(
|
||||||
"pointerup",
|
"pointerup",
|
||||||
|
@ -105,12 +106,13 @@ impl PointerHandler {
|
||||||
match event.pointer_type().as_str() {
|
match event.pointer_type().as_str() {
|
||||||
"touch" => touch_handler(
|
"touch" => touch_handler(
|
||||||
event.pointer_id(),
|
event.pointer_id(),
|
||||||
event::touch_position(&event, &canvas).to_physical(super::scale_factor()),
|
event::touch_position(&event, &canvas)
|
||||||
|
.to_physical(super::scale_factor(&window)),
|
||||||
Force::Normalized(event.pressure() as f64),
|
Force::Normalized(event.pressure() as f64),
|
||||||
),
|
),
|
||||||
"mouse" => mouse_handler(
|
"mouse" => mouse_handler(
|
||||||
event.pointer_id(),
|
event.pointer_id(),
|
||||||
event::mouse_position(&event).to_physical(super::scale_factor()),
|
event::mouse_position(&event).to_physical(super::scale_factor(&window)),
|
||||||
event::mouse_button(&event).expect("no mouse button released"),
|
event::mouse_button(&event).expect("no mouse button released"),
|
||||||
),
|
),
|
||||||
_ => (),
|
_ => (),
|
||||||
|
@ -131,6 +133,7 @@ impl PointerHandler {
|
||||||
M: 'static + FnMut(i32, PhysicalPosition<f64>, MouseButton),
|
M: 'static + FnMut(i32, PhysicalPosition<f64>, MouseButton),
|
||||||
T: 'static + FnMut(i32, PhysicalPosition<f64>, Force),
|
T: 'static + FnMut(i32, PhysicalPosition<f64>, Force),
|
||||||
{
|
{
|
||||||
|
let window = canvas_common.window.clone();
|
||||||
let canvas = canvas_common.raw.clone();
|
let canvas = canvas_common.raw.clone();
|
||||||
self.on_pointer_press = Some(canvas_common.add_user_event(
|
self.on_pointer_press = Some(canvas_common.add_user_event(
|
||||||
"pointerdown",
|
"pointerdown",
|
||||||
|
@ -149,14 +152,14 @@ impl PointerHandler {
|
||||||
touch_handler(
|
touch_handler(
|
||||||
event.pointer_id(),
|
event.pointer_id(),
|
||||||
event::touch_position(&event, &canvas)
|
event::touch_position(&event, &canvas)
|
||||||
.to_physical(super::scale_factor()),
|
.to_physical(super::scale_factor(&window)),
|
||||||
Force::Normalized(event.pressure() as f64),
|
Force::Normalized(event.pressure() as f64),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
"mouse" => {
|
"mouse" => {
|
||||||
mouse_handler(
|
mouse_handler(
|
||||||
event.pointer_id(),
|
event.pointer_id(),
|
||||||
event::mouse_position(&event).to_physical(super::scale_factor()),
|
event::mouse_position(&event).to_physical(super::scale_factor(&window)),
|
||||||
event::mouse_button(&event).expect("no mouse button pressed"),
|
event::mouse_button(&event).expect("no mouse button pressed"),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -185,6 +188,7 @@ impl PointerHandler {
|
||||||
T: 'static + FnMut(i32, PhysicalPosition<f64>, Force),
|
T: 'static + FnMut(i32, PhysicalPosition<f64>, Force),
|
||||||
B: 'static + FnMut(i32, PhysicalPosition<f64>, ButtonsState, MouseButton),
|
B: 'static + FnMut(i32, PhysicalPosition<f64>, ButtonsState, MouseButton),
|
||||||
{
|
{
|
||||||
|
let window = canvas_common.window.clone();
|
||||||
let canvas = canvas_common.raw.clone();
|
let canvas = canvas_common.raw.clone();
|
||||||
self.on_cursor_move = Some(canvas_common.add_event(
|
self.on_cursor_move = Some(canvas_common.add_event(
|
||||||
if has_pointer_raw_support(&canvas) {
|
if has_pointer_raw_support(&canvas) {
|
||||||
|
@ -230,7 +234,7 @@ impl PointerHandler {
|
||||||
|
|
||||||
button_handler(
|
button_handler(
|
||||||
id,
|
id,
|
||||||
event::mouse_position(&event).to_physical(super::scale_factor()),
|
event::mouse_position(&event).to_physical(super::scale_factor(&window)),
|
||||||
event::mouse_buttons(&event),
|
event::mouse_buttons(&event),
|
||||||
button,
|
button,
|
||||||
);
|
);
|
||||||
|
@ -261,13 +265,13 @@ impl PointerHandler {
|
||||||
match pointer_type.as_str() {
|
match pointer_type.as_str() {
|
||||||
"mouse" => mouse_handler(
|
"mouse" => mouse_handler(
|
||||||
id,
|
id,
|
||||||
event::mouse_position(&event).to_physical(super::scale_factor()),
|
event::mouse_position(&event).to_physical(super::scale_factor(&window)),
|
||||||
event::mouse_delta(&event).to_physical(super::scale_factor()),
|
event::mouse_delta(&event).to_physical(super::scale_factor(&window)),
|
||||||
),
|
),
|
||||||
"touch" => touch_handler(
|
"touch" => touch_handler(
|
||||||
id,
|
id,
|
||||||
event::touch_position(&event, &canvas)
|
event::touch_position(&event, &canvas)
|
||||||
.to_physical(super::scale_factor()),
|
.to_physical(super::scale_factor(&window)),
|
||||||
Force::Normalized(event.pressure() as f64),
|
Force::Normalized(event.pressure() as f64),
|
||||||
),
|
),
|
||||||
_ => unreachable!("didn't return early before"),
|
_ => unreachable!("didn't return early before"),
|
||||||
|
@ -281,6 +285,7 @@ impl PointerHandler {
|
||||||
where
|
where
|
||||||
F: 'static + FnMut(i32, PhysicalPosition<f64>, Force),
|
F: 'static + FnMut(i32, PhysicalPosition<f64>, Force),
|
||||||
{
|
{
|
||||||
|
let window = canvas_common.window.clone();
|
||||||
let canvas = canvas_common.raw.clone();
|
let canvas = canvas_common.raw.clone();
|
||||||
self.on_touch_cancel = Some(canvas_common.add_event(
|
self.on_touch_cancel = Some(canvas_common.add_event(
|
||||||
"pointercancel",
|
"pointercancel",
|
||||||
|
@ -288,7 +293,8 @@ impl PointerHandler {
|
||||||
if event.pointer_type() == "touch" {
|
if event.pointer_type() == "touch" {
|
||||||
handler(
|
handler(
|
||||||
event.pointer_id(),
|
event.pointer_id(),
|
||||||
event::touch_position(&event, &canvas).to_physical(super::scale_factor()),
|
event::touch_position(&event, &canvas)
|
||||||
|
.to_physical(super::scale_factor(&window)),
|
||||||
Force::Normalized(event.pressure() as f64),
|
Force::Normalized(event.pressure() as f64),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,29 +8,31 @@ use web_sys::MediaQueryListEvent;
|
||||||
pub struct ScaleChangeDetector(Rc<RefCell<ScaleChangeDetectorInternal>>);
|
pub struct ScaleChangeDetector(Rc<RefCell<ScaleChangeDetectorInternal>>);
|
||||||
|
|
||||||
impl ScaleChangeDetector {
|
impl ScaleChangeDetector {
|
||||||
pub(crate) fn new<F>(handler: F) -> Self
|
pub(crate) fn new<F>(window: web_sys::Window, handler: F) -> Self
|
||||||
where
|
where
|
||||||
F: 'static + FnMut(ScaleChangeArgs),
|
F: 'static + FnMut(ScaleChangeArgs),
|
||||||
{
|
{
|
||||||
Self(ScaleChangeDetectorInternal::new(handler))
|
Self(ScaleChangeDetectorInternal::new(window, handler))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This is a helper type to help manage the `MediaQueryList` used for detecting
|
/// This is a helper type to help manage the `MediaQueryList` used for detecting
|
||||||
/// changes of the `devicePixelRatio`.
|
/// changes of the `devicePixelRatio`.
|
||||||
struct ScaleChangeDetectorInternal {
|
struct ScaleChangeDetectorInternal {
|
||||||
|
window: web_sys::Window,
|
||||||
callback: Box<dyn FnMut(ScaleChangeArgs)>,
|
callback: Box<dyn FnMut(ScaleChangeArgs)>,
|
||||||
mql: Option<MediaQueryListHandle>,
|
mql: Option<MediaQueryListHandle>,
|
||||||
last_scale: f64,
|
last_scale: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ScaleChangeDetectorInternal {
|
impl ScaleChangeDetectorInternal {
|
||||||
fn new<F>(handler: F) -> Rc<RefCell<Self>>
|
fn new<F>(window: web_sys::Window, handler: F) -> Rc<RefCell<Self>>
|
||||||
where
|
where
|
||||||
F: 'static + FnMut(ScaleChangeArgs),
|
F: 'static + FnMut(ScaleChangeArgs),
|
||||||
{
|
{
|
||||||
let current_scale = super::scale_factor();
|
let current_scale = super::scale_factor(&window);
|
||||||
let new_self = Rc::new(RefCell::new(Self {
|
let new_self = Rc::new(RefCell::new(Self {
|
||||||
|
window: window.clone(),
|
||||||
callback: Box::new(handler),
|
callback: Box::new(handler),
|
||||||
mql: None,
|
mql: None,
|
||||||
last_scale: current_scale,
|
last_scale: current_scale,
|
||||||
|
@ -43,7 +45,7 @@ impl ScaleChangeDetectorInternal {
|
||||||
}
|
}
|
||||||
}) as Box<dyn FnMut(_)>);
|
}) as Box<dyn FnMut(_)>);
|
||||||
|
|
||||||
let mql = Self::create_mql(closure);
|
let mql = Self::create_mql(&window, closure);
|
||||||
{
|
{
|
||||||
let mut borrowed_self = new_self.borrow_mut();
|
let mut borrowed_self = new_self.borrow_mut();
|
||||||
borrowed_self.mql = mql;
|
borrowed_self.mql = mql;
|
||||||
|
@ -52,9 +54,10 @@ impl ScaleChangeDetectorInternal {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_mql(
|
fn create_mql(
|
||||||
|
window: &web_sys::Window,
|
||||||
closure: Closure<dyn FnMut(MediaQueryListEvent)>,
|
closure: Closure<dyn FnMut(MediaQueryListEvent)>,
|
||||||
) -> Option<MediaQueryListHandle> {
|
) -> Option<MediaQueryListHandle> {
|
||||||
let current_scale = super::scale_factor();
|
let current_scale = super::scale_factor(window);
|
||||||
// This media query initially matches the current `devicePixelRatio`.
|
// This media query initially matches the current `devicePixelRatio`.
|
||||||
// We add 0.0001 to the lower and upper bounds such that it won't fail
|
// We add 0.0001 to the lower and upper bounds such that it won't fail
|
||||||
// due to floating point precision limitations.
|
// due to floating point precision limitations.
|
||||||
|
@ -63,7 +66,7 @@ impl ScaleChangeDetectorInternal {
|
||||||
(-webkit-min-device-pixel-ratio: {min_scale:.4}) and (-webkit-max-device-pixel-ratio: {max_scale:.4})",
|
(-webkit-min-device-pixel-ratio: {min_scale:.4}) and (-webkit-max-device-pixel-ratio: {max_scale:.4})",
|
||||||
min_scale = current_scale - 0.0001, max_scale= current_scale + 0.0001,
|
min_scale = current_scale - 0.0001, max_scale= current_scale + 0.0001,
|
||||||
);
|
);
|
||||||
let mql = MediaQueryListHandle::new(&media_query, closure);
|
let mql = MediaQueryListHandle::new(window, &media_query, closure);
|
||||||
if let Some(mql) = &mql {
|
if let Some(mql) = &mql {
|
||||||
assert!(mql.mql().matches());
|
assert!(mql.mql().matches());
|
||||||
}
|
}
|
||||||
|
@ -76,12 +79,12 @@ impl ScaleChangeDetectorInternal {
|
||||||
.take()
|
.take()
|
||||||
.expect("DevicePixelRatioChangeDetector::mql should not be None");
|
.expect("DevicePixelRatioChangeDetector::mql should not be None");
|
||||||
let closure = mql.remove();
|
let closure = mql.remove();
|
||||||
let new_scale = super::scale_factor();
|
let new_scale = super::scale_factor(&self.window);
|
||||||
(self.callback)(ScaleChangeArgs {
|
(self.callback)(ScaleChangeArgs {
|
||||||
old_scale: self.last_scale,
|
old_scale: self.last_scale,
|
||||||
new_scale,
|
new_scale,
|
||||||
});
|
});
|
||||||
let new_mql = Self::create_mql(closure);
|
let new_mql = Self::create_mql(&self.window, closure);
|
||||||
self.mql = new_mql;
|
self.mql = new_mql;
|
||||||
self.last_scale = new_scale;
|
self.last_scale = new_scale;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,17 +6,16 @@ use wasm_bindgen::JsCast;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Timeout {
|
pub struct Timeout {
|
||||||
|
window: web_sys::Window,
|
||||||
handle: i32,
|
handle: i32,
|
||||||
_closure: Closure<dyn FnMut()>,
|
_closure: Closure<dyn FnMut()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Timeout {
|
impl Timeout {
|
||||||
pub fn new<F>(f: F, duration: Duration) -> Timeout
|
pub fn new<F>(window: web_sys::Window, f: F, duration: Duration) -> Timeout
|
||||||
where
|
where
|
||||||
F: 'static + FnMut(),
|
F: 'static + FnMut(),
|
||||||
{
|
{
|
||||||
let window = web_sys::window().expect("Failed to obtain window");
|
|
||||||
|
|
||||||
let closure = Closure::wrap(Box::new(f) as Box<dyn FnMut()>);
|
let closure = Closure::wrap(Box::new(f) as Box<dyn FnMut()>);
|
||||||
|
|
||||||
let handle = window
|
let handle = window
|
||||||
|
@ -27,6 +26,7 @@ impl Timeout {
|
||||||
.expect("Failed to set timeout");
|
.expect("Failed to set timeout");
|
||||||
|
|
||||||
Timeout {
|
Timeout {
|
||||||
|
window,
|
||||||
handle,
|
handle,
|
||||||
_closure: closure,
|
_closure: closure,
|
||||||
}
|
}
|
||||||
|
@ -35,14 +35,13 @@ impl Timeout {
|
||||||
|
|
||||||
impl Drop for Timeout {
|
impl Drop for Timeout {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
let window = web_sys::window().expect("Failed to obtain window");
|
self.window.clear_timeout_with_handle(self.handle);
|
||||||
|
|
||||||
window.clear_timeout_with_handle(self.handle);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct AnimationFrameRequest {
|
pub struct AnimationFrameRequest {
|
||||||
|
window: web_sys::Window,
|
||||||
handle: i32,
|
handle: i32,
|
||||||
// track callback state, because `cancelAnimationFrame` is slow
|
// track callback state, because `cancelAnimationFrame` is slow
|
||||||
fired: Rc<Cell<bool>>,
|
fired: Rc<Cell<bool>>,
|
||||||
|
@ -50,12 +49,10 @@ pub struct AnimationFrameRequest {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AnimationFrameRequest {
|
impl AnimationFrameRequest {
|
||||||
pub fn new<F>(mut f: F) -> AnimationFrameRequest
|
pub fn new<F>(window: web_sys::Window, mut f: F) -> AnimationFrameRequest
|
||||||
where
|
where
|
||||||
F: 'static + FnMut(),
|
F: 'static + FnMut(),
|
||||||
{
|
{
|
||||||
let window = web_sys::window().expect("Failed to obtain window");
|
|
||||||
|
|
||||||
let fired = Rc::new(Cell::new(false));
|
let fired = Rc::new(Cell::new(false));
|
||||||
let c_fired = fired.clone();
|
let c_fired = fired.clone();
|
||||||
let closure = Closure::wrap(Box::new(move || {
|
let closure = Closure::wrap(Box::new(move || {
|
||||||
|
@ -68,6 +65,7 @@ impl AnimationFrameRequest {
|
||||||
.expect("Failed to request animation frame");
|
.expect("Failed to request animation frame");
|
||||||
|
|
||||||
AnimationFrameRequest {
|
AnimationFrameRequest {
|
||||||
|
window,
|
||||||
handle,
|
handle,
|
||||||
fired,
|
fired,
|
||||||
_closure: closure,
|
_closure: closure,
|
||||||
|
@ -78,8 +76,7 @@ impl AnimationFrameRequest {
|
||||||
impl Drop for AnimationFrameRequest {
|
impl Drop for AnimationFrameRequest {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
if !(*self.fired).get() {
|
if !(*self.fired).get() {
|
||||||
let window = web_sys::window().expect("Failed to obtain window");
|
self.window
|
||||||
window
|
|
||||||
.cancel_animation_frame(self.handle)
|
.cancel_animation_frame(self.handle)
|
||||||
.expect("Failed to cancel animation frame");
|
.expect("Failed to cancel animation frame");
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,22 +8,31 @@ use crate::window::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use raw_window_handle::{RawDisplayHandle, RawWindowHandle, WebDisplayHandle, WebWindowHandle};
|
use raw_window_handle::{RawDisplayHandle, RawWindowHandle, WebDisplayHandle, WebWindowHandle};
|
||||||
|
use web_sys::HtmlCanvasElement;
|
||||||
|
|
||||||
|
use super::r#async::Dispatcher;
|
||||||
use super::{backend, monitor::MonitorHandle, EventLoopWindowTarget, Fullscreen};
|
use super::{backend, monitor::MonitorHandle, EventLoopWindowTarget, Fullscreen};
|
||||||
|
|
||||||
use std::cell::{Cell, Ref, RefCell};
|
use std::cell::RefCell;
|
||||||
use std::collections::vec_deque::IntoIter as VecDequeIter;
|
use std::collections::vec_deque::IntoIter as VecDequeIter;
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub struct Window {
|
pub struct Window {
|
||||||
|
id: WindowId,
|
||||||
|
has_focus: Arc<AtomicBool>,
|
||||||
|
pub inner: Dispatcher<Inner>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Inner {
|
||||||
|
pub window: web_sys::Window,
|
||||||
canvas: Rc<RefCell<backend::Canvas>>,
|
canvas: Rc<RefCell<backend::Canvas>>,
|
||||||
previous_pointer: RefCell<&'static str>,
|
previous_pointer: RefCell<&'static str>,
|
||||||
id: WindowId,
|
|
||||||
register_redraw_request: Box<dyn Fn()>,
|
register_redraw_request: Box<dyn Fn()>,
|
||||||
resize_notify_fn: Box<dyn Fn(PhysicalSize<u32>)>,
|
resize_notify_fn: Box<dyn Fn(PhysicalSize<u32>)>,
|
||||||
destroy_fn: Option<Box<dyn FnOnce()>>,
|
destroy_fn: Option<Box<dyn FnOnce()>>,
|
||||||
has_focus: Rc<Cell<bool>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Window {
|
impl Window {
|
||||||
|
@ -38,12 +47,13 @@ impl Window {
|
||||||
|
|
||||||
let prevent_default = platform_attr.prevent_default;
|
let prevent_default = platform_attr.prevent_default;
|
||||||
|
|
||||||
let canvas = backend::Canvas::create(platform_attr)?;
|
let window = target.runner.window();
|
||||||
|
let canvas = backend::Canvas::create(window.clone(), platform_attr)?;
|
||||||
let canvas = Rc::new(RefCell::new(canvas));
|
let canvas = Rc::new(RefCell::new(canvas));
|
||||||
|
|
||||||
let register_redraw_request = Box::new(move || runner.request_redraw(RootWI(id)));
|
let register_redraw_request = Box::new(move || runner.request_redraw(RootWI(id)));
|
||||||
|
|
||||||
let has_focus = Rc::new(Cell::new(false));
|
let has_focus = Arc::new(AtomicBool::new(false));
|
||||||
target.register(&canvas, id, prevent_default, has_focus.clone());
|
target.register(&canvas, id, prevent_default, has_focus.clone());
|
||||||
|
|
||||||
let runner = target.runner.clone();
|
let runner = target.runner.clone();
|
||||||
|
@ -57,23 +67,29 @@ impl Window {
|
||||||
let runner = target.runner.clone();
|
let runner = target.runner.clone();
|
||||||
let destroy_fn = Box::new(move || runner.notify_destroy_window(RootWI(id)));
|
let destroy_fn = Box::new(move || runner.notify_destroy_window(RootWI(id)));
|
||||||
|
|
||||||
let window = Window {
|
|
||||||
canvas,
|
|
||||||
previous_pointer: RefCell::new("auto"),
|
|
||||||
id,
|
|
||||||
register_redraw_request,
|
|
||||||
resize_notify_fn,
|
|
||||||
destroy_fn: Some(destroy_fn),
|
|
||||||
has_focus,
|
|
||||||
};
|
|
||||||
|
|
||||||
backend::set_canvas_size(
|
backend::set_canvas_size(
|
||||||
window.canvas.borrow().raw(),
|
window,
|
||||||
|
canvas.borrow().raw(),
|
||||||
attr.inner_size.unwrap_or(Size::Logical(LogicalSize {
|
attr.inner_size.unwrap_or(Size::Logical(LogicalSize {
|
||||||
width: 1024.0,
|
width: 1024.0,
|
||||||
height: 768.0,
|
height: 768.0,
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let window = Window {
|
||||||
|
id,
|
||||||
|
has_focus,
|
||||||
|
inner: Dispatcher::new(Inner {
|
||||||
|
window: window.clone(),
|
||||||
|
canvas,
|
||||||
|
previous_pointer: RefCell::new("auto"),
|
||||||
|
register_redraw_request,
|
||||||
|
resize_notify_fn,
|
||||||
|
destroy_fn: Some(destroy_fn),
|
||||||
|
})
|
||||||
|
.unwrap(),
|
||||||
|
};
|
||||||
|
|
||||||
window.set_title(&attr.title);
|
window.set_title(&attr.title);
|
||||||
window.set_maximized(attr.maximized);
|
window.set_maximized(attr.maximized);
|
||||||
window.set_visible(attr.visible);
|
window.set_visible(attr.visible);
|
||||||
|
@ -82,12 +98,20 @@ impl Window {
|
||||||
Ok(window)
|
Ok(window)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn canvas(&self) -> Ref<'_, backend::Canvas> {
|
pub fn canvas(&self) -> Option<HtmlCanvasElement> {
|
||||||
self.canvas.borrow()
|
self.inner.with(|inner| inner.canvas.borrow().raw().clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_title(&self, title: &str) {
|
pub fn set_title(&self, title: &str) {
|
||||||
self.canvas.borrow().set_attribute("alt", title);
|
if self
|
||||||
|
.inner
|
||||||
|
.with(|inner| inner.canvas.borrow().set_attribute("alt", title))
|
||||||
|
.is_none()
|
||||||
|
{
|
||||||
|
let title = title.to_owned();
|
||||||
|
self.inner
|
||||||
|
.dispatch(move |inner| inner.canvas.borrow().set_attribute("alt", &title));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_transparent(&self, _transparent: bool) {}
|
pub fn set_transparent(&self, _transparent: bool) {}
|
||||||
|
@ -102,15 +126,18 @@ impl Window {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn request_redraw(&self) {
|
pub fn request_redraw(&self) {
|
||||||
(self.register_redraw_request)();
|
self.inner
|
||||||
|
.dispatch(|inner| (inner.register_redraw_request)());
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
|
pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
|
||||||
Ok(self
|
self.inner.queue(|inner| {
|
||||||
.canvas
|
Ok(inner
|
||||||
.borrow()
|
.canvas
|
||||||
.position()
|
.borrow()
|
||||||
.to_physical(self.scale_factor()))
|
.position()
|
||||||
|
.to_physical(inner.scale_factor()))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn inner_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
|
pub fn inner_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
|
||||||
|
@ -119,17 +146,19 @@ impl Window {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_outer_position(&self, position: Position) {
|
pub fn set_outer_position(&self, position: Position) {
|
||||||
let position = position.to_logical::<f64>(self.scale_factor());
|
self.inner.dispatch(move |inner| {
|
||||||
|
let position = position.to_logical::<f64>(inner.scale_factor());
|
||||||
|
|
||||||
let canvas = self.canvas.borrow();
|
let canvas = inner.canvas.borrow();
|
||||||
canvas.set_attribute("position", "fixed");
|
canvas.set_attribute("position", "fixed");
|
||||||
canvas.set_attribute("left", &position.x.to_string());
|
canvas.set_attribute("left", &position.x.to_string());
|
||||||
canvas.set_attribute("top", &position.y.to_string());
|
canvas.set_attribute("top", &position.y.to_string());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn inner_size(&self) -> PhysicalSize<u32> {
|
pub fn inner_size(&self) -> PhysicalSize<u32> {
|
||||||
self.canvas.borrow().size()
|
self.inner.queue(|inner| inner.inner_size())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -140,12 +169,14 @@ impl Window {
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn set_inner_size(&self, size: Size) {
|
pub fn set_inner_size(&self, size: Size) {
|
||||||
let old_size = self.inner_size();
|
self.inner.dispatch(move |inner| {
|
||||||
backend::set_canvas_size(self.canvas.borrow().raw(), size);
|
let old_size = inner.inner_size();
|
||||||
let new_size = self.inner_size();
|
backend::set_canvas_size(&inner.window, inner.canvas.borrow().raw(), size);
|
||||||
if old_size != new_size {
|
let new_size = inner.inner_size();
|
||||||
(self.resize_notify_fn)(new_size);
|
if old_size != new_size {
|
||||||
}
|
(inner.resize_notify_fn)(new_size);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -187,13 +218,19 @@ impl Window {
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn scale_factor(&self) -> f64 {
|
pub fn scale_factor(&self) -> f64 {
|
||||||
super::backend::scale_factor()
|
self.inner.queue(|inner| inner.scale_factor())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn set_cursor_icon(&self, cursor: CursorIcon) {
|
pub fn set_cursor_icon(&self, cursor: CursorIcon) {
|
||||||
*self.previous_pointer.borrow_mut() = cursor.name();
|
self.inner.dispatch(move |inner| {
|
||||||
backend::set_canvas_style_property(self.canvas.borrow().raw(), "cursor", cursor.name());
|
*inner.previous_pointer.borrow_mut() = cursor.name();
|
||||||
|
backend::set_canvas_style_property(
|
||||||
|
inner.canvas.borrow().raw(),
|
||||||
|
"cursor",
|
||||||
|
cursor.name(),
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -203,29 +240,35 @@ impl Window {
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> {
|
pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> {
|
||||||
let lock = match mode {
|
self.inner.queue(move |inner| {
|
||||||
CursorGrabMode::None => false,
|
let lock = match mode {
|
||||||
CursorGrabMode::Locked => true,
|
CursorGrabMode::None => false,
|
||||||
CursorGrabMode::Confined => {
|
CursorGrabMode::Locked => true,
|
||||||
return Err(ExternalError::NotSupported(NotSupportedError::new()))
|
CursorGrabMode::Confined => {
|
||||||
}
|
return Err(ExternalError::NotSupported(NotSupportedError::new()))
|
||||||
};
|
}
|
||||||
|
};
|
||||||
|
|
||||||
self.canvas
|
inner
|
||||||
.borrow()
|
.canvas
|
||||||
.set_cursor_lock(lock)
|
.borrow()
|
||||||
.map_err(ExternalError::Os)
|
.set_cursor_lock(lock)
|
||||||
|
.map_err(ExternalError::Os)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn set_cursor_visible(&self, visible: bool) {
|
pub fn set_cursor_visible(&self, visible: bool) {
|
||||||
if !visible {
|
self.inner.dispatch(move |inner| {
|
||||||
self.canvas.borrow().set_attribute("cursor", "none");
|
if !visible {
|
||||||
} else {
|
inner.canvas.borrow().set_attribute("cursor", "none");
|
||||||
self.canvas
|
} else {
|
||||||
.borrow()
|
inner
|
||||||
.set_attribute("cursor", &self.previous_pointer.borrow());
|
.canvas
|
||||||
}
|
.borrow()
|
||||||
|
.set_attribute("cursor", &inner.previous_pointer.borrow());
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -267,20 +310,24 @@ impl Window {
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn fullscreen(&self) -> Option<Fullscreen> {
|
pub(crate) fn fullscreen(&self) -> Option<Fullscreen> {
|
||||||
if self.canvas.borrow().is_fullscreen() {
|
self.inner.queue(|inner| {
|
||||||
Some(Fullscreen::Borderless(Some(self.current_monitor_inner())))
|
if inner.canvas.borrow().is_fullscreen() {
|
||||||
} else {
|
Some(Fullscreen::Borderless(Some(MonitorHandle)))
|
||||||
None
|
} else {
|
||||||
}
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
|
pub(crate) fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
|
||||||
if fullscreen.is_some() {
|
self.inner.dispatch(move |inner| {
|
||||||
self.canvas.borrow().request_fullscreen();
|
if fullscreen.is_some() {
|
||||||
} else if self.canvas.borrow().is_fullscreen() {
|
inner.canvas.borrow().request_fullscreen();
|
||||||
backend::exit_fullscreen();
|
} else if inner.canvas.borrow().is_fullscreen() {
|
||||||
}
|
backend::exit_fullscreen(&inner.window);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -327,15 +374,9 @@ impl Window {
|
||||||
// Currently an intentional no-op
|
// Currently an intentional no-op
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
// Allow directly accessing the current monitor internally without unwrapping.
|
|
||||||
fn current_monitor_inner(&self) -> MonitorHandle {
|
|
||||||
MonitorHandle
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn current_monitor(&self) -> Option<MonitorHandle> {
|
pub fn current_monitor(&self) -> Option<MonitorHandle> {
|
||||||
Some(self.current_monitor_inner())
|
Some(MonitorHandle)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -370,25 +411,20 @@ impl Window {
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn theme(&self) -> Option<Theme> {
|
pub fn theme(&self) -> Option<Theme> {
|
||||||
web_sys::window()
|
self.inner.queue(|inner| {
|
||||||
.and_then(|window| {
|
backend::is_dark_mode(&inner.window).map(|is_dark_mode| {
|
||||||
window
|
if is_dark_mode {
|
||||||
.match_media("(prefers-color-scheme: dark)")
|
|
||||||
.ok()
|
|
||||||
.flatten()
|
|
||||||
})
|
|
||||||
.map(|media_query_list| {
|
|
||||||
if media_query_list.matches() {
|
|
||||||
Theme::Dark
|
Theme::Dark
|
||||||
} else {
|
} else {
|
||||||
Theme::Light
|
Theme::Light
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn has_focus(&self) -> bool {
|
pub fn has_focus(&self) -> bool {
|
||||||
self.has_focus.get()
|
self.has_focus.load(Ordering::Relaxed)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn title(&self) -> String {
|
pub fn title(&self) -> String {
|
||||||
|
@ -402,9 +438,23 @@ impl Window {
|
||||||
|
|
||||||
impl Drop for Window {
|
impl Drop for Window {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
if let Some(destroy_fn) = self.destroy_fn.take() {
|
self.inner.dispatch_mut(|inner| {
|
||||||
destroy_fn();
|
if let Some(destroy_fn) = inner.destroy_fn.take() {
|
||||||
}
|
destroy_fn();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Inner {
|
||||||
|
#[inline]
|
||||||
|
pub fn scale_factor(&self) -> f64 {
|
||||||
|
super::backend::scale_factor(&self.window)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn inner_size(&self) -> PhysicalSize<u32> {
|
||||||
|
self.canvas.borrow().size()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
fn needs_send<T: Send>() {}
|
fn needs_send<T: Send>() {}
|
||||||
|
|
||||||
#[cfg(not(wasm_platform))]
|
|
||||||
#[test]
|
#[test]
|
||||||
fn event_loop_proxy_send() {
|
fn event_loop_proxy_send() {
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
|
@ -11,7 +10,6 @@ fn event_loop_proxy_send() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(wasm_platform))]
|
|
||||||
#[test]
|
#[test]
|
||||||
fn window_send() {
|
fn window_send() {
|
||||||
// ensures that `winit::Window` implements `Send`
|
// ensures that `winit::Window` implements `Send`
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
fn needs_sync<T: Sync>() {}
|
fn needs_sync<T: Sync>() {}
|
||||||
|
|
||||||
#[cfg(not(wasm_platform))]
|
|
||||||
#[test]
|
#[test]
|
||||||
fn window_sync() {
|
fn window_sync() {
|
||||||
// ensures that `winit::Window` implements `Sync`
|
// ensures that `winit::Window` implements `Sync`
|
||||||
|
|
Loading…
Reference in a new issue