diff --git a/packages/core/src/events.rs b/packages/core/src/events.rs index b85e0394d..644d69f29 100644 --- a/packages/core/src/events.rs +++ b/packages/core/src/events.rs @@ -28,6 +28,27 @@ pub struct Event { } impl Event { + /// Map the event data to a new type + /// + /// # Example + /// + /// ```rust, ignore + /// rsx! { + /// button { + /// onclick: move |evt: Event| { + /// let data = evt.map(|data| data.value()); + /// assert_eq!(data.inner(), "hello world"); + /// } + /// } + /// } + /// ``` + pub fn map U>(&self, f: F) -> Event { + Event { + data: Rc::new(f(&self.data)), + propagates: self.propagates.clone(), + } + } + /// Prevent this event from continuing to bubble up the tree to parent elements. /// /// # Example diff --git a/packages/html/src/events.rs b/packages/html/src/events.rs index f044b7b43..a24294378 100644 --- a/packages/html/src/events.rs +++ b/packages/html/src/events.rs @@ -1,3 +1,6 @@ +use std::any::Any; +use std::sync::RwLock; + macro_rules! impl_event { ( $data:ty; @@ -12,8 +15,8 @@ macro_rules! impl_event { pub fn $name<'a, E: crate::EventReturn, T>(_cx: &'a ::dioxus_core::ScopeState, mut _f: impl FnMut(::dioxus_core::Event<$data>) -> E + 'a) -> ::dioxus_core::Attribute<'a> { ::dioxus_core::Attribute::new( stringify!($name), - _cx.listener(move |e: ::dioxus_core::Event<$data>| { - _f(e).spawn(_cx); + _cx.listener(move |e: ::dioxus_core::Event| { + _f(e.map(|e|e.into())).spawn(_cx); }), None, false, @@ -23,6 +26,172 @@ macro_rules! impl_event { }; } +static EVENT_CONVERTER: RwLock>> = RwLock::new(None); + +pub fn set_event_converter(converter: Box) { + *EVENT_CONVERTER.write().unwrap() = Some(converter); +} + +pub(crate) fn with_event_converter(f: F) -> R +where + F: FnOnce(&dyn HtmlEventConverter) -> R, +{ + let converter = EVENT_CONVERTER.read().unwrap(); + f(converter.as_ref().unwrap().as_ref()) +} + +/// A platform specific event. +pub struct PlatformEventData { + event: Box, +} + +impl PlatformEventData { + pub fn new(event: Box) -> Self { + Self { event } + } + + pub fn downcast(&self) -> Option<&T> { + self.event.downcast_ref::() + } + + pub fn downcast_mut(&mut self) -> Option<&mut T> { + self.event.downcast_mut::() + } + + pub fn into_inner(self) -> Option { + self.event.downcast::().ok().map(|e| *e) + } +} + +pub trait HtmlEventConverter: Send + Sync { + fn convert_animation_data(&self, event: &PlatformEventData) -> AnimationData; + fn convert_clipboard_data(&self, event: &PlatformEventData) -> ClipboardData; + fn convert_composition_data(&self, event: &PlatformEventData) -> CompositionData; + fn convert_drag_data(&self, event: &PlatformEventData) -> DragData; + fn convert_focus_data(&self, event: &PlatformEventData) -> FocusData; + fn convert_form_data(&self, event: &PlatformEventData) -> FormData; + fn convert_image_data(&self, event: &PlatformEventData) -> ImageData; + fn convert_keyboard_data(&self, event: &PlatformEventData) -> KeyboardData; + fn convert_media_data(&self, event: &PlatformEventData) -> MediaData; + fn convert_mounted_data(&self, event: &PlatformEventData) -> MountedData; + fn convert_mouse_data(&self, event: &PlatformEventData) -> MouseData; + fn convert_pointer_data(&self, event: &PlatformEventData) -> PointerData; + fn convert_scroll_data(&self, event: &PlatformEventData) -> ScrollData; + fn convert_selection_data(&self, event: &PlatformEventData) -> SelectionData; + fn convert_toggle_data(&self, event: &PlatformEventData) -> ToggleData; + fn convert_touch_data(&self, event: &PlatformEventData) -> TouchData; + fn convert_transition_data(&self, event: &PlatformEventData) -> TransitionData; + fn convert_wheel_data(&self, event: &PlatformEventData) -> WheelData; +} + +impl Into for &PlatformEventData { + fn into(self) -> AnimationData { + with_event_converter(|c| c.convert_animation_data(self)) + } +} + +impl Into for &PlatformEventData { + fn into(self) -> ClipboardData { + with_event_converter(|c| c.convert_clipboard_data(self)) + } +} + +impl Into for &PlatformEventData { + fn into(self) -> CompositionData { + with_event_converter(|c| c.convert_composition_data(self)) + } +} + +impl Into for &PlatformEventData { + fn into(self) -> DragData { + with_event_converter(|c| c.convert_drag_data(self)) + } +} + +impl Into for &PlatformEventData { + fn into(self) -> FocusData { + with_event_converter(|c| c.convert_focus_data(self)) + } +} + +impl Into for &PlatformEventData { + fn into(self) -> FormData { + with_event_converter(|c| c.convert_form_data(self)) + } +} + +impl Into for &PlatformEventData { + fn into(self) -> ImageData { + with_event_converter(|c| c.convert_image_data(self)) + } +} + +impl Into for &PlatformEventData { + fn into(self) -> KeyboardData { + with_event_converter(|c| c.convert_keyboard_data(self)) + } +} + +impl Into for &PlatformEventData { + fn into(self) -> MediaData { + with_event_converter(|c| c.convert_media_data(self)) + } +} + +impl Into for &PlatformEventData { + fn into(self) -> MountedData { + with_event_converter(|c| c.convert_mounted_data(self)) + } +} + +impl Into for &PlatformEventData { + fn into(self) -> MouseData { + with_event_converter(|c| c.convert_mouse_data(self)) + } +} + +impl Into for &PlatformEventData { + fn into(self) -> PointerData { + with_event_converter(|c| c.convert_pointer_data(self)) + } +} + +impl Into for &PlatformEventData { + fn into(self) -> ScrollData { + with_event_converter(|c| c.convert_scroll_data(self)) + } +} + +impl Into for &PlatformEventData { + fn into(self) -> SelectionData { + with_event_converter(|c| c.convert_selection_data(self)) + } +} + +impl Into for &PlatformEventData { + fn into(self) -> ToggleData { + with_event_converter(|c| c.convert_toggle_data(self)) + } +} + +impl Into for &PlatformEventData { + fn into(self) -> TouchData { + with_event_converter(|c| c.convert_touch_data(self)) + } +} + +impl Into for &PlatformEventData { + fn into(self) -> TransitionData { + with_event_converter(|c| c.convert_transition_data(self)) + } +} + +impl Into for &PlatformEventData { + fn into(self) -> WheelData { + with_event_converter(|c| c.convert_wheel_data(self)) + } +} + mod animation; mod clipboard; mod composition; diff --git a/packages/html/src/events/image.rs b/packages/html/src/events/image.rs index cf46838b7..e7d67ea69 100644 --- a/packages/html/src/events/image.rs +++ b/packages/html/src/events/image.rs @@ -26,6 +26,11 @@ impl PartialEq for ImageData { } impl ImageData { + /// Create a new ImageData + pub fn new(e: impl HasImageData) -> Self { + Self { inner: Box::new(e) } + } + /// If the renderer encountered an error while loading the image pub fn load_error(&self) -> bool { self.inner.load_error() diff --git a/packages/html/src/lib.rs b/packages/html/src/lib.rs index 8168d0c5e..2a08241d9 100644 --- a/packages/html/src/lib.rs +++ b/packages/html/src/lib.rs @@ -38,7 +38,7 @@ pub use events::*; pub use global_attributes::*; pub use render_template::*; -mod eval; +pub mod eval; pub mod prelude { pub use crate::eval::*; diff --git a/packages/html/src/web_sys_bind/events.rs b/packages/html/src/web_sys_bind/events.rs index 757cc2ea0..643a06045 100644 --- a/packages/html/src/web_sys_bind/events.rs +++ b/packages/html/src/web_sys_bind/events.rs @@ -386,6 +386,12 @@ impl std::fmt::Display for FocusError { impl std::error::Error for FocusError {} +impl HasScrollData for Event { + fn as_any(&self) -> &dyn std::any::Any { + self + } +} + impl HasClipboardData for Event { fn as_any(&self) -> &dyn std::any::Any { self diff --git a/packages/web/src/dom.rs b/packages/web/src/dom.rs index d2e154129..c19886666 100644 --- a/packages/web/src/dom.rs +++ b/packages/web/src/dom.rs @@ -1,7 +1,6 @@ //! Implementation of a renderer for Dioxus on the web. //! -//! Oustanding todos: -//! - Removing event listeners (delegation) +//! Outstanding todos: //! - Passive event listeners //! - no-op event listener patch for safari //! - tests to ensure dyn_into works for various event types. @@ -10,7 +9,10 @@ use dioxus_core::{ BorrowedAttributeValue, ElementId, Mutation, Template, TemplateAttribute, TemplateNode, }; -use dioxus_html::{event_bubbles, FileEngine, HasFormData, HasImageData, MountedData}; +use dioxus_html::{ + event_bubbles, FileEngine, FormData, HasFormData, HasImageData, HtmlEventConverter, ImageData, + MountedData, ScrollData, +}; use dioxus_interpreter_js::{get_node, minimal_bindings, save_template, Channel}; use futures_channel::mpsc; use js_sys::Array; @@ -263,53 +265,130 @@ impl WebsysDom { } } +struct WebEventConverter; + +fn downcast_event(event: &dioxus_html::PlatformEventData) -> &GenericWebSysEvent { + event + .downcast::() + .expect("event should be a GenericWebSysEvent") +} + +impl HtmlEventConverter for WebEventConverter { + fn convert_animation_data( + &self, + event: &dioxus_html::PlatformEventData, + ) -> dioxus_html::AnimationData { + downcast_event(event).raw.clone().into() + } + + fn convert_clipboard_data( + &self, + event: &dioxus_html::PlatformEventData, + ) -> dioxus_html::ClipboardData { + downcast_event(event).raw.clone().into() + } + + fn convert_composition_data( + &self, + event: &dioxus_html::PlatformEventData, + ) -> dioxus_html::CompositionData { + downcast_event(event).raw.clone().into() + } + + fn convert_drag_data(&self, event: &dioxus_html::PlatformEventData) -> dioxus_html::DragData { + downcast_event(event).raw.clone().into() + } + + fn convert_focus_data(&self, event: &dioxus_html::PlatformEventData) -> dioxus_html::FocusData { + downcast_event(event).raw.clone().into() + } + + fn convert_form_data(&self, event: &dioxus_html::PlatformEventData) -> dioxus_html::FormData { + let event = downcast_event(event); + FormData::new(WebFormData::new(event.element.clone(), event.raw.clone())) + } + + fn convert_image_data(&self, event: &dioxus_html::PlatformEventData) -> dioxus_html::ImageData { + let event = downcast_event(event); + let error = event.raw.type_() == "error"; + ImageData::new(WebImageEvent::new(event.raw.clone(), error)) + } + + fn convert_keyboard_data( + &self, + event: &dioxus_html::PlatformEventData, + ) -> dioxus_html::KeyboardData { + downcast_event(event).raw.clone().into() + } + + fn convert_media_data(&self, event: &dioxus_html::PlatformEventData) -> dioxus_html::MediaData { + downcast_event(event).raw.clone().into() + } + + fn convert_mounted_data(&self, event: &dioxus_html::PlatformEventData) -> MountedData { + MountedData::from(downcast_event(event).element.clone()) + } + + fn convert_mouse_data(&self, event: &dioxus_html::PlatformEventData) -> dioxus_html::MouseData { + downcast_event(event).raw.clone().into() + } + + fn convert_pointer_data( + &self, + event: &dioxus_html::PlatformEventData, + ) -> dioxus_html::PointerData { + downcast_event(event).raw.clone().into() + } + + fn convert_scroll_data( + &self, + event: &dioxus_html::PlatformEventData, + ) -> dioxus_html::ScrollData { + ScrollData::from(downcast_event(event).raw.clone()) + } + + fn convert_selection_data( + &self, + event: &dioxus_html::PlatformEventData, + ) -> dioxus_html::SelectionData { + downcast_event(event).raw.clone().into() + } + + fn convert_toggle_data( + &self, + event: &dioxus_html::PlatformEventData, + ) -> dioxus_html::ToggleData { + downcast_event(event).raw.clone().into() + } + + fn convert_touch_data(&self, event: &dioxus_html::PlatformEventData) -> dioxus_html::TouchData { + downcast_event(event).raw.clone().into() + } + + fn convert_transition_data( + &self, + event: &dioxus_html::PlatformEventData, + ) -> dioxus_html::TransitionData { + downcast_event(event).raw.clone().into() + } + + fn convert_wheel_data(&self, event: &dioxus_html::PlatformEventData) -> dioxus_html::WheelData { + downcast_event(event).raw.clone().into() + } +} + +struct GenericWebSysEvent { + raw: Event, + element: Element, +} + // todo: some of these events are being casted to the wrong event type. // We need tests that simulate clicks/etc and make sure every event type works. pub fn virtual_event_from_websys_event(event: web_sys::Event, target: Element) -> Rc { - use dioxus_html::events::*; - - match event.type_().as_str() { - "copy" | "cut" | "paste" => Rc::new(ClipboardData::from(event)), - "compositionend" | "compositionstart" | "compositionupdate" => { - Rc::new(CompositionData::from(event)) - } - "keydown" | "keypress" | "keyup" => Rc::new(KeyboardData::from(event)), - "focus" | "blur" | "focusout" | "focusin" => Rc::new(FocusData::from(event)), - - "change" | "input" | "invalid" | "reset" | "submit" => { - Rc::new(WebFormData::new(target, event)) - } - - "click" | "contextmenu" | "dblclick" | "doubleclick" | "mousedown" | "mouseenter" - | "mouseleave" | "mousemove" | "mouseout" | "mouseover" | "mouseup" => { - Rc::new(MouseData::from(event)) - } - "drag" | "dragend" | "dragenter" | "dragexit" | "dragleave" | "dragover" | "dragstart" - | "drop" => Rc::new(DragData::from(event)), - - "pointerdown" | "pointermove" | "pointerup" | "pointercancel" | "gotpointercapture" - | "lostpointercapture" | "pointerenter" | "pointerleave" | "pointerover" | "pointerout" => { - Rc::new(PointerData::from(event)) - } - "select" => Rc::new(SelectionData::from(event)), - "touchcancel" | "touchend" | "touchmove" | "touchstart" => Rc::new(TouchData::from(event)), - - "scroll" => Rc::new(()), - "wheel" => Rc::new(WheelData::from(event)), - "animationstart" | "animationend" | "animationiteration" => { - Rc::new(AnimationData::from(event)) - } - "transitionend" => Rc::new(TransitionData::from(event)), - "abort" | "canplay" | "canplaythrough" | "durationchange" | "emptied" | "encrypted" - | "ended" | "loadeddata" | "loadedmetadata" | "loadstart" | "pause" | "play" - | "playing" | "progress" | "ratechange" | "seeked" | "seeking" | "stalled" | "suspend" - | "timeupdate" | "volumechange" | "waiting" => Rc::new(MediaData::from(event)), - "error" => Rc::new(WebImageEvent::new(event, true)), - "load" => Rc::new(WebImageEvent::new(event, false)), - "toggle" => Rc::new(ToggleData::from(event)), - - _ => Rc::new(()), - } + Rc::new(GenericWebSysEvent { + raw: event, + element: target, + }) } pub(crate) fn load_document() -> Document { @@ -336,7 +415,7 @@ impl HasImageData for WebImageEvent { } fn as_any(&self) -> &dyn Any { - &self.raw + &self.raw as &dyn Any } } @@ -432,7 +511,7 @@ impl HasFormData for WebFormData { } fn as_any(&self) -> &dyn Any { - &self.raw + &self.raw as &dyn Any } }