From fd4db4000c7b68d736906870d7fb157835b1d67f Mon Sep 17 00:00:00 2001 From: Ryan Goldstein Date: Tue, 12 Feb 2019 20:47:31 -0500 Subject: [PATCH 01/69] Create the type layout Everything typechecks, but nothing is implemented --- Cargo.toml | 4 + src/platform_impl/mod.rs | 6 +- src/platform_impl/stdweb/mod.rs | 245 ++++++++++++++++++++++++++++++++ 3 files changed, 254 insertions(+), 1 deletion(-) create mode 100644 src/platform_impl/stdweb/mod.rs diff --git a/Cargo.toml b/Cargo.toml index 7017dc95..baa0d4ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,3 +72,7 @@ percent-encoding = "1.0" [target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "windows"))'.dependencies.parking_lot] version = "0.7" + +[target.'cfg(target_arch = "wasm32")'.dependencies.stdweb] +version = "0.4.13" +optional = true diff --git a/src/platform_impl/mod.rs b/src/platform_impl/mod.rs index be968785..2a8ccd62 100644 --- a/src/platform_impl/mod.rs +++ b/src/platform_impl/mod.rs @@ -18,9 +18,13 @@ 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"), 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")))] compile_error!("The platform you're compiling for is not supported by winit"); diff --git a/src/platform_impl/stdweb/mod.rs b/src/platform_impl/stdweb/mod.rs new file mode 100644 index 00000000..7d9d3cfe --- /dev/null +++ b/src/platform_impl/stdweb/mod.rs @@ -0,0 +1,245 @@ +use dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize}; +use event::Event; +use event_loop::{ControlFlow, EventLoopWindowTarget as RootELW, EventLoopClosed}; +use icon::Icon; +use monitor::{MonitorHandle as RootMH}; +use window::{CreationError, MouseCursor, WindowAttributes}; + +use std::collections::vec_deque::IntoIter as VecDequeIter; +use std::marker::PhantomData; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct DeviceId; + +impl DeviceId { + pub unsafe fn dummy() -> Self { + DeviceId + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct MonitorHandle; + +impl MonitorHandle { + pub fn get_hidpi_factor(&self) -> f64 { + unimplemented!(); + } + + pub fn get_position(&self) -> PhysicalPosition { + unimplemented!(); + } + + pub fn get_dimensions(&self) -> PhysicalSize { + unimplemented!(); + } + + pub fn get_name(&self) -> Option { + unimplemented!(); + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct WindowId; + +impl WindowId { + pub unsafe fn dummy() -> WindowId { + WindowId + } +} + +pub struct Window; + +impl Window { + // TODO: type of window_target + pub fn new(target: &EventLoopWindowTarget, window: WindowAttributes, platform: PlatformSpecificWindowBuilderAttributes) -> Result { + unimplemented!(); + } + + pub fn set_title(&self, title: &str) { + unimplemented!(); + } + + pub fn show(&self) { + unimplemented!(); + } + + pub fn hide(&self) { + unimplemented!(); + } + + pub fn request_redraw(&self) { + unimplemented!(); + } + + pub fn get_position(&self) -> Option { + unimplemented!(); + } + + pub fn get_inner_position(&self) -> Option { + unimplemented!(); + } + + pub fn set_position(&self, position: LogicalPosition) { + unimplemented!(); + } + + #[inline] + pub fn get_inner_size(&self) -> Option { + unimplemented!(); + } + + #[inline] + pub fn get_outer_size(&self) -> Option { + unimplemented!(); + } + + #[inline] + pub fn set_inner_size(&self, size: LogicalSize) { + unimplemented!(); + } + + #[inline] + pub fn set_min_dimensions(&self, dimensions: Option) { + unimplemented!(); + } + + #[inline] + pub fn set_max_dimensions(&self, dimensions: Option) { + unimplemented!(); + } + + #[inline] + pub fn set_resizable(&self, resizable: bool) { + unimplemented!(); + } + + #[inline] + pub fn get_hidpi_factor(&self) -> f64 { + unimplemented!(); + } + + #[inline] + pub fn set_cursor(&self, cursor: MouseCursor) { + unimplemented!(); + } + + #[inline] + pub fn set_cursor_position(&self, position: LogicalPosition) -> Result<(), String> { + unimplemented!(); + } + + #[inline] + pub fn grab_cursor(&self, grab: bool) -> Result<(), String> { + unimplemented!(); + } + + #[inline] + pub fn hide_cursor(&self, hide: bool) { + unimplemented!(); + } + + #[inline] + pub fn set_maximized(&self, maximized: bool) { + unimplemented!(); + } + + #[inline] + pub fn set_fullscreen(&self, monitor: Option) { + unimplemented!(); + } + + #[inline] + pub fn set_decorations(&self, decorations: bool) { + unimplemented!(); + } + + #[inline] + pub fn set_always_on_top(&self, always_on_top: bool) { + unimplemented!(); + } + + #[inline] + pub fn set_window_icon(&self, window_icon: Option) { + unimplemented!(); + } + + #[inline] + pub fn set_ime_spot(&self, position: LogicalPosition) { + unimplemented!(); + } + + #[inline] + pub fn get_current_monitor(&self) -> RootMH { + unimplemented!(); + } + + #[inline] + pub fn get_available_monitors(&self) -> VecDequeIter { + unimplemented!(); + } + + #[inline] + pub fn get_primary_monitor(&self) -> MonitorHandle { + unimplemented!(); + } + + #[inline] + pub fn id(&self) -> WindowId { + unimplemented!(); + } +} + +pub struct EventLoop { + _phantom: PhantomData +} + +impl EventLoop { + pub fn new() -> Self { + unimplemented!(); + } + + pub fn get_available_monitors(&self) -> VecDequeIter { + unimplemented!(); + } + + pub fn get_primary_monitor(&self) -> MonitorHandle { + unimplemented!(); + } + + pub fn run(mut self, event_handler: F) -> ! + where F: 'static + FnMut(Event, &RootELW, &mut ControlFlow) + { + unimplemented!(); + } + + pub fn create_proxy(&self) -> EventLoopProxy { + unimplemented!(); + } + + pub fn window_target(&self) -> &RootELW { + unimplemented!(); + /*&EventLoopWindowTarget { + p: self.event_loop.window_target(), + _marker: std::marker::PhantomData + }*/ + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct EventLoopProxy { + _phantom: PhantomData +} + +impl EventLoopProxy { + pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> { + unimplemented!(); + } +} + +pub struct EventLoopWindowTarget { + _phantom: PhantomData +} + +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct PlatformSpecificWindowBuilderAttributes; + From f44e98ddc92d1faf5b1a5d53f7e27cb650296817 Mon Sep 17 00:00:00 2001 From: Ryan Goldstein Date: Tue, 19 Feb 2019 20:08:18 -0500 Subject: [PATCH 02/69] Implemented a few easy methods --- src/platform_impl/stdweb/mod.rs | 163 ++++++++++++++++++++++++-------- 1 file changed, 126 insertions(+), 37 deletions(-) diff --git a/src/platform_impl/stdweb/mod.rs b/src/platform_impl/stdweb/mod.rs index 7d9d3cfe..4f8f6a45 100644 --- a/src/platform_impl/stdweb/mod.rs +++ b/src/platform_impl/stdweb/mod.rs @@ -4,9 +4,19 @@ use event_loop::{ControlFlow, EventLoopWindowTarget as RootELW, EventLoopClosed} use icon::Icon; use monitor::{MonitorHandle as RootMH}; use window::{CreationError, MouseCursor, WindowAttributes}; - +use stdweb::{ + document, + web::html_element::CanvasElement +}; +use std::cell::Cell; +use std::collections::VecDeque; use std::collections::vec_deque::IntoIter as VecDequeIter; use std::marker::PhantomData; +use std::rc::Rc; + +// TODO: dpi +// TODO: pointer locking (stdweb PR required) +// TODO: should there be a maximization / fullscreen API? #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct DeviceId; @@ -47,7 +57,10 @@ impl WindowId { } } -pub struct Window; +pub struct Window { + canvas: CanvasElement, + monitors: VecDeque +} impl Window { // TODO: type of window_target @@ -56,61 +69,74 @@ impl Window { } pub fn set_title(&self, title: &str) { - unimplemented!(); + document().set_title(title); } pub fn show(&self) { - unimplemented!(); + // Intentionally a no-op } pub fn hide(&self) { - unimplemented!(); + // Intentionally a no-op } pub fn request_redraw(&self) { + // TODO: what does this mean unimplemented!(); } pub fn get_position(&self) -> Option { - unimplemented!(); + let bounds = canvas.get_bouding_client_rect(); + Some(LogicalPosition { + x: bounds.get_x(), + y: bounds.get_y(), + }) } pub fn get_inner_position(&self) -> Option { - unimplemented!(); + self.get_inner_position() } pub fn set_position(&self, position: LogicalPosition) { + // TODO: use CSS? unimplemented!(); } #[inline] pub fn get_inner_size(&self) -> Option { - unimplemented!(); + Some(LogicalSize { + x: self.canvas.width() as f64 + y: self.canvas.height() as f64 + }) } #[inline] pub fn get_outer_size(&self) -> Option { - unimplemented!(); + Some(LogicalSize { + x: self.canvas.width() as f64 + y: self.canvas.height() as f64 + }) } #[inline] pub fn set_inner_size(&self, size: LogicalSize) { - unimplemented!(); + self.canvas.set_width(size.x as u32); + self.canvas.set_height(size.y as u32); } #[inline] - pub fn set_min_dimensions(&self, dimensions: Option) { - unimplemented!(); + pub fn set_min_dimensions(&self, _dimensions: Option) { + // Intentionally a no-op: users can't resize canvas elements } #[inline] - pub fn set_max_dimensions(&self, dimensions: Option) { - unimplemented!(); + pub fn set_max_dimensions(&self, _dimensions: Option) { + // Intentionally a no-op: users can't resize canvas elements } #[inline] - pub fn set_resizable(&self, resizable: bool) { - unimplemented!(); + pub fn set_resizable(&self, _resizable: bool) { + // Intentionally a no-op: users can't resize canvas elements } #[inline] @@ -120,46 +146,92 @@ impl Window { #[inline] pub fn set_cursor(&self, cursor: MouseCursor) { - unimplemented!(); + let text = match cursor { + MouseCursor::Default => "auto", + MouseCursor::Crosshair => "crosshair", + MouseCursor::Hand => "pointer", + MouseCursor::Arrow => "default", + MouseCursor::Move => "move", + MouseCursor::Text => "text", + MouseCursor::Wait => "wait", + MouseCursor::Help => "help", + MouseCursor::Progress => "progress", + + MouseCursor::NotAllowed => "not-allowed", + MouseCursor::ContextMenu => "context-menu", + MouseCursor::Cell => "cell", + MouseCursor::VerticalText => "vertical-text", + MouseCursor::Alias => "alias", + MouseCursor::Copy => "copy", + MouseCursor::NoDrop => "no-drop", + MouseCursor::Grab => "grab", + MouseCursor::Grabbing => "grabbing", + MouseCursor::AllScroll => "all-scroll", + MouseCursor::ZoomIn => "zoom-in", + MouseCursor::ZoomOut => "zoom-out", + + MouseCursor::EResize => "e-resize", + MouseCursor::NResize => "n-resize", + MouseCursor::NeResize => "ne-resize", + MouseCursor::NwResize => "nw-resize", + MouseCursor::SResize => "s-resize", + MouseCursor::SeResize => "se-resize", + MouseCursor::SwResize => "sw-resize", + MouseCursor::WResize => "w-resize", + MouseCursor::EwResize => "ew-resize", + MouseCursor::NsResize => "ns-resize", + MouseCursor::NeswResize => "nesw-resize", + MouseCursor::NwseResize => "nwse-resize", + MouseCursor::ColResize => "col-resize", + MouseCursor::RowResize => "row-resize", + }; + self.canvas.set_attribute("cursor", text); + .expect("Setting the cursor on the canvas"); } #[inline] pub fn set_cursor_position(&self, position: LogicalPosition) -> Result<(), String> { + // TODO: pointer capture unimplemented!(); } #[inline] pub fn grab_cursor(&self, grab: bool) -> Result<(), String> { + // TODO: pointer capture unimplemented!(); } #[inline] pub fn hide_cursor(&self, hide: bool) { - unimplemented!(); + self.canvas.set_attribute("cursor", "none") + .expect("Setting the cursor on the canvas"); } #[inline] pub fn set_maximized(&self, maximized: bool) { + // TODO: should there be a maximization / fullscreen API? unimplemented!(); } #[inline] pub fn set_fullscreen(&self, monitor: Option) { + // TODO: should there be a maximization / fullscreen API? unimplemented!(); } #[inline] - pub fn set_decorations(&self, decorations: bool) { - unimplemented!(); + 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) { - unimplemented!(); + 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) { + // TODO: should this set the favicon? unimplemented!(); } @@ -170,27 +242,43 @@ impl Window { #[inline] pub fn get_current_monitor(&self) -> RootMH { - unimplemented!(); + RootMH { + inner: MonitorHandle + } } #[inline] pub fn get_available_monitors(&self) -> VecDequeIter { - unimplemented!(); + self.monitors.iter() } #[inline] pub fn get_primary_monitor(&self) -> MonitorHandle { - unimplemented!(); + MonitorHandle } #[inline] pub fn id(&self) -> WindowId { - unimplemented!(); + WindowId::dummy() + } +} + +fn new_rootelw() -> RootELW { + RootELW { + p: EventLoopWindowTarget, + _marker: PhantomData } } pub struct EventLoop { - _phantom: PhantomData + window_target: RootELW, + monitors: VecDeque, + data: Rc>, +} + +struct EventLoopData { + events: VecDeque, + control: ControlFlow, } impl EventLoop { @@ -199,40 +287,41 @@ impl EventLoop { } pub fn get_available_monitors(&self) -> VecDequeIter { - unimplemented!(); + self.monitors.iter() } pub fn get_primary_monitor(&self) -> MonitorHandle { - unimplemented!(); + MonitorHandle } pub fn run(mut self, event_handler: F) -> ! where F: 'static + FnMut(Event, &RootELW, &mut ControlFlow) { + // TODO: Create event handlers for the JS events + // TODO: how to handle request redraw? unimplemented!(); } pub fn create_proxy(&self) -> EventLoopProxy { - unimplemented!(); + EventLoopProxy { + events: self.window_target.p.events.clone() + } } pub fn window_target(&self) -> &RootELW { - unimplemented!(); - /*&EventLoopWindowTarget { - p: self.event_loop.window_target(), - _marker: std::marker::PhantomData - }*/ + &self.window_target } } #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct EventLoopProxy { - _phantom: PhantomData + data: EventLoopData } impl EventLoopProxy { pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> { - unimplemented!(); + self.data.borrow_mut().events.push_back(Event::UserEvent(event)); + Ok(()) } } From c088f8bd030ac341feda298a67e6c4f1f53f49e3 Mon Sep 17 00:00:00 2001 From: Ryan Goldstein Date: Tue, 26 Feb 2019 13:36:48 -0500 Subject: [PATCH 03/69] Create the outline of event input and handler calls --- src/lib.rs | 2 + src/platform_impl/stdweb/mod.rs | 120 +++++++++++++++++++++++++------- 2 files changed, 97 insertions(+), 25 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index ff374806..4155fa25 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -111,6 +111,8 @@ extern crate parking_lot; extern crate percent_encoding; #[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(feature = "stdweb")] +extern crate stdweb; pub mod dpi; pub mod event; diff --git a/src/platform_impl/stdweb/mod.rs b/src/platform_impl/stdweb/mod.rs index 4f8f6a45..72701136 100644 --- a/src/platform_impl/stdweb/mod.rs +++ b/src/platform_impl/stdweb/mod.rs @@ -1,14 +1,18 @@ use dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize}; -use event::Event; +use event::{Event, StartCause}; use event_loop::{ControlFlow, EventLoopWindowTarget as RootELW, EventLoopClosed}; use icon::Icon; use monitor::{MonitorHandle as RootMH}; use window::{CreationError, MouseCursor, WindowAttributes}; use stdweb::{ - document, - web::html_element::CanvasElement + traits::*, + web::{ + document, + event::*, + html_element::CanvasElement, + } }; -use std::cell::Cell; +use std::cell::{RefCell, RefMut}; use std::collections::VecDeque; use std::collections::vec_deque::IntoIter as VecDequeIter; use std::marker::PhantomData; @@ -59,7 +63,6 @@ impl WindowId { pub struct Window { canvas: CanvasElement, - monitors: VecDeque } impl Window { @@ -86,7 +89,7 @@ impl Window { } pub fn get_position(&self) -> Option { - let bounds = canvas.get_bouding_client_rect(); + let bounds = self.canvas.get_bounding_client_rect(); Some(LogicalPosition { x: bounds.get_x(), y: bounds.get_y(), @@ -105,23 +108,23 @@ impl Window { #[inline] pub fn get_inner_size(&self) -> Option { Some(LogicalSize { - x: self.canvas.width() as f64 - y: self.canvas.height() as f64 + width: self.canvas.width() as f64, + height: self.canvas.height() as f64 }) } #[inline] pub fn get_outer_size(&self) -> Option { Some(LogicalSize { - x: self.canvas.width() as f64 - y: self.canvas.height() as f64 + 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.x as u32); - self.canvas.set_height(size.y as u32); + self.canvas.set_width(size.width as u32); + self.canvas.set_height(size.height as u32); } #[inline] @@ -185,7 +188,7 @@ impl Window { MouseCursor::ColResize => "col-resize", MouseCursor::RowResize => "row-resize", }; - self.canvas.set_attribute("cursor", text); + self.canvas.set_attribute("cursor", text) .expect("Setting the cursor on the canvas"); } @@ -249,7 +252,7 @@ impl Window { #[inline] pub fn get_available_monitors(&self) -> VecDequeIter { - self.monitors.iter() + VecDeque::new().into_iter() } #[inline] @@ -259,25 +262,28 @@ impl Window { #[inline] pub fn id(&self) -> WindowId { - WindowId::dummy() + // TODO ? + unsafe { WindowId::dummy() } } } fn new_rootelw() -> RootELW { RootELW { - p: EventLoopWindowTarget, + p: EventLoopWindowTarget { + _phantom: PhantomData + }, _marker: PhantomData } } -pub struct EventLoop { +pub struct EventLoop { window_target: RootELW, - monitors: VecDeque, - data: Rc>, + data: Rc>>, } -struct EventLoopData { - events: VecDeque, +#[derive(Clone)] +struct EventLoopData { + events: VecDeque>, control: ControlFlow, } @@ -287,7 +293,7 @@ impl EventLoop { } pub fn get_available_monitors(&self) -> VecDequeIter { - self.monitors.iter() + VecDeque::new().into_iter() } pub fn get_primary_monitor(&self) -> MonitorHandle { @@ -299,23 +305,87 @@ impl EventLoop { { // TODO: Create event handlers for the JS events // TODO: how to handle request redraw? + // TODO: onclose (stdweb PR) + // TODO: file dropping, PathBuf isn't useful for web + + let document = &document(); + self.add_event(document, |data, event: BlurEvent| { + }); + self.add_event(document, |data, event: FocusEvent| { + }); + + // TODO: what to do after attaching events unimplemented!(); } pub fn create_proxy(&self) -> EventLoopProxy { EventLoopProxy { - events: self.window_target.p.events.clone() + data: self.data.clone() } } pub fn window_target(&self) -> &RootELW { &self.window_target } + + // Apply all enqueued events + fn apply_events(&mut self, mut event_handler: F, start: StartCause) + where F: 'static + FnMut(Event, &RootELW, &mut ControlFlow) { + // TODO: how to handle ControlFlow::Exit? + let mut data = self.data.borrow_mut(); + let mut control = data.control.clone(); + let events = &mut data.events; + event_handler(Event::NewEvents(start), &new_rootelw(), &mut control); + for event in events.drain(..) { + event_handler(event, &new_rootelw(), &mut control); + } + event_handler(Event::EventsCleared, &new_rootelw(), &mut control) + } + + fn register_window(&self, other: &Window) { + let canvas = &other.canvas; + + self.add_event(canvas, |data, event: KeyDownEvent| { + // TODO: received character + // TODO: keyboard input + }); + self.add_event(canvas, |data, event: KeyUpEvent| { + // TODO: keyboard input + }); + self.add_event(canvas, |data, _: PointerOutEvent| { + // TODO + }); + self.add_event(canvas, |data, _: PointerOverEvent| { + // TODO + }); + self.add_event(canvas, |data, event: PointerMoveEvent| { + // TODO: mouse move + }); + self.add_event(canvas, |data, event: PointerUpEvent| { + // TODO: mouse pointers + }); + self.add_event(canvas, |data, event: PointerDownEvent| { + // TODO: mouse pointers + }); + } + + fn add_event(&self, target: &impl IEventTarget, mut handler: F) + where E: ConcreteEvent, F: FnMut(RefMut>, E) + 'static { + let data = self.data.clone(); + + target.add_event_listener(move |event: E| { + event.prevent_default(); + event.stop_propagation(); + event.cancel_bubble(); + + handler(data.borrow_mut(), event); + }); + } } -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Clone)] pub struct EventLoopProxy { - data: EventLoopData + data: Rc>> } impl EventLoopProxy { From f698d451df95bf6a41a594462020eb8ea4424216 Mon Sep 17 00:00:00 2001 From: Ryan Goldstein Date: Sat, 2 Mar 2019 12:31:16 -0500 Subject: [PATCH 04/69] Add key and mouse event support that typechecks --- src/lib.rs | 1 + src/platform_impl/stdweb/mod.rs | 315 +++++++++++++++++++++++++++++--- 2 files changed, 293 insertions(+), 23 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 4155fa25..07d908b1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -112,6 +112,7 @@ extern crate percent_encoding; #[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(feature = "stdweb")] +#[macro_use] extern crate stdweb; pub mod dpi; diff --git a/src/platform_impl/stdweb/mod.rs b/src/platform_impl/stdweb/mod.rs index 72701136..cea168da 100644 --- a/src/platform_impl/stdweb/mod.rs +++ b/src/platform_impl/stdweb/mod.rs @@ -1,16 +1,18 @@ use dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize}; -use event::{Event, StartCause}; +use event::{DeviceEvent, DeviceId as RootDI, ElementState, Event, KeyboardInput, ModifiersState, MouseButton, ScanCode, StartCause, VirtualKeyCode, WindowEvent}; use event_loop::{ControlFlow, EventLoopWindowTarget as RootELW, EventLoopClosed}; use icon::Icon; use monitor::{MonitorHandle as RootMH}; -use window::{CreationError, MouseCursor, WindowAttributes}; +use window::{CreationError, MouseCursor, WindowId as RootWI, WindowAttributes}; use stdweb::{ + JsSerialize, traits::*, + unstable::TryInto, web::{ document, event::*, html_element::CanvasElement, - } + }, }; use std::cell::{RefCell, RefMut}; use std::collections::VecDeque; @@ -19,15 +21,18 @@ use std::marker::PhantomData; use std::rc::Rc; // 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? #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct DeviceId; +pub struct DeviceId(i32); impl DeviceId { pub unsafe fn dummy() -> Self { - DeviceId + DeviceId(0) } } @@ -66,7 +71,6 @@ pub struct Window { } impl Window { - // TODO: type of window_target pub fn new(target: &EventLoopWindowTarget, window: WindowAttributes, platform: PlatformSpecificWindowBuilderAttributes) -> Result { unimplemented!(); } @@ -309,9 +313,9 @@ impl EventLoop { // TODO: file dropping, PathBuf isn't useful for web let document = &document(); - self.add_event(document, |data, event: BlurEvent| { + self.add_event(document, |mut data, event: BlurEvent| { }); - self.add_event(document, |data, event: FocusEvent| { + self.add_event(document, |mut data, event: FocusEvent| { }); // TODO: what to do after attaching events @@ -345,27 +349,96 @@ impl EventLoop { fn register_window(&self, other: &Window) { let canvas = &other.canvas; - self.add_event(canvas, |data, event: KeyDownEvent| { - // TODO: received character - // TODO: keyboard input + self.add_event(canvas, |mut data, 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) { + data.events.push_back(Event::WindowEvent { + window_id: RootWI(WindowId), + event: WindowEvent::ReceivedCharacter(key) + }); + } + data.events.push_back(Event::WindowEvent { + window_id: RootWI(WindowId), + event: WindowEvent::KeyboardInput { + // TODO: is there a way to get keyboard device? + device_id: RootDI(unsafe { DeviceId::dummy() }), + input: KeyboardInput { + scancode: scancode(&event), + state: ElementState::Pressed, + virtual_keycode: button_mapping(&event), + modifiers: keyboard_modifiers_state(&event), + } + } + }); }); - self.add_event(canvas, |data, event: KeyUpEvent| { - // TODO: keyboard input + self.add_event(canvas, |mut data, event: KeyUpEvent| { + data.events.push_back(Event::WindowEvent { + window_id: RootWI(WindowId), + event: WindowEvent::KeyboardInput { + // TODO: is there a way to get keyboard device? + device_id: RootDI(unsafe { DeviceId::dummy() }), + input: KeyboardInput { + scancode: scancode(&event), + state: ElementState::Released, + virtual_keycode: button_mapping(&event), + modifiers: keyboard_modifiers_state(&event), + } + } + }); }); - self.add_event(canvas, |data, _: PointerOutEvent| { - // TODO + self.add_event(canvas, |mut data, event: PointerOutEvent| { + data.events.push_back(Event::WindowEvent { + window_id: RootWI(WindowId), + event: WindowEvent::CursorLeft { + device_id: RootDI(DeviceId(event.pointer_id())) + } + }); }); - self.add_event(canvas, |data, _: PointerOverEvent| { - // TODO + self.add_event(canvas, |mut data, event: PointerOverEvent| { + data.events.push_back(Event::WindowEvent { + window_id: RootWI(WindowId), + event: WindowEvent::CursorEntered { + device_id: RootDI(DeviceId(event.pointer_id())) + } + }); }); - self.add_event(canvas, |data, event: PointerMoveEvent| { - // TODO: mouse move + self.add_event(canvas, |mut data, event: PointerMoveEvent| { + data.events.push_back(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) + } + }); }); - self.add_event(canvas, |data, event: PointerUpEvent| { - // TODO: mouse pointers + self.add_event(canvas, |mut data, event: PointerUpEvent| { + data.events.push_back(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) + } + }); }); - self.add_event(canvas, |data, event: PointerDownEvent| { - // TODO: mouse pointers + self.add_event(canvas, |mut data, event: PointerDownEvent| { + data.events.push_back(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) + } + }); }); } @@ -383,6 +456,202 @@ impl EventLoop { } } +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(), + } +} + +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), + } +} + +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(), + } +} + +fn scancode(event: &T) -> ScanCode { + let which = js! ( return @{event}.which(); ); + which.try_into().expect("The which value should be a number") +} + +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 + }) +} + #[derive(Clone)] pub struct EventLoopProxy { data: Rc>> From 37d354cf7fdee5a423436899b20e77aab0629b05 Mon Sep 17 00:00:00 2001 From: Ryan Goldstein Date: Sat, 9 Mar 2019 21:54:29 -0500 Subject: [PATCH 05/69] Get to a state where a canvas is spawned --- examples/window.rs | 6 +- src/event_loop.rs | 2 +- src/platform_impl/stdweb/mod.rs | 119 ++++++++++++++++++-------------- 3 files changed, 75 insertions(+), 52 deletions(-) diff --git a/examples/window.rs b/examples/window.rs index 3d5c7cf7..b33b5984 100644 --- a/examples/window.rs +++ b/examples/window.rs @@ -1,4 +1,7 @@ extern crate winit; +#[macro_use] +extern crate stdweb; + use winit::window::WindowBuilder; use winit::event::{Event, WindowEvent}; use winit::event_loop::{EventLoop, ControlFlow}; @@ -10,9 +13,10 @@ fn main() { .with_title("A fantastic window!") .build(&event_loop) .unwrap(); + console!(log, "Built window!"); event_loop.run(|event, _, control_flow| { - println!("{:?}", event); + console!(log, format!("{:?}", event)); match event { Event::WindowEvent { diff --git a/src/event_loop.rs b/src/event_loop.rs index ca217f7c..44cc8b90 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -139,7 +139,7 @@ impl EventLoop { /// /// [`ControlFlow`]: ./enum.ControlFlow.html #[inline] - pub fn run(self, event_handler: F) -> ! + pub fn run(self, event_handler: F) // TODO: this needs to be ! where F: 'static + FnMut(Event, &EventLoopWindowTarget, &mut ControlFlow) { self.event_loop.run(event_handler) diff --git a/src/platform_impl/stdweb/mod.rs b/src/platform_impl/stdweb/mod.rs index cea168da..2eda4318 100644 --- a/src/platform_impl/stdweb/mod.rs +++ b/src/platform_impl/stdweb/mod.rs @@ -41,7 +41,8 @@ pub struct MonitorHandle; impl MonitorHandle { pub fn get_hidpi_factor(&self) -> f64 { - unimplemented!(); + // TODO + 1.0 } pub fn get_position(&self) -> PhysicalPosition { @@ -71,8 +72,42 @@ pub struct Window { } impl Window { - pub fn new(target: &EventLoopWindowTarget, window: WindowAttributes, platform: PlatformSpecificWindowBuilderAttributes) -> Result { - unimplemented!(); + pub fn new(target: &EventLoopWindowTarget, attr: WindowAttributes, + _: PlatformSpecificWindowBuilderAttributes) -> Result { + let element = document() + .create_element("canvas") + .map_err(|_| CreationError::OsError("Failed to create canvas element".to_owned()))?; + let canvas: CanvasElement = element.try_into() + .map_err(|_| CreationError::OsError("Failed to create canvas element".to_owned()))?; + document().body() + .ok_or_else(|| CreationError::OsError("Failed to find body node".to_owned()))? + .append_child(&canvas); + let window = Window { canvas }; + if let Some(dimensions) = attr.dimensions { + window.set_inner_size(dimensions); + } else { + window.set_inner_size(LogicalSize { + width: 1024.0, + height: 768.0, + }) + } + // TODO: most of these are no-op, but should they stay here just in case? + window.set_min_dimensions(attr.min_dimensions); + window.set_max_dimensions(attr.max_dimensions); + window.set_resizable(attr.resizable); + window.set_title(&attr.title); + window.set_maximized(attr.maximized); + if attr.visible { + window.show(); + } else { + window.hide(); + } + //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); + target.register_window(&window); + Ok(window) } pub fn set_title(&self, title: &str) { @@ -88,8 +123,7 @@ impl Window { } pub fn request_redraw(&self) { - // TODO: what does this mean - unimplemented!(); + // TODO: what does this mean? If it's a 'present'-style call then it's not necessary } pub fn get_position(&self) -> Option { @@ -106,7 +140,6 @@ impl Window { pub fn set_position(&self, position: LogicalPosition) { // TODO: use CSS? - unimplemented!(); } #[inline] @@ -148,7 +181,8 @@ impl Window { #[inline] pub fn get_hidpi_factor(&self) -> f64 { - unimplemented!(); + // TODO + 1.0 } #[inline] @@ -199,13 +233,13 @@ impl Window { #[inline] pub fn set_cursor_position(&self, position: LogicalPosition) -> Result<(), String> { // TODO: pointer capture - unimplemented!(); + Ok(()) } #[inline] pub fn grab_cursor(&self, grab: bool) -> Result<(), String> { // TODO: pointer capture - unimplemented!(); + Ok(()) } #[inline] @@ -217,13 +251,11 @@ impl Window { #[inline] pub fn set_maximized(&self, maximized: bool) { // TODO: should there be a maximization / fullscreen API? - unimplemented!(); } #[inline] pub fn set_fullscreen(&self, monitor: Option) { // TODO: should there be a maximization / fullscreen API? - unimplemented!(); } #[inline] @@ -239,12 +271,11 @@ impl Window { #[inline] pub fn set_window_icon(&self, window_icon: Option) { // TODO: should this set the favicon? - unimplemented!(); } #[inline] pub fn set_ime_spot(&self, position: LogicalPosition) { - unimplemented!(); + // TODO: what is this? } #[inline] @@ -271,18 +302,8 @@ impl Window { } } -fn new_rootelw() -> RootELW { - RootELW { - p: EventLoopWindowTarget { - _phantom: PhantomData - }, - _marker: PhantomData - } -} - pub struct EventLoop { - window_target: RootELW, - data: Rc>>, + elw: RootELW, } #[derive(Clone)] @@ -291,9 +312,23 @@ struct EventLoopData { control: ControlFlow, } +pub struct EventLoopWindowTarget { + data: Rc>>, +} + impl EventLoop { pub fn new() -> Self { - unimplemented!(); + EventLoop { + elw: RootELW { + p: EventLoopWindowTarget { + data: Rc::new(RefCell::new(EventLoopData { + events: VecDeque::new(), + control: ControlFlow::Poll + })) + }, + _marker: PhantomData + } + } } pub fn get_available_monitors(&self) -> VecDequeIter { @@ -304,7 +339,7 @@ impl EventLoop { MonitorHandle } - pub fn run(mut self, event_handler: F) -> ! + pub fn run(mut self, event_handler: F) where F: 'static + FnMut(Event, &RootELW, &mut ControlFlow) { // TODO: Create event handlers for the JS events @@ -313,39 +348,26 @@ impl EventLoop { // TODO: file dropping, PathBuf isn't useful for web let document = &document(); - self.add_event(document, |mut data, event: BlurEvent| { + self.elw.p.add_event(document, |mut data, event: BlurEvent| { }); - self.add_event(document, |mut data, event: FocusEvent| { + self.elw.p.add_event(document, |mut data, event: FocusEvent| { }); - // TODO: what to do after attaching events - unimplemented!(); + stdweb::event_loop(); // TODO: this is only necessary for stdweb emscripten, should it be here? } pub fn create_proxy(&self) -> EventLoopProxy { EventLoopProxy { - data: self.data.clone() + data: self.elw.p.data.clone() } } pub fn window_target(&self) -> &RootELW { - &self.window_target - } - - // Apply all enqueued events - fn apply_events(&mut self, mut event_handler: F, start: StartCause) - where F: 'static + FnMut(Event, &RootELW, &mut ControlFlow) { - // TODO: how to handle ControlFlow::Exit? - let mut data = self.data.borrow_mut(); - let mut control = data.control.clone(); - let events = &mut data.events; - event_handler(Event::NewEvents(start), &new_rootelw(), &mut control); - for event in events.drain(..) { - event_handler(event, &new_rootelw(), &mut control); - } - event_handler(Event::EventsCleared, &new_rootelw(), &mut control) + &self.elw } +} +impl EventLoopWindowTarget { fn register_window(&self, other: &Window) { let canvas = &other.canvas; @@ -442,6 +464,7 @@ impl EventLoop { }); } + fn add_event(&self, target: &impl IEventTarget, mut handler: F) where E: ConcreteEvent, F: FnMut(RefMut>, E) + 'static { let data = self.data.clone(); @@ -664,10 +687,6 @@ impl EventLoopProxy { } } -pub struct EventLoopWindowTarget { - _phantom: PhantomData -} - #[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct PlatformSpecificWindowBuilderAttributes; From 283a8dec37f7e251ddd90b7ddff9fa9462877cbd Mon Sep 17 00:00:00 2001 From: Ryan Goldstein Date: Sat, 9 Mar 2019 22:23:39 -0500 Subject: [PATCH 06/69] Refactor out the stdweb functionality into different modules --- src/platform_impl/stdweb/events.rs | 222 ++++++++ src/platform_impl/stdweb/input_binds.rs | 202 +++++++ src/platform_impl/stdweb/mod.rs | 693 +----------------------- src/platform_impl/stdweb/window.rs | 285 ++++++++++ 4 files changed, 717 insertions(+), 685 deletions(-) create mode 100644 src/platform_impl/stdweb/events.rs create mode 100644 src/platform_impl/stdweb/input_binds.rs create mode 100644 src/platform_impl/stdweb/window.rs diff --git a/src/platform_impl/stdweb/events.rs b/src/platform_impl/stdweb/events.rs new file mode 100644 index 00000000..d3b39d9e --- /dev/null +++ b/src/platform_impl/stdweb/events.rs @@ -0,0 +1,222 @@ +use super::*; + +use dpi::{LogicalPosition, LogicalSize}; +use event::{DeviceEvent, DeviceId as RootDI, ElementState, Event, KeyboardInput, ModifiersState, MouseButton, ScanCode, StartCause, VirtualKeyCode, WindowEvent}; +use event_loop::{ControlFlow, EventLoopWindowTarget as RootELW, EventLoopClosed}; +use icon::Icon; +use window::{MouseCursor, WindowId as RootWI}; +use stdweb::{ + JsSerialize, + traits::*, + unstable::TryInto, + web::{ + document, + event::*, + html_element::CanvasElement, + }, +}; +use std::cell::{RefCell, RefMut}; +use std::collections::VecDeque; +use std::collections::vec_deque::IntoIter as VecDequeIter; +use std::marker::PhantomData; +use std::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, +} + +#[derive(Clone)] +struct EventLoopData { + events: VecDeque>, + control: ControlFlow, +} + +pub struct EventLoopWindowTarget { + data: Rc>>, +} + +impl EventLoop { + pub fn new() -> Self { + EventLoop { + elw: RootELW { + p: EventLoopWindowTarget { + data: Rc::new(RefCell::new(EventLoopData { + events: VecDeque::new(), + control: ControlFlow::Poll + })) + }, + _marker: PhantomData + } + } + } + + pub fn get_available_monitors(&self) -> VecDequeIter { + VecDeque::new().into_iter() + } + + pub fn get_primary_monitor(&self) -> MonitorHandle { + MonitorHandle + } + + pub fn run(mut self, event_handler: F) + where F: 'static + FnMut(Event, &RootELW, &mut ControlFlow) + { + // TODO: Create event handlers for the JS events + // TODO: how to handle request redraw? + // TODO: onclose (stdweb PR) + // TODO: file dropping, PathBuf isn't useful for web + + let document = &document(); + self.elw.p.add_event(document, |mut data, event: BlurEvent| { + }); + self.elw.p.add_event(document, |mut data, event: FocusEvent| { + }); + + stdweb::event_loop(); // TODO: this is only necessary for stdweb emscripten, should it be here? + } + + pub fn create_proxy(&self) -> EventLoopProxy { + EventLoopProxy { + data: self.elw.p.data.clone() + } + } + + pub fn window_target(&self) -> &RootELW { + &self.elw + } +} + +impl EventLoopWindowTarget { + pub fn register_window(&self, other: &Window) { + let canvas = &other.canvas; + + self.add_event(canvas, |mut data, 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) { + data.events.push_back(Event::WindowEvent { + window_id: RootWI(WindowId), + event: WindowEvent::ReceivedCharacter(key) + }); + } + data.events.push_back(Event::WindowEvent { + window_id: RootWI(WindowId), + event: WindowEvent::KeyboardInput { + // TODO: is there a way to get keyboard device? + device_id: RootDI(unsafe { DeviceId::dummy() }), + input: KeyboardInput { + scancode: scancode(&event), + state: ElementState::Pressed, + virtual_keycode: button_mapping(&event), + modifiers: keyboard_modifiers_state(&event), + } + } + }); + }); + self.add_event(canvas, |mut data, event: KeyUpEvent| { + data.events.push_back(Event::WindowEvent { + window_id: RootWI(WindowId), + event: WindowEvent::KeyboardInput { + // TODO: is there a way to get keyboard device? + device_id: RootDI(unsafe { DeviceId::dummy() }), + input: KeyboardInput { + scancode: scancode(&event), + state: ElementState::Released, + virtual_keycode: button_mapping(&event), + modifiers: keyboard_modifiers_state(&event), + } + } + }); + }); + self.add_event(canvas, |mut data, event: PointerOutEvent| { + data.events.push_back(Event::WindowEvent { + window_id: RootWI(WindowId), + event: WindowEvent::CursorLeft { + device_id: RootDI(DeviceId(event.pointer_id())) + } + }); + }); + self.add_event(canvas, |mut data, event: PointerOverEvent| { + data.events.push_back(Event::WindowEvent { + window_id: RootWI(WindowId), + event: WindowEvent::CursorEntered { + device_id: RootDI(DeviceId(event.pointer_id())) + } + }); + }); + self.add_event(canvas, |mut data, event: PointerMoveEvent| { + data.events.push_back(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) + } + }); + }); + self.add_event(canvas, |mut data, event: PointerUpEvent| { + data.events.push_back(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) + } + }); + }); + self.add_event(canvas, |mut data, event: PointerDownEvent| { + data.events.push_back(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) + } + }); + }); + } + + + fn add_event(&self, target: &impl IEventTarget, mut handler: F) + where E: ConcreteEvent, F: FnMut(RefMut>, E) + 'static { + let data = self.data.clone(); + + target.add_event_listener(move |event: E| { + event.prevent_default(); + event.stop_propagation(); + event.cancel_bubble(); + + handler(data.borrow_mut(), event); + }); + } +} + +#[derive(Clone)] +pub struct EventLoopProxy { + data: Rc>> +} + +impl EventLoopProxy { + pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> { + self.data.borrow_mut().events.push_back(Event::UserEvent(event)); + Ok(()) + } +} + + diff --git a/src/platform_impl/stdweb/input_binds.rs b/src/platform_impl/stdweb/input_binds.rs new file mode 100644 index 00000000..282c6664 --- /dev/null +++ b/src/platform_impl/stdweb/input_binds.rs @@ -0,0 +1,202 @@ +use stdweb::{ + JsSerialize, + web::event::{IKeyboardEvent, IMouseEvent}, + unstable::TryInto +}; +use 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/mod.rs b/src/platform_impl/stdweb/mod.rs index 2eda4318..c2b87331 100644 --- a/src/platform_impl/stdweb/mod.rs +++ b/src/platform_impl/stdweb/mod.rs @@ -1,24 +1,11 @@ -use dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize}; -use event::{DeviceEvent, DeviceId as RootDI, ElementState, Event, KeyboardInput, ModifiersState, MouseButton, ScanCode, StartCause, VirtualKeyCode, WindowEvent}; -use event_loop::{ControlFlow, EventLoopWindowTarget as RootELW, EventLoopClosed}; -use icon::Icon; -use monitor::{MonitorHandle as RootMH}; -use window::{CreationError, MouseCursor, WindowId as RootWI, WindowAttributes}; -use stdweb::{ - JsSerialize, - traits::*, - unstable::TryInto, - web::{ - document, - event::*, - html_element::CanvasElement, - }, -}; -use std::cell::{RefCell, RefMut}; -use std::collections::VecDeque; -use std::collections::vec_deque::IntoIter as VecDequeIter; -use std::marker::PhantomData; -use std::rc::Rc; +mod events; +mod input_binds; +mod window; + +pub use self::events::{DeviceId, EventLoop, EventLoopWindowTarget, EventLoopProxy}; +pub use self::window::{MonitorHandle, Window, WindowId, PlatformSpecificWindowBuilderAttributes}; +pub use self::input_binds::{button_mapping, mouse_modifiers_state, mouse_button, keyboard_modifiers_state, scancode}; + // TODO: dpi // TODO: close events (stdweb PR required) @@ -26,667 +13,3 @@ use std::rc::Rc; // TODO: mouse wheel events (stdweb PR required) // TODO: key event: .which() (stdweb PR) // TODO: should there be a maximization / fullscreen API? - -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct DeviceId(i32); - -impl DeviceId { - pub unsafe fn dummy() -> Self { - DeviceId(0) - } -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct MonitorHandle; - -impl MonitorHandle { - pub fn get_hidpi_factor(&self) -> f64 { - // TODO - 1.0 - } - - pub fn get_position(&self) -> PhysicalPosition { - unimplemented!(); - } - - pub fn get_dimensions(&self) -> PhysicalSize { - unimplemented!(); - } - - pub fn get_name(&self) -> Option { - unimplemented!(); - } -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct WindowId; - -impl WindowId { - pub unsafe fn dummy() -> WindowId { - WindowId - } -} - -pub struct Window { - canvas: CanvasElement, -} - -impl Window { - pub fn new(target: &EventLoopWindowTarget, attr: WindowAttributes, - _: PlatformSpecificWindowBuilderAttributes) -> Result { - let element = document() - .create_element("canvas") - .map_err(|_| CreationError::OsError("Failed to create canvas element".to_owned()))?; - let canvas: CanvasElement = element.try_into() - .map_err(|_| CreationError::OsError("Failed to create canvas element".to_owned()))?; - document().body() - .ok_or_else(|| CreationError::OsError("Failed to find body node".to_owned()))? - .append_child(&canvas); - let window = Window { canvas }; - if let Some(dimensions) = attr.dimensions { - window.set_inner_size(dimensions); - } else { - window.set_inner_size(LogicalSize { - width: 1024.0, - height: 768.0, - }) - } - // TODO: most of these are no-op, but should they stay here just in case? - window.set_min_dimensions(attr.min_dimensions); - window.set_max_dimensions(attr.max_dimensions); - window.set_resizable(attr.resizable); - window.set_title(&attr.title); - window.set_maximized(attr.maximized); - if attr.visible { - window.show(); - } else { - window.hide(); - } - //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); - target.register_window(&window); - Ok(window) - } - - pub fn set_title(&self, title: &str) { - document().set_title(title); - } - - pub fn show(&self) { - // Intentionally a no-op - } - - pub fn hide(&self) { - // Intentionally a no-op - } - - pub fn request_redraw(&self) { - // TODO: what does this mean? If it's a 'present'-style call then it's not necessary - } - - pub fn get_position(&self) -> Option { - let bounds = self.canvas.get_bounding_client_rect(); - Some(LogicalPosition { - x: bounds.get_x(), - y: bounds.get_y(), - }) - } - - pub fn get_inner_position(&self) -> Option { - self.get_inner_position() - } - - pub fn set_position(&self, position: LogicalPosition) { - // TODO: use CSS? - } - - #[inline] - pub fn get_inner_size(&self) -> Option { - Some(LogicalSize { - width: self.canvas.width() as f64, - height: self.canvas.height() as f64 - }) - } - - #[inline] - pub fn get_outer_size(&self) -> Option { - Some(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_dimensions(&self, _dimensions: Option) { - // Intentionally a no-op: users can't resize canvas elements - } - - #[inline] - pub fn set_max_dimensions(&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 get_hidpi_factor(&self) -> f64 { - // TODO - 1.0 - } - - #[inline] - pub fn set_cursor(&self, cursor: MouseCursor) { - let text = match cursor { - MouseCursor::Default => "auto", - MouseCursor::Crosshair => "crosshair", - MouseCursor::Hand => "pointer", - MouseCursor::Arrow => "default", - MouseCursor::Move => "move", - MouseCursor::Text => "text", - MouseCursor::Wait => "wait", - MouseCursor::Help => "help", - MouseCursor::Progress => "progress", - - MouseCursor::NotAllowed => "not-allowed", - MouseCursor::ContextMenu => "context-menu", - MouseCursor::Cell => "cell", - MouseCursor::VerticalText => "vertical-text", - MouseCursor::Alias => "alias", - MouseCursor::Copy => "copy", - MouseCursor::NoDrop => "no-drop", - MouseCursor::Grab => "grab", - MouseCursor::Grabbing => "grabbing", - MouseCursor::AllScroll => "all-scroll", - MouseCursor::ZoomIn => "zoom-in", - MouseCursor::ZoomOut => "zoom-out", - - MouseCursor::EResize => "e-resize", - MouseCursor::NResize => "n-resize", - MouseCursor::NeResize => "ne-resize", - MouseCursor::NwResize => "nw-resize", - MouseCursor::SResize => "s-resize", - MouseCursor::SeResize => "se-resize", - MouseCursor::SwResize => "sw-resize", - MouseCursor::WResize => "w-resize", - MouseCursor::EwResize => "ew-resize", - MouseCursor::NsResize => "ns-resize", - MouseCursor::NeswResize => "nesw-resize", - MouseCursor::NwseResize => "nwse-resize", - MouseCursor::ColResize => "col-resize", - MouseCursor::RowResize => "row-resize", - }; - self.canvas.set_attribute("cursor", text) - .expect("Setting the cursor on the canvas"); - } - - #[inline] - pub fn set_cursor_position(&self, position: LogicalPosition) -> Result<(), String> { - // TODO: pointer capture - Ok(()) - } - - #[inline] - pub fn grab_cursor(&self, grab: bool) -> Result<(), String> { - // TODO: pointer capture - Ok(()) - } - - #[inline] - pub fn hide_cursor(&self, hide: bool) { - self.canvas.set_attribute("cursor", "none") - .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 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) { - // TODO: should this set the favicon? - } - - #[inline] - pub fn set_ime_spot(&self, position: LogicalPosition) { - // TODO: what is this? - } - - #[inline] - pub fn get_current_monitor(&self) -> RootMH { - RootMH { - inner: MonitorHandle - } - } - - #[inline] - pub fn get_available_monitors(&self) -> VecDequeIter { - VecDeque::new().into_iter() - } - - #[inline] - pub fn get_primary_monitor(&self) -> MonitorHandle { - MonitorHandle - } - - #[inline] - pub fn id(&self) -> WindowId { - // TODO ? - unsafe { WindowId::dummy() } - } -} - -pub struct EventLoop { - elw: RootELW, -} - -#[derive(Clone)] -struct EventLoopData { - events: VecDeque>, - control: ControlFlow, -} - -pub struct EventLoopWindowTarget { - data: Rc>>, -} - -impl EventLoop { - pub fn new() -> Self { - EventLoop { - elw: RootELW { - p: EventLoopWindowTarget { - data: Rc::new(RefCell::new(EventLoopData { - events: VecDeque::new(), - control: ControlFlow::Poll - })) - }, - _marker: PhantomData - } - } - } - - pub fn get_available_monitors(&self) -> VecDequeIter { - VecDeque::new().into_iter() - } - - pub fn get_primary_monitor(&self) -> MonitorHandle { - MonitorHandle - } - - pub fn run(mut self, event_handler: F) - where F: 'static + FnMut(Event, &RootELW, &mut ControlFlow) - { - // TODO: Create event handlers for the JS events - // TODO: how to handle request redraw? - // TODO: onclose (stdweb PR) - // TODO: file dropping, PathBuf isn't useful for web - - let document = &document(); - self.elw.p.add_event(document, |mut data, event: BlurEvent| { - }); - self.elw.p.add_event(document, |mut data, event: FocusEvent| { - }); - - stdweb::event_loop(); // TODO: this is only necessary for stdweb emscripten, should it be here? - } - - pub fn create_proxy(&self) -> EventLoopProxy { - EventLoopProxy { - data: self.elw.p.data.clone() - } - } - - pub fn window_target(&self) -> &RootELW { - &self.elw - } -} - -impl EventLoopWindowTarget { - fn register_window(&self, other: &Window) { - let canvas = &other.canvas; - - self.add_event(canvas, |mut data, 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) { - data.events.push_back(Event::WindowEvent { - window_id: RootWI(WindowId), - event: WindowEvent::ReceivedCharacter(key) - }); - } - data.events.push_back(Event::WindowEvent { - window_id: RootWI(WindowId), - event: WindowEvent::KeyboardInput { - // TODO: is there a way to get keyboard device? - device_id: RootDI(unsafe { DeviceId::dummy() }), - input: KeyboardInput { - scancode: scancode(&event), - state: ElementState::Pressed, - virtual_keycode: button_mapping(&event), - modifiers: keyboard_modifiers_state(&event), - } - } - }); - }); - self.add_event(canvas, |mut data, event: KeyUpEvent| { - data.events.push_back(Event::WindowEvent { - window_id: RootWI(WindowId), - event: WindowEvent::KeyboardInput { - // TODO: is there a way to get keyboard device? - device_id: RootDI(unsafe { DeviceId::dummy() }), - input: KeyboardInput { - scancode: scancode(&event), - state: ElementState::Released, - virtual_keycode: button_mapping(&event), - modifiers: keyboard_modifiers_state(&event), - } - } - }); - }); - self.add_event(canvas, |mut data, event: PointerOutEvent| { - data.events.push_back(Event::WindowEvent { - window_id: RootWI(WindowId), - event: WindowEvent::CursorLeft { - device_id: RootDI(DeviceId(event.pointer_id())) - } - }); - }); - self.add_event(canvas, |mut data, event: PointerOverEvent| { - data.events.push_back(Event::WindowEvent { - window_id: RootWI(WindowId), - event: WindowEvent::CursorEntered { - device_id: RootDI(DeviceId(event.pointer_id())) - } - }); - }); - self.add_event(canvas, |mut data, event: PointerMoveEvent| { - data.events.push_back(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) - } - }); - }); - self.add_event(canvas, |mut data, event: PointerUpEvent| { - data.events.push_back(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) - } - }); - }); - self.add_event(canvas, |mut data, event: PointerDownEvent| { - data.events.push_back(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) - } - }); - }); - } - - - fn add_event(&self, target: &impl IEventTarget, mut handler: F) - where E: ConcreteEvent, F: FnMut(RefMut>, E) + 'static { - let data = self.data.clone(); - - target.add_event_listener(move |event: E| { - event.prevent_default(); - event.stop_propagation(); - event.cancel_bubble(); - - handler(data.borrow_mut(), event); - }); - } -} - -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(), - } -} - -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), - } -} - -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(), - } -} - -fn scancode(event: &T) -> ScanCode { - let which = js! ( return @{event}.which(); ); - which.try_into().expect("The which value should be a number") -} - -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 - }) -} - -#[derive(Clone)] -pub struct EventLoopProxy { - data: Rc>> -} - -impl EventLoopProxy { - pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> { - self.data.borrow_mut().events.push_back(Event::UserEvent(event)); - Ok(()) - } -} - -#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct PlatformSpecificWindowBuilderAttributes; - diff --git a/src/platform_impl/stdweb/window.rs b/src/platform_impl/stdweb/window.rs new file mode 100644 index 00000000..11461041 --- /dev/null +++ b/src/platform_impl/stdweb/window.rs @@ -0,0 +1,285 @@ +use dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize}; +use icon::Icon; +use monitor::{MonitorHandle as RootMH}; +use window::{CreationError, MouseCursor, WindowAttributes}; +use super::EventLoopWindowTarget; +use std::collections::VecDeque; +use std::collections::vec_deque::IntoIter as VecDequeIter; +use stdweb::{ + traits::*, + unstable::TryInto +}; +use stdweb::web::{ + document, + html_element::CanvasElement, +}; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct MonitorHandle; + +impl MonitorHandle { + pub fn get_hidpi_factor(&self) -> f64 { + // TODO + 1.0 + } + + pub fn get_position(&self) -> PhysicalPosition { + unimplemented!(); + } + + pub fn get_dimensions(&self) -> PhysicalSize { + unimplemented!(); + } + + pub fn get_name(&self) -> Option { + unimplemented!(); + } +} + +#[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, +} + +impl Window { + pub fn new(target: &EventLoopWindowTarget, attr: WindowAttributes, + _: PlatformSpecificWindowBuilderAttributes) -> Result { + let element = document() + .create_element("canvas") + .map_err(|_| CreationError::OsError("Failed to create canvas element".to_owned()))?; + let canvas: CanvasElement = element.try_into() + .map_err(|_| CreationError::OsError("Failed to create canvas element".to_owned()))?; + document().body() + .ok_or_else(|| CreationError::OsError("Failed to find body node".to_owned()))? + .append_child(&canvas); + let window = Window { canvas }; + if let Some(dimensions) = attr.dimensions { + window.set_inner_size(dimensions); + } else { + window.set_inner_size(LogicalSize { + width: 1024.0, + height: 768.0, + }) + } + // TODO: most of these are no-op, but should they stay here just in case? + window.set_min_dimensions(attr.min_dimensions); + window.set_max_dimensions(attr.max_dimensions); + window.set_resizable(attr.resizable); + window.set_title(&attr.title); + window.set_maximized(attr.maximized); + if attr.visible { + window.show(); + } else { + window.hide(); + } + //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); + target.register_window(&window); + Ok(window) + } + + pub fn set_title(&self, title: &str) { + document().set_title(title); + } + + pub fn show(&self) { + // Intentionally a no-op + } + + pub fn hide(&self) { + // Intentionally a no-op + } + + pub fn request_redraw(&self) { + // TODO: what does this mean? If it's a 'present'-style call then it's not necessary + } + + pub fn get_position(&self) -> Option { + let bounds = self.canvas.get_bounding_client_rect(); + Some(LogicalPosition { + x: bounds.get_x(), + y: bounds.get_y(), + }) + } + + pub fn get_inner_position(&self) -> Option { + // TODO + None + } + + pub fn set_position(&self, position: LogicalPosition) { + // TODO: use CSS? + } + + #[inline] + pub fn get_inner_size(&self) -> Option { + Some(LogicalSize { + width: self.canvas.width() as f64, + height: self.canvas.height() as f64 + }) + } + + #[inline] + pub fn get_outer_size(&self) -> Option { + Some(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_dimensions(&self, _dimensions: Option) { + // Intentionally a no-op: users can't resize canvas elements + } + + #[inline] + pub fn set_max_dimensions(&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 get_hidpi_factor(&self) -> f64 { + // TODO + 1.0 + } + + #[inline] + pub fn set_cursor(&self, cursor: MouseCursor) { + let text = match cursor { + MouseCursor::Default => "auto", + MouseCursor::Crosshair => "crosshair", + MouseCursor::Hand => "pointer", + MouseCursor::Arrow => "default", + MouseCursor::Move => "move", + MouseCursor::Text => "text", + MouseCursor::Wait => "wait", + MouseCursor::Help => "help", + MouseCursor::Progress => "progress", + + MouseCursor::NotAllowed => "not-allowed", + MouseCursor::ContextMenu => "context-menu", + MouseCursor::Cell => "cell", + MouseCursor::VerticalText => "vertical-text", + MouseCursor::Alias => "alias", + MouseCursor::Copy => "copy", + MouseCursor::NoDrop => "no-drop", + MouseCursor::Grab => "grab", + MouseCursor::Grabbing => "grabbing", + MouseCursor::AllScroll => "all-scroll", + MouseCursor::ZoomIn => "zoom-in", + MouseCursor::ZoomOut => "zoom-out", + + MouseCursor::EResize => "e-resize", + MouseCursor::NResize => "n-resize", + MouseCursor::NeResize => "ne-resize", + MouseCursor::NwResize => "nw-resize", + MouseCursor::SResize => "s-resize", + MouseCursor::SeResize => "se-resize", + MouseCursor::SwResize => "sw-resize", + MouseCursor::WResize => "w-resize", + MouseCursor::EwResize => "ew-resize", + MouseCursor::NsResize => "ns-resize", + MouseCursor::NeswResize => "nesw-resize", + MouseCursor::NwseResize => "nwse-resize", + MouseCursor::ColResize => "col-resize", + MouseCursor::RowResize => "row-resize", + }; + self.canvas.set_attribute("cursor", text) + .expect("Setting the cursor on the canvas"); + } + + #[inline] + pub fn set_cursor_position(&self, position: LogicalPosition) -> Result<(), String> { + // TODO: pointer capture + Ok(()) + } + + #[inline] + pub fn grab_cursor(&self, grab: bool) -> Result<(), String> { + // TODO: pointer capture + Ok(()) + } + + #[inline] + pub fn hide_cursor(&self, hide: bool) { + self.canvas.set_attribute("cursor", "none") + .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 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) { + // TODO: should this set the favicon? + } + + #[inline] + pub fn set_ime_spot(&self, position: LogicalPosition) { + // TODO: what is this? + } + + #[inline] + pub fn get_current_monitor(&self) -> RootMH { + RootMH { + inner: MonitorHandle + } + } + + #[inline] + pub fn get_available_monitors(&self) -> VecDequeIter { + VecDeque::new().into_iter() + } + + #[inline] + pub fn get_primary_monitor(&self) -> MonitorHandle { + MonitorHandle + } + + #[inline] + pub fn id(&self) -> WindowId { + // TODO ? + unsafe { WindowId::dummy() } + } +} From aaee72422abdab80b016d1c9331b1c3bc5ee6420 Mon Sep 17 00:00:00 2001 From: Ryan Goldstein Date: Mon, 11 Mar 2019 22:18:58 -0400 Subject: [PATCH 07/69] Rearchitect to allow API compliance --- src/event_loop.rs | 2 +- src/platform_impl/stdweb/events.rs | 271 ++++++++++++++---------- src/platform_impl/stdweb/input_binds.rs | 2 +- src/platform_impl/stdweb/window.rs | 2 +- 4 files changed, 163 insertions(+), 114 deletions(-) diff --git a/src/event_loop.rs b/src/event_loop.rs index c1a85a54..ab6839cd 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -139,7 +139,7 @@ impl EventLoop { /// /// [`ControlFlow`]: ./enum.ControlFlow.html #[inline] - pub fn run(self, event_handler: F) // TODO: this needs to be ! + pub fn run(self, event_handler: F) -> ! where F: 'static + FnMut(Event, &EventLoopWindowTarget, &mut ControlFlow) { self.event_loop.run(event_handler) diff --git a/src/platform_impl/stdweb/events.rs b/src/platform_impl/stdweb/events.rs index d3b39d9e..06b8c32b 100644 --- a/src/platform_impl/stdweb/events.rs +++ b/src/platform_impl/stdweb/events.rs @@ -32,30 +32,55 @@ impl DeviceId { pub struct EventLoop { elw: RootELW, -} - -#[derive(Clone)] -struct EventLoopData { - events: VecDeque>, - control: ControlFlow, + runner: EventLoopRunnerShared } pub struct EventLoopWindowTarget { - data: Rc>>, + pub(crate) canvases: RefCell>, + _marker: PhantomData +} + +impl EventLoopWindowTarget { + fn new() -> Self { + EventLoopWindowTarget { + canvases: RefCell::new(Vec::new()), + _marker: PhantomData + } + } +} + +#[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(()) + } +} + +type EventLoopRunnerShared = Rc>; + +struct ELRShared { + runner: RefCell>>, + events: RefCell>>, // TODO: this may not be necessary? +} + +struct EventLoopRunner { + control: ControlFlow, + event_handler: Box, &mut ControlFlow)>, } impl EventLoop { pub fn new() -> Self { EventLoop { elw: RootELW { - p: EventLoopWindowTarget { - data: Rc::new(RefCell::new(EventLoopData { - events: VecDeque::new(), - control: ControlFlow::Poll - })) - }, + p: EventLoopWindowTarget::new(), _marker: PhantomData - } + }, + runner: Rc::new(ELRShared::blank()), } } @@ -67,50 +92,41 @@ impl EventLoop { MonitorHandle } - pub fn run(mut self, event_handler: F) + pub fn run(mut self, mut event_handler: F) -> ! where F: 'static + FnMut(Event, &RootELW, &mut ControlFlow) { // TODO: Create event handlers for the JS events // TODO: how to handle request redraw? // TODO: onclose (stdweb PR) // TODO: file dropping, PathBuf isn't useful for web + let EventLoop { elw, runner } = self; + for canvas in elw.p.canvases.borrow().iter() { + register(&runner, canvas); + } + let relw = RootELW { + p: EventLoopWindowTarget::new(), + _marker: PhantomData + }; + runner.set_listener(Box::new(move |evt, ctrl| event_handler(evt, &relw, ctrl))); let document = &document(); - self.elw.p.add_event(document, |mut data, event: BlurEvent| { + add_event(&runner, document, |_, _: BlurEvent| { }); - self.elw.p.add_event(document, |mut data, event: FocusEvent| { + add_event(&runner, document, |_, _: FocusEvent| { + }); - - stdweb::event_loop(); // TODO: this is only necessary for stdweb emscripten, should it be here? - } - - pub fn create_proxy(&self) -> EventLoopProxy { - EventLoopProxy { - data: self.elw.p.data.clone() - } - } - - pub fn window_target(&self) -> &RootELW { - &self.elw - } -} - -impl EventLoopWindowTarget { - pub fn register_window(&self, other: &Window) { - let canvas = &other.canvas; - - self.add_event(canvas, |mut data, event: KeyDownEvent| { + 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) { - data.events.push_back(Event::WindowEvent { + elrs.send_event(Event::WindowEvent { window_id: RootWI(WindowId), event: WindowEvent::ReceivedCharacter(key) }); } - data.events.push_back(Event::WindowEvent { + elrs.send_event(Event::WindowEvent { window_id: RootWI(WindowId), event: WindowEvent::KeyboardInput { // TODO: is there a way to get keyboard device? @@ -124,8 +140,8 @@ impl EventLoopWindowTarget { } }); }); - self.add_event(canvas, |mut data, event: KeyUpEvent| { - data.events.push_back(Event::WindowEvent { + add_event(&runner, document, |elrs, event: KeyUpEvent| { + elrs.send_event(Event::WindowEvent { window_id: RootWI(WindowId), event: WindowEvent::KeyboardInput { // TODO: is there a way to get keyboard device? @@ -139,84 +155,117 @@ impl EventLoopWindowTarget { } }); }); - self.add_event(canvas, |mut data, event: PointerOutEvent| { - data.events.push_back(Event::WindowEvent { - window_id: RootWI(WindowId), - event: WindowEvent::CursorLeft { - device_id: RootDI(DeviceId(event.pointer_id())) - } - }); - }); - self.add_event(canvas, |mut data, event: PointerOverEvent| { - data.events.push_back(Event::WindowEvent { - window_id: RootWI(WindowId), - event: WindowEvent::CursorEntered { - device_id: RootDI(DeviceId(event.pointer_id())) - } - }); - }); - self.add_event(canvas, |mut data, event: PointerMoveEvent| { - data.events.push_back(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) - } - }); - }); - self.add_event(canvas, |mut data, event: PointerUpEvent| { - data.events.push_back(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) - } - }); - }); - self.add_event(canvas, |mut data, event: PointerDownEvent| { - data.events.push_back(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) - } - }); - }); + stdweb::event_loop(); // TODO: this is only necessary for stdweb emscripten, should it be here? + js! { + throw "Using exceptions for control flow, don't mind me"; + } + unreachable!(); } + pub fn create_proxy(&self) -> EventLoopProxy { + EventLoopProxy { + runner: self.runner.clone() + } + } - fn add_event(&self, target: &impl IEventTarget, mut handler: F) - where E: ConcreteEvent, F: FnMut(RefMut>, E) + 'static { - let data = self.data.clone(); - - target.add_event_listener(move |event: E| { - event.prevent_default(); - event.stop_propagation(); - event.cancel_bubble(); - - handler(data.borrow_mut(), event); - }); + pub fn window_target(&self) -> &RootELW { + &self.elw } } -#[derive(Clone)] -pub struct EventLoopProxy { - data: Rc>> +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) + } + }); + }); } -impl EventLoopProxy { - pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> { - self.data.borrow_mut().events.push_back(Event::UserEvent(event)); - Ok(()) +fn add_event(elrs: &EventLoopRunnerShared, target: &impl IEventTarget, mut handler: F) + where E: ConcreteEvent, F: FnMut(&EventLoopRunnerShared, E) + 'static { + let elrs = elrs.clone(); // TODO: necessary? + + target.add_event_listener(move |event: E| { + event.prevent_default(); + event.stop_propagation(); + event.cancel_bubble(); + + handler(&elrs, event); + }); +} + +impl ELRShared { + fn blank() -> ELRShared { + ELRShared { + runner: RefCell::new(None), + events: RefCell::new(VecDeque::new()) + } } + + fn set_listener(&self, event_handler: Box, &mut ControlFlow)>) { + *self.runner.borrow_mut() = Some(EventLoopRunner { + control: ControlFlow::Poll, + event_handler + }); + } + + // TODO: handle event loop closures + // TODO: handle event buffer + fn send_event(&self, event: Event) { + match *self.runner.borrow_mut() { + Some(ref mut runner) => { + // TODO: bracket this in control flow events? + (runner.event_handler)(event, &mut runner.control); + } + None => () + } + } + } - diff --git a/src/platform_impl/stdweb/input_binds.rs b/src/platform_impl/stdweb/input_binds.rs index 282c6664..7068fba0 100644 --- a/src/platform_impl/stdweb/input_binds.rs +++ b/src/platform_impl/stdweb/input_binds.rs @@ -197,6 +197,6 @@ pub fn keyboard_modifiers_state(event: &impl IKeyboardEvent) -> ModifiersState { } pub fn scancode(event: &T) -> ScanCode { - let which = js! ( return @{event}.which(); ); + let which = js! ( return @{event}.which; ); which.try_into().expect("The which value should be a number") } diff --git a/src/platform_impl/stdweb/window.rs b/src/platform_impl/stdweb/window.rs index 11461041..fd6d6f57 100644 --- a/src/platform_impl/stdweb/window.rs +++ b/src/platform_impl/stdweb/window.rs @@ -63,6 +63,7 @@ impl Window { document().body() .ok_or_else(|| CreationError::OsError("Failed to find body node".to_owned()))? .append_child(&canvas); + target.canvases.borrow_mut().push(canvas.clone()); let window = Window { canvas }; if let Some(dimensions) = attr.dimensions { window.set_inner_size(dimensions); @@ -87,7 +88,6 @@ impl Window { window.set_decorations(attr.decorations); window.set_always_on_top(attr.always_on_top); window.set_window_icon(attr.window_icon); - target.register_window(&window); Ok(window) } From d1deba8620127608beeb80d8e8ee4ec7e3e5e733 Mon Sep 17 00:00:00 2001 From: Ryan Goldstein Date: Mon, 11 Mar 2019 22:22:21 -0400 Subject: [PATCH 08/69] Rename modules --- src/platform_impl/stdweb/event_loop.rs | 271 +++++++++++++++ src/platform_impl/stdweb/events.rs | 443 ++++++++++-------------- src/platform_impl/stdweb/input_binds.rs | 202 ----------- src/platform_impl/stdweb/mod.rs | 6 +- 4 files changed, 461 insertions(+), 461 deletions(-) create mode 100644 src/platform_impl/stdweb/event_loop.rs delete mode 100644 src/platform_impl/stdweb/input_binds.rs diff --git a/src/platform_impl/stdweb/event_loop.rs b/src/platform_impl/stdweb/event_loop.rs new file mode 100644 index 00000000..06b8c32b --- /dev/null +++ b/src/platform_impl/stdweb/event_loop.rs @@ -0,0 +1,271 @@ +use super::*; + +use dpi::{LogicalPosition, LogicalSize}; +use event::{DeviceEvent, DeviceId as RootDI, ElementState, Event, KeyboardInput, ModifiersState, MouseButton, ScanCode, StartCause, VirtualKeyCode, WindowEvent}; +use event_loop::{ControlFlow, EventLoopWindowTarget as RootELW, EventLoopClosed}; +use icon::Icon; +use window::{MouseCursor, WindowId as RootWI}; +use stdweb::{ + JsSerialize, + traits::*, + unstable::TryInto, + web::{ + document, + event::*, + html_element::CanvasElement, + }, +}; +use std::cell::{RefCell, RefMut}; +use std::collections::VecDeque; +use std::collections::vec_deque::IntoIter as VecDequeIter; +use std::marker::PhantomData; +use std::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, + runner: EventLoopRunnerShared +} + +pub struct EventLoopWindowTarget { + pub(crate) canvases: RefCell>, + _marker: PhantomData +} + +impl EventLoopWindowTarget { + fn new() -> Self { + EventLoopWindowTarget { + canvases: RefCell::new(Vec::new()), + _marker: PhantomData + } + } +} + +#[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(()) + } +} + +type EventLoopRunnerShared = Rc>; + +struct ELRShared { + runner: RefCell>>, + events: RefCell>>, // TODO: this may not be necessary? +} + +struct EventLoopRunner { + control: ControlFlow, + event_handler: Box, &mut ControlFlow)>, +} + +impl EventLoop { + pub fn new() -> Self { + EventLoop { + elw: RootELW { + p: EventLoopWindowTarget::new(), + _marker: PhantomData + }, + runner: Rc::new(ELRShared::blank()), + } + } + + pub fn get_available_monitors(&self) -> VecDequeIter { + VecDeque::new().into_iter() + } + + pub fn get_primary_monitor(&self) -> MonitorHandle { + MonitorHandle + } + + pub fn run(mut self, mut event_handler: F) -> ! + where F: 'static + FnMut(Event, &RootELW, &mut ControlFlow) + { + // TODO: Create event handlers for the JS events + // TODO: how to handle request redraw? + // TODO: onclose (stdweb PR) + // TODO: file dropping, PathBuf isn't useful for web + let EventLoop { elw, runner } = self; + for canvas in elw.p.canvases.borrow().iter() { + register(&runner, canvas); + } + 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, |_, _: BlurEvent| { + }); + add_event(&runner, document, |_, _: FocusEvent| { + + }); + 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 { + // TODO: is there a way to get keyboard device? + 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 { + // TODO: is there a way to get keyboard device? + device_id: RootDI(unsafe { DeviceId::dummy() }), + input: KeyboardInput { + scancode: scancode(&event), + state: ElementState::Released, + virtual_keycode: button_mapping(&event), + modifiers: keyboard_modifiers_state(&event), + } + } + }); + }); + stdweb::event_loop(); // TODO: this is only necessary for stdweb emscripten, should it be here? + js! { + throw "Using exceptions for control flow, don't mind me"; + } + unreachable!(); + } + + pub fn create_proxy(&self) -> EventLoopProxy { + EventLoopProxy { + runner: self.runner.clone() + } + } + + pub fn window_target(&self) -> &RootELW { + &self.elw + } +} + +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) + } + }); + }); +} + +fn add_event(elrs: &EventLoopRunnerShared, target: &impl IEventTarget, mut handler: F) + where E: ConcreteEvent, F: FnMut(&EventLoopRunnerShared, E) + 'static { + let elrs = elrs.clone(); // TODO: necessary? + + target.add_event_listener(move |event: E| { + event.prevent_default(); + event.stop_propagation(); + event.cancel_bubble(); + + handler(&elrs, event); + }); +} + +impl ELRShared { + fn blank() -> ELRShared { + ELRShared { + runner: RefCell::new(None), + events: RefCell::new(VecDeque::new()) + } + } + + fn set_listener(&self, event_handler: Box, &mut ControlFlow)>) { + *self.runner.borrow_mut() = Some(EventLoopRunner { + control: ControlFlow::Poll, + event_handler + }); + } + + // TODO: handle event loop closures + // TODO: handle event buffer + fn send_event(&self, event: Event) { + match *self.runner.borrow_mut() { + Some(ref mut runner) => { + // TODO: bracket this in control flow events? + (runner.event_handler)(event, &mut runner.control); + } + None => () + } + } + +} + diff --git a/src/platform_impl/stdweb/events.rs b/src/platform_impl/stdweb/events.rs index 06b8c32b..7068fba0 100644 --- a/src/platform_impl/stdweb/events.rs +++ b/src/platform_impl/stdweb/events.rs @@ -1,271 +1,202 @@ -use super::*; - -use dpi::{LogicalPosition, LogicalSize}; -use event::{DeviceEvent, DeviceId as RootDI, ElementState, Event, KeyboardInput, ModifiersState, MouseButton, ScanCode, StartCause, VirtualKeyCode, WindowEvent}; -use event_loop::{ControlFlow, EventLoopWindowTarget as RootELW, EventLoopClosed}; -use icon::Icon; -use window::{MouseCursor, WindowId as RootWI}; use stdweb::{ JsSerialize, - traits::*, - unstable::TryInto, - web::{ - document, - event::*, - html_element::CanvasElement, - }, + web::event::{IKeyboardEvent, IMouseEvent}, + unstable::TryInto }; -use std::cell::{RefCell, RefMut}; -use std::collections::VecDeque; -use std::collections::vec_deque::IntoIter as VecDequeIter; -use std::marker::PhantomData; -use std::rc::Rc; +use event::{MouseButton, ModifiersState, ScanCode, VirtualKeyCode}; -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct DeviceId(i32); +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 + }) +} -impl DeviceId { - pub unsafe fn dummy() -> Self { - DeviceId(0) +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 struct EventLoop { - elw: RootELW, - runner: EventLoopRunnerShared -} - -pub struct EventLoopWindowTarget { - pub(crate) canvases: RefCell>, - _marker: PhantomData -} - -impl EventLoopWindowTarget { - fn new() -> Self { - EventLoopWindowTarget { - canvases: RefCell::new(Vec::new()), - _marker: PhantomData - } +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), } } -#[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 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(), } } -type EventLoopRunnerShared = Rc>; - -struct ELRShared { - runner: RefCell>>, - events: RefCell>>, // TODO: this may not be necessary? +pub fn scancode(event: &T) -> ScanCode { + let which = js! ( return @{event}.which; ); + which.try_into().expect("The which value should be a number") } - -struct EventLoopRunner { - control: ControlFlow, - event_handler: Box, &mut ControlFlow)>, -} - -impl EventLoop { - pub fn new() -> Self { - EventLoop { - elw: RootELW { - p: EventLoopWindowTarget::new(), - _marker: PhantomData - }, - runner: Rc::new(ELRShared::blank()), - } - } - - pub fn get_available_monitors(&self) -> VecDequeIter { - VecDeque::new().into_iter() - } - - pub fn get_primary_monitor(&self) -> MonitorHandle { - MonitorHandle - } - - pub fn run(mut self, mut event_handler: F) -> ! - where F: 'static + FnMut(Event, &RootELW, &mut ControlFlow) - { - // TODO: Create event handlers for the JS events - // TODO: how to handle request redraw? - // TODO: onclose (stdweb PR) - // TODO: file dropping, PathBuf isn't useful for web - let EventLoop { elw, runner } = self; - for canvas in elw.p.canvases.borrow().iter() { - register(&runner, canvas); - } - 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, |_, _: BlurEvent| { - }); - add_event(&runner, document, |_, _: FocusEvent| { - - }); - 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 { - // TODO: is there a way to get keyboard device? - 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 { - // TODO: is there a way to get keyboard device? - device_id: RootDI(unsafe { DeviceId::dummy() }), - input: KeyboardInput { - scancode: scancode(&event), - state: ElementState::Released, - virtual_keycode: button_mapping(&event), - modifiers: keyboard_modifiers_state(&event), - } - } - }); - }); - stdweb::event_loop(); // TODO: this is only necessary for stdweb emscripten, should it be here? - js! { - throw "Using exceptions for control flow, don't mind me"; - } - unreachable!(); - } - - pub fn create_proxy(&self) -> EventLoopProxy { - EventLoopProxy { - runner: self.runner.clone() - } - } - - pub fn window_target(&self) -> &RootELW { - &self.elw - } -} - -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) - } - }); - }); -} - -fn add_event(elrs: &EventLoopRunnerShared, target: &impl IEventTarget, mut handler: F) - where E: ConcreteEvent, F: FnMut(&EventLoopRunnerShared, E) + 'static { - let elrs = elrs.clone(); // TODO: necessary? - - target.add_event_listener(move |event: E| { - event.prevent_default(); - event.stop_propagation(); - event.cancel_bubble(); - - handler(&elrs, event); - }); -} - -impl ELRShared { - fn blank() -> ELRShared { - ELRShared { - runner: RefCell::new(None), - events: RefCell::new(VecDeque::new()) - } - } - - fn set_listener(&self, event_handler: Box, &mut ControlFlow)>) { - *self.runner.borrow_mut() = Some(EventLoopRunner { - control: ControlFlow::Poll, - event_handler - }); - } - - // TODO: handle event loop closures - // TODO: handle event buffer - fn send_event(&self, event: Event) { - match *self.runner.borrow_mut() { - Some(ref mut runner) => { - // TODO: bracket this in control flow events? - (runner.event_handler)(event, &mut runner.control); - } - None => () - } - } - -} - diff --git a/src/platform_impl/stdweb/input_binds.rs b/src/platform_impl/stdweb/input_binds.rs deleted file mode 100644 index 7068fba0..00000000 --- a/src/platform_impl/stdweb/input_binds.rs +++ /dev/null @@ -1,202 +0,0 @@ -use stdweb::{ - JsSerialize, - web::event::{IKeyboardEvent, IMouseEvent}, - unstable::TryInto -}; -use 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/mod.rs b/src/platform_impl/stdweb/mod.rs index c2b87331..9a2aab6b 100644 --- a/src/platform_impl/stdweb/mod.rs +++ b/src/platform_impl/stdweb/mod.rs @@ -1,10 +1,10 @@ +mod event_loop; mod events; -mod input_binds; mod window; -pub use self::events::{DeviceId, EventLoop, EventLoopWindowTarget, EventLoopProxy}; +pub use self::event_loop::{DeviceId, EventLoop, EventLoopWindowTarget, EventLoopProxy}; pub use self::window::{MonitorHandle, Window, WindowId, PlatformSpecificWindowBuilderAttributes}; -pub use self::input_binds::{button_mapping, mouse_modifiers_state, mouse_button, keyboard_modifiers_state, scancode}; +pub use self::events::{button_mapping, mouse_modifiers_state, mouse_button, keyboard_modifiers_state, scancode}; // TODO: dpi From a5166baba21c67a0898db6b7c538f3a0405c324b Mon Sep 17 00:00:00 2001 From: Ryan Goldstein Date: Sat, 16 Mar 2019 18:40:35 -0400 Subject: [PATCH 09/69] Implement request_redraw --- src/platform_impl/stdweb/event_loop.rs | 33 +++++++++++++------------- src/platform_impl/stdweb/mod.rs | 2 +- src/platform_impl/stdweb/window.rs | 25 +++++++++++++++---- 3 files changed, 37 insertions(+), 23 deletions(-) diff --git a/src/platform_impl/stdweb/event_loop.rs b/src/platform_impl/stdweb/event_loop.rs index 06b8c32b..478aa572 100644 --- a/src/platform_impl/stdweb/event_loop.rs +++ b/src/platform_impl/stdweb/event_loop.rs @@ -32,19 +32,19 @@ impl DeviceId { pub struct EventLoop { elw: RootELW, - runner: EventLoopRunnerShared } pub struct EventLoopWindowTarget { - pub(crate) canvases: RefCell>, - _marker: PhantomData + pub(crate) runner: EventLoopRunnerShared, } impl EventLoopWindowTarget { fn new() -> Self { EventLoopWindowTarget { - canvases: RefCell::new(Vec::new()), - _marker: PhantomData + runner: Rc::new(ELRShared { + runner: RefCell::new(None), + events: RefCell::new(VecDeque::new()) + }) } } } @@ -61,9 +61,9 @@ impl EventLoopProxy { } } -type EventLoopRunnerShared = Rc>; +pub type EventLoopRunnerShared = Rc>; -struct ELRShared { +pub struct ELRShared { runner: RefCell>>, events: RefCell>>, // TODO: this may not be necessary? } @@ -80,7 +80,6 @@ impl EventLoop { p: EventLoopWindowTarget::new(), _marker: PhantomData }, - runner: Rc::new(ELRShared::blank()), } } @@ -95,14 +94,11 @@ impl EventLoop { pub fn run(mut self, mut event_handler: F) -> ! where F: 'static + FnMut(Event, &RootELW, &mut ControlFlow) { - // TODO: Create event handlers for the JS events // TODO: how to handle request redraw? // TODO: onclose (stdweb PR) // TODO: file dropping, PathBuf isn't useful for web - let EventLoop { elw, runner } = self; - for canvas in elw.p.canvases.borrow().iter() { - register(&runner, canvas); - } + let runner = self.elw.p.runner; + let relw = RootELW { p: EventLoopWindowTarget::new(), _marker: PhantomData @@ -156,6 +152,9 @@ impl EventLoop { }); }); 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"; } @@ -164,7 +163,7 @@ impl EventLoop { pub fn create_proxy(&self) -> EventLoopProxy { EventLoopProxy { - runner: self.runner.clone() + runner: self.elw.p.runner.clone() } } @@ -173,7 +172,7 @@ impl EventLoop { } } -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), @@ -229,7 +228,7 @@ fn register(elrs: &EventLoopRunnerShared, canvas: &CanvasElement) fn add_event(elrs: &EventLoopRunnerShared, target: &impl IEventTarget, mut handler: F) where E: ConcreteEvent, F: FnMut(&EventLoopRunnerShared, E) + 'static { - let elrs = elrs.clone(); // TODO: necessary? + let elrs = elrs.clone(); target.add_event_listener(move |event: E| { event.prevent_default(); @@ -257,7 +256,7 @@ impl ELRShared { // TODO: handle event loop closures // TODO: handle event buffer - fn send_event(&self, event: Event) { + pub fn send_event(&self, event: Event) { match *self.runner.borrow_mut() { Some(ref mut runner) => { // TODO: bracket this in control flow events? diff --git a/src/platform_impl/stdweb/mod.rs b/src/platform_impl/stdweb/mod.rs index 9a2aab6b..82d8a189 100644 --- a/src/platform_impl/stdweb/mod.rs +++ b/src/platform_impl/stdweb/mod.rs @@ -2,7 +2,7 @@ mod event_loop; mod events; mod window; -pub use self::event_loop::{DeviceId, EventLoop, EventLoopWindowTarget, EventLoopProxy}; +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}; diff --git a/src/platform_impl/stdweb/window.rs b/src/platform_impl/stdweb/window.rs index fd6d6f57..a66bc191 100644 --- a/src/platform_impl/stdweb/window.rs +++ b/src/platform_impl/stdweb/window.rs @@ -1,8 +1,9 @@ use dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize}; +use event::{Event, WindowEvent}; use icon::Icon; use monitor::{MonitorHandle as RootMH}; -use window::{CreationError, MouseCursor, WindowAttributes}; -use super::EventLoopWindowTarget; +use window::{CreationError, MouseCursor, WindowAttributes, WindowId as RootWI}; +use super::{EventLoopWindowTarget, EventLoopRunnerShared, register}; use std::collections::VecDeque; use std::collections::vec_deque::IntoIter as VecDequeIter; use stdweb::{ @@ -50,6 +51,7 @@ impl WindowId { pub struct Window { pub(crate) canvas: CanvasElement, + pub(crate) redraw: Box } impl Window { @@ -63,8 +65,20 @@ impl Window { document().body() .ok_or_else(|| CreationError::OsError("Failed to find body node".to_owned()))? .append_child(&canvas); - target.canvases.borrow_mut().push(canvas.clone()); - let window = Window { canvas }; + + register(&target.runner, &canvas); + + let runner = target.runner.clone(); + let redraw = Box::new(move || runner.send_event(Event::WindowEvent { + window_id: RootWI(WindowId), + event: WindowEvent::RedrawRequested + })); + + let window = Window { + canvas, + redraw + }; + if let Some(dimensions) = attr.dimensions { window.set_inner_size(dimensions); } else { @@ -88,6 +102,7 @@ impl Window { window.set_decorations(attr.decorations); window.set_always_on_top(attr.always_on_top); window.set_window_icon(attr.window_icon); + Ok(window) } @@ -104,7 +119,7 @@ impl Window { } pub fn request_redraw(&self) { - // TODO: what does this mean? If it's a 'present'-style call then it's not necessary + (self.redraw)(); } pub fn get_position(&self) -> Option { From 96786bbb87eae65337b4cbe4cf6a2c85af089b8d Mon Sep 17 00:00:00 2001 From: Ryan Goldstein Date: Sat, 16 Mar 2019 18:44:13 -0400 Subject: [PATCH 10/69] Implement focus event --- src/platform_impl/stdweb/event_loop.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/platform_impl/stdweb/event_loop.rs b/src/platform_impl/stdweb/event_loop.rs index 478aa572..68a5efee 100644 --- a/src/platform_impl/stdweb/event_loop.rs +++ b/src/platform_impl/stdweb/event_loop.rs @@ -106,9 +106,17 @@ impl EventLoop { runner.set_listener(Box::new(move |evt, ctrl| event_handler(evt, &relw, ctrl))); let document = &document(); - add_event(&runner, document, |_, _: BlurEvent| { + add_event(&runner, document, |elrs, _: BlurEvent| { + elrs.send_event(Event::WindowEvent { + window_id: RootWI(WindowId), + event: WindowEvent::Focused(false) + }); }); - add_event(&runner, document, |_, _: FocusEvent| { + 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| { From 85446d81f3f3d4bde288f8ad55b6ccf43a198b06 Mon Sep 17 00:00:00 2001 From: Ryan Goldstein Date: Sat, 16 Mar 2019 18:51:11 -0400 Subject: [PATCH 11/69] Fix warnings --- src/platform_impl/stdweb/event_loop.rs | 20 ++++------------ src/platform_impl/stdweb/window.rs | 33 ++++++++++++++++---------- 2 files changed, 26 insertions(+), 27 deletions(-) diff --git a/src/platform_impl/stdweb/event_loop.rs b/src/platform_impl/stdweb/event_loop.rs index 68a5efee..ea2b9acd 100644 --- a/src/platform_impl/stdweb/event_loop.rs +++ b/src/platform_impl/stdweb/event_loop.rs @@ -1,21 +1,18 @@ use super::*; -use dpi::{LogicalPosition, LogicalSize}; -use event::{DeviceEvent, DeviceId as RootDI, ElementState, Event, KeyboardInput, ModifiersState, MouseButton, ScanCode, StartCause, VirtualKeyCode, WindowEvent}; +use dpi::LogicalPosition; +use event::{DeviceId as RootDI, ElementState, Event, KeyboardInput, StartCause, WindowEvent}; use event_loop::{ControlFlow, EventLoopWindowTarget as RootELW, EventLoopClosed}; -use icon::Icon; -use window::{MouseCursor, WindowId as RootWI}; +use window::{WindowId as RootWI}; use stdweb::{ - JsSerialize, traits::*, - unstable::TryInto, web::{ document, event::*, html_element::CanvasElement, }, }; -use std::cell::{RefCell, RefMut}; +use std::cell::RefCell; use std::collections::VecDeque; use std::collections::vec_deque::IntoIter as VecDequeIter; use std::marker::PhantomData; @@ -91,7 +88,7 @@ impl EventLoop { MonitorHandle } - pub fn run(mut self, mut event_handler: F) -> ! + pub fn run(self, mut event_handler: F) -> ! where F: 'static + FnMut(Event, &RootELW, &mut ControlFlow) { // TODO: how to handle request redraw? @@ -248,13 +245,6 @@ fn add_event(elrs: &EventLoopRunnerShared, target: &impl IE } impl ELRShared { - fn blank() -> ELRShared { - ELRShared { - runner: RefCell::new(None), - events: RefCell::new(VecDeque::new()) - } - } - fn set_listener(&self, event_handler: Box, &mut ControlFlow)>) { *self.runner.borrow_mut() = Some(EventLoopRunner { control: ControlFlow::Poll, diff --git a/src/platform_impl/stdweb/window.rs b/src/platform_impl/stdweb/window.rs index a66bc191..df875f03 100644 --- a/src/platform_impl/stdweb/window.rs +++ b/src/platform_impl/stdweb/window.rs @@ -3,9 +3,10 @@ use event::{Event, WindowEvent}; use icon::Icon; use monitor::{MonitorHandle as RootMH}; use window::{CreationError, MouseCursor, WindowAttributes, WindowId as RootWI}; -use super::{EventLoopWindowTarget, EventLoopRunnerShared, register}; +use super::{EventLoopWindowTarget, register}; use std::collections::VecDeque; use std::collections::vec_deque::IntoIter as VecDequeIter; +use std::cell::RefCell; use stdweb::{ traits::*, unstable::TryInto @@ -51,7 +52,8 @@ impl WindowId { pub struct Window { pub(crate) canvas: CanvasElement, - pub(crate) redraw: Box + pub(crate) redraw: Box, + previous_pointer: RefCell<&'static str> } impl Window { @@ -76,7 +78,8 @@ impl Window { let window = Window { canvas, - redraw + redraw, + previous_pointer: RefCell::new("auto") }; if let Some(dimensions) = attr.dimensions { @@ -135,7 +138,7 @@ impl Window { None } - pub fn set_position(&self, position: LogicalPosition) { + pub fn set_position(&self, _position: LogicalPosition) { // TODO: use CSS? } @@ -223,35 +226,41 @@ impl Window { MouseCursor::ColResize => "col-resize", MouseCursor::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<(), String> { + pub fn set_cursor_position(&self, _position: LogicalPosition) -> Result<(), String> { // TODO: pointer capture Ok(()) } #[inline] - pub fn grab_cursor(&self, grab: bool) -> Result<(), String> { + pub fn grab_cursor(&self, _grab: bool) -> Result<(), String> { // TODO: pointer capture Ok(()) } #[inline] pub fn hide_cursor(&self, hide: bool) { - self.canvas.set_attribute("cursor", "none") - .expect("Setting the cursor on the canvas"); + if hide { + 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) { + pub fn set_maximized(&self, _maximized: bool) { // TODO: should there be a maximization / fullscreen API? } #[inline] - pub fn set_fullscreen(&self, monitor: Option) { + pub fn set_fullscreen(&self, _monitor: Option) { // TODO: should there be a maximization / fullscreen API? } @@ -266,12 +275,12 @@ impl Window { } #[inline] - pub fn set_window_icon(&self, window_icon: Option) { + pub fn set_window_icon(&self, _window_icon: Option) { // TODO: should this set the favicon? } #[inline] - pub fn set_ime_spot(&self, position: LogicalPosition) { + pub fn set_ime_spot(&self, _position: LogicalPosition) { // TODO: what is this? } From b09629f1d4b0e09ac5215e5a861a8ee72e10429f Mon Sep 17 00:00:00 2001 From: Ryan Goldstein Date: Mon, 18 Mar 2019 22:13:30 -0400 Subject: [PATCH 12/69] Handle ControlFlow::Exit --- src/platform_impl/stdweb/event_loop.rs | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/platform_impl/stdweb/event_loop.rs b/src/platform_impl/stdweb/event_loop.rs index ea2b9acd..0602c197 100644 --- a/src/platform_impl/stdweb/event_loop.rs +++ b/src/platform_impl/stdweb/event_loop.rs @@ -67,6 +67,7 @@ pub struct ELRShared { struct EventLoopRunner { control: ControlFlow, + handling: bool, event_handler: Box, &mut ControlFlow)>, } @@ -89,13 +90,9 @@ impl EventLoop { } pub fn run(self, mut event_handler: F) -> ! - where F: 'static + FnMut(Event, &RootELW, &mut ControlFlow) - { - // TODO: how to handle request redraw? - // TODO: onclose (stdweb PR) - // TODO: file dropping, PathBuf isn't useful for web + where F: 'static + FnMut(Event, &RootELW, &mut ControlFlow) { let runner = self.elw.p.runner; - + let relw = RootELW { p: EventLoopWindowTarget::new(), _marker: PhantomData @@ -130,7 +127,6 @@ impl EventLoop { elrs.send_event(Event::WindowEvent { window_id: RootWI(WindowId), event: WindowEvent::KeyboardInput { - // TODO: is there a way to get keyboard device? device_id: RootDI(unsafe { DeviceId::dummy() }), input: KeyboardInput { scancode: scancode(&event), @@ -145,7 +141,6 @@ impl EventLoop { elrs.send_event(Event::WindowEvent { window_id: RootWI(WindowId), event: WindowEvent::KeyboardInput { - // TODO: is there a way to get keyboard device? device_id: RootDI(unsafe { DeviceId::dummy() }), input: KeyboardInput { scancode: scancode(&event), @@ -248,6 +243,7 @@ impl ELRShared { fn set_listener(&self, event_handler: Box, &mut ControlFlow)>) { *self.runner.borrow_mut() = Some(EventLoopRunner { control: ControlFlow::Poll, + handling: false event_handler }); } @@ -256,11 +252,17 @@ impl ELRShared { // TODO: handle event buffer pub fn send_event(&self, event: Event) { match *self.runner.borrow_mut() { - Some(ref mut runner) => { + Some(ref mut runner) if !runner.handling => { + runner.handling = true; + let closed = runner.control == ControlFlow::Exit; // TODO: bracket this in control flow events? (runner.event_handler)(event, &mut runner.control); + if closed { + runner.control = ControlFlow::Exit; + } + runner.handling = false; } - None => () + _ => self.events.borrow_mut().push_back(event) } } From 7c6bdcc459bf33592f7c996c4d2506ea89ad0f6b Mon Sep 17 00:00:00 2001 From: Ryan Goldstein Date: Fri, 22 Mar 2019 22:15:49 -0400 Subject: [PATCH 13/69] Handle ControlFlow::Exit and dealing with events-in-events --- src/platform_impl/stdweb/event_loop.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/platform_impl/stdweb/event_loop.rs b/src/platform_impl/stdweb/event_loop.rs index 0602c197..2827138c 100644 --- a/src/platform_impl/stdweb/event_loop.rs +++ b/src/platform_impl/stdweb/event_loop.rs @@ -231,6 +231,11 @@ fn add_event(elrs: &EventLoopRunnerShared, target: &impl IE let elrs = elrs.clone(); target.add_event_listener(move |event: E| { + // Don't capture the event if the events loop has been destroyed + if elrs.runner.borrow().control == ControlFlow::Exit { + return; + } + event.prevent_default(); event.stop_propagation(); event.cancel_bubble(); @@ -251,6 +256,22 @@ impl ELRShared { // TODO: handle event loop closures // TODO: handle event buffer pub fn send_event(&self, event: Event) { + let start_cause = StartCause::Poll; // TODO: this is obviously not right + self.handle_start(StartCause::Poll); + self.handle_event(event); + self.handle_event(Event::EventsCleared); + } + + fn handle_start(&self, start: StartCause) { + let is_handling = if Some(ref runner) = *self.runner.borrow() { + runner.handling + } else { + false + }; + self.handle_event(Event::StartCause(is_handling)); + } + + fn handle_event(&self, event: Event) { match *self.runner.borrow_mut() { Some(ref mut runner) if !runner.handling => { runner.handling = true; @@ -264,6 +285,11 @@ impl ELRShared { } _ => self.events.borrow_mut().push_back(event) } + if self.runner.borrow().is_some() { + if let Some(event) = self.events.borrow_mut().pop_front() { + self.handle_event(event); + } + } } } From 9e25561edf868dd077926f2b108407b917560c5c Mon Sep 17 00:00:00 2001 From: Ryan Goldstein Date: Tue, 2 Apr 2019 22:31:30 -0400 Subject: [PATCH 14/69] Fix compile failures and add canvas positioning --- src/platform_impl/stdweb/event_loop.rs | 15 +++++++++------ src/platform_impl/stdweb/window.rs | 22 ++++++++++++++++------ 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/src/platform_impl/stdweb/event_loop.rs b/src/platform_impl/stdweb/event_loop.rs index 2827138c..f6bdcab4 100644 --- a/src/platform_impl/stdweb/event_loop.rs +++ b/src/platform_impl/stdweb/event_loop.rs @@ -232,8 +232,9 @@ fn add_event(elrs: &EventLoopRunnerShared, target: &impl IE target.add_event_listener(move |event: E| { // Don't capture the event if the events loop has been destroyed - if elrs.runner.borrow().control == ControlFlow::Exit { - return; + match &*elrs.runner.borrow() { + Some(ref runner) if runner.control == ControlFlow::Exit => return, + _ => () } event.prevent_default(); @@ -248,7 +249,7 @@ impl ELRShared { fn set_listener(&self, event_handler: Box, &mut ControlFlow)>) { *self.runner.borrow_mut() = Some(EventLoopRunner { control: ControlFlow::Poll, - handling: false + handling: false, event_handler }); } @@ -257,18 +258,20 @@ impl ELRShared { // TODO: handle event buffer pub fn send_event(&self, event: Event) { let start_cause = StartCause::Poll; // TODO: this is obviously not right - self.handle_start(StartCause::Poll); + self.handle_start(start_cause); self.handle_event(event); self.handle_event(Event::EventsCleared); } fn handle_start(&self, start: StartCause) { - let is_handling = if Some(ref runner) = *self.runner.borrow() { + let is_handling = if let Some(ref runner) = *self.runner.borrow() { runner.handling } else { false }; - self.handle_event(Event::StartCause(is_handling)); + if is_handling { + self.handle_event(Event::NewEvents(start)); + } } fn handle_event(&self, event: Event) { diff --git a/src/platform_impl/stdweb/window.rs b/src/platform_impl/stdweb/window.rs index df875f03..2a712f12 100644 --- a/src/platform_impl/stdweb/window.rs +++ b/src/platform_impl/stdweb/window.rs @@ -53,7 +53,8 @@ impl WindowId { pub struct Window { pub(crate) canvas: CanvasElement, pub(crate) redraw: Box, - previous_pointer: RefCell<&'static str> + previous_pointer: RefCell<&'static str>, + position: RefCell, } impl Window { @@ -79,7 +80,11 @@ impl Window { let window = Window { canvas, redraw, - previous_pointer: RefCell::new("auto") + previous_pointer: RefCell::new("auto"), + position: RefCell::new(LogicalPosition { + x: 0.0, + y: 0.0 + }) }; if let Some(dimensions) = attr.dimensions { @@ -134,12 +139,17 @@ impl Window { } pub fn get_inner_position(&self) -> Option { - // TODO - None + Some(*self.position.borrow()) } - pub fn set_position(&self, _position: LogicalPosition) { - // TODO: use CSS? + pub fn set_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] From fe5e3000628f8e72eeceae20344ad1fa9b8839b0 Mon Sep 17 00:00:00 2001 From: Ryan Goldstein Date: Thu, 25 Apr 2019 00:02:13 -0400 Subject: [PATCH 15/69] Clean up and document the core of stdweb event handling --- src/platform_impl/stdweb/event_loop.rs | 84 ++++++++++++++++++++------ 1 file changed, 64 insertions(+), 20 deletions(-) diff --git a/src/platform_impl/stdweb/event_loop.rs b/src/platform_impl/stdweb/event_loop.rs index f6bdcab4..d9d2da55 100644 --- a/src/platform_impl/stdweb/event_loop.rs +++ b/src/platform_impl/stdweb/event_loop.rs @@ -67,7 +67,7 @@ pub struct ELRShared { struct EventLoopRunner { control: ControlFlow, - handling: bool, + is_busy: bool, event_handler: Box, &mut ControlFlow)>, } @@ -246,49 +246,93 @@ fn add_event(elrs: &EventLoopRunnerShared, target: &impl IE } impl ELRShared { + // 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.runner.borrow_mut() = Some(EventLoopRunner { control: ControlFlow::Poll, - handling: false, + is_busy: false, event_handler }); } - // TODO: handle event loop closures - // TODO: handle event buffer + // 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) { - let start_cause = StartCause::Poll; // TODO: this is obviously not right - self.handle_start(start_cause); - self.handle_event(event); - self.handle_event(Event::EventsCleared); - } + // If the event loop is closed, it should discard any new events + if self.closed() { + return; + } - fn handle_start(&self, start: StartCause) { - let is_handling = if let Some(ref runner) = *self.runner.borrow() { - runner.handling + 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 { - false + true // If there is no event runner yet, then there's no point in processing events }; - if is_handling { - self.handle_event(Event::NewEvents(start)); + + 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) { + let closed = self.closed(); + match *self.runner.borrow_mut() { - Some(ref mut runner) if !runner.handling => { - runner.handling = true; - let closed = runner.control == ControlFlow::Exit; + 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; } - runner.handling = false; + + // 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) } - if self.runner.borrow().is_some() { + + // 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); } From 9f801cf79e9e17ed4220dbaa051ea80e2e6226b0 Mon Sep 17 00:00:00 2001 From: Ryan Goldstein Date: Mon, 29 Apr 2019 15:39:43 -0400 Subject: [PATCH 16/69] Only send the request-redraw on the next animation frame --- src/platform_impl/stdweb/window.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/platform_impl/stdweb/window.rs b/src/platform_impl/stdweb/window.rs index 2a712f12..12fbaba5 100644 --- a/src/platform_impl/stdweb/window.rs +++ b/src/platform_impl/stdweb/window.rs @@ -12,7 +12,7 @@ use stdweb::{ unstable::TryInto }; use stdweb::web::{ - document, + document, window, html_element::CanvasElement, }; @@ -21,7 +21,6 @@ pub struct MonitorHandle; impl MonitorHandle { pub fn get_hidpi_factor(&self) -> f64 { - // TODO 1.0 } @@ -72,10 +71,10 @@ impl Window { register(&target.runner, &canvas); let runner = target.runner.clone(); - let redraw = Box::new(move || runner.send_event(Event::WindowEvent { + let redraw = Box::new(move || window().request_animation_frame(|| runner.send_event(Event::WindowEvent { window_id: RootWI(WindowId), event: WindowEvent::RedrawRequested - })); + }))); let window = Window { canvas, @@ -95,7 +94,6 @@ impl Window { height: 768.0, }) } - // TODO: most of these are no-op, but should they stay here just in case? window.set_min_dimensions(attr.min_dimensions); window.set_max_dimensions(attr.max_dimensions); window.set_resizable(attr.resizable); @@ -191,7 +189,6 @@ impl Window { #[inline] pub fn get_hidpi_factor(&self) -> f64 { - // TODO 1.0 } @@ -286,7 +283,7 @@ impl Window { #[inline] pub fn set_window_icon(&self, _window_icon: Option) { - // TODO: should this set the favicon? + // Currently an intentional no-op } #[inline] From 70c7382a099726e94542c57aa75b910dd614ad09 Mon Sep 17 00:00:00 2001 From: Ryan Goldstein Date: Wed, 1 May 2019 21:20:54 -0400 Subject: [PATCH 17/69] Fix the request_animation_frame lifetimes --- src/platform_impl/stdweb/window.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/platform_impl/stdweb/window.rs b/src/platform_impl/stdweb/window.rs index 12fbaba5..3008dd58 100644 --- a/src/platform_impl/stdweb/window.rs +++ b/src/platform_impl/stdweb/window.rs @@ -71,10 +71,13 @@ impl Window { register(&target.runner, &canvas); let runner = target.runner.clone(); - let redraw = Box::new(move || window().request_animation_frame(|| runner.send_event(Event::WindowEvent { - window_id: RootWI(WindowId), - event: WindowEvent::RedrawRequested - }))); + 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, From 37dadab745062135284c621d45fb58c63b9ad445 Mon Sep 17 00:00:00 2001 From: Ryan Goldstein Date: Fri, 31 May 2019 21:48:26 -0700 Subject: [PATCH 18/69] Add access to the canvas in the Window --- src/platform/mod.rs | 2 ++ src/platform/stdweb.rs | 8 ++++++++ src/platform_impl/stdweb/window.rs | 9 ++++++++- 3 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 src/platform/stdweb.rs diff --git a/src/platform/mod.rs b/src/platform/mod.rs index ba494ac6..da780f4b 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 00000000..4ffa7dd2 --- /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/stdweb/window.rs b/src/platform_impl/stdweb/window.rs index 3008dd58..1ab8a0d2 100644 --- a/src/platform_impl/stdweb/window.rs +++ b/src/platform_impl/stdweb/window.rs @@ -1,8 +1,9 @@ use dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize}; use event::{Event, WindowEvent}; use icon::Icon; +use platform::stdweb::WindowExtStdweb; use monitor::{MonitorHandle as RootMH}; -use window::{CreationError, MouseCursor, WindowAttributes, WindowId as RootWI}; +use window::{CreationError, MouseCursor, Window as RootWindow, WindowAttributes, WindowId as RootWI}; use super::{EventLoopWindowTarget, register}; use std::collections::VecDeque; use std::collections::vec_deque::IntoIter as VecDequeIter; @@ -317,3 +318,9 @@ impl Window { unsafe { WindowId::dummy() } } } + +impl WindowExtStdweb for RootWindow { + fn canvas(&self) -> CanvasElement { + self.window.canvas.clone() + } +} From 1409f83fb92e79aaa345bedfc9f662aa996933cf Mon Sep 17 00:00:00 2001 From: Ryan Goldstein Date: Fri, 31 May 2019 21:50:34 -0700 Subject: [PATCH 19/69] Add support for mouse wheel --- src/platform_impl/stdweb/event_loop.rs | 27 +++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/src/platform_impl/stdweb/event_loop.rs b/src/platform_impl/stdweb/event_loop.rs index d9d2da55..8ec07355 100644 --- a/src/platform_impl/stdweb/event_loop.rs +++ b/src/platform_impl/stdweb/event_loop.rs @@ -1,7 +1,7 @@ use super::*; use dpi::LogicalPosition; -use event::{DeviceId as RootDI, ElementState, Event, KeyboardInput, StartCause, WindowEvent}; +use event::{DeviceId as RootDI, ElementState, Event, KeyboardInput, MouseScrollDelta, StartCause, TouchPhase, WindowEvent}; use event_loop::{ControlFlow, EventLoopWindowTarget as RootELW, EventLoopClosed}; use window::{WindowId as RootWI}; use stdweb::{ @@ -151,12 +151,15 @@ impl EventLoop { } }); }); + + runner.send_event(Event::NewEvents(StartCause::Init)); + 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"; + throw "Using exceptions for control flow, don't mind me. This isn't actually an error!"; } unreachable!(); } @@ -224,9 +227,27 @@ pub fn register(elrs: &EventLoopRunnerShared, canvas: &CanvasElem } }); }); + 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) +fn add_event(elrs: &EventLoopRunnerShared, target: &impl IEventTarget, mut handler: F) where E: ConcreteEvent, F: FnMut(&EventLoopRunnerShared, E) + 'static { let elrs = elrs.clone(); From b59e3c670bf49a2c020fb432f3d6681ede0b1c00 Mon Sep 17 00:00:00 2001 From: Ryan Goldstein Date: Fri, 14 Jun 2019 21:15:43 -0700 Subject: [PATCH 20/69] WIP --- Cargo.toml | 2 +- src/platform_impl/stdweb/event_loop.rs | 98 ++++++++++++++++++-------- 2 files changed, 68 insertions(+), 32 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0d7e5e0a..c0a2c1c0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -74,5 +74,5 @@ percent-encoding = "1.0" version = "0.8" [target.'cfg(target_arch = "wasm32")'.dependencies.stdweb] -version = "0.4.17" +path = "../stdweb" optional = true diff --git a/src/platform_impl/stdweb/event_loop.rs b/src/platform_impl/stdweb/event_loop.rs index 37b00f77..87eeed6c 100644 --- a/src/platform_impl/stdweb/event_loop.rs +++ b/src/platform_impl/stdweb/event_loop.rs @@ -10,6 +10,7 @@ use stdweb::{ document, event::*, html_element::CanvasElement, + TimeoutHandle, }, }; use std::cell::RefCell; @@ -17,6 +18,7 @@ use std::collections::VecDeque; use std::collections::vec_deque::IntoIter as VecDequeIter; use std::marker::PhantomData; use std::rc::Rc; +use std::time::Instant; #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct DeviceId(i32); @@ -66,11 +68,44 @@ pub struct ELRShared { } struct EventLoopRunner { - control: ControlFlow, + control: ControlFlowStatus, is_busy: bool, event_handler: Box, &mut ControlFlow)>, } +enum ControlFlowStatus { + 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::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 { @@ -152,8 +187,6 @@ impl EventLoop { }); }); - runner.send_event(Event::NewEvents(StartCause::Init)); - 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 @@ -254,7 +287,7 @@ fn add_event(elrs: &EventLoopRunnerShared, target: &impl IE target.add_event_listener(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, + Some(ref runner) if runner.control.is_exit() => return, _ => () } @@ -271,10 +304,11 @@ impl ELRShared { // 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)>) { + // TODO: Start the poll here *self.runner.borrow_mut() = Some(EventLoopRunner { - control: ControlFlow::Poll, + control: ControlFlowStatus::Exit, is_busy: false, - event_handler + event_handler, }); } @@ -287,30 +321,32 @@ impl ELRShared { return; } + // TODO: Determine if a timeout needs to be cleared + 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 - }; + match *self.runner.borrow() { + Some(ref runner) if !runner.is_busy => { + let mut control = runner.control.to_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); + self.handle_event(event, &mut control); + self.handle_event(Event::EventsCleared, &mut control); - 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); + // TODO: integrate control flow change and set up the next iteration - // 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); + // 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); + } + } + _ => { + self.events.borrow_mut().push_back(event); } } } @@ -318,7 +354,7 @@ impl ELRShared { // Check if the event loop is currntly closed fn closed(&self) -> bool { match *self.runner.borrow() { - Some(ref runner) => runner.control == ControlFlow::Exit, + 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 } } @@ -326,7 +362,7 @@ impl ELRShared { // 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) { + fn handle_event(&self, event: Event, control: &mut ControlFlow) { let closed = self.closed(); match *self.runner.borrow_mut() { @@ -335,11 +371,11 @@ impl ELRShared { runner.is_busy = true; // TODO: bracket this in control flow events? - (runner.event_handler)(event, &mut runner.control); - + (runner.event_handler)(event, control); + // Maintain closed state, even if the callback changes it if closed { - runner.control = ControlFlow::Exit; + *control = ControlFlow::Exit; } // An event is no longer being processed @@ -355,7 +391,7 @@ impl ELRShared { 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); + self.handle_event(event, control); } } } From 2690306f4a028c549212a9d64c1a6a3e9d2fe08f Mon Sep 17 00:00:00 2001 From: Ryan Goldstein Date: Sun, 16 Jun 2019 21:30:05 -0700 Subject: [PATCH 21/69] Implement Poll and WaitUntil in the stdweb backend --- Cargo.toml | 7 +- src/event.rs | 4 +- src/event_loop.rs | 4 +- src/lib.rs | 1 + src/platform_impl/stdweb/event_loop.rs | 144 ++++++++++++++++++------- 5 files changed, 115 insertions(+), 45 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c0a2c1c0..4ee84239 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ categories = ["gui"] features = ["serde"] [dependencies] +instant = "0.1" lazy_static = "1" libc = "0.2" log = "0.4" @@ -73,6 +74,6 @@ 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 +[target.'cfg(target_arch = "wasm32")'.dependencies] +stdweb = { path = "../stdweb", optional = true } +instant = { version = "0.1", features = ["stdweb"] } diff --git a/src/event.rs b/src/event.rs index 1d1a8fb5..7eaee201 100644 --- a/src/event.rs +++ b/src/event.rs @@ -4,7 +4,7 @@ //! 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::time::Instant; +use instant::Instant; use std::path::PathBuf; use dpi::{LogicalPosition, LogicalSize}; @@ -58,7 +58,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 476e7ed5..91a3f912 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -10,7 +10,7 @@ //! [event_loop_proxy]: ./struct.EventLoopProxy.html //! [send_event]: ./struct.EventLoopProxy.html#method.send_event use std::{fmt, error}; -use std::time::Instant; +use instant::Instant; use std::ops::Deref; use platform_impl; @@ -69,7 +69,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 7846803a..a15664c2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -75,6 +75,7 @@ //! [`LoopDestroyed`]: ./event/enum.Event.html#variant.LoopDestroyed //! [`platform`]: ./platform/index.html +extern crate instant; #[allow(unused_imports)] #[macro_use] extern crate lazy_static; diff --git a/src/platform_impl/stdweb/event_loop.rs b/src/platform_impl/stdweb/event_loop.rs index 87eeed6c..822ebcea 100644 --- a/src/platform_impl/stdweb/event_loop.rs +++ b/src/platform_impl/stdweb/event_loop.rs @@ -3,6 +3,7 @@ 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::*, @@ -10,15 +11,17 @@ use stdweb::{ document, event::*, html_element::CanvasElement, + window, TimeoutHandle, }, }; -use std::cell::RefCell; -use std::collections::VecDeque; -use std::collections::vec_deque::IntoIter as VecDequeIter; -use std::marker::PhantomData; -use std::rc::Rc; -use std::time::Instant; +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); @@ -40,31 +43,37 @@ pub struct EventLoopWindowTarget { impl EventLoopWindowTarget { fn new() -> Self { EventLoopWindowTarget { - runner: Rc::new(ELRShared { + runner: EventLoopRunnerShared(Rc::new(ELRShared { runner: RefCell::new(None), events: RefCell::new(VecDeque::new()) - }) + })) } } } #[derive(Clone)] -pub struct EventLoopProxy { +pub struct EventLoopProxy { runner: EventLoopRunnerShared } -impl EventLoopProxy { +impl EventLoopProxy { pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> { self.runner.send_event(Event::UserEvent(event)); Ok(()) } } -pub type EventLoopRunnerShared = Rc>; +pub struct EventLoopRunnerShared(Rc>); + +impl Clone for EventLoopRunnerShared { + fn clone(&self) -> Self { + EventLoopRunnerShared(self.0.clone()) + } +} pub struct ELRShared { runner: RefCell>>, - events: RefCell>>, // TODO: this may not be necessary? + events: RefCell>>, } struct EventLoopRunner { @@ -74,6 +83,7 @@ struct EventLoopRunner { } enum ControlFlowStatus { + Init, WaitUntil { timeout: TimeoutHandle, start: Instant, @@ -91,6 +101,7 @@ enum ControlFlowStatus { 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, @@ -286,7 +297,7 @@ fn add_event(elrs: &EventLoopRunnerShared, target: &impl IE target.add_event_listener(move |event: E| { // Don't capture the event if the events loop has been destroyed - match &*elrs.runner.borrow() { + match &*elrs.0.runner.borrow() { Some(ref runner) if runner.control.is_exit() => return, _ => () } @@ -299,17 +310,17 @@ fn add_event(elrs: &EventLoopRunnerShared, target: &impl IE }); } -impl ELRShared { +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)>) { - // TODO: Start the poll here - *self.runner.borrow_mut() = Some(EventLoopRunner { - control: ControlFlowStatus::Exit, + *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 @@ -321,23 +332,46 @@ impl ELRShared { return; } - // TODO: Determine if a timeout needs to be cleared - - let start_cause = StartCause::Poll; // TODO: determine start cause - // Determine if event handling is in process, and then release the borrow on the runner - match *self.runner.borrow() { + match *self.0.runner.borrow() { Some(ref runner) if !runner.is_busy => { + let (start_cause, event_is_start) = if let Event::NewEvents(cause) = event { + (cause, true) + } else { + (match runner.control { + ControlFlowStatus::Init => StartCause::Init, + ControlFlowStatus::Poll { ref timeout } => { + timeout.clear(); + + StartCause::Poll + } + ControlFlowStatus::Wait { start } => StartCause::WaitCancelled { + start, + requested_resume: None, + }, + ControlFlowStatus::WaitUntil { start, end, ref timeout } => { + timeout.clear(); + + StartCause::WaitCancelled { + start, + requested_resume: Some(end) + } + }, + ControlFlowStatus::Exit => { return; } + }, false) + }; let mut control = runner.control.to_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); - self.handle_event(event, &mut control); + if !event_is_start { + self.handle_event(event, &mut control); + } self.handle_event(Event::EventsCleared, &mut control); - // TODO: integrate control flow change and set up the next iteration + 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 @@ -346,31 +380,22 @@ impl ELRShared { } } _ => { - self.events.borrow_mut().push_back(event); + self.0.events.borrow_mut().push_back(event); } } } - // Check if the event loop is currntly closed - fn closed(&self) -> bool { - match *self.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 - } - } - // 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.runner.borrow_mut() { + 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; - // TODO: bracket this in control flow events? (runner.event_handler)(event, control); // Maintain closed state, even if the callback changes it @@ -383,18 +408,61 @@ impl ELRShared { } // 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) + _ => 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.runner.borrow().is_some() { + if !closed && self.0.runner.borrow().is_some() { // Take an event out of the queue and handle it - if let Some(event) = self.events.borrow_mut().pop_front() { + 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 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)), 0) + } + } + 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) => { + runner.control = control_flow_status; + } + 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 + } + } } From 182beb4f8b47aedfb8c9b31cc5571d67f4946a82 Mon Sep 17 00:00:00 2001 From: Ryan Goldstein Date: Sun, 16 Jun 2019 21:34:54 -0700 Subject: [PATCH 22/69] Indicate that I will be maintaing the stdweb backend --- CONTRIBUTING.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a8a0c783..7de04ab8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -46,9 +46,10 @@ This table summarizes who can be contacted in which case, with the following leg - `T` - Tester: has the ability of testing the platform - ` `: knows nothing of this platform -| Platform | Windows | macOS | X11 | Wayland | Android | iOS | Emscripten | -| :--- | :---: | :---: | :---: | :---: | :---: | :---: | :---: | -| @mitchmindtree | T | | T | T | | | | -| @Osspial | M | | T | T | T | | T | -| @vberger | | | T | M | | | | -| @mtak- | | T | | | T | M | | +| Platform | Windows | macOS | X11 | Wayland | Android | iOS | Emscripten | Stdweb | +| :--- | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | +| @mitchmindtree | T | | T | T | | | | | +| @Osspial | M | | T | T | T | | T | | +| @vberger | | | T | M | | | | | +| @mtak- | | T | | | T | M | | | +| @ryanisacg | T | T | | | | | | M | From e4d8e22846aff4330abba1aaa50a95c35836a948 Mon Sep 17 00:00:00 2001 From: Ben Merritt Date: Mon, 3 Jun 2019 22:51:01 -0700 Subject: [PATCH 23/69] Start implementing web-sys backend --- Cargo.toml | 27 ++ examples/window.rs | 8 +- src/lib.rs | 4 + src/platform/mod.rs | 1 + src/platform/web_sys.rs | 7 + src/platform_impl/mod.rs | 44 ++- src/platform_impl/web_sys/event_loop.rs | 374 ++++++++++++++++++++++++ src/platform_impl/web_sys/events.rs | 1 + src/platform_impl/web_sys/mod.rs | 24 ++ src/platform_impl/web_sys/util.js | 3 + src/platform_impl/web_sys/window.rs | 1 + 11 files changed, 479 insertions(+), 15 deletions(-) create mode 100644 src/platform/web_sys.rs create mode 100644 src/platform_impl/web_sys/event_loop.rs create mode 100644 src/platform_impl/web_sys/events.rs create mode 100644 src/platform_impl/web_sys/mod.rs create mode 100644 src/platform_impl/web_sys/util.js create mode 100644 src/platform_impl/web_sys/window.rs diff --git a/Cargo.toml b/Cargo.toml index 4ee84239..17e759b0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,9 @@ libc = "0.2" log = "0.4" serde = { version = "1", optional = true, features = ["serde_derive"] } +[features] +web-sys-support = ["web-sys", "wasm-bindgen"] + [dev-dependencies] image = "0.21" 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] 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] stdweb = { path = "../stdweb", optional = true } instant = { version = "0.1", features = ["stdweb"] } diff --git a/examples/window.rs b/examples/window.rs index b33b5984..70e812d6 100644 --- a/examples/window.rs +++ b/examples/window.rs @@ -1,4 +1,8 @@ extern crate winit; +#[cfg(feature = "stdweb")] +#[macro_use] +extern crate stdweb; +#[cfg(feature = "wasm-bindgen")] #[macro_use] extern crate stdweb; @@ -13,10 +17,10 @@ fn main() { .with_title("A fantastic window!") .build(&event_loop) .unwrap(); - console!(log, "Built window!"); + //console!(log, "Built window!"); event_loop.run(|event, _, control_flow| { - console!(log, format!("{:?}", event)); + //console!(log, format!("{:?}", event)); match event { Event::WindowEvent { diff --git a/src/lib.rs b/src/lib.rs index a15664c2..f48dd32c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -115,6 +115,10 @@ extern crate smithay_client_toolkit as sctk; extern crate stdweb; #[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; +#[cfg(feature = "web-sys")] +extern crate web_sys; pub mod dpi; #[macro_use] diff --git a/src/platform/mod.rs b/src/platform/mod.rs index da780f4b..94f0bf08 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -21,5 +21,6 @@ pub mod unix; pub mod windows; pub mod stdweb; +pub mod web_sys; pub mod desktop; diff --git a/src/platform/web_sys.rs b/src/platform/web_sys.rs new file mode 100644 index 00000000..c4539f78 --- /dev/null +++ b/src/platform/web_sys.rs @@ -0,0 +1,7 @@ +#![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 2a8ccd62..e0ea2b96 100644 --- a/src/platform_impl/mod.rs +++ b/src/platform_impl/mod.rs @@ -1,30 +1,48 @@ pub use self::platform::*; #[cfg(target_os = "windows")] -#[path="windows/mod.rs"] +#[path = "windows/mod.rs"] mod platform; -#[cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))] -#[path="linux/mod.rs"] +#[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" +))] +#[path = "linux/mod.rs"] mod platform; #[cfg(target_os = "macos")] -#[path="macos/mod.rs"] +#[path = "macos/mod.rs"] mod platform; #[cfg(target_os = "android")] -#[path="android/mod.rs"] +#[path = "android/mod.rs"] mod platform; #[cfg(target_os = "ios")] -#[path="ios/mod.rs"] +#[path = "ios/mod.rs"] mod platform; #[cfg(target_os = "emscripten")] -#[path="emscripten/mod.rs"] +#[path = "emscripten/mod.rs"] mod platform; #[cfg(feature = "stdweb")] -#[path="stdweb/mod.rs"] +#[path = "stdweb/mod.rs"] +mod platform; +#[cfg(feature = "web-sys")] +#[path = "web_sys/mod.rs"] mod platform; -#[cfg(all(not(target_os = "ios"), 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(feature = "stdweb")))] +#[cfg(all( + not(target_os = "ios"), + 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(feature = "stdweb"), + not(feature = "web-sys") +))] compile_error!("The platform you're compiling for is not supported by winit"); diff --git a/src/platform_impl/web_sys/event_loop.rs b/src/platform_impl/web_sys/event_loop.rs new file mode 100644 index 00000000..023ae2ce --- /dev/null +++ b/src/platform_impl/web_sys/event_loop.rs @@ -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 { + elw: RootELW, +} + +pub struct EventLoopWindowTarget { + pub(crate) runner: EventLoopRunnerShared, +} + +impl EventLoopWindowTarget { + fn new() -> Self { + EventLoopWindowTarget { + runner: 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 type EventLoopRunnerShared = Rc>; + +pub struct ELRShared { + runner: RefCell>>, + events: RefCell>>, // TODO: this may not be necessary? +} + +struct EventLoopRunner { + control: ControlFlow, + is_busy: bool, + event_handler: Box, &mut ControlFlow)>, +} + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(module = "util.js", js_name = "throwToEscapeEventLoop")] + fn throw_to_escape_event_loop(); +} + +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(); + 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 { + EventLoopProxy { + runner: self.elw.p.runner.clone(), + } + } + + pub fn window_target(&self) -> &RootELW { + &self.elw + } +} + +pub fn register(elrs: &EventLoopRunnerShared, 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( + elrs: &EventLoopRunnerShared, + target: &EventTarget, + event: &str, + mut handler: F, +) where + E: AsRef + wasm_bindgen::convert::FromWasmAbi + 'static, + F: FnMut(&EventLoopRunnerShared, 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).as_ref().unchecked_ref()); +} + +impl ELRShared { + // 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.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) { + // 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) { + 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() +} diff --git a/src/platform_impl/web_sys/events.rs b/src/platform_impl/web_sys/events.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/src/platform_impl/web_sys/events.rs @@ -0,0 +1 @@ + diff --git a/src/platform_impl/web_sys/mod.rs b/src/platform_impl/web_sys/mod.rs new file mode 100644 index 00000000..a28ac36e --- /dev/null +++ b/src/platform_impl/web_sys/mod.rs @@ -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) + } +} diff --git a/src/platform_impl/web_sys/util.js b/src/platform_impl/web_sys/util.js new file mode 100644 index 00000000..a4b4c4f1 --- /dev/null +++ b/src/platform_impl/web_sys/util.js @@ -0,0 +1,3 @@ +function throwToEscapeEventLoop() { + throw "Using exceptions for control flow, don't mind me. This isn't actually an error!"; +} \ No newline at end of file diff --git a/src/platform_impl/web_sys/window.rs b/src/platform_impl/web_sys/window.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/src/platform_impl/web_sys/window.rs @@ -0,0 +1 @@ + From 54b40743697f0cf1fa20d1dbf0bab7e81a4ab98b Mon Sep 17 00:00:00 2001 From: Ben Merritt Date: Tue, 4 Jun 2019 23:08:09 -0700 Subject: [PATCH 24/69] Port remaining modules to web_sys --- Cargo.toml | 4 + examples/window.rs | 2 +- src/platform_impl/web_sys/event_loop.rs | 11 +- src/platform_impl/web_sys/events.rs | 200 +++++++++++++++ src/platform_impl/web_sys/mod.rs | 10 +- src/platform_impl/web_sys/window.rs | 320 ++++++++++++++++++++++++ 6 files changed, 537 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 17e759b0..8dde53a2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -82,12 +82,16 @@ version = "0.3.22" optional = true features = [ 'Document', + 'DomRect', + 'Element', 'Event', 'EventTarget', 'FocusEvent', 'HtmlCanvasElement', + 'HtmlElement', 'KeyboardEvent', 'MouseEvent', + 'Node', 'PointerEvent', 'Window', 'WheelEvent' diff --git a/examples/window.rs b/examples/window.rs index 70e812d6..6c3b9148 100644 --- a/examples/window.rs +++ b/examples/window.rs @@ -4,7 +4,7 @@ extern crate winit; extern crate stdweb; #[cfg(feature = "wasm-bindgen")] #[macro_use] -extern crate stdweb; +extern crate wasm_bindgen; use winit::window::WindowBuilder; use winit::event::{Event, WindowEvent}; diff --git a/src/platform_impl/web_sys/event_loop.rs b/src/platform_impl/web_sys/event_loop.rs index 023ae2ce..0bc0503d 100644 --- a/src/platform_impl/web_sys/event_loop.rs +++ b/src/platform_impl/web_sys/event_loop.rs @@ -6,13 +6,14 @@ use event::{ TouchPhase, WindowEvent, }; use event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW}; +use platform_impl::platform::document; 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 web_sys::{EventTarget, FocusEvent, HtmlCanvasElement, KeyboardEvent, PointerEvent, WheelEvent}; use window::WindowId as RootWI; #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -70,7 +71,7 @@ struct EventLoopRunner { #[wasm_bindgen] extern "C" { - #[wasm_bindgen(module = "util.js", js_name = "throwToEscapeEventLoop")] + #[wasm_bindgen(module = "/src/platform_impl/web_sys/util.js", js_name = "throwToEscapeEventLoop")] fn throw_to_escape_event_loop(); } @@ -233,7 +234,7 @@ pub fn register(elrs: &EventLoopRunnerShared, canvas: &HtmlCanvas 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, + _ => return, }; elrs.send_event(Event::WindowEvent { window_id: RootWI(WindowId), @@ -368,7 +369,3 @@ impl ELRShared { } } } - -fn document() -> Document { - web_sys::window().unwrap().document().unwrap() -} diff --git a/src/platform_impl/web_sys/events.rs b/src/platform_impl/web_sys/events.rs index 8b137891..4e13ca2a 100644 --- a/src/platform_impl/web_sys/events.rs +++ b/src/platform_impl/web_sys/events.rs @@ -1 +1,201 @@ +use std::convert::TryInto; +use web_sys::{KeyboardEvent, MouseEvent}; +use event::{MouseButton, ModifiersState, ScanCode, VirtualKeyCode}; + +pub fn button_mapping(event: &KeyboardEvent) -> 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: &MouseEvent) -> ModifiersState { + ModifiersState { + shift: event.shift_key(), + ctrl: event.ctrl_key(), + alt: event.alt_key(), + logo: event.meta_key(), + } +} + +pub fn mouse_button(event: &MouseEvent) -> MouseButton { + match event.button() { + 0 => MouseButton::Left, + 1 => MouseButton::Middle, + 2 => MouseButton::Right, + i => MouseButton::Other((i - 3).try_into().expect("very large mouse button value")), + } +} + +pub fn keyboard_modifiers_state(event: &KeyboardEvent) -> ModifiersState { + ModifiersState { + shift: event.shift_key(), + ctrl: event.ctrl_key(), + alt: event.alt_key(), + logo: event.meta_key(), + } +} + +pub fn scancode(event: &KeyboardEvent) -> ScanCode { + match event.key_code() { + 0 => event.char_code(), + i => i, + } +} diff --git a/src/platform_impl/web_sys/mod.rs b/src/platform_impl/web_sys/mod.rs index a28ac36e..1b0591b1 100644 --- a/src/platform_impl/web_sys/mod.rs +++ b/src/platform_impl/web_sys/mod.rs @@ -12,8 +12,6 @@ pub use self::events::{ }; pub use self::window::{MonitorHandle, PlatformSpecificWindowBuilderAttributes, Window, WindowId}; -// TODO: unify with stdweb impl. - #[derive(Debug)] pub struct OsError(String); @@ -22,3 +20,11 @@ impl fmt::Display for OsError { write!(f, "{}", self.0) } } + +fn window() -> web_sys::Window { + web_sys::window().unwrap() +} + +fn document() -> web_sys::Document { + window().document().unwrap() +} diff --git a/src/platform_impl/web_sys/window.rs b/src/platform_impl/web_sys/window.rs index 8b137891..5a41c5f9 100644 --- a/src/platform_impl/web_sys/window.rs +++ b/src/platform_impl/web_sys/window.rs @@ -1 +1,321 @@ +use dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize}; +use error::{ExternalError, NotSupportedError, OsError as RootOE}; +use event::{Event, WindowEvent}; +use icon::Icon; +use platform::web_sys::WindowExtWebSys; +use platform_impl::platform::{document, window}; +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 wasm_bindgen::{closure::Closure, JsCast}; +use web_sys::HtmlCanvasElement; +#[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 dimensions(&self) -> PhysicalSize { + unimplemented!(); + } + + pub fn name(&self) -> Option { + unimplemented!(); + } +} + +#[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: HtmlCanvasElement, + 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: HtmlCanvasElement = element.unchecked_into(); + 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(Closure::wrap(Box::new(move |_: f64| { + runner.send_event(Event::WindowEvent { + window_id: RootWI(WindowId), + event: WindowEvent::RedrawRequested + }); + }) as Box).as_ref().unchecked_ref()); + }); + + 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.x(), + y: bounds.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 WindowExtWebSys for RootWindow { + fn canvas(&self) -> HtmlCanvasElement { + self.window.canvas.clone() + } +} From 7dabad4d71a992028073ed4144a963a99c87ef7b Mon Sep 17 00:00:00 2001 From: Ben Merritt Date: Wed, 5 Jun 2019 22:51:11 -0700 Subject: [PATCH 25/69] Fix throwToEscapeEventLoop function --- src/platform_impl/web_sys/event_loop.rs | 4 ++-- src/platform_impl/web_sys/util.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/platform_impl/web_sys/event_loop.rs b/src/platform_impl/web_sys/event_loop.rs index 0bc0503d..78a70f01 100644 --- a/src/platform_impl/web_sys/event_loop.rs +++ b/src/platform_impl/web_sys/event_loop.rs @@ -69,9 +69,9 @@ struct EventLoopRunner { event_handler: Box, &mut ControlFlow)>, } -#[wasm_bindgen] +#[wasm_bindgen(module = "/src/platform_impl/web_sys/util.js")] extern "C" { - #[wasm_bindgen(module = "/src/platform_impl/web_sys/util.js", js_name = "throwToEscapeEventLoop")] + #[wasm_bindgen(js_name = "throwToEscapeEventLoop")] fn throw_to_escape_event_loop(); } diff --git a/src/platform_impl/web_sys/util.js b/src/platform_impl/web_sys/util.js index a4b4c4f1..57c40e22 100644 --- a/src/platform_impl/web_sys/util.js +++ b/src/platform_impl/web_sys/util.js @@ -1,3 +1,3 @@ -function throwToEscapeEventLoop() { +export function throwToEscapeEventLoop() { throw "Using exceptions for control flow, don't mind me. This isn't actually an error!"; } \ No newline at end of file From 77cd3adb01ee077610b510789e174e699c93b3fe Mon Sep 17 00:00:00 2001 From: Ben Merritt Date: Wed, 5 Jun 2019 22:58:11 -0700 Subject: [PATCH 26/69] TEMPORARY: add testing example --- .gitignore | 4 ++++ Cargo.toml | 7 ++++++- examples/window.rs | 14 ++++++++++---- test.html | 20 ++++++++++++++++++++ test.ps1 | 2 ++ 5 files changed, 42 insertions(+), 5 deletions(-) create mode 100644 test.html create mode 100644 test.ps1 diff --git a/.gitignore b/.gitignore index bf5cea56..7cd4f7f9 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,9 @@ Cargo.lock target/ rls/ .vscode/ +util/ *~ +*.wasm +*.ts +*.js #*# diff --git a/Cargo.toml b/Cargo.toml index 8dde53a2..e23c1d06 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ log = "0.4" serde = { version = "1", optional = true, features = ["serde_derive"] } [features] -web-sys-support = ["web-sys", "wasm-bindgen"] +web_sys = ["web-sys", "wasm-bindgen"] [dev-dependencies] image = "0.21" @@ -81,6 +81,7 @@ version = "0.8" version = "0.3.22" optional = true features = [ + 'console', 'Document', 'DomRect', 'Element', @@ -108,3 +109,7 @@ optional = true [target.'cfg(target_arch = "wasm32")'.dependencies] stdweb = { path = "../stdweb", optional = true } instant = { version = "0.1", features = ["stdweb"] } + +[[example]] +name = "window" +crate-type = ["cdylib"] diff --git a/examples/window.rs b/examples/window.rs index 6c3b9148..797dbf41 100644 --- a/examples/window.rs +++ b/examples/window.rs @@ -5,22 +5,28 @@ extern crate stdweb; #[cfg(feature = "wasm-bindgen")] #[macro_use] extern crate wasm_bindgen; +#[cfg(feature = "wasm-bindgen")] +extern crate web_sys; use winit::window::WindowBuilder; use winit::event::{Event, WindowEvent}; use winit::event_loop::{EventLoop, ControlFlow}; +use wasm_bindgen::{prelude::*, JsValue}; +use web_sys::console; -fn main() { +#[wasm_bindgen(start)] +pub fn main() { + console::log_1(&JsValue::from_str("main")); let event_loop = EventLoop::new(); let _window = WindowBuilder::new() .with_title("A fantastic window!") .build(&event_loop) .unwrap(); - //console!(log, "Built window!"); + console::log_1(&JsValue::from_str("Created window")); event_loop.run(|event, _, control_flow| { - //console!(log, format!("{:?}", event)); + console::log_1(&JsValue::from_str(&format!("{:?}", event))); match event { Event::WindowEvent { @@ -30,4 +36,4 @@ fn main() { _ => *control_flow = ControlFlow::Wait, } }); -} +} \ No newline at end of file diff --git a/test.html b/test.html new file mode 100644 index 00000000..ee88fd5b --- /dev/null +++ b/test.html @@ -0,0 +1,20 @@ + + + + + + + + + + \ No newline at end of file diff --git a/test.ps1 b/test.ps1 new file mode 100644 index 00000000..81924470 --- /dev/null +++ b/test.ps1 @@ -0,0 +1,2 @@ +cargo build --target wasm32-unknown-unknown --features web_sys --example window +wasm-bindgen .\target\wasm32-unknown-unknown\debug\examples\window.wasm --out-dir . --target web \ No newline at end of file From 94f6294c0a1130fa37fa1f5f3ed9c623dc124b61 Mon Sep 17 00:00:00 2001 From: Ben Merritt Date: Thu, 6 Jun 2019 21:37:57 -0700 Subject: [PATCH 27/69] Prevent callbacks from being destroyed too early --- src/platform_impl/web_sys/event_loop.rs | 7 +++++-- src/platform_impl/web_sys/window.rs | 5 +++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/platform_impl/web_sys/event_loop.rs b/src/platform_impl/web_sys/event_loop.rs index 78a70f01..62bbd82a 100644 --- a/src/platform_impl/web_sys/event_loop.rs +++ b/src/platform_impl/web_sys/event_loop.rs @@ -259,7 +259,7 @@ fn add_event( { let elrs = elrs.clone(); - target.add_event_listener_with_callback(event, Closure::wrap(Box::new(move |event: E| { + let closure = 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, @@ -272,7 +272,10 @@ fn add_event( event_ref.cancel_bubble(); handler(&elrs, event); - }) as Box).as_ref().unchecked_ref()); + }) as Box); + + target.add_event_listener_with_callback(event, &closure.as_ref().unchecked_ref()); + closure.forget(); // TODO: don't leak this. } impl ELRShared { diff --git a/src/platform_impl/web_sys/window.rs b/src/platform_impl/web_sys/window.rs index 5a41c5f9..684c580c 100644 --- a/src/platform_impl/web_sys/window.rs +++ b/src/platform_impl/web_sys/window.rs @@ -69,12 +69,13 @@ impl Window { let runner = target.runner.clone(); let redraw = Box::new(move || { let runner = runner.clone(); - window().request_animation_frame(Closure::wrap(Box::new(move |_: f64| { + let closure = Closure::once_into_js(move |_: f64| { runner.send_event(Event::WindowEvent { window_id: RootWI(WindowId), event: WindowEvent::RedrawRequested }); - }) as Box).as_ref().unchecked_ref()); + }); + window().request_animation_frame(closure.as_ref().unchecked_ref()); }); let window = Window { From 91a511ba8c9f19f4a262dccf14f54ce0fed27f0b Mon Sep 17 00:00:00 2001 From: Ben Merritt Date: Fri, 7 Jun 2019 22:35:40 -0700 Subject: [PATCH 28/69] Replace JS snippet with throw_str --- src/platform_impl/web_sys/event_loop.rs | 9 +-------- src/platform_impl/web_sys/util.js | 3 --- 2 files changed, 1 insertion(+), 11 deletions(-) delete mode 100644 src/platform_impl/web_sys/util.js diff --git a/src/platform_impl/web_sys/event_loop.rs b/src/platform_impl/web_sys/event_loop.rs index 62bbd82a..d7c5779e 100644 --- a/src/platform_impl/web_sys/event_loop.rs +++ b/src/platform_impl/web_sys/event_loop.rs @@ -69,12 +69,6 @@ struct EventLoopRunner { event_handler: Box, &mut ControlFlow)>, } -#[wasm_bindgen(module = "/src/platform_impl/web_sys/util.js")] -extern "C" { - #[wasm_bindgen(js_name = "throwToEscapeEventLoop")] - fn throw_to_escape_event_loop(); -} - impl EventLoop { pub fn new() -> Self { EventLoop { @@ -161,8 +155,7 @@ impl EventLoop { // 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!(); + wasm_bindgen::throw_str("Using exceptions for control flow, don't mind me. This isn't actually an error!"); } pub fn create_proxy(&self) -> EventLoopProxy { diff --git a/src/platform_impl/web_sys/util.js b/src/platform_impl/web_sys/util.js deleted file mode 100644 index 57c40e22..00000000 --- a/src/platform_impl/web_sys/util.js +++ /dev/null @@ -1,3 +0,0 @@ -export function throwToEscapeEventLoop() { - throw "Using exceptions for control flow, don't mind me. This isn't actually an error!"; -} \ No newline at end of file From 7de1261555072f489fe0fe25eaa7e5ed22b2266c Mon Sep 17 00:00:00 2001 From: Ben Merritt Date: Fri, 7 Jun 2019 22:39:28 -0700 Subject: [PATCH 29/69] Fix some warnings --- examples/window.rs | 1 - src/platform_impl/web_sys/event_loop.rs | 2 +- src/platform_impl/web_sys/window.rs | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/examples/window.rs b/examples/window.rs index 797dbf41..b1adb682 100644 --- a/examples/window.rs +++ b/examples/window.rs @@ -3,7 +3,6 @@ extern crate winit; #[macro_use] extern crate stdweb; #[cfg(feature = "wasm-bindgen")] -#[macro_use] extern crate wasm_bindgen; #[cfg(feature = "wasm-bindgen")] extern crate web_sys; diff --git a/src/platform_impl/web_sys/event_loop.rs b/src/platform_impl/web_sys/event_loop.rs index d7c5779e..d9f43c88 100644 --- a/src/platform_impl/web_sys/event_loop.rs +++ b/src/platform_impl/web_sys/event_loop.rs @@ -265,7 +265,7 @@ fn add_event( event_ref.cancel_bubble(); handler(&elrs, event); - }) as Box); + }) as Box); target.add_event_listener_with_callback(event, &closure.as_ref().unchecked_ref()); closure.forget(); // TODO: don't leak this. diff --git a/src/platform_impl/web_sys/window.rs b/src/platform_impl/web_sys/window.rs index 684c580c..c5c0f88e 100644 --- a/src/platform_impl/web_sys/window.rs +++ b/src/platform_impl/web_sys/window.rs @@ -62,7 +62,7 @@ impl Window { let canvas: HtmlCanvasElement = element.unchecked_into(); document().body() .ok_or_else(|| os_error!(OsError("Failed to find body node".to_owned())))? - .append_child(&canvas); + .append_child(&canvas).map_err(|_| os_error!(OsError("Failed to append canvas".to_owned())))?; register(&target.runner, &canvas); From 5d31f733026897401cd87a8937f71d3d12ac0701 Mon Sep 17 00:00:00 2001 From: Ben Merritt Date: Mon, 17 Jun 2019 22:56:37 -0700 Subject: [PATCH 30/69] Clean up Cargo.toml after incorrect rebase --- Cargo.toml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e23c1d06..13ec2984 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -102,10 +102,6 @@ features = [ 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] stdweb = { path = "../stdweb", optional = true } instant = { version = "0.1", features = ["stdweb"] } From b571362bf116f069521619f6b7312f3545ee870f Mon Sep 17 00:00:00 2001 From: Ryan Goldstein Date: Thu, 20 Jun 2019 21:46:01 -0700 Subject: [PATCH 31/69] Fix a panic due to double-borrow --- Cargo.toml | 3 ++ src/platform_impl/stdweb/event_loop.rs | 49 +++++++++++++++----------- 2 files changed, 32 insertions(+), 20 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4ee84239..5127f78f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -77,3 +77,6 @@ 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/src/platform_impl/stdweb/event_loop.rs b/src/platform_impl/stdweb/event_loop.rs index 822ebcea..97d582ff 100644 --- a/src/platform_impl/stdweb/event_loop.rs +++ b/src/platform_impl/stdweb/event_loop.rs @@ -333,9 +333,9 @@ impl EventLoopRunnerShared { } // Determine if event handling is in process, and then release the borrow on the runner - match *self.0.runner.borrow() { + let (start_cause, event_is_start) = match *self.0.runner.borrow() { Some(ref runner) if !runner.is_busy => { - let (start_cause, event_is_start) = if let Event::NewEvents(cause) = event { + if let Event::NewEvents(cause) = event { (cause, true) } else { (match runner.control { @@ -359,29 +359,30 @@ impl EventLoopRunnerShared { }, ControlFlowStatus::Exit => { return; } }, false) - }; - let mut control = runner.control.to_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); } } _ => { + // 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); } } @@ -464,5 +465,13 @@ impl EventLoopRunnerShared { 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, + } + } } From cf28751ae3d1fcf39aa25fe4d82cd654a56948e8 Mon Sep 17 00:00:00 2001 From: Ryan Goldstein Date: Sun, 23 Jun 2019 14:38:16 -0700 Subject: [PATCH 32/69] Remove unnecessary set-to-wait in example --- examples/window.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/window.rs b/examples/window.rs index b33b5984..eb87fa81 100644 --- a/examples/window.rs +++ b/examples/window.rs @@ -23,7 +23,7 @@ fn main() { event: WindowEvent::CloseRequested, .. } => *control_flow = ControlFlow::Exit, - _ => *control_flow = ControlFlow::Wait, + _ => () } }); } From a0f280e71ffb94f2cb1c4cbf768f260cfaf0a485 Mon Sep 17 00:00:00 2001 From: Ryan Goldstein Date: Sun, 23 Jun 2019 14:38:35 -0700 Subject: [PATCH 33/69] Update how timeouts are cleared to avoid possible double-clearing --- src/platform_impl/stdweb/event_loop.rs | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/platform_impl/stdweb/event_loop.rs b/src/platform_impl/stdweb/event_loop.rs index 97d582ff..c7b2f85b 100644 --- a/src/platform_impl/stdweb/event_loop.rs +++ b/src/platform_impl/stdweb/event_loop.rs @@ -340,18 +340,14 @@ impl EventLoopRunnerShared { } else { (match runner.control { ControlFlowStatus::Init => StartCause::Init, - ControlFlowStatus::Poll { ref timeout } => { - timeout.clear(); - + ControlFlowStatus::Poll { .. } => { StartCause::Poll } ControlFlowStatus::Wait { start } => StartCause::WaitCancelled { start, requested_resume: None, }, - ControlFlowStatus::WaitUntil { start, end, ref timeout } => { - timeout.clear(); - + ControlFlowStatus::WaitUntil { start, end, .. } => { StartCause::WaitCancelled { start, requested_resume: Some(end) @@ -398,7 +394,7 @@ impl EventLoopRunnerShared { runner.is_busy = true; (runner.event_handler)(event, control); - + // Maintain closed state, even if the callback changes it if closed { *control = ControlFlow::Exit; @@ -425,11 +421,11 @@ impl EventLoopRunnerShared { // 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 control_flow_status = match control_flow { + 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)), 0) + timeout: window().set_clearable_timeout(move || cloned.send_event(Event::NewEvents(StartCause::Poll)), 1) } } ControlFlow::Wait => ControlFlowStatus::Wait { start: Instant::now() }, @@ -452,7 +448,15 @@ impl EventLoopRunnerShared { match *self.0.runner.borrow_mut() { Some(ref mut runner) => { - runner.control = control_flow_status; + // 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 => () } 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 34/69] 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; From b79089ea57beee822ca4fc542cc2497426a8b274 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 18:07:47 +0200 Subject: [PATCH 35/69] Implement `web_sys::Canvas` event listeners --- src/platform_impl/web/event_loop/runner.rs | 35 +++-- src/platform_impl/web/event_loop/state.rs | 10 +- .../web/event_loop/window_target.rs | 22 ++-- src/platform_impl/web/stdweb/mod.rs | 6 + src/platform_impl/web/web_sys/canvas.rs | 124 ++++++++++++++++-- src/platform_impl/web/web_sys/event.rs | 41 ++++++ src/platform_impl/web/web_sys/mod.rs | 11 ++ src/platform_impl/web/web_sys/timeout.rs | 40 +++++- src/platform_impl/web/window.rs | 15 +-- 9 files changed, 242 insertions(+), 62 deletions(-) create mode 100644 src/platform_impl/web/web_sys/event.rs diff --git a/src/platform_impl/web/event_loop/runner.rs b/src/platform_impl/web/event_loop/runner.rs index d3611845..c5bb274b 100644 --- a/src/platform_impl/web/event_loop/runner.rs +++ b/src/platform_impl/web/event_loop/runner.rs @@ -55,7 +55,7 @@ impl Shared { // 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() { + if self.is_closed() { return; } @@ -105,7 +105,7 @@ impl Shared { 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() { + if self.is_closed() { self.handle_event(Event::LoopDestroyed, &mut control); } } @@ -114,7 +114,7 @@ impl Shared { // // It should only ever be called from send_event fn handle_event(&self, event: Event, control: &mut root::ControlFlow) { - let closed = self.closed(); + let is_closed = self.is_closed(); match *self.0.runner.borrow_mut() { Some(ref mut runner) => { @@ -124,7 +124,7 @@ impl Shared { (runner.event_handler)(event, control); // Maintain closed state, even if the callback changes it - if closed { + if is_closed { *control = root::ControlFlow::Exit; } @@ -138,7 +138,7 @@ impl Shared { // 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() { + if !is_closed && self.0.runner.borrow().is_some() { // Take an event out of the queue and handle it if let Some(event) = self.0.events.borrow_mut().pop_front() { self.handle_event(event, control); @@ -149,13 +149,13 @@ impl Shared { // 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 { + let new_state = match control_flow { root::ControlFlow::Poll => { let cloned = self.clone(); State::Poll { timeout: backend::Timeout::new( move || cloned.send_event(Event::NewEvents(StartCause::Poll)), - Duration::from_millis(1), + Duration::from_millis(0), ), } } @@ -163,13 +163,16 @@ impl Shared { 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 }; + + let cloned = self.clone(); + State::WaitUntil { start, end, @@ -184,22 +187,14 @@ impl Shared { 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(), - _ => (), - } + runner.state = new_state; } None => (), } } - // Check if the event loop is currntly closed - fn closed(&self) -> bool { + // Check if the event loop is currently closed + fn is_closed(&self) -> bool { match *self.0.runner.borrow() { Some(ref runner) => runner.state.is_exit(), None => false, // If the event loop is None, it has not been intialised yet, so it cannot be closed @@ -209,7 +204,7 @@ impl Shared { // Get the current control flow state fn current_control_flow(&self) -> root::ControlFlow { match *self.0.runner.borrow() { - Some(ref runner) => runner.state.into(), + Some(ref runner) => runner.state.control_flow(), None => root::ControlFlow::Poll, } } diff --git a/src/platform_impl/web/event_loop/state.rs b/src/platform_impl/web/event_loop/state.rs index 952e8e2a..23e8045f 100644 --- a/src/platform_impl/web/event_loop/state.rs +++ b/src/platform_impl/web/event_loop/state.rs @@ -3,7 +3,7 @@ use crate::event_loop::ControlFlow; use instant::Instant; -#[derive(Debug, Clone, Copy)] +#[derive(Debug)] pub enum State { Init, WaitUntil { @@ -27,13 +27,11 @@ impl State { _ => false, } } -} -impl From for ControlFlow { - fn from(state: State) -> ControlFlow { - match state { + pub fn control_flow(&self) -> ControlFlow { + match self { State::Init => ControlFlow::Poll, - State::WaitUntil { end, .. } => ControlFlow::WaitUntil(end), + 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 index f0eacce2..2a46592b 100644 --- a/src/platform_impl/web/event_loop/window_target.rs +++ b/src/platform_impl/web/event_loop/window_target.rs @@ -35,10 +35,9 @@ impl WindowTarget { &self.runner } - pub fn register(&self, canvas: &backend::Canvas) { - let runner = &self.runner; - - canvas.on_mouse_out(|pointer_id| { + pub fn register(&self, canvas: &mut backend::Canvas) { + let runner = self.runner.clone(); + canvas.on_mouse_out(move |pointer_id| { runner.send_event(Event::WindowEvent { window_id: WindowId(window::Id), event: WindowEvent::CursorLeft { @@ -47,7 +46,8 @@ impl WindowTarget { }); }); - canvas.on_mouse_over(|pointer_id| { + let runner = self.runner.clone(); + canvas.on_mouse_over(move |pointer_id| { runner.send_event(Event::WindowEvent { window_id: WindowId(window::Id), event: WindowEvent::CursorEntered { @@ -56,7 +56,8 @@ impl WindowTarget { }); }); - canvas.on_mouse_move(|pointer_id, position, modifiers| { + let runner = self.runner.clone(); + canvas.on_mouse_move(move |pointer_id, position, modifiers| { runner.send_event(Event::WindowEvent { window_id: WindowId(window::Id), event: WindowEvent::CursorMoved { @@ -67,7 +68,8 @@ impl WindowTarget { }); }); - canvas.on_mouse_up(|pointer_id, button, modifiers| { + let runner = self.runner.clone(); + canvas.on_mouse_up(move |pointer_id, button, modifiers| { runner.send_event(Event::WindowEvent { window_id: WindowId(window::Id), event: WindowEvent::MouseInput { @@ -79,7 +81,8 @@ impl WindowTarget { }); }); - canvas.on_mouse_down(|pointer_id, button, modifiers| { + let runner = self.runner.clone(); + canvas.on_mouse_down(move |pointer_id, button, modifiers| { runner.send_event(Event::WindowEvent { window_id: WindowId(window::Id), event: WindowEvent::MouseInput { @@ -91,7 +94,8 @@ impl WindowTarget { }); }); - canvas.on_mouse_scroll(|pointer_id, delta, modifiers| { + let runner = self.runner.clone(); + canvas.on_mouse_scroll(move |pointer_id, delta, modifiers| { runner.send_event(Event::WindowEvent { window_id: WindowId(window::Id), event: WindowEvent::MouseWheel { diff --git a/src/platform_impl/web/stdweb/mod.rs b/src/platform_impl/web/stdweb/mod.rs index e69de29b..1552e637 100644 --- a/src/platform_impl/web/stdweb/mod.rs +++ b/src/platform_impl/web/stdweb/mod.rs @@ -0,0 +1,6 @@ +#[cfg(feature = "stdweb")] +impl WindowExtStdweb for RootWindow { + fn canvas(&self) -> CanvasElement { + self.window.canvas.clone() + } +} diff --git a/src/platform_impl/web/web_sys/canvas.rs b/src/platform_impl/web/web_sys/canvas.rs index e7470ac7..27481349 100644 --- a/src/platform_impl/web/web_sys/canvas.rs +++ b/src/platform_impl/web/web_sys/canvas.rs @@ -1,12 +1,20 @@ -use crate::dpi::LogicalSize; +use super::event; +use crate::dpi::{LogicalPosition, LogicalSize}; use crate::error::OsError as RootOE; +use crate::event::{ModifiersState, MouseButton, MouseScrollDelta}; use crate::platform_impl::OsError; -use wasm_bindgen::JsCast; -use web_sys::HtmlCanvasElement; +use wasm_bindgen::{closure::Closure, JsCast}; +use web_sys::{HtmlCanvasElement, PointerEvent, WheelEvent}; pub struct Canvas { raw: HtmlCanvasElement, + on_mouse_out: Option>, + on_mouse_over: Option>, + on_mouse_up: Option>, + on_mouse_down: Option>, + on_mouse_move: Option>, + on_mouse_scroll: Option>, } impl Canvas { @@ -25,7 +33,15 @@ impl Canvas { .append_child(&canvas) .map_err(|_| os_error!(OsError("Failed to append canvas".to_owned())))?; - Ok(Canvas { raw: canvas }) + Ok(Canvas { + raw: canvas, + on_mouse_out: None, + on_mouse_over: None, + on_mouse_up: None, + on_mouse_down: None, + on_mouse_move: None, + on_mouse_scroll: None, + }) } pub fn set_attribute(&self, attribute: &str, value: &str) { @@ -53,14 +69,98 @@ impl Canvas { self.raw.set_height(size.height as u32); } - pub fn raw(&self) -> HtmlCanvasElement { - self.raw.clone() + pub fn raw(&self) -> &HtmlCanvasElement { + &self.raw } - 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) {} + pub fn on_mouse_out(&mut self, mut handler: F) + where + F: 'static + FnMut(i32), + { + self.on_mouse_out = Some(self.add_event("pointerout", move |event: PointerEvent| { + handler(event.pointer_id()); + })); + } + + pub fn on_mouse_over(&mut self, mut handler: F) + where + F: 'static + FnMut(i32), + { + self.on_mouse_over = Some(self.add_event("pointerover", move |event: PointerEvent| { + handler(event.pointer_id()); + })); + } + + pub fn on_mouse_up(&mut self, mut handler: F) + where + F: 'static + FnMut(i32, MouseButton, ModifiersState), + { + self.on_mouse_up = Some(self.add_event("pointerup", move |event: PointerEvent| { + handler( + event.pointer_id(), + event::mouse_button(&event), + event::mouse_modifiers(&event), + ); + })); + } + + pub fn on_mouse_down(&mut self, mut handler: F) + where + F: 'static + FnMut(i32, MouseButton, ModifiersState), + { + self.on_mouse_down = Some(self.add_event("pointerdown", move |event: PointerEvent| { + handler( + event.pointer_id(), + event::mouse_button(&event), + event::mouse_modifiers(&event), + ); + })); + } + + pub fn on_mouse_move(&mut self, mut handler: F) + where + F: 'static + FnMut(i32, LogicalPosition, ModifiersState), + { + self.on_mouse_move = Some(self.add_event("pointermove", move |event: PointerEvent| { + handler( + event.pointer_id(), + event::mouse_position(&event), + event::mouse_modifiers(&event), + ); + })); + } + + pub fn on_mouse_scroll(&mut self, mut handler: F) + where + F: 'static + FnMut(i32, MouseScrollDelta, ModifiersState), + { + self.on_mouse_scroll = Some(self.add_event("wheel", move |event: WheelEvent| { + if let Some(delta) = event::mouse_scroll_delta(&event) { + handler(0, delta, event::mouse_modifiers(&event)); + } + })); + } + + fn add_event(&self, event_name: &str, mut handler: F) -> Closure + where + E: 'static + AsRef + wasm_bindgen::convert::FromWasmAbi, + F: 'static + FnMut(E), + { + let closure = Closure::wrap(Box::new(move |event: E| { + { + let event_ref = event.as_ref(); + event_ref.prevent_default(); + event_ref.stop_propagation(); + event_ref.cancel_bubble(); + } + + handler(event); + }) as Box); + + self.raw + .add_event_listener_with_callback(event_name, &closure.as_ref().unchecked_ref()) + .expect("Failed to add event listener with callback"); + + closure + } } diff --git a/src/platform_impl/web/web_sys/event.rs b/src/platform_impl/web/web_sys/event.rs new file mode 100644 index 00000000..d884487b --- /dev/null +++ b/src/platform_impl/web/web_sys/event.rs @@ -0,0 +1,41 @@ +use crate::dpi::LogicalPosition; +use crate::event::{ModifiersState, MouseButton, MouseScrollDelta}; + +use std::convert::TryInto; +use web_sys::{MouseEvent, WheelEvent}; + +pub fn mouse_button(event: &MouseEvent) -> MouseButton { + match event.button() { + 0 => MouseButton::Left, + 1 => MouseButton::Middle, + 2 => MouseButton::Right, + i => MouseButton::Other((i - 3).try_into().expect("very large mouse button value")), + } +} + +pub fn mouse_modifiers(event: &MouseEvent) -> ModifiersState { + ModifiersState { + shift: event.shift_key(), + ctrl: event.ctrl_key(), + alt: event.alt_key(), + logo: event.meta_key(), + } +} + +pub fn mouse_position(event: &MouseEvent) -> LogicalPosition { + LogicalPosition { + x: event.offset_x() as f64, + y: event.offset_y() as f64, + } +} + +pub fn mouse_scroll_delta(event: &WheelEvent) -> Option { + let x = event.delta_x(); + let y = event.delta_y(); + + match event.delta_mode() { + WheelEvent::DOM_DELTA_LINE => Some(MouseScrollDelta::LineDelta(x as f32, y as f32)), + WheelEvent::DOM_DELTA_PIXEL => Some(MouseScrollDelta::PixelDelta(LogicalPosition { x, y })), + _ => None, + } +} diff --git a/src/platform_impl/web/web_sys/mod.rs b/src/platform_impl/web/web_sys/mod.rs index 6c912c53..0e3dba74 100644 --- a/src/platform_impl/web/web_sys/mod.rs +++ b/src/platform_impl/web/web_sys/mod.rs @@ -1,11 +1,16 @@ mod canvas; mod document; +mod event; mod timeout; pub use self::canvas::Canvas; pub use self::document::Document; pub use self::timeout::Timeout; +use crate::platform::web::WindowExtWebSys; +use crate::window::Window; +use web_sys::HtmlCanvasElement; + pub fn request_animation_frame(f: F) where F: Fn(), @@ -15,3 +20,9 @@ where pub fn throw(msg: &str) { wasm_bindgen::throw_str(msg); } + +impl WindowExtWebSys for Window { + fn canvas(&self) -> HtmlCanvasElement { + self.window.canvas().raw().clone() + } +} diff --git a/src/platform_impl/web/web_sys/timeout.rs b/src/platform_impl/web/web_sys/timeout.rs index e285b7a0..7fe69ab7 100644 --- a/src/platform_impl/web/web_sys/timeout.rs +++ b/src/platform_impl/web/web_sys/timeout.rs @@ -1,12 +1,40 @@ use std::time::Duration; +use wasm_bindgen::closure::Closure; +use wasm_bindgen::JsCast; -#[derive(Debug, Clone, Copy)] -pub struct Timeout {} +#[derive(Debug)] +pub struct Timeout { + handle: i32, + _closure: Closure, +} impl Timeout { - pub fn new(f: F, duration: Duration) -> Timeout { - Timeout {} - } + pub fn new(f: F, duration: Duration) -> Timeout + where + F: 'static + FnMut(), + { + let window = web_sys::window().expect("Failed to obtain window"); - pub fn clear(self) {} + let closure = Closure::wrap(Box::new(f) as Box); + + let handle = window + .set_timeout_with_callback_and_timeout_and_arguments_0( + &closure.as_ref().unchecked_ref(), + duration.as_millis() as i32, + ) + .expect("Failed to set timeout"); + + Timeout { + handle, + _closure: closure, + } + } +} + +impl Drop for Timeout { + fn drop(&mut self) { + let window = web_sys::window().expect("Failed to obtain window"); + + window.clear_timeout_with_handle(self.handle); + } } diff --git a/src/platform_impl/web/window.rs b/src/platform_impl/web/window.rs index 813c441c..dc3230c0 100644 --- a/src/platform_impl/web/window.rs +++ b/src/platform_impl/web/window.rs @@ -24,9 +24,9 @@ impl Window { attr: WindowAttributes, _: PlatformSpecificBuilderAttributes, ) -> Result { - let canvas = backend::Canvas::create()?; + let mut canvas = backend::Canvas::create()?; - target.register(&canvas); + target.register(&mut canvas); let runner = target.runner.clone(); let redraw = Box::new(move || { @@ -58,6 +58,10 @@ impl Window { Ok(window) } + pub fn canvas(&self) -> &backend::Canvas { + &self.canvas + } + pub fn set_title(&self, title: &str) { backend::Document::set_title(title); } @@ -256,13 +260,6 @@ impl Window { } } -#[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; From 9c5657b86c867287b7958baf714e8190b1c647b2 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 18:19:22 +0200 Subject: [PATCH 36/69] Remove `Canvas` when dropped --- src/platform_impl/web/web_sys/canvas.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/platform_impl/web/web_sys/canvas.rs b/src/platform_impl/web/web_sys/canvas.rs index 27481349..845c3ee3 100644 --- a/src/platform_impl/web/web_sys/canvas.rs +++ b/src/platform_impl/web/web_sys/canvas.rs @@ -17,6 +17,12 @@ pub struct Canvas { on_mouse_scroll: Option>, } +impl Drop for Canvas { + fn drop(&mut self) { + self.raw.remove(); + } +} + impl Canvas { pub fn create() -> Result { let window = web_sys::window().expect("Failed to obtain window"); From d5368d7979eefe0c2f7ae3f81fc2913d6ebbab68 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 18:39:41 +0200 Subject: [PATCH 37/69] Implement `Canvas::request_redraw` --- src/platform_impl/web/web_sys/canvas.rs | 12 +++++++++++- src/platform_impl/web/web_sys/mod.rs | 6 ------ src/platform_impl/web/window.rs | 24 +++++++++--------------- 3 files changed, 20 insertions(+), 22 deletions(-) diff --git a/src/platform_impl/web/web_sys/canvas.rs b/src/platform_impl/web/web_sys/canvas.rs index 845c3ee3..3108e1c2 100644 --- a/src/platform_impl/web/web_sys/canvas.rs +++ b/src/platform_impl/web/web_sys/canvas.rs @@ -9,6 +9,7 @@ use web_sys::{HtmlCanvasElement, PointerEvent, WheelEvent}; pub struct Canvas { raw: HtmlCanvasElement, + on_redraw: Closure, on_mouse_out: Option>, on_mouse_over: Option>, on_mouse_up: Option>, @@ -24,7 +25,10 @@ impl Drop for Canvas { } impl Canvas { - pub fn create() -> Result { + pub fn create(on_redraw: F) -> Result + where + F: 'static + Fn(), + { let window = web_sys::window().expect("Failed to obtain window"); let document = window.document().expect("Failed to obtain document"); @@ -41,6 +45,7 @@ impl Canvas { Ok(Canvas { raw: canvas, + on_redraw: Closure::wrap(Box::new(on_redraw) as Box), on_mouse_out: None, on_mouse_over: None, on_mouse_up: None, @@ -79,6 +84,11 @@ impl Canvas { &self.raw } + pub fn request_redraw(&self) { + let window = web_sys::window().expect("Failed to obtain window"); + window.request_animation_frame(&self.on_redraw.as_ref().unchecked_ref()); + } + pub fn on_mouse_out(&mut self, mut handler: F) where F: 'static + FnMut(i32), diff --git a/src/platform_impl/web/web_sys/mod.rs b/src/platform_impl/web/web_sys/mod.rs index 0e3dba74..40d8ff20 100644 --- a/src/platform_impl/web/web_sys/mod.rs +++ b/src/platform_impl/web/web_sys/mod.rs @@ -11,12 +11,6 @@ use crate::platform::web::WindowExtWebSys; use crate::window::Window; use web_sys::HtmlCanvasElement; -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/window.rs b/src/platform_impl/web/window.rs index dc3230c0..4dc06f9a 100644 --- a/src/platform_impl/web/window.rs +++ b/src/platform_impl/web/window.rs @@ -13,7 +13,6 @@ use std::collections::VecDeque; pub struct Window { canvas: backend::Canvas, - redraw: Box, previous_pointer: RefCell<&'static str>, position: RefCell, } @@ -24,24 +23,19 @@ impl Window { attr: WindowAttributes, _: PlatformSpecificBuilderAttributes, ) -> Result { - let mut canvas = backend::Canvas::create()?; + let runner = target.runner.clone(); + + let mut canvas = backend::Canvas::create(move || { + runner.send_event(Event::WindowEvent { + window_id: RootWI(Id), + event: WindowEvent::RedrawRequested, + }) + })?; target.register(&mut canvas); - let 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 }), }; @@ -71,7 +65,7 @@ impl Window { } pub fn request_redraw(&self) { - (self.redraw)(); + self.canvas.request_redraw(); } pub fn outer_position(&self) -> Result { From 8ad078b9645ab895989a6c247f1fce88ba82acb0 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 21:01:13 +0200 Subject: [PATCH 38/69] Implement keyboard and blur/focus events --- src/platform_impl/web/event_loop/mod.rs | 49 +---- .../web/event_loop/window_target.rs | 56 +++++- src/platform_impl/web/web_sys/canvas.rs | 72 ++++++- src/platform_impl/web/web_sys/document.rs | 13 -- src/platform_impl/web/web_sys/event.rs | 183 +++++++++++++++++- src/platform_impl/web/web_sys/mod.rs | 2 - src/platform_impl/web/window.rs | 2 +- 7 files changed, 303 insertions(+), 74 deletions(-) delete mode 100644 src/platform_impl/web/web_sys/document.rs diff --git a/src/platform_impl/web/event_loop/mod.rs b/src/platform_impl/web/event_loop/mod.rs index aad23ad9..7124cee7 100644 --- a/src/platform_impl/web/event_loop/mod.rs +++ b/src/platform_impl/web/event_loop/mod.rs @@ -7,9 +7,8 @@ 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::Event; use crate::event_loop as root; -use crate::window::WindowId; use std::collections::{vec_deque::IntoIter as VecDequeIter, VecDeque}; use std::marker::PhantomData; @@ -45,54 +44,10 @@ impl EventLoop { _marker: PhantomData, }; - let runner = self.elw.p.run(Box::new(move |event, flow| { + 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( diff --git a/src/platform_impl/web/event_loop/window_target.rs b/src/platform_impl/web/event_loop/window_target.rs index 2a46592b..46200d3b 100644 --- a/src/platform_impl/web/event_loop/window_target.rs +++ b/src/platform_impl/web/event_loop/window_target.rs @@ -1,5 +1,5 @@ use super::{backend, device, proxy::Proxy, runner, window}; -use crate::event::{DeviceId, ElementState, Event, TouchPhase, WindowEvent}; +use crate::event::{DeviceId, ElementState, Event, KeyboardInput, TouchPhase, WindowEvent}; use crate::event_loop::ControlFlow; use crate::window::WindowId; use std::clone::Clone; @@ -27,15 +27,59 @@ impl WindowTarget { Proxy::new(self.runner.clone()) } - pub fn run( - &self, - event_handler: Box, &mut ControlFlow)>, - ) -> &runner::Shared { + pub fn run(&self, event_handler: Box, &mut ControlFlow)>) { self.runner.set_listener(event_handler); - &self.runner } pub fn register(&self, canvas: &mut backend::Canvas) { + let runner = self.runner.clone(); + canvas.on_blur(move || { + runner.send_event(Event::WindowEvent { + window_id: WindowId(window::Id), + event: WindowEvent::Focused(false), + }); + }); + + let runner = self.runner.clone(); + canvas.on_focus(move || { + runner.send_event(Event::WindowEvent { + window_id: WindowId(window::Id), + event: WindowEvent::Focused(true), + }); + }); + + let runner = self.runner.clone(); + canvas.on_key_down(move |scancode, virtual_keycode, modifiers| { + runner.send_event(Event::WindowEvent { + window_id: WindowId(window::Id), + event: WindowEvent::KeyboardInput { + device_id: DeviceId(unsafe { device::Id::dummy() }), + input: KeyboardInput { + scancode, + state: ElementState::Pressed, + virtual_keycode, + modifiers, + }, + }, + }); + }); + + let runner = self.runner.clone(); + canvas.on_key_up(move |scancode, virtual_keycode, modifiers| { + runner.send_event(Event::WindowEvent { + window_id: WindowId(window::Id), + event: WindowEvent::KeyboardInput { + device_id: DeviceId(unsafe { device::Id::dummy() }), + input: KeyboardInput { + scancode, + state: ElementState::Released, + virtual_keycode, + modifiers, + }, + }, + }); + }); + let runner = self.runner.clone(); canvas.on_mouse_out(move |pointer_id| { runner.send_event(Event::WindowEvent { diff --git a/src/platform_impl/web/web_sys/canvas.rs b/src/platform_impl/web/web_sys/canvas.rs index 3108e1c2..6007ba82 100644 --- a/src/platform_impl/web/web_sys/canvas.rs +++ b/src/platform_impl/web/web_sys/canvas.rs @@ -1,15 +1,19 @@ use super::event; use crate::dpi::{LogicalPosition, LogicalSize}; use crate::error::OsError as RootOE; -use crate::event::{ModifiersState, MouseButton, MouseScrollDelta}; +use crate::event::{ModifiersState, MouseButton, MouseScrollDelta, ScanCode, VirtualKeyCode}; use crate::platform_impl::OsError; use wasm_bindgen::{closure::Closure, JsCast}; -use web_sys::{HtmlCanvasElement, PointerEvent, WheelEvent}; +use web_sys::{FocusEvent, HtmlCanvasElement, KeyboardEvent, PointerEvent, WheelEvent}; pub struct Canvas { raw: HtmlCanvasElement, on_redraw: Closure, + on_focus: Option>, + on_blur: Option>, + on_key_up: Option>, + on_key_down: Option>, on_mouse_out: Option>, on_mouse_over: Option>, on_mouse_up: Option>, @@ -43,9 +47,18 @@ impl Canvas { .append_child(&canvas) .map_err(|_| os_error!(OsError("Failed to append canvas".to_owned())))?; + // TODO: Set up unique ids + canvas + .set_attribute("tabindex", "0") + .expect("Failed to set a tabindex"); + Ok(Canvas { raw: canvas, on_redraw: Closure::wrap(Box::new(on_redraw) as Box), + on_blur: None, + on_focus: None, + on_key_up: None, + on_key_down: None, on_mouse_out: None, on_mouse_over: None, on_mouse_up: None, @@ -86,7 +99,53 @@ impl Canvas { pub fn request_redraw(&self) { let window = web_sys::window().expect("Failed to obtain window"); - window.request_animation_frame(&self.on_redraw.as_ref().unchecked_ref()); + window + .request_animation_frame(&self.on_redraw.as_ref().unchecked_ref()) + .expect("Failed to request animation frame"); + } + + pub fn on_blur(&mut self, mut handler: F) + where + F: 'static + FnMut(), + { + self.on_blur = Some(self.add_event("blur", move |_: FocusEvent| { + handler(); + })); + } + + pub fn on_focus(&mut self, mut handler: F) + where + F: 'static + FnMut(), + { + self.on_focus = Some(self.add_event("focus", move |_: FocusEvent| { + handler(); + })); + } + + pub fn on_key_up(&mut self, mut handler: F) + where + F: 'static + FnMut(ScanCode, Option, ModifiersState), + { + self.on_key_up = Some(self.add_event("keyup", move |event: KeyboardEvent| { + handler( + event::scan_code(&event), + event::virtual_key_code(&event), + event::keyboard_modifiers(&event), + ); + })); + } + + pub fn on_key_down(&mut self, mut handler: F) + where + F: 'static + FnMut(ScanCode, Option, ModifiersState), + { + self.on_key_down = Some(self.add_event("keydown", move |event: KeyboardEvent| { + handler( + event::scan_code(&event), + event::virtual_key_code(&event), + event::keyboard_modifiers(&event), + ); + })); } pub fn on_mouse_out(&mut self, mut handler: F) @@ -124,7 +183,14 @@ impl Canvas { where F: 'static + FnMut(i32, MouseButton, ModifiersState), { + let canvas = self.raw.clone(); + self.on_mouse_down = Some(self.add_event("pointerdown", move |event: PointerEvent| { + // We focus the canvas manually when the user clicks on it. + // This is necessary because we are preventing the default event behavior + // in `add_event` + canvas.focus().expect("Failed to focus canvas"); + handler( event.pointer_id(), event::mouse_button(&event), diff --git a/src/platform_impl/web/web_sys/document.rs b/src/platform_impl/web/web_sys/document.rs deleted file mode 100644 index 6a85e770..00000000 --- a/src/platform_impl/web/web_sys/document.rs +++ /dev/null @@ -1,13 +0,0 @@ -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/event.rs b/src/platform_impl/web/web_sys/event.rs index d884487b..a2914ccd 100644 --- a/src/platform_impl/web/web_sys/event.rs +++ b/src/platform_impl/web/web_sys/event.rs @@ -1,8 +1,8 @@ use crate::dpi::LogicalPosition; -use crate::event::{ModifiersState, MouseButton, MouseScrollDelta}; +use crate::event::{ModifiersState, MouseButton, MouseScrollDelta, ScanCode, VirtualKeyCode}; use std::convert::TryInto; -use web_sys::{MouseEvent, WheelEvent}; +use web_sys::{KeyboardEvent, MouseEvent, WheelEvent}; pub fn mouse_button(event: &MouseEvent) -> MouseButton { match event.button() { @@ -39,3 +39,182 @@ pub fn mouse_scroll_delta(event: &WheelEvent) -> Option { _ => None, } } + +pub fn scan_code(event: &KeyboardEvent) -> ScanCode { + match event.key_code() { + 0 => event.char_code(), + i => i, + } +} + +pub fn virtual_key_code(event: &KeyboardEvent) -> Option { + Some(match &event.code()[..] { + "Digit1" => VirtualKeyCode::Key1, + "Digit2" => VirtualKeyCode::Key2, + "Digit3" => VirtualKeyCode::Key3, + "Digit4" => VirtualKeyCode::Key4, + "Digit5" => VirtualKeyCode::Key5, + "Digit6" => VirtualKeyCode::Key6, + "Digit7" => VirtualKeyCode::Key7, + "Digit8" => VirtualKeyCode::Key8, + "Digit9" => VirtualKeyCode::Key9, + "Digit0" => VirtualKeyCode::Key0, + "KeyA" => VirtualKeyCode::A, + "KeyB" => VirtualKeyCode::B, + "KeyC" => VirtualKeyCode::C, + "KeyD" => VirtualKeyCode::D, + "KeyE" => VirtualKeyCode::E, + "KeyF" => VirtualKeyCode::F, + "KeyG" => VirtualKeyCode::G, + "KeyH" => VirtualKeyCode::H, + "KeyI" => VirtualKeyCode::I, + "KeyJ" => VirtualKeyCode::J, + "KeyK" => VirtualKeyCode::K, + "KeyL" => VirtualKeyCode::L, + "KeyM" => VirtualKeyCode::M, + "KeyN" => VirtualKeyCode::N, + "KeyO" => VirtualKeyCode::O, + "KeyP" => VirtualKeyCode::P, + "KeyQ" => VirtualKeyCode::Q, + "KeyR" => VirtualKeyCode::R, + "KeyS" => VirtualKeyCode::S, + "KeyT" => VirtualKeyCode::T, + "KeyU" => VirtualKeyCode::U, + "KeyV" => VirtualKeyCode::V, + "KeyW" => VirtualKeyCode::W, + "KeyX" => VirtualKeyCode::X, + "KeyY" => VirtualKeyCode::Y, + "KeyZ" => VirtualKeyCode::Z, + "Escape" => VirtualKeyCode::Escape, + "F1" => VirtualKeyCode::F1, + "F2" => VirtualKeyCode::F2, + "F3" => VirtualKeyCode::F3, + "F4" => VirtualKeyCode::F4, + "F5" => VirtualKeyCode::F5, + "F6" => VirtualKeyCode::F6, + "F7" => VirtualKeyCode::F7, + "F8" => VirtualKeyCode::F8, + "F9" => VirtualKeyCode::F9, + "F10" => VirtualKeyCode::F10, + "F11" => VirtualKeyCode::F11, + "F12" => VirtualKeyCode::F12, + "F13" => VirtualKeyCode::F13, + "F14" => VirtualKeyCode::F14, + "F15" => VirtualKeyCode::F15, + "F16" => VirtualKeyCode::F16, + "F17" => VirtualKeyCode::F17, + "F18" => VirtualKeyCode::F18, + "F19" => VirtualKeyCode::F19, + "F20" => VirtualKeyCode::F20, + "F21" => VirtualKeyCode::F21, + "F22" => VirtualKeyCode::F22, + "F23" => VirtualKeyCode::F23, + "F24" => VirtualKeyCode::F24, + "PrintScreen" => VirtualKeyCode::Snapshot, + "ScrollLock" => VirtualKeyCode::Scroll, + "Pause" => VirtualKeyCode::Pause, + "Insert" => VirtualKeyCode::Insert, + "Home" => VirtualKeyCode::Home, + "Delete" => VirtualKeyCode::Delete, + "End" => VirtualKeyCode::End, + "PageDown" => VirtualKeyCode::PageDown, + "PageUp" => VirtualKeyCode::PageUp, + "ArrowLeft" => VirtualKeyCode::Left, + "ArrowUp" => VirtualKeyCode::Up, + "ArrowRight" => VirtualKeyCode::Right, + "ArrowDown" => VirtualKeyCode::Down, + "Backspace" => VirtualKeyCode::Back, + "Enter" => VirtualKeyCode::Return, + "Space" => VirtualKeyCode::Space, + "Compose" => VirtualKeyCode::Compose, + "Caret" => VirtualKeyCode::Caret, + "NumLock" => VirtualKeyCode::Numlock, + "Numpad0" => VirtualKeyCode::Numpad0, + "Numpad1" => VirtualKeyCode::Numpad1, + "Numpad2" => VirtualKeyCode::Numpad2, + "Numpad3" => VirtualKeyCode::Numpad3, + "Numpad4" => VirtualKeyCode::Numpad4, + "Numpad5" => VirtualKeyCode::Numpad5, + "Numpad6" => VirtualKeyCode::Numpad6, + "Numpad7" => VirtualKeyCode::Numpad7, + "Numpad8" => VirtualKeyCode::Numpad8, + "Numpad9" => VirtualKeyCode::Numpad9, + "AbntC1" => VirtualKeyCode::AbntC1, + "AbntC2" => VirtualKeyCode::AbntC2, + "NumpadAdd" => VirtualKeyCode::Add, + "Quote" => VirtualKeyCode::Apostrophe, + "Apps" => VirtualKeyCode::Apps, + "At" => VirtualKeyCode::At, + "Ax" => VirtualKeyCode::Ax, + "Backslash" => VirtualKeyCode::Backslash, + "Calculator" => VirtualKeyCode::Calculator, + "Capital" => VirtualKeyCode::Capital, + "Semicolon" => VirtualKeyCode::Semicolon, + "Comma" => VirtualKeyCode::Comma, + "Convert" => VirtualKeyCode::Convert, + "NumpadDecimal" => VirtualKeyCode::Decimal, + "NumpadDivide" => VirtualKeyCode::Divide, + "Equal" => VirtualKeyCode::Equals, + "Backquote" => VirtualKeyCode::Grave, + "Kana" => VirtualKeyCode::Kana, + "Kanji" => VirtualKeyCode::Kanji, + "AltLeft" => VirtualKeyCode::LAlt, + "BracketLeft" => VirtualKeyCode::LBracket, + "ControlLeft" => VirtualKeyCode::LControl, + "ShiftLeft" => VirtualKeyCode::LShift, + "MetaLeft" => VirtualKeyCode::LWin, + "Mail" => VirtualKeyCode::Mail, + "MediaSelect" => VirtualKeyCode::MediaSelect, + "MediaStop" => VirtualKeyCode::MediaStop, + "Minus" => VirtualKeyCode::Minus, + "NumpadMultiply" => VirtualKeyCode::Multiply, + "Mute" => VirtualKeyCode::Mute, + "LaunchMyComputer" => VirtualKeyCode::MyComputer, + "NavigateForward" => VirtualKeyCode::NavigateForward, + "NavigateBackward" => VirtualKeyCode::NavigateBackward, + "NextTrack" => VirtualKeyCode::NextTrack, + "NoConvert" => VirtualKeyCode::NoConvert, + "NumpadComma" => VirtualKeyCode::NumpadComma, + "NumpadEnter" => VirtualKeyCode::NumpadEnter, + "NumpadEquals" => VirtualKeyCode::NumpadEquals, + "OEM102" => VirtualKeyCode::OEM102, + "Period" => VirtualKeyCode::Period, + "PlayPause" => VirtualKeyCode::PlayPause, + "Power" => VirtualKeyCode::Power, + "PrevTrack" => VirtualKeyCode::PrevTrack, + "AltRight" => VirtualKeyCode::RAlt, + "BracketRight" => VirtualKeyCode::RBracket, + "ControlRight" => VirtualKeyCode::RControl, + "ShiftRight" => VirtualKeyCode::RShift, + "MetaRight" => VirtualKeyCode::RWin, + "Slash" => VirtualKeyCode::Slash, + "Sleep" => VirtualKeyCode::Sleep, + "Stop" => VirtualKeyCode::Stop, + "NumpadSubtract" => VirtualKeyCode::Subtract, + "Sysrq" => VirtualKeyCode::Sysrq, + "Tab" => VirtualKeyCode::Tab, + "Underline" => VirtualKeyCode::Underline, + "Unlabeled" => VirtualKeyCode::Unlabeled, + "AudioVolumeDown" => VirtualKeyCode::VolumeDown, + "AudioVolumeUp" => VirtualKeyCode::VolumeUp, + "Wake" => VirtualKeyCode::Wake, + "WebBack" => VirtualKeyCode::WebBack, + "WebFavorites" => VirtualKeyCode::WebFavorites, + "WebForward" => VirtualKeyCode::WebForward, + "WebHome" => VirtualKeyCode::WebHome, + "WebRefresh" => VirtualKeyCode::WebRefresh, + "WebSearch" => VirtualKeyCode::WebSearch, + "WebStop" => VirtualKeyCode::WebStop, + "Yen" => VirtualKeyCode::Yen, + _ => return None, + }) +} + +pub fn keyboard_modifiers(event: &KeyboardEvent) -> ModifiersState { + ModifiersState { + shift: event.shift_key(), + ctrl: event.ctrl_key(), + alt: event.alt_key(), + logo: event.meta_key(), + } +} diff --git a/src/platform_impl/web/web_sys/mod.rs b/src/platform_impl/web/web_sys/mod.rs index 40d8ff20..33e217bd 100644 --- a/src/platform_impl/web/web_sys/mod.rs +++ b/src/platform_impl/web/web_sys/mod.rs @@ -1,10 +1,8 @@ mod canvas; -mod document; mod event; mod timeout; pub use self::canvas::Canvas; -pub use self::document::Document; pub use self::timeout::Timeout; use crate::platform::web::WindowExtWebSys; diff --git a/src/platform_impl/web/window.rs b/src/platform_impl/web/window.rs index 4dc06f9a..f8ef6cd0 100644 --- a/src/platform_impl/web/window.rs +++ b/src/platform_impl/web/window.rs @@ -57,7 +57,7 @@ impl Window { } pub fn set_title(&self, title: &str) { - backend::Document::set_title(title); + self.canvas.set_attribute("alt", title); } pub fn set_visible(&self, _visible: bool) { From 8f66d9691541d25dffff2c3fde3294d1ff074fe9 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 21:18:11 +0200 Subject: [PATCH 39/69] Support `ReceivedCharacter` event --- .../web/event_loop/window_target.rs | 8 ++++++++ src/platform_impl/web/web_sys/canvas.rs | 19 +++++++++++-------- src/platform_impl/web/web_sys/event.rs | 4 ++++ 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/platform_impl/web/event_loop/window_target.rs b/src/platform_impl/web/event_loop/window_target.rs index 46200d3b..5d2f06b2 100644 --- a/src/platform_impl/web/event_loop/window_target.rs +++ b/src/platform_impl/web/event_loop/window_target.rs @@ -80,6 +80,14 @@ impl WindowTarget { }); }); + let runner = self.runner.clone(); + canvas.on_key_press(move |char_code| { + runner.send_event(Event::WindowEvent { + window_id: WindowId(window::Id), + event: WindowEvent::ReceivedCharacter(char_code), + }); + }); + let runner = self.runner.clone(); canvas.on_mouse_out(move |pointer_id| { runner.send_event(Event::WindowEvent { diff --git a/src/platform_impl/web/web_sys/canvas.rs b/src/platform_impl/web/web_sys/canvas.rs index 6007ba82..ab7cd571 100644 --- a/src/platform_impl/web/web_sys/canvas.rs +++ b/src/platform_impl/web/web_sys/canvas.rs @@ -14,6 +14,7 @@ pub struct Canvas { on_blur: Option>, on_key_up: Option>, on_key_down: Option>, + on_key_press: Option>, on_mouse_out: Option>, on_mouse_over: Option>, on_mouse_up: Option>, @@ -59,6 +60,7 @@ impl Canvas { on_focus: None, on_key_up: None, on_key_down: None, + on_key_press: None, on_mouse_out: None, on_mouse_over: None, on_mouse_up: None, @@ -148,6 +150,15 @@ impl Canvas { })); } + pub fn on_key_press(&mut self, mut handler: F) + where + F: 'static + FnMut(char), + { + self.on_key_press = Some(self.add_event("keypress", move |event: KeyboardEvent| { + handler(event::codepoint(&event)); + })); + } + pub fn on_mouse_out(&mut self, mut handler: F) where F: 'static + FnMut(i32), @@ -183,14 +194,7 @@ impl Canvas { where F: 'static + FnMut(i32, MouseButton, ModifiersState), { - let canvas = self.raw.clone(); - self.on_mouse_down = Some(self.add_event("pointerdown", move |event: PointerEvent| { - // We focus the canvas manually when the user clicks on it. - // This is necessary because we are preventing the default event behavior - // in `add_event` - canvas.focus().expect("Failed to focus canvas"); - handler( event.pointer_id(), event::mouse_button(&event), @@ -231,7 +235,6 @@ impl Canvas { let closure = Closure::wrap(Box::new(move |event: E| { { let event_ref = event.as_ref(); - event_ref.prevent_default(); event_ref.stop_propagation(); event_ref.cancel_bubble(); } diff --git a/src/platform_impl/web/web_sys/event.rs b/src/platform_impl/web/web_sys/event.rs index a2914ccd..876fbc22 100644 --- a/src/platform_impl/web/web_sys/event.rs +++ b/src/platform_impl/web/web_sys/event.rs @@ -218,3 +218,7 @@ pub fn keyboard_modifiers(event: &KeyboardEvent) -> ModifiersState { logo: event.meta_key(), } } + +pub fn codepoint(event: &KeyboardEvent) -> char { + event.key().chars().next().unwrap() +} From 1596cc5d9ee0f34637a5fe17c0de3dd5bc4ebec4 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 21:36:24 +0200 Subject: [PATCH 40/69] Avoid leaking implementation details in `Canvas` API --- .../web/event_loop/window_target.rs | 18 +- src/platform_impl/web/web_sys/canvas.rs | 82 ++-- src/platform_impl/web/web_sys/event.rs | 3 + src/platform_impl/web_sys/event_loop.rs | 378 ------------------ src/platform_impl/web_sys/events.rs | 201 ---------- src/platform_impl/web_sys/mod.rs | 30 -- src/platform_impl/web_sys/window.rs | 330 --------------- 7 files changed, 56 insertions(+), 986 deletions(-) delete mode 100644 src/platform_impl/web_sys/event_loop.rs delete mode 100644 src/platform_impl/web_sys/events.rs delete mode 100644 src/platform_impl/web_sys/mod.rs delete mode 100644 src/platform_impl/web_sys/window.rs diff --git a/src/platform_impl/web/event_loop/window_target.rs b/src/platform_impl/web/event_loop/window_target.rs index 5d2f06b2..c8e608e4 100644 --- a/src/platform_impl/web/event_loop/window_target.rs +++ b/src/platform_impl/web/event_loop/window_target.rs @@ -49,7 +49,7 @@ impl WindowTarget { }); let runner = self.runner.clone(); - canvas.on_key_down(move |scancode, virtual_keycode, modifiers| { + canvas.on_keyboard_press(move |scancode, virtual_keycode, modifiers| { runner.send_event(Event::WindowEvent { window_id: WindowId(window::Id), event: WindowEvent::KeyboardInput { @@ -65,7 +65,7 @@ impl WindowTarget { }); let runner = self.runner.clone(); - canvas.on_key_up(move |scancode, virtual_keycode, modifiers| { + canvas.on_keyboard_release(move |scancode, virtual_keycode, modifiers| { runner.send_event(Event::WindowEvent { window_id: WindowId(window::Id), event: WindowEvent::KeyboardInput { @@ -81,7 +81,7 @@ impl WindowTarget { }); let runner = self.runner.clone(); - canvas.on_key_press(move |char_code| { + canvas.on_received_character(move |char_code| { runner.send_event(Event::WindowEvent { window_id: WindowId(window::Id), event: WindowEvent::ReceivedCharacter(char_code), @@ -89,7 +89,7 @@ impl WindowTarget { }); let runner = self.runner.clone(); - canvas.on_mouse_out(move |pointer_id| { + canvas.on_cursor_leave(move |pointer_id| { runner.send_event(Event::WindowEvent { window_id: WindowId(window::Id), event: WindowEvent::CursorLeft { @@ -99,7 +99,7 @@ impl WindowTarget { }); let runner = self.runner.clone(); - canvas.on_mouse_over(move |pointer_id| { + canvas.on_cursor_enter(move |pointer_id| { runner.send_event(Event::WindowEvent { window_id: WindowId(window::Id), event: WindowEvent::CursorEntered { @@ -109,7 +109,7 @@ impl WindowTarget { }); let runner = self.runner.clone(); - canvas.on_mouse_move(move |pointer_id, position, modifiers| { + canvas.on_cursor_move(move |pointer_id, position, modifiers| { runner.send_event(Event::WindowEvent { window_id: WindowId(window::Id), event: WindowEvent::CursorMoved { @@ -121,7 +121,7 @@ impl WindowTarget { }); let runner = self.runner.clone(); - canvas.on_mouse_up(move |pointer_id, button, modifiers| { + canvas.on_mouse_press(move |pointer_id, button, modifiers| { runner.send_event(Event::WindowEvent { window_id: WindowId(window::Id), event: WindowEvent::MouseInput { @@ -134,7 +134,7 @@ impl WindowTarget { }); let runner = self.runner.clone(); - canvas.on_mouse_down(move |pointer_id, button, modifiers| { + canvas.on_mouse_release(move |pointer_id, button, modifiers| { runner.send_event(Event::WindowEvent { window_id: WindowId(window::Id), event: WindowEvent::MouseInput { @@ -147,7 +147,7 @@ impl WindowTarget { }); let runner = self.runner.clone(); - canvas.on_mouse_scroll(move |pointer_id, delta, modifiers| { + canvas.on_mouse_wheel(move |pointer_id, delta, modifiers| { runner.send_event(Event::WindowEvent { window_id: WindowId(window::Id), event: WindowEvent::MouseWheel { diff --git a/src/platform_impl/web/web_sys/canvas.rs b/src/platform_impl/web/web_sys/canvas.rs index ab7cd571..6d1dd373 100644 --- a/src/platform_impl/web/web_sys/canvas.rs +++ b/src/platform_impl/web/web_sys/canvas.rs @@ -12,15 +12,15 @@ pub struct Canvas { on_redraw: Closure, on_focus: Option>, on_blur: Option>, - on_key_up: Option>, - on_key_down: Option>, - on_key_press: Option>, - on_mouse_out: Option>, - on_mouse_over: Option>, - on_mouse_up: Option>, - on_mouse_down: Option>, - on_mouse_move: Option>, - on_mouse_scroll: Option>, + on_keyboard_release: Option>, + on_keyboard_press: Option>, + on_received_character: Option>, + on_cursor_leave: Option>, + on_cursor_enter: Option>, + on_cursor_move: Option>, + on_mouse_press: Option>, + on_mouse_release: Option>, + on_mouse_wheel: Option>, } impl Drop for Canvas { @@ -58,15 +58,15 @@ impl Canvas { on_redraw: Closure::wrap(Box::new(on_redraw) as Box), on_blur: None, on_focus: None, - on_key_up: None, - on_key_down: None, - on_key_press: None, - on_mouse_out: None, - on_mouse_over: None, - on_mouse_up: None, - on_mouse_down: None, - on_mouse_move: None, - on_mouse_scroll: None, + on_keyboard_release: None, + on_keyboard_press: None, + on_received_character: None, + on_cursor_leave: None, + on_cursor_enter: None, + on_cursor_move: None, + on_mouse_release: None, + on_mouse_press: None, + on_mouse_wheel: None, }) } @@ -124,11 +124,11 @@ impl Canvas { })); } - pub fn on_key_up(&mut self, mut handler: F) + pub fn on_keyboard_release(&mut self, mut handler: F) where F: 'static + FnMut(ScanCode, Option, ModifiersState), { - self.on_key_up = Some(self.add_event("keyup", move |event: KeyboardEvent| { + self.on_keyboard_release = Some(self.add_event("keyup", move |event: KeyboardEvent| { handler( event::scan_code(&event), event::virtual_key_code(&event), @@ -137,11 +137,11 @@ impl Canvas { })); } - pub fn on_key_down(&mut self, mut handler: F) + pub fn on_keyboard_press(&mut self, mut handler: F) where F: 'static + FnMut(ScanCode, Option, ModifiersState), { - self.on_key_down = Some(self.add_event("keydown", move |event: KeyboardEvent| { + self.on_keyboard_press = Some(self.add_event("keydown", move |event: KeyboardEvent| { handler( event::scan_code(&event), event::virtual_key_code(&event), @@ -150,38 +150,44 @@ impl Canvas { })); } - pub fn on_key_press(&mut self, mut handler: F) + pub fn on_received_character(&mut self, mut handler: F) where F: 'static + FnMut(char), { - self.on_key_press = Some(self.add_event("keypress", move |event: KeyboardEvent| { - handler(event::codepoint(&event)); - })); + // TODO: Use `beforeinput`. + // + // The `keypress` event is deprecated, but there does not seem to be a + // viable/compatible alternative as of now. `beforeinput` is still widely + // unsupported. + self.on_received_character = + Some(self.add_event("keypress", move |event: KeyboardEvent| { + handler(event::codepoint(&event)); + })); } - pub fn on_mouse_out(&mut self, mut handler: F) + pub fn on_cursor_leave(&mut self, mut handler: F) where F: 'static + FnMut(i32), { - self.on_mouse_out = Some(self.add_event("pointerout", move |event: PointerEvent| { + self.on_cursor_leave = Some(self.add_event("pointerout", move |event: PointerEvent| { handler(event.pointer_id()); })); } - pub fn on_mouse_over(&mut self, mut handler: F) + pub fn on_cursor_enter(&mut self, mut handler: F) where F: 'static + FnMut(i32), { - self.on_mouse_over = Some(self.add_event("pointerover", move |event: PointerEvent| { + self.on_cursor_enter = Some(self.add_event("pointerover", move |event: PointerEvent| { handler(event.pointer_id()); })); } - pub fn on_mouse_up(&mut self, mut handler: F) + pub fn on_mouse_release(&mut self, mut handler: F) where F: 'static + FnMut(i32, MouseButton, ModifiersState), { - self.on_mouse_up = Some(self.add_event("pointerup", move |event: PointerEvent| { + self.on_mouse_release = Some(self.add_event("pointerup", move |event: PointerEvent| { handler( event.pointer_id(), event::mouse_button(&event), @@ -190,11 +196,11 @@ impl Canvas { })); } - pub fn on_mouse_down(&mut self, mut handler: F) + pub fn on_mouse_press(&mut self, mut handler: F) where F: 'static + FnMut(i32, MouseButton, ModifiersState), { - self.on_mouse_down = Some(self.add_event("pointerdown", move |event: PointerEvent| { + self.on_mouse_press = Some(self.add_event("pointerdown", move |event: PointerEvent| { handler( event.pointer_id(), event::mouse_button(&event), @@ -203,11 +209,11 @@ impl Canvas { })); } - pub fn on_mouse_move(&mut self, mut handler: F) + pub fn on_cursor_move(&mut self, mut handler: F) where F: 'static + FnMut(i32, LogicalPosition, ModifiersState), { - self.on_mouse_move = Some(self.add_event("pointermove", move |event: PointerEvent| { + self.on_cursor_move = Some(self.add_event("pointermove", move |event: PointerEvent| { handler( event.pointer_id(), event::mouse_position(&event), @@ -216,11 +222,11 @@ impl Canvas { })); } - pub fn on_mouse_scroll(&mut self, mut handler: F) + pub fn on_mouse_wheel(&mut self, mut handler: F) where F: 'static + FnMut(i32, MouseScrollDelta, ModifiersState), { - self.on_mouse_scroll = Some(self.add_event("wheel", move |event: WheelEvent| { + self.on_mouse_wheel = Some(self.add_event("wheel", move |event: WheelEvent| { if let Some(delta) = event::mouse_scroll_delta(&event) { handler(0, delta, event::mouse_modifiers(&event)); } diff --git a/src/platform_impl/web/web_sys/event.rs b/src/platform_impl/web/web_sys/event.rs index 876fbc22..af557b99 100644 --- a/src/platform_impl/web/web_sys/event.rs +++ b/src/platform_impl/web/web_sys/event.rs @@ -220,5 +220,8 @@ pub fn keyboard_modifiers(event: &KeyboardEvent) -> ModifiersState { } pub fn codepoint(event: &KeyboardEvent) -> char { + // `event.key()` always returns a non-empty `String`. Therefore, this should + // never panic. + // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key event.key().chars().next().unwrap() } diff --git a/src/platform_impl/web_sys/event_loop.rs b/src/platform_impl/web_sys/event_loop.rs deleted file mode 100644 index 9a6918a9..00000000 --- a/src/platform_impl/web_sys/event_loop.rs +++ /dev/null @@ -1,378 +0,0 @@ -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 platform_impl::platform::document; -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::{ - 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 { - elw: RootELW, -} - -pub struct EventLoopWindowTarget { - pub(crate) runner: EventLoopRunnerShared, -} - -impl EventLoopWindowTarget { - fn new() -> Self { - EventLoopWindowTarget { - runner: 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 type EventLoopRunnerShared = Rc>; - -pub struct ELRShared { - runner: RefCell>>, - events: RefCell>>, // TODO: this may not be necessary? -} - -struct EventLoopRunner { - control: ControlFlow, - is_busy: bool, - event_handler: Box, &mut ControlFlow)>, -} - -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(); - 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 '!' - wasm_bindgen::throw_str( - "Using exceptions for control flow, don't mind me. This isn't actually an error!", - ); - } - - 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: &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 }), - _ => 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: &EventTarget, - event: &str, - mut handler: F, -) where - E: AsRef + wasm_bindgen::convert::FromWasmAbi + 'static, - F: FnMut(&EventLoopRunnerShared, E) + 'static, -{ - let elrs = elrs.clone(); - - let closure = 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); - - target.add_event_listener_with_callback(event, &closure.as_ref().unchecked_ref()); - closure.forget(); // TODO: don't leak this. -} - -impl ELRShared { - // 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.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) { - // 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) { - 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); - } - } - } -} diff --git a/src/platform_impl/web_sys/events.rs b/src/platform_impl/web_sys/events.rs deleted file mode 100644 index 4e13ca2a..00000000 --- a/src/platform_impl/web_sys/events.rs +++ /dev/null @@ -1,201 +0,0 @@ -use std::convert::TryInto; - -use web_sys::{KeyboardEvent, MouseEvent}; -use event::{MouseButton, ModifiersState, ScanCode, VirtualKeyCode}; - -pub fn button_mapping(event: &KeyboardEvent) -> 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: &MouseEvent) -> ModifiersState { - ModifiersState { - shift: event.shift_key(), - ctrl: event.ctrl_key(), - alt: event.alt_key(), - logo: event.meta_key(), - } -} - -pub fn mouse_button(event: &MouseEvent) -> MouseButton { - match event.button() { - 0 => MouseButton::Left, - 1 => MouseButton::Middle, - 2 => MouseButton::Right, - i => MouseButton::Other((i - 3).try_into().expect("very large mouse button value")), - } -} - -pub fn keyboard_modifiers_state(event: &KeyboardEvent) -> ModifiersState { - ModifiersState { - shift: event.shift_key(), - ctrl: event.ctrl_key(), - alt: event.alt_key(), - logo: event.meta_key(), - } -} - -pub fn scancode(event: &KeyboardEvent) -> ScanCode { - match event.key_code() { - 0 => event.char_code(), - i => i, - } -} diff --git a/src/platform_impl/web_sys/mod.rs b/src/platform_impl/web_sys/mod.rs deleted file mode 100644 index 1b0591b1..00000000 --- a/src/platform_impl/web_sys/mod.rs +++ /dev/null @@ -1,30 +0,0 @@ -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}; - -#[derive(Debug)] -pub struct OsError(String); - -impl fmt::Display for OsError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.0) - } -} - -fn window() -> web_sys::Window { - web_sys::window().unwrap() -} - -fn document() -> web_sys::Document { - window().document().unwrap() -} diff --git a/src/platform_impl/web_sys/window.rs b/src/platform_impl/web_sys/window.rs deleted file mode 100644 index bd51c3f3..00000000 --- a/src/platform_impl/web_sys/window.rs +++ /dev/null @@ -1,330 +0,0 @@ -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::web_sys::WindowExtWebSys; -use platform_impl::platform::{document, window}; -use std::cell::RefCell; -use std::collections::vec_deque::IntoIter as VecDequeIter; -use std::collections::VecDeque; -use wasm_bindgen::{closure::Closure, JsCast}; -use web_sys::HtmlCanvasElement; -use window::{CursorIcon, Window as RootWindow, WindowAttributes, WindowId as RootWI}; - -#[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 dimensions(&self) -> PhysicalSize { - unimplemented!(); - } - - pub fn name(&self) -> Option { - unimplemented!(); - } -} - -#[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: HtmlCanvasElement, - 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: HtmlCanvasElement = element.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())))?; - - register(&target.runner, &canvas); - - let runner = target.runner.clone(); - let redraw = Box::new(move || { - let runner = runner.clone(); - let closure = Closure::once_into_js(move |_: f64| { - runner.send_event(Event::WindowEvent { - window_id: RootWI(WindowId), - event: WindowEvent::RedrawRequested, - }); - }); - window().request_animation_frame(closure.as_ref().unchecked_ref()); - }); - - 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.x(), - y: bounds.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 WindowExtWebSys for RootWindow { - fn canvas(&self) -> HtmlCanvasElement { - self.window.canvas.clone() - } -} From bb285984da0c82fe797324659dbef7b6a96e7108 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 27 Jun 2019 00:02:46 +0200 Subject: [PATCH 41/69] Implement `stdweb` backend for `web` platform --- Cargo.toml | 13 +- src/platform/web.rs | 8 +- src/platform_impl/stdweb/event_loop.rs | 496 ------------------ src/platform_impl/stdweb/mod.rs | 29 - src/platform_impl/stdweb/window.rs | 329 ------------ src/platform_impl/web/mod.rs | 6 +- src/platform_impl/web/stdweb/canvas.rs | 245 ++++++++- .../{stdweb/events.rs => web/stdweb/event.rs} | 89 ++-- src/platform_impl/web/stdweb/mod.rs | 21 +- src/platform_impl/web/stdweb/timeout.rs | 25 + 10 files changed, 355 insertions(+), 906 deletions(-) delete mode 100644 src/platform_impl/stdweb/event_loop.rs delete mode 100644 src/platform_impl/stdweb/mod.rs delete mode 100644 src/platform_impl/stdweb/window.rs rename src/platform_impl/{stdweb/events.rs => web/stdweb/event.rs} (82%) create mode 100644 src/platform_impl/web/stdweb/timeout.rs diff --git a/Cargo.toml b/Cargo.toml index 6161bbcd..10f243c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,10 @@ categories = ["gui"] [package.metadata.docs.rs] features = ["serde"] +[features] +use_web-sys = ["web-sys", "wasm-bindgen", "instant/wasm-bindgen"] +use_stdweb = ["stdweb", "instant/stdweb"] + [dependencies] instant = "0.1" lazy_static = "1" @@ -20,9 +24,6 @@ libc = "0.2" log = "0.4" serde = { version = "1", optional = true, features = ["serde_derive"] } -[features] -web_sys = ["web-sys", "wasm-bindgen"] - [dev-dependencies] image = "0.21" env_logger = "0.5" @@ -102,9 +103,9 @@ features = [ version = "0.2.45" optional = true -[target.'cfg(target_arch = "wasm32")'.dependencies] -stdweb = { path = "../stdweb", optional = true } -instant = { version = "0.1", features = ["wasm-bindgen"] } +[target.'cfg(target_arch = "wasm32")'.dependencies.stdweb] +version = "0.4.17" +optional = true [patch.crates-io] stdweb = { path = "../stdweb" } diff --git a/src/platform/web.rs b/src/platform/web.rs index edb37f8b..3ed460ea 100644 --- a/src/platform/web.rs +++ b/src/platform/web.rs @@ -1,15 +1,15 @@ -#[cfg(feature = "stdweb")] +#[cfg(feature = "use_stdweb")] use stdweb::web::html_element::CanvasElement; -#[cfg(feature = "stdweb")] +#[cfg(feature = "use_stdweb")] pub trait WindowExtStdweb { fn canvas(&self) -> CanvasElement; } -#[cfg(feature = "web-sys")] +#[cfg(feature = "use_web-sys")] use web_sys::HtmlCanvasElement; -#[cfg(feature = "web-sys")] +#[cfg(feature = "use_web-sys")] pub trait WindowExtWebSys { fn canvas(&self) -> HtmlCanvasElement; } diff --git a/src/platform_impl/stdweb/event_loop.rs b/src/platform_impl/stdweb/event_loop.rs deleted file mode 100644 index bb771c87..00000000 --- a/src/platform_impl/stdweb/event_loop.rs +++ /dev/null @@ -1,496 +0,0 @@ -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 instant::{Duration, Instant}; -use std::{ - cell::RefCell, - 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); - -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(); - 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), - }, - }, - }); - }); - - 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); - 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 deleted file mode 100644 index 09ab677f..00000000 --- a/src/platform_impl/stdweb/mod.rs +++ /dev/null @@ -1,29 +0,0 @@ -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}; - -#[derive(Debug)] -pub struct OsError(String); - -impl fmt::Display for OsError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.0) - } -} - -// 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? diff --git a/src/platform_impl/stdweb/window.rs b/src/platform_impl/stdweb/window.rs deleted file mode 100644 index 81cea6a7..00000000 --- a/src/platform_impl/stdweb/window.rs +++ /dev/null @@ -1,329 +0,0 @@ -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 std::cell::RefCell; -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; - -impl MonitorHandle { - 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!(); - } -} - -#[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() - } -} diff --git a/src/platform_impl/web/mod.rs b/src/platform_impl/web/mod.rs index dbe99549..7ff671ee 100644 --- a/src/platform_impl/web/mod.rs +++ b/src/platform_impl/web/mod.rs @@ -11,10 +11,14 @@ mod event_loop; mod monitor; mod window; -#[cfg(feature = "web_sys")] +#[cfg(feature = "use_web-sys")] #[path = "web_sys/mod.rs"] mod backend; +#[cfg(feature = "use_stdweb")] +#[path = "stdweb/mod.rs"] +mod backend; + pub use self::device::Id as DeviceId; pub use self::error::OsError; pub use self::event_loop::{ diff --git a/src/platform_impl/web/stdweb/canvas.rs b/src/platform_impl/web/stdweb/canvas.rs index ba095fb7..14194858 100644 --- a/src/platform_impl/web/stdweb/canvas.rs +++ b/src/platform_impl/web/stdweb/canvas.rs @@ -1,12 +1,51 @@ -pub struct Canvas; +use super::event; +use crate::dpi::{LogicalPosition, LogicalSize}; +use crate::error::OsError as RootOE; +use crate::event::{ModifiersState, MouseButton, MouseScrollDelta, ScanCode, VirtualKeyCode}; +use crate::platform_impl::OsError; + +use std::rc::Rc; +use stdweb::traits::IPointerEvent; +use stdweb::unstable::TryInto; +use stdweb::web::event::{ + BlurEvent, ConcreteEvent, FocusEvent, KeyDownEvent, KeyPressEvent, KeyUpEvent, MouseWheelEvent, + PointerDownEvent, PointerMoveEvent, PointerOutEvent, PointerOverEvent, PointerUpEvent, +}; +use stdweb::web::html_element::CanvasElement; +use stdweb::web::{ + document, window, EventListenerHandle, IChildNode, IElement, IEventTarget, IHtmlElement, INode, +}; + +pub struct Canvas { + raw: CanvasElement, + on_redraw: Rc, + on_focus: Option, + on_blur: Option, + on_keyboard_release: Option, + on_keyboard_press: Option, + on_received_character: Option, + on_cursor_leave: Option, + on_cursor_enter: Option, + on_cursor_move: Option, + on_mouse_press: Option, + on_mouse_release: Option, + on_mouse_wheel: Option, +} + +impl Drop for Canvas { + fn drop(&mut self) { + self.raw.remove(); + } +} impl Canvas { - pub fn new() -> Self { - let element = document() + pub fn create(on_redraw: F) -> Result + where + F: 'static + Fn(), + { + let canvas: CanvasElement = document() .create_element("canvas") - .map_err(|_| os_error!(OsError("Failed to create canvas element".to_owned())))?; - - let canvas: CanvasElement = element + .map_err(|_| os_error!(OsError("Failed to create canvas element".to_owned())))? .try_into() .map_err(|_| os_error!(OsError("Failed to create canvas element".to_owned())))?; @@ -15,6 +54,198 @@ impl Canvas { .ok_or_else(|| os_error!(OsError("Failed to find body node".to_owned())))? .append_child(&canvas); - Canvas(canvas) + // TODO: Set up unique ids + canvas + .set_attribute("tabindex", "0") + .expect("Failed to set a tabindex"); + + Ok(Canvas { + raw: canvas, + on_redraw: Rc::new(on_redraw), + on_blur: None, + on_focus: None, + on_keyboard_release: None, + on_keyboard_press: None, + on_received_character: None, + on_cursor_leave: None, + on_cursor_enter: None, + on_cursor_move: None, + on_mouse_release: None, + on_mouse_press: None, + on_mouse_wheel: None, + }) + } + + pub fn set_attribute(&self, attribute: &str, value: &str) { + self.raw + .set_attribute(attribute, value) + .expect(&format!("Set attribute: {}", attribute)); + } + + pub fn position(&self) -> (f64, f64) { + let bounds = self.raw.get_bounding_client_rect(); + + (bounds.get_x(), bounds.get_y()) + } + + pub fn width(&self) -> f64 { + self.raw.width() as f64 + } + + pub fn height(&self) -> f64 { + self.raw.height() as f64 + } + + pub fn set_size(&self, size: LogicalSize) { + self.raw.set_width(size.width as u32); + self.raw.set_height(size.height as u32); + } + + pub fn raw(&self) -> &CanvasElement { + &self.raw + } + + pub fn request_redraw(&self) { + let on_redraw = self.on_redraw.clone(); + window().request_animation_frame(move |_| on_redraw()); + } + + pub fn on_blur(&mut self, mut handler: F) + where + F: 'static + FnMut(), + { + self.on_blur = Some(self.add_event(move |_: BlurEvent| { + handler(); + })); + } + + pub fn on_focus(&mut self, mut handler: F) + where + F: 'static + FnMut(), + { + self.on_focus = Some(self.add_event(move |_: FocusEvent| { + handler(); + })); + } + + pub fn on_keyboard_release(&mut self, mut handler: F) + where + F: 'static + FnMut(ScanCode, Option, ModifiersState), + { + self.on_keyboard_release = Some(self.add_event(move |event: KeyUpEvent| { + handler( + event::scan_code(&event), + event::virtual_key_code(&event), + event::keyboard_modifiers(&event), + ); + })); + } + + pub fn on_keyboard_press(&mut self, mut handler: F) + where + F: 'static + FnMut(ScanCode, Option, ModifiersState), + { + self.on_keyboard_press = Some(self.add_event(move |event: KeyDownEvent| { + handler( + event::scan_code(&event), + event::virtual_key_code(&event), + event::keyboard_modifiers(&event), + ); + })); + } + + pub fn on_received_character(&mut self, mut handler: F) + where + F: 'static + FnMut(char), + { + // TODO: Use `beforeinput`. + // + // The `keypress` event is deprecated, but there does not seem to be a + // viable/compatible alternative as of now. `beforeinput` is still widely + // unsupported. + self.on_received_character = Some(self.add_event(move |event: KeyPressEvent| { + handler(event::codepoint(&event)); + })); + } + + pub fn on_cursor_leave(&mut self, mut handler: F) + where + F: 'static + FnMut(i32), + { + self.on_cursor_leave = Some(self.add_event(move |event: PointerOutEvent| { + handler(event.pointer_id()); + })); + } + + pub fn on_cursor_enter(&mut self, mut handler: F) + where + F: 'static + FnMut(i32), + { + self.on_cursor_enter = Some(self.add_event(move |event: PointerOverEvent| { + handler(event.pointer_id()); + })); + } + + pub fn on_mouse_release(&mut self, mut handler: F) + where + F: 'static + FnMut(i32, MouseButton, ModifiersState), + { + self.on_mouse_release = Some(self.add_event(move |event: PointerUpEvent| { + handler( + event.pointer_id(), + event::mouse_button(&event), + event::mouse_modifiers(&event), + ); + })); + } + + pub fn on_mouse_press(&mut self, mut handler: F) + where + F: 'static + FnMut(i32, MouseButton, ModifiersState), + { + self.on_mouse_press = Some(self.add_event(move |event: PointerDownEvent| { + handler( + event.pointer_id(), + event::mouse_button(&event), + event::mouse_modifiers(&event), + ); + })); + } + + pub fn on_cursor_move(&mut self, mut handler: F) + where + F: 'static + FnMut(i32, LogicalPosition, ModifiersState), + { + self.on_cursor_move = Some(self.add_event(move |event: PointerMoveEvent| { + handler( + event.pointer_id(), + event::mouse_position(&event), + event::mouse_modifiers(&event), + ); + })); + } + + pub fn on_mouse_wheel(&mut self, mut handler: F) + where + F: 'static + FnMut(i32, MouseScrollDelta, ModifiersState), + { + self.on_mouse_wheel = Some(self.add_event(move |event: MouseWheelEvent| { + if let Some(delta) = event::mouse_scroll_delta(&event) { + handler(0, delta, event::mouse_modifiers(&event)); + } + })); + } + + fn add_event(&self, mut handler: F) -> EventListenerHandle + where + E: ConcreteEvent, + F: 'static + FnMut(E), + { + self.raw.add_event_listener(move |event: E| { + event.stop_propagation(); + event.cancel_bubble(); + + handler(event); + }) } } diff --git a/src/platform_impl/stdweb/events.rs b/src/platform_impl/web/stdweb/event.rs similarity index 82% rename from src/platform_impl/stdweb/events.rs rename to src/platform_impl/web/stdweb/event.rs index 7068fba0..81397056 100644 --- a/src/platform_impl/stdweb/events.rs +++ b/src/platform_impl/web/stdweb/event.rs @@ -1,11 +1,55 @@ -use stdweb::{ - JsSerialize, - web::event::{IKeyboardEvent, IMouseEvent}, - unstable::TryInto -}; -use event::{MouseButton, ModifiersState, ScanCode, VirtualKeyCode}; +use crate::dpi::LogicalPosition; +use crate::event::{ModifiersState, MouseButton, MouseScrollDelta, ScanCode, VirtualKeyCode}; -pub fn button_mapping(event: &impl IKeyboardEvent) -> Option { +use stdweb::web::event::{IKeyboardEvent, IMouseEvent, MouseWheelDeltaMode, MouseWheelEvent}; +use stdweb::{unstable::TryInto, JsSerialize}; + +pub fn mouse_button(event: &impl IMouseEvent) -> MouseButton { + match event.button() { + stdweb::web::event::MouseButton::Left => MouseButton::Left, + stdweb::web::event::MouseButton::Right => MouseButton::Right, + stdweb::web::event::MouseButton::Wheel => MouseButton::Middle, + stdweb::web::event::MouseButton::Button4 => MouseButton::Other(0), + stdweb::web::event::MouseButton::Button5 => MouseButton::Other(1), + } +} + +pub fn mouse_modifiers(event: &impl IMouseEvent) -> ModifiersState { + ModifiersState { + shift: event.shift_key(), + ctrl: event.ctrl_key(), + alt: event.alt_key(), + logo: event.meta_key(), + } +} + +pub fn mouse_position(event: &impl IMouseEvent) -> LogicalPosition { + LogicalPosition { + x: event.offset_x() as f64, + y: event.offset_y() as f64, + } +} + +pub fn mouse_scroll_delta(event: &MouseWheelEvent) -> Option { + let x = event.delta_x(); + let y = event.delta_y(); + + match event.delta_mode() { + MouseWheelDeltaMode::Line => Some(MouseScrollDelta::LineDelta(x as f32, y as f32)), + MouseWheelDeltaMode::Pixel => Some(MouseScrollDelta::PixelDelta(LogicalPosition { x, y })), + MouseWheelDeltaMode::Page => None, + } +} + +pub fn scan_code(event: &T) -> ScanCode { + let key_code = js! ( return @{event}.key_code; ); + + key_code + .try_into() + .expect("The which value should be a number") +} + +pub fn virtual_key_code(event: &impl IKeyboardEvent) -> Option { Some(match &event.code()[..] { "Digit1" => VirtualKeyCode::Key1, "Digit2" => VirtualKeyCode::Key2, @@ -164,11 +208,11 @@ pub fn button_mapping(event: &impl IKeyboardEvent) -> Option { "WebSearch" => VirtualKeyCode::WebSearch, "WebStop" => VirtualKeyCode::WebStop, "Yen" => VirtualKeyCode::Yen, - _ => return None + _ => return None, }) } -pub fn mouse_modifiers_state(event: &impl IMouseEvent) -> ModifiersState { +pub fn keyboard_modifiers(event: &impl IKeyboardEvent) -> ModifiersState { ModifiersState { shift: event.shift_key(), ctrl: event.ctrl_key(), @@ -177,26 +221,9 @@ pub fn mouse_modifiers_state(event: &impl IMouseEvent) -> ModifiersState { } } -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") +pub fn codepoint(event: &impl IKeyboardEvent) -> char { + // `event.key()` always returns a non-empty `String`. Therefore, this should + // never panic. + // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key + event.key().chars().next().unwrap() } diff --git a/src/platform_impl/web/stdweb/mod.rs b/src/platform_impl/web/stdweb/mod.rs index 1552e637..27998655 100644 --- a/src/platform_impl/web/stdweb/mod.rs +++ b/src/platform_impl/web/stdweb/mod.rs @@ -1,6 +1,21 @@ -#[cfg(feature = "stdweb")] -impl WindowExtStdweb for RootWindow { +mod canvas; +mod event; +mod timeout; + +pub use self::canvas::Canvas; +pub use self::timeout::Timeout; + +use crate::platform::web::WindowExtStdweb; +use crate::window::Window; + +use stdweb::web::html_element::CanvasElement; + +pub fn throw(msg: &str) { + js! { throw @{msg} } +} + +impl WindowExtStdweb for Window { fn canvas(&self) -> CanvasElement { - self.window.canvas.clone() + self.window.canvas().raw().clone() } } diff --git a/src/platform_impl/web/stdweb/timeout.rs b/src/platform_impl/web/stdweb/timeout.rs new file mode 100644 index 00000000..fceb1113 --- /dev/null +++ b/src/platform_impl/web/stdweb/timeout.rs @@ -0,0 +1,25 @@ +use std::time::Duration; +use stdweb::web::{window, IWindowOrWorker, TimeoutHandle}; + +#[derive(Debug)] +pub struct Timeout { + handle: TimeoutHandle, +} + +impl Timeout { + pub fn new(f: F, duration: Duration) -> Timeout + where + F: 'static + FnMut(), + { + Timeout { + handle: window().set_clearable_timeout(f, duration.as_millis() as u32), + } + } +} + +impl Drop for Timeout { + fn drop(&mut self) { + let handle = std::mem::replace(&mut self.handle, unsafe { std::mem::uninitialized() }); + handle.clear(); + } +} From 2a356465205ee9b12236ba2d5435b27379f21b4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 27 Jun 2019 00:23:58 +0200 Subject: [PATCH 42/69] Use latest `stdweb` revision --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 10f243c4..c8724fdf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -108,4 +108,4 @@ version = "0.4.17" optional = true [patch.crates-io] -stdweb = { path = "../stdweb" } +stdweb = { git = "https://github.com/koute/stdweb", rev = "b3a29bb9dd9b9405540d711ed02a21cd7058d5c0"} From ea73dac753cee424cea96aaf31be1261753367d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 27 Jun 2019 01:18:46 +0200 Subject: [PATCH 43/69] Fix feature names --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 29ad74e8..51e74408 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -146,7 +146,7 @@ extern crate stdweb; target_os = "openbsd" ))] extern crate calloop; -#[cfg(feature = "web-sys")] +#[cfg(feature = "wasm-bindgen")] extern crate wasm_bindgen; #[cfg(feature = "web-sys")] extern crate web_sys; From 7f2ba0ee3efe5331213bce175c829ebdc917aa1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sat, 29 Jun 2019 17:48:01 +0200 Subject: [PATCH 44/69] Fix `set_cursor_icon` --- src/platform_impl/web/window.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/platform_impl/web/window.rs b/src/platform_impl/web/window.rs index f8ef6cd0..7a8f740a 100644 --- a/src/platform_impl/web/window.rs +++ b/src/platform_impl/web/window.rs @@ -169,7 +169,8 @@ impl Window { CursorIcon::RowResize => "row-resize", }; *self.previous_pointer.borrow_mut() = text; - self.canvas.set_attribute("cursor", text); + self.canvas + .set_attribute("style", &format!("cursor: {}", text)); } #[inline] From de120280e34a8db1de60f0a4ac1250a89a1c2c49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sat, 29 Jun 2019 17:48:22 +0200 Subject: [PATCH 45/69] Fix mouse release/press events --- src/platform_impl/web/event_loop/window_target.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform_impl/web/event_loop/window_target.rs b/src/platform_impl/web/event_loop/window_target.rs index c8e608e4..17ba26ae 100644 --- a/src/platform_impl/web/event_loop/window_target.rs +++ b/src/platform_impl/web/event_loop/window_target.rs @@ -126,7 +126,7 @@ impl WindowTarget { window_id: WindowId(window::Id), event: WindowEvent::MouseInput { device_id: DeviceId(device::Id(pointer_id)), - state: ElementState::Released, + state: ElementState::Pressed, button, modifiers, }, @@ -139,7 +139,7 @@ impl WindowTarget { window_id: WindowId(window::Id), event: WindowEvent::MouseInput { device_id: DeviceId(device::Id(pointer_id)), - state: ElementState::Pressed, + state: ElementState::Released, button, modifiers, }, From e89674d33711713b55e396adab472a38a496dc8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Mon, 1 Jul 2019 20:23:42 +0200 Subject: [PATCH 46/69] Add `dyn` keyword where necessary --- src/platform_impl/web/web_sys/canvas.rs | 2 +- src/platform_impl/web/web_sys/timeout.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform_impl/web/web_sys/canvas.rs b/src/platform_impl/web/web_sys/canvas.rs index 6d1dd373..4e435aaf 100644 --- a/src/platform_impl/web/web_sys/canvas.rs +++ b/src/platform_impl/web/web_sys/canvas.rs @@ -233,7 +233,7 @@ impl Canvas { })); } - fn add_event(&self, event_name: &str, mut handler: F) -> Closure + fn add_event(&self, event_name: &str, mut handler: F) -> Closure where E: 'static + AsRef + wasm_bindgen::convert::FromWasmAbi, F: 'static + FnMut(E), diff --git a/src/platform_impl/web/web_sys/timeout.rs b/src/platform_impl/web/web_sys/timeout.rs index 7fe69ab7..e7ce69a0 100644 --- a/src/platform_impl/web/web_sys/timeout.rs +++ b/src/platform_impl/web/web_sys/timeout.rs @@ -5,7 +5,7 @@ use wasm_bindgen::JsCast; #[derive(Debug)] pub struct Timeout { handle: i32, - _closure: Closure, + _closure: Closure, } impl Timeout { From 5cc84f32dbe142959ea5cd1f772aa8363c0dc837 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Mon, 1 Jul 2019 20:43:54 +0200 Subject: [PATCH 47/69] Improve feature names to enable web backends --- Cargo.toml | 10 ++++++---- src/lib.rs | 4 ++-- src/platform/web.rs | 8 ++++---- src/platform_impl/web/mod.rs | 4 ++-- 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c8724fdf..d0e5f1b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,8 +14,8 @@ categories = ["gui"] features = ["serde"] [features] -use_web-sys = ["web-sys", "wasm-bindgen", "instant/wasm-bindgen"] -use_stdweb = ["stdweb", "instant/stdweb"] +web-sys = ["web_sys", "wasm-bindgen", "instant/wasm-bindgen"] +stdweb = ["std_web", "instant/stdweb"] [dependencies] instant = "0.1" @@ -78,7 +78,8 @@ 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.web-sys] +[target.'cfg(target_arch = "wasm32")'.dependencies.web_sys] +package = "web-sys" version = "0.3.22" optional = true features = [ @@ -103,7 +104,8 @@ features = [ version = "0.2.45" optional = true -[target.'cfg(target_arch = "wasm32")'.dependencies.stdweb] +[target.'cfg(target_arch = "wasm32")'.dependencies.std_web] +package = "stdweb" version = "0.4.17" optional = true diff --git a/src/lib.rs b/src/lib.rs index 51e74408..ada920a2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -135,9 +135,9 @@ extern crate smithay_client_toolkit as sctk; target_os = "openbsd" ))] extern crate x11_dl; -#[cfg(feature = "stdweb")] +#[cfg(feature = "std_web")] #[macro_use] -extern crate stdweb; +extern crate std_web as stdweb; #[cfg(any( target_os = "linux", target_os = "dragonfly", diff --git a/src/platform/web.rs b/src/platform/web.rs index 3ed460ea..edb37f8b 100644 --- a/src/platform/web.rs +++ b/src/platform/web.rs @@ -1,15 +1,15 @@ -#[cfg(feature = "use_stdweb")] +#[cfg(feature = "stdweb")] use stdweb::web::html_element::CanvasElement; -#[cfg(feature = "use_stdweb")] +#[cfg(feature = "stdweb")] pub trait WindowExtStdweb { fn canvas(&self) -> CanvasElement; } -#[cfg(feature = "use_web-sys")] +#[cfg(feature = "web-sys")] use web_sys::HtmlCanvasElement; -#[cfg(feature = "use_web-sys")] +#[cfg(feature = "web-sys")] pub trait WindowExtWebSys { fn canvas(&self) -> HtmlCanvasElement; } diff --git a/src/platform_impl/web/mod.rs b/src/platform_impl/web/mod.rs index 7ff671ee..ca321b09 100644 --- a/src/platform_impl/web/mod.rs +++ b/src/platform_impl/web/mod.rs @@ -11,11 +11,11 @@ mod event_loop; mod monitor; mod window; -#[cfg(feature = "use_web-sys")] +#[cfg(feature = "web-sys")] #[path = "web_sys/mod.rs"] mod backend; -#[cfg(feature = "use_stdweb")] +#[cfg(feature = "stdweb")] #[path = "stdweb/mod.rs"] mod backend; From 76645f3b5a0c0c01de9e2df149d12f8774e5bf03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sun, 7 Jul 2019 05:51:06 +0200 Subject: [PATCH 48/69] Bump version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index d0e5f1b4..15b2c62e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "winit" -version = "0.19.1" +version = "0.20.0-alpha1" authors = ["The winit contributors", "Pierre Krieger "] description = "Cross-platform window creation library." keywords = ["windowing"] From 7b23d190b1a60fbbddb3c5fd90db356e8a59bc95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n?= Date: Thu, 11 Jul 2019 00:54:54 +0200 Subject: [PATCH 49/69] Fix `web` errors (#1040) * Fix old `use` declarations * Fix hidden lifetime parameter * Fix missing methods in `web::Monitor`. Originally fixed by @ryanisaacg in 94387c4bf5bca35f4e24562ce89a4f4badd53aa8. * Disable some tests and examples on `wasm32` --- examples/multithreaded.rs | 33 +++++++++++++++++++------------- examples/proxy.rs | 18 +++++++++++------ examples/request_redraw.rs | 3 ++- examples/timer.rs | 3 ++- examples/window_run_return.rs | 19 +++++++++++------- src/event_loop.rs | 6 +++--- src/platform_impl/web/error.rs | 2 +- src/platform_impl/web/monitor.rs | 14 ++++++++++---- tests/send_objects.rs | 2 ++ tests/sync_object.rs | 1 + 10 files changed, 65 insertions(+), 36 deletions(-) diff --git a/examples/multithreaded.rs b/examples/multithreaded.rs index 39f7af3d..e6f3cac4 100644 --- a/examples/multithreaded.rs +++ b/examples/multithreaded.rs @@ -1,16 +1,18 @@ -extern crate env_logger; -use std::{collections::HashMap, sync::mpsc, thread, time::Duration}; - -use winit::{ - event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, - window::{CursorIcon, WindowBuilder}, -}; - -const WINDOW_COUNT: usize = 3; -const WINDOW_SIZE: (u32, u32) = (600, 400); - +#[cfg(not(target_arch = "wasm32"))] fn main() { + extern crate env_logger; + + use std::{collections::HashMap, sync::mpsc, thread, time::Duration}; + + use winit::{ + event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + window::{CursorIcon, WindowBuilder}, + }; + + const WINDOW_COUNT: usize = 3; + const WINDOW_SIZE: (u32, u32) = (600, 400); + env_logger::init(); let event_loop = EventLoop::new(); let mut window_senders = HashMap::with_capacity(WINDOW_COUNT); @@ -36,7 +38,7 @@ fn main() { } => { window.set_title(&format!("{:?}", key)); let state = !modifiers.shift; - use self::VirtualKeyCode::*; + use VirtualKeyCode::*; match key { A => window.set_always_on_top(state), C => window.set_cursor_icon(match state { @@ -125,3 +127,8 @@ fn main() { } }) } + +#[cfg(target_arch = "wasm32")] +fn main() { + panic!("Example not supported on Wasm"); +} diff --git a/examples/proxy.rs b/examples/proxy.rs index 06198ecd..6c5bbbc6 100644 --- a/examples/proxy.rs +++ b/examples/proxy.rs @@ -1,10 +1,11 @@ -use winit::{ - event::{Event, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, - window::WindowBuilder, -}; - +#[cfg(not(target_arch = "wasm32"))] fn main() { + use winit::{ + event::{Event, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + window::WindowBuilder, + }; + let event_loop: EventLoop = EventLoop::new_user_event(); let _window = WindowBuilder::new() @@ -35,3 +36,8 @@ fn main() { } }); } + +#[cfg(target_arch = "wasm32")] +fn main() { + panic!("Example not supported on Wasm"); +} diff --git a/examples/request_redraw.rs b/examples/request_redraw.rs index ac9377e7..b55bd970 100644 --- a/examples/request_redraw.rs +++ b/examples/request_redraw.rs @@ -1,4 +1,5 @@ -use std::time::{Duration, Instant}; +use instant::Instant; +use std::time::Duration; use winit::{ event::{Event, WindowEvent}, diff --git a/examples/timer.rs b/examples/timer.rs index 8d3b9bde..e1e3f290 100644 --- a/examples/timer.rs +++ b/examples/timer.rs @@ -1,4 +1,5 @@ -use std::time::{Duration, Instant}; +use instant::Instant; +use std::time::Duration; use winit::{ event::{Event, StartCause, WindowEvent}, event_loop::{ControlFlow, EventLoop}, diff --git a/examples/window_run_return.rs b/examples/window_run_return.rs index d2516c7a..3598e10c 100644 --- a/examples/window_run_return.rs +++ b/examples/window_run_return.rs @@ -1,11 +1,11 @@ -use winit::{ - event::{Event, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, - platform::desktop::EventLoopExtDesktop, - window::WindowBuilder, -}; - +#[cfg(not(target_arch = "wasm32"))] fn main() { + use winit::{ + event::{Event, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + platform::desktop::EventLoopExtDesktop, + window::WindowBuilder, + }; let mut event_loop = EventLoop::new(); let window = WindowBuilder::new() @@ -39,3 +39,8 @@ fn main() { println!("Okay we're done now for real."); } + +#[cfg(target_arch = "wasm32")] +fn main() { + panic!("Example not supported on Wasm"); +} diff --git a/src/event_loop.rs b/src/event_loop.rs index 68c6f93d..5230c6cf 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -13,9 +13,9 @@ use instant::Instant; use std::ops::Deref; use std::{error, fmt}; -use event::Event; -use monitor::{AvailableMonitorsIter, MonitorHandle}; -use platform_impl; +use crate::event::Event; +use crate::monitor::{AvailableMonitorsIter, MonitorHandle}; +use crate::platform_impl; /// Provides a way to retrieve events from the system and from the windows that were registered to /// the events loop. diff --git a/src/platform_impl/web/error.rs b/src/platform_impl/web/error.rs index 8f85d6cb..6995f2bc 100644 --- a/src/platform_impl/web/error.rs +++ b/src/platform_impl/web/error.rs @@ -4,7 +4,7 @@ use std::fmt; pub struct OsError(pub String); impl fmt::Display for OsError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) } } diff --git a/src/platform_impl/web/monitor.rs b/src/platform_impl/web/monitor.rs index 8ac60fb2..1aeaa320 100644 --- a/src/platform_impl/web/monitor.rs +++ b/src/platform_impl/web/monitor.rs @@ -1,4 +1,5 @@ use crate::dpi::{PhysicalPosition, PhysicalSize}; +use crate::monitor::VideoMode; #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Handle; @@ -12,11 +13,16 @@ impl Handle { unimplemented!(); } - pub fn dimensions(&self) -> PhysicalSize { - 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() + } } diff --git a/tests/send_objects.rs b/tests/send_objects.rs index 9462d073..252b271a 100644 --- a/tests/send_objects.rs +++ b/tests/send_objects.rs @@ -1,6 +1,7 @@ #[allow(dead_code)] fn needs_send() {} +#[cfg(not(target_arch = "wasm32"))] #[test] fn event_loop_proxy_send() { #[allow(dead_code)] @@ -10,6 +11,7 @@ fn event_loop_proxy_send() { } } +#[cfg(not(target_arch = "wasm32"))] #[test] fn window_send() { // ensures that `winit::Window` implements `Send` diff --git a/tests/sync_object.rs b/tests/sync_object.rs index dad65202..56524cb2 100644 --- a/tests/sync_object.rs +++ b/tests/sync_object.rs @@ -1,6 +1,7 @@ #[allow(dead_code)] fn needs_sync() {} +#[cfg(not(target_arch = "wasm32"))] #[test] fn window_sync() { // ensures that `winit::Window` implements `Sync` From fe129963825798c95850df874bab863e9ed8c819 Mon Sep 17 00:00:00 2001 From: Ryan Goldstein Date: Tue, 16 Jul 2019 20:00:03 -0700 Subject: [PATCH 50/69] Update the web backend todo list --- src/platform_impl/web/mod.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/platform_impl/web/mod.rs b/src/platform_impl/web/mod.rs index ca321b09..7715336a 100644 --- a/src/platform_impl/web/mod.rs +++ b/src/platform_impl/web/mod.rs @@ -1,9 +1,6 @@ -// TODO: dpi -// TODO: close events (stdweb PR required) +// TODO: close events (port from old stdweb branch) // 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? +// TODO: fullscreen API (stdweb PR required) mod device; mod error; From e897d70733a759ff44ef9c8504d4ca339e7ba11b Mon Sep 17 00:00:00 2001 From: Ryan G Date: Tue, 16 Jul 2019 20:56:58 -0700 Subject: [PATCH 51/69] Bump the stdweb version to 0.4.18 (#1049) This removes the need to patch to a git version --- Cargo.toml | 5 +---- src/lib.rs | 1 - src/platform_impl/web/stdweb/event.rs | 2 +- src/platform_impl/web/stdweb/mod.rs | 1 + 4 files changed, 3 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9885cd23..8617f5ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -113,8 +113,5 @@ optional = true [target.'cfg(target_arch = "wasm32")'.dependencies.std_web] package = "stdweb" -version = "0.4.17" +version = "0.4.18" optional = true - -[patch.crates-io] -stdweb = { git = "https://github.com/koute/stdweb", rev = "b3a29bb9dd9b9405540d711ed02a21cd7058d5c0"} diff --git a/src/lib.rs b/src/lib.rs index 87d7d2a5..68a4040c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -130,7 +130,6 @@ extern crate bitflags; #[macro_use] extern crate objc; #[cfg(feature = "std_web")] -#[macro_use] extern crate std_web as stdweb; pub mod dpi; diff --git a/src/platform_impl/web/stdweb/event.rs b/src/platform_impl/web/stdweb/event.rs index 81397056..f05e1ddb 100644 --- a/src/platform_impl/web/stdweb/event.rs +++ b/src/platform_impl/web/stdweb/event.rs @@ -2,7 +2,7 @@ use crate::dpi::LogicalPosition; use crate::event::{ModifiersState, MouseButton, MouseScrollDelta, ScanCode, VirtualKeyCode}; use stdweb::web::event::{IKeyboardEvent, IMouseEvent, MouseWheelDeltaMode, MouseWheelEvent}; -use stdweb::{unstable::TryInto, JsSerialize}; +use stdweb::{js, unstable::TryInto, JsSerialize}; pub fn mouse_button(event: &impl IMouseEvent) -> MouseButton { match event.button() { diff --git a/src/platform_impl/web/stdweb/mod.rs b/src/platform_impl/web/stdweb/mod.rs index 27998655..87058b70 100644 --- a/src/platform_impl/web/stdweb/mod.rs +++ b/src/platform_impl/web/stdweb/mod.rs @@ -8,6 +8,7 @@ pub use self::timeout::Timeout; use crate::platform::web::WindowExtStdweb; use crate::window::Window; +use stdweb::js; use stdweb::web::html_element::CanvasElement; pub fn throw(msg: &str) { From dbdde3d7818e4f7ac0c96d8abe10a187575b750a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n?= Date: Thu, 8 Aug 2019 23:51:41 +0200 Subject: [PATCH 52/69] Stop appending canvas to document in web platform (#1089) * Stop appending canvas to document in web platform * Remove `tabindex` TODO in web backend * Return `OsError` instead of panicking on web canvas creation --- src/platform_impl/web/stdweb/canvas.rs | 13 ++++++------- src/platform_impl/web/web_sys/canvas.rs | 22 ++++++++++++---------- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/platform_impl/web/stdweb/canvas.rs b/src/platform_impl/web/stdweb/canvas.rs index 14194858..1cd99e7e 100644 --- a/src/platform_impl/web/stdweb/canvas.rs +++ b/src/platform_impl/web/stdweb/canvas.rs @@ -49,15 +49,14 @@ impl Canvas { .try_into() .map_err(|_| os_error!(OsError("Failed to create canvas element".to_owned())))?; - document() - .body() - .ok_or_else(|| os_error!(OsError("Failed to find body node".to_owned())))? - .append_child(&canvas); - - // TODO: Set up unique ids + // A tabindex is needed in order to capture local keyboard events. + // A "0" value means that the element should be focusable in + // sequential keyboard navigation, but its order is defined by the + // document's source order. + // https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/tabindex canvas .set_attribute("tabindex", "0") - .expect("Failed to set a tabindex"); + .map_err(|_| os_error!(OsError("Failed to set a tabindex".to_owned())))?; Ok(Canvas { raw: canvas, diff --git a/src/platform_impl/web/web_sys/canvas.rs b/src/platform_impl/web/web_sys/canvas.rs index 4e435aaf..39a17e5b 100644 --- a/src/platform_impl/web/web_sys/canvas.rs +++ b/src/platform_impl/web/web_sys/canvas.rs @@ -34,24 +34,26 @@ impl Canvas { where F: 'static + Fn(), { - let window = web_sys::window().expect("Failed to obtain window"); - let document = window.document().expect("Failed to obtain document"); + let window = + web_sys::window().ok_or(os_error!(OsError("Failed to obtain window".to_owned())))?; + + let document = window + .document() + .ok_or(os_error!(OsError("Failed to obtain document".to_owned())))?; let canvas: HtmlCanvasElement = document .create_element("canvas") .map_err(|_| os_error!(OsError("Failed to create canvas element".to_owned())))? .unchecked_into(); - document - .body() - .ok_or_else(|| os_error!(OsError("Failed to find body node".to_owned())))? - .append_child(&canvas) - .map_err(|_| os_error!(OsError("Failed to append canvas".to_owned())))?; - - // TODO: Set up unique ids + // A tabindex is needed in order to capture local keyboard events. + // A "0" value means that the element should be focusable in + // sequential keyboard navigation, but its order is defined by the + // document's source order. + // https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/tabindex canvas .set_attribute("tabindex", "0") - .expect("Failed to set a tabindex"); + .map_err(|_| os_error!(OsError("Failed to set a tabindex".to_owned())))?; Ok(Canvas { raw: canvas, From 7ee9d5639b21d6a60d283d32dfc4cb31dce20a10 Mon Sep 17 00:00:00 2001 From: Zakarum Date: Wed, 14 Aug 2019 11:20:05 +0300 Subject: [PATCH 53/69] Disable web module on other targets than wasm32 (#1102) --- src/platform/web.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/platform/web.rs b/src/platform/web.rs index edb37f8b..6a1a98cd 100644 --- a/src/platform/web.rs +++ b/src/platform/web.rs @@ -1,3 +1,5 @@ +#![cfg(target_arch = "wasm32")] + #[cfg(feature = "stdweb")] use stdweb::web::html_element::CanvasElement; From e87bc3db20d7b7e8b2d717e386ea7e685cdd7853 Mon Sep 17 00:00:00 2001 From: Ryan G Date: Wed, 11 Sep 2019 11:47:03 -0400 Subject: [PATCH 54/69] Send a LoopDestroyed event when the browser is closed (#1155) * Add the plumbing for handling browser closes * Implement the business logic for handling closes --- Cargo.toml | 1 + src/platform_impl/web/event_loop/runner.rs | 9 +++++++++ src/platform_impl/web/stdweb/mod.rs | 7 +++++++ src/platform_impl/web/web_sys/mod.rs | 15 ++++++++++++++- 4 files changed, 31 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 8617f5ec..814d6777 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -91,6 +91,7 @@ version = "0.3.22" optional = true features = [ 'console', + 'BeforeUnloadEvent', 'Document', 'DomRect', 'Element', diff --git a/src/platform_impl/web/event_loop/runner.rs b/src/platform_impl/web/event_loop/runner.rs index c5bb274b..cadd1dcd 100644 --- a/src/platform_impl/web/event_loop/runner.rs +++ b/src/platform_impl/web/event_loop/runner.rs @@ -48,6 +48,9 @@ impl Shared { 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)); + + let close_instance = self.clone(); + backend::on_unload(move || close_instance.handle_unload()); } // Add an event to the event loop runner @@ -110,6 +113,12 @@ impl Shared { } } + fn handle_unload(&self) { + self.apply_control_flow(root::ControlFlow::Exit); + let mut control = self.current_control_flow(); + 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 diff --git a/src/platform_impl/web/stdweb/mod.rs b/src/platform_impl/web/stdweb/mod.rs index 87058b70..f7caa232 100644 --- a/src/platform_impl/web/stdweb/mod.rs +++ b/src/platform_impl/web/stdweb/mod.rs @@ -9,12 +9,19 @@ use crate::platform::web::WindowExtStdweb; use crate::window::Window; use stdweb::js; +use stdweb::web::event::BeforeUnloadEvent; use stdweb::web::html_element::CanvasElement; +use stdweb::web::window; +use stdweb::web::IEventTarget; pub fn throw(msg: &str) { js! { throw @{msg} } } +pub fn on_unload(mut handler: impl FnMut() + 'static) { + window().add_event_listener(move |_: BeforeUnloadEvent| handler()); +} + impl WindowExtStdweb for Window { fn canvas(&self) -> CanvasElement { self.window.canvas().raw().clone() diff --git a/src/platform_impl/web/web_sys/mod.rs b/src/platform_impl/web/web_sys/mod.rs index 33e217bd..811b6f68 100644 --- a/src/platform_impl/web/web_sys/mod.rs +++ b/src/platform_impl/web/web_sys/mod.rs @@ -7,12 +7,25 @@ pub use self::timeout::Timeout; use crate::platform::web::WindowExtWebSys; use crate::window::Window; -use web_sys::HtmlCanvasElement; +use wasm_bindgen::{closure::Closure, JsCast}; +use web_sys::{BeforeUnloadEvent, HtmlCanvasElement}; pub fn throw(msg: &str) { wasm_bindgen::throw_str(msg); } +pub fn on_unload(mut handler: impl FnMut() + 'static) { + let window = web_sys::window().expect("Failed to obtain window"); + + let closure = Closure::wrap( + Box::new(move |_: BeforeUnloadEvent| handler()) as Box + ); + + window + .add_event_listener_with_callback("beforeunload", &closure.as_ref().unchecked_ref()) + .expect("Failed to add close listener"); +} + impl WindowExtWebSys for Window { fn canvas(&self) -> HtmlCanvasElement { self.window.canvas().raw().clone() From 2c47c43f47196bac880cd16664a4a2ac46911ea5 Mon Sep 17 00:00:00 2001 From: Ryan G Date: Thu, 19 Sep 2019 18:40:18 -0400 Subject: [PATCH 55/69] Implement WindowID on the web platform (#1177) * Use actual numeric IDs to differentiate Windows This is generally important to identifying which window should recieve which event, but is also specifically crucial for fixing RedrawRequested on web. * Cargo fmt --- src/platform_impl/web/event_loop/runner.rs | 11 +++++++ .../web/event_loop/window_target.rs | 29 +++++++++++-------- src/platform_impl/web/stdweb/canvas.rs | 2 +- src/platform_impl/web/window.rs | 15 ++++++---- 4 files changed, 38 insertions(+), 19 deletions(-) diff --git a/src/platform_impl/web/event_loop/runner.rs b/src/platform_impl/web/event_loop/runner.rs index cadd1dcd..f704346e 100644 --- a/src/platform_impl/web/event_loop/runner.rs +++ b/src/platform_impl/web/event_loop/runner.rs @@ -16,6 +16,7 @@ impl Clone for Shared { pub struct Execution { runner: RefCell>>, events: RefCell>>, + id: RefCell, } struct Runner { @@ -39,6 +40,7 @@ impl Shared { Shared(Rc::new(Execution { runner: RefCell::new(None), events: RefCell::new(VecDeque::new()), + id: RefCell::new(0), })) } @@ -53,6 +55,15 @@ impl Shared { backend::on_unload(move || close_instance.handle_unload()); } + // Generate a strictly increasing ID + // This is used to differentiate windows when handling events + pub fn generate_id(&self) -> u32 { + let mut id = self.0.id.borrow_mut(); + *id += 1; + + *id + } + // 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 diff --git a/src/platform_impl/web/event_loop/window_target.rs b/src/platform_impl/web/event_loop/window_target.rs index 17ba26ae..e7bab388 100644 --- a/src/platform_impl/web/event_loop/window_target.rs +++ b/src/platform_impl/web/event_loop/window_target.rs @@ -31,11 +31,16 @@ impl WindowTarget { self.runner.set_listener(event_handler); } - pub fn register(&self, canvas: &mut backend::Canvas) { + pub fn generate_id(&self) -> window::Id { + window::Id(self.runner.generate_id()) + } + + pub fn register(&self, canvas: &mut backend::Canvas, id: window::Id) { let runner = self.runner.clone(); + canvas.on_blur(move || { runner.send_event(Event::WindowEvent { - window_id: WindowId(window::Id), + window_id: WindowId(id), event: WindowEvent::Focused(false), }); }); @@ -43,7 +48,7 @@ impl WindowTarget { let runner = self.runner.clone(); canvas.on_focus(move || { runner.send_event(Event::WindowEvent { - window_id: WindowId(window::Id), + window_id: WindowId(id), event: WindowEvent::Focused(true), }); }); @@ -51,7 +56,7 @@ impl WindowTarget { let runner = self.runner.clone(); canvas.on_keyboard_press(move |scancode, virtual_keycode, modifiers| { runner.send_event(Event::WindowEvent { - window_id: WindowId(window::Id), + window_id: WindowId(id), event: WindowEvent::KeyboardInput { device_id: DeviceId(unsafe { device::Id::dummy() }), input: KeyboardInput { @@ -67,7 +72,7 @@ impl WindowTarget { let runner = self.runner.clone(); canvas.on_keyboard_release(move |scancode, virtual_keycode, modifiers| { runner.send_event(Event::WindowEvent { - window_id: WindowId(window::Id), + window_id: WindowId(id), event: WindowEvent::KeyboardInput { device_id: DeviceId(unsafe { device::Id::dummy() }), input: KeyboardInput { @@ -83,7 +88,7 @@ impl WindowTarget { let runner = self.runner.clone(); canvas.on_received_character(move |char_code| { runner.send_event(Event::WindowEvent { - window_id: WindowId(window::Id), + window_id: WindowId(id), event: WindowEvent::ReceivedCharacter(char_code), }); }); @@ -91,7 +96,7 @@ impl WindowTarget { let runner = self.runner.clone(); canvas.on_cursor_leave(move |pointer_id| { runner.send_event(Event::WindowEvent { - window_id: WindowId(window::Id), + window_id: WindowId(id), event: WindowEvent::CursorLeft { device_id: DeviceId(device::Id(pointer_id)), }, @@ -101,7 +106,7 @@ impl WindowTarget { let runner = self.runner.clone(); canvas.on_cursor_enter(move |pointer_id| { runner.send_event(Event::WindowEvent { - window_id: WindowId(window::Id), + window_id: WindowId(id), event: WindowEvent::CursorEntered { device_id: DeviceId(device::Id(pointer_id)), }, @@ -111,7 +116,7 @@ impl WindowTarget { let runner = self.runner.clone(); canvas.on_cursor_move(move |pointer_id, position, modifiers| { runner.send_event(Event::WindowEvent { - window_id: WindowId(window::Id), + window_id: WindowId(id), event: WindowEvent::CursorMoved { device_id: DeviceId(device::Id(pointer_id)), position, @@ -123,7 +128,7 @@ impl WindowTarget { let runner = self.runner.clone(); canvas.on_mouse_press(move |pointer_id, button, modifiers| { runner.send_event(Event::WindowEvent { - window_id: WindowId(window::Id), + window_id: WindowId(id), event: WindowEvent::MouseInput { device_id: DeviceId(device::Id(pointer_id)), state: ElementState::Pressed, @@ -136,7 +141,7 @@ impl WindowTarget { let runner = self.runner.clone(); canvas.on_mouse_release(move |pointer_id, button, modifiers| { runner.send_event(Event::WindowEvent { - window_id: WindowId(window::Id), + window_id: WindowId(id), event: WindowEvent::MouseInput { device_id: DeviceId(device::Id(pointer_id)), state: ElementState::Released, @@ -149,7 +154,7 @@ impl WindowTarget { let runner = self.runner.clone(); canvas.on_mouse_wheel(move |pointer_id, delta, modifiers| { runner.send_event(Event::WindowEvent { - window_id: WindowId(window::Id), + window_id: WindowId(id), event: WindowEvent::MouseWheel { device_id: DeviceId(device::Id(pointer_id)), delta, diff --git a/src/platform_impl/web/stdweb/canvas.rs b/src/platform_impl/web/stdweb/canvas.rs index 1cd99e7e..cf670471 100644 --- a/src/platform_impl/web/stdweb/canvas.rs +++ b/src/platform_impl/web/stdweb/canvas.rs @@ -13,7 +13,7 @@ use stdweb::web::event::{ }; use stdweb::web::html_element::CanvasElement; use stdweb::web::{ - document, window, EventListenerHandle, IChildNode, IElement, IEventTarget, IHtmlElement, INode, + document, window, EventListenerHandle, IChildNode, IElement, IEventTarget, IHtmlElement, }; pub struct Canvas { diff --git a/src/platform_impl/web/window.rs b/src/platform_impl/web/window.rs index 7a8f740a..be9a9082 100644 --- a/src/platform_impl/web/window.rs +++ b/src/platform_impl/web/window.rs @@ -15,6 +15,7 @@ pub struct Window { canvas: backend::Canvas, previous_pointer: RefCell<&'static str>, position: RefCell, + id: Id, } impl Window { @@ -25,19 +26,22 @@ impl Window { ) -> Result { let runner = target.runner.clone(); + let id = target.generate_id(); + let mut canvas = backend::Canvas::create(move || { runner.send_event(Event::WindowEvent { - window_id: RootWI(Id), + window_id: RootWI(id), event: WindowEvent::RedrawRequested, }) })?; - target.register(&mut canvas); + target.register(&mut canvas, id); let window = Window { canvas, previous_pointer: RefCell::new("auto"), position: RefCell::new(LogicalPosition { x: 0.0, y: 0.0 }), + id, }; window.set_inner_size(attr.inner_size.unwrap_or(LogicalSize { @@ -250,17 +254,16 @@ impl Window { #[inline] pub fn id(&self) -> Id { - // TODO ? - unsafe { Id::dummy() } + return self.id; } } #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct Id; +pub struct Id(pub(crate) u32); impl Id { pub unsafe fn dummy() -> Id { - Id + Id(0) } } From 28a50817aff48d3535ec92681206f788223ef693 Mon Sep 17 00:00:00 2001 From: Ryan G Date: Mon, 23 Sep 2019 09:14:26 -0400 Subject: [PATCH 56/69] Fix web redraw requested (#1181) * Keep track of what windows have requested redraw Instead of using request_animation_frame and sending redraw request events, just keep track of all windows that have asked for a redraw. This doesn't handle dispatching the events * Issue redraw events to windows that request it * Cargo fmt --- src/platform_impl/web/event_loop/runner.rs | 27 ++++++++++++++++++++-- src/platform_impl/web/stdweb/canvas.rs | 15 ++---------- src/platform_impl/web/web_sys/canvas.rs | 14 +---------- src/platform_impl/web/window.rs | 14 +++++------ 4 files changed, 34 insertions(+), 36 deletions(-) diff --git a/src/platform_impl/web/event_loop/runner.rs b/src/platform_impl/web/event_loop/runner.rs index f704346e..b3889fad 100644 --- a/src/platform_impl/web/event_loop/runner.rs +++ b/src/platform_impl/web/event_loop/runner.rs @@ -1,9 +1,15 @@ use super::{backend, state::State}; -use crate::event::{Event, StartCause}; +use crate::event::{Event, StartCause, WindowEvent}; use crate::event_loop as root; +use crate::window::WindowId; use instant::{Duration, Instant}; -use std::{cell::RefCell, clone::Clone, collections::VecDeque, rc::Rc}; +use std::{ + cell::RefCell, + clone::Clone, + collections::{HashSet, VecDeque}, + rc::Rc, +}; pub struct Shared(Rc>); @@ -17,6 +23,7 @@ pub struct Execution { runner: RefCell>>, events: RefCell>>, id: RefCell, + redraw_pending: RefCell>, } struct Runner { @@ -41,6 +48,7 @@ impl Shared { runner: RefCell::new(None), events: RefCell::new(VecDeque::new()), id: RefCell::new(0), + redraw_pending: RefCell::new(HashSet::new()), })) } @@ -64,6 +72,10 @@ impl Shared { *id } + pub fn request_redraw(&self, id: WindowId) { + self.0.redraw_pending.borrow_mut().insert(id); + } + // 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 @@ -115,6 +127,17 @@ impl Shared { if !event_is_start { self.handle_event(event, &mut control); } + // Collect all of the redraw events to avoid double-locking the RefCell + let redraw_events: Vec = self.0.redraw_pending.borrow_mut().drain().collect(); + for window_id in redraw_events { + self.handle_event( + Event::WindowEvent { + window_id, + event: WindowEvent::RedrawRequested, + }, + &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 diff --git a/src/platform_impl/web/stdweb/canvas.rs b/src/platform_impl/web/stdweb/canvas.rs index cf670471..92e46bac 100644 --- a/src/platform_impl/web/stdweb/canvas.rs +++ b/src/platform_impl/web/stdweb/canvas.rs @@ -4,7 +4,6 @@ use crate::error::OsError as RootOE; use crate::event::{ModifiersState, MouseButton, MouseScrollDelta, ScanCode, VirtualKeyCode}; use crate::platform_impl::OsError; -use std::rc::Rc; use stdweb::traits::IPointerEvent; use stdweb::unstable::TryInto; use stdweb::web::event::{ @@ -13,12 +12,11 @@ use stdweb::web::event::{ }; use stdweb::web::html_element::CanvasElement; use stdweb::web::{ - document, window, EventListenerHandle, IChildNode, IElement, IEventTarget, IHtmlElement, + document, EventListenerHandle, IChildNode, IElement, IEventTarget, IHtmlElement, }; pub struct Canvas { raw: CanvasElement, - on_redraw: Rc, on_focus: Option, on_blur: Option, on_keyboard_release: Option, @@ -39,10 +37,7 @@ impl Drop for Canvas { } impl Canvas { - pub fn create(on_redraw: F) -> Result - where - F: 'static + Fn(), - { + pub fn create() -> Result { let canvas: CanvasElement = document() .create_element("canvas") .map_err(|_| os_error!(OsError("Failed to create canvas element".to_owned())))? @@ -60,7 +55,6 @@ impl Canvas { Ok(Canvas { raw: canvas, - on_redraw: Rc::new(on_redraw), on_blur: None, on_focus: None, on_keyboard_release: None, @@ -104,11 +98,6 @@ impl Canvas { &self.raw } - pub fn request_redraw(&self) { - let on_redraw = self.on_redraw.clone(); - window().request_animation_frame(move |_| on_redraw()); - } - pub fn on_blur(&mut self, mut handler: F) where F: 'static + FnMut(), diff --git a/src/platform_impl/web/web_sys/canvas.rs b/src/platform_impl/web/web_sys/canvas.rs index 39a17e5b..bcf6ce4a 100644 --- a/src/platform_impl/web/web_sys/canvas.rs +++ b/src/platform_impl/web/web_sys/canvas.rs @@ -9,7 +9,6 @@ use web_sys::{FocusEvent, HtmlCanvasElement, KeyboardEvent, PointerEvent, WheelE pub struct Canvas { raw: HtmlCanvasElement, - on_redraw: Closure, on_focus: Option>, on_blur: Option>, on_keyboard_release: Option>, @@ -30,10 +29,7 @@ impl Drop for Canvas { } impl Canvas { - pub fn create(on_redraw: F) -> Result - where - F: 'static + Fn(), - { + pub fn create() -> Result { let window = web_sys::window().ok_or(os_error!(OsError("Failed to obtain window".to_owned())))?; @@ -57,7 +53,6 @@ impl Canvas { Ok(Canvas { raw: canvas, - on_redraw: Closure::wrap(Box::new(on_redraw) as Box), on_blur: None, on_focus: None, on_keyboard_release: None, @@ -101,13 +96,6 @@ impl Canvas { &self.raw } - pub fn request_redraw(&self) { - let window = web_sys::window().expect("Failed to obtain window"); - window - .request_animation_frame(&self.on_redraw.as_ref().unchecked_ref()) - .expect("Failed to request animation frame"); - } - pub fn on_blur(&mut self, mut handler: F) where F: 'static + FnMut(), diff --git a/src/platform_impl/web/window.rs b/src/platform_impl/web/window.rs index be9a9082..5a14c6db 100644 --- a/src/platform_impl/web/window.rs +++ b/src/platform_impl/web/window.rs @@ -1,6 +1,5 @@ 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}; @@ -16,6 +15,7 @@ pub struct Window { previous_pointer: RefCell<&'static str>, position: RefCell, id: Id, + register_redraw_request: Box, } impl Window { @@ -28,12 +28,9 @@ impl Window { let id = target.generate_id(); - let mut canvas = backend::Canvas::create(move || { - runner.send_event(Event::WindowEvent { - window_id: RootWI(id), - event: WindowEvent::RedrawRequested, - }) - })?; + let mut canvas = backend::Canvas::create()?; + + let register_redraw_request = Box::new(move || runner.request_redraw(RootWI(id))); target.register(&mut canvas, id); @@ -42,6 +39,7 @@ impl Window { previous_pointer: RefCell::new("auto"), position: RefCell::new(LogicalPosition { x: 0.0, y: 0.0 }), id, + register_redraw_request, }; window.set_inner_size(attr.inner_size.unwrap_or(LogicalSize { @@ -69,7 +67,7 @@ impl Window { } pub fn request_redraw(&self) { - self.canvas.request_redraw(); + (self.register_redraw_request)(); } pub fn outer_position(&self) -> Result { From 8cea3e262be4073b80f920f79d80cc8dc4942ed4 Mon Sep 17 00:00:00 2001 From: Ryan G Date: Tue, 24 Sep 2019 19:33:32 -0400 Subject: [PATCH 57/69] Update the documentation to reflect web support (#1183) * Update the documentation to reflect web support Indicate which methods have platform-specific web behavior * cargo fmt --- CHANGELOG.md | 1 + src/dpi.rs | 1 + src/monitor.rs | 17 +++++++++++++++++ src/platform/mod.rs | 1 + src/platform/web.rs | 5 +++++ src/platform_impl/web/monitor.rs | 10 ++++++---- src/platform_impl/web/window.rs | 8 ++++---- src/window.rs | 18 ++++++++++++++++++ 8 files changed, 53 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 65555cf0..aece17e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ # Unreleased +- Add web support via the 'stdweb' or 'web-sys' features # 0.20.0 Alpha 2 (2019-07-09) diff --git a/src/dpi.rs b/src/dpi.rs index 9c89c12c..99848ae9 100644 --- a/src/dpi.rs +++ b/src/dpi.rs @@ -55,6 +55,7 @@ //! - **Wayland:** On Wayland, DPI factors are set per-screen by the server, and are always integers (most often 1 or 2). //! - **iOS:** DPI factors are both constant and device-specific on iOS. //! - **Android:** This feature isn't yet implemented on Android, so the DPI factor will always be returned as 1.0. +//! - **Web:** DPI factors are handled by the browser and will always be 1.0 for your application. //! //! The window's logical size is conserved across DPI changes, resulting in the physical size changing instead. This //! may be surprising on X11, but is quite standard elsewhere. Physical size changes always produce a diff --git a/src/monitor.rs b/src/monitor.rs index f27ef9d7..56358f8f 100644 --- a/src/monitor.rs +++ b/src/monitor.rs @@ -99,12 +99,20 @@ impl MonitorHandle { /// Returns a human-readable name of the monitor. /// /// Returns `None` if the monitor doesn't exist anymore. + /// + /// ## Platform-specific + /// + /// - **Web:** Always returns None #[inline] pub fn name(&self) -> Option { self.inner.name() } /// Returns the monitor's resolution. + /// + /// ## Platform-specific + /// + /// - **Web:** Always returns (0,0) #[inline] pub fn size(&self) -> PhysicalSize { self.inner.size() @@ -112,6 +120,10 @@ impl MonitorHandle { /// Returns the top-left corner position of the monitor relative to the larger full /// screen area. + /// + /// ## Platform-specific + /// + /// - **Web:** Always returns (0,0) #[inline] pub fn position(&self) -> PhysicalPosition { self.inner.position() @@ -125,12 +137,17 @@ impl MonitorHandle { /// /// - **X11:** Can be overridden using the `WINIT_HIDPI_FACTOR` environment variable. /// - **Android:** Always returns 1.0. + /// - **Web:** Always returns 1.0 #[inline] pub fn hidpi_factor(&self) -> f64 { self.inner.hidpi_factor() } /// Returns all fullscreen video modes supported by this monitor. + /// + /// ## Platform-specific + /// + /// - **Web:** Always returns an empty iterator #[inline] pub fn video_modes(&self) -> impl Iterator { self.inner.video_modes() diff --git a/src/platform/mod.rs b/src/platform/mod.rs index 01125fbb..27ecde18 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -7,6 +7,7 @@ //! - `macos` //! - `unix` //! - `windows` +//! - `web` //! //! And the following platform-specific module: //! diff --git a/src/platform/web.rs b/src/platform/web.rs index 6a1a98cd..5fca0c42 100644 --- a/src/platform/web.rs +++ b/src/platform/web.rs @@ -1,5 +1,10 @@ #![cfg(target_arch = "wasm32")] +//! The web target does not automatically insert the canvas element object into the web page, to +//! allow end users to determine how the page should be laid out. Use the `WindowExtStdweb` or +//! `WindowExtWebSys` traits (depending on your web backend) to retrieve the canvas from the +//! Window. + #[cfg(feature = "stdweb")] use stdweb::web::html_element::CanvasElement; diff --git a/src/platform_impl/web/monitor.rs b/src/platform_impl/web/monitor.rs index 1aeaa320..5cde23e5 100644 --- a/src/platform_impl/web/monitor.rs +++ b/src/platform_impl/web/monitor.rs @@ -10,19 +10,21 @@ impl Handle { } pub fn position(&self) -> PhysicalPosition { - unimplemented!(); + PhysicalPosition { x: 0.0, y: 0.0 } } pub fn name(&self) -> Option { - unimplemented!(); + None } pub fn size(&self) -> PhysicalSize { - unimplemented!(); + PhysicalSize { + width: 0.0, + height: 0.0, + } } pub fn video_modes(&self) -> impl Iterator { - // TODO: is this possible ? std::iter::empty() } } diff --git a/src/platform_impl/web/window.rs b/src/platform_impl/web/window.rs index 5a14c6db..73602dd4 100644 --- a/src/platform_impl/web/window.rs +++ b/src/platform_impl/web/window.rs @@ -177,13 +177,13 @@ impl Window { #[inline] pub fn set_cursor_position(&self, _position: LogicalPosition) -> Result<(), ExternalError> { - // TODO: pointer capture + // Intentionally a no-op, as the web does not support setting cursor positions Ok(()) } #[inline] pub fn set_cursor_grab(&self, _grab: bool) -> Result<(), ExternalError> { - // TODO: pointer capture + // Intentionally a no-op, as the web does not (properly) support grabbing the cursor Ok(()) } @@ -199,7 +199,7 @@ impl Window { #[inline] pub fn set_maximized(&self, _maximized: bool) { - // TODO: should there be a maximization / fullscreen API? + // Intentionally a no-op, as canvases cannot be 'maximized' } #[inline] @@ -230,7 +230,7 @@ impl Window { #[inline] pub fn set_ime_position(&self, _position: LogicalPosition) { - // TODO: what is this? + // Currently a no-op as it does not seem there is good support for this on web } #[inline] diff --git a/src/window.rs b/src/window.rs index dfde6713..657fa0f1 100644 --- a/src/window.rs +++ b/src/window.rs @@ -286,6 +286,10 @@ impl WindowBuilder { /// Builds the window. /// /// Possible causes of error include denied permission, incompatible system, and lack of memory. + /// + /// Platform-specific behavior: + /// - **Web**: The window is created but not inserted into the web page automatically. Please + /// see the web platform module for more information. #[inline] pub fn build( self, @@ -305,6 +309,10 @@ impl Window { /// /// Error should be very rare and only occur in case of permission denied, incompatible system, /// out of memory, etc. + /// + /// Platform-specific behavior: + /// - **Web**: The window is created but not inserted into the web page automatically. Please + /// see the web platform module for more information. #[inline] pub fn new(event_loop: &EventLoopWindowTarget) -> Result { let builder = WindowBuilder::new(); @@ -460,6 +468,7 @@ impl Window { /// ## Platform-specific /// /// - **iOS:** Has no effect. + /// - **Web:** Has no effect. #[inline] pub fn set_min_inner_size(&self, dimensions: Option) { self.window.set_min_inner_size(dimensions) @@ -470,6 +479,7 @@ impl Window { /// ## Platform-specific /// /// - **iOS:** Has no effect. + /// - **Web:** Has no effect. #[inline] pub fn set_max_inner_size(&self, dimensions: Option) { self.window.set_max_inner_size(dimensions) @@ -495,6 +505,7 @@ impl Window { /// /// - **Android:** Has no effect. /// - **iOS:** Can only be called on the main thread. + /// - **Web:** Has no effect. #[inline] pub fn set_visible(&self, visible: bool) { self.window.set_visible(visible) @@ -514,6 +525,7 @@ impl Window { /// ## Platform-specific /// /// - **iOS:** Has no effect. + /// - **Web:** Has no effect. #[inline] pub fn set_resizable(&self, resizable: bool) { self.window.set_resizable(resizable) @@ -524,6 +536,7 @@ impl Window { /// ## Platform-specific /// /// - **iOS:** Has no effect. + /// - **Web:** Has no effect. #[inline] pub fn set_maximized(&self, maximized: bool) { self.window.set_maximized(maximized) @@ -555,6 +568,7 @@ impl Window { /// /// - **iOS:** Can only be called on the main thread. Controls whether the status bar is hidden /// via [`setPrefersStatusBarHidden`]. + /// - **Web:** Has no effect. /// /// [`setPrefersStatusBarHidden`]: https://developer.apple.com/documentation/uikit/uiviewcontroller/1621440-prefersstatusbarhidden?language=objc #[inline] @@ -567,6 +581,7 @@ impl Window { /// ## Platform-specific /// /// - **iOS:** Has no effect. + /// - **Web:** Has no effect. #[inline] pub fn set_always_on_top(&self, always_on_top: bool) { self.window.set_always_on_top(always_on_top) @@ -590,6 +605,7 @@ impl Window { /// ## Platform-specific /// /// **iOS:** Has no effect. + /// - **Web:** Has no effect. #[inline] pub fn set_ime_position(&self, position: LogicalPosition) { self.window.set_ime_position(position) @@ -614,6 +630,7 @@ impl Window { /// ## Platform-specific /// /// - **iOS:** Always returns an `Err`. + /// - **Web:** Has no effect. #[inline] pub fn set_cursor_position(&self, position: LogicalPosition) -> Result<(), ExternalError> { self.window.set_cursor_position(position) @@ -627,6 +644,7 @@ impl Window { /// awkward. /// - **Android:** Has no effect. /// - **iOS:** Always returns an Err. + /// - **Web:** Has no effect. #[inline] pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> { self.window.set_cursor_grab(grab) From 6732fa731df4e84cb001ee1cf25a2e127bd6a8ea Mon Sep 17 00:00:00 2001 From: Ryan Goldstein Date: Tue, 24 Sep 2019 19:39:13 -0400 Subject: [PATCH 58/69] Fix compilation errors --- Cargo.toml | 2 +- src/platform_impl/web/event_loop/proxy.rs | 11 +++++++-- src/platform_impl/web/mod.rs | 4 +++- src/platform_impl/web/monitor.rs | 27 ++++++++++++++++++++++- src/platform_impl/web/window.rs | 18 ++++++++++++--- 5 files changed, 54 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b3446b4c..9aa5d983 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,7 @@ libc = "0.2" log = "0.4" serde = { version = "1", optional = true, features = ["serde_derive"] } derivative = "1.0.2" -raw-window-handle = "0.1" +raw-window-handle = { git = "https://github.com/ryanisaacg/raw-window-handle", branch = "add-web-support" } [dev-dependencies] image = "0.21" diff --git a/src/platform_impl/web/event_loop/proxy.rs b/src/platform_impl/web/event_loop/proxy.rs index cbc4732e..ad9a35f7 100644 --- a/src/platform_impl/web/event_loop/proxy.rs +++ b/src/platform_impl/web/event_loop/proxy.rs @@ -2,8 +2,7 @@ use super::runner; use crate::event::Event; use crate::event_loop::EventLoopClosed; -#[derive(Clone)] -pub struct Proxy { +pub struct Proxy{ runner: runner::Shared, } @@ -17,3 +16,11 @@ impl Proxy { Ok(()) } } + +impl Clone for Proxy { + fn clone(&self) -> Self { + Proxy { + runner: self.runner.clone() + } + } +} diff --git a/src/platform_impl/web/mod.rs b/src/platform_impl/web/mod.rs index 7715336a..a272835d 100644 --- a/src/platform_impl/web/mod.rs +++ b/src/platform_impl/web/mod.rs @@ -21,7 +21,9 @@ 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::monitor::{ + Handle as MonitorHandle, Mode as VideoMode +}; 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 index 1aeaa320..196c23c8 100644 --- a/src/platform_impl/web/monitor.rs +++ b/src/platform_impl/web/monitor.rs @@ -1,5 +1,5 @@ use crate::dpi::{PhysicalPosition, PhysicalSize}; -use crate::monitor::VideoMode; +use crate::monitor::{MonitorHandle, VideoMode}; #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Handle; @@ -26,3 +26,28 @@ impl Handle { std::iter::empty() } } + +#[derive(Derivative)] +#[derivative(Clone, Debug, PartialEq, Eq, Hash)] +pub struct Mode; + +impl Mode { + pub fn size(&self) -> PhysicalSize { + unimplemented!(); + } + + pub fn bit_depth(&self) -> u16 { + unimplemented!(); + } + + pub fn refresh_rate(&self) -> u16 { + 32 + } + + pub fn monitor(&self) -> MonitorHandle { + MonitorHandle { + inner: Handle + } + } +} + diff --git a/src/platform_impl/web/window.rs b/src/platform_impl/web/window.rs index 5a14c6db..0ac1cdc2 100644 --- a/src/platform_impl/web/window.rs +++ b/src/platform_impl/web/window.rs @@ -2,7 +2,9 @@ use crate::dpi::{LogicalPosition, LogicalSize}; use crate::error::{ExternalError, NotSupportedError, OsError as RootOE}; use crate::icon::Icon; use crate::monitor::MonitorHandle as RootMH; -use crate::window::{CursorIcon, WindowAttributes, WindowId as RootWI}; +use crate::window::{CursorIcon, Fullscreen, WindowAttributes, WindowId as RootWI}; + +use raw_window_handle::{RawWindowHandle, web::WebHandle}; use super::{backend, monitor, EventLoopWindowTarget}; @@ -203,13 +205,13 @@ impl Window { } #[inline] - pub fn fullscreen(&self) -> Option { + pub fn fullscreen(&self) -> Option { // TODO: should there be a maximization / fullscreen API? None } #[inline] - pub fn set_fullscreen(&self, _monitor: Option) { + pub fn set_fullscreen(&self, _monitor: Option) { // TODO: should there be a maximization / fullscreen API? } @@ -254,6 +256,16 @@ impl Window { pub fn id(&self) -> Id { return self.id; } + + #[inline] + pub fn raw_window_handle(&self) -> raw_window_handle::RawWindowHandle { + let handle = WebHandle { + id: self.id.0, + ..WebHandle::empty() + }; + + raw_window_handle::RawWindowHandle::Web(handle) + } } #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] From dcd9ddde50e82dff90cc2170a6fe694060b3db57 Mon Sep 17 00:00:00 2001 From: Ryan Goldstein Date: Tue, 24 Sep 2019 19:44:43 -0400 Subject: [PATCH 59/69] Fix the examples --- examples/custom_events.rs | 28 +++++++++++++++++----------- examples/multithreaded.rs | 8 -------- examples/window_run_return.rs | 16 +--------------- 3 files changed, 18 insertions(+), 34 deletions(-) diff --git a/examples/custom_events.rs b/examples/custom_events.rs index c85d4ecd..dba43624 100644 --- a/examples/custom_events.rs +++ b/examples/custom_events.rs @@ -1,15 +1,16 @@ -use winit::{ - event::{Event, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, - window::WindowBuilder, -}; - -#[derive(Debug, Clone, Copy)] -enum CustomEvent { - Timer, -} - +#[cfg(not(target_arch = "wasm32"))] fn main() { + use winit::{ + event::{Event, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + window::WindowBuilder, + }; + + #[derive(Debug, Clone, Copy)] + enum CustomEvent { + Timer, + } + let event_loop = EventLoop::::with_user_event(); let _window = WindowBuilder::new() @@ -39,3 +40,8 @@ fn main() { _ => *control_flow = ControlFlow::Wait, }); } + +#[cfg(target_arch = "wasm32")] +fn main() { + panic!("This example is not supported on web."); +} diff --git a/examples/multithreaded.rs b/examples/multithreaded.rs index 65afeae8..aa03b627 100644 --- a/examples/multithreaded.rs +++ b/examples/multithreaded.rs @@ -2,15 +2,7 @@ fn main() { extern crate env_logger; -<<<<<<< HEAD use std::{collections::HashMap, sync::mpsc, thread, time::Duration}; -======= -use winit::{ - event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, - window::{CursorIcon, Fullscreen, WindowBuilder}, -}; ->>>>>>> master use winit::{ event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}, diff --git a/examples/window_run_return.rs b/examples/window_run_return.rs index 82c49d61..29ea7b3c 100644 --- a/examples/window_run_return.rs +++ b/examples/window_run_return.rs @@ -1,6 +1,3 @@ -<<<<<<< HEAD -#[cfg(not(target_arch = "wasm32"))] -======= // Limit this example to only compatible platforms. #[cfg(any( target_os = "windows", @@ -11,7 +8,6 @@ target_os = "netbsd", target_os = "openbsd" ))] ->>>>>>> master fn main() { use winit::{ event::{Event, WindowEvent}, @@ -19,10 +15,6 @@ fn main() { platform::desktop::EventLoopExtDesktop, window::WindowBuilder, }; -<<<<<<< HEAD -======= - ->>>>>>> master let mut event_loop = EventLoop::new(); let window = WindowBuilder::new() @@ -57,13 +49,7 @@ fn main() { println!("Okay we're done now for real."); } -<<<<<<< HEAD -#[cfg(target_arch = "wasm32")] -fn main() { - panic!("Example not supported on Wasm"); -======= -#[cfg(any(target_os = "ios", target_os = "android"))] +#[cfg(any(target_os = "ios", target_os = "android", target_arch = "wasm32"))] fn main() { println!("This platform doesn't support run_return."); ->>>>>>> master } From ea93a0130d193945dd48d32de01deeb30f2bd7fb Mon Sep 17 00:00:00 2001 From: Ryan Goldstein Date: Fri, 27 Sep 2019 16:17:40 -0400 Subject: [PATCH 60/69] Switch to the released version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 9aa5d983..d21b59fe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,7 @@ libc = "0.2" log = "0.4" serde = { version = "1", optional = true, features = ["serde_derive"] } derivative = "1.0.2" -raw-window-handle = { git = "https://github.com/ryanisaacg/raw-window-handle", branch = "add-web-support" } +raw-window-handle = "0.2.0" [dev-dependencies] image = "0.21" From a336e9e959627f473a9517a98d092d7d6f56578e Mon Sep 17 00:00:00 2001 From: Ryan Goldstein Date: Fri, 27 Sep 2019 16:57:25 -0400 Subject: [PATCH 61/69] Fix the formatting in the changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b0d0aa4a..13c99439 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Unreleased -- Add web support via the 'stdweb' or 'web-sys' features +- Add web support via the 'stdweb' or 'web-sys' features - On macOS, implement `run_return`. - On iOS, fix inverted parameter in `set_prefers_home_indicator_hidden`. - On X11, performance is improved when rapidly calling `Window::set_cursor_icon`. From cf3b0f3b707e580094aac839c51b0fcdf728ea47 Mon Sep 17 00:00:00 2001 From: Ryan Goldstein Date: Fri, 27 Sep 2019 17:06:14 -0400 Subject: [PATCH 62/69] Attach the raw handle data attribute --- src/platform_impl/web/event_loop/window_target.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/platform_impl/web/event_loop/window_target.rs b/src/platform_impl/web/event_loop/window_target.rs index e7bab388..60c917d8 100644 --- a/src/platform_impl/web/event_loop/window_target.rs +++ b/src/platform_impl/web/event_loop/window_target.rs @@ -38,6 +38,8 @@ impl WindowTarget { pub fn register(&self, canvas: &mut backend::Canvas, id: window::Id) { let runner = self.runner.clone(); + canvas.set_attribute("data-raw-handle", &id.0.to_string()); + canvas.on_blur(move || { runner.send_event(Event::WindowEvent { window_id: WindowId(id), From ab4d971c5e8aa6bc945cadd8e1185e711ff33cfd Mon Sep 17 00:00:00 2001 From: Ryan Goldstein Date: Mon, 30 Sep 2019 15:55:27 -0400 Subject: [PATCH 63/69] Fix imports in the multithreaded example --- examples/multithreaded.rs | 2 +- src/platform_impl/ios/view.rs | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/multithreaded.rs b/examples/multithreaded.rs index aa03b627..8ce36077 100644 --- a/examples/multithreaded.rs +++ b/examples/multithreaded.rs @@ -7,7 +7,7 @@ fn main() { use winit::{ event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}, event_loop::{ControlFlow, EventLoop}, - window::{CursorIcon, WindowBuilder}, + window::{CursorIcon, Fullscreen, WindowBuilder}, }; const WINDOW_COUNT: usize = 3; diff --git a/src/platform_impl/ios/view.rs b/src/platform_impl/ios/view.rs index 6f712dd3..5868059b 100644 --- a/src/platform_impl/ios/view.rs +++ b/src/platform_impl/ios/view.rs @@ -500,7 +500,9 @@ pub unsafe fn create_window( let () = msg_send![uiscreen, setCurrentMode: video_mode.video_mode.screen_mode]; msg_send![window, setScreen:video_mode.monitor().ui_screen()] } - Some(Fullscreen::Borderless(ref monitor)) => msg_send![window, setScreen:monitor.ui_screen()], + Some(Fullscreen::Borderless(ref monitor)) => { + msg_send![window, setScreen:monitor.ui_screen()] + } None => (), } From 02f281569d757039da24f883274f6ef44ad8e154 Mon Sep 17 00:00:00 2001 From: Ryan Goldstein Date: Mon, 30 Sep 2019 16:33:41 -0400 Subject: [PATCH 64/69] Update cargo fmt --- src/platform_impl/ios/view.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/platform_impl/ios/view.rs b/src/platform_impl/ios/view.rs index 5868059b..6f712dd3 100644 --- a/src/platform_impl/ios/view.rs +++ b/src/platform_impl/ios/view.rs @@ -500,9 +500,7 @@ pub unsafe fn create_window( let () = msg_send![uiscreen, setCurrentMode: video_mode.video_mode.screen_mode]; msg_send![window, setScreen:video_mode.monitor().ui_screen()] } - Some(Fullscreen::Borderless(ref monitor)) => { - msg_send![window, setScreen:monitor.ui_screen()] - } + Some(Fullscreen::Borderless(ref monitor)) => msg_send![window, setScreen:monitor.ui_screen()], None => (), } From 157ca9cd1740b04cb4ae3eb24515ca7a2d9880ac Mon Sep 17 00:00:00 2001 From: Ryan G Date: Wed, 9 Oct 2019 19:49:12 -0400 Subject: [PATCH 65/69] Update the feature matrix for wasm (#1218) --- FEATURES.md | 50 +++++++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/FEATURES.md b/FEATURES.md index 9610b7ba..b83caac0 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -163,39 +163,39 @@ Legend: ### Windowing |Feature |Windows|MacOS |Linux x11 |Linux Wayland |Android|iOS |WASM | |-------------------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | -|Window initialization |✔️ |✔️ |▢[#5] |✔️ |▢[#33]|▢[#33] |❓ | -|Providing pointer to init OpenGL |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |❓ | -|Providing pointer to init Vulkan |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |❓ | -|Window decorations |✔️ |✔️ |✔️ |▢[#306] |**N/A**|**N/A**|❓ | -|Window decorations toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❓ | -|Window resizing |✔️ |▢[#219]|✔️ |▢[#306] |**N/A**|**N/A**|❓ | -|Window resize increments |❌ |❌ |❌ |❌ |❌ |❌ |❓ | -|Window transparency |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❓ | -|Window maximization |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❓ | -|Window maximization toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❓ | -|Fullscreen |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |❓ | -|Fullscreen toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |❓ | -|Exclusive fullscreen |✔️ |✔️ |✔️ |**N/A** |❌ |✔️ |❓ | -|HiDPI support |✔️ |✔️ |✔️ |✔️ |▢[#721]|✔️ |❓ | -|Popup windows |❌ |❌ |❌ |❌ |❌ |❌ |❓ | +|Window initialization |✔️ |✔️ |▢[#5] |✔️ |▢[#33]|▢[#33] |✔️ | +|Providing pointer to init OpenGL |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A**| +|Providing pointer to init Vulkan |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |**N/A**| +|Window decorations |✔️ |✔️ |✔️ |▢[#306] |**N/A**|**N/A**|**N/A**| +|Window decorations toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**| +|Window resizing |✔️ |▢[#219]|✔️ |▢[#306] |**N/A**|**N/A**|✔️ | +|Window resize increments |❌ |❌ |❌ |❌ |❌ |❌ |**N/A**| +|Window transparency |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|N/A | +|Window maximization |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**| +|Window maximization toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**| +|Fullscreen |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |✔️ | +|Fullscreen toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |✔️ | +|Exclusive fullscreen |✔️ |✔️ |✔️ |**N/A** |❌ |✔️ |**N/A**| +|HiDPI support |✔️ |✔️ |✔️ |✔️ |▢[#721]|✔️ |**N/A**| +|Popup windows |❌ |❌ |❌ |❌ |❌ |❌ |**N/A**| ### System information |Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |WASM | |---------------- | ----- | ---- | ------- | ----------- | ----- | ------- | -------- | -|Monitor list |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |❓ | -|Video mode query |✔️ |✔️ |✔️ |✔️ |❌ |✔️ |❓ | +|Monitor list |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |**N/A**| +|Video mode query |✔️ |✔️ |✔️ |✔️ |❌ |✔️ |**N/A**| ### Input handling |Feature |Windows |MacOS |Linux x11|Linux Wayland|Android|iOS |WASM | |----------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | -|Mouse events |✔️ |▢[#63] |✔️ |✔️ |**N/A**|**N/A**|❓ | -|Mouse set location |✔️ |✔️ |✔️ |❓ |**N/A**|**N/A**|❓ | -|Cursor grab |✔️ |▢[#165] |▢[#242] |❌[#306] |**N/A**|**N/A**|❓ | -|Cursor icon |✔️ |✔️ |✔️ |❌[#306] |**N/A**|**N/A**|❓ | -|Touch events |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |❓ | -|Touch pressure |✔️ |❌ |❌ |❌ |❌ |✔️ |❓ | -|Multitouch |✔️ |❌ |✔️ |✔️ |❓ |✔️ |❓ | -|Keyboard events |✔️ |✔️ |✔️ |✔️ |❓ |❌ |❓ | +|Mouse events |✔️ |▢[#63] |✔️ |✔️ |**N/A**|**N/A**|✔️ | +|Mouse set location |✔️ |✔️ |✔️ |❓ |**N/A**|**N/A**|**N/A**| +|Cursor grab |✔️ |▢[#165] |▢[#242] |✔️ |**N/A**|**N/A**|❓ | +|Cursor icon |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ | +|Touch events |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |✔️ | +|Touch pressure |✔️ |❌ |❌ |❌ |❌ |✔️ |✔️ | +|Multitouch |✔️ |❌ |✔️ |✔️ |❓ |✔️ |✔️ | +|Keyboard events |✔️ |✔️ |✔️ |✔️ |❓ |❌ |✔️ | |Drag & Drop |▢[#720] |▢[#720] |▢[#720] |❌[#306] |**N/A**|**N/A**|❓ | |Raw Device Events |▢[#750] |▢[#750] |▢[#750] |❌ |❌ |❌ |❓ | |Gamepad/Joystick events |❌[#804] |❌ |❌ |❌ |❌ |❌ |❓ | From bedb889693385bf77520eba0f74b24c45eca84d4 Mon Sep 17 00:00:00 2001 From: Ryan G Date: Fri, 11 Oct 2019 11:41:49 -0400 Subject: [PATCH 66/69] Fix the event key code variable name (#1219) --- src/platform_impl/web/stdweb/event.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform_impl/web/stdweb/event.rs b/src/platform_impl/web/stdweb/event.rs index f05e1ddb..14456dc0 100644 --- a/src/platform_impl/web/stdweb/event.rs +++ b/src/platform_impl/web/stdweb/event.rs @@ -42,7 +42,7 @@ pub fn mouse_scroll_delta(event: &MouseWheelEvent) -> Option { } pub fn scan_code(event: &T) -> ScanCode { - let key_code = js! ( return @{event}.key_code; ); + let key_code = js! ( return @{event}.keyCode; ); key_code .try_into() From 3ff4834bd5c217482a09e412ce168fc29f1a2825 Mon Sep 17 00:00:00 2001 From: Ryan G Date: Fri, 11 Oct 2019 11:45:07 -0400 Subject: [PATCH 67/69] Add web fullscreen support (#1142) Adds fullscreen using native web APIs to the stdweb and web-sys backends. Due to limitations of browser APIs, requests for fullscreen can only be fulfilled during a short-lived user-triggered event. This commit does automatically handle that under the hood, but it does introduce unavoidable latency in full-screening the canvas. --- Cargo.toml | 4 +- .../web/event_loop/window_target.rs | 30 ++++- src/platform_impl/web/stdweb/canvas.rs | 57 ++++++++- src/platform_impl/web/stdweb/mod.rs | 25 +++- src/platform_impl/web/web_sys/canvas.rs | 120 +++++++++++++----- src/platform_impl/web/web_sys/mod.rs | 39 +++++- src/platform_impl/web/window.rs | 15 ++- 7 files changed, 242 insertions(+), 48 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ddffed0c..80ca553f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -115,6 +115,6 @@ optional = true [target.'cfg(target_arch = "wasm32")'.dependencies.std_web] package = "stdweb" -version = "0.4.18" +version = "=0.4.20" optional = true - +features = ["experimental_features_which_may_break_on_minor_version_bumps"] diff --git a/src/platform_impl/web/event_loop/window_target.rs b/src/platform_impl/web/event_loop/window_target.rs index 60c917d8..d302edb8 100644 --- a/src/platform_impl/web/event_loop/window_target.rs +++ b/src/platform_impl/web/event_loop/window_target.rs @@ -1,4 +1,5 @@ use super::{backend, device, proxy::Proxy, runner, window}; +use crate::dpi::LogicalSize; use crate::event::{DeviceId, ElementState, Event, KeyboardInput, TouchPhase, WindowEvent}; use crate::event_loop::ControlFlow; use crate::window::WindowId; @@ -37,7 +38,6 @@ impl WindowTarget { pub fn register(&self, canvas: &mut backend::Canvas, id: window::Id) { let runner = self.runner.clone(); - canvas.set_attribute("data-raw-handle", &id.0.to_string()); canvas.on_blur(move || { @@ -165,5 +165,33 @@ impl WindowTarget { }, }); }); + + let runner = self.runner.clone(); + let raw = canvas.raw().clone(); + let mut intended_size = LogicalSize { + width: raw.width() as f64, + height: raw.height() as f64, + }; + canvas.on_fullscreen_change(move || { + // If the canvas is marked as fullscreen, it is moving *into* fullscreen + // If it is not, it is moving *out of* fullscreen + let new_size = if backend::is_fullscreen(&raw) { + intended_size = LogicalSize { + width: raw.width() as f64, + height: raw.height() as f64, + }; + + backend::window_size() + } else { + intended_size + }; + raw.set_width(new_size.width as u32); + raw.set_height(new_size.height as u32); + runner.send_event(Event::WindowEvent { + window_id: WindowId(id), + event: WindowEvent::Resized(new_size), + }); + runner.request_redraw(WindowId(id)); + }); } } diff --git a/src/platform_impl/web/stdweb/canvas.rs b/src/platform_impl/web/stdweb/canvas.rs index 92e46bac..a0ff5af2 100644 --- a/src/platform_impl/web/stdweb/canvas.rs +++ b/src/platform_impl/web/stdweb/canvas.rs @@ -4,11 +4,14 @@ use crate::error::OsError as RootOE; use crate::event::{ModifiersState, MouseButton, MouseScrollDelta, ScanCode, VirtualKeyCode}; use crate::platform_impl::OsError; +use std::cell::RefCell; +use std::rc::Rc; use stdweb::traits::IPointerEvent; use stdweb::unstable::TryInto; use stdweb::web::event::{ - BlurEvent, ConcreteEvent, FocusEvent, KeyDownEvent, KeyPressEvent, KeyUpEvent, MouseWheelEvent, - PointerDownEvent, PointerMoveEvent, PointerOutEvent, PointerOverEvent, PointerUpEvent, + BlurEvent, ConcreteEvent, FocusEvent, FullscreenChangeEvent, KeyDownEvent, KeyPressEvent, + KeyUpEvent, MouseWheelEvent, PointerDownEvent, PointerMoveEvent, PointerOutEvent, + PointerOverEvent, PointerUpEvent, }; use stdweb::web::html_element::CanvasElement; use stdweb::web::{ @@ -28,6 +31,8 @@ pub struct Canvas { on_mouse_press: Option, on_mouse_release: Option, on_mouse_wheel: Option, + on_fullscreen_change: Option, + wants_fullscreen: Rc>, } impl Drop for Canvas { @@ -66,6 +71,8 @@ impl Canvas { on_mouse_release: None, on_mouse_press: None, on_mouse_wheel: None, + on_fullscreen_change: None, + wants_fullscreen: Rc::new(RefCell::new(false)), }) } @@ -120,7 +127,7 @@ impl Canvas { where F: 'static + FnMut(ScanCode, Option, ModifiersState), { - self.on_keyboard_release = Some(self.add_event(move |event: KeyUpEvent| { + self.on_keyboard_release = Some(self.add_user_event(move |event: KeyUpEvent| { handler( event::scan_code(&event), event::virtual_key_code(&event), @@ -133,7 +140,7 @@ impl Canvas { where F: 'static + FnMut(ScanCode, Option, ModifiersState), { - self.on_keyboard_press = Some(self.add_event(move |event: KeyDownEvent| { + self.on_keyboard_press = Some(self.add_user_event(move |event: KeyDownEvent| { handler( event::scan_code(&event), event::virtual_key_code(&event), @@ -151,7 +158,7 @@ impl Canvas { // The `keypress` event is deprecated, but there does not seem to be a // viable/compatible alternative as of now. `beforeinput` is still widely // unsupported. - self.on_received_character = Some(self.add_event(move |event: KeyPressEvent| { + self.on_received_character = Some(self.add_user_event(move |event: KeyPressEvent| { handler(event::codepoint(&event)); })); } @@ -178,7 +185,7 @@ impl Canvas { where F: 'static + FnMut(i32, MouseButton, ModifiersState), { - self.on_mouse_release = Some(self.add_event(move |event: PointerUpEvent| { + self.on_mouse_release = Some(self.add_user_event(move |event: PointerUpEvent| { handler( event.pointer_id(), event::mouse_button(&event), @@ -191,7 +198,7 @@ impl Canvas { where F: 'static + FnMut(i32, MouseButton, ModifiersState), { - self.on_mouse_press = Some(self.add_event(move |event: PointerDownEvent| { + self.on_mouse_press = Some(self.add_user_event(move |event: PointerDownEvent| { handler( event.pointer_id(), event::mouse_button(&event), @@ -224,6 +231,13 @@ impl Canvas { })); } + pub fn on_fullscreen_change(&mut self, mut handler: F) + where + F: 'static + FnMut(), + { + self.on_fullscreen_change = Some(self.add_event(move |_: FullscreenChangeEvent| handler())); + } + fn add_event(&self, mut handler: F) -> EventListenerHandle where E: ConcreteEvent, @@ -236,4 +250,33 @@ impl Canvas { handler(event); }) } + + // The difference between add_event and add_user_event is that the latter has a special meaning + // for browser security. A user event is a deliberate action by the user (like a mouse or key + // press) and is the only time things like a fullscreen request may be successfully completed.) + fn add_user_event(&self, mut handler: F) -> EventListenerHandle + where + E: ConcreteEvent, + F: 'static + FnMut(E), + { + let wants_fullscreen = self.wants_fullscreen.clone(); + let canvas = self.raw.clone(); + + self.add_event(move |event: E| { + handler(event); + + if *wants_fullscreen.borrow() { + canvas.request_fullscreen(); + *wants_fullscreen.borrow_mut() = false; + } + }) + } + + pub fn request_fullscreen(&self) { + *self.wants_fullscreen.borrow_mut() = true; + } + + pub fn is_fullscreen(&self) -> bool { + super::is_fullscreen(&self.raw) + } } diff --git a/src/platform_impl/web/stdweb/mod.rs b/src/platform_impl/web/stdweb/mod.rs index f7caa232..d49dbf02 100644 --- a/src/platform_impl/web/stdweb/mod.rs +++ b/src/platform_impl/web/stdweb/mod.rs @@ -5,19 +5,24 @@ mod timeout; pub use self::canvas::Canvas; pub use self::timeout::Timeout; +use crate::dpi::LogicalSize; use crate::platform::web::WindowExtStdweb; use crate::window::Window; use stdweb::js; use stdweb::web::event::BeforeUnloadEvent; -use stdweb::web::html_element::CanvasElement; use stdweb::web::window; use stdweb::web::IEventTarget; +use stdweb::web::{document, html_element::CanvasElement, Element}; pub fn throw(msg: &str) { js! { throw @{msg} } } +pub fn exit_fullscreen() { + document().exit_fullscreen(); +} + pub fn on_unload(mut handler: impl FnMut() + 'static) { window().add_event_listener(move |_: BeforeUnloadEvent| handler()); } @@ -27,3 +32,21 @@ impl WindowExtStdweb for Window { self.window.canvas().raw().clone() } } + +pub fn window_size() -> LogicalSize { + let window = window(); + let width = window.inner_width() as f64; + let height = window.inner_height() as f64; + + LogicalSize { width, height } +} + +pub fn is_fullscreen(canvas: &CanvasElement) -> bool { + match document().fullscreen_element() { + Some(elem) => { + let raw: Element = canvas.clone().into(); + raw == elem + } + None => false, + } +} diff --git a/src/platform_impl/web/web_sys/canvas.rs b/src/platform_impl/web/web_sys/canvas.rs index bcf6ce4a..0543055e 100644 --- a/src/platform_impl/web/web_sys/canvas.rs +++ b/src/platform_impl/web/web_sys/canvas.rs @@ -4,8 +4,11 @@ use crate::error::OsError as RootOE; use crate::event::{ModifiersState, MouseButton, MouseScrollDelta, ScanCode, VirtualKeyCode}; use crate::platform_impl::OsError; +use std::cell::RefCell; +use std::rc::Rc; + use wasm_bindgen::{closure::Closure, JsCast}; -use web_sys::{FocusEvent, HtmlCanvasElement, KeyboardEvent, PointerEvent, WheelEvent}; +use web_sys::{Event, FocusEvent, HtmlCanvasElement, KeyboardEvent, PointerEvent, WheelEvent}; pub struct Canvas { raw: HtmlCanvasElement, @@ -20,6 +23,8 @@ pub struct Canvas { on_mouse_press: Option>, on_mouse_release: Option>, on_mouse_wheel: Option>, + on_fullscreen_change: Option>, + wants_fullscreen: Rc>, } impl Drop for Canvas { @@ -64,6 +69,8 @@ impl Canvas { on_mouse_release: None, on_mouse_press: None, on_mouse_wheel: None, + on_fullscreen_change: None, + wants_fullscreen: Rc::new(RefCell::new(false)), }) } @@ -118,26 +125,28 @@ impl Canvas { where F: 'static + FnMut(ScanCode, Option, ModifiersState), { - self.on_keyboard_release = Some(self.add_event("keyup", move |event: KeyboardEvent| { - handler( - event::scan_code(&event), - event::virtual_key_code(&event), - event::keyboard_modifiers(&event), - ); - })); + self.on_keyboard_release = + Some(self.add_user_event("keyup", move |event: KeyboardEvent| { + handler( + event::scan_code(&event), + event::virtual_key_code(&event), + event::keyboard_modifiers(&event), + ); + })); } pub fn on_keyboard_press(&mut self, mut handler: F) where F: 'static + FnMut(ScanCode, Option, ModifiersState), { - self.on_keyboard_press = Some(self.add_event("keydown", move |event: KeyboardEvent| { - handler( - event::scan_code(&event), - event::virtual_key_code(&event), - event::keyboard_modifiers(&event), - ); - })); + self.on_keyboard_press = + Some(self.add_user_event("keydown", move |event: KeyboardEvent| { + handler( + event::scan_code(&event), + event::virtual_key_code(&event), + event::keyboard_modifiers(&event), + ); + })); } pub fn on_received_character(&mut self, mut handler: F) @@ -149,10 +158,12 @@ impl Canvas { // The `keypress` event is deprecated, but there does not seem to be a // viable/compatible alternative as of now. `beforeinput` is still widely // unsupported. - self.on_received_character = - Some(self.add_event("keypress", move |event: KeyboardEvent| { + self.on_received_character = Some(self.add_user_event( + "keypress", + move |event: KeyboardEvent| { handler(event::codepoint(&event)); - })); + }, + )); } pub fn on_cursor_leave(&mut self, mut handler: F) @@ -177,26 +188,32 @@ impl Canvas { where F: 'static + FnMut(i32, MouseButton, ModifiersState), { - self.on_mouse_release = Some(self.add_event("pointerup", move |event: PointerEvent| { - handler( - event.pointer_id(), - event::mouse_button(&event), - event::mouse_modifiers(&event), - ); - })); + self.on_mouse_release = Some(self.add_user_event( + "pointerup", + move |event: PointerEvent| { + handler( + event.pointer_id(), + event::mouse_button(&event), + event::mouse_modifiers(&event), + ); + }, + )); } pub fn on_mouse_press(&mut self, mut handler: F) where F: 'static + FnMut(i32, MouseButton, ModifiersState), { - self.on_mouse_press = Some(self.add_event("pointerdown", move |event: PointerEvent| { - handler( - event.pointer_id(), - event::mouse_button(&event), - event::mouse_modifiers(&event), - ); - })); + self.on_mouse_press = Some(self.add_user_event( + "pointerdown", + move |event: PointerEvent| { + handler( + event.pointer_id(), + event::mouse_button(&event), + event::mouse_modifiers(&event), + ); + }, + )); } pub fn on_cursor_move(&mut self, mut handler: F) @@ -223,6 +240,14 @@ impl Canvas { })); } + pub fn on_fullscreen_change(&mut self, mut handler: F) + where + F: 'static + FnMut(), + { + self.on_fullscreen_change = + Some(self.add_event("fullscreenchange", move |_: Event| handler())); + } + fn add_event(&self, event_name: &str, mut handler: F) -> Closure where E: 'static + AsRef + wasm_bindgen::convert::FromWasmAbi, @@ -244,4 +269,35 @@ impl Canvas { closure } + + // The difference between add_event and add_user_event is that the latter has a special meaning + // for browser security. A user event is a deliberate action by the user (like a mouse or key + // press) and is the only time things like a fullscreen request may be successfully completed.) + fn add_user_event(&self, event_name: &str, mut handler: F) -> Closure + where + E: 'static + AsRef + wasm_bindgen::convert::FromWasmAbi, + F: 'static + FnMut(E), + { + let wants_fullscreen = self.wants_fullscreen.clone(); + let canvas = self.raw.clone(); + + self.add_event(event_name, move |event: E| { + handler(event); + + if *wants_fullscreen.borrow() { + canvas + .request_fullscreen() + .expect("Failed to enter fullscreen"); + *wants_fullscreen.borrow_mut() = false; + } + }) + } + + pub fn request_fullscreen(&self) { + *self.wants_fullscreen.borrow_mut() = true; + } + + pub fn is_fullscreen(&self) -> bool { + super::is_fullscreen(&self.raw) + } } diff --git a/src/platform_impl/web/web_sys/mod.rs b/src/platform_impl/web/web_sys/mod.rs index 811b6f68..205519d1 100644 --- a/src/platform_impl/web/web_sys/mod.rs +++ b/src/platform_impl/web/web_sys/mod.rs @@ -5,15 +5,23 @@ mod timeout; pub use self::canvas::Canvas; pub use self::timeout::Timeout; +use crate::dpi::LogicalSize; use crate::platform::web::WindowExtWebSys; use crate::window::Window; use wasm_bindgen::{closure::Closure, JsCast}; -use web_sys::{BeforeUnloadEvent, HtmlCanvasElement}; +use web_sys::{window, BeforeUnloadEvent, Element, HtmlCanvasElement}; pub fn throw(msg: &str) { wasm_bindgen::throw_str(msg); } +pub fn exit_fullscreen() { + let window = web_sys::window().expect("Failed to obtain window"); + let document = window.document().expect("Failed to obtain document"); + + document.exit_fullscreen(); +} + pub fn on_unload(mut handler: impl FnMut() + 'static) { let window = web_sys::window().expect("Failed to obtain window"); @@ -31,3 +39,32 @@ impl WindowExtWebSys for Window { self.window.canvas().raw().clone() } } + +pub fn window_size() -> LogicalSize { + let window = web_sys::window().expect("Failed to obtain window"); + let width = window + .inner_width() + .expect("Failed to get width") + .as_f64() + .expect("Failed to get width as f64"); + let height = window + .inner_height() + .expect("Failed to get height") + .as_f64() + .expect("Failed to get height as f64"); + + LogicalSize { width, height } +} + +pub fn is_fullscreen(canvas: &HtmlCanvasElement) -> bool { + let window = window().expect("Failed to obtain window"); + let document = window.document().expect("Failed to obtain document"); + + match document.fullscreen_element() { + Some(elem) => { + let raw: Element = canvas.clone().into(); + raw == elem + } + None => false, + } +} diff --git a/src/platform_impl/web/window.rs b/src/platform_impl/web/window.rs index 43c0511d..8752d8b6 100644 --- a/src/platform_impl/web/window.rs +++ b/src/platform_impl/web/window.rs @@ -206,13 +206,20 @@ impl Window { #[inline] pub fn fullscreen(&self) -> Option { - // TODO: should there be a maximization / fullscreen API? - None + if self.canvas.is_fullscreen() { + Some(Fullscreen::Borderless(self.current_monitor())) + } else { + None + } } #[inline] - pub fn set_fullscreen(&self, _monitor: Option) { - // TODO: should there be a maximization / fullscreen API? + pub fn set_fullscreen(&self, monitor: Option) { + if monitor.is_some() { + self.canvas.request_fullscreen(); + } else if self.canvas.is_fullscreen() { + backend::exit_fullscreen(); + } } #[inline] From a557b3cfb60bbd1d75ffd6dfd8e52c4ceea95167 Mon Sep 17 00:00:00 2001 From: Ryan G Date: Sat, 12 Oct 2019 23:44:30 -0400 Subject: [PATCH 68/69] Add web targets to travis (#1220) * Add web targets to travis * Fix some bash syntax * Avoid crash on providing no features * Fix syntax error * Syntax? * Fix pleas * Fix features check * Add a comment * Add nightly builds --- .travis.yml | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index aaa56d9e..7a73219b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -58,6 +58,21 @@ matrix: os: osx rust: stable + # wasm stdweb + - env: TARGET=wasm32-unknown-unknown WEB=web FEATURES=stdweb + os: linux + rust: stable + - env: TARGET=wasm32-unknown-unknown WEB=web FEATURES=stdweb + os: linux + rust: nightly + # wasm web-sys + - env: TARGET=wasm32-unknown-unknown FEATURES=web-sys + os: linux + rust: stable + - env: TARGET=wasm32-unknown-unknown FEATURES=web-sys + os: linux + rust: nightly + install: - rustup self update - rustup target add $TARGET; true @@ -66,11 +81,19 @@ install: script: - cargo +stable fmt --all -- --check - - cargo build --target $TARGET --verbose - - cargo build --target $TARGET --features serde --verbose + # Install cargo-web to build stdweb + - if [[ $WEB = "web" ]]; then cargo install -f cargo-web; fi + # Build without serde then with serde + - if [[ -z "$FEATURES" ]]; then + cargo $WEB build --target $TARGET --verbose; + else + cargo $WEB build --target $TARGET --features $FEATURES --verbose; + fi + - cargo $WEB build --target $TARGET --features serde,$FEATURES --verbose # Running iOS apps on macOS requires the Simulator so we skip that for now - - if [[ $TARGET != *-apple-ios ]]; then cargo test --target $TARGET --verbose; fi - - if [[ $TARGET != *-apple-ios ]]; then cargo test --target $TARGET --features serde --verbose; fi + # The web targets also don't support running tests + - if [[ $TARGET != *-apple-ios && $TARGET != wasm32-* ]]; then cargo test --target $TARGET --verbose; fi + - if [[ $TARGET != *-apple-ios && $TARGET != wasm32-* ]]; then cargo test --target $TARGET --features serde --verbose; fi after_success: - | From 35bc65f6fad14a1646420377d3b93e9c3fb0fb24 Mon Sep 17 00:00:00 2001 From: Ryan Goldstein Date: Sun, 13 Oct 2019 14:36:54 -0400 Subject: [PATCH 69/69] Remove usage of derivative --- src/platform_impl/web/monitor.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/platform_impl/web/monitor.rs b/src/platform_impl/web/monitor.rs index 0bec465a..4889c88d 100644 --- a/src/platform_impl/web/monitor.rs +++ b/src/platform_impl/web/monitor.rs @@ -29,8 +29,7 @@ impl Handle { } } -#[derive(Derivative)] -#[derivative(Clone, Debug, PartialEq, Eq, Hash)] +#[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct Mode; impl Mode {