mirror of
https://github.com/italicsjenga/winit-sonoma-fix.git
synced 2025-01-23 02:16:33 +11:00
commit
53e646dabc
28 changed files with 2008 additions and 11 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -2,5 +2,9 @@ Cargo.lock
|
||||||
target/
|
target/
|
||||||
rls/
|
rls/
|
||||||
.vscode/
|
.vscode/
|
||||||
|
util/
|
||||||
*~
|
*~
|
||||||
|
*.wasm
|
||||||
|
*.ts
|
||||||
|
*.js
|
||||||
#*#
|
#*#
|
||||||
|
|
39
Cargo.toml
39
Cargo.toml
|
@ -14,7 +14,12 @@ categories = ["gui"]
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
features = ["serde"]
|
features = ["serde"]
|
||||||
|
|
||||||
|
[features]
|
||||||
|
web-sys = ["web_sys", "wasm-bindgen", "instant/wasm-bindgen"]
|
||||||
|
stdweb = ["std_web", "instant/stdweb"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
instant = "0.1"
|
||||||
lazy_static = "1"
|
lazy_static = "1"
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
@ -79,3 +84,37 @@ percent-encoding = "1.0"
|
||||||
|
|
||||||
[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "windows"))'.dependencies.parking_lot]
|
[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "windows"))'.dependencies.parking_lot]
|
||||||
version = "0.8"
|
version = "0.8"
|
||||||
|
|
||||||
|
[target.'cfg(target_arch = "wasm32")'.dependencies.web_sys]
|
||||||
|
package = "web-sys"
|
||||||
|
version = "0.3.22"
|
||||||
|
optional = true
|
||||||
|
features = [
|
||||||
|
'console',
|
||||||
|
'Document',
|
||||||
|
'DomRect',
|
||||||
|
'Element',
|
||||||
|
'Event',
|
||||||
|
'EventTarget',
|
||||||
|
'FocusEvent',
|
||||||
|
'HtmlCanvasElement',
|
||||||
|
'HtmlElement',
|
||||||
|
'KeyboardEvent',
|
||||||
|
'MouseEvent',
|
||||||
|
'Node',
|
||||||
|
'PointerEvent',
|
||||||
|
'Window',
|
||||||
|
'WheelEvent'
|
||||||
|
]
|
||||||
|
|
||||||
|
[target.'cfg(target_arch = "wasm32")'.dependencies.wasm-bindgen]
|
||||||
|
version = "0.2.45"
|
||||||
|
optional = true
|
||||||
|
|
||||||
|
[target.'cfg(target_arch = "wasm32")'.dependencies.std_web]
|
||||||
|
package = "stdweb"
|
||||||
|
version = "0.4.17"
|
||||||
|
optional = true
|
||||||
|
|
||||||
|
[patch.crates-io]
|
||||||
|
stdweb = { git = "https://github.com/koute/stdweb", rev = "b3a29bb9dd9b9405540d711ed02a21cd7058d5c0"}
|
||||||
|
|
|
@ -4,7 +4,8 @@
|
||||||
//! processed and used to modify the program state. For more details, see the root-level documentation.
|
//! processed and used to modify the program state. For more details, see the root-level documentation.
|
||||||
//!
|
//!
|
||||||
//! [event_loop_run]: ../event_loop/struct.EventLoop.html#method.run
|
//! [event_loop_run]: ../event_loop/struct.EventLoop.html#method.run
|
||||||
use std::{path::PathBuf, time::Instant};
|
use instant::Instant;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
dpi::{LogicalPosition, LogicalSize},
|
dpi::{LogicalPosition, LogicalSize},
|
||||||
|
@ -61,7 +62,7 @@ impl<T> Event<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Describes the reason the event loop is resuming.
|
/// Describes the reason the event loop is resuming.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum StartCause {
|
pub enum StartCause {
|
||||||
/// Sent if the time specified by `ControlFlow::WaitUntil` has been reached. Contains the
|
/// Sent if the time specified by `ControlFlow::WaitUntil` has been reached. Contains the
|
||||||
/// moment the timeout was requested and the requested resume time. The actual resume time is
|
/// moment the timeout was requested and the requested resume time. The actual resume time is
|
||||||
|
|
|
@ -9,13 +9,13 @@
|
||||||
//! [create_proxy]: ./struct.EventLoop.html#method.create_proxy
|
//! [create_proxy]: ./struct.EventLoop.html#method.create_proxy
|
||||||
//! [event_loop_proxy]: ./struct.EventLoopProxy.html
|
//! [event_loop_proxy]: ./struct.EventLoopProxy.html
|
||||||
//! [send_event]: ./struct.EventLoopProxy.html#method.send_event
|
//! [send_event]: ./struct.EventLoopProxy.html#method.send_event
|
||||||
use std::{error, fmt, ops::Deref, time::Instant};
|
use instant::Instant;
|
||||||
|
use std::ops::Deref;
|
||||||
|
use std::{error, fmt};
|
||||||
|
|
||||||
use crate::{
|
use event::Event;
|
||||||
event::Event,
|
use monitor::{AvailableMonitorsIter, MonitorHandle};
|
||||||
monitor::{AvailableMonitorsIter, MonitorHandle},
|
use platform_impl;
|
||||||
platform_impl,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Provides a way to retrieve events from the system and from the windows that were registered to
|
/// Provides a way to retrieve events from the system and from the windows that were registered to
|
||||||
/// the events loop.
|
/// the events loop.
|
||||||
|
@ -69,7 +69,7 @@ impl<T> fmt::Debug for EventLoopWindowTarget<T> {
|
||||||
/// the control flow to `Poll`.
|
/// the control flow to `Poll`.
|
||||||
///
|
///
|
||||||
/// [events_cleared]: ../event/enum.Event.html#variant.EventsCleared
|
/// [events_cleared]: ../event/enum.Event.html#variant.EventsCleared
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
pub enum ControlFlow {
|
pub enum ControlFlow {
|
||||||
/// When the current loop iteration finishes, immediately begin a new iteration regardless of
|
/// When the current loop iteration finishes, immediately begin a new iteration regardless of
|
||||||
/// whether or not new events are available to process.
|
/// whether or not new events are available to process.
|
||||||
|
|
|
@ -50,7 +50,7 @@
|
||||||
//! match event {
|
//! match event {
|
||||||
//! Event::EventsCleared => {
|
//! Event::EventsCleared => {
|
||||||
//! // Application update code.
|
//! // Application update code.
|
||||||
//!
|
//!
|
||||||
//! // Queue a RedrawRequested event.
|
//! // Queue a RedrawRequested event.
|
||||||
//! window.request_redraw();
|
//! window.request_redraw();
|
||||||
//! },
|
//! },
|
||||||
|
@ -129,6 +129,9 @@ extern crate bitflags;
|
||||||
#[cfg(any(target_os = "macos", target_os = "ios"))]
|
#[cfg(any(target_os = "macos", target_os = "ios"))]
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate objc;
|
extern crate objc;
|
||||||
|
#[cfg(feature = "std_web")]
|
||||||
|
#[macro_use]
|
||||||
|
extern crate std_web as stdweb;
|
||||||
|
|
||||||
pub mod dpi;
|
pub mod dpi;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
|
|
|
@ -21,3 +21,4 @@ pub mod unix;
|
||||||
pub mod windows;
|
pub mod windows;
|
||||||
|
|
||||||
pub mod desktop;
|
pub mod desktop;
|
||||||
|
pub mod web;
|
||||||
|
|
15
src/platform/web.rs
Normal file
15
src/platform/web.rs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
#[cfg(feature = "stdweb")]
|
||||||
|
use stdweb::web::html_element::CanvasElement;
|
||||||
|
|
||||||
|
#[cfg(feature = "stdweb")]
|
||||||
|
pub trait WindowExtStdweb {
|
||||||
|
fn canvas(&self) -> CanvasElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "web-sys")]
|
||||||
|
use web_sys::HtmlCanvasElement;
|
||||||
|
|
||||||
|
#[cfg(feature = "web-sys")]
|
||||||
|
pub trait WindowExtWebSys {
|
||||||
|
fn canvas(&self) -> HtmlCanvasElement;
|
||||||
|
}
|
|
@ -24,6 +24,9 @@ mod platform;
|
||||||
#[cfg(target_os = "emscripten")]
|
#[cfg(target_os = "emscripten")]
|
||||||
#[path = "emscripten/mod.rs"]
|
#[path = "emscripten/mod.rs"]
|
||||||
mod platform;
|
mod platform;
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
#[path = "web/mod.rs"]
|
||||||
|
mod platform;
|
||||||
|
|
||||||
#[cfg(all(
|
#[cfg(all(
|
||||||
not(target_os = "ios"),
|
not(target_os = "ios"),
|
||||||
|
@ -35,6 +38,7 @@ mod platform;
|
||||||
not(target_os = "freebsd"),
|
not(target_os = "freebsd"),
|
||||||
not(target_os = "netbsd"),
|
not(target_os = "netbsd"),
|
||||||
not(target_os = "openbsd"),
|
not(target_os = "openbsd"),
|
||||||
not(target_os = "emscripten")
|
not(target_os = "emscripten"),
|
||||||
|
not(target_arch = "wasm32"),
|
||||||
))]
|
))]
|
||||||
compile_error!("The platform you're compiling for is not supported by winit");
|
compile_error!("The platform you're compiling for is not supported by winit");
|
||||||
|
|
8
src/platform_impl/web/device.rs
Normal file
8
src/platform_impl/web/device.rs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub struct Id(pub i32);
|
||||||
|
|
||||||
|
impl Id {
|
||||||
|
pub unsafe fn dummy() -> Self {
|
||||||
|
Id(0)
|
||||||
|
}
|
||||||
|
}
|
10
src/platform_impl/web/error.rs
Normal file
10
src/platform_impl/web/error.rs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct OsError(pub String);
|
||||||
|
|
||||||
|
impl fmt::Display for OsError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "{}", self.0)
|
||||||
|
}
|
||||||
|
}
|
67
src/platform_impl/web/event_loop/mod.rs
Normal file
67
src/platform_impl/web/event_loop/mod.rs
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
mod proxy;
|
||||||
|
mod runner;
|
||||||
|
mod state;
|
||||||
|
mod window_target;
|
||||||
|
|
||||||
|
pub use self::proxy::Proxy;
|
||||||
|
pub use self::window_target::WindowTarget;
|
||||||
|
|
||||||
|
use super::{backend, device, monitor, window};
|
||||||
|
use crate::event::Event;
|
||||||
|
use crate::event_loop as root;
|
||||||
|
|
||||||
|
use std::collections::{vec_deque::IntoIter as VecDequeIter, VecDeque};
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
|
pub struct EventLoop<T: 'static> {
|
||||||
|
elw: root::EventLoopWindowTarget<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> EventLoop<T> {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
EventLoop {
|
||||||
|
elw: root::EventLoopWindowTarget {
|
||||||
|
p: WindowTarget::new(),
|
||||||
|
_marker: PhantomData,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn available_monitors(&self) -> VecDequeIter<monitor::Handle> {
|
||||||
|
VecDeque::new().into_iter()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn primary_monitor(&self) -> monitor::Handle {
|
||||||
|
monitor::Handle
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run<F>(self, mut event_handler: F) -> !
|
||||||
|
where
|
||||||
|
F: 'static + FnMut(Event<T>, &root::EventLoopWindowTarget<T>, &mut root::ControlFlow),
|
||||||
|
{
|
||||||
|
let target = root::EventLoopWindowTarget {
|
||||||
|
p: self.elw.p.clone(),
|
||||||
|
_marker: PhantomData,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.elw.p.run(Box::new(move |event, flow| {
|
||||||
|
event_handler(event, &target, flow)
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Throw an exception to break out of Rust exceution and use unreachable to tell the
|
||||||
|
// compiler this function won't return, giving it a return type of '!'
|
||||||
|
backend::throw(
|
||||||
|
"Using exceptions for control flow, don't mind me. This isn't actually an error!",
|
||||||
|
);
|
||||||
|
|
||||||
|
unreachable!();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_proxy(&self) -> Proxy<T> {
|
||||||
|
self.elw.p.proxy()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn window_target(&self) -> &root::EventLoopWindowTarget<T> {
|
||||||
|
&self.elw
|
||||||
|
}
|
||||||
|
}
|
19
src/platform_impl/web/event_loop/proxy.rs
Normal file
19
src/platform_impl/web/event_loop/proxy.rs
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
use super::runner;
|
||||||
|
use crate::event::Event;
|
||||||
|
use crate::event_loop::EventLoopClosed;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Proxy<T: 'static> {
|
||||||
|
runner: runner::Shared<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: 'static> Proxy<T> {
|
||||||
|
pub fn new(runner: runner::Shared<T>) -> Self {
|
||||||
|
Proxy { runner }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> {
|
||||||
|
self.runner.send_event(Event::UserEvent(event));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
211
src/platform_impl/web/event_loop/runner.rs
Normal file
211
src/platform_impl/web/event_loop/runner.rs
Normal file
|
@ -0,0 +1,211 @@
|
||||||
|
use super::{backend, state::State};
|
||||||
|
use crate::event::{Event, StartCause};
|
||||||
|
use crate::event_loop as root;
|
||||||
|
|
||||||
|
use instant::{Duration, Instant};
|
||||||
|
use std::{cell::RefCell, clone::Clone, collections::VecDeque, rc::Rc};
|
||||||
|
|
||||||
|
pub struct Shared<T>(Rc<Execution<T>>);
|
||||||
|
|
||||||
|
impl<T> Clone for Shared<T> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Shared(self.0.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Execution<T> {
|
||||||
|
runner: RefCell<Option<Runner<T>>>,
|
||||||
|
events: RefCell<VecDeque<Event<T>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Runner<T> {
|
||||||
|
state: State,
|
||||||
|
is_busy: bool,
|
||||||
|
event_handler: Box<dyn FnMut(Event<T>, &mut root::ControlFlow)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: 'static> Runner<T> {
|
||||||
|
pub fn new(event_handler: Box<dyn FnMut(Event<T>, &mut root::ControlFlow)>) -> Self {
|
||||||
|
Runner {
|
||||||
|
state: State::Init,
|
||||||
|
is_busy: false,
|
||||||
|
event_handler,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: 'static> Shared<T> {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Shared(Rc::new(Execution {
|
||||||
|
runner: RefCell::new(None),
|
||||||
|
events: RefCell::new(VecDeque::new()),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the event callback to use for the event loop runner
|
||||||
|
// This the event callback is a fairly thin layer over the user-provided callback that closes
|
||||||
|
// over a RootEventLoopWindowTarget reference
|
||||||
|
pub fn set_listener(&self, event_handler: Box<dyn FnMut(Event<T>, &mut root::ControlFlow)>) {
|
||||||
|
self.0.runner.replace(Some(Runner::new(event_handler)));
|
||||||
|
self.send_event(Event::NewEvents(StartCause::Init));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add an event to the event loop runner
|
||||||
|
//
|
||||||
|
// It will determine if the event should be immediately sent to the user or buffered for later
|
||||||
|
pub fn send_event(&self, event: Event<T>) {
|
||||||
|
// If the event loop is closed, it should discard any new events
|
||||||
|
if self.is_closed() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine if event handling is in process, and then release the borrow on the runner
|
||||||
|
let (start_cause, event_is_start) = match *self.0.runner.borrow() {
|
||||||
|
Some(ref runner) if !runner.is_busy => {
|
||||||
|
if let Event::NewEvents(cause) = event {
|
||||||
|
(cause, true)
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
match runner.state {
|
||||||
|
State::Init => StartCause::Init,
|
||||||
|
State::Poll { .. } => StartCause::Poll,
|
||||||
|
State::Wait { start } => StartCause::WaitCancelled {
|
||||||
|
start,
|
||||||
|
requested_resume: None,
|
||||||
|
},
|
||||||
|
State::WaitUntil { start, end, .. } => StartCause::WaitCancelled {
|
||||||
|
start,
|
||||||
|
requested_resume: Some(end),
|
||||||
|
},
|
||||||
|
State::Exit => {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// Events are currently being handled, so queue this one and don't try to
|
||||||
|
// double-process the event queue
|
||||||
|
self.0.events.borrow_mut().push_back(event);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let mut control = self.current_control_flow();
|
||||||
|
// Handle starting a new batch of events
|
||||||
|
//
|
||||||
|
// The user is informed via Event::NewEvents that there is a batch of events to process
|
||||||
|
// However, there is only one of these per batch of events
|
||||||
|
self.handle_event(Event::NewEvents(start_cause), &mut control);
|
||||||
|
if !event_is_start {
|
||||||
|
self.handle_event(event, &mut control);
|
||||||
|
}
|
||||||
|
self.handle_event(Event::EventsCleared, &mut control);
|
||||||
|
self.apply_control_flow(control);
|
||||||
|
// If the event loop is closed, it has been closed this iteration and now the closing
|
||||||
|
// event should be emitted
|
||||||
|
if self.is_closed() {
|
||||||
|
self.handle_event(Event::LoopDestroyed, &mut control);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle_event takes in events and either queues them or applies a callback
|
||||||
|
//
|
||||||
|
// It should only ever be called from send_event
|
||||||
|
fn handle_event(&self, event: Event<T>, control: &mut root::ControlFlow) {
|
||||||
|
let is_closed = self.is_closed();
|
||||||
|
|
||||||
|
match *self.0.runner.borrow_mut() {
|
||||||
|
Some(ref mut runner) => {
|
||||||
|
// An event is being processed, so the runner should be marked busy
|
||||||
|
runner.is_busy = true;
|
||||||
|
|
||||||
|
(runner.event_handler)(event, control);
|
||||||
|
|
||||||
|
// Maintain closed state, even if the callback changes it
|
||||||
|
if is_closed {
|
||||||
|
*control = root::ControlFlow::Exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// An event is no longer being processed
|
||||||
|
runner.is_busy = false;
|
||||||
|
}
|
||||||
|
// If an event is being handled without a runner somehow, add it to the event queue so
|
||||||
|
// it will eventually be processed
|
||||||
|
_ => self.0.events.borrow_mut().push_back(event),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't take events out of the queue if the loop is closed or the runner doesn't exist
|
||||||
|
// If the runner doesn't exist and this method recurses, it will recurse infinitely
|
||||||
|
if !is_closed && self.0.runner.borrow().is_some() {
|
||||||
|
// Take an event out of the queue and handle it
|
||||||
|
if let Some(event) = self.0.events.borrow_mut().pop_front() {
|
||||||
|
self.handle_event(event, control);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply the new ControlFlow that has been selected by the user
|
||||||
|
// Start any necessary timeouts etc
|
||||||
|
fn apply_control_flow(&self, control_flow: root::ControlFlow) {
|
||||||
|
let new_state = match control_flow {
|
||||||
|
root::ControlFlow::Poll => {
|
||||||
|
let cloned = self.clone();
|
||||||
|
State::Poll {
|
||||||
|
timeout: backend::Timeout::new(
|
||||||
|
move || cloned.send_event(Event::NewEvents(StartCause::Poll)),
|
||||||
|
Duration::from_millis(0),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
root::ControlFlow::Wait => State::Wait {
|
||||||
|
start: Instant::now(),
|
||||||
|
},
|
||||||
|
root::ControlFlow::WaitUntil(end) => {
|
||||||
|
let start = Instant::now();
|
||||||
|
|
||||||
|
let delay = if end <= start {
|
||||||
|
Duration::from_millis(0)
|
||||||
|
} else {
|
||||||
|
end - start
|
||||||
|
};
|
||||||
|
|
||||||
|
let cloned = self.clone();
|
||||||
|
|
||||||
|
State::WaitUntil {
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
timeout: backend::Timeout::new(
|
||||||
|
move || cloned.send_event(Event::NewEvents(StartCause::Poll)),
|
||||||
|
delay,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
root::ControlFlow::Exit => State::Exit,
|
||||||
|
};
|
||||||
|
|
||||||
|
match *self.0.runner.borrow_mut() {
|
||||||
|
Some(ref mut runner) => {
|
||||||
|
runner.state = new_state;
|
||||||
|
}
|
||||||
|
None => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the event loop is currently closed
|
||||||
|
fn is_closed(&self) -> bool {
|
||||||
|
match *self.0.runner.borrow() {
|
||||||
|
Some(ref runner) => runner.state.is_exit(),
|
||||||
|
None => false, // If the event loop is None, it has not been intialised yet, so it cannot be closed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the current control flow state
|
||||||
|
fn current_control_flow(&self) -> root::ControlFlow {
|
||||||
|
match *self.0.runner.borrow() {
|
||||||
|
Some(ref runner) => runner.state.control_flow(),
|
||||||
|
None => root::ControlFlow::Poll,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
40
src/platform_impl/web/event_loop/state.rs
Normal file
40
src/platform_impl/web/event_loop/state.rs
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
use super::backend;
|
||||||
|
use crate::event_loop::ControlFlow;
|
||||||
|
|
||||||
|
use instant::Instant;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum State {
|
||||||
|
Init,
|
||||||
|
WaitUntil {
|
||||||
|
timeout: backend::Timeout,
|
||||||
|
start: Instant,
|
||||||
|
end: Instant,
|
||||||
|
},
|
||||||
|
Wait {
|
||||||
|
start: Instant,
|
||||||
|
},
|
||||||
|
Poll {
|
||||||
|
timeout: backend::Timeout,
|
||||||
|
},
|
||||||
|
Exit,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl State {
|
||||||
|
pub fn is_exit(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
State::Exit => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn control_flow(&self) -> ControlFlow {
|
||||||
|
match self {
|
||||||
|
State::Init => ControlFlow::Poll,
|
||||||
|
State::WaitUntil { end, .. } => ControlFlow::WaitUntil(*end),
|
||||||
|
State::Wait { .. } => ControlFlow::Wait,
|
||||||
|
State::Poll { .. } => ControlFlow::Poll,
|
||||||
|
State::Exit => ControlFlow::Exit,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
162
src/platform_impl/web/event_loop/window_target.rs
Normal file
162
src/platform_impl/web/event_loop/window_target.rs
Normal file
|
@ -0,0 +1,162 @@
|
||||||
|
use super::{backend, device, proxy::Proxy, runner, window};
|
||||||
|
use crate::event::{DeviceId, ElementState, Event, KeyboardInput, TouchPhase, WindowEvent};
|
||||||
|
use crate::event_loop::ControlFlow;
|
||||||
|
use crate::window::WindowId;
|
||||||
|
use std::clone::Clone;
|
||||||
|
|
||||||
|
pub struct WindowTarget<T: 'static> {
|
||||||
|
pub(crate) runner: runner::Shared<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Clone for WindowTarget<T> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
WindowTarget {
|
||||||
|
runner: self.runner.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> WindowTarget<T> {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
WindowTarget {
|
||||||
|
runner: runner::Shared::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn proxy(&self) -> Proxy<T> {
|
||||||
|
Proxy::new(self.runner.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run(&self, event_handler: Box<dyn FnMut(Event<T>, &mut ControlFlow)>) {
|
||||||
|
self.runner.set_listener(event_handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn register(&self, canvas: &mut backend::Canvas) {
|
||||||
|
let runner = self.runner.clone();
|
||||||
|
canvas.on_blur(move || {
|
||||||
|
runner.send_event(Event::WindowEvent {
|
||||||
|
window_id: WindowId(window::Id),
|
||||||
|
event: WindowEvent::Focused(false),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
let runner = self.runner.clone();
|
||||||
|
canvas.on_focus(move || {
|
||||||
|
runner.send_event(Event::WindowEvent {
|
||||||
|
window_id: WindowId(window::Id),
|
||||||
|
event: WindowEvent::Focused(true),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
let runner = self.runner.clone();
|
||||||
|
canvas.on_keyboard_press(move |scancode, virtual_keycode, modifiers| {
|
||||||
|
runner.send_event(Event::WindowEvent {
|
||||||
|
window_id: WindowId(window::Id),
|
||||||
|
event: WindowEvent::KeyboardInput {
|
||||||
|
device_id: DeviceId(unsafe { device::Id::dummy() }),
|
||||||
|
input: KeyboardInput {
|
||||||
|
scancode,
|
||||||
|
state: ElementState::Pressed,
|
||||||
|
virtual_keycode,
|
||||||
|
modifiers,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
let runner = self.runner.clone();
|
||||||
|
canvas.on_keyboard_release(move |scancode, virtual_keycode, modifiers| {
|
||||||
|
runner.send_event(Event::WindowEvent {
|
||||||
|
window_id: WindowId(window::Id),
|
||||||
|
event: WindowEvent::KeyboardInput {
|
||||||
|
device_id: DeviceId(unsafe { device::Id::dummy() }),
|
||||||
|
input: KeyboardInput {
|
||||||
|
scancode,
|
||||||
|
state: ElementState::Released,
|
||||||
|
virtual_keycode,
|
||||||
|
modifiers,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
let runner = self.runner.clone();
|
||||||
|
canvas.on_received_character(move |char_code| {
|
||||||
|
runner.send_event(Event::WindowEvent {
|
||||||
|
window_id: WindowId(window::Id),
|
||||||
|
event: WindowEvent::ReceivedCharacter(char_code),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
let runner = self.runner.clone();
|
||||||
|
canvas.on_cursor_leave(move |pointer_id| {
|
||||||
|
runner.send_event(Event::WindowEvent {
|
||||||
|
window_id: WindowId(window::Id),
|
||||||
|
event: WindowEvent::CursorLeft {
|
||||||
|
device_id: DeviceId(device::Id(pointer_id)),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
let runner = self.runner.clone();
|
||||||
|
canvas.on_cursor_enter(move |pointer_id| {
|
||||||
|
runner.send_event(Event::WindowEvent {
|
||||||
|
window_id: WindowId(window::Id),
|
||||||
|
event: WindowEvent::CursorEntered {
|
||||||
|
device_id: DeviceId(device::Id(pointer_id)),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
let runner = self.runner.clone();
|
||||||
|
canvas.on_cursor_move(move |pointer_id, position, modifiers| {
|
||||||
|
runner.send_event(Event::WindowEvent {
|
||||||
|
window_id: WindowId(window::Id),
|
||||||
|
event: WindowEvent::CursorMoved {
|
||||||
|
device_id: DeviceId(device::Id(pointer_id)),
|
||||||
|
position,
|
||||||
|
modifiers,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
let runner = self.runner.clone();
|
||||||
|
canvas.on_mouse_press(move |pointer_id, button, modifiers| {
|
||||||
|
runner.send_event(Event::WindowEvent {
|
||||||
|
window_id: WindowId(window::Id),
|
||||||
|
event: WindowEvent::MouseInput {
|
||||||
|
device_id: DeviceId(device::Id(pointer_id)),
|
||||||
|
state: ElementState::Pressed,
|
||||||
|
button,
|
||||||
|
modifiers,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
let runner = self.runner.clone();
|
||||||
|
canvas.on_mouse_release(move |pointer_id, button, modifiers| {
|
||||||
|
runner.send_event(Event::WindowEvent {
|
||||||
|
window_id: WindowId(window::Id),
|
||||||
|
event: WindowEvent::MouseInput {
|
||||||
|
device_id: DeviceId(device::Id(pointer_id)),
|
||||||
|
state: ElementState::Released,
|
||||||
|
button,
|
||||||
|
modifiers,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
let runner = self.runner.clone();
|
||||||
|
canvas.on_mouse_wheel(move |pointer_id, delta, modifiers| {
|
||||||
|
runner.send_event(Event::WindowEvent {
|
||||||
|
window_id: WindowId(window::Id),
|
||||||
|
event: WindowEvent::MouseWheel {
|
||||||
|
device_id: DeviceId(device::Id(pointer_id)),
|
||||||
|
delta,
|
||||||
|
phase: TouchPhase::Moved,
|
||||||
|
modifiers,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
31
src/platform_impl/web/mod.rs
Normal file
31
src/platform_impl/web/mod.rs
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
// TODO: dpi
|
||||||
|
// TODO: close events (stdweb PR required)
|
||||||
|
// TODO: pointer locking (stdweb PR required)
|
||||||
|
// TODO: mouse wheel events (stdweb PR required)
|
||||||
|
// TODO: key event: .which() (stdweb PR)
|
||||||
|
// TODO: should there be a maximization / fullscreen API?
|
||||||
|
|
||||||
|
mod device;
|
||||||
|
mod error;
|
||||||
|
mod event_loop;
|
||||||
|
mod monitor;
|
||||||
|
mod window;
|
||||||
|
|
||||||
|
#[cfg(feature = "web-sys")]
|
||||||
|
#[path = "web_sys/mod.rs"]
|
||||||
|
mod backend;
|
||||||
|
|
||||||
|
#[cfg(feature = "stdweb")]
|
||||||
|
#[path = "stdweb/mod.rs"]
|
||||||
|
mod backend;
|
||||||
|
|
||||||
|
pub use self::device::Id as DeviceId;
|
||||||
|
pub use self::error::OsError;
|
||||||
|
pub use self::event_loop::{
|
||||||
|
EventLoop, Proxy as EventLoopProxy, WindowTarget as EventLoopWindowTarget,
|
||||||
|
};
|
||||||
|
pub use self::monitor::Handle as MonitorHandle;
|
||||||
|
pub use self::window::{
|
||||||
|
Id as WindowId, PlatformSpecificBuilderAttributes as PlatformSpecificWindowBuilderAttributes,
|
||||||
|
Window,
|
||||||
|
};
|
22
src/platform_impl/web/monitor.rs
Normal file
22
src/platform_impl/web/monitor.rs
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
use crate::dpi::{PhysicalPosition, PhysicalSize};
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub struct Handle;
|
||||||
|
|
||||||
|
impl Handle {
|
||||||
|
pub fn hidpi_factor(&self) -> f64 {
|
||||||
|
1.0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn position(&self) -> PhysicalPosition {
|
||||||
|
unimplemented!();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dimensions(&self) -> PhysicalSize {
|
||||||
|
unimplemented!();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn name(&self) -> Option<String> {
|
||||||
|
unimplemented!();
|
||||||
|
}
|
||||||
|
}
|
251
src/platform_impl/web/stdweb/canvas.rs
Normal file
251
src/platform_impl/web/stdweb/canvas.rs
Normal file
|
@ -0,0 +1,251 @@
|
||||||
|
use super::event;
|
||||||
|
use crate::dpi::{LogicalPosition, LogicalSize};
|
||||||
|
use crate::error::OsError as RootOE;
|
||||||
|
use crate::event::{ModifiersState, MouseButton, MouseScrollDelta, ScanCode, VirtualKeyCode};
|
||||||
|
use crate::platform_impl::OsError;
|
||||||
|
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
use stdweb::web::html_element::CanvasElement;
|
||||||
|
use stdweb::web::{
|
||||||
|
document, window, EventListenerHandle, IChildNode, IElement, IEventTarget, IHtmlElement, INode,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct Canvas {
|
||||||
|
raw: CanvasElement,
|
||||||
|
on_redraw: Rc<dyn Fn()>,
|
||||||
|
on_focus: Option<EventListenerHandle>,
|
||||||
|
on_blur: Option<EventListenerHandle>,
|
||||||
|
on_keyboard_release: Option<EventListenerHandle>,
|
||||||
|
on_keyboard_press: Option<EventListenerHandle>,
|
||||||
|
on_received_character: Option<EventListenerHandle>,
|
||||||
|
on_cursor_leave: Option<EventListenerHandle>,
|
||||||
|
on_cursor_enter: Option<EventListenerHandle>,
|
||||||
|
on_cursor_move: Option<EventListenerHandle>,
|
||||||
|
on_mouse_press: Option<EventListenerHandle>,
|
||||||
|
on_mouse_release: Option<EventListenerHandle>,
|
||||||
|
on_mouse_wheel: Option<EventListenerHandle>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Canvas {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.raw.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Canvas {
|
||||||
|
pub fn create<F>(on_redraw: F) -> Result<Self, RootOE>
|
||||||
|
where
|
||||||
|
F: 'static + Fn(),
|
||||||
|
{
|
||||||
|
let canvas: CanvasElement = document()
|
||||||
|
.create_element("canvas")
|
||||||
|
.map_err(|_| os_error!(OsError("Failed to create canvas element".to_owned())))?
|
||||||
|
.try_into()
|
||||||
|
.map_err(|_| os_error!(OsError("Failed to create canvas element".to_owned())))?;
|
||||||
|
|
||||||
|
document()
|
||||||
|
.body()
|
||||||
|
.ok_or_else(|| os_error!(OsError("Failed to find body node".to_owned())))?
|
||||||
|
.append_child(&canvas);
|
||||||
|
|
||||||
|
// TODO: Set up unique ids
|
||||||
|
canvas
|
||||||
|
.set_attribute("tabindex", "0")
|
||||||
|
.expect("Failed to set a tabindex");
|
||||||
|
|
||||||
|
Ok(Canvas {
|
||||||
|
raw: canvas,
|
||||||
|
on_redraw: Rc::new(on_redraw),
|
||||||
|
on_blur: None,
|
||||||
|
on_focus: None,
|
||||||
|
on_keyboard_release: None,
|
||||||
|
on_keyboard_press: None,
|
||||||
|
on_received_character: None,
|
||||||
|
on_cursor_leave: None,
|
||||||
|
on_cursor_enter: None,
|
||||||
|
on_cursor_move: None,
|
||||||
|
on_mouse_release: None,
|
||||||
|
on_mouse_press: None,
|
||||||
|
on_mouse_wheel: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_attribute(&self, attribute: &str, value: &str) {
|
||||||
|
self.raw
|
||||||
|
.set_attribute(attribute, value)
|
||||||
|
.expect(&format!("Set attribute: {}", attribute));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn position(&self) -> (f64, f64) {
|
||||||
|
let bounds = self.raw.get_bounding_client_rect();
|
||||||
|
|
||||||
|
(bounds.get_x(), bounds.get_y())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn width(&self) -> f64 {
|
||||||
|
self.raw.width() as f64
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn height(&self) -> f64 {
|
||||||
|
self.raw.height() as f64
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_size(&self, size: LogicalSize) {
|
||||||
|
self.raw.set_width(size.width as u32);
|
||||||
|
self.raw.set_height(size.height as u32);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn raw(&self) -> &CanvasElement {
|
||||||
|
&self.raw
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn request_redraw(&self) {
|
||||||
|
let on_redraw = self.on_redraw.clone();
|
||||||
|
window().request_animation_frame(move |_| on_redraw());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_blur<F>(&mut self, mut handler: F)
|
||||||
|
where
|
||||||
|
F: 'static + FnMut(),
|
||||||
|
{
|
||||||
|
self.on_blur = Some(self.add_event(move |_: BlurEvent| {
|
||||||
|
handler();
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_focus<F>(&mut self, mut handler: F)
|
||||||
|
where
|
||||||
|
F: 'static + FnMut(),
|
||||||
|
{
|
||||||
|
self.on_focus = Some(self.add_event(move |_: FocusEvent| {
|
||||||
|
handler();
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_keyboard_release<F>(&mut self, mut handler: F)
|
||||||
|
where
|
||||||
|
F: 'static + FnMut(ScanCode, Option<VirtualKeyCode>, ModifiersState),
|
||||||
|
{
|
||||||
|
self.on_keyboard_release = Some(self.add_event(move |event: KeyUpEvent| {
|
||||||
|
handler(
|
||||||
|
event::scan_code(&event),
|
||||||
|
event::virtual_key_code(&event),
|
||||||
|
event::keyboard_modifiers(&event),
|
||||||
|
);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_keyboard_press<F>(&mut self, mut handler: F)
|
||||||
|
where
|
||||||
|
F: 'static + FnMut(ScanCode, Option<VirtualKeyCode>, ModifiersState),
|
||||||
|
{
|
||||||
|
self.on_keyboard_press = Some(self.add_event(move |event: KeyDownEvent| {
|
||||||
|
handler(
|
||||||
|
event::scan_code(&event),
|
||||||
|
event::virtual_key_code(&event),
|
||||||
|
event::keyboard_modifiers(&event),
|
||||||
|
);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_received_character<F>(&mut self, mut handler: F)
|
||||||
|
where
|
||||||
|
F: 'static + FnMut(char),
|
||||||
|
{
|
||||||
|
// TODO: Use `beforeinput`.
|
||||||
|
//
|
||||||
|
// 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| {
|
||||||
|
handler(event::codepoint(&event));
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_cursor_leave<F>(&mut self, mut handler: F)
|
||||||
|
where
|
||||||
|
F: 'static + FnMut(i32),
|
||||||
|
{
|
||||||
|
self.on_cursor_leave = Some(self.add_event(move |event: PointerOutEvent| {
|
||||||
|
handler(event.pointer_id());
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_cursor_enter<F>(&mut self, mut handler: F)
|
||||||
|
where
|
||||||
|
F: 'static + FnMut(i32),
|
||||||
|
{
|
||||||
|
self.on_cursor_enter = Some(self.add_event(move |event: PointerOverEvent| {
|
||||||
|
handler(event.pointer_id());
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_mouse_release<F>(&mut self, mut handler: F)
|
||||||
|
where
|
||||||
|
F: 'static + FnMut(i32, MouseButton, ModifiersState),
|
||||||
|
{
|
||||||
|
self.on_mouse_release = Some(self.add_event(move |event: PointerUpEvent| {
|
||||||
|
handler(
|
||||||
|
event.pointer_id(),
|
||||||
|
event::mouse_button(&event),
|
||||||
|
event::mouse_modifiers(&event),
|
||||||
|
);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_mouse_press<F>(&mut self, mut handler: F)
|
||||||
|
where
|
||||||
|
F: 'static + FnMut(i32, MouseButton, ModifiersState),
|
||||||
|
{
|
||||||
|
self.on_mouse_press = Some(self.add_event(move |event: PointerDownEvent| {
|
||||||
|
handler(
|
||||||
|
event.pointer_id(),
|
||||||
|
event::mouse_button(&event),
|
||||||
|
event::mouse_modifiers(&event),
|
||||||
|
);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_cursor_move<F>(&mut self, mut handler: F)
|
||||||
|
where
|
||||||
|
F: 'static + FnMut(i32, LogicalPosition, ModifiersState),
|
||||||
|
{
|
||||||
|
self.on_cursor_move = Some(self.add_event(move |event: PointerMoveEvent| {
|
||||||
|
handler(
|
||||||
|
event.pointer_id(),
|
||||||
|
event::mouse_position(&event),
|
||||||
|
event::mouse_modifiers(&event),
|
||||||
|
);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_mouse_wheel<F>(&mut self, mut handler: F)
|
||||||
|
where
|
||||||
|
F: 'static + FnMut(i32, MouseScrollDelta, ModifiersState),
|
||||||
|
{
|
||||||
|
self.on_mouse_wheel = Some(self.add_event(move |event: MouseWheelEvent| {
|
||||||
|
if let Some(delta) = event::mouse_scroll_delta(&event) {
|
||||||
|
handler(0, delta, event::mouse_modifiers(&event));
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_event<E, F>(&self, mut handler: F) -> EventListenerHandle
|
||||||
|
where
|
||||||
|
E: ConcreteEvent,
|
||||||
|
F: 'static + FnMut(E),
|
||||||
|
{
|
||||||
|
self.raw.add_event_listener(move |event: E| {
|
||||||
|
event.stop_propagation();
|
||||||
|
event.cancel_bubble();
|
||||||
|
|
||||||
|
handler(event);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
229
src/platform_impl/web/stdweb/event.rs
Normal file
229
src/platform_impl/web/stdweb/event.rs
Normal file
|
@ -0,0 +1,229 @@
|
||||||
|
use crate::dpi::LogicalPosition;
|
||||||
|
use crate::event::{ModifiersState, MouseButton, MouseScrollDelta, ScanCode, VirtualKeyCode};
|
||||||
|
|
||||||
|
use stdweb::web::event::{IKeyboardEvent, IMouseEvent, MouseWheelDeltaMode, MouseWheelEvent};
|
||||||
|
use stdweb::{unstable::TryInto, JsSerialize};
|
||||||
|
|
||||||
|
pub fn mouse_button(event: &impl IMouseEvent) -> MouseButton {
|
||||||
|
match event.button() {
|
||||||
|
stdweb::web::event::MouseButton::Left => MouseButton::Left,
|
||||||
|
stdweb::web::event::MouseButton::Right => MouseButton::Right,
|
||||||
|
stdweb::web::event::MouseButton::Wheel => MouseButton::Middle,
|
||||||
|
stdweb::web::event::MouseButton::Button4 => MouseButton::Other(0),
|
||||||
|
stdweb::web::event::MouseButton::Button5 => MouseButton::Other(1),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mouse_modifiers(event: &impl IMouseEvent) -> ModifiersState {
|
||||||
|
ModifiersState {
|
||||||
|
shift: event.shift_key(),
|
||||||
|
ctrl: event.ctrl_key(),
|
||||||
|
alt: event.alt_key(),
|
||||||
|
logo: event.meta_key(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mouse_position(event: &impl IMouseEvent) -> LogicalPosition {
|
||||||
|
LogicalPosition {
|
||||||
|
x: event.offset_x() as f64,
|
||||||
|
y: event.offset_y() as f64,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mouse_scroll_delta(event: &MouseWheelEvent) -> Option<MouseScrollDelta> {
|
||||||
|
let x = event.delta_x();
|
||||||
|
let y = event.delta_y();
|
||||||
|
|
||||||
|
match event.delta_mode() {
|
||||||
|
MouseWheelDeltaMode::Line => Some(MouseScrollDelta::LineDelta(x as f32, y as f32)),
|
||||||
|
MouseWheelDeltaMode::Pixel => Some(MouseScrollDelta::PixelDelta(LogicalPosition { x, y })),
|
||||||
|
MouseWheelDeltaMode::Page => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn scan_code<T: JsSerialize>(event: &T) -> ScanCode {
|
||||||
|
let key_code = js! ( return @{event}.key_code; );
|
||||||
|
|
||||||
|
key_code
|
||||||
|
.try_into()
|
||||||
|
.expect("The which value should be a number")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn virtual_key_code(event: &impl IKeyboardEvent) -> Option<VirtualKeyCode> {
|
||||||
|
Some(match &event.code()[..] {
|
||||||
|
"Digit1" => VirtualKeyCode::Key1,
|
||||||
|
"Digit2" => VirtualKeyCode::Key2,
|
||||||
|
"Digit3" => VirtualKeyCode::Key3,
|
||||||
|
"Digit4" => VirtualKeyCode::Key4,
|
||||||
|
"Digit5" => VirtualKeyCode::Key5,
|
||||||
|
"Digit6" => VirtualKeyCode::Key6,
|
||||||
|
"Digit7" => VirtualKeyCode::Key7,
|
||||||
|
"Digit8" => VirtualKeyCode::Key8,
|
||||||
|
"Digit9" => VirtualKeyCode::Key9,
|
||||||
|
"Digit0" => VirtualKeyCode::Key0,
|
||||||
|
"KeyA" => VirtualKeyCode::A,
|
||||||
|
"KeyB" => VirtualKeyCode::B,
|
||||||
|
"KeyC" => VirtualKeyCode::C,
|
||||||
|
"KeyD" => VirtualKeyCode::D,
|
||||||
|
"KeyE" => VirtualKeyCode::E,
|
||||||
|
"KeyF" => VirtualKeyCode::F,
|
||||||
|
"KeyG" => VirtualKeyCode::G,
|
||||||
|
"KeyH" => VirtualKeyCode::H,
|
||||||
|
"KeyI" => VirtualKeyCode::I,
|
||||||
|
"KeyJ" => VirtualKeyCode::J,
|
||||||
|
"KeyK" => VirtualKeyCode::K,
|
||||||
|
"KeyL" => VirtualKeyCode::L,
|
||||||
|
"KeyM" => VirtualKeyCode::M,
|
||||||
|
"KeyN" => VirtualKeyCode::N,
|
||||||
|
"KeyO" => VirtualKeyCode::O,
|
||||||
|
"KeyP" => VirtualKeyCode::P,
|
||||||
|
"KeyQ" => VirtualKeyCode::Q,
|
||||||
|
"KeyR" => VirtualKeyCode::R,
|
||||||
|
"KeyS" => VirtualKeyCode::S,
|
||||||
|
"KeyT" => VirtualKeyCode::T,
|
||||||
|
"KeyU" => VirtualKeyCode::U,
|
||||||
|
"KeyV" => VirtualKeyCode::V,
|
||||||
|
"KeyW" => VirtualKeyCode::W,
|
||||||
|
"KeyX" => VirtualKeyCode::X,
|
||||||
|
"KeyY" => VirtualKeyCode::Y,
|
||||||
|
"KeyZ" => VirtualKeyCode::Z,
|
||||||
|
"Escape" => VirtualKeyCode::Escape,
|
||||||
|
"F1" => VirtualKeyCode::F1,
|
||||||
|
"F2" => VirtualKeyCode::F2,
|
||||||
|
"F3" => VirtualKeyCode::F3,
|
||||||
|
"F4" => VirtualKeyCode::F4,
|
||||||
|
"F5" => VirtualKeyCode::F5,
|
||||||
|
"F6" => VirtualKeyCode::F6,
|
||||||
|
"F7" => VirtualKeyCode::F7,
|
||||||
|
"F8" => VirtualKeyCode::F8,
|
||||||
|
"F9" => VirtualKeyCode::F9,
|
||||||
|
"F10" => VirtualKeyCode::F10,
|
||||||
|
"F11" => VirtualKeyCode::F11,
|
||||||
|
"F12" => VirtualKeyCode::F12,
|
||||||
|
"F13" => VirtualKeyCode::F13,
|
||||||
|
"F14" => VirtualKeyCode::F14,
|
||||||
|
"F15" => VirtualKeyCode::F15,
|
||||||
|
"F16" => VirtualKeyCode::F16,
|
||||||
|
"F17" => VirtualKeyCode::F17,
|
||||||
|
"F18" => VirtualKeyCode::F18,
|
||||||
|
"F19" => VirtualKeyCode::F19,
|
||||||
|
"F20" => VirtualKeyCode::F20,
|
||||||
|
"F21" => VirtualKeyCode::F21,
|
||||||
|
"F22" => VirtualKeyCode::F22,
|
||||||
|
"F23" => VirtualKeyCode::F23,
|
||||||
|
"F24" => VirtualKeyCode::F24,
|
||||||
|
"PrintScreen" => VirtualKeyCode::Snapshot,
|
||||||
|
"ScrollLock" => VirtualKeyCode::Scroll,
|
||||||
|
"Pause" => VirtualKeyCode::Pause,
|
||||||
|
"Insert" => VirtualKeyCode::Insert,
|
||||||
|
"Home" => VirtualKeyCode::Home,
|
||||||
|
"Delete" => VirtualKeyCode::Delete,
|
||||||
|
"End" => VirtualKeyCode::End,
|
||||||
|
"PageDown" => VirtualKeyCode::PageDown,
|
||||||
|
"PageUp" => VirtualKeyCode::PageUp,
|
||||||
|
"ArrowLeft" => VirtualKeyCode::Left,
|
||||||
|
"ArrowUp" => VirtualKeyCode::Up,
|
||||||
|
"ArrowRight" => VirtualKeyCode::Right,
|
||||||
|
"ArrowDown" => VirtualKeyCode::Down,
|
||||||
|
"Backspace" => VirtualKeyCode::Back,
|
||||||
|
"Enter" => VirtualKeyCode::Return,
|
||||||
|
"Space" => VirtualKeyCode::Space,
|
||||||
|
"Compose" => VirtualKeyCode::Compose,
|
||||||
|
"Caret" => VirtualKeyCode::Caret,
|
||||||
|
"NumLock" => VirtualKeyCode::Numlock,
|
||||||
|
"Numpad0" => VirtualKeyCode::Numpad0,
|
||||||
|
"Numpad1" => VirtualKeyCode::Numpad1,
|
||||||
|
"Numpad2" => VirtualKeyCode::Numpad2,
|
||||||
|
"Numpad3" => VirtualKeyCode::Numpad3,
|
||||||
|
"Numpad4" => VirtualKeyCode::Numpad4,
|
||||||
|
"Numpad5" => VirtualKeyCode::Numpad5,
|
||||||
|
"Numpad6" => VirtualKeyCode::Numpad6,
|
||||||
|
"Numpad7" => VirtualKeyCode::Numpad7,
|
||||||
|
"Numpad8" => VirtualKeyCode::Numpad8,
|
||||||
|
"Numpad9" => VirtualKeyCode::Numpad9,
|
||||||
|
"AbntC1" => VirtualKeyCode::AbntC1,
|
||||||
|
"AbntC2" => VirtualKeyCode::AbntC2,
|
||||||
|
"NumpadAdd" => VirtualKeyCode::Add,
|
||||||
|
"Quote" => VirtualKeyCode::Apostrophe,
|
||||||
|
"Apps" => VirtualKeyCode::Apps,
|
||||||
|
"At" => VirtualKeyCode::At,
|
||||||
|
"Ax" => VirtualKeyCode::Ax,
|
||||||
|
"Backslash" => VirtualKeyCode::Backslash,
|
||||||
|
"Calculator" => VirtualKeyCode::Calculator,
|
||||||
|
"Capital" => VirtualKeyCode::Capital,
|
||||||
|
"Semicolon" => VirtualKeyCode::Semicolon,
|
||||||
|
"Comma" => VirtualKeyCode::Comma,
|
||||||
|
"Convert" => VirtualKeyCode::Convert,
|
||||||
|
"NumpadDecimal" => VirtualKeyCode::Decimal,
|
||||||
|
"NumpadDivide" => VirtualKeyCode::Divide,
|
||||||
|
"Equal" => VirtualKeyCode::Equals,
|
||||||
|
"Backquote" => VirtualKeyCode::Grave,
|
||||||
|
"Kana" => VirtualKeyCode::Kana,
|
||||||
|
"Kanji" => VirtualKeyCode::Kanji,
|
||||||
|
"AltLeft" => VirtualKeyCode::LAlt,
|
||||||
|
"BracketLeft" => VirtualKeyCode::LBracket,
|
||||||
|
"ControlLeft" => VirtualKeyCode::LControl,
|
||||||
|
"ShiftLeft" => VirtualKeyCode::LShift,
|
||||||
|
"MetaLeft" => VirtualKeyCode::LWin,
|
||||||
|
"Mail" => VirtualKeyCode::Mail,
|
||||||
|
"MediaSelect" => VirtualKeyCode::MediaSelect,
|
||||||
|
"MediaStop" => VirtualKeyCode::MediaStop,
|
||||||
|
"Minus" => VirtualKeyCode::Minus,
|
||||||
|
"NumpadMultiply" => VirtualKeyCode::Multiply,
|
||||||
|
"Mute" => VirtualKeyCode::Mute,
|
||||||
|
"LaunchMyComputer" => VirtualKeyCode::MyComputer,
|
||||||
|
"NavigateForward" => VirtualKeyCode::NavigateForward,
|
||||||
|
"NavigateBackward" => VirtualKeyCode::NavigateBackward,
|
||||||
|
"NextTrack" => VirtualKeyCode::NextTrack,
|
||||||
|
"NoConvert" => VirtualKeyCode::NoConvert,
|
||||||
|
"NumpadComma" => VirtualKeyCode::NumpadComma,
|
||||||
|
"NumpadEnter" => VirtualKeyCode::NumpadEnter,
|
||||||
|
"NumpadEquals" => VirtualKeyCode::NumpadEquals,
|
||||||
|
"OEM102" => VirtualKeyCode::OEM102,
|
||||||
|
"Period" => VirtualKeyCode::Period,
|
||||||
|
"PlayPause" => VirtualKeyCode::PlayPause,
|
||||||
|
"Power" => VirtualKeyCode::Power,
|
||||||
|
"PrevTrack" => VirtualKeyCode::PrevTrack,
|
||||||
|
"AltRight" => VirtualKeyCode::RAlt,
|
||||||
|
"BracketRight" => VirtualKeyCode::RBracket,
|
||||||
|
"ControlRight" => VirtualKeyCode::RControl,
|
||||||
|
"ShiftRight" => VirtualKeyCode::RShift,
|
||||||
|
"MetaRight" => VirtualKeyCode::RWin,
|
||||||
|
"Slash" => VirtualKeyCode::Slash,
|
||||||
|
"Sleep" => VirtualKeyCode::Sleep,
|
||||||
|
"Stop" => VirtualKeyCode::Stop,
|
||||||
|
"NumpadSubtract" => VirtualKeyCode::Subtract,
|
||||||
|
"Sysrq" => VirtualKeyCode::Sysrq,
|
||||||
|
"Tab" => VirtualKeyCode::Tab,
|
||||||
|
"Underline" => VirtualKeyCode::Underline,
|
||||||
|
"Unlabeled" => VirtualKeyCode::Unlabeled,
|
||||||
|
"AudioVolumeDown" => VirtualKeyCode::VolumeDown,
|
||||||
|
"AudioVolumeUp" => VirtualKeyCode::VolumeUp,
|
||||||
|
"Wake" => VirtualKeyCode::Wake,
|
||||||
|
"WebBack" => VirtualKeyCode::WebBack,
|
||||||
|
"WebFavorites" => VirtualKeyCode::WebFavorites,
|
||||||
|
"WebForward" => VirtualKeyCode::WebForward,
|
||||||
|
"WebHome" => VirtualKeyCode::WebHome,
|
||||||
|
"WebRefresh" => VirtualKeyCode::WebRefresh,
|
||||||
|
"WebSearch" => VirtualKeyCode::WebSearch,
|
||||||
|
"WebStop" => VirtualKeyCode::WebStop,
|
||||||
|
"Yen" => VirtualKeyCode::Yen,
|
||||||
|
_ => return None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn keyboard_modifiers(event: &impl IKeyboardEvent) -> ModifiersState {
|
||||||
|
ModifiersState {
|
||||||
|
shift: event.shift_key(),
|
||||||
|
ctrl: event.ctrl_key(),
|
||||||
|
alt: event.alt_key(),
|
||||||
|
logo: event.meta_key(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn codepoint(event: &impl IKeyboardEvent) -> char {
|
||||||
|
// `event.key()` always returns a non-empty `String`. Therefore, this should
|
||||||
|
// never panic.
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key
|
||||||
|
event.key().chars().next().unwrap()
|
||||||
|
}
|
21
src/platform_impl/web/stdweb/mod.rs
Normal file
21
src/platform_impl/web/stdweb/mod.rs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
mod canvas;
|
||||||
|
mod event;
|
||||||
|
mod timeout;
|
||||||
|
|
||||||
|
pub use self::canvas::Canvas;
|
||||||
|
pub use self::timeout::Timeout;
|
||||||
|
|
||||||
|
use crate::platform::web::WindowExtStdweb;
|
||||||
|
use crate::window::Window;
|
||||||
|
|
||||||
|
use stdweb::web::html_element::CanvasElement;
|
||||||
|
|
||||||
|
pub fn throw(msg: &str) {
|
||||||
|
js! { throw @{msg} }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WindowExtStdweb for Window {
|
||||||
|
fn canvas(&self) -> CanvasElement {
|
||||||
|
self.window.canvas().raw().clone()
|
||||||
|
}
|
||||||
|
}
|
25
src/platform_impl/web/stdweb/timeout.rs
Normal file
25
src/platform_impl/web/stdweb/timeout.rs
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
use std::time::Duration;
|
||||||
|
use stdweb::web::{window, IWindowOrWorker, TimeoutHandle};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Timeout {
|
||||||
|
handle: TimeoutHandle,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Timeout {
|
||||||
|
pub fn new<F>(f: F, duration: Duration) -> Timeout
|
||||||
|
where
|
||||||
|
F: 'static + FnMut(),
|
||||||
|
{
|
||||||
|
Timeout {
|
||||||
|
handle: window().set_clearable_timeout(f, duration.as_millis() as u32),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Timeout {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let handle = std::mem::replace(&mut self.handle, unsafe { std::mem::uninitialized() });
|
||||||
|
handle.clear();
|
||||||
|
}
|
||||||
|
}
|
257
src/platform_impl/web/web_sys/canvas.rs
Normal file
257
src/platform_impl/web/web_sys/canvas.rs
Normal file
|
@ -0,0 +1,257 @@
|
||||||
|
use super::event;
|
||||||
|
use crate::dpi::{LogicalPosition, LogicalSize};
|
||||||
|
use crate::error::OsError as RootOE;
|
||||||
|
use crate::event::{ModifiersState, MouseButton, MouseScrollDelta, ScanCode, VirtualKeyCode};
|
||||||
|
use crate::platform_impl::OsError;
|
||||||
|
|
||||||
|
use wasm_bindgen::{closure::Closure, JsCast};
|
||||||
|
use web_sys::{FocusEvent, HtmlCanvasElement, KeyboardEvent, PointerEvent, WheelEvent};
|
||||||
|
|
||||||
|
pub struct Canvas {
|
||||||
|
raw: HtmlCanvasElement,
|
||||||
|
on_redraw: Closure<dyn Fn()>,
|
||||||
|
on_focus: Option<Closure<dyn FnMut(FocusEvent)>>,
|
||||||
|
on_blur: Option<Closure<dyn FnMut(FocusEvent)>>,
|
||||||
|
on_keyboard_release: Option<Closure<dyn FnMut(KeyboardEvent)>>,
|
||||||
|
on_keyboard_press: Option<Closure<dyn FnMut(KeyboardEvent)>>,
|
||||||
|
on_received_character: Option<Closure<dyn FnMut(KeyboardEvent)>>,
|
||||||
|
on_cursor_leave: Option<Closure<dyn FnMut(PointerEvent)>>,
|
||||||
|
on_cursor_enter: Option<Closure<dyn FnMut(PointerEvent)>>,
|
||||||
|
on_cursor_move: Option<Closure<dyn FnMut(PointerEvent)>>,
|
||||||
|
on_mouse_press: Option<Closure<dyn FnMut(PointerEvent)>>,
|
||||||
|
on_mouse_release: Option<Closure<dyn FnMut(PointerEvent)>>,
|
||||||
|
on_mouse_wheel: Option<Closure<dyn FnMut(WheelEvent)>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Canvas {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.raw.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Canvas {
|
||||||
|
pub fn create<F>(on_redraw: F) -> Result<Self, RootOE>
|
||||||
|
where
|
||||||
|
F: 'static + Fn(),
|
||||||
|
{
|
||||||
|
let window = web_sys::window().expect("Failed to obtain window");
|
||||||
|
let document = window.document().expect("Failed to obtain document");
|
||||||
|
|
||||||
|
let canvas: HtmlCanvasElement = document
|
||||||
|
.create_element("canvas")
|
||||||
|
.map_err(|_| os_error!(OsError("Failed to create canvas element".to_owned())))?
|
||||||
|
.unchecked_into();
|
||||||
|
|
||||||
|
document
|
||||||
|
.body()
|
||||||
|
.ok_or_else(|| os_error!(OsError("Failed to find body node".to_owned())))?
|
||||||
|
.append_child(&canvas)
|
||||||
|
.map_err(|_| os_error!(OsError("Failed to append canvas".to_owned())))?;
|
||||||
|
|
||||||
|
// TODO: Set up unique ids
|
||||||
|
canvas
|
||||||
|
.set_attribute("tabindex", "0")
|
||||||
|
.expect("Failed to set a tabindex");
|
||||||
|
|
||||||
|
Ok(Canvas {
|
||||||
|
raw: canvas,
|
||||||
|
on_redraw: Closure::wrap(Box::new(on_redraw) as Box<dyn Fn()>),
|
||||||
|
on_blur: None,
|
||||||
|
on_focus: None,
|
||||||
|
on_keyboard_release: None,
|
||||||
|
on_keyboard_press: None,
|
||||||
|
on_received_character: None,
|
||||||
|
on_cursor_leave: None,
|
||||||
|
on_cursor_enter: None,
|
||||||
|
on_cursor_move: None,
|
||||||
|
on_mouse_release: None,
|
||||||
|
on_mouse_press: None,
|
||||||
|
on_mouse_wheel: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_attribute(&self, attribute: &str, value: &str) {
|
||||||
|
self.raw
|
||||||
|
.set_attribute(attribute, value)
|
||||||
|
.expect(&format!("Set attribute: {}", attribute));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn position(&self) -> (f64, f64) {
|
||||||
|
let bounds = self.raw.get_bounding_client_rect();
|
||||||
|
|
||||||
|
(bounds.x(), bounds.y())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn width(&self) -> f64 {
|
||||||
|
self.raw.width() as f64
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn height(&self) -> f64 {
|
||||||
|
self.raw.height() as f64
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_size(&self, size: LogicalSize) {
|
||||||
|
self.raw.set_width(size.width as u32);
|
||||||
|
self.raw.set_height(size.height as u32);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn raw(&self) -> &HtmlCanvasElement {
|
||||||
|
&self.raw
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn request_redraw(&self) {
|
||||||
|
let window = web_sys::window().expect("Failed to obtain window");
|
||||||
|
window
|
||||||
|
.request_animation_frame(&self.on_redraw.as_ref().unchecked_ref())
|
||||||
|
.expect("Failed to request animation frame");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_blur<F>(&mut self, mut handler: F)
|
||||||
|
where
|
||||||
|
F: 'static + FnMut(),
|
||||||
|
{
|
||||||
|
self.on_blur = Some(self.add_event("blur", move |_: FocusEvent| {
|
||||||
|
handler();
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_focus<F>(&mut self, mut handler: F)
|
||||||
|
where
|
||||||
|
F: 'static + FnMut(),
|
||||||
|
{
|
||||||
|
self.on_focus = Some(self.add_event("focus", move |_: FocusEvent| {
|
||||||
|
handler();
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_keyboard_release<F>(&mut self, mut handler: F)
|
||||||
|
where
|
||||||
|
F: 'static + FnMut(ScanCode, Option<VirtualKeyCode>, 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),
|
||||||
|
);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_keyboard_press<F>(&mut self, mut handler: F)
|
||||||
|
where
|
||||||
|
F: 'static + FnMut(ScanCode, Option<VirtualKeyCode>, 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),
|
||||||
|
);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_received_character<F>(&mut self, mut handler: F)
|
||||||
|
where
|
||||||
|
F: 'static + FnMut(char),
|
||||||
|
{
|
||||||
|
// TODO: Use `beforeinput`.
|
||||||
|
//
|
||||||
|
// 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| {
|
||||||
|
handler(event::codepoint(&event));
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_cursor_leave<F>(&mut self, mut handler: F)
|
||||||
|
where
|
||||||
|
F: 'static + FnMut(i32),
|
||||||
|
{
|
||||||
|
self.on_cursor_leave = Some(self.add_event("pointerout", move |event: PointerEvent| {
|
||||||
|
handler(event.pointer_id());
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_cursor_enter<F>(&mut self, mut handler: F)
|
||||||
|
where
|
||||||
|
F: 'static + FnMut(i32),
|
||||||
|
{
|
||||||
|
self.on_cursor_enter = Some(self.add_event("pointerover", move |event: PointerEvent| {
|
||||||
|
handler(event.pointer_id());
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_mouse_release<F>(&mut self, mut handler: F)
|
||||||
|
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),
|
||||||
|
);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_mouse_press<F>(&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),
|
||||||
|
);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_cursor_move<F>(&mut self, mut handler: F)
|
||||||
|
where
|
||||||
|
F: 'static + FnMut(i32, LogicalPosition, ModifiersState),
|
||||||
|
{
|
||||||
|
self.on_cursor_move = Some(self.add_event("pointermove", move |event: PointerEvent| {
|
||||||
|
handler(
|
||||||
|
event.pointer_id(),
|
||||||
|
event::mouse_position(&event),
|
||||||
|
event::mouse_modifiers(&event),
|
||||||
|
);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_mouse_wheel<F>(&mut self, mut handler: F)
|
||||||
|
where
|
||||||
|
F: 'static + FnMut(i32, MouseScrollDelta, ModifiersState),
|
||||||
|
{
|
||||||
|
self.on_mouse_wheel = Some(self.add_event("wheel", move |event: WheelEvent| {
|
||||||
|
if let Some(delta) = event::mouse_scroll_delta(&event) {
|
||||||
|
handler(0, delta, event::mouse_modifiers(&event));
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_event<E, F>(&self, event_name: &str, mut handler: F) -> Closure<dyn FnMut(E)>
|
||||||
|
where
|
||||||
|
E: 'static + AsRef<web_sys::Event> + wasm_bindgen::convert::FromWasmAbi,
|
||||||
|
F: 'static + FnMut(E),
|
||||||
|
{
|
||||||
|
let closure = Closure::wrap(Box::new(move |event: E| {
|
||||||
|
{
|
||||||
|
let event_ref = event.as_ref();
|
||||||
|
event_ref.stop_propagation();
|
||||||
|
event_ref.cancel_bubble();
|
||||||
|
}
|
||||||
|
|
||||||
|
handler(event);
|
||||||
|
}) as Box<dyn FnMut(E)>);
|
||||||
|
|
||||||
|
self.raw
|
||||||
|
.add_event_listener_with_callback(event_name, &closure.as_ref().unchecked_ref())
|
||||||
|
.expect("Failed to add event listener with callback");
|
||||||
|
|
||||||
|
closure
|
||||||
|
}
|
||||||
|
}
|
227
src/platform_impl/web/web_sys/event.rs
Normal file
227
src/platform_impl/web/web_sys/event.rs
Normal file
|
@ -0,0 +1,227 @@
|
||||||
|
use crate::dpi::LogicalPosition;
|
||||||
|
use crate::event::{ModifiersState, MouseButton, MouseScrollDelta, ScanCode, VirtualKeyCode};
|
||||||
|
|
||||||
|
use std::convert::TryInto;
|
||||||
|
use web_sys::{KeyboardEvent, MouseEvent, WheelEvent};
|
||||||
|
|
||||||
|
pub fn mouse_button(event: &MouseEvent) -> MouseButton {
|
||||||
|
match event.button() {
|
||||||
|
0 => MouseButton::Left,
|
||||||
|
1 => MouseButton::Middle,
|
||||||
|
2 => MouseButton::Right,
|
||||||
|
i => MouseButton::Other((i - 3).try_into().expect("very large mouse button value")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mouse_modifiers(event: &MouseEvent) -> ModifiersState {
|
||||||
|
ModifiersState {
|
||||||
|
shift: event.shift_key(),
|
||||||
|
ctrl: event.ctrl_key(),
|
||||||
|
alt: event.alt_key(),
|
||||||
|
logo: event.meta_key(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mouse_position(event: &MouseEvent) -> LogicalPosition {
|
||||||
|
LogicalPosition {
|
||||||
|
x: event.offset_x() as f64,
|
||||||
|
y: event.offset_y() as f64,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mouse_scroll_delta(event: &WheelEvent) -> Option<MouseScrollDelta> {
|
||||||
|
let x = event.delta_x();
|
||||||
|
let y = event.delta_y();
|
||||||
|
|
||||||
|
match event.delta_mode() {
|
||||||
|
WheelEvent::DOM_DELTA_LINE => Some(MouseScrollDelta::LineDelta(x as f32, y as f32)),
|
||||||
|
WheelEvent::DOM_DELTA_PIXEL => Some(MouseScrollDelta::PixelDelta(LogicalPosition { x, y })),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn scan_code(event: &KeyboardEvent) -> ScanCode {
|
||||||
|
match event.key_code() {
|
||||||
|
0 => event.char_code(),
|
||||||
|
i => i,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn virtual_key_code(event: &KeyboardEvent) -> Option<VirtualKeyCode> {
|
||||||
|
Some(match &event.code()[..] {
|
||||||
|
"Digit1" => VirtualKeyCode::Key1,
|
||||||
|
"Digit2" => VirtualKeyCode::Key2,
|
||||||
|
"Digit3" => VirtualKeyCode::Key3,
|
||||||
|
"Digit4" => VirtualKeyCode::Key4,
|
||||||
|
"Digit5" => VirtualKeyCode::Key5,
|
||||||
|
"Digit6" => VirtualKeyCode::Key6,
|
||||||
|
"Digit7" => VirtualKeyCode::Key7,
|
||||||
|
"Digit8" => VirtualKeyCode::Key8,
|
||||||
|
"Digit9" => VirtualKeyCode::Key9,
|
||||||
|
"Digit0" => VirtualKeyCode::Key0,
|
||||||
|
"KeyA" => VirtualKeyCode::A,
|
||||||
|
"KeyB" => VirtualKeyCode::B,
|
||||||
|
"KeyC" => VirtualKeyCode::C,
|
||||||
|
"KeyD" => VirtualKeyCode::D,
|
||||||
|
"KeyE" => VirtualKeyCode::E,
|
||||||
|
"KeyF" => VirtualKeyCode::F,
|
||||||
|
"KeyG" => VirtualKeyCode::G,
|
||||||
|
"KeyH" => VirtualKeyCode::H,
|
||||||
|
"KeyI" => VirtualKeyCode::I,
|
||||||
|
"KeyJ" => VirtualKeyCode::J,
|
||||||
|
"KeyK" => VirtualKeyCode::K,
|
||||||
|
"KeyL" => VirtualKeyCode::L,
|
||||||
|
"KeyM" => VirtualKeyCode::M,
|
||||||
|
"KeyN" => VirtualKeyCode::N,
|
||||||
|
"KeyO" => VirtualKeyCode::O,
|
||||||
|
"KeyP" => VirtualKeyCode::P,
|
||||||
|
"KeyQ" => VirtualKeyCode::Q,
|
||||||
|
"KeyR" => VirtualKeyCode::R,
|
||||||
|
"KeyS" => VirtualKeyCode::S,
|
||||||
|
"KeyT" => VirtualKeyCode::T,
|
||||||
|
"KeyU" => VirtualKeyCode::U,
|
||||||
|
"KeyV" => VirtualKeyCode::V,
|
||||||
|
"KeyW" => VirtualKeyCode::W,
|
||||||
|
"KeyX" => VirtualKeyCode::X,
|
||||||
|
"KeyY" => VirtualKeyCode::Y,
|
||||||
|
"KeyZ" => VirtualKeyCode::Z,
|
||||||
|
"Escape" => VirtualKeyCode::Escape,
|
||||||
|
"F1" => VirtualKeyCode::F1,
|
||||||
|
"F2" => VirtualKeyCode::F2,
|
||||||
|
"F3" => VirtualKeyCode::F3,
|
||||||
|
"F4" => VirtualKeyCode::F4,
|
||||||
|
"F5" => VirtualKeyCode::F5,
|
||||||
|
"F6" => VirtualKeyCode::F6,
|
||||||
|
"F7" => VirtualKeyCode::F7,
|
||||||
|
"F8" => VirtualKeyCode::F8,
|
||||||
|
"F9" => VirtualKeyCode::F9,
|
||||||
|
"F10" => VirtualKeyCode::F10,
|
||||||
|
"F11" => VirtualKeyCode::F11,
|
||||||
|
"F12" => VirtualKeyCode::F12,
|
||||||
|
"F13" => VirtualKeyCode::F13,
|
||||||
|
"F14" => VirtualKeyCode::F14,
|
||||||
|
"F15" => VirtualKeyCode::F15,
|
||||||
|
"F16" => VirtualKeyCode::F16,
|
||||||
|
"F17" => VirtualKeyCode::F17,
|
||||||
|
"F18" => VirtualKeyCode::F18,
|
||||||
|
"F19" => VirtualKeyCode::F19,
|
||||||
|
"F20" => VirtualKeyCode::F20,
|
||||||
|
"F21" => VirtualKeyCode::F21,
|
||||||
|
"F22" => VirtualKeyCode::F22,
|
||||||
|
"F23" => VirtualKeyCode::F23,
|
||||||
|
"F24" => VirtualKeyCode::F24,
|
||||||
|
"PrintScreen" => VirtualKeyCode::Snapshot,
|
||||||
|
"ScrollLock" => VirtualKeyCode::Scroll,
|
||||||
|
"Pause" => VirtualKeyCode::Pause,
|
||||||
|
"Insert" => VirtualKeyCode::Insert,
|
||||||
|
"Home" => VirtualKeyCode::Home,
|
||||||
|
"Delete" => VirtualKeyCode::Delete,
|
||||||
|
"End" => VirtualKeyCode::End,
|
||||||
|
"PageDown" => VirtualKeyCode::PageDown,
|
||||||
|
"PageUp" => VirtualKeyCode::PageUp,
|
||||||
|
"ArrowLeft" => VirtualKeyCode::Left,
|
||||||
|
"ArrowUp" => VirtualKeyCode::Up,
|
||||||
|
"ArrowRight" => VirtualKeyCode::Right,
|
||||||
|
"ArrowDown" => VirtualKeyCode::Down,
|
||||||
|
"Backspace" => VirtualKeyCode::Back,
|
||||||
|
"Enter" => VirtualKeyCode::Return,
|
||||||
|
"Space" => VirtualKeyCode::Space,
|
||||||
|
"Compose" => VirtualKeyCode::Compose,
|
||||||
|
"Caret" => VirtualKeyCode::Caret,
|
||||||
|
"NumLock" => VirtualKeyCode::Numlock,
|
||||||
|
"Numpad0" => VirtualKeyCode::Numpad0,
|
||||||
|
"Numpad1" => VirtualKeyCode::Numpad1,
|
||||||
|
"Numpad2" => VirtualKeyCode::Numpad2,
|
||||||
|
"Numpad3" => VirtualKeyCode::Numpad3,
|
||||||
|
"Numpad4" => VirtualKeyCode::Numpad4,
|
||||||
|
"Numpad5" => VirtualKeyCode::Numpad5,
|
||||||
|
"Numpad6" => VirtualKeyCode::Numpad6,
|
||||||
|
"Numpad7" => VirtualKeyCode::Numpad7,
|
||||||
|
"Numpad8" => VirtualKeyCode::Numpad8,
|
||||||
|
"Numpad9" => VirtualKeyCode::Numpad9,
|
||||||
|
"AbntC1" => VirtualKeyCode::AbntC1,
|
||||||
|
"AbntC2" => VirtualKeyCode::AbntC2,
|
||||||
|
"NumpadAdd" => VirtualKeyCode::Add,
|
||||||
|
"Quote" => VirtualKeyCode::Apostrophe,
|
||||||
|
"Apps" => VirtualKeyCode::Apps,
|
||||||
|
"At" => VirtualKeyCode::At,
|
||||||
|
"Ax" => VirtualKeyCode::Ax,
|
||||||
|
"Backslash" => VirtualKeyCode::Backslash,
|
||||||
|
"Calculator" => VirtualKeyCode::Calculator,
|
||||||
|
"Capital" => VirtualKeyCode::Capital,
|
||||||
|
"Semicolon" => VirtualKeyCode::Semicolon,
|
||||||
|
"Comma" => VirtualKeyCode::Comma,
|
||||||
|
"Convert" => VirtualKeyCode::Convert,
|
||||||
|
"NumpadDecimal" => VirtualKeyCode::Decimal,
|
||||||
|
"NumpadDivide" => VirtualKeyCode::Divide,
|
||||||
|
"Equal" => VirtualKeyCode::Equals,
|
||||||
|
"Backquote" => VirtualKeyCode::Grave,
|
||||||
|
"Kana" => VirtualKeyCode::Kana,
|
||||||
|
"Kanji" => VirtualKeyCode::Kanji,
|
||||||
|
"AltLeft" => VirtualKeyCode::LAlt,
|
||||||
|
"BracketLeft" => VirtualKeyCode::LBracket,
|
||||||
|
"ControlLeft" => VirtualKeyCode::LControl,
|
||||||
|
"ShiftLeft" => VirtualKeyCode::LShift,
|
||||||
|
"MetaLeft" => VirtualKeyCode::LWin,
|
||||||
|
"Mail" => VirtualKeyCode::Mail,
|
||||||
|
"MediaSelect" => VirtualKeyCode::MediaSelect,
|
||||||
|
"MediaStop" => VirtualKeyCode::MediaStop,
|
||||||
|
"Minus" => VirtualKeyCode::Minus,
|
||||||
|
"NumpadMultiply" => VirtualKeyCode::Multiply,
|
||||||
|
"Mute" => VirtualKeyCode::Mute,
|
||||||
|
"LaunchMyComputer" => VirtualKeyCode::MyComputer,
|
||||||
|
"NavigateForward" => VirtualKeyCode::NavigateForward,
|
||||||
|
"NavigateBackward" => VirtualKeyCode::NavigateBackward,
|
||||||
|
"NextTrack" => VirtualKeyCode::NextTrack,
|
||||||
|
"NoConvert" => VirtualKeyCode::NoConvert,
|
||||||
|
"NumpadComma" => VirtualKeyCode::NumpadComma,
|
||||||
|
"NumpadEnter" => VirtualKeyCode::NumpadEnter,
|
||||||
|
"NumpadEquals" => VirtualKeyCode::NumpadEquals,
|
||||||
|
"OEM102" => VirtualKeyCode::OEM102,
|
||||||
|
"Period" => VirtualKeyCode::Period,
|
||||||
|
"PlayPause" => VirtualKeyCode::PlayPause,
|
||||||
|
"Power" => VirtualKeyCode::Power,
|
||||||
|
"PrevTrack" => VirtualKeyCode::PrevTrack,
|
||||||
|
"AltRight" => VirtualKeyCode::RAlt,
|
||||||
|
"BracketRight" => VirtualKeyCode::RBracket,
|
||||||
|
"ControlRight" => VirtualKeyCode::RControl,
|
||||||
|
"ShiftRight" => VirtualKeyCode::RShift,
|
||||||
|
"MetaRight" => VirtualKeyCode::RWin,
|
||||||
|
"Slash" => VirtualKeyCode::Slash,
|
||||||
|
"Sleep" => VirtualKeyCode::Sleep,
|
||||||
|
"Stop" => VirtualKeyCode::Stop,
|
||||||
|
"NumpadSubtract" => VirtualKeyCode::Subtract,
|
||||||
|
"Sysrq" => VirtualKeyCode::Sysrq,
|
||||||
|
"Tab" => VirtualKeyCode::Tab,
|
||||||
|
"Underline" => VirtualKeyCode::Underline,
|
||||||
|
"Unlabeled" => VirtualKeyCode::Unlabeled,
|
||||||
|
"AudioVolumeDown" => VirtualKeyCode::VolumeDown,
|
||||||
|
"AudioVolumeUp" => VirtualKeyCode::VolumeUp,
|
||||||
|
"Wake" => VirtualKeyCode::Wake,
|
||||||
|
"WebBack" => VirtualKeyCode::WebBack,
|
||||||
|
"WebFavorites" => VirtualKeyCode::WebFavorites,
|
||||||
|
"WebForward" => VirtualKeyCode::WebForward,
|
||||||
|
"WebHome" => VirtualKeyCode::WebHome,
|
||||||
|
"WebRefresh" => VirtualKeyCode::WebRefresh,
|
||||||
|
"WebSearch" => VirtualKeyCode::WebSearch,
|
||||||
|
"WebStop" => VirtualKeyCode::WebStop,
|
||||||
|
"Yen" => VirtualKeyCode::Yen,
|
||||||
|
_ => return None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn keyboard_modifiers(event: &KeyboardEvent) -> ModifiersState {
|
||||||
|
ModifiersState {
|
||||||
|
shift: event.shift_key(),
|
||||||
|
ctrl: event.ctrl_key(),
|
||||||
|
alt: event.alt_key(),
|
||||||
|
logo: event.meta_key(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn codepoint(event: &KeyboardEvent) -> char {
|
||||||
|
// `event.key()` always returns a non-empty `String`. Therefore, this should
|
||||||
|
// never panic.
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key
|
||||||
|
event.key().chars().next().unwrap()
|
||||||
|
}
|
20
src/platform_impl/web/web_sys/mod.rs
Normal file
20
src/platform_impl/web/web_sys/mod.rs
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
mod canvas;
|
||||||
|
mod event;
|
||||||
|
mod timeout;
|
||||||
|
|
||||||
|
pub use self::canvas::Canvas;
|
||||||
|
pub use self::timeout::Timeout;
|
||||||
|
|
||||||
|
use crate::platform::web::WindowExtWebSys;
|
||||||
|
use crate::window::Window;
|
||||||
|
use web_sys::HtmlCanvasElement;
|
||||||
|
|
||||||
|
pub fn throw(msg: &str) {
|
||||||
|
wasm_bindgen::throw_str(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WindowExtWebSys for Window {
|
||||||
|
fn canvas(&self) -> HtmlCanvasElement {
|
||||||
|
self.window.canvas().raw().clone()
|
||||||
|
}
|
||||||
|
}
|
40
src/platform_impl/web/web_sys/timeout.rs
Normal file
40
src/platform_impl/web/web_sys/timeout.rs
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
use std::time::Duration;
|
||||||
|
use wasm_bindgen::closure::Closure;
|
||||||
|
use wasm_bindgen::JsCast;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Timeout {
|
||||||
|
handle: i32,
|
||||||
|
_closure: Closure<dyn FnMut()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Timeout {
|
||||||
|
pub fn new<F>(f: F, duration: Duration) -> Timeout
|
||||||
|
where
|
||||||
|
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 handle = window
|
||||||
|
.set_timeout_with_callback_and_timeout_and_arguments_0(
|
||||||
|
&closure.as_ref().unchecked_ref(),
|
||||||
|
duration.as_millis() as i32,
|
||||||
|
)
|
||||||
|
.expect("Failed to set timeout");
|
||||||
|
|
||||||
|
Timeout {
|
||||||
|
handle,
|
||||||
|
_closure: closure,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Timeout {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let window = web_sys::window().expect("Failed to obtain window");
|
||||||
|
|
||||||
|
window.clear_timeout_with_handle(self.handle);
|
||||||
|
}
|
||||||
|
}
|
268
src/platform_impl/web/window.rs
Normal file
268
src/platform_impl/web/window.rs
Normal file
|
@ -0,0 +1,268 @@
|
||||||
|
use crate::dpi::{LogicalPosition, LogicalSize};
|
||||||
|
use crate::error::{ExternalError, NotSupportedError, OsError as RootOE};
|
||||||
|
use crate::event::{Event, WindowEvent};
|
||||||
|
use crate::icon::Icon;
|
||||||
|
use crate::monitor::MonitorHandle as RootMH;
|
||||||
|
use crate::window::{CursorIcon, WindowAttributes, WindowId as RootWI};
|
||||||
|
|
||||||
|
use super::{backend, monitor, EventLoopWindowTarget};
|
||||||
|
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::collections::vec_deque::IntoIter as VecDequeIter;
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
|
pub struct Window {
|
||||||
|
canvas: backend::Canvas,
|
||||||
|
previous_pointer: RefCell<&'static str>,
|
||||||
|
position: RefCell<LogicalPosition>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Window {
|
||||||
|
pub fn new<T>(
|
||||||
|
target: &EventLoopWindowTarget<T>,
|
||||||
|
attr: WindowAttributes,
|
||||||
|
_: PlatformSpecificBuilderAttributes,
|
||||||
|
) -> Result<Self, RootOE> {
|
||||||
|
let runner = target.runner.clone();
|
||||||
|
|
||||||
|
let mut canvas = backend::Canvas::create(move || {
|
||||||
|
runner.send_event(Event::WindowEvent {
|
||||||
|
window_id: RootWI(Id),
|
||||||
|
event: WindowEvent::RedrawRequested,
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
|
||||||
|
target.register(&mut canvas);
|
||||||
|
|
||||||
|
let window = Window {
|
||||||
|
canvas,
|
||||||
|
previous_pointer: RefCell::new("auto"),
|
||||||
|
position: RefCell::new(LogicalPosition { x: 0.0, y: 0.0 }),
|
||||||
|
};
|
||||||
|
|
||||||
|
window.set_inner_size(attr.inner_size.unwrap_or(LogicalSize {
|
||||||
|
width: 1024.0,
|
||||||
|
height: 768.0,
|
||||||
|
}));
|
||||||
|
window.set_title(&attr.title);
|
||||||
|
window.set_maximized(attr.maximized);
|
||||||
|
window.set_visible(attr.visible);
|
||||||
|
window.set_window_icon(attr.window_icon);
|
||||||
|
|
||||||
|
Ok(window)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn canvas(&self) -> &backend::Canvas {
|
||||||
|
&self.canvas
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_title(&self, title: &str) {
|
||||||
|
self.canvas.set_attribute("alt", title);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_visible(&self, _visible: bool) {
|
||||||
|
// Intentionally a no-op
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn request_redraw(&self) {
|
||||||
|
self.canvas.request_redraw();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn outer_position(&self) -> Result<LogicalPosition, NotSupportedError> {
|
||||||
|
let (x, y) = self.canvas.position();
|
||||||
|
|
||||||
|
Ok(LogicalPosition { x, y })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn inner_position(&self) -> Result<LogicalPosition, NotSupportedError> {
|
||||||
|
Ok(*self.position.borrow())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_outer_position(&self, position: LogicalPosition) {
|
||||||
|
*self.position.borrow_mut() = position;
|
||||||
|
|
||||||
|
self.canvas.set_attribute("position", "fixed");
|
||||||
|
self.canvas.set_attribute("left", &position.x.to_string());
|
||||||
|
self.canvas.set_attribute("top", &position.y.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn inner_size(&self) -> LogicalSize {
|
||||||
|
LogicalSize {
|
||||||
|
width: self.canvas.width() as f64,
|
||||||
|
height: self.canvas.height() as f64,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn outer_size(&self) -> LogicalSize {
|
||||||
|
LogicalSize {
|
||||||
|
width: self.canvas.width() as f64,
|
||||||
|
height: self.canvas.height() as f64,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn set_inner_size(&self, size: LogicalSize) {
|
||||||
|
self.canvas.set_size(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn set_min_inner_size(&self, _dimensions: Option<LogicalSize>) {
|
||||||
|
// Intentionally a no-op: users can't resize canvas elements
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn set_max_inner_size(&self, _dimensions: Option<LogicalSize>) {
|
||||||
|
// Intentionally a no-op: users can't resize canvas elements
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn set_resizable(&self, _resizable: bool) {
|
||||||
|
// Intentionally a no-op: users can't resize canvas elements
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn hidpi_factor(&self) -> f64 {
|
||||||
|
1.0
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn set_cursor_icon(&self, cursor: CursorIcon) {
|
||||||
|
let text = match cursor {
|
||||||
|
CursorIcon::Default => "auto",
|
||||||
|
CursorIcon::Crosshair => "crosshair",
|
||||||
|
CursorIcon::Hand => "pointer",
|
||||||
|
CursorIcon::Arrow => "default",
|
||||||
|
CursorIcon::Move => "move",
|
||||||
|
CursorIcon::Text => "text",
|
||||||
|
CursorIcon::Wait => "wait",
|
||||||
|
CursorIcon::Help => "help",
|
||||||
|
CursorIcon::Progress => "progress",
|
||||||
|
|
||||||
|
CursorIcon::NotAllowed => "not-allowed",
|
||||||
|
CursorIcon::ContextMenu => "context-menu",
|
||||||
|
CursorIcon::Cell => "cell",
|
||||||
|
CursorIcon::VerticalText => "vertical-text",
|
||||||
|
CursorIcon::Alias => "alias",
|
||||||
|
CursorIcon::Copy => "copy",
|
||||||
|
CursorIcon::NoDrop => "no-drop",
|
||||||
|
CursorIcon::Grab => "grab",
|
||||||
|
CursorIcon::Grabbing => "grabbing",
|
||||||
|
CursorIcon::AllScroll => "all-scroll",
|
||||||
|
CursorIcon::ZoomIn => "zoom-in",
|
||||||
|
CursorIcon::ZoomOut => "zoom-out",
|
||||||
|
|
||||||
|
CursorIcon::EResize => "e-resize",
|
||||||
|
CursorIcon::NResize => "n-resize",
|
||||||
|
CursorIcon::NeResize => "ne-resize",
|
||||||
|
CursorIcon::NwResize => "nw-resize",
|
||||||
|
CursorIcon::SResize => "s-resize",
|
||||||
|
CursorIcon::SeResize => "se-resize",
|
||||||
|
CursorIcon::SwResize => "sw-resize",
|
||||||
|
CursorIcon::WResize => "w-resize",
|
||||||
|
CursorIcon::EwResize => "ew-resize",
|
||||||
|
CursorIcon::NsResize => "ns-resize",
|
||||||
|
CursorIcon::NeswResize => "nesw-resize",
|
||||||
|
CursorIcon::NwseResize => "nwse-resize",
|
||||||
|
CursorIcon::ColResize => "col-resize",
|
||||||
|
CursorIcon::RowResize => "row-resize",
|
||||||
|
};
|
||||||
|
*self.previous_pointer.borrow_mut() = text;
|
||||||
|
self.canvas
|
||||||
|
.set_attribute("style", &format!("cursor: {}", text));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn set_cursor_position(&self, _position: LogicalPosition) -> Result<(), ExternalError> {
|
||||||
|
// TODO: pointer capture
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn set_cursor_grab(&self, _grab: bool) -> Result<(), ExternalError> {
|
||||||
|
// TODO: pointer capture
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn set_cursor_visible(&self, visible: bool) {
|
||||||
|
if !visible {
|
||||||
|
self.canvas.set_attribute("cursor", "none");
|
||||||
|
} else {
|
||||||
|
self.canvas
|
||||||
|
.set_attribute("cursor", *self.previous_pointer.borrow());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn set_maximized(&self, _maximized: bool) {
|
||||||
|
// TODO: should there be a maximization / fullscreen API?
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn fullscreen(&self) -> Option<RootMH> {
|
||||||
|
// TODO: should there be a maximization / fullscreen API?
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn set_fullscreen(&self, _monitor: Option<RootMH>) {
|
||||||
|
// TODO: should there be a maximization / fullscreen API?
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn set_decorations(&self, _decorations: bool) {
|
||||||
|
// Intentionally a no-op, no canvas decorations
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn set_always_on_top(&self, _always_on_top: bool) {
|
||||||
|
// Intentionally a no-op, no window ordering
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn set_window_icon(&self, _window_icon: Option<Icon>) {
|
||||||
|
// Currently an intentional no-op
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn set_ime_position(&self, _position: LogicalPosition) {
|
||||||
|
// TODO: what is this?
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn current_monitor(&self) -> RootMH {
|
||||||
|
RootMH {
|
||||||
|
inner: monitor::Handle,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn available_monitors(&self) -> VecDequeIter<monitor::Handle> {
|
||||||
|
VecDeque::new().into_iter()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn primary_monitor(&self) -> monitor::Handle {
|
||||||
|
monitor::Handle
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn id(&self) -> Id {
|
||||||
|
// TODO ?
|
||||||
|
unsafe { Id::dummy() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub struct Id;
|
||||||
|
|
||||||
|
impl Id {
|
||||||
|
pub unsafe fn dummy() -> Id {
|
||||||
|
Id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub struct PlatformSpecificBuilderAttributes;
|
20
test.html
Normal file
20
test.html
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<script type="module">
|
||||||
|
import { default as init } from './window.js';
|
||||||
|
|
||||||
|
async function run() {
|
||||||
|
console.log("Loading");
|
||||||
|
await init('./window_bg.wasm');
|
||||||
|
console.log("Loaded");
|
||||||
|
}
|
||||||
|
|
||||||
|
run();
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<canvas id="test_canvas"></canvas>
|
||||||
|
</body>
|
||||||
|
</html>
|
2
test.ps1
Normal file
2
test.ps1
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
cargo build --target wasm32-unknown-unknown --features web_sys --example window
|
||||||
|
wasm-bindgen .\target\wasm32-unknown-unknown\debug\examples\window.wasm --out-dir . --target web
|
Loading…
Add table
Reference in a new issue