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 std::collections::HashMap;
@@ -19,8 +15,8 @@ struct ListBreeds {
}
async fn app_root(cx: Scope<'_>) -> Element {
let breed = use_state(&cx, || "deerhound".to_string());
let breeds = use_future(&cx, (), |_| async move {
let breed = use_state(cx, || "deerhound".to_string());
let breeds = use_future!(cx, || async move {
reqwest::get("https://dog.ceo/api/breeds/list/all")
.await
.unwrap()
@@ -45,7 +41,9 @@ async fn app_root(cx: Scope<'_>) -> Element {
}
}
}
div { flex: "50%", Breed { breed: breed.to_string() } }
div { flex: "50%",
breed_pic { breed: breed.to_string() }
}
}
}
}),
@@ -59,10 +57,8 @@ struct DogApi {
}
#[inline_props]
async fn Breed(cx: Scope, breed: String) -> Element {
println!("Rendering Breed: {}", breed);
let fut = use_future(&cx, (breed,), |(breed,)| async move {
async fn breed_pic(cx: Scope, breed: String) -> Element {
let fut = use_future!(cx, |breed| async move {
reqwest::get(format!("https://dog.ceo/api/breed/{}/images/random", breed))
.await
.unwrap()
@@ -70,11 +66,7 @@ async fn Breed(cx: Scope, breed: String) -> Element {
.await
});
let resp = fut.await;
println!("achieved results!");
match resp {
match fut.await {
Ok(resp) => cx.render(rsx! {
div {
button {
@@ -88,6 +80,6 @@ async fn Breed(cx: Scope, breed: String) -> Element {
}
}
}),
Err(e) => cx.render(rsx! { div { "loading dogs failed" } }),
Err(_) => cx.render(rsx! { div { "loading dogs failed" } }),
}
}
+2 -8
View File
@@ -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
View File
@@ -2,7 +2,6 @@
//!
//! There is some conversion happening when input types are checkbox/radio/select/textarea etc.
use dioxus::events::FormData;
use dioxus::prelude::*;
fn main() {
@@ -29,7 +28,6 @@ const FIELDS: &[(&str, &str)] = &[
("text", ""),
("time", ""),
("url", ""),
//
// less supported things
("hidden", ""),
("month", ""), // degrades to text most of the time, but works properly as "value'"
@@ -115,7 +113,7 @@ fn app(cx: Scope) -> Element {
}
}
FIELDS.iter().map(|(field, value)| rsx!(
FIELDS.iter().map(|(field, value)| rsx! {
div {
input {
id: "{field}",
@@ -132,8 +130,7 @@ fn app(cx: Scope) -> Element {
}
br {}
}
))
})
}
})
}
+17 -7
View File
@@ -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
View File
@@ -1,7 +1,5 @@
use std::ops::Bound;
use crate::factory::RenderReturn;
use crate::innerlude::{Mutations, SuspenseContext, SuspenseId};
use crate::innerlude::{Mutations, SuspenseContext};
use crate::mutations::Mutation;
use crate::mutations::Mutation::*;
use crate::nodes::VNode;
@@ -75,13 +73,13 @@ impl VirtualDom {
DynamicNode::Text {
id: slot, value, ..
} => {
let id = self.next_element(template);
let id = self.next_element(template, template.template.node_paths[*id]);
slot.set(id);
mutations.push(CreateTextNode { value, id });
1
}
DynamicNode::Placeholder(slot) => {
let id = self.next_element(template);
let id = self.next_element(template, template.template.node_paths[*id]);
slot.set(id);
mutations.push(CreatePlaceholder { id });
1
@@ -95,7 +93,7 @@ impl VirtualDom {
while let Some((mut attr_id, path)) =
dynamic_attrs.next_if(|(_, p)| p[0] == root_idx as u8)
{
let id = self.next_element(template);
let id = self.next_element(template, template.template.attr_paths[attr_id]);
mutations.push(AssignId {
path: &path[1..],
id,
@@ -118,7 +116,8 @@ impl VirtualDom {
id,
}),
AttributeValue::Listener(_) => mutations.push(NewEventListener {
event_name: attribute.name,
// all listeners start with "on"
event_name: &attribute.name[2..],
scope: cur_scope,
id,
}),
@@ -178,8 +177,8 @@ impl VirtualDom {
) {
match *node {
// Todo: create the children's template
TemplateNode::Dynamic(_) => {
let id = self.next_element(template);
TemplateNode::Dynamic(idx) => {
let id = self.next_element(template, template.template.node_paths[idx]);
mutations.push(CreatePlaceholder { id })
}
TemplateNode::Text(value) => mutations.push(CreateStaticText { value }),
@@ -194,7 +193,7 @@ impl VirtualDom {
tag,
inner_opt,
} => {
let id = self.next_element(template);
let id = self.next_element(template, &[]); // never gets referenced, empty path is fine, I think?
mutations.push(CreateElement {
name: tag,
@@ -239,7 +238,7 @@ impl VirtualDom {
) -> usize {
match &node {
DynamicNode::Text { id, value, .. } => {
let new_id = self.next_element(template);
let new_id = self.next_element(template, template.template.node_paths[idx]);
id.set(new_id);
mutations.push(HydrateText {
id: new_id,
@@ -269,7 +268,7 @@ impl VirtualDom {
}
RenderReturn::Async(_) => {
let new_id = self.next_element(template);
let new_id = self.next_element(template, template.template.node_paths[idx]);
placeholder.set(Some(new_id));
self.scopes[scope.0].placeholder.set(Some(new_id));
@@ -305,7 +304,8 @@ impl VirtualDom {
self.scopes[scope.0].has_context::<SuspenseContext>()
{
// Since this is a boundary, use it as a placeholder
let new_id = self.next_element(template);
let new_id =
self.next_element(template, template.template.node_paths[idx]);
placeholder.set(Some(new_id));
self.scopes[scope.0].placeholder.set(Some(new_id));
mutations.push(AssignId {
@@ -350,7 +350,7 @@ impl VirtualDom {
}
DynamicNode::Placeholder(slot) => {
let id = self.next_element(template);
let id = self.next_element(template, template.template.node_paths[idx]);
slot.set(id);
mutations.push(AssignId {
path: &template.template.node_paths[idx][1..],
+1 -1
View File
@@ -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
View File
@@ -6,21 +6,30 @@ use std::{
rc::Rc,
};
pub struct UiEvent<T: 'static> {
pub(crate) bubble_state: Cell<bool>,
pub struct UiEvent<T: 'static + ?Sized> {
pub(crate) bubbles: Rc<Cell<bool>>,
pub(crate) data: Rc<T>,
}
impl<T> Clone for UiEvent<T> {
impl UiEvent<dyn Any> {
pub fn downcast<T: 'static + Sized>(self) -> Option<UiEvent<T>> {
Some(UiEvent {
bubbles: self.bubbles,
data: self.data.downcast().ok()?,
})
}
}
impl<T: ?Sized> Clone for UiEvent<T> {
fn clone(&self) -> Self {
Self {
bubble_state: self.bubble_state.clone(),
bubbles: self.bubbles.clone(),
data: self.data.clone(),
}
}
}
impl<T> UiEvent<T> {
pub fn cancel_bubble(&self) {
self.bubble_state.set(false);
self.bubbles.set(false);
}
}
@@ -35,7 +44,7 @@ impl<T> std::ops::Deref for UiEvent<T> {
impl<T: Debug> std::fmt::Debug for UiEvent<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("UiEvent")
.field("bubble_state", &self.bubble_state)
.field("bubble_state", &self.bubbles)
.field("data", &self.data)
.finish()
}
@@ -93,18 +102,6 @@ pub enum EventPriority {
Low = 0,
}
// ensures a strict descendant relationship
// returns false if the paths are equal
pub fn is_path_ascendant(small: &[u8], big: &[u8]) -> bool {
if small.len() >= big.len() {
return false;
}
small == &big[..small.len()]
}
pub type InternalHandler<'bump> = &'bump RefCell<Option<InternalListenerCallback<'bump>>>;
type InternalListenerCallback<'bump> = BumpBox<'bump, dyn FnMut(&'bump dyn Any) + 'bump>;
type ExternalListenerCallback<'bump, T> = BumpBox<'bump, dyn FnMut(T) + 'bump>;
/// The callback type generated by the `rsx!` macro when an `on` field is specified for components.
+1 -1
View File
@@ -74,7 +74,7 @@ pub use crate::innerlude::{
DynamicNode,
Element,
ElementId,
ElementPath,
ElementRef,
EventPriority,
Fragment,
LazyNodes,
+26 -13
View File
@@ -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))
}
+96 -65
View File
@@ -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 {
+3 -1
View File
@@ -27,12 +27,14 @@ tokio = { version = "1.16.1", features = [
"rt-multi-thread",
"rt",
"time",
"macros",
], optional = true, default-features = false }
webbrowser = "0.8.0"
infer = "0.9.0"
infer = "0.11.0"
dunce = "1.0.2"
interprocess = { version = "1.1.1" }
futures-util = "0.3.25"
[target.'cfg(target_os = "macos")'.dependencies]
core-foundation = "0.9.3"
+93 -31
View File
@@ -1,10 +1,17 @@
use crate::desktop_context::{DesktopContext, UserWindowEvent};
use dioxus_core::*;
use dioxus_html::events::*;
use futures_channel::mpsc::UnboundedReceiver;
use futures_util::StreamExt;
use serde::Deserialize;
use serde_json::from_value;
use std::any::Any;
use std::rc::Rc;
use std::{
collections::HashMap,
sync::Arc,
sync::{atomic::AtomicBool, Mutex},
time::Duration,
};
use wry::{
self,
@@ -12,6 +19,25 @@ use wry::{
webview::WebView,
};
macro_rules! match_data {
(
$m:ident;
$name:ident;
$(
$tip:ty => $($mname:literal)|* ;
)*
) => {
match $name {
$( $($mname)|* => {
println!("casting to type {:?}", std::any::TypeId::of::<$tip>());
let val: $tip = from_value::<$tip>($m).ok()?;
Rc::new(val) as Rc<dyn Any>
})*
_ => return None,
}
};
}
pub(super) struct DesktopController {
pub(super) webviews: HashMap<WindowId, WebView>,
pub(super) pending_edits: Arc<Mutex<Vec<String>>>,
@@ -26,6 +52,7 @@ impl DesktopController {
root: Component<P>,
props: P,
proxy: EventLoopProxy<UserWindowEvent>,
mut event_rx: UnboundedReceiver<serde_json::Value>,
) -> Self {
let edit_queue = Arc::new(Mutex::new(Vec::new()));
@@ -33,55 +60,51 @@ impl DesktopController {
let desktop_context_proxy = proxy.clone();
std::thread::spawn(move || {
// We create the runtime as multithreaded, so you can still "spawn" onto multiple threads
// We create the runtime as multithreaded, so you can still "tokio::spawn" onto multiple threads
// I'd personally not require tokio to be built-in to Dioxus-Desktop, but the DX is worse without it
let runtime = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap();
runtime.block_on(async move {
println!("starting vdom");
let mut dom = VirtualDom::new_with_props(root, props);
let window_context = DesktopContext::new(desktop_context_proxy);
dom.base_scope().provide_context(window_context);
let mut dom = VirtualDom::new_with_props(root, props)
.with_root_context(DesktopContext::new(desktop_context_proxy));
{
let edits = dom.rebuild();
let mut queue = edit_queue.lock().unwrap();
queue.push(serde_json::to_string(&edits.template_mutations).unwrap());
queue.push(serde_json::to_string(&edits.edits).unwrap());
proxy.send_event(UserWindowEvent::Update).unwrap();
}
// println!("got muts: {:#?}", edits);
{
let mut queue = edit_queue.lock().unwrap();
queue.push(serde_json::to_string(&edits.template_mutations).unwrap());
queue.push(serde_json::to_string(&edits.edits).unwrap());
proxy.send_event(UserWindowEvent::Update).unwrap();
drop(queue);
}
runtime.block_on(async move {
loop {
// todo: add the channel of the event loop in
tokio::select! {
_ = dom.wait_for_work() => {}
Some(json_value) = event_rx.next() => {
if let Ok(value) = serde_json::from_value::<EventMessage>(json_value) {
let name = value.event.clone();
let el_id = ElementId(value.mounted_dom_id);
let evt = decode_event(value);
if let Some(evt) = evt {
dom.handle_event(&name, evt, el_id, true, EventPriority::Medium);
}
}
}
}
let muts = dom
.render_with_deadline(tokio::time::sleep(
tokio::time::Duration::from_millis(16),
))
.render_with_deadline(tokio::time::sleep(Duration::from_millis(16)))
.await;
{
let mut queue = edit_queue.lock().unwrap();
queue.push(serde_json::to_string(&muts.template_mutations).unwrap());
queue.push(serde_json::to_string(&muts.edits).unwrap());
drop(queue);
let _ = proxy.send_event(UserWindowEvent::Update);
}
let _ = proxy.send_event(UserWindowEvent::Update);
}
})
});
@@ -113,8 +136,6 @@ impl DesktopController {
let (_id, view) = self.webviews.iter_mut().next().unwrap();
println!("sending edits {:#?}", new_queue);
for edit in new_queue.drain(..) {
view.evaluate_script(&format!("window.interpreter.handleEdits({})", edit))
.unwrap();
@@ -122,3 +143,44 @@ impl DesktopController {
}
}
}
#[derive(Deserialize)]
struct EventMessage {
contents: serde_json::Value,
event: String,
mounted_dom_id: usize,
}
fn decode_event(value: EventMessage) -> Option<Rc<dyn Any>> {
let val = value.contents;
let name = value.event.as_str();
let el_id = ElementId(value.mounted_dom_id);
type DragData = MouseData;
let evt = match_data! { val; name;
MouseData => "click" | "contextmenu" | "dblclick" | "doubleclick" | "mousedown" | "mouseenter" | "mouseleave" | "mousemove" | "mouseout" | "mouseover" | "mouseup";
ClipboardData => "copy" | "cut" | "paste";
CompositionData => "compositionend" | "compositionstart" | "compositionupdate";
KeyboardData => "keydown" | "keypress" | "keyup";
FocusData => "blur" | "focus" | "focusin" | "focusout";
FormData => "change" | "input" | "invalid" | "reset" | "submit";
DragData => "drag" | "dragend" | "dragenter" | "dragexit" | "dragleave" | "dragover" | "dragstart" | "drop";
PointerData => "pointerlockchange" | "pointerlockerror" | "pointerdown" | "pointermove" | "pointerup" | "pointerover" | "pointerout" | "pointerenter" | "pointerleave" | "gotpointercapture" | "lostpointercapture";
SelectionData => "selectstart" | "selectionchange" | "select";
TouchData => "touchcancel" | "touchend" | "touchmove" | "touchstart";
ScrollData => "scroll";
WheelData => "wheel";
MediaData => "abort" | "canplay" | "canplaythrough" | "durationchange" | "emptied"
| "encrypted" | "ended" | "interruptbegin" | "interruptend" | "loadeddata"
| "loadedmetadata" | "loadstart" | "pause" | "play" | "playing" | "progress"
| "ratechange" | "seeked" | "seeking" | "stalled" | "suspend" | "timeupdate"
| "volumechange" | "waiting" | "error" | "load" | "loadend" | "timeout";
AnimationData => "animationstart" | "animationend" | "animationiteration";
TransitionData => "transitionend";
ToggleData => "toggle";
// ImageData => "load" | "error";
// OtherData => "abort" | "afterprint" | "beforeprint" | "beforeunload" | "hashchange" | "languagechange" | "message" | "offline" | "online" | "pagehide" | "pageshow" | "popstate" | "rejectionhandled" | "storage" | "unhandledrejection" | "unload" | "userproximity" | "vrdisplayactivate" | "vrdisplayblur" | "vrdisplayconnect" | "vrdisplaydeactivate" | "vrdisplaydisconnect" | "vrdisplayfocus" | "vrdisplaypointerrestricted" | "vrdisplaypointerunrestricted" | "vrdisplaypresentchange";
};
Some(evt)
}
+11 -4
View File
@@ -1,12 +1,11 @@
//! Convert a serialized event to an event trigger
use std::any::Any;
use std::sync::Arc;
use dioxus_core::ElementId;
use dioxus_core::EventPriority;
use dioxus_html::events::*;
use serde::{Deserialize, Serialize};
use serde_json::from_value;
use std::any::Any;
use std::rc::Rc;
#[derive(Deserialize, Serialize)]
pub(crate) struct IpcMessage {
@@ -40,3 +39,11 @@ struct ImEvent {
mounted_dom_id: ElementId,
contents: serde_json::Value,
}
// pub fn make_synthetic_event(name: &str, val: serde_json::Value) -> Option<Rc<dyn Any>> {
// // right now we don't support the datatransfer in Drag
// type DragData = MouseData;
// type ProgressData = MediaData;
// Some(res)
// }
+6 -5
View File
@@ -14,6 +14,7 @@ mod protocol;
use desktop_context::UserWindowEvent;
pub use desktop_context::{use_eval, use_window, DesktopContext};
use futures_channel::mpsc::unbounded;
pub use wry;
pub use wry::application as tao;
@@ -100,7 +101,9 @@ pub fn launch_cfg(root: Component, config_builder: Config) {
pub fn launch_with_props<P: 'static + Send>(root: Component<P>, props: P, mut cfg: Config) {
let event_loop = EventLoop::with_user_event();
let mut desktop = DesktopController::new_on_tokio(root, props, event_loop.create_proxy());
let (event_tx, event_rx) = unbounded();
let mut desktop =
DesktopController::new_on_tokio(root, props, event_loop.create_proxy(), event_rx);
let proxy = event_loop.create_proxy();
// We assume that if the icon is None, then the user just didnt set it
@@ -135,6 +138,7 @@ pub fn launch_with_props<P: 'static + Send>(root: Component<P>, props: P, mut cf
let custom_head = cfg.custom_head.clone();
let resource_dir = cfg.resource_dir.clone();
let index_file = cfg.custom_index.clone();
let event_tx = event_tx.clone();
let mut webview = WebViewBuilder::new(window)
.unwrap()
@@ -145,10 +149,7 @@ pub fn launch_with_props<P: 'static + Send>(root: Component<P>, props: P, mut cf
parse_ipc_message(&payload)
.map(|message| match message.method() {
"user_event" => {
println!("user event!");
// let event = trigger_from_serialized(message.params());
// log::trace!("User event: {:?}", event);
// sender.unbounded_send(SchedulerMsg::Event(event)).unwrap();
_ = event_tx.unbounded_send(message.params());
}
"initialize" => {
is_ready.store(true, std::sync::atomic::Ordering::Relaxed);
+1
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, H = h,);
/// A helper macro that merges uses the closure syntax to elaborate the dependency array
#[macro_export]
macro_rules! use_future {
($cx:ident, || $($rest:tt)*) => { use_future( $cx, (), |_| $($rest)* ) };
+6
View File
@@ -68,3 +68,9 @@ pub use transition::*;
mod 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))]
#[derive(Debug, Clone)]
pub struct AnimationEvent {
pub struct AnimationData {
pub animation_name: String,
pub pseudo_element: String,
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::{
ClientPoint, Coordinates, ElementPoint, LinesVector, PagePoint, PagesVector, PixelsVector,
ScreenPoint, WheelDelta,
};
use crate::input_data::{
decode_key_location, decode_mouse_button_set, encode_key_location, encode_mouse_button_set,
MouseButton, MouseButtonSet,
};
use dioxus_core::{NodeFactory, UiEvent};
use euclid::UnknownUnit;
use crate::input_data::{decode_key_location, encode_key_location};
use dioxus_core::UiEvent;
use keyboard_types::{Code, Key, Location, Modifiers};
use std::collections::HashMap;
use std::convert::TryInto;
use std::fmt::{Debug, Formatter};
use std::str::FromStr;
+6
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) {
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;
}