diff --git a/Cargo.toml b/Cargo.toml index 456a5fb7e1..cc1ece408d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ categories = ["gui"] features = ["serde"] [dependencies] +instant = "0.1" lazy_static = "1" libc = "0.2" log = "0.4" @@ -79,3 +80,10 @@ 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] version = "0.8" + +[target.'cfg(target_arch = "wasm32")'.dependencies] +stdweb = { path = "../stdweb", optional = true } +instant = { version = "0.1", features = ["stdweb"] } + +[patch.crates-io] +stdweb = { path = "../stdweb" } diff --git a/examples/window.rs b/examples/window.rs index 12e0593e93..93e6d2423c 100644 --- a/examples/window.rs +++ b/examples/window.rs @@ -13,8 +13,6 @@ fn main() { .unwrap(); event_loop.run(move |event, _, control_flow| { - println!("{:?}", event); - match event { Event::WindowEvent { event: WindowEvent::CloseRequested, diff --git a/src/event.rs b/src/event.rs index 8e0ac0430f..236e7f78c0 100644 --- a/src/event.rs +++ b/src/event.rs @@ -4,7 +4,9 @@ //! 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 -use std::{path::PathBuf, time::Instant}; + +use instant::Instant; +use std::path::PathBuf; use crate::{ dpi::{LogicalPosition, LogicalSize}, @@ -61,7 +63,7 @@ impl Event { } /// Describes the reason the event loop is resuming. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum StartCause { /// 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 diff --git a/src/event_loop.rs b/src/event_loop.rs index dc0e2e3b9f..7141dc6127 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -9,7 +9,9 @@ //! [create_proxy]: ./struct.EventLoop.html#method.create_proxy //! [event_loop_proxy]: ./struct.EventLoopProxy.html //! [send_event]: ./struct.EventLoopProxy.html#method.send_event -use std::{error, fmt, ops::Deref, time::Instant}; + +use instant::Instant; +use std::{error, fmt, ops::Deref}; use crate::{ event::Event, @@ -69,7 +71,7 @@ impl fmt::Debug for EventLoopWindowTarget { /// the control flow to `Poll`. /// /// [events_cleared]: ../event/enum.Event.html#variant.EventsCleared -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum ControlFlow { /// When the current loop iteration finishes, immediately begin a new iteration regardless of /// whether or not new events are available to process. diff --git a/src/lib.rs b/src/lib.rs index 1290bcd1bf..5bc5629dfd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -110,26 +110,6 @@ //! [`LoopDestroyed`]: ./event/enum.Event.html#variant.LoopDestroyed //! [`platform`]: ./platform/index.html -#![deny(rust_2018_idioms)] - -#[allow(unused_imports)] -#[macro_use] -extern crate lazy_static; -#[macro_use] -extern crate log; -#[cfg(feature = "serde")] -#[macro_use] -extern crate serde; -#[macro_use] -#[cfg(target_os = "windows")] -extern crate derivative; -#[macro_use] -#[cfg(target_os = "windows")] -extern crate bitflags; -#[cfg(any(target_os = "macos", target_os = "ios"))] -#[macro_use] -extern crate objc; - pub mod dpi; #[macro_use] pub mod error; diff --git a/src/platform/mod.rs b/src/platform/mod.rs index ba494ac6d3..da780f4bd0 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -20,4 +20,6 @@ pub mod macos; pub mod unix; pub mod windows; +pub mod stdweb; + pub mod desktop; diff --git a/src/platform/stdweb.rs b/src/platform/stdweb.rs new file mode 100644 index 0000000000..4ffa7dd2bb --- /dev/null +++ b/src/platform/stdweb.rs @@ -0,0 +1,8 @@ +#![cfg(feature = "stdweb")] + +use stdweb::web::html_element::CanvasElement; + +pub trait WindowExtStdweb { + fn canvas(&self) -> CanvasElement; +} + diff --git a/src/platform_impl/mod.rs b/src/platform_impl/mod.rs index 3b8152087d..6302c5f760 100644 --- a/src/platform_impl/mod.rs +++ b/src/platform_impl/mod.rs @@ -24,6 +24,9 @@ mod platform; #[cfg(target_os = "emscripten")] #[path = "emscripten/mod.rs"] mod platform; +#[cfg(feature = "stdweb")] +#[path="stdweb/mod.rs"] +mod platform; #[cfg(all( not(target_os = "ios"), @@ -35,6 +38,7 @@ mod platform; not(target_os = "freebsd"), not(target_os = "netbsd"), not(target_os = "openbsd"), - not(target_os = "emscripten") + not(target_os = "emscripten"), + not(feature = "stdweb") ))] compile_error!("The platform you're compiling for is not supported by winit"); diff --git a/src/platform_impl/stdweb/event.rs b/src/platform_impl/stdweb/event.rs new file mode 100644 index 0000000000..259840bb89 --- /dev/null +++ b/src/platform_impl/stdweb/event.rs @@ -0,0 +1,203 @@ +use stdweb::{ + js, + JsSerialize, + web::event::{IKeyboardEvent, IMouseEvent}, + unstable::TryInto +}; +use crate::event::{MouseButton, ModifiersState, ScanCode, VirtualKeyCode}; + +pub fn button_mapping(event: &impl IKeyboardEvent) -> Option { + 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 mouse_modifiers_state(event: &impl IMouseEvent) -> ModifiersState { + ModifiersState { + shift: event.shift_key(), + ctrl: event.ctrl_key(), + alt: event.alt_key(), + logo: event.meta_key(), + } +} + +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 keyboard_modifiers_state(event: &impl IKeyboardEvent) -> ModifiersState { + ModifiersState { + shift: event.shift_key(), + ctrl: event.ctrl_key(), + alt: event.alt_key(), + logo: event.meta_key(), + } +} + +pub fn scancode(event: &T) -> ScanCode { + let which = js! ( return @{event}.which; ); + which.try_into().expect("The which value should be a number") +} diff --git a/src/platform_impl/stdweb/event_loop.rs b/src/platform_impl/stdweb/event_loop.rs new file mode 100644 index 0000000000..18dd4afa67 --- /dev/null +++ b/src/platform_impl/stdweb/event_loop.rs @@ -0,0 +1,494 @@ +use super::*; + +use crate::dpi::LogicalPosition; +use crate::event::{DeviceId as RootDI, ElementState, Event, KeyboardInput, MouseScrollDelta, StartCause, TouchPhase, WindowEvent}; +use crate::event_loop::{ControlFlow, EventLoopWindowTarget as RootELW, EventLoopClosed}; +use instant::{Duration, Instant}; +use crate::window::{WindowId as RootWI}; +use stdweb::{ + js, + traits::*, + web::{ + document, + event::*, + html_element::CanvasElement, + window, + TimeoutHandle, + }, +}; +use std::{ + cell::RefCell, + collections::{VecDeque, vec_deque::IntoIter as VecDequeIter}, + clone::Clone, + marker::PhantomData, + rc::Rc, +}; + +#[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 { + elw: RootELW, +} + +pub struct EventLoopWindowTarget { + pub(crate) runner: EventLoopRunnerShared, +} + +impl EventLoopWindowTarget { + fn new() -> Self { + EventLoopWindowTarget { + runner: EventLoopRunnerShared(Rc::new(ELRShared { + runner: RefCell::new(None), + events: RefCell::new(VecDeque::new()) + })) + } + } +} + +#[derive(Clone)] +pub struct EventLoopProxy { + runner: EventLoopRunnerShared +} + +impl EventLoopProxy { + pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> { + self.runner.send_event(Event::UserEvent(event)); + Ok(()) + } +} + +pub struct EventLoopRunnerShared(Rc>); + +impl Clone for EventLoopRunnerShared { + fn clone(&self) -> Self { + EventLoopRunnerShared(self.0.clone()) + } +} + +pub struct ELRShared { + runner: RefCell>>, + events: RefCell>>, +} + +struct EventLoopRunner { + control: ControlFlowStatus, + is_busy: bool, + event_handler: Box, &mut ControlFlow)>, +} + +enum ControlFlowStatus { + Init, + WaitUntil { + timeout: TimeoutHandle, + start: Instant, + end: Instant + }, + Wait { + start: Instant, + }, + Poll { + timeout: TimeoutHandle + }, + Exit +} + +impl ControlFlowStatus { + fn to_control_flow(&self) -> ControlFlow { + match self { + ControlFlowStatus::Init => ControlFlow::Poll, // During the Init loop, the user should get Poll, the default control value + ControlFlowStatus::WaitUntil { end, .. } => ControlFlow::WaitUntil(*end), + ControlFlowStatus::Wait { .. } => ControlFlow::Wait, + ControlFlowStatus::Poll { .. } => ControlFlow::Poll, + ControlFlowStatus::Exit => ControlFlow::Exit, + } + } + + fn is_exit(&self) -> bool { + match self { + ControlFlowStatus::Exit => true, + _ => false, + } + } +} + +impl EventLoop { + pub fn new() -> Self { + EventLoop { + elw: RootELW { + p: EventLoopWindowTarget::new(), + _marker: PhantomData + }, + } + } + + pub fn available_monitors(&self) -> VecDequeIter { + VecDeque::new().into_iter() + } + + pub fn primary_monitor(&self) -> MonitorHandle { + MonitorHandle + } + + pub fn run(self, mut event_handler: F) -> ! + where F: 'static + FnMut(Event, &RootELW, &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(); + let window = &window(); + + add_event(&runner, document, |elrs, _: BlurEvent| { + elrs.send_event(Event::WindowEvent { + window_id: RootWI(WindowId), + event: WindowEvent::Focused(false) + }); + }); + add_event(&runner, document, |elrs, _: FocusEvent| { + elrs.send_event(Event::WindowEvent { + window_id: RootWI(WindowId), + event: WindowEvent::Focused(true) + }); + + }); + add_event(&runner, document, |elrs, event: KeyDownEvent| { + 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, |elrs, event: KeyUpEvent| { + 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), + } + } + }); + }); + add_event(&runner, window, |elrs, _event: UnloadEvent| { + elrs.send_event(Event::LoopDestroyed); + // Mark the event loop as exited, so new events are chucked out + match *elrs.0.runner.borrow_mut() { + Some(ref mut runner) => runner.control = ControlFlowStatus::Exit, + None => () + } + }); + + stdweb::event_loop(); // TODO: this is only necessary for stdweb emscripten, should it be here? + + // 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 '!' + js! { + throw "Using exceptions for control flow, don't mind me. This isn't actually an error!"; + } + unreachable!(); + } + + pub fn create_proxy(&self) -> EventLoopProxy { + EventLoopProxy { + runner: self.elw.p.runner.clone() + } + } + + pub fn window_target(&self) -> &RootELW { + &self.elw + } +} + +pub fn register(elrs: &EventLoopRunnerShared, canvas: &CanvasElement) { + add_event(elrs, canvas, |elrs, event: PointerOutEvent| { + elrs.send_event(Event::WindowEvent { + window_id: RootWI(WindowId), + event: WindowEvent::CursorLeft { + device_id: RootDI(DeviceId(event.pointer_id())) + } + }); + }); + add_event(elrs, canvas, |elrs, event: PointerOverEvent| { + elrs.send_event(Event::WindowEvent { + window_id: RootWI(WindowId), + event: WindowEvent::CursorEntered { + device_id: RootDI(DeviceId(event.pointer_id())) + } + }); + }); + add_event(elrs, canvas, |elrs, event: PointerMoveEvent| { + 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(), + y: event.offset_y() + }, + modifiers: mouse_modifiers_state(&event) + } + }); + }); + add_event(elrs, canvas, |elrs, event: PointerUpEvent| { + 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, |elrs, event: PointerDownEvent| { + 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, |elrs, event: MouseWheelEvent| { + let x = event.delta_x(); + let y = event.delta_y(); + let delta = match event.delta_mode() { + MouseWheelDeltaMode::Line => MouseScrollDelta::LineDelta(x as f32, y as f32), + MouseWheelDeltaMode::Pixel => MouseScrollDelta::PixelDelta(LogicalPosition { x, y }), + MouseWheelDeltaMode::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(elrs: &EventLoopRunnerShared, target: &impl IEventTarget, mut handler: F) + where E: ConcreteEvent, F: FnMut(&EventLoopRunnerShared, E) + 'static { + let elrs = elrs.clone(); + + target.add_event_listener(move |event: E| { + // Don't capture the event if the events loop has been destroyed + match &*elrs.0.runner.borrow() { + Some(ref runner) if runner.control.is_exit() => return, + _ => () + } + + event.prevent_default(); + event.stop_propagation(); + event.cancel_bubble(); + + handler(&elrs, event); + }); +} + +impl EventLoopRunnerShared { + // 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, &mut ControlFlow)>) { + *self.0.runner.borrow_mut() = Some(EventLoopRunner { + control: ControlFlowStatus::Init, + is_busy: false, + 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) { + // If the event loop is closed, it should discard any new events + if self.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.control { + ControlFlowStatus::Init => StartCause::Init, + ControlFlowStatus::Poll { .. } => { + StartCause::Poll + } + ControlFlowStatus::Wait { start } => StartCause::WaitCancelled { + start, + requested_resume: None, + }, + ControlFlowStatus::WaitUntil { start, end, .. } => { + StartCause::WaitCancelled { + start, + requested_resume: Some(end) + } + }, + ControlFlowStatus::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); + // In the case the event is just a NewEvents (a Poll has completed or a WaitUntil expired) + // don't run the event handler twice. + 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.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, control: &mut ControlFlow) { + let closed = self.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 closed { + *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.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 !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: ControlFlow) { + let mut control_flow_status = match control_flow { + ControlFlow::Poll => { + let cloned = self.clone(); + ControlFlowStatus::Poll { + timeout: window().set_clearable_timeout(move || cloned.send_event(Event::NewEvents(StartCause::Poll)), 1) + } + } + ControlFlow::Wait => ControlFlowStatus::Wait { start: Instant::now() }, + ControlFlow::WaitUntil(end) => { + let cloned = self.clone(); + let start = Instant::now(); + let delay = if end <= start { + Duration::from_millis(0) + } else { + end - start + }; + ControlFlowStatus::WaitUntil { + start, + end, + timeout: window().set_clearable_timeout(move || cloned.send_event(Event::NewEvents(StartCause::Poll)), delay.as_millis() as u32) + } + } + ControlFlow::Exit => ControlFlowStatus::Exit, + }; + + match *self.0.runner.borrow_mut() { + Some(ref mut runner) => { + // Put the new control flow status in the runner, and take out the old one + // This way we can safely take ownership of the TimeoutHandle and clear it, + // so that we don't get 'ghost' invocations of Poll or WaitUntil from earlier + // set_timeout invocations + std::mem::swap(&mut runner.control, &mut control_flow_status); + match control_flow_status { + ControlFlowStatus::Poll { timeout } | ControlFlowStatus::WaitUntil { timeout, .. } => timeout.clear(), + _ => (), + } + } + None => () + } + } + + // Check if the event loop is currntly closed + fn closed(&self) -> bool { + match *self.0.runner.borrow() { + Some(ref runner) => runner.control.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) -> ControlFlow { + match *self.0.runner.borrow() { + Some(ref runner) => runner.control.to_control_flow(), + None => ControlFlow::Poll, + } + } +} + diff --git a/src/platform_impl/stdweb/mod.rs b/src/platform_impl/stdweb/mod.rs new file mode 100644 index 0000000000..d934ff1667 --- /dev/null +++ b/src/platform_impl/stdweb/mod.rs @@ -0,0 +1,19 @@ +use std::fmt; + +mod event_loop; +mod event; +mod window; + +pub use self::event_loop::{DeviceId, EventLoop, EventLoopRunnerShared, EventLoopWindowTarget, EventLoopProxy, register}; +pub use self::window::{MonitorHandle, Window, WindowId, PlatformSpecificWindowBuilderAttributes}; +pub use self::event::{button_mapping, mouse_modifiers_state, mouse_button, keyboard_modifiers_state, scancode}; + +#[derive(Debug)] +pub struct OsError(String); + +impl fmt::Display for OsError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.0) + } +} + diff --git a/src/platform_impl/stdweb/window.rs b/src/platform_impl/stdweb/window.rs new file mode 100644 index 0000000000..dc2b68ff23 --- /dev/null +++ b/src/platform_impl/stdweb/window.rs @@ -0,0 +1,330 @@ +use crate::dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize}; +use crate::error::{ExternalError, NotSupportedError, OsError as RootOE}; +use crate::event::{Event, WindowEvent}; +use crate::icon::Icon; +use crate::platform::stdweb::WindowExtStdweb; +use crate::monitor::{MonitorHandle as RootMH, VideoMode}; +use crate::window::{CursorIcon, Window as RootWindow, WindowAttributes, WindowId as RootWI}; +use super::{EventLoopWindowTarget, OsError, register}; +use std::collections::VecDeque; +use std::collections::vec_deque::IntoIter as VecDequeIter; +use std::cell::RefCell; +use stdweb::{ + traits::*, + unstable::TryInto +}; +use stdweb::web::{ + document, window, + html_element::CanvasElement, +}; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct MonitorHandle; + +impl MonitorHandle { + pub fn hidpi_factor(&self) -> f64 { + 1.0 + } + + pub fn position(&self) -> PhysicalPosition { + unimplemented!(); + } + + pub fn name(&self) -> Option { + unimplemented!(); + } + + pub fn size(&self) -> PhysicalSize { + unimplemented!(); + } + + pub fn video_modes(&self) -> impl Iterator { + // TODO: is this possible ? + std::iter::empty() + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct WindowId; + +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct PlatformSpecificWindowBuilderAttributes; + +impl WindowId { + pub unsafe fn dummy() -> WindowId { + WindowId + } +} + +pub struct Window { + pub(crate) canvas: CanvasElement, + pub(crate) redraw: Box, + previous_pointer: RefCell<&'static str>, + position: RefCell, +} + +impl Window { + pub fn new(target: &EventLoopWindowTarget, attr: WindowAttributes, + _: PlatformSpecificWindowBuilderAttributes) -> Result { + let element = document() + .create_element("canvas") + .map_err(|_| os_error!(OsError("Failed to create canvas element".to_owned())))?; + let canvas: CanvasElement = element.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); + + register(&target.runner, &canvas); + + let runner = target.runner.clone(); + let redraw = Box::new(move || { + let runner = runner.clone(); + window().request_animation_frame(move |_| runner.send_event(Event::WindowEvent { + window_id: RootWI(WindowId), + event: WindowEvent::RedrawRequested + })); + }); + + let window = Window { + canvas, + redraw, + previous_pointer: RefCell::new("auto"), + position: RefCell::new(LogicalPosition { + x: 0.0, + y: 0.0 + }) + }; + + if let Some(inner_size) = attr.inner_size { + window.set_inner_size(inner_size); + } else { + window.set_inner_size(LogicalSize { + width: 1024.0, + height: 768.0, + }) + } + window.set_min_inner_size(attr.min_inner_size); + window.set_max_inner_size(attr.max_inner_size); + window.set_resizable(attr.resizable); + window.set_title(&attr.title); + window.set_maximized(attr.maximized); + window.set_visible(attr.visible); + //window.set_transparent(attr.transparent); + window.set_decorations(attr.decorations); + window.set_always_on_top(attr.always_on_top); + window.set_window_icon(attr.window_icon); + + Ok(window) + } + + pub fn set_title(&self, title: &str) { + document().set_title(title); + } + + pub fn set_visible(&self, _visible: bool) { + // Intentionally a no-op + } + + pub fn request_redraw(&self) { + (self.redraw)(); + } + + pub fn outer_position(&self) -> Result { + let bounds = self.canvas.get_bounding_client_rect(); + Ok(LogicalPosition { + x: bounds.get_x(), + y: bounds.get_y(), + }) + } + + pub fn inner_position(&self) -> Result { + Ok(*self.position.borrow()) + } + + pub fn set_outer_position(&self, position: LogicalPosition) { + *self.position.borrow_mut() = position; + self.canvas.set_attribute("position", "fixed") + .expect("Setting the position for the canvas"); + self.canvas.set_attribute("left", &position.x.to_string()) + .expect("Setting the position for the canvas"); + self.canvas.set_attribute("top", &position.y.to_string()) + .expect("Setting the position for the canvas"); + } + + #[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_width(size.width as u32); + self.canvas.set_height(size.height as u32); + } + + #[inline] + pub fn set_min_inner_size(&self, _dimensions: Option) { + // Intentionally a no-op: users can't resize canvas elements + } + + #[inline] + pub fn set_max_inner_size(&self, _dimensions: Option) { + // 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("cursor", text) + .expect("Setting the cursor on the canvas"); + } + + #[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") + .expect("Setting the cursor on the canvas"); + } else { + self.canvas.set_attribute("cursor", *self.previous_pointer.borrow()) + .expect("Setting the cursor on the canvas"); + } + } + + #[inline] + pub fn set_maximized(&self, _maximized: bool) { + // TODO: should there be a maximization / fullscreen API? + } + + #[inline] + pub fn fullscreen(&self) -> Option { + // TODO: should there be a maximization / fullscreen API? + None + } + + #[inline] + pub fn set_fullscreen(&self, _monitor: Option) { + // 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) { + // 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: MonitorHandle + } + } + + #[inline] + pub fn available_monitors(&self) -> VecDequeIter { + VecDeque::new().into_iter() + } + + #[inline] + pub fn primary_monitor(&self) -> MonitorHandle { + MonitorHandle + } + + #[inline] + pub fn id(&self) -> WindowId { + // TODO ? + unsafe { WindowId::dummy() } + } +} + +impl WindowExtStdweb for RootWindow { + fn canvas(&self) -> CanvasElement { + self.window.canvas.clone() + } +}