Start implementing web-sys backend

This commit is contained in:
Ben Merritt 2019-06-03 22:51:01 -07:00
parent 182beb4f8b
commit e4d8e22846
No known key found for this signature in database
GPG key ID: F8AD20ED4E6239B7
11 changed files with 479 additions and 15 deletions

View file

@ -20,6 +20,9 @@ libc = "0.2"
log = "0.4" log = "0.4"
serde = { version = "1", optional = true, features = ["serde_derive"] } serde = { version = "1", optional = true, features = ["serde_derive"] }
[features]
web-sys-support = ["web-sys", "wasm-bindgen"]
[dev-dependencies] [dev-dependencies]
image = "0.21" image = "0.21"
env_logger = "0.5" env_logger = "0.5"
@ -74,6 +77,30 @@ 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]
version = "0.3.22"
optional = true
features = [
'Document',
'Event',
'EventTarget',
'FocusEvent',
'HtmlCanvasElement',
'KeyboardEvent',
'MouseEvent',
'PointerEvent',
'Window',
'WheelEvent'
]
[target.'cfg(target_arch = "wasm32")'.dependencies.wasm-bindgen]
version = "0.2.45"
optional = true
[target.'cfg(target_arch = "wasm32")'.dependencies.stdweb]
version = "0.4.17"
optional = true
[target.'cfg(target_arch = "wasm32")'.dependencies] [target.'cfg(target_arch = "wasm32")'.dependencies]
stdweb = { path = "../stdweb", optional = true } stdweb = { path = "../stdweb", optional = true }
instant = { version = "0.1", features = ["stdweb"] } instant = { version = "0.1", features = ["stdweb"] }

View file

@ -1,4 +1,8 @@
extern crate winit; extern crate winit;
#[cfg(feature = "stdweb")]
#[macro_use]
extern crate stdweb;
#[cfg(feature = "wasm-bindgen")]
#[macro_use] #[macro_use]
extern crate stdweb; extern crate stdweb;
@ -13,10 +17,10 @@ fn main() {
.with_title("A fantastic window!") .with_title("A fantastic window!")
.build(&event_loop) .build(&event_loop)
.unwrap(); .unwrap();
console!(log, "Built window!"); //console!(log, "Built window!");
event_loop.run(|event, _, control_flow| { event_loop.run(|event, _, control_flow| {
console!(log, format!("{:?}", event)); //console!(log, format!("{:?}", event));
match event { match event {
Event::WindowEvent { Event::WindowEvent {

View file

@ -115,6 +115,10 @@ extern crate smithay_client_toolkit as sctk;
extern crate stdweb; extern crate stdweb;
#[cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))] #[cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))]
extern crate calloop; extern crate calloop;
#[cfg(feature = "web-sys")]
extern crate wasm_bindgen;
#[cfg(feature = "web-sys")]
extern crate web_sys;
pub mod dpi; pub mod dpi;
#[macro_use] #[macro_use]

View file

@ -21,5 +21,6 @@ pub mod unix;
pub mod windows; pub mod windows;
pub mod stdweb; pub mod stdweb;
pub mod web_sys;
pub mod desktop; pub mod desktop;

7
src/platform/web_sys.rs Normal file
View file

@ -0,0 +1,7 @@
#![cfg(feature = "web-sys")]
use web_sys::HtmlCanvasElement;
pub trait WindowExtWebSys {
fn canvas(&self) -> HtmlCanvasElement;
}

View file

@ -1,30 +1,48 @@
pub use self::platform::*; pub use self::platform::*;
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
#[path="windows/mod.rs"] #[path = "windows/mod.rs"]
mod platform; mod platform;
#[cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))] #[cfg(any(
#[path="linux/mod.rs"] target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
))]
#[path = "linux/mod.rs"]
mod platform; mod platform;
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
#[path="macos/mod.rs"] #[path = "macos/mod.rs"]
mod platform; mod platform;
#[cfg(target_os = "android")] #[cfg(target_os = "android")]
#[path="android/mod.rs"] #[path = "android/mod.rs"]
mod platform; mod platform;
#[cfg(target_os = "ios")] #[cfg(target_os = "ios")]
#[path="ios/mod.rs"] #[path = "ios/mod.rs"]
mod platform; mod platform;
#[cfg(target_os = "emscripten")] #[cfg(target_os = "emscripten")]
#[path="emscripten/mod.rs"] #[path = "emscripten/mod.rs"]
mod platform; mod platform;
#[cfg(feature = "stdweb")] #[cfg(feature = "stdweb")]
#[path="stdweb/mod.rs"] #[path = "stdweb/mod.rs"]
mod platform;
#[cfg(feature = "web-sys")]
#[path = "web_sys/mod.rs"]
mod platform; mod platform;
#[cfg(all(not(target_os = "ios"), not(target_os = "windows"), not(target_os = "linux"), #[cfg(all(
not(target_os = "macos"), not(target_os = "android"), not(target_os = "dragonfly"), not(target_os = "ios"),
not(target_os = "freebsd"), not(target_os = "netbsd"), not(target_os = "openbsd"), not(target_os = "windows"),
not(target_os = "linux"),
not(target_os = "macos"),
not(target_os = "android"),
not(target_os = "dragonfly"),
not(target_os = "freebsd"),
not(target_os = "netbsd"),
not(target_os = "openbsd"),
not(target_os = "emscripten"), not(target_os = "emscripten"),
not(feature = "stdweb")))] not(feature = "stdweb"),
not(feature = "web-sys")
))]
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");

