Explorar el Código

feat: events bubble now

Jonathan Kelley hace 3 años
padre
commit
f2234068ba

+ 4 - 2
packages/core/src/nodes.rs

@@ -12,6 +12,7 @@ use std::{
     any::Any,
     cell::{Cell, RefCell},
     fmt::{Arguments, Debug, Formatter},
+    sync::Arc,
 };
 
 /// A composable "VirtualNode" to declare a User Interface in the Dioxus VirtualDOM.
@@ -366,7 +367,8 @@ pub struct Listener<'bump> {
     pub event: &'static str,
 
     /// The actual callback that the user specified
-    pub(crate) callback: RefCell<Option<BumpBox<'bump, dyn FnMut(Box<dyn Any + Send>) + 'bump>>>,
+    pub(crate) callback:
+        RefCell<Option<BumpBox<'bump, dyn FnMut(std::sync::Arc<dyn Any + Send + Sync>) + 'bump>>>,
 }
 
 /// Virtual Components for custom user-defined components
@@ -643,7 +645,7 @@ impl<'a> NodeFactory<'a> {
     pub fn listener(
         self,
         event: &'static str,
-        callback: BumpBox<'a, dyn FnMut(Box<dyn Any + Send>) + 'a>,
+        callback: BumpBox<'a, dyn FnMut(Arc<dyn Any + Send + Sync>) + 'a>,
     ) -> Listener<'a> {
         Listener {
             mounted_node: Cell::new(None),

+ 0 - 24
packages/core/src/scope.rs

@@ -449,30 +449,6 @@ impl Scope {
         self.generation.set(self.generation.get() + 1);
     }
 
-    /// A safe wrapper around calling listeners
-    pub(crate) fn call_listener(&self, event: UserEvent, element: ElementId) {
-        let listners = &mut self.items.borrow_mut().listeners;
-
-        let listener = listners.iter().find(|lis| {
-            let search = lis;
-            if search.event == event.name {
-                let search_id = search.mounted_node.get();
-                search_id.map(|f| f == element).unwrap_or(false)
-            } else {
-                false
-            }
-        });
-
-        if let Some(listener) = listener {
-            let mut cb = listener.callback.borrow_mut();
-            if let Some(cb) = cb.as_mut() {
-                (cb)(event.event);
-            }
-        } else {
-            log::warn!("An event was triggered but there was no listener to handle it");
-        }
-    }
-
     // General strategy here is to load up the appropriate suspended task and then run it.
     // Suspended nodes cannot be called repeatedly.
     pub(crate) fn call_suspended_node<'a>(&'a mut self, task_id: u64) {

+ 26 - 1
packages/core/src/scopearena.rs

@@ -2,7 +2,10 @@ use bumpalo::Bump;
 use futures_channel::mpsc::UnboundedSender;
 use fxhash::FxHashMap;
 use slab::Slab;
-use std::cell::{Cell, RefCell};
+use std::{
+    borrow::Borrow,
+    cell::{Cell, RefCell},
+};
 
 use crate::innerlude::*;
 
@@ -361,6 +364,28 @@ impl ScopeArena {
         }
     }
 
+    pub fn call_listener_with_bubbling(&self, event: UserEvent, element: ElementId) {
+        let nodes = self.nodes.borrow();
+        let mut cur_el = Some(element);
+
+        while let Some(id) = cur_el.take() {
+            if let Some(el) = nodes.get(id.0) {
+                let real_el = unsafe { &**el };
+                if let VNode::Element(real_el) = real_el {
+                    for listener in real_el.listeners.borrow().iter() {
+                        if listener.event == event.name {
+                            let mut cb = listener.callback.borrow_mut();
+                            if let Some(cb) = cb.as_mut() {
+                                (cb)(event.event.clone());
+                            }
+                        }
+                    }
+                    cur_el = real_el.parent_id.get();
+                }
+            }
+        }
+    }
+
     // The head of the bumpframe is the first linked NodeLink
     pub fn wip_head(&self, id: &ScopeId) -> &VNode {
         let scope = self.get_scope(id).unwrap();

+ 4 - 4
packages/core/src/virtual_dom.rs

@@ -8,6 +8,8 @@ use futures_util::{Future, StreamExt};
 use fxhash::FxHashSet;
 use indexmap::IndexSet;
 use std::pin::Pin;
+use std::rc::Rc;
+use std::sync::Arc;
 use std::task::Poll;
 use std::{any::Any, collections::VecDeque};
 
@@ -402,9 +404,7 @@ impl VirtualDom {
                             log::info!("Calling listener {:?}, {:?}", event.scope_id, element);
 
                             if let Some(scope) = self.scopes.get_scope(&event.scope_id) {
-                                // TODO: bubble properly here
-                                scope.call_listener(event, element);
-
+                                self.scopes.call_listener_with_bubbling(event, element);
                                 while let Ok(Some(dirty_scope)) = self.receiver.try_next() {
                                     self.pending_messages.push_front(dirty_scope);
                                 }
@@ -644,7 +644,7 @@ pub struct UserEvent {
     pub name: &'static str,
 
     /// Event Data
-    pub event: Box<dyn Any + Send>,
+    pub event: Arc<dyn Any + Send + Sync>,
 }
 
 /// Priority of Event Triggers.

+ 6 - 5
packages/html/src/events.rs

@@ -5,6 +5,7 @@ use std::any::Any;
 
 pub mod on {
     use super::*;
+    use std::sync::Arc;
     macro_rules! event_directory {
         ( $(
             $( #[$attr:meta] )*
@@ -23,19 +24,19 @@ pub mod on {
                         c: NodeFactory<'a>,
                         mut callback: F,
                     ) -> Listener<'a>
-                        where F: FnMut($wrapper) + 'a
+                        where F: FnMut(&$wrapper) + 'a
                     {
                         let bump = &c.bump();
 
                         // we can't allocate unsized in bumpalo's box, so we need to craft the box manually
                         // safety: this is essentially the same as calling Box::new() but manually
                         // The box is attached to the lifetime of the bumpalo allocator
-                        let cb: &mut dyn FnMut(Box<dyn Any + Send>) = bump.alloc(move |evt: Box<dyn Any + Send>| {
-                            let event = evt.downcast::<$wrapper>().unwrap();
-                            callback(*event)
+                        let cb: &mut dyn FnMut(Arc<dyn Any + Send + Sync>) = bump.alloc(move |evt: Arc<dyn Any + Send + Sync>| {
+                            let event = evt.downcast_ref::<$wrapper>().unwrap();
+                            callback(event)
                         });
 
-                        let callback: BumpBox<dyn FnMut(Box<dyn Any + Send>) + 'a> = unsafe { BumpBox::from_raw(cb) };
+                        let callback: BumpBox<dyn FnMut(Arc<dyn Any + Send + Sync>) + 'a> = unsafe { BumpBox::from_raw(cb) };
 
                         // ie oncopy
                         let event_name = stringify!($name);

+ 23 - 21
packages/web/src/dom.rs

@@ -9,7 +9,7 @@
 
 use dioxus_core::{DomEdit, ElementId, SchedulerMsg, ScopeId, UserEvent};
 use fxhash::FxHashMap;
-use std::{any::Any, fmt::Debug, rc::Rc};
+use std::{any::Any, fmt::Debug, rc::Rc, sync::Arc};
 use wasm_bindgen::{closure::Closure, JsCast};
 use web_sys::{
     CssStyleDeclaration, Document, Element, Event, HtmlElement, HtmlInputElement,
@@ -92,7 +92,7 @@ impl WebsysDom {
     //                 .as_ref()
     //                 .unwrap()
     //                 .clone();
-    //             bla.set(Box::new(node)).unwrap();
+    //             bla.set(Arc::new(node)).unwrap();
     //         }
     //     }
     // }
@@ -286,14 +286,16 @@ impl WebsysDom {
         } else {
             let trigger = self.sender_callback.clone();
 
-            let handler = Closure::wrap(Box::new(move |event: &web_sys::Event| {
+            let c: Box<dyn FnMut(&Event)> = Box::new(move |event: &web_sys::Event| {
                 // "Result" cannot be received from JS
                 // Instead, we just build and immediately execute a closure that returns result
                 match decode_trigger(event) {
                     Ok(synthetic_event) => trigger.as_ref()(SchedulerMsg::UiEvent(synthetic_event)),
                     Err(e) => log::error!("Error decoding Dioxus event attribute. {:#?}", e),
                 };
-            }) as Box<dyn FnMut(&Event)>);
+            });
+
+            let handler = Closure::wrap(c);
 
             self.root
                 .add_event_listener_with_callback(event, (&handler).as_ref().unchecked_ref())
@@ -483,21 +485,21 @@ unsafe impl Sync for DioxusWebsysEvent {}
 
 // 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.
-fn virtual_event_from_websys_event(event: web_sys::Event) -> Box<dyn Any + Send> {
+fn virtual_event_from_websys_event(event: web_sys::Event) -> Arc<dyn Any + Send + Sync> {
     use dioxus_html::on::*;
     use dioxus_html::KeyCode;
     // use dioxus_core::events::on::*;
     match event.type_().as_str() {
-        "copy" | "cut" | "paste" => Box::new(ClipboardEvent {}),
+        "copy" | "cut" | "paste" => Arc::new(ClipboardEvent {}),
         "compositionend" | "compositionstart" | "compositionupdate" => {
             let evt: &web_sys::CompositionEvent = event.dyn_ref().unwrap();
-            Box::new(CompositionEvent {
+            Arc::new(CompositionEvent {
                 data: evt.data().unwrap_or_default(),
             })
         }
         "keydown" | "keypress" | "keyup" => {
             let evt: &web_sys::KeyboardEvent = event.dyn_ref().unwrap();
-            Box::new(KeyboardEvent {
+            Arc::new(KeyboardEvent {
                 alt_key: evt.alt_key(),
                 char_code: evt.char_code(),
                 key: evt.key(),
@@ -513,7 +515,7 @@ fn virtual_event_from_websys_event(event: web_sys::Event) -> Box<dyn Any + Send>
         }
         "focus" | "blur" => {
             //
-            Box::new(FocusEvent {})
+            Arc::new(FocusEvent {})
         }
         // "change" => SyntheticEvent::GenericEvent(DioxusEvent::new((), DioxusWebsysEvent(event))),
 
@@ -558,13 +560,13 @@ fn virtual_event_from_websys_event(event: web_sys::Event) -> Box<dyn Any + Send>
                 })
                 .expect("only an InputElement or TextAreaElement or an element with contenteditable=true can have an oninput event listener");
 
-            Box::new(FormEvent { value })
+            Arc::new(FormEvent { value })
         }
         "click" | "contextmenu" | "doubleclick" | "drag" | "dragend" | "dragenter" | "dragexit"
         | "dragleave" | "dragover" | "dragstart" | "drop" | "mousedown" | "mouseenter"
         | "mouseleave" | "mousemove" | "mouseout" | "mouseover" | "mouseup" => {
             let evt: &web_sys::MouseEvent = event.dyn_ref().unwrap();
-            Box::new(MouseEvent {
+            Arc::new(MouseEvent {
                 alt_key: evt.alt_key(),
                 button: evt.button(),
                 buttons: evt.buttons(),
@@ -582,7 +584,7 @@ fn virtual_event_from_websys_event(event: web_sys::Event) -> Box<dyn Any + Send>
         "pointerdown" | "pointermove" | "pointerup" | "pointercancel" | "gotpointercapture"
         | "lostpointercapture" | "pointerenter" | "pointerleave" | "pointerover" | "pointerout" => {
             let evt: &web_sys::PointerEvent = event.dyn_ref().unwrap();
-            Box::new(PointerEvent {
+            Arc::new(PointerEvent {
                 alt_key: evt.alt_key(),
                 button: evt.button(),
                 buttons: evt.buttons(),
@@ -608,11 +610,11 @@ fn virtual_event_from_websys_event(event: web_sys::Event) -> Box<dyn Any + Send>
                 // get_modifier_state: evt.get_modifier_state(),
             })
         }
-        "select" => Box::new(SelectionEvent {}),
+        "select" => Arc::new(SelectionEvent {}),
 
         "touchcancel" | "touchend" | "touchmove" | "touchstart" => {
             let evt: &web_sys::TouchEvent = event.dyn_ref().unwrap();
-            Box::new(TouchEvent {
+            Arc::new(TouchEvent {
                 alt_key: evt.alt_key(),
                 ctrl_key: evt.ctrl_key(),
                 meta_key: evt.meta_key(),
@@ -620,11 +622,11 @@ fn virtual_event_from_websys_event(event: web_sys::Event) -> Box<dyn Any + Send>
             })
         }
 
-        "scroll" => Box::new(()),
+        "scroll" => Arc::new(()),
 
         "wheel" => {
             let evt: &web_sys::WheelEvent = event.dyn_ref().unwrap();
-            Box::new(WheelEvent {
+            Arc::new(WheelEvent {
                 delta_x: evt.delta_x(),
                 delta_y: evt.delta_y(),
                 delta_z: evt.delta_z(),
@@ -634,7 +636,7 @@ fn virtual_event_from_websys_event(event: web_sys::Event) -> Box<dyn Any + Send>
 
         "animationstart" | "animationend" | "animationiteration" => {
             let evt: &web_sys::AnimationEvent = event.dyn_ref().unwrap();
-            Box::new(AnimationEvent {
+            Arc::new(AnimationEvent {
                 elapsed_time: evt.elapsed_time(),
                 animation_name: evt.animation_name(),
                 pseudo_element: evt.pseudo_element(),
@@ -643,7 +645,7 @@ fn virtual_event_from_websys_event(event: web_sys::Event) -> Box<dyn Any + Send>
 
         "transitionend" => {
             let evt: &web_sys::TransitionEvent = event.dyn_ref().unwrap();
-            Box::new(TransitionEvent {
+            Arc::new(TransitionEvent {
                 elapsed_time: evt.elapsed_time(),
                 property_name: evt.property_name(),
                 pseudo_element: evt.pseudo_element(),
@@ -655,15 +657,15 @@ fn virtual_event_from_websys_event(event: web_sys::Event) -> Box<dyn Any + Send>
         | "playing" | "progress" | "ratechange" | "seeked" | "seeking" | "stalled" | "suspend"
         | "timeupdate" | "volumechange" | "waiting" => {
             //
-            Box::new(MediaEvent {})
+            Arc::new(MediaEvent {})
         }
 
         "toggle" => {
             //
-            Box::new(ToggleEvent {})
+            Arc::new(ToggleEvent {})
         }
 
-        _ => Box::new(()),
+        _ => Arc::new(()),
     }
 }