feat: event handling on desktop

This commit is contained in:
Jonathan Kelley committed 2022-11-17 20:00:39 -08:00
1 parent 073fea9ed3
commit 30ef225812
29 files changed
+424 -205

No files matched your search

+9 -17
View File
@@ -1,7 +1,3 @@
#![allow(non_snake_case)]
//! Render a bunch of doggos!
use dioxus::prelude::*; use dioxus::prelude::*;
use std::collections::HashMap; use std::collections::HashMap;
@@ -19,8 +15,8 @@ struct ListBreeds {
} }
async fn app_root(cx: Scope<'_>) -> Element { async fn app_root(cx: Scope<'_>) -> Element {
let breed = use_state(&cx, || "deerhound".to_string()); let breed = use_state(cx, || "deerhound".to_string());
let breeds = use_future(&cx, (), |_| async move { let breeds = use_future!(cx, || async move {
reqwest::get("https://dog.ceo/api/breeds/list/all") reqwest::get("https://dog.ceo/api/breeds/list/all")
.await .await
.unwrap() .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] #[inline_props]
async fn Breed(cx: Scope, breed: String) -> Element { async fn breed_pic(cx: Scope, breed: String) -> Element {
println!("Rendering Breed: {}", breed); let fut = use_future!(cx, |breed| async move {
let fut = use_future(&cx, (breed,), |(breed,)| async move {
reqwest::get(format!("https://dog.ceo/api/breed/{}/images/random", breed)) reqwest::get(format!("https://dog.ceo/api/breed/{}/images/random", breed))
.await .await
.unwrap() .unwrap()
@@ -70,11 +66,7 @@ async fn Breed(cx: Scope, breed: String) -> Element {
.await .await
}); });
let resp = fut.await; match fut.await {
println!("achieved results!");
match resp {
Ok(resp) => cx.render(rsx! { Ok(resp) => cx.render(rsx! {
div { div {
button { 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
View File
@@ -1,5 +1,3 @@
use std::fmt::Display;
use dioxus::prelude::*; use dioxus::prelude::*;
fn main() { fn main() {
@@ -7,13 +5,9 @@ fn main() {
} }
fn app(cx: Scope) -> Element { fn app(cx: Scope) -> Element {
cx.render(rsx! { cx.render(rsx! { generic_child::<i32>{} })
generic_child::<i32>{}
})
} }
fn generic_child<T>(cx: Scope) -> Element { fn generic_child<T>(cx: Scope) -> Element {
cx.render(rsx! { cx.render(rsx! { div {} })
div {}
})
} }
+2 -5
View File
@@ -2,7 +2,6 @@
//! //!
//! There is some conversion happening when input types are checkbox/radio/select/textarea etc. //! There is some conversion happening when input types are checkbox/radio/select/textarea etc.
use dioxus::events::FormData;
use dioxus::prelude::*; use dioxus::prelude::*;
fn main() { fn main() {
@@ -29,7 +28,6 @@ const FIELDS: &[(&str, &str)] = &[
("text", ""), ("text", ""),
("time", ""), ("time", ""),
("url", ""), ("url", ""),
//
// less supported things // less supported things
("hidden", ""), ("hidden", ""),
("month", ""), // degrades to text most of the time, but works properly as "value'" ("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 { div {
input { input {
id: "{field}", id: "{field}",
@@ -132,8 +130,7 @@ fn app(cx: Scope) -> Element {
} }
br {} br {}
} }
)) })
} }
}) })
} }
+17 -7
View File
@@ -4,28 +4,31 @@ use crate::{nodes::VNode, virtual_dom::VirtualDom};
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct ElementId(pub usize); 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 template: *mut VNode<'static>,
pub element: usize,
} }
impl ElementPath { impl ElementRef {
pub fn null() -> Self { pub fn null() -> Self {
Self { Self {
template: std::ptr::null_mut(), template: std::ptr::null_mut(),
element: 0, path: &[],
} }
} }
} }
impl VirtualDom { 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 entry = self.elements.vacant_entry();
let id = entry.key(); let id = entry.key();
entry.insert(ElementPath { entry.insert(ElementRef {
template: template as *const _ as *mut _, template: template as *const _ as *mut _,
element: id, path,
}); });
ElementId(id) ElementId(id)
@@ -35,3 +38,10 @@ impl VirtualDom {
self.elements.remove(id.0); self.elements.remove(id.0);
} }
} }
/*
now......
an ID is mostly a pointer to a node in the real dom.
We need to
*/
+14 -14
View File
@@ -1,7 +1,5 @@
use std::ops::Bound;
use crate::factory::RenderReturn; 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::mutations::Mutation::*; use crate::mutations::Mutation::*;
use crate::nodes::VNode; use crate::nodes::VNode;
@@ -75,13 +73,13 @@ impl VirtualDom {
DynamicNode::Text { DynamicNode::Text {
id: slot, value, .. id: slot, value, ..
} => { } => {
let id = self.next_element(template); let id = self.next_element(template, template.template.node_paths[*id]);
slot.set(id); slot.set(id);
mutations.push(CreateTextNode { value, id }); mutations.push(CreateTextNode { value, id });
1 1
} }
DynamicNode::Placeholder(slot) => { DynamicNode::Placeholder(slot) => {
let id = self.next_element(template); let id = self.next_element(template, template.template.node_paths[*id]);
slot.set(id); slot.set(id);
mutations.push(CreatePlaceholder { id }); mutations.push(CreatePlaceholder { id });
1 1
@@ -95,7 +93,7 @@ impl VirtualDom {
while let Some((mut attr_id, path)) = while let Some((mut attr_id, path)) =
dynamic_attrs.next_if(|(_, p)| p[0] == root_idx as u8) 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 { mutations.push(AssignId {
path: &path[1..], path: &path[1..],
id, id,
@@ -118,7 +116,8 @@ impl VirtualDom {
id, id,
}), }),
AttributeValue::Listener(_) => mutations.push(NewEventListener { AttributeValue::Listener(_) => mutations.push(NewEventListener {
event_name: attribute.name, // all listeners start with "on"
event_name: &attribute.name[2..],
scope: cur_scope, scope: cur_scope,
id, id,
}), }),
@@ -178,8 +177,8 @@ impl VirtualDom {
) { ) {
match *node { match *node {
// Todo: create the children's template // Todo: create the children's template
TemplateNode::Dynamic(_) => { TemplateNode::Dynamic(idx) => {
let id = self.next_element(template); let id = self.next_element(template, template.template.node_paths[idx]);
mutations.push(CreatePlaceholder { id }) mutations.push(CreatePlaceholder { id })
} }
TemplateNode::Text(value) => mutations.push(CreateStaticText { value }), TemplateNode::Text(value) => mutations.push(CreateStaticText { value }),
@@ -194,7 +193,7 @@ impl VirtualDom {
tag, tag,
inner_opt, 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 { mutations.push(CreateElement {
name: tag, name: tag,
@@ -239,7 +238,7 @@ impl VirtualDom {
) -> usize { ) -> usize {
match &node { match &node {
DynamicNode::Text { id, value, .. } => { 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); id.set(new_id);
mutations.push(HydrateText { mutations.push(HydrateText {
id: new_id, id: new_id,
@@ -269,7 +268,7 @@ impl VirtualDom {
} }
RenderReturn::Async(_) => { 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)); placeholder.set(Some(new_id));
self.scopes[scope.0].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>() self.scopes[scope.0].has_context::<SuspenseContext>()
{ {
// Since this is a boundary, use it as a placeholder // 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)); placeholder.set(Some(new_id));
self.scopes[scope.0].placeholder.set(Some(new_id)); self.scopes[scope.0].placeholder.set(Some(new_id));
mutations.push(AssignId { mutations.push(AssignId {
@@ -350,7 +350,7 @@ impl VirtualDom {
} }
DynamicNode::Placeholder(slot) => { DynamicNode::Placeholder(slot) => {
let id = self.next_element(template); let id = self.next_element(template, template.template.node_paths[idx]);
slot.set(id); slot.set(id);
mutations.push(AssignId { mutations.push(AssignId {
path: &template.template.node_paths[idx][1..], path: &template.template.node_paths[idx][1..],
+1 -1
View File
@@ -82,7 +82,7 @@ impl<'b> VirtualDom {
// remove old with placeholder // remove old with placeholder
(Sync(Some(l)), Sync(None)) | (Sync(Some(l)), Async(_)) => { (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 }); m.push(Mutation::CreatePlaceholder { id });
self.drop_template(m, l, true); self.drop_template(m, l, true);
} }
+15 -18
View File
@@ -6,21 +6,30 @@ use std::{
rc::Rc, rc::Rc,
}; };
pub struct UiEvent<T: 'static> { pub struct UiEvent<T: 'static + ?Sized> {
pub(crate) bubble_state: Cell<bool>, pub(crate) bubbles: Rc<Cell<bool>>,
pub(crate) data: Rc<T>, 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 { fn clone(&self) -> Self {
Self { Self {
bubble_state: self.bubble_state.clone(), bubbles: self.bubbles.clone(),
data: self.data.clone(), data: self.data.clone(),
} }
} }
} }
impl<T> UiEvent<T> { impl<T> UiEvent<T> {
pub fn cancel_bubble(&self) { 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> { impl<T: Debug> std::fmt::Debug for UiEvent<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("UiEvent") f.debug_struct("UiEvent")
.field("bubble_state", &self.bubble_state) .field("bubble_state", &self.bubbles)
.field("data", &self.data) .field("data", &self.data)
.finish() .finish()
} }
@@ -93,18 +102,6 @@ pub enum EventPriority {
Low = 0, 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>; 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. /// The callback type generated by the `rsx!` macro when an `on` field is specified for components.
+1 -1
View File
@@ -74,7 +74,7 @@ pub use crate::innerlude::{
DynamicNode, DynamicNode,
Element, Element,
ElementId, ElementId,
ElementPath, ElementRef,
EventPriority, EventPriority,
Fragment, Fragment,
LazyNodes, LazyNodes,
+26 -13
View File
@@ -17,7 +17,7 @@ pub struct VNode<'a> {
pub key: Option<&'a str>, pub key: Option<&'a str>,
// When rendered, this template will be linked to its parent manually // 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>, pub template: Template<'static>,
@@ -153,7 +153,7 @@ pub enum AttributeValue<'a> {
Float(f32), Float(f32),
Int(i32), Int(i32),
Bool(bool), Bool(bool),
Listener(RefCell<&'a mut dyn FnMut(&dyn Any)>), Listener(RefCell<&'a mut dyn FnMut(UiEvent<dyn Any>)>),
Any(&'a dyn AnyValue), Any(&'a dyn AnyValue),
None, None,
} }
@@ -163,17 +163,30 @@ impl<'a> AttributeValue<'a> {
cx: &'a ScopeState, cx: &'a ScopeState,
mut f: impl FnMut(UiEvent<T>) + 'a, mut f: impl FnMut(UiEvent<T>) + 'a,
) -> AttributeValue<'a> { ) -> AttributeValue<'a> {
let f = cx.bump().alloc(move |a: &dyn Any| { println!("creating listener with type od id {:?}", TypeId::of::<T>());
a.downcast_ref::<UiEvent<T>>()
.map(|a| f(a.clone())) let f = cx.bump().alloc(move |a: UiEvent<dyn Any>| {
.unwrap_or_else(|| { println!(
panic!( "incoming: {:?}, desired {:?}",
"Expected UiEvent<{}>, got {:?}", a.data.type_id(),
std::any::type_name::<T>(), std::any::TypeId::of::<T>()
a );
)
}) let data = a.data.downcast::<T>().unwrap();
}) as &mut dyn FnMut(&dyn Any);
// 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)) AttributeValue::Listener(RefCell::new(f))
} }
+96 -65
View File
@@ -1,7 +1,7 @@
use crate::{ use crate::{
any_props::VComponentProps, any_props::VComponentProps,
arena::ElementId, arena::ElementId,
arena::ElementPath, arena::ElementRef,
diff::DirtyScope, diff::DirtyScope,
factory::RenderReturn, factory::RenderReturn,
innerlude::{Mutations, Scheduler, SchedulerMsg}, innerlude::{Mutations, Scheduler, SchedulerMsg},
@@ -13,12 +13,12 @@ use crate::{
}; };
use futures_util::{pin_mut, FutureExt, StreamExt}; use futures_util::{pin_mut, FutureExt, StreamExt};
use slab::Slab; use slab::Slab;
use std::future::Future;
use std::rc::Rc; use std::rc::Rc;
use std::{ use std::{
any::Any, any::Any,
collections::{BTreeSet, HashMap}, collections::{BTreeSet, HashMap},
}; };
use std::{cell::Cell, future::Future};
/// A virtual node system that progresses user events and diffs UI trees. /// A virtual node system that progresses user events and diffs UI trees.
/// ///
@@ -145,12 +145,14 @@ use std::{
/// ``` /// ```
pub struct VirtualDom { pub struct VirtualDom {
pub(crate) templates: HashMap<TemplateId, Template<'static>>, pub(crate) templates: HashMap<TemplateId, Template<'static>>,
pub(crate) elements: Slab<ElementPath>,
pub(crate) scopes: Slab<ScopeState>, pub(crate) scopes: Slab<ScopeState>,
pub(crate) element_stack: Vec<ElementId>, pub(crate) element_stack: Vec<ElementId>,
pub(crate) dirty_scopes: BTreeSet<DirtyScope>, pub(crate) dirty_scopes: BTreeSet<DirtyScope>,
pub(crate) scheduler: Rc<Scheduler>, 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. // While diffing we need some sort of way of breaking off a stream of suspended mutations.
pub(crate) scope_stack: Vec<ScopeId>, pub(crate) scope_stack: Vec<ScopeId>,
pub(crate) collected_leaves: Vec<SuspenseId>, pub(crate) collected_leaves: Vec<SuspenseId>,
@@ -246,7 +248,7 @@ impl VirtualDom {
root.provide_context(SuspenseBoundary::new(ScopeId(0))); root.provide_context(SuspenseBoundary::new(ScopeId(0)));
// the root element is always given element 0 // the root element is always given element 0
dom.elements.insert(ElementPath::null()); dom.elements.insert(ElementRef::null());
dom dom
} }
@@ -259,6 +261,12 @@ impl VirtualDom {
self.scopes.get(0).unwrap() 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) { fn mark_dirty_scope(&mut self, id: ScopeId) {
let height = self.scopes[id.0].height; let height = self.scopes[id.0].height;
self.dirty_scopes.insert(DirtyScope { height, id }); 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, &mut self,
name: &str, name: &str,
data: Rc<T>, data: Rc<dyn Any>,
element: ElementId, element: ElementId,
bubbles: bool, bubbles: bool,
priority: EventPriority, // todo: priority is helpful when scheduling work around suspense, but we don't currently use it
) -> Option<()> { _priority: EventPriority,
/* ) {
- click registers let uievent = UiEvent {
- walk upwards until first element with onclick listener bubbles: Rc::new(Cell::new(bubbles)),
- get template from ElementID
- break out of wait loop
- send event to virtualdom
*/
let event = UiEvent {
bubble_state: std::cell::Cell::new(true),
data, 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 With the target path, we try and move up to the parent until there is no parent.
.dynamic_attrs Due to how bubbling works, we call the listeners before walking to the parent.
.iter()
.position(|attr| attr.mounted_element.get() == element)?;
// 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 mut attrs = template.dynamic_attrs.iter().enumerate();
// let parent = unsafe { &mut *raw_parent };
// let path = parent.template.node_paths[dyn_index];
// listeners.extend( while let Some((idx, attr)) = attrs.next() {
// parent pub fn is_path_ascendant(small: &[u8], big: &[u8]) -> bool {
// .dynamic_attrs small.len() >= big.len() && small == &big[..small.len()]
// .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; let this_path = template.template.attr_paths[idx];
// }
for listener in listeners { println!(
if let AttributeValue::Listener(listener) = &listener.value { "is {:?} ascendant of {:?} ? {}",
(listener.borrow_mut())(&event.clone()) 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. /// Wait for the scheduler to have any work.
@@ -508,22 +539,22 @@ impl VirtualDom {
} }
} }
fn mark_dirty_scope(&mut self, scope_id: ScopeId) { // fn mark_dirty_scope(&mut self, scope_id: ScopeId) {
let scopes = &self.scopes; // let scopes = &self.scopes;
if let Some(scope) = scopes.get_scope(scope_id) { // if let Some(scope) = scopes.get_scope(scope_id) {
let height = scope.height; // let height = scope.height;
let id = scope_id.0; // let id = scope_id.0;
if let Err(index) = self.dirty_scopes.binary_search_by(|new| { // if let Err(index) = self.dirty_scopes.binary_search_by(|new| {
let scope = scopes.get_scope(*new).unwrap(); // let scope = scopes.get_scope(*new).unwrap();
let new_height = scope.height; // let new_height = scope.height;
let new_id = &scope.scope_id(); // let new_id = &scope.scope_id();
height.cmp(&new_height).then(new_id.0.cmp(&id)) // height.cmp(&new_height).then(new_id.0.cmp(&id))
}) { // }) {
self.dirty_scopes.insert(index, scope_id); // self.dirty_scopes.insert(index, scope_id);
log::info!("mark_dirty_scope: {:?}", self.dirty_scopes); // log::info!("mark_dirty_scope: {:?}", self.dirty_scopes);
} // }
} // }
} // }
} }
impl Drop for VirtualDom { impl Drop for VirtualDom {
+3 -1
View File
@@ -27,12 +27,14 @@ tokio = { version = "1.16.1", features = [
"rt-multi-thread", "rt-multi-thread",
"rt", "rt",
"time", "time",
"macros",
], optional = true, default-features = false } ], optional = true, default-features = false }
webbrowser = "0.8.0" webbrowser = "0.8.0"
infer = "0.9.0" infer = "0.11.0"
dunce = "1.0.2" dunce = "1.0.2"
interprocess = { version = "1.1.1" } interprocess = { version = "1.1.1" }
futures-util = "0.3.25"
[target.'cfg(target_os = "macos")'.dependencies] [target.'cfg(target_os = "macos")'.dependencies]
core-foundation = "0.9.3" core-foundation = "0.9.3"
+93 -31
View File
@@ -1,10 +1,17 @@
use crate::desktop_context::{DesktopContext, UserWindowEvent}; use crate::desktop_context::{DesktopContext, UserWindowEvent};
use dioxus_core::*; 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::{ use std::{
collections::HashMap, collections::HashMap,
sync::Arc, sync::Arc,
sync::{atomic::AtomicBool, Mutex}, sync::{atomic::AtomicBool, Mutex},
time::Duration,
}; };
use wry::{ use wry::{
self, self,
@@ -12,6 +19,25 @@ use wry::{
webview::WebView, 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) struct DesktopController {
pub(super) webviews: HashMap<WindowId, WebView>, pub(super) webviews: HashMap<WindowId, WebView>,
pub(super) pending_edits: Arc<Mutex<Vec<String>>>, pub(super) pending_edits: Arc<Mutex<Vec<String>>>,
@@ -26,6 +52,7 @@ impl DesktopController {
root: Component<P>, root: Component<P>,
props: P, props: P,
proxy: EventLoopProxy<UserWindowEvent>, proxy: EventLoopProxy<UserWindowEvent>,
mut event_rx: UnboundedReceiver<serde_json::Value>,
) -> Self { ) -> Self {
let edit_queue = Arc::new(Mutex::new(Vec::new())); let edit_queue = Arc::new(Mutex::new(Vec::new()));
@@ -33,55 +60,51 @@ impl DesktopController {
let desktop_context_proxy = proxy.clone(); let desktop_context_proxy = proxy.clone();
std::thread::spawn(move || { 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() let runtime = tokio::runtime::Builder::new_multi_thread()
.enable_all() .enable_all()
.build() .build()
.unwrap(); .unwrap();
runtime.block_on(async move { let mut dom = VirtualDom::new_with_props(root, props)
println!("starting vdom"); .with_root_context(DesktopContext::new(desktop_context_proxy));
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 edits = dom.rebuild(); 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); runtime.block_on(async move {
{
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);
}
loop { loop {
// todo: add the channel of the event loop in
tokio::select! { tokio::select! {
_ = dom.wait_for_work() => {} _ = 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 let muts = dom
.render_with_deadline(tokio::time::sleep( .render_with_deadline(tokio::time::sleep(Duration::from_millis(16)))
tokio::time::Duration::from_millis(16),
))
.await; .await;
{ {
let mut queue = edit_queue.lock().unwrap(); let mut queue = edit_queue.lock().unwrap();
queue.push(serde_json::to_string(&muts.template_mutations).unwrap()); queue.push(serde_json::to_string(&muts.template_mutations).unwrap());
queue.push(serde_json::to_string(&muts.edits).unwrap()); queue.push(serde_json::to_string(&muts.edits).unwrap());
let _ = proxy.send_event(UserWindowEvent::Update);
drop(queue);
} }
let _ = proxy.send_event(UserWindowEvent::Update);
} }
}) })
}); });
@@ -113,8 +136,6 @@ impl DesktopController {
let (_id, view) = self.webviews.iter_mut().next().unwrap(); let (_id, view) = self.webviews.iter_mut().next().unwrap();
println!("sending edits {:#?}", new_queue);
for edit in new_queue.drain(..) { for edit in new_queue.drain(..) {
view.evaluate_script(&format!("window.interpreter.handleEdits({})", edit)) view.evaluate_script(&format!("window.interpreter.handleEdits({})", edit))
.unwrap(); .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
View File
@@ -1,12 +1,11 @@
//! Convert a serialized event to an event trigger //! Convert a serialized event to an event trigger
use std::any::Any;
use std::sync::Arc;
use dioxus_core::ElementId; use dioxus_core::ElementId;
use dioxus_core::EventPriority;
use dioxus_html::events::*; use dioxus_html::events::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::from_value;
use std::any::Any;
use std::rc::Rc;
#[derive(Deserialize, Serialize)] #[derive(Deserialize, Serialize)]
pub(crate) struct IpcMessage { pub(crate) struct IpcMessage {
@@ -40,3 +39,11 @@ struct ImEvent {
mounted_dom_id: ElementId, mounted_dom_id: ElementId,
contents: serde_json::Value, 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
View File
@@ -14,6 +14,7 @@ mod protocol;
use desktop_context::UserWindowEvent; use desktop_context::UserWindowEvent;
pub use desktop_context::{use_eval, use_window, DesktopContext}; pub use desktop_context::{use_eval, use_window, DesktopContext};
use futures_channel::mpsc::unbounded;
pub use wry; pub use wry;
pub use wry::application as tao; 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) { pub fn launch_with_props<P: 'static + Send>(root: Component<P>, props: P, mut cfg: Config) {
let event_loop = EventLoop::with_user_event(); 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(); let proxy = event_loop.create_proxy();
// We assume that if the icon is None, then the user just didnt set it // 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 custom_head = cfg.custom_head.clone();
let resource_dir = cfg.resource_dir.clone(); let resource_dir = cfg.resource_dir.clone();
let index_file = cfg.custom_index.clone(); let index_file = cfg.custom_index.clone();
let event_tx = event_tx.clone();
let mut webview = WebViewBuilder::new(window) let mut webview = WebViewBuilder::new(window)
.unwrap() .unwrap()
@@ -145,10 +149,7 @@ pub fn launch_with_props<P: 'static + Send>(root: Component<P>, props: P, mut cf
parse_ipc_message(&payload) parse_ipc_message(&payload)
.map(|message| match message.method() { .map(|message| match message.method() {
"user_event" => { "user_event" => {
println!("user event!"); _ = event_tx.unbounded_send(message.params());
// let event = trigger_from_serialized(message.params());
// log::trace!("User event: {:?}", event);
// sender.unbounded_send(SchedulerMsg::Event(event)).unwrap();
} }
"initialize" => { "initialize" => {
is_ready.store(true, std::sync::atomic::Ordering::Relaxed); is_ready.store(true, std::sync::atomic::Ordering::Relaxed);
+1
View File
@@ -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,);
impl_dep!(A = a, B = b, C = c, D = d, E = e, F = f, G = g, H = h,); 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_export]
macro_rules! use_future { macro_rules! use_future {
($cx:ident, || $($rest:tt)*) => { use_future( $cx, (), |_| $($rest)* ) }; ($cx:ident, || $($rest:tt)*) => { use_future( $cx, (), |_| $($rest)* ) };
+6
View File
@@ -68,3 +68,9 @@ pub use transition::*;
mod wheel; mod wheel;
pub use wheel::*; pub use wheel::*;
mod clipboard;
pub use clipboard::*;
mod scroll;
pub use scroll::*;
+5 -1
View File
@@ -1,6 +1,10 @@
use dioxus_core::UiEvent;
pub type AnimationEvent = UiEvent<AnimationData>;
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct AnimationEvent { pub struct AnimationData {
pub animation_name: String, pub animation_name: String,
pub pseudo_element: String, pub pseudo_element: String,
pub elapsed_time: f32, pub elapsed_time: f32,
+8
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -1,15 +1,6 @@
use crate::geometry::{ use crate::input_data::{decode_key_location, encode_key_location};
ClientPoint, Coordinates, ElementPoint, LinesVector, PagePoint, PagesVector, PixelsVector, use dioxus_core::UiEvent;
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 keyboard_types::{Code, Key, Location, Modifiers}; use keyboard_types::{Code, Key, Location, Modifiers};
use std::collections::HashMap;
use std::convert::TryInto; use std::convert::TryInto;
use std::fmt::{Debug, Formatter}; use std::fmt::{Debug, Formatter};
use std::str::FromStr; use std::str::FromStr;
+6
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -18,6 +18,7 @@ class ListenerMap {
} }
create(event_name, element, handler, bubbles) { create(event_name, element, handler, bubbles) {
console.log("creating listener for", event_name, element, handler, bubbles);
if (bubbles) { if (bubbles) {
if (this.global[event_name] === undefined) { if (this.global[event_name] === undefined) {
this.global[event_name] = {}; this.global[event_name] = {};
@@ -64,15 +65,13 @@ class ListenerMap {
export class Interpreter { export class Interpreter {
constructor(root) { constructor(root) {
console.log("interpreter created", root);
this.root = root; this.root = root;
this.listeners = new ListenerMap(root); this.listeners = new ListenerMap(root);
this.nodes = [root]; this.nodes = [root];
this.stack = [root]; this.stack = [root];
this.handlers = {}; this.handlers = {};
this.lastNodeWasText = false;
this.templates = {}; this.templates = {};
console.log(this); this.lastNodeWasText = false;
} }
top() { top() {
return this.stack[this.stack.length - 1]; return this.stack[this.stack.length - 1];
@@ -328,6 +327,8 @@ export class Interpreter {
// method is not used by the web implementation // method is not used by the web implementation
let handler = (event) => { let handler = (event) => {
console.log("event", event);
let target = event.target; let target = event.target;
if (target != null) { if (target != null) {
let realId = target.getAttribute(`data-dioxus-id`); 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)); this.NewEventListener(edit.event_name, edit.id, handler, event_bubbles(edit.event_name));
break; break;
@@ -729,6 +732,8 @@ function is_element_node(node) {
} }
function event_bubbles(event) { function event_bubbles(event) {
console.log("event_bubbles", event);
switch (event) { switch (event) {
case "copy": case "copy":
return true; return true;
@@ -895,4 +900,6 @@ function event_bubbles(event) {
case "toggle": case "toggle":
return true; return true;
} }
return true;
} }