From f2234068ba7cd915a00a81e41660d7d6ee1177cc Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Thu, 11 Nov 2021 22:07:38 -0500 Subject: [PATCH] feat: events bubble now --- packages/core/src/nodes.rs | 6 +++-- packages/core/src/scope.rs | 24 ----------------- packages/core/src/scopearena.rs | 27 +++++++++++++++++++- packages/core/src/virtual_dom.rs | 8 +++--- packages/html/src/events.rs | 11 ++++---- packages/web/src/dom.rs | 44 +++++++++++++++++--------------- 6 files changed, 63 insertions(+), 57 deletions(-) diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 55464d9dc..865ff19c3 100644 --- a/packages/core/src/nodes.rs +++ b/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) + 'bump>>>, + pub(crate) callback: + RefCell) + '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) + 'a>, + callback: BumpBox<'a, dyn FnMut(Arc) + 'a>, ) -> Listener<'a> { Listener { mounted_node: Cell::new(None), diff --git a/packages/core/src/scope.rs b/packages/core/src/scope.rs index c8668a26e..02989f7ff 100644 --- a/packages/core/src/scope.rs +++ b/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) { diff --git a/packages/core/src/scopearena.rs b/packages/core/src/scopearena.rs index e6f3f8f20..3ae7c091c 100644 --- a/packages/core/src/scopearena.rs +++ b/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(); diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 8782024a9..1232c5fa7 100644 --- a/packages/core/src/virtual_dom.rs +++ b/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, + pub event: Arc, } /// Priority of Event Triggers. diff --git a/packages/html/src/events.rs b/packages/html/src/events.rs index 0ced89cca..5dae615f5 100644 --- a/packages/html/src/events.rs +++ b/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) = bump.alloc(move |evt: Box| { - let event = evt.downcast::<$wrapper>().unwrap(); - callback(*event) + let cb: &mut dyn FnMut(Arc) = bump.alloc(move |evt: Arc| { + let event = evt.downcast_ref::<$wrapper>().unwrap(); + callback(event) }); - let callback: BumpBox) + 'a> = unsafe { BumpBox::from_raw(cb) }; + let callback: BumpBox) + 'a> = unsafe { BumpBox::from_raw(cb) }; // ie oncopy let event_name = stringify!($name); diff --git a/packages/web/src/dom.rs b/packages/web/src/dom.rs index 2d109f02f..c4a09b2bc 100644 --- a/packages/web/src/dom.rs +++ b/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 = 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); + }); + + 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 { +fn virtual_event_from_websys_event(event: web_sys::Event) -> Arc { 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 } "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 }) .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 "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 // 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 }) } - "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 "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 "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 | "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(()), } }