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) {