From 30ef225812136e72e5f5e4e135d10e4f2fec9130 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Thu, 17 Nov 2022 20:00:39 -0800 Subject: [PATCH] feat: event handling on desktop --- examples/dog_app.rs | 26 ++-- examples/generic_component.rs | 10 +- examples/inputs.rs | 7 +- packages/core/src/arena.rs | 24 ++-- packages/core/src/create.rs | 28 ++--- packages/core/src/diff.rs | 2 +- packages/core/src/events.rs | 33 +++-- packages/core/src/lib.rs | 2 +- packages/core/src/nodes.rs | 39 ++++-- packages/core/src/virtual_dom.rs | 161 ++++++++++++++---------- packages/desktop/Cargo.toml | 4 +- packages/desktop/src/controller.rs | 124 +++++++++++++----- packages/desktop/src/events.rs | 15 ++- packages/desktop/src/lib.rs | 11 +- packages/hooks/src/usefuture.rs | 1 + packages/html/src/events.rs | 6 + packages/html/src/events/animation.rs | 6 +- packages/html/src/events/clipboard.rs | 8 ++ packages/html/src/events/composition.rs | 8 ++ packages/html/src/events/image.rs | 8 ++ packages/html/src/events/keyboard.rs | 13 +- packages/html/src/events/media.rs | 6 + packages/html/src/events/pointer.rs | 31 +++++ packages/html/src/events/scroll.rs | 6 + packages/html/src/events/selection.rs | 6 + packages/html/src/events/toggle.rs | 6 + packages/html/src/events/touch.rs | 15 +++ packages/html/src/events/transition.rs | 10 ++ packages/interpreter/src/interpreter.js | 13 +- 29 files changed, 424 insertions(+), 205 deletions(-) create mode 100644 packages/html/src/events/scroll.rs diff --git a/examples/dog_app.rs b/examples/dog_app.rs index d9bc89866..fd2611e38 100644 --- a/examples/dog_app.rs +++ b/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" } }), } } diff --git a/examples/generic_component.rs b/examples/generic_component.rs index 48751402f..9757b1032 100644 --- a/examples/generic_component.rs +++ b/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::{} - }) + cx.render(rsx! { generic_child::{} }) } fn generic_child(cx: Scope) -> Element { - cx.render(rsx! { - div {} - }) + cx.render(rsx! { div {} }) } diff --git a/examples/inputs.rs b/examples/inputs.rs index b7b930a9d..223e3a7e7 100644 --- a/examples/inputs.rs +++ b/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 {} } - )) - + }) } }) } diff --git a/packages/core/src/arena.rs b/packages/core/src/arena.rs index 827e0d903..c4fbe2b23 100644 --- a/packages/core/src/arena.rs +++ b/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 +*/ diff --git a/packages/core/src/create.rs b/packages/core/src/create.rs index b5baf68b4..f5574522b 100644 --- a/packages/core/src/create.rs +++ b/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::() { // 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..], diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 739afe6c3..9dd315b6c 100644 --- a/packages/core/src/diff.rs +++ b/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); } diff --git a/packages/core/src/events.rs b/packages/core/src/events.rs index 8bf544ce6..deb96ffdf 100644 --- a/packages/core/src/events.rs +++ b/packages/core/src/events.rs @@ -6,21 +6,30 @@ use std::{ rc::Rc, }; -pub struct UiEvent { - pub(crate) bubble_state: Cell, +pub struct UiEvent { + pub(crate) bubbles: Rc>, pub(crate) data: Rc, } -impl Clone for UiEvent { + +impl UiEvent { + pub fn downcast(self) -> Option> { + Some(UiEvent { + bubbles: self.bubbles, + data: self.data.downcast().ok()?, + }) + } +} +impl Clone for UiEvent { fn clone(&self) -> Self { Self { - bubble_state: self.bubble_state.clone(), + bubbles: self.bubbles.clone(), data: self.data.clone(), } } } impl UiEvent { pub fn cancel_bubble(&self) { - self.bubble_state.set(false); + self.bubbles.set(false); } } @@ -35,7 +44,7 @@ impl std::ops::Deref for UiEvent { impl std::fmt::Debug for UiEvent { 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>>; -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. diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index 9934facd6..30196d0d3 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -74,7 +74,7 @@ pub use crate::innerlude::{ DynamicNode, Element, ElementId, - ElementPath, + ElementRef, EventPriority, Fragment, LazyNodes, diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 7f87fe776..843fec939 100644 --- a/packages/core/src/nodes.rs +++ b/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, 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)>), Any(&'a dyn AnyValue), None, } @@ -163,17 +163,30 @@ impl<'a> AttributeValue<'a> { cx: &'a ScopeState, mut f: impl FnMut(UiEvent) + 'a, ) -> AttributeValue<'a> { - let f = cx.bump().alloc(move |a: &dyn Any| { - a.downcast_ref::>() - .map(|a| f(a.clone())) - .unwrap_or_else(|| { - panic!( - "Expected UiEvent<{}>, got {:?}", - std::any::type_name::(), - a - ) - }) - }) as &mut dyn FnMut(&dyn Any); + println!("creating listener with type od id {:?}", TypeId::of::()); + + let f = cx.bump().alloc(move |a: UiEvent| { + println!( + "incoming: {:?}, desired {:?}", + a.data.type_id(), + std::any::TypeId::of::() + ); + + let data = a.data.downcast::().unwrap(); + + // println!("casting to type {:?}", TypeId::of::()); + + f(UiEvent { + bubbles: a.bubbles, + data, + }) + + // println!("listener called"); + // if let Some(event) = a.downcast::() { + // println!("downcast success"); + // f(event) + // } + }) as &mut dyn FnMut(UiEvent); AttributeValue::Listener(RefCell::new(f)) } diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index a953d3404..f30fd0749 100644 --- a/packages/core/src/virtual_dom.rs +++ b/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>, - pub(crate) elements: Slab, pub(crate) scopes: Slab, pub(crate) element_stack: Vec, pub(crate) dirty_scopes: BTreeSet, pub(crate) scheduler: Rc, + // 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, + // While diffing we need some sort of way of breaking off a stream of suspended mutations. pub(crate) scope_stack: Vec, pub(crate) collected_leaves: Vec, @@ -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(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( + pub fn handle_event( &mut self, name: &str, - data: Rc, + data: Rc, element: ElementId, bubbles: bool, - priority: EventPriority, - ) -> Option<()> { - /* - - click registers - - walk upwards until first element with onclick listener - - get template from ElementID - - break out of wait loop - - send event to virtualdom - */ - - let event = UiEvent { - bubble_state: std::cell::Cell::new(true), + // 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, }; - let path = &self.elements[element.0]; - let template = unsafe { &*path.template }; - let dynamic = &template.dynamic_nodes[path.element]; + /* + ------------------------ + The algorithm works by walking through the list of dynamic attributes, checking their paths, and breaking when + we find the target path. - let location = template - .dynamic_attrs - .iter() - .position(|attr| attr.mounted_element.get() == element)?; + 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. - // let mut index = Some((path.template, location)); + 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 mut listeners = Vec::<&Attribute>::new(); + while let Some(el_ref) = parent_path { + let template = unsafe { &*el_ref.template }; + let target_path = el_ref.path; - // while let Some((raw_parent, dyn_index)) = index { - // let parent = unsafe { &mut *raw_parent }; - // let path = parent.template.node_paths[dyn_index]; + let mut attrs = template.dynamic_attrs.iter().enumerate(); - // 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, - // } - // }), - // ); + 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()] + } - // index = parent.parent; - // } + let this_path = template.template.attr_paths[idx]; - for listener in listeners { - if let AttributeValue::Listener(listener) = &listener.value { - (listener.borrow_mut())(&event.clone()) + 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); + + // 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; + } + } } - } - Some(()) + 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; + } + } + } + + 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 { diff --git a/packages/desktop/Cargo.toml b/packages/desktop/Cargo.toml index 134fc7ade..e8ec74bc0 100644 --- a/packages/desktop/Cargo.toml +++ b/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" diff --git a/packages/desktop/src/controller.rs b/packages/desktop/src/controller.rs index 8d6ce5901..24734b232 100644 --- a/packages/desktop/src/controller.rs +++ b/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 + })* + _ => return None, + } + }; +} + pub(super) struct DesktopController { pub(super) webviews: HashMap, pub(super) pending_edits: Arc>>, @@ -26,6 +52,7 @@ impl DesktopController { root: Component

, props: P, proxy: EventLoopProxy, + mut event_rx: UnboundedReceiver, ) -> 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::(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> { + 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) +} diff --git a/packages/desktop/src/events.rs b/packages/desktop/src/events.rs index 0e5bcac85..2d2905474 100644 --- a/packages/desktop/src/events.rs +++ b/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> { +// // right now we don't support the datatransfer in Drag +// type DragData = MouseData; +// type ProgressData = MediaData; + +// Some(res) +// } diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index db97b6b74..cbdf3a4c4 100644 --- a/packages/desktop/src/lib.rs +++ b/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(root: Component

, 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(root: Component

, 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(root: Component

, 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); diff --git a/packages/hooks/src/usefuture.rs b/packages/hooks/src/usefuture.rs index 782c774b0..ac4169281 100644 --- a/packages/hooks/src/usefuture.rs +++ b/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)* ) }; diff --git a/packages/html/src/events.rs b/packages/html/src/events.rs index a9705ef9c..9ed28fc62 100644 --- a/packages/html/src/events.rs +++ b/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::*; diff --git a/packages/html/src/events/animation.rs b/packages/html/src/events/animation.rs index dbaf566ca..1d90fe9a7 100644 --- a/packages/html/src/events/animation.rs +++ b/packages/html/src/events/animation.rs @@ -1,6 +1,10 @@ +use dioxus_core::UiEvent; + +pub type AnimationEvent = UiEvent; + #[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, diff --git a/packages/html/src/events/clipboard.rs b/packages/html/src/events/clipboard.rs index e69de29bb..53f1b9803 100644 --- a/packages/html/src/events/clipboard.rs +++ b/packages/html/src/events/clipboard.rs @@ -0,0 +1,8 @@ +use dioxus_core::UiEvent; + +pub type ClipboardEvent = UiEvent; +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone)] +pub struct ClipboardData { + // DOMDataTransfer clipboardData +} diff --git a/packages/html/src/events/composition.rs b/packages/html/src/events/composition.rs index e69de29bb..89f374770 100644 --- a/packages/html/src/events/composition.rs +++ b/packages/html/src/events/composition.rs @@ -0,0 +1,8 @@ +use dioxus_core::UiEvent; + +pub type CompositionEvent = UiEvent; +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone)] +pub struct CompositionData { + pub data: String, +} diff --git a/packages/html/src/events/image.rs b/packages/html/src/events/image.rs index e69de29bb..5be6ed1a0 100644 --- a/packages/html/src/events/image.rs +++ b/packages/html/src/events/image.rs @@ -0,0 +1,8 @@ +use dioxus_core::UiEvent; + +pub type ImageEvent = UiEvent; +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone)] +pub struct ImageData { + pub load_error: bool, +} diff --git a/packages/html/src/events/keyboard.rs b/packages/html/src/events/keyboard.rs index a9917a4ee..040b2658e 100644 --- a/packages/html/src/events/keyboard.rs +++ b/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; diff --git a/packages/html/src/events/media.rs b/packages/html/src/events/media.rs index e69de29bb..505d0db5b 100644 --- a/packages/html/src/events/media.rs +++ b/packages/html/src/events/media.rs @@ -0,0 +1,6 @@ +use dioxus_core::UiEvent; + +pub type MediaEvent = UiEvent; +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone)] +pub struct MediaData {} diff --git a/packages/html/src/events/pointer.rs b/packages/html/src/events/pointer.rs index e69de29bb..0ab696d89 100644 --- a/packages/html/src/events/pointer.rs +++ b/packages/html/src/events/pointer.rs @@ -0,0 +1,31 @@ +use dioxus_core::UiEvent; + +pub type PointerEvent = UiEvent; +#[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, +} diff --git a/packages/html/src/events/scroll.rs b/packages/html/src/events/scroll.rs new file mode 100644 index 000000000..562041a16 --- /dev/null +++ b/packages/html/src/events/scroll.rs @@ -0,0 +1,6 @@ +use dioxus_core::UiEvent; + +pub type ScrollEvent = UiEvent; +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone)] +pub struct ScrollData {} diff --git a/packages/html/src/events/selection.rs b/packages/html/src/events/selection.rs index e69de29bb..58afbd278 100644 --- a/packages/html/src/events/selection.rs +++ b/packages/html/src/events/selection.rs @@ -0,0 +1,6 @@ +use dioxus_core::UiEvent; + +pub type SelectionEvent = UiEvent; +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone)] +pub struct SelectionData {} diff --git a/packages/html/src/events/toggle.rs b/packages/html/src/events/toggle.rs index e69de29bb..7201c3f1d 100644 --- a/packages/html/src/events/toggle.rs +++ b/packages/html/src/events/toggle.rs @@ -0,0 +1,6 @@ +use dioxus_core::UiEvent; + +pub type ToggleEvent = UiEvent; +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone)] +pub struct ToggleData {} diff --git a/packages/html/src/events/touch.rs b/packages/html/src/events/touch.rs index e69de29bb..9e5841764 100644 --- a/packages/html/src/events/touch.rs +++ b/packages/html/src/events/touch.rs @@ -0,0 +1,15 @@ +use dioxus_core::UiEvent; + +pub type TouchEvent = UiEvent; +#[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, +} diff --git a/packages/html/src/events/transition.rs b/packages/html/src/events/transition.rs index e69de29bb..ff3a64ac6 100644 --- a/packages/html/src/events/transition.rs +++ b/packages/html/src/events/transition.rs @@ -0,0 +1,10 @@ +use dioxus_core::UiEvent; + +pub type TransitionEvent = UiEvent; +#[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, +} diff --git a/packages/interpreter/src/interpreter.js b/packages/interpreter/src/interpreter.js index 58613ea0b..85b4de8dd 100644 --- a/packages/interpreter/src/interpreter.js +++ b/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; }