瀏覽代碼

feat: event handling on desktop

Jonathan Kelley 2 年之前
父節點
當前提交
30ef225812

+ 9 - 17
examples/dog_app.rs

@@ -1,7 +1,3 @@
-#![allow(non_snake_case)]
-
-//! Render a bunch of doggos!
-
 use dioxus::prelude::*;
 use std::collections::HashMap;
 
@@ -19,8 +15,8 @@ struct ListBreeds {
 }
 
 async fn app_root(cx: Scope<'_>) -> Element {
-    let breed = use_state(&cx, || "deerhound".to_string());
-    let breeds = use_future(&cx, (), |_| async move {
+    let breed = use_state(cx, || "deerhound".to_string());
+    let breeds = use_future!(cx, || async move {
         reqwest::get("https://dog.ceo/api/breeds/list/all")
             .await
             .unwrap()
@@ -45,7 +41,9 @@ async fn app_root(cx: Scope<'_>) -> Element {
                             }
                         }
                     }
-                    div { flex: "50%", Breed { breed: breed.to_string() } }
+                    div { flex: "50%",
+                        breed_pic { breed: breed.to_string() }
+                    }
                 }
             }
         }),
@@ -59,10 +57,8 @@ struct DogApi {
 }
 
 #[inline_props]
-async fn Breed(cx: Scope, breed: String) -> Element {
-    println!("Rendering Breed: {}", breed);
-
-    let fut = use_future(&cx, (breed,), |(breed,)| async move {
+async fn breed_pic(cx: Scope, breed: String) -> Element {
+    let fut = use_future!(cx, |breed| async move {
         reqwest::get(format!("https://dog.ceo/api/breed/{}/images/random", breed))
             .await
             .unwrap()
@@ -70,11 +66,7 @@ async fn Breed(cx: Scope, breed: String) -> Element {
             .await
     });
 
-    let resp = fut.await;
-
-    println!("achieved results!");
-
-    match resp {
+    match fut.await {
         Ok(resp) => cx.render(rsx! {
             div {
                 button {
@@ -88,6 +80,6 @@ async fn Breed(cx: Scope, breed: String) -> Element {
                 }
             }
         }),
-        Err(e) => cx.render(rsx! { div { "loading dogs failed" } }),
+        Err(_) => cx.render(rsx! { div { "loading dogs failed" } }),
     }
 }

+ 2 - 8
examples/generic_component.rs

@@ -1,5 +1,3 @@
-use std::fmt::Display;
-
 use dioxus::prelude::*;
 
 fn main() {
@@ -7,13 +5,9 @@ fn main() {
 }
 
 fn app(cx: Scope) -> Element {
-    cx.render(rsx! {
-        generic_child::<i32>{}
-    })
+    cx.render(rsx! { generic_child::<i32>{} })
 }
 
 fn generic_child<T>(cx: Scope) -> Element {
-    cx.render(rsx! {
-        div {}
-    })
+    cx.render(rsx! { div {} })
 }

+ 2 - 5
examples/inputs.rs

@@ -2,7 +2,6 @@
 //!
 //! There is some conversion happening when input types are checkbox/radio/select/textarea etc.
 
-use dioxus::events::FormData;
 use dioxus::prelude::*;
 
 fn main() {
@@ -29,7 +28,6 @@ const FIELDS: &[(&str, &str)] = &[
     ("text", ""),
     ("time", ""),
     ("url", ""),
-    //
     // less supported things
     ("hidden", ""),
     ("month", ""), // degrades to text most of the time, but works properly as "value'"
@@ -115,7 +113,7 @@ fn app(cx: Scope) -> Element {
                 }
             }
 
-            FIELDS.iter().map(|(field, value)| rsx!(
+            FIELDS.iter().map(|(field, value)| rsx! {
                 div {
                     input {
                         id: "{field}",
@@ -132,8 +130,7 @@ fn app(cx: Scope) -> Element {
                     }
                     br {}
                 }
-            ))
-
+            })
         }
     })
 }

+ 17 - 7
packages/core/src/arena.rs

@@ -4,28 +4,31 @@ use crate::{nodes::VNode, virtual_dom::VirtualDom};
 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
 pub struct ElementId(pub usize);
 
-pub struct ElementPath {
+pub struct ElementRef {
+    // the pathway of the real element inside the template
+    pub path: &'static [u8],
+
+    // The actual template
     pub template: *mut VNode<'static>,
-    pub element: usize,
 }
 
-impl ElementPath {
+impl ElementRef {
     pub fn null() -> Self {
         Self {
             template: std::ptr::null_mut(),
-            element: 0,
+            path: &[],
         }
     }
 }
 
 impl VirtualDom {
-    pub fn next_element(&mut self, template: &VNode) -> ElementId {
+    pub fn next_element(&mut self, template: &VNode, path: &'static [u8]) -> ElementId {
         let entry = self.elements.vacant_entry();
         let id = entry.key();
 
-        entry.insert(ElementPath {
+        entry.insert(ElementRef {
             template: template as *const _ as *mut _,
-            element: id,
+            path,
         });
 
         ElementId(id)
@@ -35,3 +38,10 @@ impl VirtualDom {
         self.elements.remove(id.0);
     }
 }
+
+/*
+now......
+
+an ID is mostly a pointer to a node in the real dom.
+We need to
+*/

+ 14 - 14
packages/core/src/create.rs

@@ -1,7 +1,5 @@
-use std::ops::Bound;
-
 use crate::factory::RenderReturn;
-use crate::innerlude::{Mutations, SuspenseContext, SuspenseId};
+use crate::innerlude::{Mutations, SuspenseContext};
 use crate::mutations::Mutation;
 use crate::mutations::Mutation::*;
 use crate::nodes::VNode;
@@ -75,13 +73,13 @@ impl VirtualDom {
                         DynamicNode::Text {
                             id: slot, value, ..
                         } => {
-                            let id = self.next_element(template);
+                            let id = self.next_element(template, template.template.node_paths[*id]);
                             slot.set(id);
                             mutations.push(CreateTextNode { value, id });
                             1
                         }
                         DynamicNode::Placeholder(slot) => {
-                            let id = self.next_element(template);
+                            let id = self.next_element(template, template.template.node_paths[*id]);
                             slot.set(id);
                             mutations.push(CreatePlaceholder { id });
                             1
@@ -95,7 +93,7 @@ impl VirtualDom {
             while let Some((mut attr_id, path)) =
                 dynamic_attrs.next_if(|(_, p)| p[0] == root_idx as u8)
             {
-                let id = self.next_element(template);
+                let id = self.next_element(template, template.template.attr_paths[attr_id]);
                 mutations.push(AssignId {
                     path: &path[1..],
                     id,
@@ -118,7 +116,8 @@ impl VirtualDom {
                             id,
                         }),
                         AttributeValue::Listener(_) => mutations.push(NewEventListener {
-                            event_name: attribute.name,
+                            // all listeners start with "on"
+                            event_name: &attribute.name[2..],
                             scope: cur_scope,
                             id,
                         }),
@@ -178,8 +177,8 @@ impl VirtualDom {
     ) {
         match *node {
             // Todo: create the children's template
-            TemplateNode::Dynamic(_) => {
-                let id = self.next_element(template);
+            TemplateNode::Dynamic(idx) => {
+                let id = self.next_element(template, template.template.node_paths[idx]);
                 mutations.push(CreatePlaceholder { id })
             }
             TemplateNode::Text(value) => mutations.push(CreateStaticText { value }),
@@ -194,7 +193,7 @@ impl VirtualDom {
                 tag,
                 inner_opt,
             } => {
-                let id = self.next_element(template);
+                let id = self.next_element(template, &[]); // never gets referenced, empty path is fine, I think?
 
                 mutations.push(CreateElement {
                     name: tag,
@@ -239,7 +238,7 @@ impl VirtualDom {
     ) -> usize {
         match &node {
             DynamicNode::Text { id, value, .. } => {
-                let new_id = self.next_element(template);
+                let new_id = self.next_element(template, template.template.node_paths[idx]);
                 id.set(new_id);
                 mutations.push(HydrateText {
                     id: new_id,
@@ -269,7 +268,7 @@ impl VirtualDom {
                     }
 
                     RenderReturn::Async(_) => {
-                        let new_id = self.next_element(template);
+                        let new_id = self.next_element(template, template.template.node_paths[idx]);
                         placeholder.set(Some(new_id));
                         self.scopes[scope.0].placeholder.set(Some(new_id));
 
@@ -305,7 +304,8 @@ impl VirtualDom {
                                 self.scopes[scope.0].has_context::<SuspenseContext>()
                             {
                                 // Since this is a boundary, use it as a placeholder
-                                let new_id = self.next_element(template);
+                                let new_id =
+                                    self.next_element(template, template.template.node_paths[idx]);
                                 placeholder.set(Some(new_id));
                                 self.scopes[scope.0].placeholder.set(Some(new_id));
                                 mutations.push(AssignId {
@@ -350,7 +350,7 @@ impl VirtualDom {
             }
 
             DynamicNode::Placeholder(slot) => {
-                let id = self.next_element(template);
+                let id = self.next_element(template, template.template.node_paths[idx]);
                 slot.set(id);
                 mutations.push(AssignId {
                     path: &template.template.node_paths[idx][1..],

+ 1 - 1
packages/core/src/diff.rs

@@ -82,7 +82,7 @@ impl<'b> VirtualDom {
             // remove old with placeholder
             (Sync(Some(l)), Sync(None)) | (Sync(Some(l)), Async(_)) => {
                 //
-                let id = self.next_element(l); // todo!
+                let id = self.next_element(l, &[]); // todo!
                 m.push(Mutation::CreatePlaceholder { id });
                 self.drop_template(m, l, true);
             }

+ 15 - 18
packages/core/src/events.rs

@@ -6,21 +6,30 @@ use std::{
     rc::Rc,
 };
 
-pub struct UiEvent<T: 'static> {
-    pub(crate) bubble_state: Cell<bool>,
+pub struct UiEvent<T: 'static + ?Sized> {
+    pub(crate) bubbles: Rc<Cell<bool>>,
     pub(crate) data: Rc<T>,
 }
-impl<T> Clone for UiEvent<T> {
+
+impl UiEvent<dyn Any> {
+    pub fn downcast<T: 'static + Sized>(self) -> Option<UiEvent<T>> {
+        Some(UiEvent {
+            bubbles: self.bubbles,
+            data: self.data.downcast().ok()?,
+        })
+    }
+}
+impl<T: ?Sized> Clone for UiEvent<T> {
     fn clone(&self) -> Self {
         Self {
-            bubble_state: self.bubble_state.clone(),
+            bubbles: self.bubbles.clone(),
             data: self.data.clone(),
         }
     }
 }
 impl<T> UiEvent<T> {
     pub fn cancel_bubble(&self) {
-        self.bubble_state.set(false);
+        self.bubbles.set(false);
     }
 }
 
@@ -35,7 +44,7 @@ impl<T> std::ops::Deref for UiEvent<T> {
 impl<T: Debug> std::fmt::Debug for UiEvent<T> {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         f.debug_struct("UiEvent")
-            .field("bubble_state", &self.bubble_state)
+            .field("bubble_state", &self.bubbles)
             .field("data", &self.data)
             .finish()
     }
@@ -93,18 +102,6 @@ pub enum EventPriority {
     Low = 0,
 }
 
-// ensures a strict descendant relationship
-// returns false if the paths are equal
-pub fn is_path_ascendant(small: &[u8], big: &[u8]) -> bool {
-    if small.len() >= big.len() {
-        return false;
-    }
-
-    small == &big[..small.len()]
-}
-
-pub type InternalHandler<'bump> = &'bump RefCell<Option<InternalListenerCallback<'bump>>>;
-type InternalListenerCallback<'bump> = BumpBox<'bump, dyn FnMut(&'bump dyn Any) + 'bump>;
 type ExternalListenerCallback<'bump, T> = BumpBox<'bump, dyn FnMut(T) + 'bump>;
 
 /// The callback type generated by the `rsx!` macro when an `on` field is specified for components.

+ 1 - 1
packages/core/src/lib.rs

@@ -74,7 +74,7 @@ pub use crate::innerlude::{
     DynamicNode,
     Element,
     ElementId,
-    ElementPath,
+    ElementRef,
     EventPriority,
     Fragment,
     LazyNodes,

+ 26 - 13
packages/core/src/nodes.rs

@@ -17,7 +17,7 @@ pub struct VNode<'a> {
     pub key: Option<&'a str>,
 
     // When rendered, this template will be linked to its parent manually
-    pub parent: Option<*const DynamicNode<'a>>,
+    pub parent: Option<ElementId>,
 
     pub template: Template<'static>,
 
@@ -153,7 +153,7 @@ pub enum AttributeValue<'a> {
     Float(f32),
     Int(i32),
     Bool(bool),
-    Listener(RefCell<&'a mut dyn FnMut(&dyn Any)>),
+    Listener(RefCell<&'a mut dyn FnMut(UiEvent<dyn Any>)>),
     Any(&'a dyn AnyValue),
     None,
 }
@@ -163,17 +163,30 @@ impl<'a> AttributeValue<'a> {
         cx: &'a ScopeState,
         mut f: impl FnMut(UiEvent<T>) + 'a,
     ) -> AttributeValue<'a> {
-        let f = cx.bump().alloc(move |a: &dyn Any| {
-            a.downcast_ref::<UiEvent<T>>()
-                .map(|a| f(a.clone()))
-                .unwrap_or_else(|| {
-                    panic!(
-                        "Expected UiEvent<{}>, got {:?}",
-                        std::any::type_name::<T>(),
-                        a
-                    )
-                })
-        }) as &mut dyn FnMut(&dyn Any);
+        println!("creating listener with type od id {:?}", TypeId::of::<T>());
+
+        let f = cx.bump().alloc(move |a: UiEvent<dyn Any>| {
+            println!(
+                "incoming: {:?}, desired {:?}",
+                a.data.type_id(),
+                std::any::TypeId::of::<T>()
+            );
+
+            let data = a.data.downcast::<T>().unwrap();
+
+            // println!("casting to type {:?}", TypeId::of::<T>());
+
+            f(UiEvent {
+                bubbles: a.bubbles,
+                data,
+            })
+
+            // println!("listener called");
+            // if let Some(event) = a.downcast::<T>() {
+            //     println!("downcast success");
+            //     f(event)
+            // }
+        }) as &mut dyn FnMut(UiEvent<dyn Any>);
 
         AttributeValue::Listener(RefCell::new(f))
     }

+ 102 - 71
packages/core/src/virtual_dom.rs

@@ -1,7 +1,7 @@
 use crate::{
     any_props::VComponentProps,
     arena::ElementId,
-    arena::ElementPath,
+    arena::ElementRef,
     diff::DirtyScope,
     factory::RenderReturn,
     innerlude::{Mutations, Scheduler, SchedulerMsg},
@@ -13,12 +13,12 @@ use crate::{
 };
 use futures_util::{pin_mut, FutureExt, StreamExt};
 use slab::Slab;
-use std::future::Future;
 use std::rc::Rc;
 use std::{
     any::Any,
     collections::{BTreeSet, HashMap},
 };
+use std::{cell::Cell, future::Future};
 
 /// A virtual node system that progresses user events and diffs UI trees.
 ///
@@ -145,12 +145,14 @@ use std::{
 /// ```
 pub struct VirtualDom {
     pub(crate) templates: HashMap<TemplateId, Template<'static>>,
-    pub(crate) elements: Slab<ElementPath>,
     pub(crate) scopes: Slab<ScopeState>,
     pub(crate) element_stack: Vec<ElementId>,
     pub(crate) dirty_scopes: BTreeSet<DirtyScope>,
     pub(crate) scheduler: Rc<Scheduler>,
 
+    // Every element is actually a dual reference - one to the template and the other to the dynamic node in that template
+    pub(crate) elements: Slab<ElementRef>,
+
     // While diffing we need some sort of way of breaking off a stream of suspended mutations.
     pub(crate) scope_stack: Vec<ScopeId>,
     pub(crate) collected_leaves: Vec<SuspenseId>,
@@ -246,7 +248,7 @@ impl VirtualDom {
         root.provide_context(SuspenseBoundary::new(ScopeId(0)));
 
         // the root element is always given element 0
-        dom.elements.insert(ElementPath::null());
+        dom.elements.insert(ElementRef::null());
 
         dom
     }
@@ -259,6 +261,12 @@ impl VirtualDom {
         self.scopes.get(0).unwrap()
     }
 
+    /// Build the virtualdom with a global context inserted into the base scope
+    pub fn with_root_context<T: Clone + 'static>(self, context: T) -> Self {
+        self.base_scope().provide_context(context);
+        self
+    }
+
     fn mark_dirty_scope(&mut self, id: ScopeId) {
         let height = self.scopes[id.0].height;
         self.dirty_scopes.insert(DirtyScope { height, id });
@@ -288,67 +296,90 @@ impl VirtualDom {
     ///
     ///
     ///
-    pub fn handle_event<T: 'static>(
+    pub fn handle_event(
         &mut self,
         name: &str,
-        data: Rc<T>,
+        data: Rc<dyn Any>,
         element: ElementId,
         bubbles: bool,
-        priority: EventPriority,
-    ) -> Option<()> {
+        // todo: priority is helpful when scheduling work around suspense, but we don't currently use it
+        _priority: EventPriority,
+    ) {
+        let uievent = UiEvent {
+            bubbles: Rc::new(Cell::new(bubbles)),
+            data,
+        };
+
         /*
-        - click registers
-        - walk upwards until first element with onclick listener
-        - get template from ElementID
-        - break out of wait loop
-        - send event to virtualdom
+        ------------------------
+        The algorithm works by walking through the list of dynamic attributes, checking their paths, and breaking when
+        we find the target path.
+
+        With the target path, we try and move up to the parent until there is no parent.
+        Due to how bubbling works, we call the listeners before walking to the parent.
+
+        If we wanted to do capturing, then we would accumulate all the listeners and call them in reverse order.
+        ----------------------
+        |           <-- yes (is ascendant)
+        | | |       <-- no  (is not ascendant)
+        | |         <-- yes (is ascendant)
+        | | | | |   <--- target element, break early
+        | | |       <-- no, broke early
+        |           <-- no, broke early
         */
+        let mut parent_path = self.elements.get(element.0);
+        let mut listeners = vec![];
 
-        let event = UiEvent {
-            bubble_state: std::cell::Cell::new(true),
-            data,
-        };
+        while let Some(el_ref) = parent_path {
+            let template = unsafe { &*el_ref.template };
+            let target_path = el_ref.path;
+
+            let mut attrs = template.dynamic_attrs.iter().enumerate();
+
+            while let Some((idx, attr)) = attrs.next() {
+                pub fn is_path_ascendant(small: &[u8], big: &[u8]) -> bool {
+                    small.len() >= big.len() && small == &big[..small.len()]
+                }
+
+                let this_path = template.template.attr_paths[idx];
+
+                println!(
+                    "is {:?} ascendant of {:?} ? {}",
+                    this_path,
+                    target_path,
+                    is_path_ascendant(this_path, target_path)
+                );
+
+                println!("{ } - {name}, - {}", attr.name, &attr.name[2..]);
+
+                if &attr.name[2..] == name && is_path_ascendant(&target_path, &this_path) {
+                    listeners.push(&attr.value);
 
-        let path = &self.elements[element.0];
-        let template = unsafe { &*path.template };
-        let dynamic = &template.dynamic_nodes[path.element];
-
-        let location = template
-            .dynamic_attrs
-            .iter()
-            .position(|attr| attr.mounted_element.get() == element)?;
-
-        // let mut index = Some((path.template, location));
-
-        let mut listeners = Vec::<&Attribute>::new();
-
-        // while let Some((raw_parent, dyn_index)) = index {
-        //     let parent = unsafe { &mut *raw_parent };
-        //     let path = parent.template.node_paths[dyn_index];
-
-        //     listeners.extend(
-        //         parent
-        //             .dynamic_attrs
-        //             .iter()
-        //             .enumerate()
-        //             .filter_map(|(idx, attr)| {
-        //                 match is_path_ascendant(parent.template.node_paths[idx], path) {
-        //                     true if attr.name == event.name => Some(attr),
-        //                     _ => None,
-        //                 }
-        //             }),
-        //     );
-
-        //     index = parent.parent;
-        // }
-
-        for listener in listeners {
-            if let AttributeValue::Listener(listener) = &listener.value {
-                (listener.borrow_mut())(&event.clone())
+                    // Break if the event doesn't bubble anyways
+                    if !bubbles {
+                        break;
+                    }
+
+                    // Break if this is the exact target element
+                    if this_path == target_path {
+                        break;
+                    }
+                }
+            }
+
+            for listener in listeners.drain(..).rev() {
+                if let AttributeValue::Listener(listener) = listener {
+                    listener.borrow_mut()(uievent.clone());
+
+                    // Break if the event doesn't bubble
+                    if !uievent.bubbles.get() {
+                        return;
+                    }
+                }
             }
-        }
 
-        Some(())
+            parent_path = template.parent.and_then(|id| self.elements.get(id.0));
+        }
     }
 
     /// Wait for the scheduler to have any work.
@@ -508,22 +539,22 @@ impl VirtualDom {
         }
     }
 
-    fn mark_dirty_scope(&mut self, scope_id: ScopeId) {
-        let scopes = &self.scopes;
-        if let Some(scope) = scopes.get_scope(scope_id) {
-            let height = scope.height;
-            let id = scope_id.0;
-            if let Err(index) = self.dirty_scopes.binary_search_by(|new| {
-                let scope = scopes.get_scope(*new).unwrap();
-                let new_height = scope.height;
-                let new_id = &scope.scope_id();
-                height.cmp(&new_height).then(new_id.0.cmp(&id))
-            }) {
-                self.dirty_scopes.insert(index, scope_id);
-                log::info!("mark_dirty_scope: {:?}", self.dirty_scopes);
-            }
-        }
-    }
+    // fn mark_dirty_scope(&mut self, scope_id: ScopeId) {
+    //     let scopes = &self.scopes;
+    //     if let Some(scope) = scopes.get_scope(scope_id) {
+    //         let height = scope.height;
+    //         let id = scope_id.0;
+    //         if let Err(index) = self.dirty_scopes.binary_search_by(|new| {
+    //             let scope = scopes.get_scope(*new).unwrap();
+    //             let new_height = scope.height;
+    //             let new_id = &scope.scope_id();
+    //             height.cmp(&new_height).then(new_id.0.cmp(&id))
+    //         }) {
+    //             self.dirty_scopes.insert(index, scope_id);
+    //             log::info!("mark_dirty_scope: {:?}", self.dirty_scopes);
+    //         }
+    //     }
+    // }
 }
 
 impl Drop for VirtualDom {

+ 3 - 1
packages/desktop/Cargo.toml

@@ -27,12 +27,14 @@ tokio = { version = "1.16.1", features = [
     "rt-multi-thread",
     "rt",
     "time",
+    "macros",
 ], optional = true, default-features = false }
 webbrowser = "0.8.0"
-infer = "0.9.0"
+infer = "0.11.0"
 dunce = "1.0.2"
 
 interprocess = { version = "1.1.1" }
+futures-util = "0.3.25"
 
 [target.'cfg(target_os = "macos")'.dependencies]
 core-foundation = "0.9.3"

+ 93 - 31
packages/desktop/src/controller.rs

@@ -1,10 +1,17 @@
 use crate::desktop_context::{DesktopContext, UserWindowEvent};
-
 use dioxus_core::*;
+use dioxus_html::events::*;
+use futures_channel::mpsc::UnboundedReceiver;
+use futures_util::StreamExt;
+use serde::Deserialize;
+use serde_json::from_value;
+use std::any::Any;
+use std::rc::Rc;
 use std::{
     collections::HashMap,
     sync::Arc,
     sync::{atomic::AtomicBool, Mutex},
+    time::Duration,
 };
 use wry::{
     self,
@@ -12,6 +19,25 @@ use wry::{
     webview::WebView,
 };
 
+macro_rules! match_data {
+    (
+        $m:ident;
+        $name:ident;
+        $(
+            $tip:ty => $($mname:literal)|* ;
+        )*
+    ) => {
+        match $name {
+            $( $($mname)|* => {
+                println!("casting to type {:?}", std::any::TypeId::of::<$tip>());
+                let val: $tip = from_value::<$tip>($m).ok()?;
+                Rc::new(val) as Rc<dyn Any>
+            })*
+            _ => return None,
+        }
+    };
+}
+
 pub(super) struct DesktopController {
     pub(super) webviews: HashMap<WindowId, WebView>,
     pub(super) pending_edits: Arc<Mutex<Vec<String>>>,
@@ -26,6 +52,7 @@ impl DesktopController {
         root: Component<P>,
         props: P,
         proxy: EventLoopProxy<UserWindowEvent>,
+        mut event_rx: UnboundedReceiver<serde_json::Value>,
     ) -> Self {
         let edit_queue = Arc::new(Mutex::new(Vec::new()));
 
@@ -33,55 +60,51 @@ impl DesktopController {
         let desktop_context_proxy = proxy.clone();
 
         std::thread::spawn(move || {
-            // We create the runtime as multithreaded, so you can still "spawn" onto multiple threads
+            // We create the runtime as multithreaded, so you can still "tokio::spawn" onto multiple threads
+            // I'd personally not require tokio to be built-in to Dioxus-Desktop, but the DX is worse without it
             let runtime = tokio::runtime::Builder::new_multi_thread()
                 .enable_all()
                 .build()
                 .unwrap();
 
-            runtime.block_on(async move {
-                println!("starting vdom");
-
-                let mut dom = VirtualDom::new_with_props(root, props);
-
-                let window_context = DesktopContext::new(desktop_context_proxy);
-
-                dom.base_scope().provide_context(window_context);
+            let mut dom = VirtualDom::new_with_props(root, props)
+                .with_root_context(DesktopContext::new(desktop_context_proxy));
 
+            {
                 let edits = dom.rebuild();
+                let mut queue = edit_queue.lock().unwrap();
+                queue.push(serde_json::to_string(&edits.template_mutations).unwrap());
+                queue.push(serde_json::to_string(&edits.edits).unwrap());
+                proxy.send_event(UserWindowEvent::Update).unwrap();
+            }
 
-                // println!("got muts: {:#?}", edits);
-
-                {
-                    let mut queue = edit_queue.lock().unwrap();
-                    queue.push(serde_json::to_string(&edits.template_mutations).unwrap());
-                    queue.push(serde_json::to_string(&edits.edits).unwrap());
-                    proxy.send_event(UserWindowEvent::Update).unwrap();
-                    drop(queue);
-                }
-
+            runtime.block_on(async move {
                 loop {
-                    // todo: add the channel of the event loop in
                     tokio::select! {
                         _ = dom.wait_for_work() => {}
+                        Some(json_value) = event_rx.next() => {
+                            if let Ok(value) = serde_json::from_value::<EventMessage>(json_value) {
+                                let name = value.event.clone();
+                                let el_id = ElementId(value.mounted_dom_id);
+                                let evt = decode_event(value);
+
+                                if let Some(evt) = evt {
+                                    dom.handle_event(&name,  evt, el_id, true, EventPriority::Medium);
+                                }
+                            }
+                        }
                     }
 
                     let muts = dom
-                        .render_with_deadline(tokio::time::sleep(
-                            tokio::time::Duration::from_millis(16),
-                        ))
+                        .render_with_deadline(tokio::time::sleep(Duration::from_millis(16)))
                         .await;
 
                     {
                         let mut queue = edit_queue.lock().unwrap();
-
                         queue.push(serde_json::to_string(&muts.template_mutations).unwrap());
                         queue.push(serde_json::to_string(&muts.edits).unwrap());
-
-                        drop(queue);
+                        let _ = proxy.send_event(UserWindowEvent::Update);
                     }
-
-                    let _ = proxy.send_event(UserWindowEvent::Update);
                 }
             })
         });
@@ -113,8 +136,6 @@ impl DesktopController {
 
             let (_id, view) = self.webviews.iter_mut().next().unwrap();
 
-            println!("sending edits {:#?}", new_queue);
-
             for edit in new_queue.drain(..) {
                 view.evaluate_script(&format!("window.interpreter.handleEdits({})", edit))
                     .unwrap();
@@ -122,3 +143,44 @@ impl DesktopController {
         }
     }
 }
+
+#[derive(Deserialize)]
+struct EventMessage {
+    contents: serde_json::Value,
+    event: String,
+    mounted_dom_id: usize,
+}
+
+fn decode_event(value: EventMessage) -> Option<Rc<dyn Any>> {
+    let val = value.contents;
+    let name = value.event.as_str();
+    let el_id = ElementId(value.mounted_dom_id);
+    type DragData = MouseData;
+
+    let evt = match_data! { val; name;
+        MouseData => "click" | "contextmenu" | "dblclick" | "doubleclick" | "mousedown" | "mouseenter" | "mouseleave" | "mousemove" | "mouseout" | "mouseover" | "mouseup";
+        ClipboardData => "copy" | "cut" | "paste";
+        CompositionData => "compositionend" | "compositionstart" | "compositionupdate";
+        KeyboardData => "keydown" | "keypress" | "keyup";
+        FocusData => "blur" | "focus" | "focusin" | "focusout";
+        FormData => "change" | "input" | "invalid" | "reset" | "submit";
+        DragData => "drag" | "dragend" | "dragenter" | "dragexit" | "dragleave" | "dragover" | "dragstart" | "drop";
+        PointerData => "pointerlockchange" | "pointerlockerror" | "pointerdown" | "pointermove" | "pointerup" | "pointerover" | "pointerout" | "pointerenter" | "pointerleave" | "gotpointercapture" | "lostpointercapture";
+        SelectionData => "selectstart" | "selectionchange" | "select";
+        TouchData => "touchcancel" | "touchend" | "touchmove" | "touchstart";
+        ScrollData => "scroll";
+        WheelData => "wheel";
+        MediaData => "abort" | "canplay" | "canplaythrough" | "durationchange" | "emptied"
+            | "encrypted" | "ended" | "interruptbegin" | "interruptend" | "loadeddata"
+            | "loadedmetadata" | "loadstart" | "pause" | "play" | "playing" | "progress"
+            | "ratechange" | "seeked" | "seeking" | "stalled" | "suspend" | "timeupdate"
+            | "volumechange" | "waiting" | "error" | "load" | "loadend" | "timeout";
+        AnimationData => "animationstart" | "animationend" | "animationiteration";
+        TransitionData => "transitionend";
+        ToggleData => "toggle";
+        // ImageData => "load" | "error";
+        // OtherData => "abort" | "afterprint" | "beforeprint" | "beforeunload" | "hashchange" | "languagechange" | "message" | "offline" | "online" | "pagehide" | "pageshow" | "popstate" | "rejectionhandled" | "storage" | "unhandledrejection" | "unload" | "userproximity" | "vrdisplayactivate" | "vrdisplayblur" | "vrdisplayconnect" | "vrdisplaydeactivate" | "vrdisplaydisconnect" | "vrdisplayfocus" | "vrdisplaypointerrestricted" | "vrdisplaypointerunrestricted" | "vrdisplaypresentchange";
+    };
+
+    Some(evt)
+}

+ 11 - 4
packages/desktop/src/events.rs

@@ -1,12 +1,11 @@
 //! Convert a serialized event to an event trigger
 
-use std::any::Any;
-use std::sync::Arc;
-
 use dioxus_core::ElementId;
-use dioxus_core::EventPriority;
 use dioxus_html::events::*;
 use serde::{Deserialize, Serialize};
+use serde_json::from_value;
+use std::any::Any;
+use std::rc::Rc;
 
 #[derive(Deserialize, Serialize)]
 pub(crate) struct IpcMessage {
@@ -40,3 +39,11 @@ struct ImEvent {
     mounted_dom_id: ElementId,
     contents: serde_json::Value,
 }
+
+// pub fn make_synthetic_event(name: &str, val: serde_json::Value) -> Option<Rc<dyn Any>> {
+//     // right now we don't support the datatransfer in Drag
+//     type DragData = MouseData;
+//     type ProgressData = MediaData;
+
+//     Some(res)
+// }

+ 6 - 5
packages/desktop/src/lib.rs

@@ -14,6 +14,7 @@ mod protocol;
 
 use desktop_context::UserWindowEvent;
 pub use desktop_context::{use_eval, use_window, DesktopContext};
+use futures_channel::mpsc::unbounded;
 pub use wry;
 pub use wry::application as tao;
 
@@ -100,7 +101,9 @@ pub fn launch_cfg(root: Component, config_builder: Config) {
 pub fn launch_with_props<P: 'static + Send>(root: Component<P>, props: P, mut cfg: Config) {
     let event_loop = EventLoop::with_user_event();
 
-    let mut desktop = DesktopController::new_on_tokio(root, props, event_loop.create_proxy());
+    let (event_tx, event_rx) = unbounded();
+    let mut desktop =
+        DesktopController::new_on_tokio(root, props, event_loop.create_proxy(), event_rx);
     let proxy = event_loop.create_proxy();
 
     // We assume that if the icon is None, then the user just didnt set it
@@ -135,6 +138,7 @@ pub fn launch_with_props<P: 'static + Send>(root: Component<P>, props: P, mut cf
                 let custom_head = cfg.custom_head.clone();
                 let resource_dir = cfg.resource_dir.clone();
                 let index_file = cfg.custom_index.clone();
+                let event_tx = event_tx.clone();
 
                 let mut webview = WebViewBuilder::new(window)
                     .unwrap()
@@ -145,10 +149,7 @@ pub fn launch_with_props<P: 'static + Send>(root: Component<P>, props: P, mut cf
                         parse_ipc_message(&payload)
                             .map(|message| match message.method() {
                                 "user_event" => {
-                                    println!("user event!");
-                                    // let event = trigger_from_serialized(message.params());
-                                    // log::trace!("User event: {:?}", event);
-                                    // sender.unbounded_send(SchedulerMsg::Event(event)).unwrap();
+                                    _ = event_tx.unbounded_send(message.params());
                                 }
                                 "initialize" => {
                                     is_ready.store(true, std::sync::atomic::Ordering::Relaxed);

+ 1 - 0
packages/hooks/src/usefuture.rs

@@ -287,6 +287,7 @@ impl_dep!(A = a, B = b, C = c, D = d, E = e, F = f,);
 impl_dep!(A = a, B = b, C = c, D = d, E = e, F = f, G = g,);
 impl_dep!(A = a, B = b, C = c, D = d, E = e, F = f, G = g, H = h,);
 
+/// A helper macro that merges uses the closure syntax to elaborate the dependency array
 #[macro_export]
 macro_rules! use_future {
     ($cx:ident, || $($rest:tt)*) => { use_future( $cx, (), |_| $($rest)* ) };

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

@@ -68,3 +68,9 @@ pub use transition::*;
 
 mod wheel;
 pub use wheel::*;
+
+mod clipboard;
+pub use clipboard::*;
+
+mod scroll;
+pub use scroll::*;

+ 5 - 1
packages/html/src/events/animation.rs

@@ -1,6 +1,10 @@
+use dioxus_core::UiEvent;
+
+pub type AnimationEvent = UiEvent<AnimationData>;
+
 #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
 #[derive(Debug, Clone)]
-pub struct AnimationEvent {
+pub struct AnimationData {
     pub animation_name: String,
     pub pseudo_element: String,
     pub elapsed_time: f32,

+ 8 - 0
packages/html/src/events/clipboard.rs

@@ -0,0 +1,8 @@
+use dioxus_core::UiEvent;
+
+pub type ClipboardEvent = UiEvent<ClipboardData>;
+#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
+#[derive(Debug, Clone)]
+pub struct ClipboardData {
+    // DOMDataTransfer clipboardData
+}

+ 8 - 0
packages/html/src/events/composition.rs

@@ -0,0 +1,8 @@
+use dioxus_core::UiEvent;
+
+pub type CompositionEvent = UiEvent<CompositionData>;
+#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
+#[derive(Debug, Clone)]
+pub struct CompositionData {
+    pub data: String,
+}

+ 8 - 0
packages/html/src/events/image.rs

@@ -0,0 +1,8 @@
+use dioxus_core::UiEvent;
+
+pub type ImageEvent = UiEvent<ImageData>;
+#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
+#[derive(Debug, Clone)]
+pub struct ImageData {
+    pub load_error: bool,
+}

+ 2 - 11
packages/html/src/events/keyboard.rs

@@ -1,15 +1,6 @@
-use crate::geometry::{
-    ClientPoint, Coordinates, ElementPoint, LinesVector, PagePoint, PagesVector, PixelsVector,
-    ScreenPoint, WheelDelta,
-};
-use crate::input_data::{
-    decode_key_location, decode_mouse_button_set, encode_key_location, encode_mouse_button_set,
-    MouseButton, MouseButtonSet,
-};
-use dioxus_core::{NodeFactory, UiEvent};
-use euclid::UnknownUnit;
+use crate::input_data::{decode_key_location, encode_key_location};
+use dioxus_core::UiEvent;
 use keyboard_types::{Code, Key, Location, Modifiers};
-use std::collections::HashMap;
 use std::convert::TryInto;
 use std::fmt::{Debug, Formatter};
 use std::str::FromStr;

+ 6 - 0
packages/html/src/events/media.rs

@@ -0,0 +1,6 @@
+use dioxus_core::UiEvent;
+
+pub type MediaEvent = UiEvent<MediaData>;
+#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
+#[derive(Debug, Clone)]
+pub struct MediaData {}

+ 31 - 0
packages/html/src/events/pointer.rs

@@ -0,0 +1,31 @@
+use dioxus_core::UiEvent;
+
+pub type PointerEvent = UiEvent<PointerData>;
+#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
+#[derive(Debug, Clone)]
+pub struct PointerData {
+    // Mouse only
+    pub alt_key: bool,
+    pub button: i16,
+    pub buttons: u16,
+    pub client_x: i32,
+    pub client_y: i32,
+    pub ctrl_key: bool,
+    pub meta_key: bool,
+    pub page_x: i32,
+    pub page_y: i32,
+    pub screen_x: i32,
+    pub screen_y: i32,
+    pub shift_key: bool,
+    pub pointer_id: i32,
+    pub width: i32,
+    pub height: i32,
+    pub pressure: f32,
+    pub tangential_pressure: f32,
+    pub tilt_x: i32,
+    pub tilt_y: i32,
+    pub twist: i32,
+    pub pointer_type: String,
+    pub is_primary: bool,
+    // pub get_modifier_state: bool,
+}

+ 6 - 0
packages/html/src/events/scroll.rs

@@ -0,0 +1,6 @@
+use dioxus_core::UiEvent;
+
+pub type ScrollEvent = UiEvent<ScrollData>;
+#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
+#[derive(Debug, Clone)]
+pub struct ScrollData {}

+ 6 - 0
packages/html/src/events/selection.rs

@@ -0,0 +1,6 @@
+use dioxus_core::UiEvent;
+
+pub type SelectionEvent = UiEvent<SelectionData>;
+#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
+#[derive(Debug, Clone)]
+pub struct SelectionData {}

+ 6 - 0
packages/html/src/events/toggle.rs

@@ -0,0 +1,6 @@
+use dioxus_core::UiEvent;
+
+pub type ToggleEvent = UiEvent<ToggleData>;
+#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
+#[derive(Debug, Clone)]
+pub struct ToggleData {}

+ 15 - 0
packages/html/src/events/touch.rs

@@ -0,0 +1,15 @@
+use dioxus_core::UiEvent;
+
+pub type TouchEvent = UiEvent<TouchData>;
+#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
+#[derive(Debug, Clone)]
+pub struct TouchData {
+    pub alt_key: bool,
+    pub ctrl_key: bool,
+    pub meta_key: bool,
+    pub shift_key: bool,
+    // get_modifier_state: bool,
+    // changedTouches: DOMTouchList,
+    // targetTouches: DOMTouchList,
+    // touches: DOMTouchList,
+}

+ 10 - 0
packages/html/src/events/transition.rs

@@ -0,0 +1,10 @@
+use dioxus_core::UiEvent;
+
+pub type TransitionEvent = UiEvent<TransitionData>;
+#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
+#[derive(Debug, Clone)]
+pub struct TransitionData {
+    pub property_name: String,
+    pub pseudo_element: String,
+    pub elapsed_time: f32,
+}

+ 10 - 3
packages/interpreter/src/interpreter.js

@@ -18,6 +18,7 @@ class ListenerMap {
   }
 
   create(event_name, element, handler, bubbles) {
+    console.log("creating listener for", event_name, element, handler, bubbles);
     if (bubbles) {
       if (this.global[event_name] === undefined) {
         this.global[event_name] = {};
@@ -64,15 +65,13 @@ class ListenerMap {
 
 export class Interpreter {
   constructor(root) {
-    console.log("interpreter created", root);
     this.root = root;
     this.listeners = new ListenerMap(root);
     this.nodes = [root];
     this.stack = [root];
     this.handlers = {};
-    this.lastNodeWasText = false;
     this.templates = {};
-    console.log(this);
+    this.lastNodeWasText = false;
   }
   top() {
     return this.stack[this.stack.length - 1];
@@ -328,6 +327,8 @@ export class Interpreter {
         // method is not used by the web implementation
         let handler = (event) => {
 
+          console.log("event", event);
+
           let target = event.target;
           if (target != null) {
             let realId = target.getAttribute(`data-dioxus-id`);
@@ -415,6 +416,8 @@ export class Interpreter {
             );
           }
         };
+
+        console.log("adding event listener", edit);
         this.NewEventListener(edit.event_name, edit.id, handler, event_bubbles(edit.event_name));
 
         break;
@@ -729,6 +732,8 @@ function is_element_node(node) {
 }
 
 function event_bubbles(event) {
+  console.log("event_bubbles", event);
+
   switch (event) {
     case "copy":
       return true;
@@ -895,4 +900,6 @@ function event_bubbles(event) {
     case "toggle":
       return true;
   }
+
+  return true;
 }