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 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" } }),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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
@@ -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 {}
|
||||||
}
|
}
|
||||||
))
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -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
@@ -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..],
|
||||||
|
|||||||
@@ -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
@@ -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.
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ pub use crate::innerlude::{
|
|||||||
DynamicNode,
|
DynamicNode,
|
||||||
Element,
|
Element,
|
||||||
ElementId,
|
ElementId,
|
||||||
ElementPath,
|
ElementRef,
|
||||||
EventPriority,
|
EventPriority,
|
||||||
Fragment,
|
Fragment,
|
||||||
LazyNodes,
|
LazyNodes,
|
||||||
|
|||||||
+26
-13
@@ -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))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
// }
|
||||||
@@ -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);
|
||||||
|
|||||||
@@ -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)* ) };
|
||||||
|
|||||||
@@ -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::*;
|
||||||
@@ -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,
|
||||||
|
|||||||
@@ -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::{
|
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;
|
||||||
|
|||||||
@@ -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) {
|
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;
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user