feat: diffing works on desktop!

This commit is contained in:
Jonathan Kelley committed 2022-11-17 22:31:14 -08:00
1 parent 30ef225812
commit 20f9957fbe
10 files changed
+176 -166

No files matched your search

+9 -2
View File
@@ -42,8 +42,10 @@ impl<'b> VirtualDom {
pub fn diff_scope(&mut self, mutations: &mut Mutations<'b>, scope: ScopeId) { pub fn diff_scope(&mut self, mutations: &mut Mutations<'b>, scope: ScopeId) {
let scope_state = &mut self.scopes[scope.0]; let scope_state = &mut self.scopes[scope.0];
let cur_arena = scope_state.current_frame(); let cur_arena = scope_state.previous_frame();
let prev_arena = scope_state.previous_frame(); let prev_arena = scope_state.current_frame();
// let cur_arena = scope_state.current_frame();
// let prev_arena = scope_state.previous_frame();
// relax the borrow checker // relax the borrow checker
let cur_arena: &BumpFrame = unsafe { std::mem::transmute(cur_arena) }; let cur_arena: &BumpFrame = unsafe { std::mem::transmute(cur_arena) };
@@ -62,8 +64,10 @@ impl<'b> VirtualDom {
); );
self.scope_stack.push(scope); self.scope_stack.push(scope);
let left = unsafe { prev_arena.load_node() }; let left = unsafe { prev_arena.load_node() };
let right = unsafe { cur_arena.load_node() }; let right = unsafe { cur_arena.load_node() };
self.diff_maybe_node(mutations, left, right); self.diff_maybe_node(mutations, left, right);
self.scope_stack.pop(); self.scope_stack.pop();
} }
@@ -105,6 +109,8 @@ impl<'b> VirtualDom {
left_template: &'b VNode<'b>, left_template: &'b VNode<'b>,
right_template: &'b VNode<'b>, right_template: &'b VNode<'b>,
) { ) {
println!("diffing {:?} and {:?}", left_template, right_template);
if left_template.template.id != right_template.template.id { if left_template.template.id != right_template.template.id {
// do a light diff of the roots nodes. // do a light diff of the roots nodes.
return; return;
@@ -125,6 +131,7 @@ impl<'b> VirtualDom {
.set(left_attr.mounted_element.get()); .set(left_attr.mounted_element.get());
if left_attr.value != right_attr.value { if left_attr.value != right_attr.value {
println!("DIFF ATTR: {:?} -> {:?}", left_attr, right_attr);
let value = "todo!()"; let value = "todo!()";
muts.push(Mutation::SetAttribute { muts.push(Mutation::SetAttribute {
id: left_attr.mounted_element.get(), id: left_attr.mounted_element.get(),
+15
View File
@@ -19,6 +19,19 @@ pub struct SuspenseBoundary {
pub waiting_on: RefCell<HashSet<SuspenseId>>, pub waiting_on: RefCell<HashSet<SuspenseId>>,
pub mutations: RefCell<Mutations<'static>>, pub mutations: RefCell<Mutations<'static>>,
pub placeholder: Cell<Option<ElementId>>, pub placeholder: Cell<Option<ElementId>>,
// whenever the suspense resolves, we call this onresolve function
// this lets us do things like putting up a loading spinner
//
// todo: we need a way of controlling whether or not a component hides itself but still processes changes
// If we run into suspense, we perform a diff, so its important that the old elements are still around.
//
// When the timer expires, I imagine a container could hide the elements and show the spinner. This, however,
// can not be
pub onresolve: Option<Box<dyn FnOnce()>>,
/// Called when
pub onstart: Option<Box<dyn FnOnce()>>,
} }
impl SuspenseBoundary { impl SuspenseBoundary {
@@ -28,6 +41,8 @@ impl SuspenseBoundary {
waiting_on: Default::default(), waiting_on: Default::default(),
mutations: RefCell::new(Mutations::new(0)), mutations: RefCell::new(Mutations::new(0)),
placeholder: Cell::new(None), placeholder: Cell::new(None),
onresolve: None,
onstart: None,
}) })
} }
} }
+22 -14
View File
@@ -1,8 +1,9 @@
use super::{waker::RcWake, Scheduler, SchedulerMsg}; use super::{waker::RcWake, Scheduler, SchedulerMsg};
use crate::ScopeId; use crate::ScopeId;
use std::cell::RefCell;
use std::future::Future; use std::future::Future;
use std::task::Context; use std::task::Context;
use std::{cell::UnsafeCell, pin::Pin, rc::Rc, task::Poll}; use std::{pin::Pin, rc::Rc, task::Poll};
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
@@ -10,23 +11,19 @@ pub struct TaskId(pub usize);
/// the task itself is the waker /// the task itself is the waker
pub(crate) struct LocalTask { pub(crate) struct LocalTask {
pub id: TaskId,
pub scope: ScopeId, pub scope: ScopeId,
pub tx: futures_channel::mpsc::UnboundedSender<SchedulerMsg>, id: TaskId,
tx: futures_channel::mpsc::UnboundedSender<SchedulerMsg>,
// todo: use rc and weak, or the bump slab instead of unsafecell task: RefCell<Pin<Box<dyn Future<Output = ()> + 'static>>>,
pub task: UnsafeCell<Pin<Box<dyn Future<Output = ()> + 'static>>>,
} }
impl LocalTask { impl LocalTask {
pub fn progress(self: &Rc<Self>) -> bool { /// Poll this task and return whether or not it is complete
pub(super) fn progress(self: &Rc<Self>) -> bool {
let waker = self.waker(); let waker = self.waker();
let mut cx = Context::from_waker(&waker); let mut cx = Context::from_waker(&waker);
// safety: the waker owns its task and everythig is single threaded match self.task.borrow_mut().as_mut().poll(&mut cx) {
let fut = unsafe { &mut *self.task.get() };
match Pin::new(fut).poll(&mut cx) {
Poll::Ready(_) => true, Poll::Ready(_) => true,
_ => false, _ => false,
} }
@@ -34,6 +31,15 @@ impl LocalTask {
} }
impl Scheduler { impl Scheduler {
/// Start a new future on the same thread as the rest of the VirtualDom.
///
/// This future will not contribute to suspense resolving, so you should primarily use this for reacting to changes
/// and long running tasks.
///
/// Whenever the component that owns this future is dropped, the future will be dropped as well.
///
/// Spawning a future onto the root scope will cause it to be dropped when the root component is dropped - which
/// will only occur when the VirtuaalDom itself has been dropped.
pub fn spawn(&self, scope: ScopeId, task: impl Future<Output = ()> + 'static) -> TaskId { pub fn spawn(&self, scope: ScopeId, task: impl Future<Output = ()> + 'static) -> TaskId {
let mut tasks = self.tasks.borrow_mut(); let mut tasks = self.tasks.borrow_mut();
let entry = tasks.vacant_entry(); let entry = tasks.vacant_entry();
@@ -42,7 +48,7 @@ impl Scheduler {
entry.insert(Rc::new(LocalTask { entry.insert(Rc::new(LocalTask {
id: task_id, id: task_id,
tx: self.sender.clone(), tx: self.sender.clone(),
task: UnsafeCell::new(Box::pin(task)), task: RefCell::new(Box::pin(task)),
scope, scope,
})); }));
@@ -53,9 +59,11 @@ impl Scheduler {
task_id task_id
} }
// drops the future /// Drop the future with the given TaskId
///
/// This does nto abort the task, so you'll want to wrap it in an aborthandle if that's important to you
pub fn remove(&self, id: TaskId) { pub fn remove(&self, id: TaskId) {
// self.tasks.borrow_mut().remove(id.0);
} }
} }
+2 -2
View File
@@ -4,10 +4,10 @@ use std::task::{Context, Poll};
use crate::{ use crate::{
factory::RenderReturn, factory::RenderReturn,
innerlude::{Mutation, Mutations, SuspenseContext}, innerlude::{Mutation, Mutations, SuspenseContext},
ScopeId, TaskId, VNode, VirtualDom, TaskId, VNode, VirtualDom,
}; };
use super::{waker::RcWake, SuspenseId, SuspenseLeaf}; use super::{waker::RcWake, SuspenseId};
impl VirtualDom { impl VirtualDom {
/// Handle notifications by tasks inside the scheduler /// Handle notifications by tasks inside the scheduler
+13 -5
View File
@@ -65,8 +65,11 @@ impl VirtualDom {
} }
pub(crate) fn run_scope(&mut self, scope_id: ScopeId) -> &RenderReturn { pub(crate) fn run_scope(&mut self, scope_id: ScopeId) -> &RenderReturn {
println!("run_scope: {:?}", scope_id);
let mut new_nodes = unsafe { let mut new_nodes = unsafe {
let scope = &mut self.scopes[scope_id.0]; let scope = &mut self.scopes[scope_id.0];
println!("run_scope: scope: {:?}", scope.render_cnt.get());
scope.hook_idx.set(0); scope.hook_idx.set(0);
// safety: due to how we traverse the tree, we know that the scope is not currently aliased // safety: due to how we traverse the tree, we know that the scope is not currently aliased
@@ -123,17 +126,22 @@ impl VirtualDom {
} }
}; };
/*
todo: use proper mutability here
right now we're aliasing the scope, which is not allowed
*/
let scope = &mut self.scopes[scope_id.0]; let scope = &mut self.scopes[scope_id.0];
let frame = match scope.render_cnt % 2 { let frame = scope.current_frame();
0 => &mut scope.node_arena_1,
1 => &mut scope.node_arena_2,
_ => unreachable!(),
};
// set the head of the bump frame // set the head of the bump frame
let alloced = frame.bump.alloc(new_nodes); let alloced = frame.bump.alloc(new_nodes);
frame.node.set(alloced); frame.node.set(alloced);
// And move the render generation forward by one
scope.render_cnt.set(scope.render_cnt.get() + 1);
// rebind the lifetime now that its stored internally // rebind the lifetime now that its stored internally
unsafe { mem::transmute(alloced) } unsafe { mem::transmute(alloced) }
} }
+3 -3
View File
@@ -43,7 +43,7 @@ impl<'a, T> std::ops::Deref for Scoped<'a, T> {
pub struct ScopeId(pub usize); pub struct ScopeId(pub usize);
pub struct ScopeState { pub struct ScopeState {
pub(crate) render_cnt: usize, pub(crate) render_cnt: Cell<usize>,
pub(crate) node_arena_1: BumpFrame, pub(crate) node_arena_1: BumpFrame,
pub(crate) node_arena_2: BumpFrame, pub(crate) node_arena_2: BumpFrame,
@@ -69,14 +69,14 @@ pub struct ScopeState {
impl ScopeState { impl ScopeState {
pub fn current_frame(&self) -> &BumpFrame { pub fn current_frame(&self) -> &BumpFrame {
match self.render_cnt % 2 { match self.render_cnt.get() % 2 {
0 => &self.node_arena_1, 0 => &self.node_arena_1,
1 => &self.node_arena_2, 1 => &self.node_arena_2,
_ => unreachable!(), _ => unreachable!(),
} }
} }
pub fn previous_frame(&self) -> &BumpFrame { pub fn previous_frame(&self) -> &BumpFrame {
match self.render_cnt % 2 { match self.render_cnt.get() % 2 {
1 => &self.node_arena_1, 1 => &self.node_arena_1,
0 => &self.node_arena_2, 0 => &self.node_arena_2,
_ => unreachable!(), _ => unreachable!(),
+54 -60
View File
@@ -9,9 +9,9 @@ use crate::{
nodes::{Template, TemplateId}, nodes::{Template, TemplateId},
scheduler::{SuspenseBoundary, SuspenseId}, scheduler::{SuspenseBoundary, SuspenseId},
scopes::{ScopeId, ScopeState}, scopes::{ScopeId, ScopeState},
Attribute, AttributeValue, Element, EventPriority, Scope, SuspenseContext, UiEvent, AttributeValue, Element, EventPriority, Scope, SuspenseContext, UiEvent,
}; };
use futures_util::{pin_mut, FutureExt, StreamExt}; use futures_util::{pin_mut, StreamExt};
use slab::Slab; use slab::Slab;
use std::rc::Rc; use std::rc::Rc;
use std::{ use std::{
@@ -244,7 +244,7 @@ impl VirtualDom {
)))); ))));
// The root component is always a suspense boundary for any async children // The root component is always a suspense boundary for any async children
// This could be unexpected, so we might rethink this behavior // This could be unexpected, so we might rethink this behavior later
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
@@ -253,26 +253,36 @@ impl VirtualDom {
dom dom
} }
/// Get the state for any scope given its ID
///
/// This is useful for inserting or removing contexts from a scope, or rendering out its root node
pub fn get_scope(&self, id: ScopeId) -> Option<&ScopeState> { pub fn get_scope(&self, id: ScopeId) -> Option<&ScopeState> {
self.scopes.get(id.0) self.scopes.get(id.0)
} }
/// Get the single scope at the top of the VirtualDom tree that will always be around
///
/// This scope has a ScopeId of 0 and is the root of the tree
pub fn base_scope(&self) -> &ScopeState { pub fn base_scope(&self) -> &ScopeState {
self.scopes.get(0).unwrap() self.scopes.get(0).unwrap()
} }
/// Build the virtualdom with a global context inserted into the base scope /// Build the virtualdom with a global context inserted into the base scope
///
/// This is useful for what is essentially dependency injection, when building the app
pub fn with_root_context<T: Clone + 'static>(self, context: T) -> Self { pub fn with_root_context<T: Clone + 'static>(self, context: T) -> Self {
self.base_scope().provide_context(context); self.base_scope().provide_context(context);
self self
} }
fn mark_dirty_scope(&mut self, id: ScopeId) { /// Manually mark a scope as requiring a re-render
pub 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 });
} }
fn is_scope_suspended(&self, id: ScopeId) -> bool { /// Determine whether or not a scope is currently in a suspended state
pub fn is_scope_suspended(&self, id: ScopeId) -> bool {
!self.scopes[id.0] !self.scopes[id.0]
.consume_context::<SuspenseContext>() .consume_context::<SuspenseContext>()
.unwrap() .unwrap()
@@ -281,35 +291,29 @@ impl VirtualDom {
.is_empty() .is_empty()
} }
/// Returns true if there is any suspended work left to be done. /// Determine is the tree is at all suspended. Used by SSR and other outside mechanisms to determine if the tree is
/// ready to be rendered.
pub fn has_suspended_work(&self) -> bool { pub fn has_suspended_work(&self) -> bool {
!self.scheduler.leaves.borrow().is_empty() !self.scheduler.leaves.borrow().is_empty()
} }
/// Call a listener inside the VirtualDom with data from outside the VirtualDom. /// Call a listener inside the VirtualDom with data from outside the VirtualDom.
/// ///
/// This method will identify the appropriate element /// This method will identify the appropriate element. The data must match up with the listener delcared. Note that
/// /// this method does not give any indication as to the success of the listener call. If the listener is not found,
/// /// nothing will happen.
///
///
///
/// ///
/// It is up to the listeners themselves to mark nodes as dirty.
/// ///
/// If you have multiple events, you can call this method multiple times before calling "render_with_deadline"
pub fn handle_event( pub fn handle_event(
&mut self, &mut self,
name: &str, name: &str,
data: Rc<dyn Any>, data: Rc<dyn Any>,
element: ElementId, element: ElementId,
bubbles: bool, bubbles: bool,
// todo: priority is helpful when scheduling work around suspense, but we don't currently use it
_priority: EventPriority, _priority: EventPriority,
) { ) {
let uievent = UiEvent {
bubbles: Rc::new(Cell::new(bubbles)),
data,
};
/* /*
------------------------ ------------------------
The algorithm works by walking through the list of dynamic attributes, checking their paths, and breaking when The algorithm works by walking through the list of dynamic attributes, checking their paths, and breaking when
@@ -321,37 +325,35 @@ impl VirtualDom {
If we wanted to do capturing, then we would accumulate all the listeners and call them in reverse order. If we wanted to do capturing, then we would accumulate all the listeners and call them in reverse order.
---------------------- ----------------------
| <-- yes (is ascendant) | <-- yes (is ascendant)
| | | <-- no (is not ascendant) | | | <-- no (is not direct ascendant)
| | <-- yes (is ascendant) | | <-- yes (is ascendant)
| | | | | <--- target element, break early | | | | | <--- target element, break early, don't check other listeners
| | | <-- no, broke early | | | <-- no, broke early
| <-- no, broke early | <-- no, broke early
*/ */
let mut parent_path = self.elements.get(element.0); let mut parent_path = self.elements.get(element.0);
let mut listeners = vec![]; let mut listeners = vec![];
// We will clone this later. The data itself is wrapped in RC to be used in callbacks if required
let uievent = UiEvent {
bubbles: Rc::new(Cell::new(bubbles)),
data,
};
// Loop through each dynamic attribute in this template before moving up to the template's parent.
while let Some(el_ref) = parent_path { while let Some(el_ref) = parent_path {
// safety: we maintain references of all vnodes in the element slab
let template = unsafe { &*el_ref.template }; let template = unsafe { &*el_ref.template };
let target_path = el_ref.path; let target_path = el_ref.path;
let mut attrs = template.dynamic_attrs.iter().enumerate(); for (idx, attr) in template.dynamic_attrs.iter().enumerate() {
fn is_path_ascendant(small: &[u8], big: &[u8]) -> bool {
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()] small.len() >= big.len() && small == &big[..small.len()]
} }
let this_path = template.template.attr_paths[idx]; let this_path = template.template.attr_paths[idx];
println!( // listeners are required to be prefixed with "on", but they come back to the virtualdom with that missing
"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) { if &attr.name[2..] == name && is_path_ascendant(&target_path, &this_path) {
listeners.push(&attr.value); listeners.push(&attr.value);
@@ -367,11 +369,11 @@ impl VirtualDom {
} }
} }
// Now that we've accumulated all the parent attributes for the target element, call them in reverse order
// We check the bubble state between each call to see if the event has been stopped from bubbling
for listener in listeners.drain(..).rev() { for listener in listeners.drain(..).rev() {
if let AttributeValue::Listener(listener) = listener { if let AttributeValue::Listener(listener) = listener {
listener.borrow_mut()(uievent.clone()); listener.borrow_mut()(uievent.clone());
// Break if the event doesn't bubble
if !uievent.bubbles.get() { if !uievent.bubbles.get() {
return; return;
} }
@@ -452,13 +454,17 @@ impl VirtualDom {
pub fn rebuild<'a>(&'a mut self) -> Mutations<'a> { pub fn rebuild<'a>(&'a mut self) -> Mutations<'a> {
let mut mutations = Mutations::new(0); let mut mutations = Mutations::new(0);
let root_node = unsafe { self.run_scope_extend(ScopeId(0)) }; match unsafe { self.run_scope_extend(ScopeId(0)) } {
match root_node { // Rebuilding implies we append the created elements to the root
RenderReturn::Sync(Some(node)) => { RenderReturn::Sync(Some(node)) => {
let m = self.create_scope(ScopeId(0), &mut mutations, node); let m = self.create_scope(ScopeId(0), &mut mutations, node);
mutations.push(Mutation::AppendChildren { m }); mutations.push(Mutation::AppendChildren { m });
} }
RenderReturn::Sync(None) => {} // If nothing was rendered, then insert a placeholder element instead
RenderReturn::Sync(None) => {
mutations.push(Mutation::CreatePlaceholder { id: ElementId(1) });
mutations.push(Mutation::AppendChildren { m: 1 });
}
RenderReturn::Async(_) => unreachable!("Root scope cannot be an async component"), RenderReturn::Async(_) => unreachable!("Root scope cannot be an async component"),
} }
@@ -475,7 +481,9 @@ impl VirtualDom {
// Now run render with deadline but dont even try to poll any async tasks // Now run render with deadline but dont even try to poll any async tasks
let fut = self.render_with_deadline(std::future::ready(())); let fut = self.render_with_deadline(std::future::ready(()));
pin_mut!(fut); pin_mut!(fut);
match fut.poll_unpin(&mut cx) {
// The root component is not allowed to be async
match fut.poll(&mut cx) {
std::task::Poll::Ready(mutations) => mutations, std::task::Poll::Ready(mutations) => mutations,
std::task::Poll::Pending => panic!("render_immediate should never return pending"), std::task::Poll::Pending => panic!("render_immediate should never return pending"),
} }
@@ -491,20 +499,20 @@ impl VirtualDom {
deadline: impl Future<Output = ()>, deadline: impl Future<Output = ()>,
) -> Mutations<'a> { ) -> Mutations<'a> {
use futures_util::future::{select, Either}; use futures_util::future::{select, Either};
pin_mut!(deadline);
let mut mutations = Mutations::new(0); let mut mutations = Mutations::new(0);
pin_mut!(deadline);
loop { loop {
// first, unload any complete suspense trees // first, unload any complete suspense trees
for finished_fiber in self.finished_fibers.drain(..) { for finished_fiber in self.finished_fibers.drain(..) {
let scope = &mut self.scopes[finished_fiber.0]; let scope = &mut self.scopes[finished_fiber.0];
let context = scope.has_context::<SuspenseContext>().unwrap(); let context = scope.has_context::<SuspenseContext>().unwrap();
println!("unloading suspense tree {:?}", context.mutations);
mutations.extend(context.mutations.borrow_mut().template_mutations.drain(..)); mutations.extend(context.mutations.borrow_mut().template_mutations.drain(..));
mutations.extend(context.mutations.borrow_mut().drain(..)); mutations.extend(context.mutations.borrow_mut().drain(..));
// TODO: count how many nodes are on the stack?
mutations.push(Mutation::ReplaceWith { mutations.push(Mutation::ReplaceWith {
id: context.placeholder.get().unwrap(), id: context.placeholder.get().unwrap(),
m: 1, m: 1,
@@ -525,36 +533,22 @@ impl VirtualDom {
// Wait for suspense, or a deadline // Wait for suspense, or a deadline
if self.dirty_scopes.is_empty() { if self.dirty_scopes.is_empty() {
// If there's no suspense, then we have no reason to wait
if self.scheduler.leaves.borrow().is_empty() { if self.scheduler.leaves.borrow().is_empty() {
return mutations; return mutations;
} }
let (work, deadline) = (self.wait_for_work(), &mut deadline); // Poll the suspense leaves in the meantime
let work = self.wait_for_work();
pin_mut!(work); pin_mut!(work);
if let Either::Left((_, _)) = select(deadline, work).await { // If the deadline is exceded (left) then we should return the mutations we have
if let Either::Left((_, _)) = select(&mut deadline, work).await {
return mutations; return mutations;
} }
} }
} }
} }
// 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 { impl Drop for VirtualDom {
+2 -68
View File
@@ -1,12 +1,8 @@
use crate::desktop_context::{DesktopContext, UserWindowEvent}; use crate::desktop_context::{DesktopContext, UserWindowEvent};
use crate::events::{decode_event, EventMessage};
use dioxus_core::*; use dioxus_core::*;
use dioxus_html::events::*;
use futures_channel::mpsc::UnboundedReceiver; use futures_channel::mpsc::UnboundedReceiver;
use futures_util::StreamExt; 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,
@@ -19,25 +15,6 @@ 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>>>,
@@ -86,9 +63,7 @@ impl DesktopController {
if let Ok(value) = serde_json::from_value::<EventMessage>(json_value) { if let Ok(value) = serde_json::from_value::<EventMessage>(json_value) {
let name = value.event.clone(); let name = value.event.clone();
let el_id = ElementId(value.mounted_dom_id); let el_id = ElementId(value.mounted_dom_id);
let evt = decode_event(value); if let Some(evt) = decode_event(value) {
if let Some(evt) = evt {
dom.handle_event(&name, evt, el_id, true, EventPriority::Medium); dom.handle_event(&name, evt, el_id, true, EventPriority::Medium);
} }
} }
@@ -143,44 +118,3 @@ 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)
}
+55 -11
View File
@@ -33,17 +33,61 @@ pub(crate) fn parse_ipc_message(payload: &str) -> Option<IpcMessage> {
} }
} }
#[derive(Deserialize, Serialize)] macro_rules! match_data {
struct ImEvent { (
event: String, $m:ident;
mounted_dom_id: ElementId, $name:ident;
contents: serde_json::Value, $(
$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 fn make_synthetic_event(name: &str, val: serde_json::Value) -> Option<Rc<dyn Any>> { #[derive(Deserialize)]
// // right now we don't support the datatransfer in Drag pub struct EventMessage {
// type DragData = MouseData; pub contents: serde_json::Value,
// type ProgressData = MediaData; pub event: String,
pub mounted_dom_id: usize,
}
// Some(res) pub fn decode_event(value: EventMessage) -> Option<Rc<dyn Any>> {
// } let val = value.contents;
let name = value.event.as_str();
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 -1
View File
@@ -311,7 +311,7 @@ export class Interpreter {
this.CreateElementNs(edit.name, edit.id, edit.ns); this.CreateElementNs(edit.name, edit.id, edit.ns);
break; break;
case "SetText": case "SetText":
this.SetText(edit.id, edit.text); this.SetText(edit.id, edit.value);
break; break;
case "SetAttribute": case "SetAttribute":
this.SetAttribute(edit.id, edit.name, edit.value, edit.ns); this.SetAttribute(edit.id, edit.name, edit.value, edit.ns);