From c5703eb00a1559be745fac4516a7b43cf552e3a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 25 Jun 2019 03:15:34 +0200 Subject: [PATCH] Draft `web` platform structure --- src/event_loop.rs | 22 +- src/lib.rs | 49 +++- src/platform/mod.rs | 4 +- src/platform/stdweb.rs | 8 - src/platform/web.rs | 15 + src/platform/web_sys.rs | 7 - src/platform_impl/mod.rs | 10 +- src/platform_impl/stdweb/event_loop.rs | 161 +++++----- src/platform_impl/stdweb/mod.rs | 10 +- src/platform_impl/stdweb/window.rs | 72 ++--- src/platform_impl/web/device.rs | 8 + src/platform_impl/web/error.rs | 10 + src/platform_impl/web/event_loop/mod.rs | 112 +++++++ src/platform_impl/web/event_loop/proxy.rs | 19 ++ src/platform_impl/web/event_loop/runner.rs | 216 ++++++++++++++ src/platform_impl/web/event_loop/state.rs | 42 +++ .../web/event_loop/window_target.rs | 106 +++++++ src/platform_impl/web/mod.rs | 27 ++ src/platform_impl/web/monitor.rs | 22 ++ src/platform_impl/web/stdweb/canvas.rs | 20 ++ src/platform_impl/web/stdweb/mod.rs | 0 src/platform_impl/web/web_sys/canvas.rs | 66 +++++ src/platform_impl/web/web_sys/document.rs | 13 + src/platform_impl/web/web_sys/mod.rs | 17 ++ src/platform_impl/web/web_sys/timeout.rs | 12 + src/platform_impl/web/window.rs | 276 ++++++++++++++++++ 26 files changed, 1171 insertions(+), 153 deletions(-) delete mode 100644 src/platform/stdweb.rs create mode 100644 src/platform/web.rs delete mode 100644 src/platform/web_sys.rs create mode 100644 src/platform_impl/web/device.rs create mode 100644 src/platform_impl/web/error.rs create mode 100644 src/platform_impl/web/event_loop/mod.rs create mode 100644 src/platform_impl/web/event_loop/proxy.rs create mode 100644 src/platform_impl/web/event_loop/runner.rs create mode 100644 src/platform_impl/web/event_loop/state.rs create mode 100644 src/platform_impl/web/event_loop/window_target.rs create mode 100644 src/platform_impl/web/mod.rs create mode 100644 src/platform_impl/web/monitor.rs create mode 100644 src/platform_impl/web/stdweb/canvas.rs create mode 100644 src/platform_impl/web/stdweb/mod.rs create mode 100644 src/platform_impl/web/web_sys/canvas.rs create mode 100644 src/platform_impl/web/web_sys/document.rs create mode 100644 src/platform_impl/web/web_sys/mod.rs create mode 100644 src/platform_impl/web/web_sys/timeout.rs create mode 100644 src/platform_impl/web/window.rs diff --git a/src/event_loop.rs b/src/event_loop.rs index 91a3f912..1e5fb21c 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -9,13 +9,13 @@ //! [create_proxy]: ./struct.EventLoop.html#method.create_proxy //! [event_loop_proxy]: ./struct.EventLoopProxy.html //! [send_event]: ./struct.EventLoopProxy.html#method.send_event -use std::{fmt, error}; use instant::Instant; use std::ops::Deref; +use std::{error, fmt}; -use platform_impl; use event::Event; use monitor::{AvailableMonitorsIter, MonitorHandle}; +use platform_impl; /// Provides a way to retrieve events from the system and from the windows that were registered to /// the events loop. @@ -32,7 +32,7 @@ use monitor::{AvailableMonitorsIter, MonitorHandle}; /// `EventLoopProxy` allows you to wake up an `EventLoop` from an other thread. pub struct EventLoop { pub(crate) event_loop: platform_impl::EventLoop, - pub(crate) _marker: ::std::marker::PhantomData<*mut ()> // Not Send nor Sync + pub(crate) _marker: ::std::marker::PhantomData<*mut ()>, // Not Send nor Sync } /// Target that associates windows with an `EventLoop`. @@ -42,7 +42,7 @@ pub struct EventLoop { /// take `&EventLoop`. pub struct EventLoopWindowTarget { pub(crate) p: platform_impl::EventLoopWindowTarget, - pub(crate) _marker: ::std::marker::PhantomData<*mut ()> // Not Send nor Sync + pub(crate) _marker: ::std::marker::PhantomData<*mut ()>, // Not Send nor Sync } impl fmt::Debug for EventLoop { @@ -82,7 +82,7 @@ pub enum ControlFlow { /// Send a `LoopDestroyed` event and stop the event loop. This variant is *sticky* - once set, /// `control_flow` cannot be changed from `Exit`, and any future attempts to do so will result /// in the `control_flow` parameter being reset to `Exit`. - Exit + Exit, } impl Default for ControlFlow { @@ -133,7 +133,8 @@ impl EventLoop { /// [`ControlFlow`]: ./enum.ControlFlow.html #[inline] pub fn run(self, event_handler: F) -> ! - where F: 'static + FnMut(Event, &EventLoopWindowTarget, &mut ControlFlow) + where + F: 'static + FnMut(Event, &EventLoopWindowTarget, &mut ControlFlow), { self.event_loop.run(event_handler) } @@ -151,13 +152,17 @@ impl EventLoop { #[inline] pub fn available_monitors(&self) -> AvailableMonitorsIter { let data = self.event_loop.available_monitors(); - AvailableMonitorsIter{ data: data.into_iter() } + AvailableMonitorsIter { + data: data.into_iter(), + } } /// Returns the primary monitor of the system. #[inline] pub fn primary_monitor(&self) -> MonitorHandle { - MonitorHandle { inner: self.event_loop.primary_monitor() } + MonitorHandle { + inner: self.event_loop.primary_monitor(), + } } } @@ -207,4 +212,3 @@ impl error::Error for EventLoopClosed { "Tried to wake up a closed `EventLoop`" } } - diff --git a/src/lib.rs b/src/lib.rs index f48dd32c..29ad74e8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -97,23 +97,54 @@ extern crate objc; #[cfg(target_os = "macos")] extern crate cocoa; #[cfg(target_os = "macos")] -extern crate dispatch; -#[cfg(target_os = "macos")] extern crate core_foundation; #[cfg(target_os = "macos")] extern crate core_graphics; -#[cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))] -extern crate x11_dl; -#[cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd", target_os = "windows"))] +#[cfg(target_os = "macos")] +extern crate dispatch; +#[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd", + target_os = "windows" +))] extern crate parking_lot; -#[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 percent_encoding; -#[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 smithay_client_toolkit as sctk; +#[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" +))] +extern crate x11_dl; #[cfg(feature = "stdweb")] #[macro_use] 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; #[cfg(feature = "web-sys")] extern crate wasm_bindgen; @@ -126,8 +157,8 @@ pub mod error; pub mod event; pub mod event_loop; mod icon; +pub mod monitor; mod platform_impl; pub mod window; -pub mod monitor; pub mod platform; diff --git a/src/platform/mod.rs b/src/platform/mod.rs index 94f0bf08..01125fbb 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -20,7 +20,5 @@ pub mod macos; pub mod unix; pub mod windows; -pub mod stdweb; -pub mod web_sys; - pub mod desktop; +pub mod web; diff --git a/src/platform/stdweb.rs b/src/platform/stdweb.rs deleted file mode 100644 index 4ffa7dd2..00000000 --- a/src/platform/stdweb.rs +++ /dev/null @@ -1,8 +0,0 @@ -#![cfg(feature = "stdweb")] - -use stdweb::web::html_element::CanvasElement; - -pub trait WindowExtStdweb { - fn canvas(&self) -> CanvasElement; -} - diff --git a/src/platform/web.rs b/src/platform/web.rs new file mode 100644 index 00000000..edb37f8b --- /dev/null +++ b/src/platform/web.rs @@ -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; +} diff --git a/src/platform/web_sys.rs b/src/platform/web_sys.rs deleted file mode 100644 index c4539f78..00000000 --- a/src/platform/web_sys.rs +++ /dev/null @@ -1,7 +0,0 @@ -#![cfg(feature = "web-sys")] - -use web_sys::HtmlCanvasElement; - -pub trait WindowExtWebSys { - fn canvas(&self) -> HtmlCanvasElement; -} diff --git a/src/platform_impl/mod.rs b/src/platform_impl/mod.rs index 5cb0485a..1c474f94 100644 --- a/src/platform_impl/mod.rs +++ b/src/platform_impl/mod.rs @@ -24,11 +24,8 @@ mod platform; #[cfg(target_os = "emscripten")] #[path = "emscripten/mod.rs"] mod platform; -#[cfg(feature = "stdweb")] -#[path = "stdweb/mod.rs"] -mod platform; -#[cfg(feature = "web_sys")] -#[path = "web_sys/mod.rs"] +#[cfg(target_arch = "wasm32")] +#[path = "web/mod.rs"] mod platform; #[cfg(all( @@ -42,7 +39,6 @@ mod platform; not(target_os = "netbsd"), not(target_os = "openbsd"), not(target_os = "emscripten"), - not(feature = "stdweb"), - not(feature = "web_sys") + not(target_arch = "wasm32"), ))] compile_error!("The platform you're compiling for is not supported by winit"); diff --git a/src/platform_impl/stdweb/event_loop.rs b/src/platform_impl/stdweb/event_loop.rs index c7b2f85b..bb771c87 100644 --- a/src/platform_impl/stdweb/event_loop.rs +++ b/src/platform_impl/stdweb/event_loop.rs @@ -1,27 +1,24 @@ use super::*; use dpi::LogicalPosition; -use event::{DeviceId as RootDI, ElementState, Event, KeyboardInput, MouseScrollDelta, StartCause, TouchPhase, WindowEvent}; -use event_loop::{ControlFlow, EventLoopWindowTarget as RootELW, EventLoopClosed}; -use instant::{Duration, Instant}; -use window::{WindowId as RootWI}; -use stdweb::{ - traits::*, - web::{ - document, - event::*, - html_element::CanvasElement, - window, - TimeoutHandle, - }, +use event::{ + DeviceId as RootDI, ElementState, Event, KeyboardInput, MouseScrollDelta, StartCause, + TouchPhase, WindowEvent, }; +use event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW}; +use instant::{Duration, Instant}; use std::{ cell::RefCell, - collections::{VecDeque, vec_deque::IntoIter as VecDequeIter}, clone::Clone, + collections::{vec_deque::IntoIter as VecDequeIter, VecDeque}, marker::PhantomData, rc::Rc, }; +use stdweb::{ + traits::*, + web::{document, event::*, html_element::CanvasElement, window, TimeoutHandle}, +}; +use window::WindowId as RootWI; #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct DeviceId(i32); @@ -45,15 +42,15 @@ impl EventLoopWindowTarget { EventLoopWindowTarget { runner: EventLoopRunnerShared(Rc::new(ELRShared { runner: RefCell::new(None), - events: RefCell::new(VecDeque::new()) - })) + events: RefCell::new(VecDeque::new()), + })), } } } #[derive(Clone)] pub struct EventLoopProxy { - runner: EventLoopRunnerShared + runner: EventLoopRunnerShared, } impl EventLoopProxy { @@ -87,15 +84,15 @@ enum ControlFlowStatus { WaitUntil { timeout: TimeoutHandle, start: Instant, - end: Instant + end: Instant, }, Wait { start: Instant, }, Poll { - timeout: TimeoutHandle + timeout: TimeoutHandle, }, - Exit + Exit, } impl ControlFlowStatus { @@ -122,7 +119,7 @@ impl EventLoop { EventLoop { elw: RootELW { p: EventLoopWindowTarget::new(), - _marker: PhantomData + _marker: PhantomData, }, } } @@ -136,12 +133,14 @@ impl EventLoop { } pub fn run(self, mut event_handler: F) -> ! - where F: 'static + FnMut(Event, &RootELW, &mut ControlFlow) { + where + F: 'static + FnMut(Event, &RootELW, &mut ControlFlow), + { let runner = self.elw.p.runner; let relw = RootELW { p: EventLoopWindowTarget::new(), - _marker: PhantomData + _marker: PhantomData, }; runner.set_listener(Box::new(move |evt, ctrl| event_handler(evt, &relw, ctrl))); @@ -149,15 +148,14 @@ impl EventLoop { add_event(&runner, document, |elrs, _: BlurEvent| { elrs.send_event(Event::WindowEvent { window_id: RootWI(WindowId), - event: WindowEvent::Focused(false) + event: WindowEvent::Focused(false), }); }); add_event(&runner, document, |elrs, _: FocusEvent| { elrs.send_event(Event::WindowEvent { window_id: RootWI(WindowId), - event: WindowEvent::Focused(true) + event: WindowEvent::Focused(true), }); - }); add_event(&runner, document, |elrs, event: KeyDownEvent| { let key = event.key(); @@ -167,7 +165,7 @@ impl EventLoop { if let (Some(key), None) = (first, second) { elrs.send_event(Event::WindowEvent { window_id: RootWI(WindowId), - event: WindowEvent::ReceivedCharacter(key) + event: WindowEvent::ReceivedCharacter(key), }); } elrs.send_event(Event::WindowEvent { @@ -179,8 +177,8 @@ impl EventLoop { state: ElementState::Pressed, virtual_keycode: button_mapping(&event), modifiers: keyboard_modifiers_state(&event), - } - } + }, + }, }); }); add_event(&runner, document, |elrs, event: KeyUpEvent| { @@ -193,8 +191,8 @@ impl EventLoop { state: ElementState::Released, virtual_keycode: button_mapping(&event), modifiers: keyboard_modifiers_state(&event), - } - } + }, + }, }); }); @@ -210,7 +208,7 @@ impl EventLoop { pub fn create_proxy(&self) -> EventLoopProxy { EventLoopProxy { - runner: self.elw.p.runner.clone() + runner: self.elw.p.runner.clone(), } } @@ -219,21 +217,21 @@ impl EventLoop { } } -pub fn register(elrs: &EventLoopRunnerShared, canvas: &CanvasElement) { +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())) - } + 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())) - } + device_id: RootDI(DeviceId(event.pointer_id())), + }, }); }); add_event(elrs, canvas, |elrs, event: PointerMoveEvent| { @@ -243,10 +241,10 @@ pub fn register(elrs: &EventLoopRunnerShared, canvas: &CanvasElem device_id: RootDI(DeviceId(event.pointer_id())), position: LogicalPosition { x: event.offset_x(), - y: event.offset_y() + y: event.offset_y(), }, - modifiers: mouse_modifiers_state(&event) - } + modifiers: mouse_modifiers_state(&event), + }, }); }); add_event(elrs, canvas, |elrs, event: PointerUpEvent| { @@ -256,8 +254,8 @@ pub fn register(elrs: &EventLoopRunnerShared, canvas: &CanvasElem device_id: RootDI(DeviceId(event.pointer_id())), state: ElementState::Pressed, button: mouse_button(&event), - modifiers: mouse_modifiers_state(&event) - } + modifiers: mouse_modifiers_state(&event), + }, }); }); add_event(elrs, canvas, |elrs, event: PointerDownEvent| { @@ -267,8 +265,8 @@ pub fn register(elrs: &EventLoopRunnerShared, canvas: &CanvasElem device_id: RootDI(DeviceId(event.pointer_id())), state: ElementState::Released, button: mouse_button(&event), - modifiers: mouse_modifiers_state(&event) - } + modifiers: mouse_modifiers_state(&event), + }, }); }); add_event(elrs, canvas, |elrs, event: MouseWheelEvent| { @@ -285,21 +283,27 @@ pub fn register(elrs: &EventLoopRunnerShared, canvas: &CanvasElem device_id: RootDI(DeviceId(0)), delta, phase: TouchPhase::Moved, - modifiers: mouse_modifiers_state(&event) - } + modifiers: mouse_modifiers_state(&event), + }, }); }); } -fn add_event(elrs: &EventLoopRunnerShared, target: &impl IEventTarget, mut handler: F) - where E: ConcreteEvent, F: FnMut(&EventLoopRunnerShared, E) + 'static { +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(); @@ -338,23 +342,26 @@ impl EventLoopRunnerShared { 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 { + ( + match runner.control { + ControlFlowStatus::Init => StartCause::Init, + ControlFlowStatus::Poll { .. } => StartCause::Poll, + ControlFlowStatus::Wait { start } => StartCause::WaitCancelled { start, - requested_resume: Some(end) + requested_resume: None, + }, + ControlFlowStatus::WaitUntil { start, end, .. } => { + StartCause::WaitCancelled { + start, + requested_resume: Some(end), + } + } + ControlFlowStatus::Exit => { + return; } }, - ControlFlowStatus::Exit => { return; } - }, false) + false, + ) } } _ => { @@ -405,7 +412,7 @@ impl EventLoopRunnerShared { } // 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) + _ => 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 @@ -422,13 +429,18 @@ impl EventLoopRunnerShared { // Start any necessary timeouts etc fn apply_control_flow(&self, control_flow: ControlFlow) { let mut control_flow_status = match control_flow { - ControlFlow::Poll => { + ControlFlow::Poll => { let cloned = self.clone(); ControlFlowStatus::Poll { - timeout: window().set_clearable_timeout(move || cloned.send_event(Event::NewEvents(StartCause::Poll)), 1) + timeout: window().set_clearable_timeout( + move || cloned.send_event(Event::NewEvents(StartCause::Poll)), + 1, + ), } } - ControlFlow::Wait => ControlFlowStatus::Wait { start: Instant::now() }, + ControlFlow::Wait => ControlFlowStatus::Wait { + start: Instant::now(), + }, ControlFlow::WaitUntil(end) => { let cloned = self.clone(); let start = Instant::now(); @@ -440,12 +452,15 @@ impl EventLoopRunnerShared { ControlFlowStatus::WaitUntil { start, end, - timeout: window().set_clearable_timeout(move || cloned.send_event(Event::NewEvents(StartCause::Poll)), delay.as_millis() as u32) + 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 @@ -454,11 +469,12 @@ impl EventLoopRunnerShared { // 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(), + ControlFlowStatus::Poll { timeout } + | ControlFlowStatus::WaitUntil { timeout, .. } => timeout.clear(), _ => (), } } - None => () + None => (), } } @@ -478,4 +494,3 @@ impl EventLoopRunnerShared { } } } - diff --git a/src/platform_impl/stdweb/mod.rs b/src/platform_impl/stdweb/mod.rs index 6751019e..09ab677f 100644 --- a/src/platform_impl/stdweb/mod.rs +++ b/src/platform_impl/stdweb/mod.rs @@ -4,9 +4,13 @@ mod event_loop; mod events; mod window; -pub use self::event_loop::{DeviceId, EventLoop, EventLoopRunnerShared, EventLoopWindowTarget, EventLoopProxy, register}; -pub use self::window::{MonitorHandle, Window, WindowId, PlatformSpecificWindowBuilderAttributes}; -pub use self::events::{button_mapping, mouse_modifiers_state, mouse_button, keyboard_modifiers_state, scancode}; +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}; #[derive(Debug)] pub struct OsError(String); diff --git a/src/platform_impl/stdweb/window.rs b/src/platform_impl/stdweb/window.rs index b57d5f1e..81cea6a7 100644 --- a/src/platform_impl/stdweb/window.rs +++ b/src/platform_impl/stdweb/window.rs @@ -1,22 +1,16 @@ +use super::{register, EventLoopWindowTarget, OsError}; use dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize}; use error::{ExternalError, NotSupportedError, OsError as RootOE}; use event::{Event, WindowEvent}; use icon::Icon; +use monitor::MonitorHandle as RootMH; use platform::stdweb::WindowExtStdweb; -use monitor::{MonitorHandle as RootMH}; -use 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, -}; +use std::collections::vec_deque::IntoIter as VecDequeIter; +use std::collections::VecDeque; +use stdweb::web::{document, html_element::CanvasElement, window}; +use stdweb::{traits::*, unstable::TryInto}; +use window::{CursorIcon, Window as RootWindow, WindowAttributes, WindowId as RootWI}; #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct MonitorHandle; @@ -59,14 +53,19 @@ pub struct Window { } impl Window { - pub fn new(target: &EventLoopWindowTarget, attr: WindowAttributes, - _: PlatformSpecificWindowBuilderAttributes) -> Result { + 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() + let canvas: CanvasElement = element + .try_into() .map_err(|_| os_error!(OsError("Failed to create canvas element".to_owned())))?; - document().body() + document() + .body() .ok_or_else(|| os_error!(OsError("Failed to find body node".to_owned())))? .append_child(&canvas); @@ -75,20 +74,19 @@ impl Window { 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 - })); + 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 - }) + position: RefCell::new(LogicalPosition { x: 0.0, y: 0.0 }), }; if let Some(inner_size) = attr.inner_size { @@ -139,11 +137,14 @@ impl Window { pub fn set_outer_position(&self, position: LogicalPosition) { *self.position.borrow_mut() = position; - self.canvas.set_attribute("position", "fixed") + self.canvas + .set_attribute("position", "fixed") .expect("Setting the position for the canvas"); - self.canvas.set_attribute("left", &position.x.to_string()) + 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()) + self.canvas + .set_attribute("top", &position.y.to_string()) .expect("Setting the position for the canvas"); } @@ -151,7 +152,7 @@ impl Window { pub fn inner_size(&self) -> LogicalSize { LogicalSize { width: self.canvas.width() as f64, - height: self.canvas.height() as f64 + height: self.canvas.height() as f64, } } @@ -159,7 +160,7 @@ impl Window { pub fn outer_size(&self) -> LogicalSize { LogicalSize { width: self.canvas.width() as f64, - height: self.canvas.height() as f64 + height: self.canvas.height() as f64, } } @@ -231,7 +232,8 @@ impl Window { CursorIcon::RowResize => "row-resize", }; *self.previous_pointer.borrow_mut() = text; - self.canvas.set_attribute("cursor", text) + self.canvas + .set_attribute("cursor", text) .expect("Setting the cursor on the canvas"); } @@ -250,10 +252,12 @@ impl Window { #[inline] pub fn set_cursor_visible(&self, visible: bool) { if !visible { - self.canvas.set_attribute("cursor", "none") + self.canvas + .set_attribute("cursor", "none") .expect("Setting the cursor on the canvas"); } else { - self.canvas.set_attribute("cursor", *self.previous_pointer.borrow()) + self.canvas + .set_attribute("cursor", *self.previous_pointer.borrow()) .expect("Setting the cursor on the canvas"); } } @@ -297,7 +301,7 @@ impl Window { #[inline] pub fn current_monitor(&self) -> RootMH { RootMH { - inner: MonitorHandle + inner: MonitorHandle, } } diff --git a/src/platform_impl/web/device.rs b/src/platform_impl/web/device.rs new file mode 100644 index 00000000..a2f00b69 --- /dev/null +++ b/src/platform_impl/web/device.rs @@ -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) + } +} diff --git a/src/platform_impl/web/error.rs b/src/platform_impl/web/error.rs new file mode 100644 index 00000000..8f85d6cb --- /dev/null +++ b/src/platform_impl/web/error.rs @@ -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) + } +} diff --git a/src/platform_impl/web/event_loop/mod.rs b/src/platform_impl/web/event_loop/mod.rs new file mode 100644 index 00000000..aad23ad9 --- /dev/null +++ b/src/platform_impl/web/event_loop/mod.rs @@ -0,0 +1,112 @@ +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::{DeviceId, ElementState, Event, KeyboardInput, WindowEvent}; +use crate::event_loop as root; +use crate::window::WindowId; + +use std::collections::{vec_deque::IntoIter as VecDequeIter, VecDeque}; +use std::marker::PhantomData; + +pub struct EventLoop { + elw: root::EventLoopWindowTarget, +} + +impl EventLoop { + pub fn new() -> Self { + EventLoop { + elw: root::EventLoopWindowTarget { + p: WindowTarget::new(), + _marker: PhantomData, + }, + } + } + + pub fn available_monitors(&self) -> VecDequeIter { + VecDeque::new().into_iter() + } + + pub fn primary_monitor(&self) -> monitor::Handle { + monitor::Handle + } + + pub fn run(self, mut event_handler: F) -> ! + where + F: 'static + FnMut(Event, &root::EventLoopWindowTarget, &mut root::ControlFlow), + { + let target = root::EventLoopWindowTarget { + p: self.elw.p.clone(), + _marker: PhantomData, + }; + + let runner = self.elw.p.run(Box::new(move |event, flow| { + event_handler(event, &target, flow) + })); + + backend::Document::on_blur(|| { + runner.send_event(Event::WindowEvent { + window_id: WindowId(window::Id), + event: WindowEvent::Focused(false), + }); + }); + + backend::Document::on_focus(|| { + runner.send_event(Event::WindowEvent { + window_id: WindowId(window::Id), + event: WindowEvent::Focused(true), + }); + }); + + backend::Document::on_key_down(|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, + }, + }, + }); + }); + + backend::Document::on_key_up(|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, + }, + }, + }); + }); + + // 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 { + self.elw.p.proxy() + } + + pub fn window_target(&self) -> &root::EventLoopWindowTarget { + &self.elw + } +} diff --git a/src/platform_impl/web/event_loop/proxy.rs b/src/platform_impl/web/event_loop/proxy.rs new file mode 100644 index 00000000..cbc4732e --- /dev/null +++ b/src/platform_impl/web/event_loop/proxy.rs @@ -0,0 +1,19 @@ +use super::runner; +use crate::event::Event; +use crate::event_loop::EventLoopClosed; + +#[derive(Clone)] +pub struct Proxy { + runner: runner::Shared, +} + +impl Proxy { + pub fn new(runner: runner::Shared) -> Self { + Proxy { runner } + } + + pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> { + self.runner.send_event(Event::UserEvent(event)); + Ok(()) + } +} diff --git a/src/platform_impl/web/event_loop/runner.rs b/src/platform_impl/web/event_loop/runner.rs new file mode 100644 index 00000000..d3611845 --- /dev/null +++ b/src/platform_impl/web/event_loop/runner.rs @@ -0,0 +1,216 @@ +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(Rc>); + +impl Clone for Shared { + fn clone(&self) -> Self { + Shared(self.0.clone()) + } +} + +pub struct Execution { + runner: RefCell>>, + events: RefCell>>, +} + +struct Runner { + state: State, + is_busy: bool, + event_handler: Box, &mut root::ControlFlow)>, +} + +impl Runner { + pub fn new(event_handler: Box, &mut root::ControlFlow)>) -> Self { + Runner { + state: State::Init, + is_busy: false, + event_handler, + } + } +} + +impl Shared { + 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, &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) { + // 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.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.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 root::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 = 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 !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 mut control_flow_status = 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(1), + ), + } + } + root::ControlFlow::Wait => State::Wait { + start: Instant::now(), + }, + root::ControlFlow::WaitUntil(end) => { + let cloned = self.clone(); + let start = Instant::now(); + let delay = if end <= start { + Duration::from_millis(0) + } else { + end - start + }; + 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) => { + // 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.state, &mut control_flow_status); + match control_flow_status { + State::Poll { timeout } | State::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.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.into(), + None => root::ControlFlow::Poll, + } + } +} diff --git a/src/platform_impl/web/event_loop/state.rs b/src/platform_impl/web/event_loop/state.rs new file mode 100644 index 00000000..952e8e2a --- /dev/null +++ b/src/platform_impl/web/event_loop/state.rs @@ -0,0 +1,42 @@ +use super::backend; +use crate::event_loop::ControlFlow; + +use instant::Instant; + +#[derive(Debug, Clone, Copy)] +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, + } + } +} + +impl From for ControlFlow { + fn from(state: State) -> ControlFlow { + match state { + State::Init => ControlFlow::Poll, + State::WaitUntil { end, .. } => ControlFlow::WaitUntil(end), + State::Wait { .. } => ControlFlow::Wait, + State::Poll { .. } => ControlFlow::Poll, + State::Exit => ControlFlow::Exit, + } + } +} diff --git a/src/platform_impl/web/event_loop/window_target.rs b/src/platform_impl/web/event_loop/window_target.rs new file mode 100644 index 00000000..f0eacce2 --- /dev/null +++ b/src/platform_impl/web/event_loop/window_target.rs @@ -0,0 +1,106 @@ +use super::{backend, device, proxy::Proxy, runner, window}; +use crate::event::{DeviceId, ElementState, Event, TouchPhase, WindowEvent}; +use crate::event_loop::ControlFlow; +use crate::window::WindowId; +use std::clone::Clone; + +pub struct WindowTarget { + pub(crate) runner: runner::Shared, +} + +impl Clone for WindowTarget { + fn clone(&self) -> Self { + WindowTarget { + runner: self.runner.clone(), + } + } +} + +impl WindowTarget { + pub fn new() -> Self { + WindowTarget { + runner: runner::Shared::new(), + } + } + + pub fn proxy(&self) -> Proxy { + Proxy::new(self.runner.clone()) + } + + pub fn run( + &self, + event_handler: Box, &mut ControlFlow)>, + ) -> &runner::Shared { + self.runner.set_listener(event_handler); + &self.runner + } + + pub fn register(&self, canvas: &backend::Canvas) { + let runner = &self.runner; + + canvas.on_mouse_out(|pointer_id| { + runner.send_event(Event::WindowEvent { + window_id: WindowId(window::Id), + event: WindowEvent::CursorLeft { + device_id: DeviceId(device::Id(pointer_id)), + }, + }); + }); + + canvas.on_mouse_over(|pointer_id| { + runner.send_event(Event::WindowEvent { + window_id: WindowId(window::Id), + event: WindowEvent::CursorEntered { + device_id: DeviceId(device::Id(pointer_id)), + }, + }); + }); + + canvas.on_mouse_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, + }, + }); + }); + + canvas.on_mouse_up(|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, + }, + }); + }); + + canvas.on_mouse_down(|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, + }, + }); + }); + + canvas.on_mouse_scroll(|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, + }, + }); + }); + } +} diff --git a/src/platform_impl/web/mod.rs b/src/platform_impl/web/mod.rs new file mode 100644 index 00000000..dbe99549 --- /dev/null +++ b/src/platform_impl/web/mod.rs @@ -0,0 +1,27 @@ +// 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; + +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, +}; diff --git a/src/platform_impl/web/monitor.rs b/src/platform_impl/web/monitor.rs new file mode 100644 index 00000000..8ac60fb2 --- /dev/null +++ b/src/platform_impl/web/monitor.rs @@ -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 { + unimplemented!(); + } +} diff --git a/src/platform_impl/web/stdweb/canvas.rs b/src/platform_impl/web/stdweb/canvas.rs new file mode 100644 index 00000000..ba095fb7 --- /dev/null +++ b/src/platform_impl/web/stdweb/canvas.rs @@ -0,0 +1,20 @@ +pub struct Canvas; + +impl Canvas { + pub fn new() -> Self { + 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); + + Canvas(canvas) + } +} diff --git a/src/platform_impl/web/stdweb/mod.rs b/src/platform_impl/web/stdweb/mod.rs new file mode 100644 index 00000000..e69de29b diff --git a/src/platform_impl/web/web_sys/canvas.rs b/src/platform_impl/web/web_sys/canvas.rs new file mode 100644 index 00000000..e7470ac7 --- /dev/null +++ b/src/platform_impl/web/web_sys/canvas.rs @@ -0,0 +1,66 @@ +use crate::dpi::LogicalSize; +use crate::error::OsError as RootOE; +use crate::platform_impl::OsError; + +use wasm_bindgen::JsCast; +use web_sys::HtmlCanvasElement; + +pub struct Canvas { + raw: HtmlCanvasElement, +} + +impl Canvas { + pub fn create() -> Result { + 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())))?; + + Ok(Canvas { raw: canvas }) + } + + 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.clone() + } + + pub fn on_mouse_out(&self, f: F) {} + pub fn on_mouse_over(&self, f: F) {} + pub fn on_mouse_up(&self, f: F) {} + pub fn on_mouse_down(&self, f: F) {} + pub fn on_mouse_move(&self, f: F) {} + pub fn on_mouse_scroll(&self, f: F) {} +} diff --git a/src/platform_impl/web/web_sys/document.rs b/src/platform_impl/web/web_sys/document.rs new file mode 100644 index 00000000..6a85e770 --- /dev/null +++ b/src/platform_impl/web/web_sys/document.rs @@ -0,0 +1,13 @@ +pub struct Document; + +impl Document { + pub fn set_title(title: &str) {} + + pub fn on_blur(f: F) {} + + pub fn on_focus(f: F) {} + + pub fn on_key_up(f: F) {} + + pub fn on_key_down(f: F) {} +} diff --git a/src/platform_impl/web/web_sys/mod.rs b/src/platform_impl/web/web_sys/mod.rs new file mode 100644 index 00000000..6c912c53 --- /dev/null +++ b/src/platform_impl/web/web_sys/mod.rs @@ -0,0 +1,17 @@ +mod canvas; +mod document; +mod timeout; + +pub use self::canvas::Canvas; +pub use self::document::Document; +pub use self::timeout::Timeout; + +pub fn request_animation_frame(f: F) +where + F: Fn(), +{ +} + +pub fn throw(msg: &str) { + wasm_bindgen::throw_str(msg); +} diff --git a/src/platform_impl/web/web_sys/timeout.rs b/src/platform_impl/web/web_sys/timeout.rs new file mode 100644 index 00000000..e285b7a0 --- /dev/null +++ b/src/platform_impl/web/web_sys/timeout.rs @@ -0,0 +1,12 @@ +use std::time::Duration; + +#[derive(Debug, Clone, Copy)] +pub struct Timeout {} + +impl Timeout { + pub fn new(f: F, duration: Duration) -> Timeout { + Timeout {} + } + + pub fn clear(self) {} +} diff --git a/src/platform_impl/web/window.rs b/src/platform_impl/web/window.rs new file mode 100644 index 00000000..813c441c --- /dev/null +++ b/src/platform_impl/web/window.rs @@ -0,0 +1,276 @@ +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, + redraw: Box, + previous_pointer: RefCell<&'static str>, + position: RefCell, +} + +impl Window { + pub fn new( + target: &EventLoopWindowTarget, + attr: WindowAttributes, + _: PlatformSpecificBuilderAttributes, + ) -> Result { + let canvas = backend::Canvas::create()?; + + target.register(&canvas); + + let runner = target.runner.clone(); + let redraw = Box::new(move || { + let runner = runner.clone(); + backend::request_animation_frame(move || { + runner.send_event(Event::WindowEvent { + window_id: RootWI(Id), + event: WindowEvent::RedrawRequested, + }) + }); + }); + + let window = Window { + canvas, + redraw, + 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 set_title(&self, title: &str) { + backend::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 (x, y) = self.canvas.position(); + + Ok(LogicalPosition { x, 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"); + 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) { + // 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); + } + + #[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 { + // 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: monitor::Handle, + } + } + + #[inline] + pub fn available_monitors(&self) -> VecDequeIter { + VecDeque::new().into_iter() + } + + #[inline] + pub fn primary_monitor(&self) -> monitor::Handle { + monitor::Handle + } + + #[inline] + pub fn id(&self) -> Id { + // TODO ? + unsafe { Id::dummy() } + } +} + +#[cfg(feature = "stdweb")] +impl WindowExtStdweb for RootWindow { + fn canvas(&self) -> CanvasElement { + self.window.canvas.clone() + } +} + +#[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;