View file

@ -0,0 +1,374 @@
use super::*;
use dpi::LogicalPosition;
use event::{
DeviceId as RootDI, ElementState, Event, KeyboardInput, MouseScrollDelta, StartCause,
TouchPhase, WindowEvent,
};
use event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW};
use std::cell::RefCell;
use std::collections::vec_deque::IntoIter as VecDequeIter;
use std::collections::VecDeque;
use std::marker::PhantomData;
use std::rc::Rc;
use wasm_bindgen::{prelude::*, JsCast};
use web_sys::{Document, EventTarget, FocusEvent, HtmlCanvasElement, KeyboardEvent, PointerEvent, WheelEvent};
use window::WindowId as RootWI;
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId(i32);
impl DeviceId {
pub unsafe fn dummy() -> Self {
DeviceId(0)
}
}
pub struct EventLoop<T: 'static> {
elw: RootELW<T>,
}
pub struct EventLoopWindowTarget<T: 'static> {
pub(crate) runner: EventLoopRunnerShared<T>,
}
impl<T> EventLoopWindowTarget<T> {
fn new() -> Self {
EventLoopWindowTarget {
runner: Rc::new(ELRShared {
runner: RefCell::new(None),
events: RefCell::new(VecDeque::new()),
}),
}
}
}
#[derive(Clone)]
pub struct EventLoopProxy<T> {
runner: EventLoopRunnerShared<T>,
}
impl<T> EventLoopProxy<T> {
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> {
self.runner.send_event(Event::UserEvent(event));
Ok(())
}
}
pub type EventLoopRunnerShared<T> = Rc<ELRShared<T>>;
pub struct ELRShared<T> {
runner: RefCell<Option<EventLoopRunner<T>>>,
events: RefCell<VecDeque<Event<T>>>, // TODO: this may not be necessary?
}
struct EventLoopRunner<T> {
control: ControlFlow,
is_busy: bool,
event_handler: Box<dyn FnMut(Event<T>, &mut ControlFlow)>,
}
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(module = "util.js", js_name = "throwToEscapeEventLoop")]
fn throw_to_escape_event_loop();
}
impl<T> EventLoop<T> {
pub fn new() -> Self {
EventLoop {
elw: RootELW {
p: EventLoopWindowTarget::new(),
_marker: PhantomData,
},
}
}
pub fn available_monitors(&self) -> VecDequeIter<MonitorHandle> {
VecDeque::new().into_iter()
}
pub fn primary_monitor(&self) -> MonitorHandle {
MonitorHandle
}
pub fn run<F>(self, mut event_handler: F) -> !
where
F: 'static + FnMut(Event<T>, &RootELW<T>, &mut ControlFlow),
{
let runner = self.elw.p.runner;
let relw = RootELW {
p: EventLoopWindowTarget::new(),
_marker: PhantomData,
};
runner.set_listener(Box::new(move |evt, ctrl| event_handler(evt, &relw, ctrl)));
let document = &document();
add_event(&runner, document, "blur", |elrs, _: FocusEvent| {
elrs.send_event(Event::WindowEvent {
window_id: RootWI(WindowId),
event: WindowEvent::Focused(false),
});
});
add_event(&runner, document, "focus", |elrs, _: FocusEvent| {
elrs.send_event(Event::WindowEvent {
window_id: RootWI(WindowId),
event: WindowEvent::Focused(true),
});
});
add_event(&runner, document, "keydown", |elrs, event: KeyboardEvent| {
let key = event.key();
let mut characters = key.chars();
let first = characters.next();
let second = characters.next();
if let (Some(key), None) = (first, second) {
elrs.send_event(Event::WindowEvent {
window_id: RootWI(WindowId),
event: WindowEvent::ReceivedCharacter(key),
});
}
elrs.send_event(Event::WindowEvent {
window_id: RootWI(WindowId),
event: WindowEvent::KeyboardInput {
device_id: RootDI(unsafe { DeviceId::dummy() }),
input: KeyboardInput {
scancode: scancode(&event),
state: ElementState::Pressed,
virtual_keycode: button_mapping(&event),
modifiers: keyboard_modifiers_state(&event),
},
},
});
});
add_event(&runner, document, "keyup", |elrs, event: KeyboardEvent| {
elrs.send_event(Event::WindowEvent {
window_id: RootWI(WindowId),
event: WindowEvent::KeyboardInput {
device_id: RootDI(unsafe { DeviceId::dummy() }),
input: KeyboardInput {
scancode: scancode(&event),
state: ElementState::Released,
virtual_keycode: button_mapping(&event),
modifiers: keyboard_modifiers_state(&event),
},
},
});
});
runner.send_event(Event::NewEvents(StartCause::Init));
// 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 '!'
throw_to_escape_event_loop();
unreachable!();
}
pub fn create_proxy(&self) -> EventLoopProxy<T> {
EventLoopProxy {
runner: self.elw.p.runner.clone(),
}
}
pub fn window_target(&self) -> &RootELW<T> {
&self.elw
}
}
pub fn register<T: 'static>(elrs: &EventLoopRunnerShared<T>, canvas: &HtmlCanvasElement) {
add_event(elrs, canvas, "pointerout", |elrs, event: PointerEvent| {
elrs.send_event(Event::WindowEvent {
window_id: RootWI(WindowId),
event: WindowEvent::CursorLeft {
device_id: RootDI(DeviceId(event.pointer_id())),
},
});
});
add_event(elrs, canvas, "pointerover", |elrs, event: PointerEvent| {
elrs.send_event(Event::WindowEvent {
window_id: RootWI(WindowId),
event: WindowEvent::CursorEntered {
device_id: RootDI(DeviceId(event.pointer_id())),
},
});
});
add_event(elrs, canvas, "pointermove", |elrs, event: PointerEvent| {
elrs.send_event(Event::WindowEvent {
window_id: RootWI(WindowId),
event: WindowEvent::CursorMoved {
device_id: RootDI(DeviceId(event.pointer_id())),
position: LogicalPosition {
x: event.offset_x().into(),
y: event.offset_y().into(),
},
modifiers: mouse_modifiers_state(&event),
},
});
});
add_event(elrs, canvas, "pointerup", |elrs, event: PointerEvent| {
elrs.send_event(Event::WindowEvent {
window_id: RootWI(WindowId),
event: WindowEvent::MouseInput {
device_id: RootDI(DeviceId(event.pointer_id())),
state: ElementState::Pressed,
button: mouse_button(&event),
modifiers: mouse_modifiers_state(&event),
},
});
});
add_event(elrs, canvas, "pointerdown", |elrs, event: PointerEvent| {
elrs.send_event(Event::WindowEvent {
window_id: RootWI(WindowId),
event: WindowEvent::MouseInput {
device_id: RootDI(DeviceId(event.pointer_id())),
state: ElementState::Released,
button: mouse_button(&event),
modifiers: mouse_modifiers_state(&event),
},
});
});
add_event(elrs, canvas, "wheel", |elrs, event: WheelEvent| {
let x = event.delta_x();
let y = event.delta_y();
let delta = match event.delta_mode() {
WheelEvent::DOM_DELTA_LINE => MouseScrollDelta::LineDelta(x as f32, y as f32),
WheelEvent::DOM_DELTA_PIXEL => MouseScrollDelta::PixelDelta(LogicalPosition { x, y }),
WheelEvent::DOM_DELTA_PAGE => return,
};
elrs.send_event(Event::WindowEvent {
window_id: RootWI(WindowId),
event: WindowEvent::MouseWheel {
device_id: RootDI(DeviceId(0)),
delta,
phase: TouchPhase::Moved,
modifiers: mouse_modifiers_state(&event),
},
});
});
}
fn add_event<T: 'static, E, F>(
elrs: &EventLoopRunnerShared<T>,
target: &EventTarget,
event: &str,
mut handler: F,
) where
E: AsRef<web_sys::Event> + wasm_bindgen::convert::FromWasmAbi + 'static,
F: FnMut(&EventLoopRunnerShared<T>, E) + 'static,
{
let elrs = elrs.clone();
target.add_event_listener_with_callback(event, Closure::wrap(Box::new(move |event: E| {
// Don't capture the event if the events loop has been destroyed
match &*elrs.runner.borrow() {
Some(ref runner) if runner.control == ControlFlow::Exit => return,
_ => (),
}
let event_ref = event.as_ref();
event_ref.prevent_default();
event_ref.stop_propagation();
event_ref.cancel_bubble();
handler(&elrs, event);
}) as Box<FnMut(E)>).as_ref().unchecked_ref());
}
impl<T> ELRShared<T> {
// 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
fn set_listener(&self, event_handler: Box<dyn FnMut(Event<T>, &mut ControlFlow)>) {
*self.runner.borrow_mut() = Some(EventLoopRunner {
control: ControlFlow::Poll,
is_busy: false,
event_handler,
});
}
// 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.closed() {
return;
}
let start_cause = StartCause::Poll; // TODO: determine start cause
// Determine if event handling is in process, and then release the borrow on the runner
let is_busy = if let Some(ref runner) = *self.runner.borrow() {
runner.is_busy
} else {
true // If there is no event runner yet, then there's no point in processing events
};
if is_busy {
self.events.borrow_mut().push_back(event);
} else {
// 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));
self.handle_event(event);
self.handle_event(Event::EventsCleared);
// If the event loop is closed, it has been closed this iteration and now the closing
// event should be emitted
if self.closed() {
self.handle_event(Event::LoopDestroyed);
}
}
}
// Check if the event loop is currntly closed
fn closed(&self) -> bool {
match *self.runner.borrow() {
Some(ref runner) => runner.control == ControlFlow::Exit,
None => false, // If the event loop is None, it has not been intialised yet, so it cannot be closed
}
}
// 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>) {
let closed = self.closed();
match *self.runner.borrow_mut() {
Some(ref mut runner) => {
// An event is being processed, so the runner should be marked busy
runner.is_busy = true;
// TODO: bracket this in control flow events?
(runner.event_handler)(event, &mut runner.control);
// Maintain closed state, even if the callback changes it
if closed {
runner.control = 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.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 !closed && self.runner.borrow().is_some() {
// Take an event out of the queue and handle it
if let Some(event) = self.events.borrow_mut().pop_front() {
self.handle_event(event);
}
}
}
}
fn document() -> Document {
web_sys::window().unwrap().document().unwrap()
}

View file

@ -0,0 +1 @@

View file

@ -0,0 +1,24 @@
use std::fmt;
mod event_loop;
mod events;
mod window;
pub use self::event_loop::{
register, DeviceId, EventLoop, EventLoopProxy, EventLoopRunnerShared, EventLoopWindowTarget,
};
pub use self::events::{
button_mapping, keyboard_modifiers_state, mouse_button, mouse_modifiers_state, scancode,
};
pub use self::window::{MonitorHandle, PlatformSpecificWindowBuilderAttributes, Window, WindowId};
// TODO: unify with stdweb impl.
#[derive(Debug)]
pub struct OsError(String);
impl fmt::Display for OsError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}

View file

@ -0,0 +1,3 @@
function throwToEscapeEventLoop() {
throw "Using exceptions for control flow, don't mind me. This isn't actually an error!";
}

View file

@ -0,0 +1 @@