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(); + } +}