Ver código fonte

wip: better desktop support

Jonathan Kelley 3 anos atrás
pai
commit
25a8411

+ 1 - 0
packages/core/Cargo.toml

@@ -39,6 +39,7 @@ indexmap = "1.7.0"
 
 # Serialize the Edits for use in Webview/Liveview instances
 serde = { version = "1", features = ["derive"], optional = true }
+backtrace = "0.3.63"
 
 [dev-dependencies]
 anyhow = "1.0.42"

+ 22 - 21
packages/core/src/nodes.rs

@@ -643,9 +643,9 @@ impl<'a> NodeFactory<'a> {
         }
     }
 
-    pub fn fragment_from_iter(
+    pub fn fragment_from_iter<'b, 'c>(
         self,
-        node_iter: impl IntoIterator<Item = impl IntoVNode<'a>>,
+        node_iter: impl IntoIterator<Item = impl IntoVNode<'a> + 'c> + 'b,
     ) -> VNode<'a> {
         let bump = self.bump;
         let mut nodes = bumpalo::collections::Vec::new_in(bump);
@@ -662,24 +662,25 @@ impl<'a> NodeFactory<'a> {
 
         let children = nodes.into_bump_slice();
 
-        // TODO
-        // We need a dedicated path in the rsx! macro that will trigger the "you need keys" warning
-        //
-        // if cfg!(debug_assertions) {
-        //     if children.len() > 1 {
-        //         if children.last().unwrap().key().is_none() {
-        //             log::error!(
-        //                 r#"
-        // Warning: Each child in an array or iterator should have a unique "key" prop.
-        // Not providing a key will lead to poor performance with lists.
-        // See docs.rs/dioxus for more information.
-        // ---
-        // To help you identify where this error is coming from, we've generated a backtrace.
-        //                         "#,
-        //             );
-        //         }
-        //     }
-        // }
+        // todo: add a backtrace
+        if cfg!(debug_assertions) && children.len() > 1 && children.last().unwrap().key().is_none()
+        {
+            // todo: rsx! calls get turned into fragments which means they always trip this error
+            //
+            //
+            // use backtrace::Backtrace;
+            // let bt = Backtrace::new();
+
+            log::error!(
+                r#"
+                Warning: Each child in an array or iterator should have a unique "key" prop.
+                Not providing a key will lead to poor performance with lists.
+                See docs.rs/dioxus for more information.
+                -------------
+                "#,
+                // bt
+            );
+        }
 
         VNode::Fragment(VFragment {
             children,
@@ -820,7 +821,7 @@ impl<'a> IntoVNode<'a> for Option<LazyNodes<'a, '_>> {
     }
 }
 
-impl<'a> IntoVNode<'a> for LazyNodes<'a, '_> {
+impl<'a, 'b> IntoVNode<'a> for LazyNodes<'a, 'b> {
     fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> {
         self.call(cx)
     }

+ 13 - 3
packages/core/src/scope.rs

@@ -196,6 +196,7 @@ impl Scope {
         let chan = self.sender.clone();
         let id = self.scope_id();
         Rc::new(move || {
+            log::debug!("set on channel an update for scope {:?}", id);
             let _ = chan.unbounded_send(SchedulerMsg::Immediate(id));
         })
     }
@@ -303,10 +304,18 @@ impl Scope {
 
     /// Pushes the future onto the poll queue to be polled
     /// The future is forcibly dropped if the component is not ready by the next render
-    pub fn push_task<'src, F: Future<Output = ()> + 'src>(
+    pub fn push_task<'src, F: Future<Output = ()>>(
         &'src self,
-        mut fut: impl FnOnce() -> F + 'src,
-    ) -> usize {
+        fut: impl FnOnce() -> F + 'src,
+    ) -> usize
+    where
+        F::Output: 'src,
+        F: 'src,
+    {
+        self.sender
+            .unbounded_send(SchedulerMsg::TaskPushed(self.our_arena_idx))
+            .unwrap();
+
         // allocate the future
         let fut = fut();
         let fut: &mut dyn Future<Output = ()> = self.bump().alloc(fut);
@@ -318,6 +327,7 @@ impl Scope {
         let self_ref_fut = unsafe { std::mem::transmute(boxed_fut) };
 
         let mut items = self.items.borrow_mut();
+
         items.tasks.push(self_ref_fut);
         items.tasks.len() - 1
     }

+ 2 - 4
packages/core/src/scopearena.rs

@@ -133,8 +133,6 @@ impl ScopeArena {
 
             let any_item = self.scopes.borrow_mut().insert(new_scope_id, scope);
             debug_assert!(any_item.is_none());
-
-            new_scope_id
         } else {
             let (node_capacity, hook_capacity) = {
                 let heuristics = self.heuristics.borrow();
@@ -196,9 +194,9 @@ impl ScopeArena {
 
             let any_item = self.scopes.borrow_mut().insert(new_scope_id, scope);
             debug_assert!(any_item.is_none());
-
-            new_scope_id
         }
+
+        new_scope_id
     }
 
     pub fn try_remove(&self, id: &ScopeId) -> Option<()> {

+ 89 - 104
packages/core/src/virtual_dom.rs

@@ -260,87 +260,45 @@ impl VirtualDom {
     /// Waits for the scheduler to have work
     /// This lets us poll async tasks during idle periods without blocking the main thread.
     pub async fn wait_for_work(&mut self) {
-        // todo: poll the events once even if there is work to do to prevent starvation
-
-        // if there's no futures in the virtualdom, just wait for a scheduler message and put it into the queue to be processed
-        if self.scopes.pending_futures.borrow().is_empty() {
-            self.pending_messages
-                .push_front(self.receiver.next().await.unwrap());
-        } else {
-            struct PollTasks<'a> {
-                scopes: &'a mut ScopeArena,
+        loop {
+            if !self.dirty_scopes.is_empty() && self.pending_messages.is_empty() {
+                break;
             }
 
-            impl<'a> Future for PollTasks<'a> {
-                type Output = ();
-
-                fn poll(
-                    self: Pin<&mut Self>,
-                    cx: &mut std::task::Context<'_>,
-                ) -> Poll<Self::Output> {
-                    let mut all_pending = true;
-
-                    let mut unfinished_tasks: SmallVec<[_; 10]> = smallvec::smallvec![];
-                    let mut scopes_to_clear: SmallVec<[_; 10]> = smallvec::smallvec![];
-
-                    // Poll every scope manually
-                    for fut in self.scopes.pending_futures.borrow().iter() {
-                        let scope = self
-                            .scopes
-                            .get_scope(fut)
-                            .expect("Scope should never be moved");
-
-                        let mut items = scope.items.borrow_mut();
-
-                        // really this should just be retain_mut but that doesn't exist yet
-                        while let Some(mut task) = items.tasks.pop() {
-                            // todo: does this make sense?
-                            // I don't usually write futures by hand
-                            // I think the futures neeed to be pinned using bumpbox or something
-                            // right now, they're bump allocated so this shouldn't matter anyway - they're not going to move
-                            let task_mut = task.as_mut();
-                            let unpinned = unsafe { Pin::new_unchecked(task_mut) };
-
-                            if unpinned.poll(cx).is_ready() {
-                                all_pending = false
-                            } else {
-                                unfinished_tasks.push(task);
-                            }
-                        }
-
-                        if unfinished_tasks.is_empty() {
-                            scopes_to_clear.push(*fut);
-                        }
-
-                        items.tasks.extend(unfinished_tasks.drain(..));
-                    }
-
-                    for scope in scopes_to_clear {
-                        self.scopes.pending_futures.borrow_mut().remove(&scope);
-                    }
+            if self.pending_messages.is_empty() {
+                if self.scopes.pending_futures.borrow().is_empty() {
+                    self.pending_messages
+                        .push_front(self.receiver.next().await.unwrap());
+                } else {
+                    use futures_util::future::{select, Either};
 
-                    // Resolve the future if any singular task is ready
-                    match all_pending {
-                        true => Poll::Pending,
-                        false => Poll::Ready(()),
+                    match select(PollTasks(&mut self.scopes), self.receiver.next()).await {
+                        Either::Left((_, _)) => {}
+                        Either::Right((msg, _)) => self.pending_messages.push_front(msg.unwrap()),
                     }
                 }
             }
 
-            // Poll both the futures and the scheduler message queue simulataneously
-            use futures_util::future::{select, Either};
-
-            let scheduler_fut = self.receiver.next();
-            let tasks_fut = PollTasks {
-                scopes: &mut self.scopes,
-            };
-
-            match select(tasks_fut, scheduler_fut).await {
-                // Futures don't generate work
-                Either::Left((_, _)) => {}
+            while let Ok(Some(msg)) = self.receiver.try_next() {
+                self.pending_messages.push_front(msg);
+            }
 
-                // Save these messages in FIFO to be processed later
-                Either::Right((msg, _)) => self.pending_messages.push_front(msg.unwrap()),
+            if let Some(msg) = self.pending_messages.pop_back() {
+                match msg {
+                    // just keep looping, the task is now saved but we should actually poll it
+                    SchedulerMsg::TaskPushed(id) => {
+                        self.scopes.pending_futures.borrow_mut().insert(id);
+                    }
+                    SchedulerMsg::UiEvent(event) => {
+                        if let Some(element) = event.element {
+                            self.scopes.call_listener_with_bubbling(event, element);
+                        }
+                    }
+                    SchedulerMsg::Immediate(s) => {
+                        self.dirty_scopes.insert(s);
+                    }
+                    SchedulerMsg::Suspended { scope } => todo!(),
+                }
             }
         }
     }
@@ -393,36 +351,8 @@ impl VirtualDom {
     pub fn work_with_deadline(&mut self, mut deadline: impl FnMut() -> bool) -> Vec<Mutations> {
         let mut committed_mutations = vec![];
 
-        while self.has_any_work() {
-            while let Ok(Some(msg)) = self.receiver.try_next() {
-                self.pending_messages.push_front(msg);
-            }
-
-            while let Some(msg) = self.pending_messages.pop_back() {
-                match msg {
-                    // TODO: Suspsense
-                    SchedulerMsg::Immediate(id) => {
-                        self.dirty_scopes.insert(id);
-                    }
-                    SchedulerMsg::Suspended { node, scope } => {
-                        todo!("should wire up suspense")
-                        // self.suspense_scopes.insert(id);
-                    }
-                    SchedulerMsg::UiEvent(event) => {
-                        if let Some(element) = event.element {
-                            log::info!("Calling listener {:?}, {:?}", event.scope_id, element);
-
-                            self.scopes.call_listener_with_bubbling(event, element);
-                            while let Ok(Some(dirty_scope)) = self.receiver.try_next() {
-                                self.pending_messages.push_front(dirty_scope);
-                            }
-                        } else {
-                            log::debug!("User event without a targetted ElementId. Not currently supported.\nUnsure how to proceed. {:?}", event);
-                        }
-                    }
-                }
-            }
-
+        while !self.dirty_scopes.is_empty() {
+            log::debug!("working with deadline");
             let scopes = &self.scopes;
             let mut diff_state = DiffState::new(scopes);
 
@@ -616,7 +546,10 @@ pub enum SchedulerMsg {
     // setstate
     Immediate(ScopeId),
 
-    Suspended { scope: ScopeId, node: NodeLink },
+    // an async task pushed from an event handler (or just spawned)
+    TaskPushed(ScopeId),
+
+    Suspended { scope: ScopeId },
 }
 
 /// User Events are events that are shuttled from the renderer into the VirtualDom trhough the scheduler channel.
@@ -720,3 +653,55 @@ pub enum EventPriority {
     /// This is considered "idle" work or "background" work.
     Low = 0,
 }
+
+struct PollTasks<'a>(&'a mut ScopeArena);
+
+impl<'a> Future for PollTasks<'a> {
+    type Output = ();
+
+    fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Self::Output> {
+        let mut all_pending = true;
+
+        let mut unfinished_tasks: SmallVec<[_; 10]> = smallvec::smallvec![];
+        let mut scopes_to_clear: SmallVec<[_; 10]> = smallvec::smallvec![];
+
+        // Poll every scope manually
+        for fut in self.0.pending_futures.borrow().iter() {
+            let scope = self.0.get_scope(fut).expect("Scope should never be moved");
+
+            let mut items = scope.items.borrow_mut();
+
+            // really this should just be retain_mut but that doesn't exist yet
+            while let Some(mut task) = items.tasks.pop() {
+                // todo: does this make sense?
+                // I don't usually write futures by hand
+                // I think the futures neeed to be pinned using bumpbox or something
+                // right now, they're bump allocated so this shouldn't matter anyway - they're not going to move
+                let task_mut = task.as_mut();
+                let unpinned = unsafe { Pin::new_unchecked(task_mut) };
+
+                if unpinned.poll(cx).is_ready() {
+                    all_pending = false
+                } else {
+                    unfinished_tasks.push(task);
+                }
+            }
+
+            if unfinished_tasks.is_empty() {
+                scopes_to_clear.push(*fut);
+            }
+
+            items.tasks.extend(unfinished_tasks.drain(..));
+        }
+
+        for scope in scopes_to_clear {
+            self.0.pending_futures.borrow_mut().remove(&scope);
+        }
+
+        // Resolve the future if any singular task is ready
+        match all_pending {
+            true => Poll::Pending,
+            false => Poll::Ready(()),
+        }
+    }
+}

+ 1 - 0
packages/desktop/Cargo.toml

@@ -22,6 +22,7 @@ tokio = { version = "1.12.0", features = [
     "sync",
     "rt-multi-thread",
     "rt",
+    "time",
 ], optional = true, default-features = false }
 dioxus-core-macro = { path = "../core-macro" }
 dioxus-html = { path = "../html", features = ["serialize"] }

+ 34 - 0
packages/desktop/examples/async.rs

@@ -0,0 +1,34 @@
+//! Example: README.md showcase
+//!
+//! The example from the README.md.
+
+use std::time::Duration;
+
+use dioxus::prelude::*;
+use dioxus_core as dioxus;
+use dioxus_core_macro::*;
+use dioxus_hooks::*;
+use dioxus_html as dioxus_elements;
+
+fn main() {
+    dioxus_desktop::launch(App, |c| c);
+}
+
+static App: FC<()> = |cx, props| {
+    let mut count = use_state(cx, || 0);
+
+    cx.push_task(|| async move {
+        tokio::time::sleep(Duration::from_millis(100)).await;
+        count += 1;
+    });
+
+    cx.render(rsx! {
+        div {
+            h1 { "High-Five counter: {count}" }
+            button {
+                onclick: move |_| count.set(0),
+                "Click me!"
+            }
+        }
+    })
+};

+ 227 - 290
packages/desktop/src/index.js

@@ -1,4 +1,213 @@
 
+function serialize_event(event) {
+  switch (event.type) {
+    case "copy":
+    case "cut":
+    case "past":
+      return {};
+
+    case "compositionend":
+    case "compositionstart":
+    case "compositionupdate":
+      return {
+        data: event.data
+      }
+
+    case "keydown":
+    case "keypress":
+    case "keyup":
+      return {
+        char_code: event.charCode,
+        key: event.key,
+        alt_key: event.altKey,
+        ctrl_key: event.ctrlKey,
+        meta_key: event.metaKey,
+        key_code: event.keyCode,
+        shift_key: event.shiftKey,
+        locale: "locale",
+        location: event.location,
+        repeat: event.repeat,
+        which: event.which,
+        // locale: event.locale,
+      }
+
+    case "focus":
+    case "blur":
+      return {};
+
+    case "change":
+      let target = event.target;
+      let value;
+      if (target.type === "checkbox" || target.type === "radio") {
+        value = target.checked ? "true" : "false";
+      } else {
+        value = target.value ?? target.textContent;
+      }
+
+      return {
+        value: value
+      };
+
+    case "input":
+    case "invalid":
+    case "reset":
+    case "submit": {
+      let target = event.target;
+      let value = target.value ?? target.textContent;
+      return {
+        value: value
+      };
+    }
+
+    case "click":
+    case "contextmenu":
+    case "doubleclick":
+    case "drag":
+    case "dragend":
+    case "dragenter":
+    case "dragexit":
+    case "dragleave":
+    case "dragover":
+    case "dragstart":
+    case "drop":
+    case "mousedown":
+    case "mouseenter":
+    case "mouseleave":
+    case "mousemove":
+    case "mouseout":
+    case "mouseover":
+    case "mouseup":
+      return {
+        alt_key: event.altKey,
+        button: event.button,
+        buttons: event.buttons,
+        client_x: event.clientX,
+        client_y: event.clientY,
+        ctrl_key: event.ctrlKey,
+        meta_key: event.metaKey,
+        page_x: event.pageX,
+        page_y: event.pageY,
+        screen_x: event.screenX,
+        screen_y: event.screenY,
+        shift_key: event.shiftKey,
+      }
+
+    case "pointerdown":
+    case "pointermove":
+    case "pointerup":
+    case "pointercancel":
+    case "gotpointercapture":
+    case "lostpointercapture":
+    case "pointerenter":
+    case "pointerleave":
+    case "pointerover":
+    case "pointerout":
+      return {
+        alt_key: event.altKey,
+        button: event.button,
+        buttons: event.buttons,
+        client_x: event.clientX,
+        client_y: event.clientY,
+        ctrl_key: event.ctrlKey,
+        meta_key: event.metaKey,
+        page_x: event.pageX,
+        page_y: event.pageY,
+        screen_x: event.screenX,
+        screen_y: event.screenY,
+        shift_key: event.shiftKey,
+        pointer_id: event.pointerId,
+        width: event.width,
+        height: event.height,
+        pressure: event.pressure,
+        tangential_pressure: event.tangentialPressure,
+        tilt_x: event.tiltX,
+        tilt_y: event.tiltY,
+        twist: event.twist,
+        pointer_type: event.pointerType,
+        is_primary: event.isPrimary,
+      }
+
+    case "select":
+      return {};
+
+    case "touchcancel":
+    case "touchend":
+    case "touchmove":
+    case "touchstart":
+      return {
+        alt_key: event.altKey,
+        ctrl_key: event.ctrlKey,
+        meta_key: event.metaKey,
+        shift_key: event.shiftKey,
+
+        // changed_touches: event.changedTouches,
+        // target_touches: event.targetTouches,
+        // touches: event.touches,
+      }
+
+    case "scroll":
+      return {};
+
+    case "wheel":
+      return {
+        delta_x: event.deltaX,
+        delta_y: event.deltaY,
+        delta_z: event.deltaZ,
+        delta_mode: event.deltaMode,
+      };
+
+    case "animationstart":
+    case "animationend":
+    case "animationiteration":
+      return {
+        animation_name: event.animationName,
+        elapsed_time: event.elapsedTime,
+        pseudo_element: event.pseudoElement,
+      };
+
+    case "transitionend":
+      return {
+        property_name: event.propertyName,
+        elapsed_time: event.elapsedTime,
+        pseudo_element: event.pseudoElement,
+      };
+
+    case "abort":
+    case "canplay":
+    case "canplaythrough":
+    case "durationchange":
+    case "emptied":
+    case "encrypted":
+    case "ended":
+    case "error":
+    case "loadeddata":
+    case "loadedmetadata":
+    case "loadstart":
+    case "pause":
+    case "play":
+    case "playing":
+    case "progress":
+    case "ratechange":
+    case "seeked":
+    case "seeking":
+    case "stalled":
+    case "suspend":
+    case "timeupdate":
+    case "volumechange":
+    case "waiting":
+      return {};
+
+    case "toggle":
+      return {};
+
+    default:
+      return {};
+  }
+
+
+}
+
+
 class Interpreter {
   constructor(root) {
     this.root = root;
@@ -125,22 +334,8 @@ class Interpreter {
           mounted_dom_id: parseInt(real_id),
           contents: contents,
         };
-        // console.log(evt);
-        rpc.call('user_event', evt).then((reply) => {
-          // console.log("reply received", reply);
-
-          this.stack.push(this.root);
-
-          reply.map((reply) => {
-            let edits = reply.edits;
-            for (let x = 0; x < edits.length; x++) {
-              let edit = edits[x];
-              let f = this[edit.type];
-              f.call(this, edit);
-            }
-          });
-
-        })
+
+        rpc.call('user_event', evt);
       });
     }
   }
@@ -197,286 +392,28 @@ class Interpreter {
     }
   }
 
-}
-
-async function initialize() {
-  let root = window.document.getElementById("_dioxusroot");
-  const interpreter = new Interpreter(root);
-
-  const reply = await rpc.call('initiate');
+  handleEdits(edits) {
 
-  let pre_rendered = reply.pre_rendered;
-  if (pre_rendered !== undefined) {
-    root.innerHTML = pre_rendered;
-  }
+    // console.log("handling raw edits", rawedits);
+    // let edits = JSON.parse(rawedits);
+    // console.log("handling  edits", edits);
 
-  const edits = reply.edits;
-
-  apply_edits(edits, interpreter);
-}
+    this.stack.push(this.root);
 
-function apply_edits(edits, interpreter) {
-  // console.log(edits);
-  for (let x = 0; x < edits.length; x++) {
-    let edit = edits[x];
-    let f = interpreter[edit.type];
-    f.call(interpreter, edit);
-  }
-
-  // // console.log("stack completed: ", interpreter.stack);
-}
-
-function serialize_event(event) {
-  let serializer = SerializeMap[event.type];
-  if (serializer === undefined) {
-    return {};
-  } else {
-    return serializer(event);
-  }
-}
-
-const SerializeMap = {
-  "copy": serialize_clipboard,
-  "cut": serialize_clipboard,
-  "paste": serialize_clipboard,
-
-  "compositionend": serialize_composition,
-  "compositionstart": serialize_composition,
-  "compositionupdate": serialize_composition,
-
-  "keydown": serialize_keyboard,
-  "keypress": serialize_keyboard,
-  "keyup": serialize_keyboard,
-
-  "focus": serialize_focus,
-  "blur": serialize_focus,
-
-  // "change": serialize_form,
-  "change": serialize_change,
-
-  "input": serialize_form,
-  "invalid": serialize_form,
-  "reset": serialize_form,
-  "submit": serialize_form,
-
-  "click": serialize_mouse,
-  "contextmenu": serialize_mouse,
-  "doubleclick": serialize_mouse,
-  "drag": serialize_mouse,
-  "dragend": serialize_mouse,
-  "dragenter": serialize_mouse,
-  "dragexit": serialize_mouse,
-  "dragleave": serialize_mouse,
-  "dragover": serialize_mouse,
-  "dragstart": serialize_mouse,
-  "drop": serialize_mouse,
-  "mousedown": serialize_mouse,
-  "mouseenter": serialize_mouse,
-  "mouseleave": serialize_mouse,
-  "mousemove": serialize_mouse,
-  "mouseout": serialize_mouse,
-  "mouseover": serialize_mouse,
-  "mouseup": serialize_mouse,
-
-  "pointerdown": serialize_pointer,
-  "pointermove": serialize_pointer,
-  "pointerup": serialize_pointer,
-  "pointercancel": serialize_pointer,
-  "gotpointercapture": serialize_pointer,
-  "lostpointercapture": serialize_pointer,
-  "pointerenter": serialize_pointer,
-  "pointerleave": serialize_pointer,
-  "pointerover": serialize_pointer,
-  "pointerout": serialize_pointer,
-
-  "select": serialize_selection,
-
-  "touchcancel": serialize_touch,
-  "touchend": serialize_touch,
-  "touchmove": serialize_touch,
-  "touchstart": serialize_touch,
-
-  "scroll": serialize_scroll,
-
-  "wheel": serialize_wheel,
-
-  "animationstart": serialize_animation,
-  "animationend": serialize_animation,
-  "animationiteration": serialize_animation,
-
-  "transitionend": serialize_transition,
-
-  "abort": serialize_media,
-  "canplay": serialize_media,
-  "canplaythrough": serialize_media,
-  "durationchange": serialize_media,
-  "emptied": serialize_media,
-  "encrypted": serialize_media,
-  "ended": serialize_media,
-  "error": serialize_media,
-  "loadeddata": serialize_media,
-  "loadedmetadata": serialize_media,
-  "loadstart": serialize_media,
-  "pause": serialize_media,
-  "play": serialize_media,
-  "playing": serialize_media,
-  "progress": serialize_media,
-  "ratechange": serialize_media,
-  "seeked": serialize_media,
-  "seeking": serialize_media,
-  "stalled": serialize_media,
-  "suspend": serialize_media,
-  "timeupdate": serialize_media,
-  "volumechange": serialize_media,
-  "waiting": serialize_media,
-
-  "toggle": serialize_toggle
-}
-
-function serialize_clipboard(_event) {
-  return {};
-}
-function serialize_composition(event) {
-  return {
-    data: event.data
-  }
-}
-function serialize_keyboard(event) {
-  return {
-    char_code: event.charCode,
-    key: event.key,
-    alt_key: event.altKey,
-    ctrl_key: event.ctrlKey,
-    meta_key: event.metaKey,
-    key_code: event.keyCode,
-    shift_key: event.shiftKey,
-    locale: "locale",
-    // locale: event.locale,
-    location: event.location,
-    repeat: event.repeat,
-    which: event.which,
-  }
-}
-function serialize_focus(_event) {
-  return {}
-}
-
-function serialize_change(event) {
-  let target = event.target;
-  let value;
-  if (target.type === "checkbox" || target.type === "radio") {
-    value = target.checked ? "true" : "false";
-  } else {
-    value = target.value ?? target.textContent;
-  }
-
-  return {
-    value: value
-  }
-}
-function serialize_form(event) {
-  let target = event.target;
-  let value = target.value ?? target.textContent;
-  return {
-    value: value
-  }
-}
-function serialize_mouse(event) {
-  return {
-    alt_key: event.altKey,
-    button: event.button,
-    buttons: event.buttons,
-    client_x: event.clientX,
-    client_y: event.clientY,
-    ctrl_key: event.ctrlKey,
-    meta_key: event.metaKey,
-    page_x: event.pageX,
-    page_y: event.pageY,
-    screen_x: event.screenX,
-    screen_y: event.screenY,
-    shift_key: event.shiftKey,
-  }
-}
-
-function serialize_pointer(event) {
-  return {
-    alt_key: event.altKey,
-    button: event.button,
-    buttons: event.buttons,
-    client_x: event.clientX,
-    client_y: event.clientY,
-    ctrl_key: event.ctrlKey,
-    meta_key: event.metaKey,
-    page_x: event.pageX,
-    page_y: event.pageY,
-    screen_x: event.screenX,
-    screen_y: event.screenY,
-    shift_key: event.shiftKey,
-    pointer_id: event.pointerId,
-    width: event.width,
-    height: event.height,
-    pressure: event.pressure,
-    tangential_pressure: event.tangentialPressure,
-    tilt_x: event.tiltX,
-    tilt_y: event.tiltY,
-    twist: event.twist,
-    pointer_type: event.pointerType,
-    is_primary: event.isPrimary,
-  }
-}
-
-function serialize_selection(event) {
-  return {}
-}
-
-function serialize_touch(event) {
-  return {
-    alt_key: event.altKey,
-    ctrl_key: event.ctrlKey,
-    meta_key: event.metaKey,
-    shift_key: event.shiftKey,
-
-    // changed_touches: event.changedTouches,
-    // target_touches: event.targetTouches,
-    // touches: event.touches,
-  }
-}
-function serialize_scroll(event) {
-  return {}
-}
-
-function serialize_wheel(event) {
-  return {
-    delta_x: event.deltaX,
-    delta_y: event.deltaY,
-    delta_z: event.deltaZ,
-    delta_mode: event.deltaMode,
-  }
-}
-
-function serialize_animation(event) {
-  return {
-    animation_name: event.animationName,
-    elapsed_time: event.elapsedTime,
-    pseudo_element: event.pseudoElement,
-  }
-}
-
-function serialize_transition(event) {
-  return {
-    property_name: event.propertyName,
-    elapsed_time: event.elapsedTime,
-    pseudo_element: event.pseudoElement,
+    for (let x = 0; x < edits.length; x++) {
+      let edit = edits[x];
+      let f = this[edit.type];
+      f.call(this, edit);
+    }
   }
 }
 
-function serialize_media(event) {
-  return {}
-}
+function main() {
+  let root = window.document.getElementById("_dioxusroot");
+  window.interpreter = new Interpreter(root);
+  console.log(window.interpreter);
 
-function serialize_toggle(event) {
-  return {}
+  rpc.call('initialize');
 }
 
-
-initialize();
+main()

+ 121 - 158
packages/desktop/src/lib.rs

@@ -5,7 +5,7 @@
 
 use std::borrow::BorrowMut;
 use std::cell::{Cell, RefCell};
-use std::collections::HashMap;
+use std::collections::{HashMap, VecDeque};
 use std::ops::{Deref, DerefMut};
 use std::rc::Rc;
 use std::sync::atomic::AtomicBool;
@@ -20,10 +20,10 @@ pub use wry;
 
 use wry::application::accelerator::{Accelerator, SysMods};
 use wry::application::event::{ElementState, Event, StartCause, WindowEvent};
-use wry::application::event_loop::{self, ControlFlow, EventLoop};
+use wry::application::event_loop::{self, ControlFlow, EventLoop, EventLoopWindowTarget};
 use wry::application::keyboard::{Key, KeyCode, ModifiersState};
 use wry::application::menu::{MenuBar, MenuItem, MenuItemAttributes};
-use wry::application::window::Fullscreen;
+use wry::application::window::{Fullscreen, WindowId};
 use wry::webview::{WebView, WebViewBuilder};
 use wry::{
     application::menu,
@@ -59,12 +59,6 @@ enum RpcEvent<'a> {
     Initialize { edits: Vec<DomEdit<'a>> },
 }
 
-#[derive(Debug)]
-enum BridgeEvent {
-    Initialize(serde_json::Value),
-    Update(serde_json::Value),
-}
-
 #[derive(Serialize)]
 struct Response<'a> {
     pre_rendered: Option<String>,
@@ -88,111 +82,29 @@ pub fn run<T: 'static + Send + Sync>(
 
     // All of our webview windows are stored in a way that we can look them up later
     // The "DesktopContext" will provide functionality for spawning these windows
-    let mut webviews = HashMap::new();
+    let mut webviews = HashMap::<WindowId, WebView>::new();
     let event_loop = EventLoop::new();
 
     let props_shared = Cell::new(Some(props));
 
     // create local modifier state
-    let mut modifiers = ModifiersState::default();
+    let modifiers = ModifiersState::default();
 
     let quit_hotkey = Accelerator::new(SysMods::Cmd, KeyCode::KeyQ);
 
-    event_loop.run(move |event, event_loop, control_flow| {
+    let edit_queue = Arc::new(RwLock::new(VecDeque::new()));
+    let is_ready: Arc<AtomicBool> = Default::default();
+
+    event_loop.run(move |window_event, event_loop, control_flow| {
         *control_flow = ControlFlow::Wait;
 
-        match event {
+        match window_event {
             Event::NewEvents(StartCause::Init) => {
-                // create main menubar menu
-                let mut menu_bar_menu = MenuBar::new();
-
-                // create `first_menu`
-                let mut first_menu = MenuBar::new();
-
-                first_menu.add_native_item(MenuItem::About("Todos".to_string()));
-                first_menu.add_native_item(MenuItem::Services);
-                first_menu.add_native_item(MenuItem::Separator);
-                first_menu.add_native_item(MenuItem::Hide);
-                first_menu.add_native_item(MenuItem::HideOthers);
-                first_menu.add_native_item(MenuItem::ShowAll);
-
-                first_menu.add_native_item(MenuItem::Quit);
-                first_menu.add_native_item(MenuItem::CloseWindow);
-
-                // create second menu
-                let mut second_menu = MenuBar::new();
-
-                // second_menu.add_submenu("Sub menu", true, my_sub_menu);
-                second_menu.add_native_item(MenuItem::Copy);
-                second_menu.add_native_item(MenuItem::Paste);
-                second_menu.add_native_item(MenuItem::SelectAll);
-
-                menu_bar_menu.add_submenu("First menu", true, first_menu);
-                menu_bar_menu.add_submenu("Second menu", true, second_menu);
-
-                let window = WindowBuilder::new()
-                    .with_maximized(true)
-                    .with_menu(menu_bar_menu)
-                    .with_title("Dioxus App")
-                    .build(event_loop)
-                    .unwrap();
+                let window = create_window(event_loop);
                 let window_id = window.id();
-
-                let (event_tx, event_rx) = tokio::sync::mpsc::unbounded_channel();
-                let my_props = props_shared.take().unwrap();
-
-                let sender = launch_vdom_with_tokio(root, my_props, event_tx);
-
-                let locked_receiver = Rc::new(RefCell::new(event_rx));
-
-                let webview = WebViewBuilder::new(window)
-                    .unwrap()
-                    .with_url("wry://index.html")
-                    .unwrap()
-                    .with_rpc_handler(move |_window: &Window, mut req: RpcRequest| {
-                        let mut rx = (*locked_receiver).borrow_mut();
-                        match req.method.as_str() {
-                            "initiate" => {
-                                if let Ok(BridgeEvent::Initialize(edits)) = rx.try_recv() {
-                                    Some(RpcResponse::new_result(req.id.take(), Some(edits)))
-                                } else {
-                                    None
-                                }
-                            }
-                            "user_event" => {
-                                let event = events::trigger_from_serialized(req.params.unwrap());
-                                log::debug!("User event: {:?}", event);
-
-                                sender.unbounded_send(SchedulerMsg::UiEvent(event)).unwrap();
-
-                                if let Some(BridgeEvent::Update(edits)) = rx.blocking_recv() {
-                                    log::info!("bridge received message");
-                                    Some(RpcResponse::new_result(req.id.take(), Some(edits)))
-                                } else {
-                                    log::info!("none received message");
-                                    None
-                                }
-                            }
-                            _ => None,
-                        }
-                    })
-                    // Any content that that uses the `wry://` scheme will be shuttled through this handler as a "special case"
-                    // For now, we only serve two pieces of content which get included as bytes into the final binary.
-                    .with_custom_protocol("wry".into(), move |request| {
-                        let path = request.uri().replace("wry://", "");
-                        let (data, meta) = match path.as_str() {
-                            "index.html" => (include_bytes!("./index.html").to_vec(), "text/html"),
-                            "index.html/index.js" => {
-                                (include_bytes!("./index.js").to_vec(), "text/javascript")
-                            }
-                            _ => unimplemented!("path {}", path),
-                        };
-
-                        wry::http::ResponseBuilder::new().mimetype(meta).body(data)
-                    })
-                    .build()
-                    .unwrap();
-
+                let sender =
+                    launch_vdom_with_tokio(root, props_shared.take().unwrap(), edit_queue.clone());
+                let webview = create_webview(window, is_ready.clone(), sender);
                 webviews.insert(window_id, webview);
             }
 
@@ -206,29 +118,17 @@ pub fn run<T: 'static + Send + Sync>(
                         *control_flow = ControlFlow::Exit;
                     }
                 }
-                // catch only pressed event
+                WindowEvent::Moved(pos) => {
+                    //
+                }
+
                 WindowEvent::KeyboardInput { event, .. } => {
-                    log::debug!("keybowrd input");
                     if quit_hotkey.matches(&modifiers, &event.physical_key) {
-                        log::debug!("quitting");
-
                         webviews.remove(&window_id);
                         if webviews.is_empty() {
                             *control_flow = ControlFlow::Exit;
                         }
                     }
-
-                    // println!(
-                    //     "KeyEvent:  `Shift` + `1` | logical_key: {:?}",
-                    //     &event.logical_key
-                    // );
-                    // we can match manually without `Accelerator`
-
-                    // else if event.key_without_modifiers() == Key::Character("1")
-                    //     && modifiers.is_empty()
-                    // {
-                    //     println!("KeyEvent: `1`");
-                    // }
                 }
 
                 WindowEvent::Resized(_) | WindowEvent::Moved(_) => {
@@ -240,7 +140,18 @@ pub fn run<T: 'static + Send + Sync>(
                 _ => {}
             },
 
-            Event::MainEventsCleared => {}
+            Event::MainEventsCleared => {
+                // I hate this ready hack but it's needed to wait for the "onload" to occur
+                // We can't run any initializion scripts because the window isn't ready yet?
+                if is_ready.load(std::sync::atomic::Ordering::Relaxed) {
+                    let mut queue = edit_queue.write().unwrap();
+                    let (id, view) = webviews.iter_mut().next().unwrap();
+                    while let Some(edit) = queue.pop_back() {
+                        view.evaluate_script(&format!("window.interpreter.handleEdits({})", edit))
+                            .unwrap();
+                    }
+                }
+            }
             Event::Resumed => {}
             Event::Suspended => {}
             Event::LoopDestroyed => {}
@@ -250,19 +161,11 @@ pub fn run<T: 'static + Send + Sync>(
     })
 }
 
-pub fn start<P: 'static + Send>(
-    root: FC<P>,
-    config_builder: impl for<'a, 'b> FnOnce(&'b mut DesktopConfig<'a>) -> &'b mut DesktopConfig<'a>,
-) -> ((), ()) {
-    //
-    ((), ())
-}
-
 // Create a new tokio runtime on a dedicated thread and then launch the apps VirtualDom.
 pub(crate) fn launch_vdom_with_tokio<P: Send + 'static>(
     root: FC<P>,
     props: P,
-    event_tx: tokio::sync::mpsc::UnboundedSender<BridgeEvent>,
+    edit_queue: Arc<RwLock<VecDeque<String>>>,
 ) -> futures_channel::mpsc::UnboundedSender<SchedulerMsg> {
     let (sender, receiver) = futures_channel::mpsc::unbounded::<SchedulerMsg>();
     let return_sender = sender.clone();
@@ -275,47 +178,107 @@ pub(crate) fn launch_vdom_with_tokio<P: Send + 'static>(
             .unwrap();
 
         runtime.block_on(async move {
-            let mut vir = VirtualDom::new_with_props_and_scheduler(root, props, sender, receiver);
-            let _ = vir.get_scheduler_channel();
+            let mut dom = VirtualDom::new_with_props_and_scheduler(root, props, sender, receiver);
+
+            let edits = dom.rebuild();
 
-            let edits = vir.rebuild();
+            edit_queue
+                .write()
+                .unwrap()
+                .push_front(serde_json::to_string(&edits.edits).unwrap());
 
-            // the receiving end expects something along these lines
-            #[derive(Serialize)]
-            struct Evt<'a> {
-                edits: Vec<DomEdit<'a>>,
+            loop {
+                dom.wait_for_work().await;
+                let mut muts = dom.work_with_deadline(|| false);
+                while let Some(edit) = muts.pop() {
+                    edit_queue
+                        .write()
+                        .unwrap()
+                        .push_front(serde_json::to_string(&edit.edits).unwrap());
+                }
             }
+        })
+    });
 
-            let edit_string = serde_json::to_value(Evt { edits: edits.edits }).unwrap();
+    return_sender
+}
 
-            event_tx
-                .send(BridgeEvent::Initialize(edit_string))
-                .expect("Sending should not fail");
+fn build_menu() -> MenuBar {
+    // create main menubar menu
+    let mut menu_bar_menu = MenuBar::new();
 
-            loop {
-                vir.wait_for_work().await;
-                // we're running on our own thread, so we don't need to worry about blocking anything
-                // todo: maybe we want to schedule ourselves in
-                // on average though, the virtualdom running natively is stupid fast
+    // create `first_menu`
+    let mut first_menu = MenuBar::new();
 
-                let mut muts = vir.work_with_deadline(|| false);
+    first_menu.add_native_item(MenuItem::About("Todos".to_string()));
+    first_menu.add_native_item(MenuItem::Services);
+    first_menu.add_native_item(MenuItem::Separator);
+    first_menu.add_native_item(MenuItem::Hide);
+    first_menu.add_native_item(MenuItem::HideOthers);
+    first_menu.add_native_item(MenuItem::ShowAll);
 
-                log::debug!("finished running with deadline");
+    first_menu.add_native_item(MenuItem::Quit);
+    first_menu.add_native_item(MenuItem::CloseWindow);
 
-                let mut edits = vec![];
+    // create second menu
+    let mut second_menu = MenuBar::new();
 
-                while let Some(edit) = muts.pop() {
-                    let edit_string = serde_json::to_value(Evt { edits: edit.edits })
-                        .expect("serializing edits should never fail");
-                    edits.push(edit_string);
-                }
+    // second_menu.add_submenu("Sub menu", true, my_sub_menu);
+    second_menu.add_native_item(MenuItem::Copy);
+    second_menu.add_native_item(MenuItem::Paste);
+    second_menu.add_native_item(MenuItem::SelectAll);
+
+    menu_bar_menu.add_submenu("First menu", true, first_menu);
+    menu_bar_menu.add_submenu("Second menu", true, second_menu);
+
+    menu_bar_menu
+}
 
-                event_tx
-                    .send(BridgeEvent::Update(serde_json::Value::Array(edits)))
-                    .expect("Sending should not fail");
+fn create_window(event_loop: &EventLoopWindowTarget<()>) -> Window {
+    WindowBuilder::new()
+        .with_maximized(true)
+        .with_menu(build_menu())
+        .with_title("Dioxus App")
+        .build(event_loop)
+        .unwrap()
+}
+
+fn create_webview(
+    window: Window,
+    is_ready: Arc<AtomicBool>,
+    sender: futures_channel::mpsc::UnboundedSender<SchedulerMsg>,
+) -> WebView {
+    WebViewBuilder::new(window)
+        .unwrap()
+        .with_url("wry://index.html")
+        .unwrap()
+        .with_rpc_handler(move |_window: &Window, mut req: RpcRequest| {
+            match req.method.as_str() {
+                "user_event" => {
+                    let event = events::trigger_from_serialized(req.params.unwrap());
+                    log::debug!("User event: {:?}", event);
+                    sender.unbounded_send(SchedulerMsg::UiEvent(event)).unwrap();
+                }
+                "initialize" => {
+                    is_ready.store(true, std::sync::atomic::Ordering::Relaxed);
+                }
+                _ => {}
             }
-        })
-    });
 
-    return_sender
+            None
+        })
+        // Any content that that uses the `wry://` scheme will be shuttled through this handler as a "special case"
+        // For now, we only serve two pieces of content which get included as bytes into the final binary.
+        .with_custom_protocol("wry".into(), move |request| {
+            let path = request.uri().replace("wry://", "");
+            let (data, meta) = match path.as_str() {
+                "index.html" => (include_bytes!("./index.html").to_vec(), "text/html"),
+                "index.html/index.js" => (include_bytes!("./index.js").to_vec(), "text/javascript"),
+                _ => unimplemented!("path {}", path),
+            };
+
+            wry::http::ResponseBuilder::new().mimetype(meta).body(data)
+        })
+        .build()
+        .unwrap()
 }

+ 0 - 0
packages/desktop/src/serialize.js


+ 4 - 1
packages/hooks/src/usestate.rs

@@ -126,7 +126,10 @@ impl<'a, T: 'static> UseState<'a, T> {
 
     pub fn setter(&self) -> Rc<dyn Fn(T)> {
         let slot = self.inner.wip.clone();
-        Rc::new(move |new| *slot.borrow_mut() = Some(new))
+        Rc::new(move |new| {
+            //
+            *slot.borrow_mut() = Some(new);
+        })
     }
 
     pub fn for_async(&self) -> AsyncUseState<T> {