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;