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