feat: event handling on desktop
This commit is contained in:
1 parent
073fea9ed3
commit
30ef225812
29 files changed
+424
-205
No files matched your search
+9
-17
@@ -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" } }),
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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 {}
|
||||
}
|
||||
))
|
||||
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -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
@@ -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..],
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
@@ -74,7 +74,7 @@ pub use crate::innerlude::{
|
||||
DynamicNode,
|
||||
Element,
|
||||
ElementId,
|
||||
ElementPath,
|
||||
ElementRef,
|
||||
EventPriority,
|
||||
Fragment,
|
||||
LazyNodes,
|
||||
|
||||
+26
-13
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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<()> {
|
||||
/*
|
||||
- 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 {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
// }
|
||||
@@ -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);
|
||||
|
||||
@@ -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)* ) };
|
||||
|
||||
@@ -68,3 +68,9 @@ pub use transition::*;
|
||||
|
||||
mod wheel;
|
||||
pub use wheel::*;
|
||||
|
||||
mod clipboard;
|
||||
pub use clipboard::*;
|
||||
|
||||
mod scroll;
|
||||
pub use scroll::*;
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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 {}
|
||||
@@ -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 {}
|
||||
@@ -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 {}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
Reference in new issue
Block a user