浏览代码

Merge branch 'master' of github.com:DioxusLabs/dioxus into router-2

Adrian Wannenmacher 2 年之前
父节点
当前提交
dd54c0acf7
共有 53 个文件被更改,包括 2195 次插入373 次删除
  1. 14 1
      examples/eval.rs
  2. 1 2
      examples/rsx_usage.rs
  3. 6 0
      examples/svg_basic.rs
  4. 1 1
      packages/core-macro/Cargo.toml
  5. 0 10
      packages/core-macro/src/lib.rs
  6. 3 1
      packages/core/Cargo.toml
  7. 11 15
      packages/core/src/create.rs
  8. 5 3
      packages/core/src/diff.rs
  9. 9 199
      packages/core/src/lazynodes.rs
  10. 3 3
      packages/core/src/lib.rs
  11. 1 1
      packages/core/src/mutations.rs
  12. 6 0
      packages/core/src/nodes.rs
  13. 13 6
      packages/core/src/scope_arena.rs
  14. 3 3
      packages/core/src/scopes.rs
  15. 3 9
      packages/core/src/virtual_dom.rs
  16. 5 1
      packages/desktop/src/controller.rs
  17. 59 19
      packages/desktop/src/desktop_context.rs
  18. 7 1
      packages/desktop/src/lib.rs
  19. 4 4
      packages/desktop/src/protocol.rs
  20. 0 2
      packages/dioxus/src/lib.rs
  21. 3 1
      packages/interpreter/Cargo.toml
  22. 3 3
      packages/interpreter/src/bindings.rs
  23. 2 1
      packages/interpreter/src/interpreter.js
  24. 5 0
      packages/interpreter/src/lib.rs
  25. 231 0
      packages/interpreter/src/sledgehammer_bindings.rs
  26. 1 2
      packages/native-core-macro/src/lib.rs
  27. 10 23
      packages/native-core/src/real_dom.rs
  28. 484 0
      packages/native-core/src/utils/cursor.rs
  29. 3 0
      packages/native-core/src/utils/mod.rs
  30. 0 0
      packages/native-core/src/utils/persistant_iterator.rs
  31. 6 2
      packages/rsx/Cargo.toml
  32. 1 2
      packages/rsx/src/element.rs
  33. 3 3
      packages/rsx/src/lib.rs
  34. 1 0
      packages/tui/Cargo.toml
  35. 93 0
      packages/tui/examples/tui_widgets.rs
  36. 4 1
      packages/tui/src/hooks.rs
  37. 54 1
      packages/tui/src/layout.rs
  38. 18 3
      packages/tui/src/lib.rs
  39. 1 0
      packages/tui/src/prelude/mod.rs
  40. 1 1
      packages/tui/src/query.rs
  41. 1 1
      packages/tui/src/render.rs
  42. 59 0
      packages/tui/src/widgets/button.rs
  43. 82 0
      packages/tui/src/widgets/checkbox.rs
  44. 102 0
      packages/tui/src/widgets/input.rs
  45. 18 0
      packages/tui/src/widgets/mod.rs
  46. 209 0
      packages/tui/src/widgets/number.rs
  47. 186 0
      packages/tui/src/widgets/password.rs
  48. 108 0
      packages/tui/src/widgets/slider.rs
  49. 182 0
      packages/tui/src/widgets/textbox.rs
  50. 2 1
      packages/web/Cargo.toml
  51. 112 38
      packages/web/src/dom.rs
  52. 6 5
      packages/web/src/lib.rs
  53. 50 4
      packages/web/src/util.rs

+ 14 - 1
examples/eval.rs

@@ -1,4 +1,5 @@
 use dioxus::prelude::*;
 use dioxus::prelude::*;
+use dioxus_desktop::EvalResult;
 
 
 fn main() {
 fn main() {
     dioxus_desktop::launch(app);
     dioxus_desktop::launch(app);
@@ -7,6 +8,15 @@ fn main() {
 fn app(cx: Scope) -> Element {
 fn app(cx: Scope) -> Element {
     let script = use_state(cx, String::new);
     let script = use_state(cx, String::new);
     let eval = dioxus_desktop::use_eval(cx);
     let eval = dioxus_desktop::use_eval(cx);
+    let future: &UseRef<Option<EvalResult>> = use_ref(cx, || None);
+    if future.read().is_some() {
+        let future_clone = future.clone();
+        cx.spawn(async move {
+            if let Some(fut) = future_clone.with_mut(|o| o.take()) {
+                println!("{:?}", fut.await)
+            }
+        });
+    }
 
 
     cx.render(rsx! {
     cx.render(rsx! {
         div {
         div {
@@ -16,7 +26,10 @@ fn app(cx: Scope) -> Element {
                 oninput: move |e| script.set(e.value.clone()),
                 oninput: move |e| script.set(e.value.clone()),
             }
             }
             button {
             button {
-                onclick: move |_| eval(script.to_string()),
+                onclick: move |_| {
+                    let fut = eval(script);
+                    future.set(Some(fut));
+                },
                 "Execute"
                 "Execute"
             }
             }
         }
         }

+ 1 - 2
examples/rsx_usage.rs

@@ -1,5 +1,4 @@
 #![allow(non_snake_case)]
 #![allow(non_snake_case)]
-
 //! A tour of the rsx! macro
 //! A tour of the rsx! macro
 //! ------------------------
 //! ------------------------
 //!
 //!
@@ -39,7 +38,7 @@
 //! - Accept a list of vnodes as children for a Fragment component
 //! - Accept a list of vnodes as children for a Fragment component
 //! - Allow keyed fragments in iterators
 //! - Allow keyed fragments in iterators
 //! - Allow top-level fragments
 //! - Allow top-level fragments
-//!
+
 fn main() {
 fn main() {
     dioxus_desktop::launch(app);
     dioxus_desktop::launch(app);
 }
 }

+ 6 - 0
examples/svg_basic.rs

@@ -67,6 +67,12 @@ fn app(cx: Scope) -> Element {
             stroke: "blue",
             stroke: "blue",
             stroke_width: "5",
             stroke_width: "5",
         }
         }
+        path {
+            d: "M9.00001 9C9 62 103.5 124 103.5 178",
+            stroke: "#3CC4DC",
+            "stroke-linecap": "square",
+            "stroke-width": "square",
+        }
     }))
     }))
 }
 }
 
 

+ 1 - 1
packages/core-macro/Cargo.toml

@@ -18,7 +18,7 @@ proc-macro = true
 proc-macro2 = { version = "1.0" }
 proc-macro2 = { version = "1.0" }
 quote = "1.0"
 quote = "1.0"
 syn = { version = "1.0", features = ["full", "extra-traits"] }
 syn = { version = "1.0", features = ["full", "extra-traits"] }
-dioxus-rsx = {  path = "../rsx" }
+dioxus-rsx = {  path = "../rsx", version = "0.0.1" }
 
 
 # testing
 # testing
 [dev-dependencies]
 [dev-dependencies]

+ 0 - 10
packages/core-macro/src/lib.rs

@@ -26,11 +26,6 @@ pub fn derive_typed_builder(input: proc_macro::TokenStream) -> proc_macro::Token
 }
 }
 
 
 /// The rsx! macro makes it easy for developers to write jsx-style markup in their components.
 /// The rsx! macro makes it easy for developers to write jsx-style markup in their components.
-///
-/// ## Complete Reference Guide:
-/// ```ignore
-#[doc = include_str!("../../../examples/rsx_usage.rs")]
-/// ```
 #[proc_macro]
 #[proc_macro]
 pub fn rsx(s: TokenStream) -> TokenStream {
 pub fn rsx(s: TokenStream) -> TokenStream {
     match syn::parse::<rsx::CallBody>(s) {
     match syn::parse::<rsx::CallBody>(s) {
@@ -42,11 +37,6 @@ pub fn rsx(s: TokenStream) -> TokenStream {
 /// The render! macro makes it easy for developers to write jsx-style markup in their components.
 /// The render! macro makes it easy for developers to write jsx-style markup in their components.
 ///
 ///
 /// The render macro automatically renders rsx - making it unhygenic.
 /// The render macro automatically renders rsx - making it unhygenic.
-///
-/// ## Complete Reference Guide:
-/// ```ignore
-#[doc = include_str!("../../../examples/rsx_usage.rs")]
-/// ```
 #[proc_macro]
 #[proc_macro]
 pub fn render(s: TokenStream) -> TokenStream {
 pub fn render(s: TokenStream) -> TokenStream {
     match syn::parse::<rsx::CallBody>(s) {
     match syn::parse::<rsx::CallBody>(s) {

+ 3 - 1
packages/core/Cargo.toml

@@ -18,7 +18,7 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
 bumpalo = { version = "3.6", features = ["collections", "boxed"] }
 bumpalo = { version = "3.6", features = ["collections", "boxed"] }
 
 
 # faster hashmaps
 # faster hashmaps
-fxhash = "0.2"
+rustc-hash = "1.1.0"
 
 
 # Used in diffing
 # Used in diffing
 longest-increasing-subsequence = "0.1.0"
 longest-increasing-subsequence = "0.1.0"
@@ -35,6 +35,8 @@ indexmap = "1.7"
 serde = { version = "1", features = ["derive"], optional = true }
 serde = { version = "1", features = ["derive"], optional = true }
 anyhow = "1.0.66"
 anyhow = "1.0.66"
 
 
+smallbox = "0.8.1"
+
 [dev-dependencies]
 [dev-dependencies]
 tokio = { version = "*", features = ["full"] }
 tokio = { version = "*", features = ["full"] }
 dioxus = { path = "../dioxus" }
 dioxus = { path = "../dioxus" }

+ 11 - 15
packages/core/src/create.rs

@@ -36,6 +36,9 @@ impl<'b> VirtualDom {
 
 
         let cur_scope = self.scope_stack.last().copied().unwrap();
         let cur_scope = self.scope_stack.last().copied().unwrap();
 
 
+        // we know that this will generate at least one mutation per node
+        self.mutations.edits.reserve(template.template.roots.len());
+
         let mut on_stack = 0;
         let mut on_stack = 0;
         for (root_idx, root) in template.template.roots.iter().enumerate() {
         for (root_idx, root) in template.template.roots.iter().enumerate() {
             // We might need to generate an ID for the root node
             // We might need to generate an ID for the root node
@@ -208,19 +211,12 @@ impl<'b> VirtualDom {
 
 
         // If it's all dynamic nodes, then we don't need to register it
         // If it's all dynamic nodes, then we don't need to register it
         // Quickly run through and see if it's all just dynamic nodes
         // Quickly run through and see if it's all just dynamic nodes
-        let dynamic_roots = template
-            .template
-            .roots
-            .iter()
-            .filter(|root| {
-                matches!(
-                    root,
-                    TemplateNode::Dynamic { .. } | TemplateNode::DynamicText { .. }
-                )
-            })
-            .count();
-
-        if dynamic_roots == template.template.roots.len() {
+        if template.template.roots.iter().all(|root| {
+            matches!(
+                root,
+                TemplateNode::Dynamic { .. } | TemplateNode::DynamicText { .. }
+            )
+        }) {
             return;
             return;
         }
         }
 
 
@@ -291,7 +287,7 @@ impl<'b> VirtualDom {
     }
     }
 
 
     pub(crate) fn create_fragment(&mut self, nodes: &'b [VNode<'b>]) -> usize {
     pub(crate) fn create_fragment(&mut self, nodes: &'b [VNode<'b>]) -> usize {
-        nodes.iter().fold(0, |acc, child| acc + self.create(child))
+        nodes.iter().map(|child| self.create(child)).sum()
     }
     }
 
 
     pub(super) fn create_component_node(
     pub(super) fn create_component_node(
@@ -302,7 +298,7 @@ impl<'b> VirtualDom {
     ) -> usize {
     ) -> usize {
         let props = component
         let props = component
             .props
             .props
-            .replace(None)
+            .take()
             .expect("Props to always exist when a component is being created");
             .expect("Props to always exist when a component is being created");
 
 
         let unbounded_props = unsafe { std::mem::transmute(props) };
         let unbounded_props = unsafe { std::mem::transmute(props) };

+ 5 - 3
packages/core/src/diff.rs

@@ -11,7 +11,7 @@ use crate::{
     AttributeValue, TemplateNode,
     AttributeValue, TemplateNode,
 };
 };
 
 
-use fxhash::{FxHashMap, FxHashSet};
+use rustc_hash::{FxHashMap, FxHashSet};
 use DynamicNode::*;
 use DynamicNode::*;
 
 
 impl<'b> VirtualDom {
 impl<'b> VirtualDom {
@@ -56,7 +56,9 @@ impl<'b> VirtualDom {
     fn diff_err_to_ok(&mut self, _e: &anyhow::Error, _l: &'b VNode<'b>) {}
     fn diff_err_to_ok(&mut self, _e: &anyhow::Error, _l: &'b VNode<'b>) {}
 
 
     fn diff_node(&mut self, left_template: &'b VNode<'b>, right_template: &'b VNode<'b>) {
     fn diff_node(&mut self, left_template: &'b VNode<'b>, right_template: &'b VNode<'b>) {
-        if left_template.template.name != right_template.template.name {
+        if !std::ptr::eq(left_template.template.name, right_template.template.name)
+            && left_template.template.name != right_template.template.name
+        {
             return self.light_diff_templates(left_template, right_template);
             return self.light_diff_templates(left_template, right_template);
         }
         }
 
 
@@ -432,7 +434,7 @@ impl<'b> VirtualDom {
     // The stack is empty upon entry.
     // The stack is empty upon entry.
     fn diff_keyed_children(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) {
     fn diff_keyed_children(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) {
         if cfg!(debug_assertions) {
         if cfg!(debug_assertions) {
-            let mut keys = fxhash::FxHashSet::default();
+            let mut keys = rustc_hash::FxHashSet::default();
             let mut assert_unique_keys = |children: &'b [VNode<'b>]| {
             let mut assert_unique_keys = |children: &'b [VNode<'b>]| {
                 keys.clear();
                 keys.clear();
                 for child in children {
                 for child in children {

+ 9 - 199
packages/core/src/lazynodes.rs

@@ -11,8 +11,9 @@
 //! The logic for this was borrowed from <https://docs.rs/stack_dst/0.6.1/stack_dst/>. Unfortunately, this crate does not
 //! The logic for this was borrowed from <https://docs.rs/stack_dst/0.6.1/stack_dst/>. Unfortunately, this crate does not
 //! support non-static closures, so we've implemented the core logic of `ValueA` in this module.
 //! support non-static closures, so we've implemented the core logic of `ValueA` in this module.
 
 
+use smallbox::{smallbox, space::S16, SmallBox};
+
 use crate::{innerlude::VNode, ScopeState};
 use crate::{innerlude::VNode, ScopeState};
-use std::mem;
 
 
 /// A concrete type provider for closures that build [`VNode`] structures.
 /// A concrete type provider for closures that build [`VNode`] structures.
 ///
 ///
@@ -24,14 +25,7 @@ use std::mem;
 /// LazyNodes::new(|f| f.element("div", [], [], [] None))
 /// LazyNodes::new(|f| f.element("div", [], [], [] None))
 /// ```
 /// ```
 pub struct LazyNodes<'a, 'b> {
 pub struct LazyNodes<'a, 'b> {
-    inner: StackNodeStorage<'a, 'b>,
-}
-
-type StackHeapSize = [usize; 16];
-
-enum StackNodeStorage<'a, 'b> {
-    Stack(LazyStack),
-    Heap(Box<dyn FnMut(Option<&'a ScopeState>) -> Option<VNode<'a>> + 'b>),
+    inner: SmallBox<dyn FnMut(&'a ScopeState) -> VNode<'a> + 'b, S16>,
 }
 }
 
 
 impl<'a, 'b> LazyNodes<'a, 'b> {
 impl<'a, 'b> LazyNodes<'a, 'b> {
@@ -44,114 +38,11 @@ impl<'a, 'b> LazyNodes<'a, 'b> {
         // there's no way to call FnOnce without a box, so we need to store it in a slot and use static dispatch
         // there's no way to call FnOnce without a box, so we need to store it in a slot and use static dispatch
         let mut slot = Some(val);
         let mut slot = Some(val);
 
 
-        let val = move |fac: Option<&'a ScopeState>| {
-            fac.map(
-                slot.take()
-                    .expect("LazyNodes closure to be called only once"),
-            )
-        };
-
-        // miri does not know how to work with mucking directly into bytes
-        // just use a heap allocated type when miri is running
-        if cfg!(miri) {
-            Self {
-                inner: StackNodeStorage::Heap(Box::new(val)),
-            }
-        } else {
-            unsafe { LazyNodes::new_inner(val) }
-        }
-    }
-
-    /// Create a new [`LazyNodes`] closure, but force it onto the heap.
-    pub fn new_boxed<F>(inner: F) -> Self
-    where
-        F: FnOnce(&'a ScopeState) -> VNode<'a> + 'b,
-    {
-        // there's no way to call FnOnce without a box, so we need to store it in a slot and use static dispatch
-        let mut slot = Some(inner);
-
         Self {
         Self {
-            inner: StackNodeStorage::Heap(Box::new(move |fac: Option<&'a ScopeState>| {
-                fac.map(
-                    slot.take()
-                        .expect("LazyNodes closure to be called only once"),
-                )
-            })),
-        }
-    }
-
-    unsafe fn new_inner<F>(val: F) -> Self
-    where
-        F: FnMut(Option<&'a ScopeState>) -> Option<VNode<'a>> + 'b,
-    {
-        let mut ptr: *const _ = &val as &dyn FnMut(Option<&'a ScopeState>) -> Option<VNode<'a>>;
-
-        assert_eq!(
-            ptr as *const u8, &val as *const _ as *const u8,
-            "MISUSE: Closure returned different pointer"
-        );
-        assert_eq!(
-            std::mem::size_of_val(&*ptr),
-            std::mem::size_of::<F>(),
-            "MISUSE: Closure returned a subset pointer"
-        );
-
-        let words = ptr_as_slice(&mut ptr);
-        assert!(
-            words[0] == &val as *const _ as usize,
-            "BUG: Pointer layout is not (data_ptr, info...)"
-        );
-
-        // - Ensure that Self is aligned same as data requires
-        assert!(
-            std::mem::align_of::<F>() <= std::mem::align_of::<Self>(),
-            "TODO: Enforce alignment >{} (requires {})",
-            std::mem::align_of::<Self>(),
-            std::mem::align_of::<F>()
-        );
-
-        let info = &words[1..];
-        let data = words[0] as *mut ();
-        let size = mem::size_of::<F>();
-
-        let stored_size = info.len() * mem::size_of::<usize>() + size;
-        let max_size = mem::size_of::<StackHeapSize>();
-
-        if stored_size > max_size {
-            Self {
-                inner: StackNodeStorage::Heap(Box::new(val)),
-            }
-        } else {
-            let mut buf: StackHeapSize = StackHeapSize::default();
-
-            assert!(info.len() + round_to_words(size) <= buf.as_ref().len());
-
-            // Place pointer information at the end of the region
-            // - Allows the data to be at the start for alignment purposes
-            {
-                let info_ofs = buf.as_ref().len() - info.len();
-                let info_dst = &mut buf.as_mut()[info_ofs..];
-                for (d, v) in Iterator::zip(info_dst.iter_mut(), info.iter()) {
-                    *d = *v;
-                }
-            }
-
-            let src_ptr = data as *const u8;
-            let dataptr = buf.as_mut_ptr().cast::<u8>();
-
-            for i in 0..size {
-                *dataptr.add(i) = *src_ptr.add(i);
-            }
-
-            std::mem::forget(val);
-
-            Self {
-                inner: StackNodeStorage::Stack(LazyStack {
-                    _align: [],
-                    buf,
-                    dropped: false,
-                }),
-            }
+            inner: smallbox!(move |f| {
+                let val = slot.take().expect("cannot call LazyNodes twice");
+                val(f)
+            }),
         }
         }
     }
     }
 
 
@@ -163,88 +54,7 @@ impl<'a, 'b> LazyNodes<'a, 'b> {
     /// let node = f.call(cac);
     /// let node = f.call(cac);
     /// ```
     /// ```
     #[must_use]
     #[must_use]
-    pub fn call(self, f: &'a ScopeState) -> VNode<'a> {
-        match self.inner {
-            StackNodeStorage::Heap(mut lazy) => {
-                lazy(Some(f)).expect("Closure should not be called twice")
-            }
-            StackNodeStorage::Stack(mut stack) => stack.call(f),
-        }
+    pub fn call(mut self, f: &'a ScopeState) -> VNode<'a> {
+        (self.inner)(f)
     }
     }
 }
 }
-
-struct LazyStack {
-    _align: [u64; 0],
-    buf: StackHeapSize,
-    dropped: bool,
-}
-
-impl LazyStack {
-    fn call<'a>(&mut self, f: &'a ScopeState) -> VNode<'a> {
-        let LazyStack { buf, .. } = self;
-        let data = buf.as_ref();
-
-        let info_size =
-            mem::size_of::<*mut dyn FnMut(Option<&'a ScopeState>) -> Option<VNode<'a>>>()
-                / mem::size_of::<usize>()
-                - 1;
-
-        let info_ofs = data.len() - info_size;
-
-        let g: *mut dyn FnMut(Option<&'a ScopeState>) -> Option<VNode<'a>> =
-            unsafe { make_fat_ptr(data[..].as_ptr() as usize, &data[info_ofs..]) };
-
-        self.dropped = true;
-
-        let clos = unsafe { &mut *g };
-        clos(Some(f)).unwrap()
-    }
-}
-impl Drop for LazyStack {
-    fn drop(&mut self) {
-        if !self.dropped {
-            let LazyStack { buf, .. } = self;
-            let data = buf.as_ref();
-
-            let info_size =
-                mem::size_of::<*mut dyn FnMut(Option<&ScopeState>) -> Option<VNode<'_>>>()
-                    / mem::size_of::<usize>()
-                    - 1;
-
-            let info_ofs = data.len() - info_size;
-
-            let g: *mut dyn FnMut(Option<&ScopeState>) -> Option<VNode<'_>> =
-                unsafe { make_fat_ptr(data[..].as_ptr() as usize, &data[info_ofs..]) };
-
-            self.dropped = true;
-
-            let clos = unsafe { &mut *g };
-            clos(None);
-        }
-    }
-}
-
-/// Obtain mutable access to a pointer's words
-fn ptr_as_slice<T>(ptr: &mut T) -> &mut [usize] {
-    assert!(mem::size_of::<T>() % mem::size_of::<usize>() == 0);
-    let words = mem::size_of::<T>() / mem::size_of::<usize>();
-    // SAFE: Points to valid memory (a raw pointer)
-    unsafe { core::slice::from_raw_parts_mut(ptr as *mut _ as *mut usize, words) }
-}
-
-/// Re-construct a fat pointer
-unsafe fn make_fat_ptr<T: ?Sized>(data_ptr: usize, meta_vals: &[usize]) -> *mut T {
-    let mut rv = mem::MaybeUninit::<*mut T>::uninit();
-    {
-        let s = ptr_as_slice(&mut rv);
-        s[0] = data_ptr;
-        s[1..].copy_from_slice(meta_vals);
-    }
-    let rv = rv.assume_init();
-    assert_eq!(rv as *const (), data_ptr as *const ());
-    rv
-}
-
-fn round_to_words(len: usize) -> usize {
-    (len + mem::size_of::<usize>() - 1) / mem::size_of::<usize>()
-}

+ 3 - 3
packages/core/src/lib.rs

@@ -81,9 +81,9 @@ pub use crate::innerlude::{
 /// This includes types like [`Scope`], [`Element`], and [`Component`].
 /// This includes types like [`Scope`], [`Element`], and [`Component`].
 pub mod prelude {
 pub mod prelude {
     pub use crate::innerlude::{
     pub use crate::innerlude::{
-        fc_to_builder, Element, Event, EventHandler, Fragment, LazyNodes, Properties, Scope,
-        ScopeId, ScopeState, Scoped, TaskId, Template, TemplateAttribute, TemplateNode, VNode,
-        VirtualDom,
+        fc_to_builder, Component, Element, Event, EventHandler, Fragment, LazyNodes, Properties,
+        Scope, ScopeId, ScopeState, Scoped, TaskId, Template, TemplateAttribute, TemplateNode,
+        VNode, VirtualDom,
     };
     };
 }
 }
 
 

+ 1 - 1
packages/core/src/mutations.rs

@@ -1,4 +1,4 @@
-use fxhash::FxHashSet;
+use rustc_hash::FxHashSet;
 
 
 use crate::{arena::ElementId, ScopeId, Template};
 use crate::{arena::ElementId, ScopeId, Template};
 
 

+ 6 - 0
packages/core/src/nodes.rs

@@ -441,6 +441,12 @@ impl<'a> IntoDynNode<'a> for VNode<'a> {
     }
     }
 }
 }
 
 
+impl<'a> IntoDynNode<'a> for DynamicNode<'a> {
+    fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
+        self
+    }
+}
+
 // An element that's an error is currently lost into the ether
 // An element that's an error is currently lost into the ether
 impl<'a> IntoDynNode<'a> for Element<'a> {
 impl<'a> IntoDynNode<'a> for Element<'a> {
     fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
     fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {

+ 13 - 6
packages/core/src/scope_arena.rs

@@ -8,6 +8,7 @@ use crate::{
     scopes::{ScopeId, ScopeState},
     scopes::{ScopeId, ScopeState},
     virtual_dom::VirtualDom,
     virtual_dom::VirtualDom,
 };
 };
+use bumpalo::Bump;
 use futures_util::FutureExt;
 use futures_util::FutureExt;
 use std::{
 use std::{
     mem,
     mem,
@@ -34,8 +35,8 @@ impl VirtualDom {
             props: Some(props),
             props: Some(props),
             name,
             name,
             placeholder: Default::default(),
             placeholder: Default::default(),
-            node_arena_1: BumpFrame::new(50),
-            node_arena_2: BumpFrame::new(50),
+            node_arena_1: BumpFrame::new(0),
+            node_arena_2: BumpFrame::new(0),
             spawned_tasks: Default::default(),
             spawned_tasks: Default::default(),
             render_cnt: Default::default(),
             render_cnt: Default::default(),
             hook_arena: Default::default(),
             hook_arena: Default::default(),
@@ -62,7 +63,13 @@ impl VirtualDom {
         let mut new_nodes = unsafe {
         let mut new_nodes = unsafe {
             let scope = self.scopes[scope_id.0].as_mut();
             let scope = self.scopes[scope_id.0].as_mut();
 
 
-            scope.previous_frame_mut().bump.reset();
+            // if this frame hasn't been intialized yet, we can guess the size of the next frame to be more efficient
+            if scope.previous_frame().bump.allocated_bytes() == 0 {
+                scope.previous_frame_mut().bump =
+                    Bump::with_capacity(scope.current_frame().bump.allocated_bytes());
+            } else {
+                scope.previous_frame_mut().bump.reset();
+            }
 
 
             // Make sure to reset the hook counter so we give out hooks in the right order
             // Make sure to reset the hook counter so we give out hooks in the right order
             scope.hook_idx.set(0);
             scope.hook_idx.set(0);
@@ -128,8 +135,8 @@ impl VirtualDom {
         let frame = scope.previous_frame();
         let frame = scope.previous_frame();
 
 
         // set the new head of the bump frame
         // set the new head of the bump frame
-        let alloced = &*frame.bump.alloc(new_nodes);
-        frame.node.set(alloced);
+        let allocated = &*frame.bump.alloc(new_nodes);
+        frame.node.set(allocated);
 
 
         // And move the render generation forward by one
         // And move the render generation forward by one
         scope.render_cnt.set(scope.render_cnt.get() + 1);
         scope.render_cnt.set(scope.render_cnt.get() + 1);
@@ -141,6 +148,6 @@ impl VirtualDom {
         });
         });
 
 
         // rebind the lifetime now that its stored internally
         // rebind the lifetime now that its stored internally
-        unsafe { mem::transmute(alloced) }
+        unsafe { mem::transmute(allocated) }
     }
     }
 }
 }

+ 3 - 3
packages/core/src/scopes.rs

@@ -10,10 +10,10 @@ use crate::{
     Attribute, AttributeValue, Element, Event, Properties, TaskId,
     Attribute, AttributeValue, Element, Event, Properties, TaskId,
 };
 };
 use bumpalo::{boxed::Box as BumpBox, Bump};
 use bumpalo::{boxed::Box as BumpBox, Bump};
+use rustc_hash::{FxHashMap, FxHashSet};
 use std::{
 use std::{
     any::{Any, TypeId},
     any::{Any, TypeId},
     cell::{Cell, RefCell},
     cell::{Cell, RefCell},
-    collections::{HashMap, HashSet},
     fmt::Arguments,
     fmt::Arguments,
     future::Future,
     future::Future,
     rc::Rc,
     rc::Rc,
@@ -82,10 +82,10 @@ pub struct ScopeState {
     pub(crate) hook_list: RefCell<Vec<*mut dyn Any>>,
     pub(crate) hook_list: RefCell<Vec<*mut dyn Any>>,
     pub(crate) hook_idx: Cell<usize>,
     pub(crate) hook_idx: Cell<usize>,
 
 
-    pub(crate) shared_contexts: RefCell<HashMap<TypeId, Box<dyn Any>>>,
+    pub(crate) shared_contexts: RefCell<FxHashMap<TypeId, Box<dyn Any>>>,
 
 
     pub(crate) tasks: Rc<Scheduler>,
     pub(crate) tasks: Rc<Scheduler>,
-    pub(crate) spawned_tasks: HashSet<TaskId>,
+    pub(crate) spawned_tasks: FxHashSet<TaskId>,
 
 
     pub(crate) props: Option<Box<dyn AnyProps<'static>>>,
     pub(crate) props: Option<Box<dyn AnyProps<'static>>>,
     pub(crate) placeholder: Cell<Option<ElementId>>,
     pub(crate) placeholder: Cell<Option<ElementId>>,

+ 3 - 9
packages/core/src/virtual_dom.rs

@@ -14,15 +14,9 @@ use crate::{
     AttributeValue, Element, Event, Scope, SuspenseContext,
     AttributeValue, Element, Event, Scope, SuspenseContext,
 };
 };
 use futures_util::{pin_mut, StreamExt};
 use futures_util::{pin_mut, StreamExt};
+use rustc_hash::FxHashMap;
 use slab::Slab;
 use slab::Slab;
-use std::{
-    any::Any,
-    borrow::BorrowMut,
-    cell::Cell,
-    collections::{BTreeSet, HashMap},
-    future::Future,
-    rc::Rc,
-};
+use std::{any::Any, borrow::BorrowMut, cell::Cell, collections::BTreeSet, future::Future, rc::Rc};
 
 
 /// A virtual node system that progresses user events and diffs UI trees.
 /// A virtual node system that progresses user events and diffs UI trees.
 ///
 ///
@@ -148,7 +142,7 @@ use std::{
 /// }
 /// }
 /// ```
 /// ```
 pub struct VirtualDom {
 pub struct VirtualDom {
-    pub(crate) templates: HashMap<TemplateId, Template<'static>>,
+    pub(crate) templates: FxHashMap<TemplateId, Template<'static>>,
     pub(crate) scopes: Slab<Box<ScopeState>>,
     pub(crate) scopes: Slab<Box<ScopeState>>,
     pub(crate) dirty_scopes: BTreeSet<DirtyScope>,
     pub(crate) dirty_scopes: BTreeSet<DirtyScope>,
     pub(crate) scheduler: Rc<Scheduler>,
     pub(crate) scheduler: Rc<Scheduler>,

+ 5 - 1
packages/desktop/src/controller.rs

@@ -5,6 +5,7 @@ use futures_channel::mpsc::{unbounded, UnboundedSender};
 use futures_util::StreamExt;
 use futures_util::StreamExt;
 #[cfg(target_os = "ios")]
 #[cfg(target_os = "ios")]
 use objc::runtime::Object;
 use objc::runtime::Object;
+use serde_json::Value;
 use std::{
 use std::{
     collections::HashMap,
     collections::HashMap,
     sync::Arc,
     sync::Arc,
@@ -19,6 +20,7 @@ use wry::{
 
 
 pub(super) struct DesktopController {
 pub(super) struct DesktopController {
     pub(super) webviews: HashMap<WindowId, WebView>,
     pub(super) webviews: HashMap<WindowId, WebView>,
+    pub(super) eval_sender: tokio::sync::mpsc::UnboundedSender<Value>,
     pub(super) pending_edits: Arc<Mutex<Vec<String>>>,
     pub(super) pending_edits: Arc<Mutex<Vec<String>>>,
     pub(super) quit_app_on_close: bool,
     pub(super) quit_app_on_close: bool,
     pub(super) is_ready: Arc<AtomicBool>,
     pub(super) is_ready: Arc<AtomicBool>,
@@ -43,6 +45,7 @@ impl DesktopController {
 
 
         let pending_edits = edit_queue.clone();
         let pending_edits = edit_queue.clone();
         let desktop_context_proxy = proxy.clone();
         let desktop_context_proxy = proxy.clone();
+        let (eval_sender, eval_reciever) = tokio::sync::mpsc::unbounded_channel::<Value>();
 
 
         std::thread::spawn(move || {
         std::thread::spawn(move || {
             // We create the runtime as multithreaded, so you can still "tokio::spawn" onto multiple threads
             // We create the runtime as multithreaded, so you can still "tokio::spawn" onto multiple threads
@@ -54,7 +57,7 @@ impl DesktopController {
 
 
             runtime.block_on(async move {
             runtime.block_on(async move {
                 let mut dom = VirtualDom::new_with_props(root, props)
                 let mut dom = VirtualDom::new_with_props(root, props)
-                    .with_root_context(DesktopContext::new(desktop_context_proxy));
+                    .with_root_context(DesktopContext::new(desktop_context_proxy, eval_reciever));
                 {
                 {
                     let edits = dom.rebuild();
                     let edits = dom.rebuild();
                     let mut queue = edit_queue.lock().unwrap();
                     let mut queue = edit_queue.lock().unwrap();
@@ -88,6 +91,7 @@ impl DesktopController {
 
 
         Self {
         Self {
             pending_edits,
             pending_edits,
+            eval_sender,
             webviews: HashMap::new(),
             webviews: HashMap::new(),
             is_ready: Arc::new(AtomicBool::new(false)),
             is_ready: Arc::new(AtomicBool::new(false)),
             quit_app_on_close: true,
             quit_app_on_close: true,

+ 59 - 19
packages/desktop/src/desktop_context.rs

@@ -2,6 +2,11 @@ use std::rc::Rc;
 
 
 use crate::controller::DesktopController;
 use crate::controller::DesktopController;
 use dioxus_core::ScopeState;
 use dioxus_core::ScopeState;
+use serde::de::Error;
+use serde_json::Value;
+use std::future::Future;
+use std::future::IntoFuture;
+use std::pin::Pin;
 use wry::application::event_loop::ControlFlow;
 use wry::application::event_loop::ControlFlow;
 use wry::application::event_loop::EventLoopProxy;
 use wry::application::event_loop::EventLoopProxy;
 #[cfg(target_os = "ios")]
 #[cfg(target_os = "ios")]
@@ -19,16 +24,6 @@ pub fn use_window(cx: &ScopeState) -> &DesktopContext {
         .unwrap()
         .unwrap()
 }
 }
 
 
-/// Get a closure that executes any JavaScript in the WebView context.
-pub fn use_eval(cx: &ScopeState) -> &Rc<dyn Fn(String)> {
-    let desktop = use_window(cx);
-
-    &*cx.use_hook(|| {
-        let desktop = desktop.clone();
-        Rc::new(move |script| desktop.eval(script))
-    } as Rc<dyn Fn(String)>)
-}
-
 /// An imperative interface to the current window.
 /// An imperative interface to the current window.
 ///
 ///
 /// To get a handle to the current window, use the [`use_window`] hook.
 /// To get a handle to the current window, use the [`use_window`] hook.
@@ -45,11 +40,18 @@ pub fn use_eval(cx: &ScopeState) -> &Rc<dyn Fn(String)> {
 pub struct DesktopContext {
 pub struct DesktopContext {
     /// The wry/tao proxy to the current window
     /// The wry/tao proxy to the current window
     pub proxy: ProxyType,
     pub proxy: ProxyType,
+    pub(super) eval_reciever: Rc<tokio::sync::Mutex<tokio::sync::mpsc::UnboundedReceiver<Value>>>,
 }
 }
 
 
 impl DesktopContext {
 impl DesktopContext {
-    pub(crate) fn new(proxy: ProxyType) -> Self {
-        Self { proxy }
+    pub(crate) fn new(
+        proxy: ProxyType,
+        eval_reciever: tokio::sync::mpsc::UnboundedReceiver<Value>,
+    ) -> Self {
+        Self {
+            proxy,
+            eval_reciever: Rc::new(tokio::sync::Mutex::new(eval_reciever)),
+        }
     }
     }
 
 
     /// trigger the drag-window event
     /// trigger the drag-window event
@@ -242,6 +244,18 @@ impl DesktopController {
             Resizable(state) => window.set_resizable(state),
             Resizable(state) => window.set_resizable(state),
             AlwaysOnTop(state) => window.set_always_on_top(state),
             AlwaysOnTop(state) => window.set_always_on_top(state),
 
 
+            Eval(code) => {
+                let script = format!(
+                    r#"window.ipc.postMessage(JSON.stringify({{"method":"eval_result", params: (function(){{
+                        {}
+                    }})()}}));"#,
+                    code
+                );
+                if let Err(e) = webview.evaluate_script(&script) {
+                    // we can't panic this error.
+                    log::warn!("Eval script error: {e}");
+                }
+            }
             CursorVisible(state) => window.set_cursor_visible(state),
             CursorVisible(state) => window.set_cursor_visible(state),
             CursorGrab(state) => {
             CursorGrab(state) => {
                 let _ = window.set_cursor_grab(state);
                 let _ = window.set_cursor_grab(state);
@@ -265,13 +279,6 @@ impl DesktopController {
                 log::warn!("Devtools are disabled in release builds");
                 log::warn!("Devtools are disabled in release builds");
             }
             }
 
 
-            Eval(code) => {
-                if let Err(e) = webview.evaluate_script(code.as_str()) {
-                    // we can't panic this error.
-                    log::warn!("Eval script error: {e}");
-                }
-            }
-
             #[cfg(target_os = "ios")]
             #[cfg(target_os = "ios")]
             PushView(view) => unsafe {
             PushView(view) => unsafe {
                 use objc::runtime::Object;
                 use objc::runtime::Object;
@@ -301,6 +308,39 @@ impl DesktopController {
     }
     }
 }
 }
 
 
+/// Get a closure that executes any JavaScript in the WebView context.
+pub fn use_eval<S: std::string::ToString>(cx: &ScopeState) -> &dyn Fn(S) -> EvalResult {
+    let desktop = use_window(cx).clone();
+    cx.use_hook(|| {
+        move |script| {
+            desktop.eval(script);
+            let recv = desktop.eval_reciever.clone();
+            EvalResult { reciever: recv }
+        }
+    })
+}
+
+/// A future that resolves to the result of a JavaScript evaluation.
+pub struct EvalResult {
+    reciever: Rc<tokio::sync::Mutex<tokio::sync::mpsc::UnboundedReceiver<serde_json::Value>>>,
+}
+
+impl IntoFuture for EvalResult {
+    type Output = Result<serde_json::Value, serde_json::Error>;
+
+    type IntoFuture = Pin<Box<dyn Future<Output = Result<serde_json::Value, serde_json::Error>>>>;
+
+    fn into_future(self) -> Self::IntoFuture {
+        Box::pin(async move {
+            let mut reciever = self.reciever.lock().await;
+            match reciever.recv().await {
+                Some(result) => Ok(result),
+                None => Err(serde_json::Error::custom("No result returned")),
+            }
+        }) as Pin<Box<dyn Future<Output = Result<serde_json::Value, serde_json::Error>>>>
+    }
+}
+
 #[cfg(target_os = "ios")]
 #[cfg(target_os = "ios")]
 fn is_main_thread() -> bool {
 fn is_main_thread() -> bool {
     use objc::runtime::{Class, BOOL, NO};
     use objc::runtime::{Class, BOOL, NO};

+ 7 - 1
packages/desktop/src/lib.rs

@@ -17,7 +17,7 @@ use std::sync::atomic::AtomicBool;
 use std::sync::Arc;
 use std::sync::Arc;
 
 
 use desktop_context::UserWindowEvent;
 use desktop_context::UserWindowEvent;
-pub use desktop_context::{use_eval, use_window, DesktopContext};
+pub use desktop_context::{use_eval, use_window, DesktopContext, EvalResult};
 use futures_channel::mpsc::UnboundedSender;
 use futures_channel::mpsc::UnboundedSender;
 pub use wry;
 pub use wry;
 pub use wry::application as tao;
 pub use wry::application as tao;
@@ -142,6 +142,7 @@ impl DesktopController {
             event_loop,
             event_loop,
             self.is_ready.clone(),
             self.is_ready.clone(),
             self.proxy.clone(),
             self.proxy.clone(),
+            self.eval_sender.clone(),
             self.event_tx.clone(),
             self.event_tx.clone(),
         );
         );
 
 
@@ -154,6 +155,7 @@ fn build_webview(
     event_loop: &tao::event_loop::EventLoopWindowTarget<UserWindowEvent>,
     event_loop: &tao::event_loop::EventLoopWindowTarget<UserWindowEvent>,
     is_ready: Arc<AtomicBool>,
     is_ready: Arc<AtomicBool>,
     proxy: tao::event_loop::EventLoopProxy<UserWindowEvent>,
     proxy: tao::event_loop::EventLoopProxy<UserWindowEvent>,
+    eval_sender: tokio::sync::mpsc::UnboundedSender<serde_json::Value>,
     event_tx: UnboundedSender<serde_json::Value>,
     event_tx: UnboundedSender<serde_json::Value>,
 ) -> wry::webview::WebView {
 ) -> wry::webview::WebView {
     let builder = cfg.window.clone();
     let builder = cfg.window.clone();
@@ -183,6 +185,10 @@ fn build_webview(
         .with_ipc_handler(move |_window: &Window, payload: String| {
         .with_ipc_handler(move |_window: &Window, payload: String| {
             parse_ipc_message(&payload)
             parse_ipc_message(&payload)
                 .map(|message| match message.method() {
                 .map(|message| match message.method() {
+                    "eval_result" => {
+                        let result = message.params();
+                        eval_sender.send(result).unwrap();
+                    }
                     "user_event" => {
                     "user_event" => {
                         _ = event_tx.unbounded_send(message.params());
                         _ = event_tx.unbounded_send(message.params());
                     }
                     }

+ 4 - 4
packages/desktop/src/protocol.rs

@@ -104,10 +104,10 @@ fn get_asset_root() -> Option<PathBuf> {
     #[cfg(target_os = "macos")]
     #[cfg(target_os = "macos")]
     {
     {
         let bundle = core_foundation::bundle::CFBundle::main_bundle();
         let bundle = core_foundation::bundle::CFBundle::main_bundle();
-        let bundle_path = dbg!(bundle.path()?);
-        let resources_path = dbg!(bundle.resources_path()?);
-        let absolute_resources_root = dbg!(bundle_path.join(resources_path));
-        let canonical_resources_root = dbg!(dunce::canonicalize(absolute_resources_root).ok()?);
+        let bundle_path = bundle.path()?;
+        let resources_path = bundle.resources_path()?;
+        let absolute_resources_root = bundle_path.join(resources_path);
+        let canonical_resources_root = dunce::canonicalize(absolute_resources_root).ok()?;
 
 
         return Some(canonical_resources_root);
         return Some(canonical_resources_root);
     }
     }

+ 0 - 2
packages/dioxus/src/lib.rs

@@ -1,5 +1,3 @@
-#![doc = include_str!("../../../notes/README.md")]
-
 pub use dioxus_core as core;
 pub use dioxus_core as core;
 
 
 #[cfg(feature = "hooks")]
 #[cfg(feature = "hooks")]

+ 3 - 1
packages/interpreter/Cargo.toml

@@ -17,8 +17,10 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
 wasm-bindgen = { version = "0.2.79", optional = true }
 wasm-bindgen = { version = "0.2.79", optional = true }
 js-sys = { version = "0.3.56", optional = true }
 js-sys = { version = "0.3.56", optional = true }
 web-sys = { version = "0.3.56", optional = true, features = ["Element", "Node"] }
 web-sys = { version = "0.3.56", optional = true, features = ["Element", "Node"] }
-
+sledgehammer_bindgen = { version = "0.1.3", optional = true }
+sledgehammer_utils = { version = "0.1.0", optional = true }
 
 
 [features]
 [features]
 default = []
 default = []
 web = ["wasm-bindgen", "js-sys", "web-sys"]
 web = ["wasm-bindgen", "js-sys", "web-sys"]
+sledgehammer = ["wasm-bindgen", "js-sys", "web-sys", "sledgehammer_bindgen", "sledgehammer_utils"]

+ 3 - 3
packages/interpreter/src/bindings.rs

@@ -17,9 +17,6 @@ extern "C" {
     #[wasm_bindgen(method)]
     #[wasm_bindgen(method)]
     pub fn MountToRoot(this: &Interpreter);
     pub fn MountToRoot(this: &Interpreter);
 
 
-    #[wasm_bindgen(method)]
-    pub fn AppendChildren(this: &Interpreter, m: u32, id: u32);
-
     #[wasm_bindgen(method)]
     #[wasm_bindgen(method)]
     pub fn AssignId(this: &Interpreter, path: &[u8], id: u32);
     pub fn AssignId(this: &Interpreter, path: &[u8], id: u32);
 
 
@@ -76,4 +73,7 @@ extern "C" {
 
 
     #[wasm_bindgen(method)]
     #[wasm_bindgen(method)]
     pub fn PushRoot(this: &Interpreter, id: u32);
     pub fn PushRoot(this: &Interpreter, id: u32);
+
+    #[wasm_bindgen(method)]
+    pub fn AppendChildren(this: &Interpreter, id: u32, m: u32);
 }
 }

+ 2 - 1
packages/interpreter/src/interpreter.js

@@ -90,7 +90,8 @@ export class Interpreter {
     this.stack.pop();
     this.stack.pop();
   }
   }
   AppendChildren(many) {
   AppendChildren(many) {
-    let root = this.stack[this.stack.length - (1 + many)];
+    // let root = this.nodes[id];
+    let root = this.stack[this.stack.length - 1 - many];
     let to_add = this.stack.splice(this.stack.length - many);
     let to_add = this.stack.splice(this.stack.length - many);
     for (let i = 0; i < many; i++) {
     for (let i = 0; i < many; i++) {
       root.appendChild(to_add[i]);
       root.appendChild(to_add[i]);

+ 5 - 0
packages/interpreter/src/lib.rs

@@ -1,5 +1,10 @@
 pub static INTERPRETER_JS: &str = include_str!("./interpreter.js");
 pub static INTERPRETER_JS: &str = include_str!("./interpreter.js");
 
 
+#[cfg(feature = "sledgehammer")]
+mod sledgehammer_bindings;
+#[cfg(feature = "sledgehammer")]
+pub use sledgehammer_bindings::*;
+
 #[cfg(feature = "web")]
 #[cfg(feature = "web")]
 mod bindings;
 mod bindings;
 
 

+ 231 - 0
packages/interpreter/src/sledgehammer_bindings.rs

@@ -0,0 +1,231 @@
+use js_sys::Function;
+use sledgehammer_bindgen::bindgen;
+use web_sys::Node;
+
+#[bindgen]
+mod js {
+    const JS: &str = r#"
+    class ListenerMap {
+        constructor(root) {
+            // bubbling events can listen at the root element
+            this.global = {};
+            // non bubbling events listen at the element the listener was created at
+            this.local = {};
+            this.root = null;
+            this.handler = null;
+        }
+
+        create(event_name, element, bubbles) {
+            if (bubbles) {
+                if (this.global[event_name] === undefined) {
+                    this.global[event_name] = {};
+                    this.global[event_name].active = 1;
+                    this.root.addEventListener(event_name, this.handler);
+                } else {
+                    this.global[event_name].active++;
+                }
+            }
+            else {
+                const id = element.getAttribute("data-dioxus-id");
+                if (!this.local[id]) {
+                    this.local[id] = {};
+                }
+                element.addEventListener(event_name, this.handler);
+            }
+        }
+
+        remove(element, event_name, bubbles) {
+            if (bubbles) {
+                this.global[event_name].active--;
+                if (this.global[event_name].active === 0) {
+                    this.root.removeEventListener(event_name, this.global[event_name].callback);
+                    delete this.global[event_name];
+                }
+            }
+            else {
+                const id = element.getAttribute("data-dioxus-id");
+                delete this.local[id][event_name];
+                if (this.local[id].length === 0) {
+                    delete this.local[id];
+                }
+                element.removeEventListener(event_name, this.handler);
+            }
+        }
+
+        removeAllNonBubbling(element) {
+            const id = element.getAttribute("data-dioxus-id");
+            delete this.local[id];
+        }
+    }
+    function SetAttributeInner(node, field, value, ns) {
+        const name = field;
+        if (ns === "style") {
+            // ????? why do we need to do this
+            if (node.style === undefined) {
+                node.style = {};
+            }
+            node.style[name] = value;
+        } else if (ns !== null && ns !== undefined && ns !== "") {
+            node.setAttributeNS(ns, name, value);
+        } else {
+            switch (name) {
+                case "value":
+                    if (value !== node.value) {
+                        node.value = value;
+                    }
+                    break;
+                case "checked":
+                    node.checked = value === "true";
+                    break;
+                case "selected":
+                    node.selected = value === "true";
+                    break;
+                case "dangerous_inner_html":
+                    node.innerHTML = value;
+                    break;
+                default:
+                    // https://github.com/facebook/react/blob/8b88ac2592c5f555f315f9440cbb665dd1e7457a/packages/react-dom/src/shared/DOMProperty.js#L352-L364
+                    if (value === "false" && bool_attrs.hasOwnProperty(name)) {
+                        node.removeAttribute(name);
+                    } else {
+                        node.setAttribute(name, value);
+                    }
+            }
+        }
+    }
+    function LoadChild(ptr, len) {
+        // iterate through each number and get that child
+        node = stack[stack.length - 1];
+        ptr_end = ptr + len;
+        for (; ptr < ptr_end; ptr++) {
+            end = m.getUint8(ptr);
+            for (node = node.firstChild; end > 0; end--) {
+                node = node.nextSibling;
+            }
+        }
+        return node;
+    }
+    const listeners = new ListenerMap();
+    let nodes = [];
+    let stack = [];
+    const templates = {};
+    let node, els, end, ptr_end, k;
+    export function save_template(nodes, tmpl_id) {
+        templates[tmpl_id] = nodes;
+    }
+    export function set_node(id, node) {
+        nodes[id] = node;
+    }
+    export function initilize(root, handler) {
+        listeners.handler = handler;
+        nodes = [root];
+        stack = [root];
+        listeners.root = root;
+    }
+    function AppendChildren(id, many){
+        root = nodes[id];
+        els = stack.splice(stack.length-many);
+        for (k = 0; k < many; k++) {
+            root.appendChild(els[k]);
+        }
+    }
+    "#;
+
+    extern "C" {
+        #[wasm_bindgen]
+        pub fn save_template(nodes: Vec<Node>, tmpl_id: u32);
+
+        #[wasm_bindgen]
+        pub fn set_node(id: u32, node: Node);
+
+        #[wasm_bindgen]
+        pub fn initilize(root: Node, handler: &Function);
+    }
+
+    fn mount_to_root() {
+        "{AppendChildren(root, stack.length-1);}"
+    }
+    fn push_root(root: u32) {
+        "{stack.push(nodes[$root$]);}"
+    }
+    fn append_children(id: u32, many: u32) {
+        "{AppendChildren($id$, $many$);}"
+    }
+    fn pop_root() {
+        "{stack.pop();}"
+    }
+    fn replace_with(id: u32, n: u32) {
+        "{root = nodes[$id$]; els = stack.splice(stack.length-$n$); if (root.listening) { listeners.removeAllNonBubbling(root); } root.replaceWith(...els);}"
+    }
+    fn insert_after(id: u32, n: u32) {
+        "{nodes[$id$].after(...stack.splice(stack.length-$n$));}"
+    }
+    fn insert_before(id: u32, n: u32) {
+        "{nodes[$id$].before(...stack.splice(stack.length-$n$));}"
+    }
+    fn remove(id: u32) {
+        "{node = nodes[$id$]; if (node !== undefined) { if (node.listening) { listeners.removeAllNonBubbling(node); } node.remove(); }}"
+    }
+    fn create_raw_text(text: &str) {
+        "{stack.push(document.createTextNode($text$));}"
+    }
+    fn create_text_node(text: &str, id: u32) {
+        "{node = document.createTextNode($text$); nodes[$id$] = node; stack.push(node);}"
+    }
+    fn create_placeholder(id: u32) {
+        "{node = document.createElement('pre'); node.hidden = true; stack.push(node); nodes[$id$] = node;}"
+    }
+    fn new_event_listener(event_name: &str<u8, evt>, id: u32, bubbles: u8) {
+        r#"node = nodes[id]; if(node.listening){node.listening += 1;}else{node.listening = 1;} node.setAttribute('data-dioxus-id', `\${id}`); listeners.create($event_name$, node, $bubbles$);"#
+    }
+    fn remove_event_listener(event_name: &str<u8, evt>, id: u32, bubbles: u8) {
+        "{node = nodes[$id$]; node.listening -= 1; node.removeAttribute('data-dioxus-id'); listeners.remove(node, $event_name$, $bubbles$);}"
+    }
+    fn set_text(id: u32, text: &str) {
+        "{nodes[$id$].textContent = $text$;}"
+    }
+    fn set_attribute(id: u32, field: &str<u8, attr>, value: &str, ns: &str<u8, ns_cache>) {
+        "{node = nodes[$id$]; SetAttributeInner(node, $field$, $value$, $ns$);}"
+    }
+    fn remove_attribute(id: u32, field: &str<u8, attr>, ns: &str<u8, ns_cache>) {
+        r#"{name = $field$;
+        node = this.nodes[$id$];
+        if (ns == "style") {
+            node.style.removeProperty(name);
+        } else if (ns !== null && ns !== undefined && ns !== "") {
+            node.removeAttributeNS(ns, name);
+        } else if (name === "value") {
+            node.value = "";
+        } else if (name === "checked") {
+            node.checked = false;
+        } else if (name === "selected") {
+            node.selected = false;
+        } else if (name === "dangerous_inner_html") {
+            node.innerHTML = "";
+        } else {
+            node.removeAttribute(name);
+        }}"#
+    }
+    fn assign_id(ptr: u32, len: u8, id: u32) {
+        "{nodes[$id$] = LoadChild($ptr$, $len$);}"
+    }
+    fn hydrate_text(ptr: u32, len: u8, value: &str, id: u32) {
+        r#"{
+            node = LoadChild($ptr$, $len$);
+            if (node.nodeType == Node.TEXT_NODE) {
+                node.textContent = value;
+            } else {
+                let text = document.createTextNode(value);
+                node.replaceWith(text);
+                node = text;
+            }
+            nodes[$id$] = node;
+        }"#
+    }
+    fn replace_placeholder(ptr: u32, len: u8, n: u32) {
+        "{els = stack.splice(stack.length - $n$); node = LoadChild($ptr$, $len$); node.replaceWith(...els);}"
+    }
+    fn load_template(tmpl_id: u32, index: u32, id: u32) {
+        "{node = templates[$tmpl_id$][$index$].cloneNode(true); nodes[$id$] = node; stack.push(node);}"
+    }
+}

+ 1 - 2
packages/native-core-macro/src/lib.rs

@@ -306,8 +306,7 @@ impl Member {
                     + field.ty.to_token_stream().to_string().as_str())
                     + field.ty.to_token_stream().to_string().as_str())
                 .as_str(),
                 .as_str(),
                 Span::call_site(),
                 Span::call_site(),
-            )
-            .into(),
+            ),
             ident: field.ident.as_ref()?.clone(),
             ident: field.ident.as_ref()?.clone(),
         })
         })
     }
     }

+ 10 - 23
packages/native-core/src/real_dom.rs

@@ -102,11 +102,7 @@ impl<S: State> RealDom<S> {
         self.tree.add_child(node_id, child_id);
         self.tree.add_child(node_id, child_id);
     }
     }
 
 
-    fn create_template_node(
-        &mut self,
-        node: &TemplateNode,
-        mutations_vec: &mut FxHashMap<RealNodeId, NodeMask>,
-    ) -> RealNodeId {
+    fn create_template_node(&mut self, node: &TemplateNode) -> RealNodeId {
         match node {
         match node {
             TemplateNode::Element {
             TemplateNode::Element {
                 tag,
                 tag,
@@ -139,27 +135,18 @@ impl<S: State> RealDom<S> {
                 });
                 });
                 let node_id = self.create_node(node);
                 let node_id = self.create_node(node);
                 for child in *children {
                 for child in *children {
-                    let child_id = self.create_template_node(child, mutations_vec);
+                    let child_id = self.create_template_node(child);
                     self.add_child(node_id, child_id);
                     self.add_child(node_id, child_id);
                 }
                 }
                 node_id
                 node_id
             }
             }
-            TemplateNode::Text { text } => {
-                let node_id = self.create_node(Node::new(NodeType::Text {
-                    text: text.to_string(),
-                }));
-                node_id
-            }
-            TemplateNode::Dynamic { .. } => {
-                let node_id = self.create_node(Node::new(NodeType::Placeholder));
-                node_id
-            }
-            TemplateNode::DynamicText { .. } => {
-                let node_id = self.create_node(Node::new(NodeType::Text {
-                    text: String::new(),
-                }));
-                node_id
-            }
+            TemplateNode::Text { text } => self.create_node(Node::new(NodeType::Text {
+                text: text.to_string(),
+            })),
+            TemplateNode::Dynamic { .. } => self.create_node(Node::new(NodeType::Placeholder)),
+            TemplateNode::DynamicText { .. } => self.create_node(Node::new(NodeType::Text {
+                text: String::new(),
+            })),
         }
         }
     }
     }
 
 
@@ -172,7 +159,7 @@ impl<S: State> RealDom<S> {
         for template in mutations.templates {
         for template in mutations.templates {
             let mut template_root_ids = Vec::new();
             let mut template_root_ids = Vec::new();
             for root in template.roots {
             for root in template.roots {
-                let id = self.create_template_node(root, &mut nodes_updated);
+                let id = self.create_template_node(root);
                 template_root_ids.push(id);
                 template_root_ids.push(id);
             }
             }
             self.templates
             self.templates

+ 484 - 0
packages/native-core/src/utils/cursor.rs

@@ -0,0 +1,484 @@
+use std::cmp::Ordering;
+
+use dioxus_html::input_data::keyboard_types::{Code, Key, Modifiers};
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct Pos {
+    pub col: usize,
+    pub row: usize,
+}
+
+impl Pos {
+    pub fn new(col: usize, row: usize) -> Self {
+        Self { row, col }
+    }
+
+    pub fn up(&mut self, rope: &str) {
+        self.move_row(-1, rope);
+    }
+
+    pub fn down(&mut self, rope: &str) {
+        self.move_row(1, rope);
+    }
+
+    pub fn right(&mut self, rope: &str) {
+        self.move_col(1, rope);
+    }
+
+    pub fn left(&mut self, rope: &str) {
+        self.move_col(-1, rope);
+    }
+
+    pub fn move_row(&mut self, change: i32, rope: &str) {
+        let new = self.row as i32 + change;
+        if new >= 0 && new < rope.lines().count() as i32 {
+            self.row = new as usize;
+        }
+    }
+
+    pub fn move_col(&mut self, change: i32, rope: &str) {
+        self.realize_col(rope);
+        let idx = self.idx(rope) as i32;
+        if idx + change >= 0 && idx + change <= rope.len() as i32 {
+            let len_line = self.len_line(rope) as i32;
+            let new_col = self.col as i32 + change;
+            let diff = new_col - len_line;
+            if diff > 0 {
+                self.down(rope);
+                self.col = 0;
+                self.move_col(diff - 1, rope);
+            } else if new_col < 0 {
+                self.up(rope);
+                self.col = self.len_line(rope);
+                self.move_col(new_col + 1, rope);
+            } else {
+                self.col = new_col as usize;
+            }
+        }
+    }
+
+    pub fn col(&self, rope: &str) -> usize {
+        self.col.min(self.len_line(rope))
+    }
+
+    pub fn row(&self) -> usize {
+        self.row
+    }
+
+    fn len_line(&self, rope: &str) -> usize {
+        let line = rope.lines().nth(self.row).unwrap_or_default();
+        let len = line.len();
+        if len > 0 && line.chars().nth(len - 1) == Some('\n') {
+            len - 1
+        } else {
+            len
+        }
+    }
+
+    pub fn idx(&self, rope: &str) -> usize {
+        rope.lines().take(self.row).map(|l| l.len()).sum::<usize>() + self.col(rope)
+    }
+
+    // the column can be more than the line length, cap it
+    pub fn realize_col(&mut self, rope: &str) {
+        self.col = self.col(rope);
+    }
+}
+
+impl Ord for Pos {
+    fn cmp(&self, other: &Self) -> Ordering {
+        self.row.cmp(&other.row).then(self.col.cmp(&other.col))
+    }
+}
+
+impl PartialOrd for Pos {
+    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+        Some(self.cmp(other))
+    }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct Cursor {
+    pub start: Pos,
+    pub end: Option<Pos>,
+}
+
+impl Cursor {
+    pub fn from_start(pos: Pos) -> Self {
+        Self {
+            start: pos,
+            end: None,
+        }
+    }
+
+    pub fn new(start: Pos, end: Pos) -> Self {
+        Self {
+            start,
+            end: Some(end),
+        }
+    }
+
+    fn move_cursor(&mut self, f: impl FnOnce(&mut Pos), shift: bool) {
+        if shift {
+            self.with_end(f);
+        } else {
+            f(&mut self.start);
+            self.end = None;
+        }
+    }
+
+    fn delete_selection(&mut self, text: &mut String) -> [i32; 2] {
+        let first = self.first();
+        let last = self.last();
+        let dr = first.row as i32 - last.row as i32;
+        let dc = if dr != 0 {
+            -(last.col as i32)
+        } else {
+            first.col as i32 - last.col as i32
+        };
+        text.replace_range(first.idx(text)..last.idx(text), "");
+        if let Some(end) = self.end.take() {
+            if self.start > end {
+                self.start = end;
+            }
+        }
+        [dc, dr]
+    }
+
+    pub fn handle_input(
+        &mut self,
+        data: &dioxus_html::KeyboardData,
+        text: &mut String,
+        max_width: usize,
+    ) {
+        use Code::*;
+        match data.code() {
+            ArrowUp => {
+                self.move_cursor(|c| c.up(text), data.modifiers().contains(Modifiers::SHIFT));
+            }
+            ArrowDown => {
+                self.move_cursor(
+                    |c| c.down(text),
+                    data.modifiers().contains(Modifiers::SHIFT),
+                );
+            }
+            ArrowRight => {
+                if data.modifiers().contains(Modifiers::CONTROL) {
+                    self.move_cursor(
+                        |c| {
+                            let mut change = 1;
+                            let idx = c.idx(text);
+                            let length = text.len();
+                            while idx + change < length {
+                                let chr = text.chars().nth(idx + change).unwrap();
+                                if chr.is_whitespace() {
+                                    break;
+                                }
+                                change += 1;
+                            }
+                            c.move_col(change as i32, text);
+                        },
+                        data.modifiers().contains(Modifiers::SHIFT),
+                    );
+                } else {
+                    self.move_cursor(
+                        |c| c.right(text),
+                        data.modifiers().contains(Modifiers::SHIFT),
+                    );
+                }
+            }
+            ArrowLeft => {
+                if data.modifiers().contains(Modifiers::CONTROL) {
+                    self.move_cursor(
+                        |c| {
+                            let mut change = -1;
+                            let idx = c.idx(text) as i32;
+                            while idx + change > 0 {
+                                let chr = text.chars().nth((idx + change) as usize).unwrap();
+                                if chr == ' ' {
+                                    break;
+                                }
+                                change -= 1;
+                            }
+                            c.move_col(change as i32, text);
+                        },
+                        data.modifiers().contains(Modifiers::SHIFT),
+                    );
+                } else {
+                    self.move_cursor(
+                        |c| c.left(text),
+                        data.modifiers().contains(Modifiers::SHIFT),
+                    );
+                }
+            }
+            End => {
+                self.move_cursor(
+                    |c| c.col = c.len_line(text),
+                    data.modifiers().contains(Modifiers::SHIFT),
+                );
+            }
+            Home => {
+                self.move_cursor(|c| c.col = 0, data.modifiers().contains(Modifiers::SHIFT));
+            }
+            Backspace => {
+                self.start.realize_col(text);
+                let mut start_idx = self.start.idx(text);
+                if self.end.is_some() {
+                    self.delete_selection(text);
+                } else if start_idx > 0 {
+                    self.start.left(text);
+                    text.replace_range(start_idx - 1..start_idx, "");
+                    if data.modifiers().contains(Modifiers::CONTROL) {
+                        start_idx = self.start.idx(text);
+                        while start_idx > 0
+                            && text
+                                .chars()
+                                .nth(start_idx - 1)
+                                .filter(|c| *c != ' ')
+                                .is_some()
+                        {
+                            self.start.left(text);
+                            text.replace_range(start_idx - 1..start_idx, "");
+                            start_idx = self.start.idx(text);
+                        }
+                    }
+                }
+            }
+            Enter => {
+                if text.len() + 1 - self.selection_len(text) <= max_width {
+                    text.insert(self.start.idx(text), '\n');
+                    self.start.col = 0;
+                    self.start.down(text);
+                }
+            }
+            Tab => {
+                if text.len() + 1 - self.selection_len(text) <= max_width {
+                    self.start.realize_col(text);
+                    self.delete_selection(text);
+                    text.insert(self.start.idx(text), '\t');
+                    self.start.right(text);
+                }
+            }
+            _ => {
+                self.start.realize_col(text);
+                if let Key::Character(character) = data.key() {
+                    if text.len() + 1 - self.selection_len(text) <= max_width {
+                        self.delete_selection(text);
+                        let character = character.chars().next().unwrap();
+                        text.insert(self.start.idx(text), character);
+                        self.start.right(text);
+                    }
+                }
+            }
+        }
+    }
+
+    pub fn with_end(&mut self, f: impl FnOnce(&mut Pos)) {
+        let mut new = self.end.take().unwrap_or_else(|| self.start.clone());
+        f(&mut new);
+        self.end.replace(new);
+    }
+
+    pub fn first(&self) -> &Pos {
+        if let Some(e) = &self.end {
+            e.min(&self.start)
+        } else {
+            &self.start
+        }
+    }
+
+    pub fn last(&self) -> &Pos {
+        if let Some(e) = &self.end {
+            e.max(&self.start)
+        } else {
+            &self.start
+        }
+    }
+
+    pub fn selection_len(&self, text: &str) -> usize {
+        self.last().idx(text) - self.first().idx(text)
+    }
+}
+
+impl Default for Cursor {
+    fn default() -> Self {
+        Self {
+            start: Pos::new(0, 0),
+            end: None,
+        }
+    }
+}
+
+#[test]
+fn pos_direction_movement() {
+    let mut pos = Pos::new(100, 0);
+    let text = "hello world\nhi";
+
+    assert_eq!(pos.col(text), text.lines().next().unwrap_or_default().len());
+    pos.down(text);
+    assert_eq!(pos.col(text), text.lines().nth(1).unwrap_or_default().len());
+    pos.up(text);
+    assert_eq!(pos.col(text), text.lines().next().unwrap_or_default().len());
+    pos.left(text);
+    assert_eq!(
+        pos.col(text),
+        text.lines().next().unwrap_or_default().len() - 1
+    );
+    pos.right(text);
+    assert_eq!(pos.col(text), text.lines().next().unwrap_or_default().len());
+}
+
+#[test]
+fn pos_col_movement() {
+    let mut pos = Pos::new(100, 0);
+    let text = "hello world\nhi";
+
+    // move inside a row
+    pos.move_col(-5, text);
+    assert_eq!(
+        pos.col(text),
+        text.lines().next().unwrap_or_default().len() - 5
+    );
+    pos.move_col(5, text);
+    assert_eq!(pos.col(text), text.lines().next().unwrap_or_default().len());
+
+    // move between rows
+    pos.move_col(3, text);
+    assert_eq!(pos.col(text), 2);
+    pos.move_col(-3, text);
+    assert_eq!(pos.col(text), text.lines().next().unwrap_or_default().len());
+
+    // don't panic if moving out of range
+    pos.move_col(-100, text);
+    pos.move_col(1000, text);
+}
+
+#[test]
+fn cursor_row_movement() {
+    let mut pos = Pos::new(100, 0);
+    let text = "hello world\nhi";
+
+    pos.move_row(1, text);
+    assert_eq!(pos.row(), 1);
+    pos.move_row(-1, text);
+    assert_eq!(pos.row(), 0);
+
+    // don't panic if moving out of range
+    pos.move_row(-100, text);
+    pos.move_row(1000, text);
+}
+
+#[test]
+fn cursor_input() {
+    let mut cursor = Cursor::from_start(Pos::new(0, 0));
+    let mut text = "hello world\nhi".to_string();
+
+    for _ in 0..5 {
+        cursor.handle_input(
+            &dioxus_html::KeyboardData::new(
+                dioxus_html::input_data::keyboard_types::Key::ArrowRight,
+                dioxus_html::input_data::keyboard_types::Code::ArrowRight,
+                dioxus_html::input_data::keyboard_types::Location::Standard,
+                false,
+                Modifiers::empty(),
+            ),
+            &mut text,
+            10,
+        );
+    }
+
+    for _ in 0..5 {
+        cursor.handle_input(
+            &dioxus_html::KeyboardData::new(
+                dioxus_html::input_data::keyboard_types::Key::Backspace,
+                dioxus_html::input_data::keyboard_types::Code::Backspace,
+                dioxus_html::input_data::keyboard_types::Location::Standard,
+                false,
+                Modifiers::empty(),
+            ),
+            &mut text,
+            10,
+        );
+    }
+
+    assert_eq!(text, " world\nhi");
+
+    let goal_text = "hello world\nhi";
+    let max_width = goal_text.len();
+    cursor.handle_input(
+        &dioxus_html::KeyboardData::new(
+            dioxus_html::input_data::keyboard_types::Key::Character("h".to_string()),
+            dioxus_html::input_data::keyboard_types::Code::KeyH,
+            dioxus_html::input_data::keyboard_types::Location::Standard,
+            false,
+            Modifiers::empty(),
+        ),
+        &mut text,
+        max_width,
+    );
+
+    cursor.handle_input(
+        &dioxus_html::KeyboardData::new(
+            dioxus_html::input_data::keyboard_types::Key::Character("e".to_string()),
+            dioxus_html::input_data::keyboard_types::Code::KeyE,
+            dioxus_html::input_data::keyboard_types::Location::Standard,
+            false,
+            Modifiers::empty(),
+        ),
+        &mut text,
+        max_width,
+    );
+
+    cursor.handle_input(
+        &dioxus_html::KeyboardData::new(
+            dioxus_html::input_data::keyboard_types::Key::Character("l".to_string()),
+            dioxus_html::input_data::keyboard_types::Code::KeyL,
+            dioxus_html::input_data::keyboard_types::Location::Standard,
+            false,
+            Modifiers::empty(),
+        ),
+        &mut text,
+        max_width,
+    );
+
+    cursor.handle_input(
+        &dioxus_html::KeyboardData::new(
+            dioxus_html::input_data::keyboard_types::Key::Character("l".to_string()),
+            dioxus_html::input_data::keyboard_types::Code::KeyL,
+            dioxus_html::input_data::keyboard_types::Location::Standard,
+            false,
+            Modifiers::empty(),
+        ),
+        &mut text,
+        max_width,
+    );
+
+    cursor.handle_input(
+        &dioxus_html::KeyboardData::new(
+            dioxus_html::input_data::keyboard_types::Key::Character("o".to_string()),
+            dioxus_html::input_data::keyboard_types::Code::KeyO,
+            dioxus_html::input_data::keyboard_types::Location::Standard,
+            false,
+            Modifiers::empty(),
+        ),
+        &mut text,
+        max_width,
+    );
+
+    // these should be ignored
+    for _ in 0..10 {
+        cursor.handle_input(
+            &dioxus_html::KeyboardData::new(
+                dioxus_html::input_data::keyboard_types::Key::Character("o".to_string()),
+                dioxus_html::input_data::keyboard_types::Code::KeyO,
+                dioxus_html::input_data::keyboard_types::Location::Standard,
+                false,
+                Modifiers::empty(),
+            ),
+            &mut text,
+            max_width,
+        );
+    }
+
+    assert_eq!(text.to_string(), goal_text);
+}

+ 3 - 0
packages/native-core/src/utils/mod.rs

@@ -0,0 +1,3 @@
+mod persistant_iterator;
+pub use persistant_iterator::*;
+pub mod cursor;

+ 0 - 0
packages/native-core/src/utils.rs → packages/native-core/src/utils/persistant_iterator.rs


+ 6 - 2
packages/rsx/Cargo.toml

@@ -1,8 +1,13 @@
 [package]
 [package]
 name = "dioxus-rsx"
 name = "dioxus-rsx"
-version = "0.0.0"
+version = "0.0.1"
 edition = "2018"
 edition = "2018"
 license = "MIT/Apache-2.0"
 license = "MIT/Apache-2.0"
+description = "Core functionality for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences"
+repository = "https://github.com/DioxusLabs/dioxus/"
+homepage = "https://dioxuslabs.com"
+documentation = "https://docs.rs/dioxus-rsx"
+keywords = ["dom", "ui", "gui", "react", "wasm"]
 
 
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 
@@ -10,5 +15,4 @@ license = "MIT/Apache-2.0"
 proc-macro2 = { version = "1.0", features = ["span-locations"] }
 proc-macro2 = { version = "1.0", features = ["span-locations"] }
 syn = { version = "1.0", features = ["full", "extra-traits"] }
 syn = { version = "1.0", features = ["full", "extra-traits"] }
 quote = { version = "1.0" }
 quote = { version = "1.0" }
-dioxus-core = { path = "../core", features = ["serialize"] }
 serde = { version = "1.0", features = ["derive"] }
 serde = { version = "1.0", features = ["derive"] }

+ 1 - 2
packages/rsx/src/element.rs

@@ -45,7 +45,7 @@ impl Parse for Element {
 
 
                 content.parse::<Token![:]>()?;
                 content.parse::<Token![:]>()?;
 
 
-                if content.peek(LitStr) && content.peek2(Token![,]) {
+                if content.peek(LitStr) {
                     let value = content.parse()?;
                     let value = content.parse()?;
                     attributes.push(ElementAttrNamed {
                     attributes.push(ElementAttrNamed {
                         el_name: el_name.clone(),
                         el_name: el_name.clone(),
@@ -53,7 +53,6 @@ impl Parse for Element {
                     });
                     });
                 } else {
                 } else {
                     let value = content.parse::<Expr>()?;
                     let value = content.parse::<Expr>()?;
-
                     attributes.push(ElementAttrNamed {
                     attributes.push(ElementAttrNamed {
                         el_name: el_name.clone(),
                         el_name: el_name.clone(),
                         attr: ElementAttr::CustomAttrExpression { name, value },
                         attr: ElementAttr::CustomAttrExpression { name, value },

+ 3 - 3
packages/rsx/src/lib.rs

@@ -182,7 +182,7 @@ impl<'a> DynamicContext<'a> {
 
 
                 let static_attrs = el.attributes.iter().map(|attr| match &attr.attr {
                 let static_attrs = el.attributes.iter().map(|attr| match &attr.attr {
                     ElementAttr::AttrText { name, value } if value.is_static() => {
                     ElementAttr::AttrText { name, value } if value.is_static() => {
-                        let value = value.source.as_ref().unwrap();
+                        let value = value.to_static().unwrap();
                         quote! {
                         quote! {
                             ::dioxus::core::TemplateAttribute::Static {
                             ::dioxus::core::TemplateAttribute::Static {
                                 name: dioxus_elements::#el_name::#name.0,
                                 name: dioxus_elements::#el_name::#name.0,
@@ -196,7 +196,7 @@ impl<'a> DynamicContext<'a> {
                     }
                     }
 
 
                     ElementAttr::CustomAttrText { name, value } if value.is_static() => {
                     ElementAttr::CustomAttrText { name, value } if value.is_static() => {
-                        let value = value.source.as_ref().unwrap();
+                        let value = value.to_static().unwrap();
                         quote! {
                         quote! {
                             ::dioxus::core::TemplateAttribute::Static {
                             ::dioxus::core::TemplateAttribute::Static {
                                 name: #name,
                                 name: #name,
@@ -244,7 +244,7 @@ impl<'a> DynamicContext<'a> {
             }
             }
 
 
             BodyNode::Text(text) if text.is_static() => {
             BodyNode::Text(text) if text.is_static() => {
-                let text = text.source.as_ref().unwrap();
+                let text = text.to_static().unwrap();
                 quote! { ::dioxus::core::TemplateNode::Text{ text: #text } }
                 quote! { ::dioxus::core::TemplateNode::Text{ text: #text } }
             }
             }
 
 

+ 1 - 0
packages/tui/Cargo.toml

@@ -13,6 +13,7 @@ license = "MIT/Apache-2.0"
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 
 [dependencies]
 [dependencies]
+dioxus = { path = "../dioxus", version = "^0.2.1" }
 dioxus-core = { path = "../core", version = "^0.2.1" }
 dioxus-core = { path = "../core", version = "^0.2.1" }
 dioxus-html = { path = "../html", version = "^0.2.1" }
 dioxus-html = { path = "../html", version = "^0.2.1" }
 dioxus-native-core = { path = "../native-core", version = "^0.2.0" }
 dioxus-native-core = { path = "../native-core", version = "^0.2.0" }

+ 93 - 0
packages/tui/examples/tui_widgets.rs

@@ -0,0 +1,93 @@
+use dioxus::prelude::*;
+use dioxus_html::FormData;
+use dioxus_tui::prelude::*;
+use dioxus_tui::Config;
+
+fn main() {
+    dioxus_tui::launch_cfg(app, Config::new());
+}
+
+fn app(cx: Scope) -> Element {
+    let bg_green = use_state(cx, || false);
+
+    let color = if *bg_green.get() { "green" } else { "red" };
+    cx.render(rsx! {
+        div{
+            width: "100%",
+            background_color: "{color}",
+            flex_direction: "column",
+            align_items: "center",
+            justify_content: "center",
+
+            Input{
+                oninput: |data: FormData| if &data.value == "good"{
+                    bg_green.set(true);
+                } else{
+                    bg_green.set(false);
+                },
+                r#type: "checkbox",
+                value: "good",
+                width: "50%",
+                height: "10%",
+                checked: "true",
+            }
+            Input{
+                oninput: |data: FormData| if &data.value == "hello world"{
+                    bg_green.set(true);
+                } else{
+                    bg_green.set(false);
+                },
+                width: "50%",
+                height: "10%",
+                maxlength: "11",
+            }
+            Input{
+                oninput: |data: FormData| {
+                    if (data.value.parse::<f32>().unwrap() - 40.0).abs() < 5.0 {
+                        bg_green.set(true);
+                    } else{
+                        bg_green.set(false);
+                    }
+                },
+                r#type: "range",
+                width: "50%",
+                height: "10%",
+                min: "20",
+                max: "80",
+            }
+            Input{
+                oninput: |data: FormData| {
+                    if data.value == "10"{
+                        bg_green.set(true);
+                    } else{
+                        bg_green.set(false);
+                    }
+                },
+                r#type: "number",
+                width: "50%",
+                height: "10%",
+                maxlength: "4",
+            }
+            Input{
+                oninput: |data: FormData| {
+                    if data.value == "hello world"{
+                        bg_green.set(true);
+                    } else{
+                        bg_green.set(false);
+                    }
+                },
+                r#type: "password",
+                width: "50%",
+                height: "10%",
+                maxlength: "11",
+            }
+            Input{
+                onclick: |_: FormData| bg_green.set(true),
+                r#type: "button",
+                value: "green",
+                width: "50%",
+                height: "10%",
+            }
+        }
+    })
+}

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

@@ -287,7 +287,10 @@ impl InnerInputState {
 
 
         fn prepare_mouse_data(mouse_data: &MouseData, layout: &Layout) -> MouseData {
         fn prepare_mouse_data(mouse_data: &MouseData, layout: &Layout) -> MouseData {
             let Point { x, y } = layout.location;
             let Point { x, y } = layout.location;
-            let node_origin = ClientPoint::new(x.into(), y.into());
+            let node_origin = ClientPoint::new(
+                layout_to_screen_space(x).into(),
+                layout_to_screen_space(y).into(),
+            );
 
 
             let new_client_coordinates = (mouse_data.client_coordinates() - node_origin)
             let new_client_coordinates = (mouse_data.client_coordinates() - node_origin)
                 .to_point()
                 .to_point()

+ 54 - 1
packages/tui/src/layout.rs

@@ -7,7 +7,7 @@ use dioxus_native_core::state::ChildDepState;
 use dioxus_native_core_macro::sorted_str_slice;
 use dioxus_native_core_macro::sorted_str_slice;
 use taffy::prelude::*;
 use taffy::prelude::*;
 
 
-use crate::screen_to_layout_space;
+use crate::{screen_to_layout_space, unit_to_layout_space};
 
 
 #[derive(Debug, Clone, Copy, PartialEq)]
 #[derive(Debug, Clone, Copy, PartialEq)]
 pub(crate) enum PossiblyUninitalized<T> {
 pub(crate) enum PossiblyUninitalized<T> {
@@ -105,6 +105,59 @@ impl ChildDepState for TaffyLayout {
                 child_layout.push(l.node.unwrap());
                 child_layout.push(l.node.unwrap());
             }
             }
 
 
+            fn scale_dimention(d: Dimension) -> Dimension {
+                match d {
+                    Dimension::Points(p) => Dimension::Points(unit_to_layout_space(p)),
+                    Dimension::Percent(p) => Dimension::Percent(p),
+                    Dimension::Auto => Dimension::Auto,
+                    Dimension::Undefined => Dimension::Undefined,
+                }
+            }
+            let style = Style {
+                position: Rect {
+                    left: scale_dimention(style.position.left),
+                    right: scale_dimention(style.position.right),
+                    top: scale_dimention(style.position.top),
+                    bottom: scale_dimention(style.position.bottom),
+                },
+                margin: Rect {
+                    left: scale_dimention(style.margin.left),
+                    right: scale_dimention(style.margin.right),
+                    top: scale_dimention(style.margin.top),
+                    bottom: scale_dimention(style.margin.bottom),
+                },
+                padding: Rect {
+                    left: scale_dimention(style.padding.left),
+                    right: scale_dimention(style.padding.right),
+                    top: scale_dimention(style.padding.top),
+                    bottom: scale_dimention(style.padding.bottom),
+                },
+                border: Rect {
+                    left: scale_dimention(style.border.left),
+                    right: scale_dimention(style.border.right),
+                    top: scale_dimention(style.border.top),
+                    bottom: scale_dimention(style.border.bottom),
+                },
+                gap: Size {
+                    width: scale_dimention(style.gap.width),
+                    height: scale_dimention(style.gap.height),
+                },
+                flex_basis: scale_dimention(style.flex_basis),
+                size: Size {
+                    width: scale_dimention(style.size.width),
+                    height: scale_dimention(style.size.height),
+                },
+                min_size: Size {
+                    width: scale_dimention(style.min_size.width),
+                    height: scale_dimention(style.min_size.height),
+                },
+                max_size: Size {
+                    width: scale_dimention(style.max_size.width),
+                    height: scale_dimention(style.max_size.height),
+                },
+                ..style
+            };
+
             if let PossiblyUninitalized::Initialized(n) = self.node {
             if let PossiblyUninitalized::Initialized(n) = self.node {
                 if self.style != style {
                 if self.style != style {
                     taffy.set_style(n, style).unwrap();
                     taffy.set_style(n, style).unwrap();

+ 18 - 3
packages/tui/src/lib.rs

@@ -1,5 +1,6 @@
 use anyhow::Result;
 use anyhow::Result;
 use crossterm::{
 use crossterm::{
+    cursor::{MoveTo, RestorePosition, SavePosition, Show},
     event::{DisableMouseCapture, EnableMouseCapture, Event as TermEvent, KeyCode, KeyModifiers},
     event::{DisableMouseCapture, EnableMouseCapture, Event as TermEvent, KeyCode, KeyModifiers},
     execute,
     execute,
     terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
     terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
@@ -28,11 +29,13 @@ mod focus;
 mod hooks;
 mod hooks;
 mod layout;
 mod layout;
 mod node;
 mod node;
+pub mod prelude;
 pub mod query;
 pub mod query;
 mod render;
 mod render;
 mod style;
 mod style;
 mod style_attributes;
 mod style_attributes;
 mod widget;
 mod widget;
+mod widgets;
 
 
 pub use config::*;
 pub use config::*;
 pub use hooks::*;
 pub use hooks::*;
@@ -43,6 +46,10 @@ pub(crate) fn screen_to_layout_space(screen: u16) -> f32 {
     screen as f32 * 10.0
     screen as f32 * 10.0
 }
 }
 
 
+pub(crate) fn unit_to_layout_space(screen: f32) -> f32 {
+    screen * 10.0
+}
+
 pub(crate) fn layout_to_screen_space(layout: f32) -> f32 {
 pub(crate) fn layout_to_screen_space(layout: f32) -> f32 {
     layout / 10.0
     layout / 10.0
 }
 }
@@ -136,7 +143,13 @@ fn render_vdom(
             let mut terminal = (!cfg.headless).then(|| {
             let mut terminal = (!cfg.headless).then(|| {
                 enable_raw_mode().unwrap();
                 enable_raw_mode().unwrap();
                 let mut stdout = std::io::stdout();
                 let mut stdout = std::io::stdout();
-                execute!(stdout, EnterAlternateScreen, EnableMouseCapture).unwrap();
+                execute!(
+                    stdout,
+                    EnterAlternateScreen,
+                    EnableMouseCapture,
+                    MoveTo(0, 1000)
+                )
+                .unwrap();
                 let backend = CrosstermBackend::new(io::stdout());
                 let backend = CrosstermBackend::new(io::stdout());
                 Terminal::new(backend).unwrap()
                 Terminal::new(backend).unwrap()
             });
             });
@@ -181,14 +194,16 @@ fn render_vdom(
                         taffy.compute_layout(root_node, size).unwrap();
                         taffy.compute_layout(root_node, size).unwrap();
                     }
                     }
                     if let Some(terminal) = &mut terminal {
                     if let Some(terminal) = &mut terminal {
+                        execute!(terminal.backend_mut(), SavePosition).unwrap();
                         terminal.draw(|frame| {
                         terminal.draw(|frame| {
                             let rdom = rdom.borrow();
                             let rdom = rdom.borrow();
                             let mut taffy = taffy.lock().expect("taffy lock poisoned");
                             let mut taffy = taffy.lock().expect("taffy lock poisoned");
                             // size is guaranteed to not change when rendering
                             // size is guaranteed to not change when rendering
-                            resize(frame.size(), &mut *taffy, &rdom);
+                            resize(frame.size(), &mut taffy, &rdom);
                             let root = &rdom[NodeId(0)];
                             let root = &rdom[NodeId(0)];
-                            render::render_vnode(frame, &*taffy, &rdom, root, cfg, Point::ZERO);
+                            render::render_vnode(frame, &taffy, &rdom, root, cfg, Point::ZERO);
                         })?;
                         })?;
+                        execute!(terminal.backend_mut(), RestorePosition, Show).unwrap();
                     } else {
                     } else {
                         let rdom = rdom.borrow();
                         let rdom = rdom.borrow();
                         resize(
                         resize(

+ 1 - 0
packages/tui/src/prelude/mod.rs

@@ -0,0 +1 @@
+pub use crate::widgets::*;

+ 1 - 1
packages/tui/src/query.rs

@@ -89,7 +89,7 @@ impl<'a> ElementRef<'a> {
             .ok();
             .ok();
         layout.map(|layout| Layout {
         layout.map(|layout| Layout {
             order: layout.order,
             order: layout.order,
-            size: layout.size.map(|v| layout_to_screen_space(v)),
+            size: layout.size.map(layout_to_screen_space),
             location: Point {
             location: Point {
                 x: layout_to_screen_space(layout.location.x),
                 x: layout_to_screen_space(layout.location.x),
                 y: layout_to_screen_space(layout.location.y),
                 y: layout_to_screen_space(layout.location.y),

+ 1 - 1
packages/tui/src/render.rs

@@ -41,7 +41,7 @@ pub(crate) fn render_vnode(
     let x = layout_to_screen_space(fx).round() as u16;
     let x = layout_to_screen_space(fx).round() as u16;
     let y = layout_to_screen_space(fy).round() as u16;
     let y = layout_to_screen_space(fy).round() as u16;
     let Size { width, height } = *size;
     let Size { width, height } = *size;
-    let width = layout_to_screen_space(fx + width).round() as u16 + x;
+    let width = layout_to_screen_space(fx + width).round() as u16 - x;
     let height = layout_to_screen_space(fy + height).round() as u16 - y;
     let height = layout_to_screen_space(fy + height).round() as u16 - y;
 
 
     match &node.node_data.node_type {
     match &node.node_data.node_type {

+ 59 - 0
packages/tui/src/widgets/button.rs

@@ -0,0 +1,59 @@
+use std::collections::HashMap;
+
+use dioxus::prelude::*;
+use dioxus_elements::input_data::keyboard_types::Key;
+use dioxus_html as dioxus_elements;
+use dioxus_html::FormData;
+
+#[derive(Props)]
+pub(crate) struct ButtonProps<'a> {
+    #[props(!optional)]
+    raw_onclick: Option<&'a EventHandler<'a, FormData>>,
+    #[props(!optional)]
+    value: Option<&'a str>,
+    #[props(!optional)]
+    width: Option<&'a str>,
+    #[props(!optional)]
+    height: Option<&'a str>,
+}
+
+#[allow(non_snake_case)]
+pub(crate) fn Button<'a>(cx: Scope<'a, ButtonProps>) -> Element<'a> {
+    let state = use_state(cx, || false);
+    let width = cx.props.width.unwrap_or("1px");
+    let height = cx.props.height.unwrap_or("1px");
+
+    let single_char = width == "1px" || height == "1px";
+    let text = if let Some(v) = cx.props.value { v } else { "" };
+    let border_style = if single_char { "none" } else { "solid" };
+    let update = || {
+        let new_state = !state.get();
+        if let Some(callback) = cx.props.raw_onclick {
+            callback.call(FormData {
+                value: text.to_string(),
+                values: HashMap::new(),
+                files: None,
+            });
+        }
+        state.set(new_state);
+    };
+    cx.render(rsx! {
+        div{
+            width: "{width}",
+            height: "{height}",
+            border_style: "{border_style}",
+            flex_direction: "row",
+            align_items: "center",
+            justify_content: "center",
+            onclick: move |_| {
+                update();
+            },
+            onkeydown: move |evt|{
+                if !evt.is_auto_repeating() && match evt.key(){ Key::Character(c) if c == " " =>true, Key::Enter=>true, _=>false }  {
+                    update();
+                }
+            },
+            "{text}"
+        }
+    })
+}

+ 82 - 0
packages/tui/src/widgets/checkbox.rs

@@ -0,0 +1,82 @@
+use std::collections::HashMap;
+
+use dioxus::prelude::*;
+use dioxus_elements::input_data::keyboard_types::Key;
+use dioxus_html as dioxus_elements;
+use dioxus_html::FormData;
+
+#[derive(Props)]
+pub(crate) struct CheckBoxProps<'a> {
+    #[props(!optional)]
+    raw_oninput: Option<&'a EventHandler<'a, FormData>>,
+    #[props(!optional)]
+    value: Option<&'a str>,
+    #[props(!optional)]
+    width: Option<&'a str>,
+    #[props(!optional)]
+    height: Option<&'a str>,
+    #[props(!optional)]
+    checked: Option<&'a str>,
+}
+
+#[allow(non_snake_case)]
+pub(crate) fn CheckBox<'a>(cx: Scope<'a, CheckBoxProps>) -> Element<'a> {
+    let state = use_state(cx, || cx.props.checked.filter(|&c| c == "true").is_some());
+    let width = cx.props.width.unwrap_or("1px");
+    let height = cx.props.height.unwrap_or("1px");
+
+    let single_char = width == "1px" && height == "1px";
+    let text = if single_char {
+        if *state.get() {
+            "☑"
+        } else {
+            "☐"
+        }
+    } else if *state.get() {
+        "✓"
+    } else {
+        " "
+    };
+    let border_style = if width == "1px" || height == "1px" {
+        "none"
+    } else {
+        "solid"
+    };
+    let update = move || {
+        let new_state = !state.get();
+        if let Some(callback) = cx.props.raw_oninput {
+            callback.call(FormData {
+                value: if let Some(value) = &cx.props.value {
+                    if new_state {
+                        value.to_string()
+                    } else {
+                        String::new()
+                    }
+                } else {
+                    "on".to_string()
+                },
+                values: HashMap::new(),
+                files: None,
+            });
+        }
+        state.set(new_state);
+    };
+    cx.render(rsx! {
+        div {
+            width: "{width}",
+            height: "{height}",
+            border_style: "{border_style}",
+            align_items: "center",
+            justify_content: "center",
+            onclick: move |_| {
+                update();
+            },
+            onkeydown: move |evt| {
+                if !evt.is_auto_repeating() && match evt.key(){ Key::Character(c) if c == " " =>true, Key::Enter=>true, _=>false }  {
+                    update();
+                }
+            },
+            "{text}"
+        }
+    })
+}

+ 102 - 0
packages/tui/src/widgets/input.rs

@@ -0,0 +1,102 @@
+use dioxus::prelude::*;
+use dioxus_core::prelude::fc_to_builder;
+use dioxus_html::FormData;
+
+use crate::widgets::button::Button;
+use crate::widgets::checkbox::CheckBox;
+use crate::widgets::number::NumbericInput;
+use crate::widgets::password::Password;
+use crate::widgets::slider::Slider;
+use crate::widgets::textbox::TextBox;
+
+#[derive(Props)]
+pub struct InputProps<'a> {
+    r#type: Option<&'static str>,
+    oninput: Option<EventHandler<'a, FormData>>,
+    onclick: Option<EventHandler<'a, FormData>>,
+    value: Option<&'a str>,
+    size: Option<&'a str>,
+    maxlength: Option<&'a str>,
+    width: Option<&'a str>,
+    height: Option<&'a str>,
+    min: Option<&'a str>,
+    max: Option<&'a str>,
+    step: Option<&'a str>,
+    checked: Option<&'a str>,
+}
+
+#[allow(non_snake_case)]
+pub fn Input<'a>(cx: Scope<'a, InputProps<'a>>) -> Element<'a> {
+    cx.render(match cx.props.r#type {
+        Some("checkbox") => {
+            rsx! {
+                CheckBox{
+                    raw_oninput: cx.props.oninput.as_ref(),
+                    value: cx.props.value,
+                    width: cx.props.width,
+                    height: cx.props.height,
+                    checked: cx.props.checked,
+                }
+            }
+        }
+        Some("range") => {
+            rsx! {
+                Slider{
+                    raw_oninput: cx.props.oninput.as_ref(),
+                    value: cx.props.value,
+                    width: cx.props.width,
+                    height: cx.props.height,
+                    max: cx.props.max,
+                    min: cx.props.min,
+                    step: cx.props.step,
+                }
+            }
+        }
+        Some("button") => {
+            rsx! {
+                Button{
+                    raw_onclick: cx.props.onclick.as_ref(),
+                    value: cx.props.value,
+                    width: cx.props.width,
+                    height: cx.props.height,
+                }
+            }
+        }
+        Some("number") => {
+            rsx! {
+                NumbericInput{
+                    raw_oninput: cx.props.oninput.as_ref(),
+                    value: cx.props.value,
+                    size: cx.props.size,
+                    max_length: cx.props.maxlength,
+                    width: cx.props.width,
+                    height: cx.props.height,
+                }
+            }
+        }
+        Some("password") => {
+            rsx! {
+                Password{
+                    raw_oninput: cx.props.oninput.as_ref(),
+                    value: cx.props.value,
+                    size: cx.props.size,
+                    max_length: cx.props.maxlength,
+                    width: cx.props.width,
+                    height: cx.props.height,
+                }
+            }
+        }
+        _ => {
+            rsx! {
+                TextBox{
+                    raw_oninput: cx.props.oninput.as_ref(),
+                    value: cx.props.value,
+                    size: cx.props.size,
+                    max_length: cx.props.maxlength,
+                    width: cx.props.width,
+                    height: cx.props.height,
+                }
+            }
+        }
+    })
+}

+ 18 - 0
packages/tui/src/widgets/mod.rs

@@ -0,0 +1,18 @@
+mod button;
+mod checkbox;
+mod input;
+mod number;
+mod password;
+mod slider;
+mod textbox;
+
+use dioxus_core::{ElementId, RenderReturn, Scope};
+pub use input::*;
+
+pub(crate) fn get_root_id<T>(cx: Scope<T>) -> Option<ElementId> {
+    if let RenderReturn::Sync(Ok(sync)) = cx.root_node() {
+        sync.root_ids.get(0).map(|id| id.get())
+    } else {
+        None
+    }
+}

+ 209 - 0
packages/tui/src/widgets/number.rs

@@ -0,0 +1,209 @@
+use crate::widgets::get_root_id;
+use crate::Query;
+use crossterm::{cursor::MoveTo, execute};
+use dioxus::prelude::*;
+use dioxus_elements::input_data::keyboard_types::Key;
+use dioxus_html as dioxus_elements;
+use dioxus_html::FormData;
+use dioxus_native_core::utils::cursor::{Cursor, Pos};
+use std::{collections::HashMap, io::stdout};
+use taffy::geometry::Point;
+
+#[derive(Props)]
+pub(crate) struct NumbericInputProps<'a> {
+    #[props(!optional)]
+    raw_oninput: Option<&'a EventHandler<'a, FormData>>,
+    #[props(!optional)]
+    value: Option<&'a str>,
+    #[props(!optional)]
+    size: Option<&'a str>,
+    #[props(!optional)]
+    max_length: Option<&'a str>,
+    #[props(!optional)]
+    width: Option<&'a str>,
+    #[props(!optional)]
+    height: Option<&'a str>,
+}
+#[allow(non_snake_case)]
+pub(crate) fn NumbericInput<'a>(cx: Scope<'a, NumbericInputProps>) -> Element<'a> {
+    let tui_query: Query = cx.consume_context().unwrap();
+    let tui_query_clone = tui_query.clone();
+
+    let text_ref = use_ref(cx, || {
+        if let Some(intial_text) = cx.props.value {
+            intial_text.to_string()
+        } else {
+            String::new()
+        }
+    });
+    let cursor = use_ref(cx, Cursor::default);
+    let dragging = use_state(cx, || false);
+
+    let text = text_ref.read().clone();
+    let start_highlight = cursor.read().first().idx(&text);
+    let end_highlight = cursor.read().last().idx(&text);
+    let (text_before_first_cursor, text_after_first_cursor) = text.split_at(start_highlight);
+    let (text_highlighted, text_after_second_cursor) =
+        text_after_first_cursor.split_at(end_highlight - start_highlight);
+
+    let max_len = cx
+        .props
+        .max_length
+        .as_ref()
+        .and_then(|s| s.parse().ok())
+        .unwrap_or(usize::MAX);
+
+    let width = cx
+        .props
+        .width
+        .map(|s| s.to_string())
+        // px is the same as em in tui
+        .or_else(|| cx.props.size.map(|s| s.to_string() + "px"))
+        .unwrap_or_else(|| "10px".to_string());
+    let height = cx.props.height.unwrap_or("3px");
+
+    // don't draw a border unless there is enough space
+    let border = if width
+        .strip_suffix("px")
+        .and_then(|w| w.parse::<i32>().ok())
+        .filter(|w| *w < 3)
+        .is_some()
+        || height
+            .strip_suffix("px")
+            .and_then(|h| h.parse::<i32>().ok())
+            .filter(|h| *h < 3)
+            .is_some()
+    {
+        "none"
+    } else {
+        "solid"
+    };
+
+    let update = |text: String| {
+        if let Some(input_handler) = &cx.props.raw_oninput {
+            input_handler.call(FormData {
+                value: text,
+                values: HashMap::new(),
+                files: None,
+            });
+        }
+    };
+    let increase = move || {
+        let mut text = text_ref.write();
+        *text = (text.parse::<f64>().unwrap_or(0.0) + 1.0).to_string();
+        update(text.clone());
+    };
+    let decrease = move || {
+        let mut text = text_ref.write();
+        *text = (text.parse::<f64>().unwrap_or(0.0) - 1.0).to_string();
+        update(text.clone());
+    };
+
+    render! {
+        div{
+            width: "{width}",
+            height: "{height}",
+            border_style: "{border}",
+
+            onkeydown: move |k| {
+                let is_text = match k.key(){
+                    Key::ArrowLeft | Key::ArrowRight | Key::Backspace => true,
+                    Key::Character(c) if c=="." || c== "-" || c.chars().all(|c|c.is_numeric())=> true,
+                    _  => false,
+                };
+                if is_text{
+                    let mut text = text_ref.write();
+                    cursor.write().handle_input(&k, &mut text, max_len);
+                    update(text.clone());
+
+                    let node = tui_query.get(get_root_id(cx).unwrap());
+                    let Point{ x, y } = node.pos().unwrap();
+
+                    let Pos { col, row } = cursor.read().start;
+                    let (x, y) = (col as u16 + x as u16 + if border == "none" {0} else {1}, row as u16 + y as u16 + if border == "none" {0} else {1});
+                    if let Ok(pos) = crossterm::cursor::position() {
+                        if pos != (x, y){
+                            execute!(stdout(), MoveTo(x, y)).unwrap();
+                        }
+                    }
+                    else{
+                        execute!(stdout(), MoveTo(x, y)).unwrap();
+                    }
+                }
+                else{
+                    match k.key() {
+                        Key::ArrowUp =>{
+                            increase();
+                        }
+                        Key::ArrowDown =>{
+                            decrease();
+                        }
+                        _ => ()
+                    }
+                }
+            },
+            onmousemove: move |evt| {
+                if *dragging.get() {
+                    let offset = evt.data.element_coordinates();
+                    let mut new = Pos::new(offset.x as usize, offset.y as usize);
+                    if border != "none" {
+                        new.col = new.col.saturating_sub(1);
+                    }
+                    // textboxs are only one line tall
+                    new.row = 0;
+
+                    if new != cursor.read().start {
+                        cursor.write().end = Some(new);
+                    }
+                }
+            },
+            onmousedown: move |evt| {
+                let offset = evt.data.element_coordinates();
+                let mut new = Pos::new(offset.x as usize,  offset.y as usize);
+                if border != "none" {
+                    new.col = new.col.saturating_sub(1);
+                }
+                new.row = 0;
+
+                new.realize_col(&text_ref.read());
+                cursor.set(Cursor::from_start(new));
+                dragging.set(true);
+                let node = tui_query_clone.get(get_root_id(cx).unwrap());
+                let Point{ x, y } = node.pos().unwrap();
+
+                let Pos { col, row } = cursor.read().start;
+                let (x, y) = (col as u16 + x as u16 + if border == "none" {0} else {1}, row as u16 + y as u16 + if border == "none" {0} else {1});
+                if let Ok(pos) = crossterm::cursor::position() {
+                    if pos != (x, y){
+                        execute!(stdout(), MoveTo(x, y)).unwrap();
+                    }
+                }
+                else{
+                    execute!(stdout(), MoveTo(x, y)).unwrap();
+                }
+            },
+            onmouseup: move |_| {
+                dragging.set(false);
+            },
+            onmouseleave: move |_| {
+                dragging.set(false);
+            },
+            onmouseenter: move |_| {
+                dragging.set(false);
+            },
+            onfocusout: |_| {
+                execute!(stdout(), MoveTo(0, 1000)).unwrap();
+            },
+
+            "{text_before_first_cursor}"
+
+            span{
+                background_color: "rgba(255, 255, 255, 50%)",
+
+                "{text_highlighted}"
+            }
+
+            "{text_after_second_cursor}"
+        }
+    }
+}

+ 186 - 0
packages/tui/src/widgets/password.rs

@@ -0,0 +1,186 @@
+use crate::widgets::get_root_id;
+use crate::Query;
+use crossterm::{cursor::*, execute};
+use dioxus::prelude::*;
+use dioxus_elements::input_data::keyboard_types::Key;
+use dioxus_html as dioxus_elements;
+use dioxus_html::FormData;
+use dioxus_native_core::utils::cursor::{Cursor, Pos};
+use std::{collections::HashMap, io::stdout};
+use taffy::geometry::Point;
+
+#[derive(Props)]
+pub(crate) struct PasswordProps<'a> {
+    #[props(!optional)]
+    raw_oninput: Option<&'a EventHandler<'a, FormData>>,
+    #[props(!optional)]
+    value: Option<&'a str>,
+    #[props(!optional)]
+    size: Option<&'a str>,
+    #[props(!optional)]
+    max_length: Option<&'a str>,
+    #[props(!optional)]
+    width: Option<&'a str>,
+    #[props(!optional)]
+    height: Option<&'a str>,
+}
+#[allow(non_snake_case)]
+pub(crate) fn Password<'a>(cx: Scope<'a, PasswordProps>) -> Element<'a> {
+    let tui_query: Query = cx.consume_context().unwrap();
+    let tui_query_clone = tui_query.clone();
+
+    let text_ref = use_ref(cx, || {
+        if let Some(intial_text) = cx.props.value {
+            intial_text.to_string()
+        } else {
+            String::new()
+        }
+    });
+    let cursor = use_ref(cx, Cursor::default);
+    let dragging = use_state(cx, || false);
+
+    let text = text_ref.read().clone();
+    let start_highlight = cursor.read().first().idx(&text);
+    let end_highlight = cursor.read().last().idx(&text);
+    let (text_before_first_cursor, text_after_first_cursor) = text.split_at(start_highlight);
+    let (text_highlighted, text_after_second_cursor) =
+        text_after_first_cursor.split_at(end_highlight - start_highlight);
+
+    let text_before_first_cursor = ".".repeat(text_before_first_cursor.len());
+    let text_highlighted = ".".repeat(text_highlighted.len());
+    let text_after_second_cursor = ".".repeat(text_after_second_cursor.len());
+
+    let max_len = cx
+        .props
+        .max_length
+        .as_ref()
+        .and_then(|s| s.parse().ok())
+        .unwrap_or(usize::MAX);
+
+    let width = cx
+        .props
+        .width
+        .map(|s| s.to_string())
+        // px is the same as em in tui
+        .or_else(|| cx.props.size.map(|s| s.to_string() + "px"))
+        .unwrap_or_else(|| "10px".to_string());
+    let height = cx.props.height.unwrap_or("3px");
+
+    // don't draw a border unless there is enough space
+    let border = if width
+        .strip_suffix("px")
+        .and_then(|w| w.parse::<i32>().ok())
+        .filter(|w| *w < 3)
+        .is_some()
+        || height
+            .strip_suffix("px")
+            .and_then(|h| h.parse::<i32>().ok())
+            .filter(|h| *h < 3)
+            .is_some()
+    {
+        "none"
+    } else {
+        "solid"
+    };
+
+    render! {
+        div{
+            width: "{width}",
+            height: "{height}",
+            border_style: "{border}",
+
+            onkeydown: move |k| {
+                if k.key()== Key::Enter {
+                    return;
+                }
+                let mut text = text_ref.write();
+                cursor.write().handle_input(&k, &mut text, max_len);
+                if let Some(input_handler) = &cx.props.raw_oninput{
+                    input_handler.call(FormData{
+                        value: text.clone(),
+                        values: HashMap::new(),
+                        files: None
+                    });
+                }
+
+                let node = tui_query.get(get_root_id(cx).unwrap());
+                let Point{ x, y } = node.pos().unwrap();
+
+                let Pos { col, row } = cursor.read().start;
+                let (x, y) = (col as u16 + x as u16 + if border == "none" {0} else {1}, row as u16 + y as u16 + if border == "none" {0} else {1});
+                if let Ok(pos) = crossterm::cursor::position() {
+                    if pos != (x, y){
+                        execute!(stdout(), MoveTo(x, y)).unwrap();
+                    }
+                }
+                else{
+                    execute!(stdout(), MoveTo(x, y)).unwrap();
+                }
+            },
+
+            onmousemove: move |evt| {
+                if *dragging.get() {
+                    let offset = evt.data.element_coordinates();
+                    let mut new = Pos::new(offset.x as usize, offset.y as usize);
+                    if border != "none" {
+                        new.col = new.col.saturating_sub(1);
+                    }
+                    // textboxs are only one line tall
+                    new.row = 0;
+
+                    if new != cursor.read().start {
+                        cursor.write().end = Some(new);
+                    }
+                }
+            },
+            onmousedown: move |evt| {
+                let offset = evt.data.element_coordinates();
+                let mut new = Pos::new(offset.x as usize, offset.y as usize);
+                if border != "none" {
+                    new.col = new.col.saturating_sub(1);
+                }
+                // textboxs are only one line tall
+                new.row = 0;
+
+                new.realize_col(&text_ref.read());
+                cursor.set(Cursor::from_start(new));
+                dragging.set(true);
+                let node = tui_query_clone.get(get_root_id(cx).unwrap());
+                let Point{ x, y } = node.pos().unwrap();
+
+                let Pos { col, row } = cursor.read().start;
+                let (x, y) = (col as u16 + x as u16 + if border == "none" {0} else {1}, row as u16 + y as u16 + if border == "none" {0} else {1});
+                if let Ok(pos) = crossterm::cursor::position() {
+                    if pos != (x, y){
+                        execute!(stdout(), MoveTo(x, y)).unwrap();
+                    }
+                }
+                else{
+                    execute!(stdout(), MoveTo(x, y)).unwrap();
+                }
+            },
+            onmouseup: move |_| {
+                dragging.set(false);
+            },
+            onmouseleave: move |_| {
+                dragging.set(false);
+            },
+            onmouseenter: move |_| {
+                dragging.set(false);
+            },
+            onfocusout: |_| {
+                execute!(stdout(), MoveTo(0, 1000)).unwrap();
+            },
+
+            "{text_before_first_cursor}"
+
+            span{
+                background_color: "rgba(255, 255, 255, 50%)",
+
+                "{text_highlighted}"
+            }
+
+            "{text_after_second_cursor}"
+        }
+    }
+}

+ 108 - 0
packages/tui/src/widgets/slider.rs

@@ -0,0 +1,108 @@
+use std::collections::HashMap;
+
+use crate::widgets::get_root_id;
+use crate::Query;
+use dioxus::prelude::*;
+use dioxus_elements::input_data::keyboard_types::Key;
+use dioxus_html as dioxus_elements;
+use dioxus_html::FormData;
+
+#[derive(Props)]
+pub(crate) struct SliderProps<'a> {
+    #[props(!optional)]
+    raw_oninput: Option<&'a EventHandler<'a, FormData>>,
+    #[props(!optional)]
+    value: Option<&'a str>,
+    #[props(!optional)]
+    width: Option<&'a str>,
+    #[props(!optional)]
+    height: Option<&'a str>,
+    #[props(!optional)]
+    min: Option<&'a str>,
+    #[props(!optional)]
+    max: Option<&'a str>,
+    #[props(!optional)]
+    step: Option<&'a str>,
+}
+
+#[allow(non_snake_case)]
+pub(crate) fn Slider<'a>(cx: Scope<'a, SliderProps>) -> Element<'a> {
+    let tui_query: Query = cx.consume_context().unwrap();
+
+    let value_state = use_state(cx, || 0.0);
+    let value: Option<f32> = cx.props.value.and_then(|v| v.parse().ok());
+    let width = cx.props.width.unwrap_or("20px");
+    let height = cx.props.height.unwrap_or("1px");
+    let min = cx.props.min.and_then(|v| v.parse().ok()).unwrap_or(0.0);
+    let max = cx.props.max.and_then(|v| v.parse().ok()).unwrap_or(100.0);
+    let size = max - min;
+    let step = cx
+        .props
+        .step
+        .and_then(|v| v.parse().ok())
+        .unwrap_or(size / 10.0);
+
+    let current_value = if let Some(value) = value {
+        value
+    } else {
+        *value_state.get()
+    }
+    .max(min)
+    .min(max);
+    let fst_width = 100.0 * (current_value - min) / size;
+    let snd_width = 100.0 * (max - current_value) / size;
+    assert!(fst_width + snd_width > 99.0 && fst_width + snd_width < 101.0);
+
+    let update = |value: String| {
+        if let Some(oninput) = cx.props.raw_oninput {
+            oninput.call(FormData {
+                value,
+                values: HashMap::new(),
+                files: None,
+            });
+        }
+    };
+
+    render! {
+        div{
+            width: "{width}",
+            height: "{height}",
+            display: "flex",
+            flex_direction: "row",
+            onkeydown: move |event| {
+                match event.key() {
+                    Key::ArrowLeft => {
+                        value_state.set((current_value - step).max(min).min(max));
+                        update(value_state.current().to_string());
+                    }
+                    Key::ArrowRight => {
+                        value_state.set((current_value + step).max(min).min(max));
+                        update(value_state.current().to_string());
+                    }
+                    _ => ()
+                }
+            },
+            onmousemove: move |evt| {
+                let mouse = evt.data;
+                if !mouse.held_buttons().is_empty(){
+                    let node = tui_query.get(get_root_id(cx).unwrap());
+                    let width = node.size().unwrap().width;
+                    let offset = mouse.element_coordinates();
+                    value_state.set(min + size*(offset.x as f32) / width as f32);
+                    update(value_state.current().to_string());
+                }
+            },
+            div{
+                width: "{fst_width}%",
+                background_color: "rgba(10,10,10,0.5)",
+            }
+            div{
+                "|"
+            }
+            div{
+                width: "{snd_width}%",
+                background_color: "rgba(10,10,10,0.5)",
+            }
+        }
+    }
+}

+ 182 - 0
packages/tui/src/widgets/textbox.rs

@@ -0,0 +1,182 @@
+use crate::widgets::get_root_id;
+use crate::Query;
+use crossterm::{cursor::*, execute};
+use dioxus::prelude::*;
+use dioxus_elements::input_data::keyboard_types::Key;
+use dioxus_html as dioxus_elements;
+use dioxus_html::FormData;
+use dioxus_native_core::utils::cursor::{Cursor, Pos};
+use std::{collections::HashMap, io::stdout};
+use taffy::geometry::Point;
+
+#[derive(Props)]
+pub(crate) struct TextBoxProps<'a> {
+    #[props(!optional)]
+    raw_oninput: Option<&'a EventHandler<'a, FormData>>,
+    #[props(!optional)]
+    value: Option<&'a str>,
+    #[props(!optional)]
+    size: Option<&'a str>,
+    #[props(!optional)]
+    max_length: Option<&'a str>,
+    #[props(!optional)]
+    width: Option<&'a str>,
+    #[props(!optional)]
+    height: Option<&'a str>,
+}
+#[allow(non_snake_case)]
+pub(crate) fn TextBox<'a>(cx: Scope<'a, TextBoxProps>) -> Element<'a> {
+    let tui_query: Query = cx.consume_context().unwrap();
+    let tui_query_clone = tui_query.clone();
+
+    let text_ref = use_ref(cx, || {
+        if let Some(intial_text) = cx.props.value {
+            intial_text.to_string()
+        } else {
+            String::new()
+        }
+    });
+    let cursor = use_ref(cx, Cursor::default);
+    let dragging = use_state(cx, || false);
+
+    let text = text_ref.read().clone();
+    let start_highlight = cursor.read().first().idx(&text);
+    let end_highlight = cursor.read().last().idx(&text);
+    let (text_before_first_cursor, text_after_first_cursor) = text.split_at(start_highlight);
+    let (text_highlighted, text_after_second_cursor) =
+        text_after_first_cursor.split_at(end_highlight - start_highlight);
+
+    let max_len = cx
+        .props
+        .max_length
+        .as_ref()
+        .and_then(|s| s.parse().ok())
+        .unwrap_or(usize::MAX);
+
+    let width = cx
+        .props
+        .width
+        .map(|s| s.to_string())
+        // px is the same as em in tui
+        .or_else(|| cx.props.size.map(|s| s.to_string() + "px"))
+        .unwrap_or_else(|| "10px".to_string());
+    let height = cx.props.height.unwrap_or("3px");
+
+    // don't draw a border unless there is enough space
+    let border = if width
+        .strip_suffix("px")
+        .and_then(|w| w.parse::<i32>().ok())
+        .filter(|w| *w < 3)
+        .is_some()
+        || height
+            .strip_suffix("px")
+            .and_then(|h| h.parse::<i32>().ok())
+            .filter(|h| *h < 3)
+            .is_some()
+    {
+        "none"
+    } else {
+        "solid"
+    };
+
+    render! {
+        div{
+            width: "{width}",
+            height: "{height}",
+            border_style: "{border}",
+
+            onkeydown: move |k| {
+                if k.key() == Key::Enter {
+                    return;
+                }
+                let mut text = text_ref.write();
+                cursor.write().handle_input(&k, &mut text, max_len);
+                if let Some(input_handler) = &cx.props.raw_oninput{
+                    input_handler.call(FormData{
+                        value: text.clone(),
+                        values: HashMap::new(),
+                        files: None
+                    });
+                }
+
+                let node = tui_query.get(get_root_id(cx).unwrap());
+                let Point{ x, y } = node.pos().unwrap();
+
+                let Pos { col, row } = cursor.read().start;
+                let (x, y) = (col as u16 + x as u16 + if border == "none" {0} else {1}, row as u16 + y as u16 + if border == "none" {0} else {1});
+                if let Ok(pos) = crossterm::cursor::position() {
+                    if pos != (x, y){
+                        execute!(stdout(), MoveTo(x, y)).unwrap();
+                    }
+                }
+                else{
+                    execute!(stdout(), MoveTo(x, y)).unwrap();
+                }
+            },
+
+            onmousemove: move |evt| {
+                if *dragging.get() {
+                    let offset = evt.data.element_coordinates();
+                    let mut new = Pos::new(offset.x as usize, offset.y as usize);
+                    if border != "none" {
+                        new.col = new.col.saturating_sub(1);
+                    }
+                    // textboxs are only one line tall
+                    new.row = 0;
+
+                    if new != cursor.read().start {
+                        cursor.write().end = Some(new);
+                    }
+                }
+            },
+            onmousedown: move |evt| {
+                let offset = evt.data.element_coordinates();
+                let mut new = Pos::new(offset.x as usize, offset.y as usize);
+                if border != "none" {
+                    new.col = new.col.saturating_sub(1);
+                }
+                // textboxs are only one line tall
+                new.row = 0;
+
+                new.realize_col(&text_ref.read());
+                cursor.set(Cursor::from_start(new));
+                dragging.set(true);
+                let node = tui_query_clone.get(get_root_id(cx).unwrap());
+                let Point{ x, y } = node.pos().unwrap();
+
+                let Pos { col, row } = cursor.read().start;
+                let (x, y) = (col as u16 + x as u16 + if border == "none" {0} else {1}, row as u16 + y as u16 + if border == "none" {0} else {1});
+                if let Ok(pos) = crossterm::cursor::position() {
+                    if pos != (x, y){
+                        execute!(stdout(), MoveTo(x, y)).unwrap();
+                    }
+                }
+                else{
+                    execute!(stdout(), MoveTo(x, y)).unwrap();
+                }
+            },
+            onmouseup: move |_| {
+                dragging.set(false);
+            },
+            onmouseleave: move |_| {
+                dragging.set(false);
+            },
+            onmouseenter: move |_| {
+                dragging.set(false);
+            },
+            onfocusout: |_| {
+                execute!(stdout(), MoveTo(0, 1000)).unwrap();
+            },
+
+            "{text_before_first_cursor}"
+
+            span{
+                background_color: "rgba(255, 255, 255, 50%)",
+
+                "{text_highlighted}"
+            }
+
+            "{text_after_second_cursor}"
+        }
+    }
+}

+ 2 - 1
packages/web/Cargo.toml

@@ -14,7 +14,7 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
 dioxus-core = { path = "../core", version = "^0.2.1", features = ["serialize"] }
 dioxus-core = { path = "../core", version = "^0.2.1", features = ["serialize"] }
 dioxus-html = { path = "../html", version = "^0.2.1", features = ["wasm-bind"] }
 dioxus-html = { path = "../html", version = "^0.2.1", features = ["wasm-bind"] }
 dioxus-interpreter-js = { path = "../interpreter", version = "^0.2.1", features = [
 dioxus-interpreter-js = { path = "../interpreter", version = "^0.2.1", features = [
-    "web"
+    "sledgehammer"
 ] }
 ] }
 
 
 js-sys = "0.3.56"
 js-sys = "0.3.56"
@@ -30,6 +30,7 @@ futures-util = "0.3.19"
 smallstr = "0.2.0"
 smallstr = "0.2.0"
 futures-channel = "0.3.21"
 futures-channel = "0.3.21"
 serde_json = { version = "1.0" }
 serde_json = { version = "1.0" }
+serde = { version = "1.0" }
 serde-wasm-bindgen = "0.4.5"
 serde-wasm-bindgen = "0.4.5"
 
 
 [dependencies.web-sys]
 [dependencies.web-sys]

+ 112 - 38
packages/web/src/dom.rs

@@ -7,20 +7,22 @@
 //! - tests to ensure dyn_into works for various event types.
 //! - tests to ensure dyn_into works for various event types.
 //! - Partial delegation?>
 //! - Partial delegation?>
 
 
-use dioxus_core::{Mutation, Template};
+use dioxus_core::{Mutation, Template, TemplateAttribute, TemplateNode};
 use dioxus_html::{event_bubbles, CompositionData, FormData};
 use dioxus_html::{event_bubbles, CompositionData, FormData};
-use dioxus_interpreter_js::Interpreter;
+use dioxus_interpreter_js::{save_template, Channel};
 use futures_channel::mpsc;
 use futures_channel::mpsc;
+use rustc_hash::FxHashMap;
 use std::{any::Any, rc::Rc};
 use std::{any::Any, rc::Rc};
 use wasm_bindgen::{closure::Closure, JsCast};
 use wasm_bindgen::{closure::Closure, JsCast};
-use web_sys::{Document, Element, Event};
+use web_sys::{Document, Element, Event, HtmlElement};
 
 
 use crate::Config;
 use crate::Config;
 
 
 pub struct WebsysDom {
 pub struct WebsysDom {
-    interpreter: Interpreter,
-    handler: Closure<dyn FnMut(&Event)>,
-    _root: Element,
+    document: Document,
+    templates: FxHashMap<String, u32>,
+    max_template_id: u32,
+    interpreter: Channel,
 }
 }
 
 
 impl WebsysDom {
 impl WebsysDom {
@@ -32,67 +34,139 @@ impl WebsysDom {
             Some(root) => root,
             Some(root) => root,
             None => document.create_element("body").ok().unwrap(),
             None => document.create_element("body").ok().unwrap(),
         };
         };
+        let interpreter = Channel::default();
 
 
-        Self {
-            interpreter: Interpreter::new(root.clone()),
-            _root: root,
-            handler: Closure::wrap(Box::new(move |event: &web_sys::Event| {
+        let handler: Closure<dyn FnMut(&Event)> =
+            Closure::wrap(Box::new(move |event: &web_sys::Event| {
                 let _ = event_channel.unbounded_send(event.clone());
                 let _ = event_channel.unbounded_send(event.clone());
-            })),
+            }));
+
+        dioxus_interpreter_js::initilize(root.unchecked_into(), handler.as_ref().unchecked_ref());
+        handler.forget();
+        Self {
+            document,
+            interpreter,
+            templates: FxHashMap::default(),
+            max_template_id: 0,
         }
         }
     }
     }
 
 
     pub fn mount(&mut self) {
     pub fn mount(&mut self) {
-        self.interpreter.MountToRoot();
+        self.interpreter.mount_to_root();
     }
     }
 
 
     pub fn load_templates(&mut self, templates: &[Template]) {
     pub fn load_templates(&mut self, templates: &[Template]) {
         log::debug!("Loading templates {:?}", templates);
         log::debug!("Loading templates {:?}", templates);
 
 
         for template in templates {
         for template in templates {
-            self.interpreter
-                .SaveTemplate(serde_wasm_bindgen::to_value(&template).unwrap());
+            let mut roots = vec![];
+
+            for root in template.roots {
+                roots.push(self.create_template_node(root))
+            }
+
+            self.templates
+                .insert(template.name.to_owned(), self.max_template_id);
+            save_template(roots, self.max_template_id);
+            self.max_template_id += 1
+        }
+    }
+
+    fn create_template_node(&self, v: &TemplateNode) -> web_sys::Node {
+        use TemplateNode::*;
+        match v {
+            Element {
+                tag,
+                namespace,
+                attrs,
+                children,
+                ..
+            } => {
+                let el = match namespace {
+                    Some(ns) => self.document.create_element_ns(Some(ns), tag).unwrap(),
+                    None => self.document.create_element(tag).unwrap(),
+                };
+                for attr in *attrs {
+                    if let TemplateAttribute::Static {
+                        name,
+                        value,
+                        namespace,
+                    } = attr
+                    {
+                        match namespace {
+                            Some(ns) if *ns == "style" => el
+                                .dyn_ref::<HtmlElement>()
+                                .unwrap()
+                                .style()
+                                .set_property(name, value)
+                                .unwrap(),
+                            Some(ns) => el.set_attribute_ns(Some(ns), name, value).unwrap(),
+                            None => el.set_attribute(name, value).unwrap(),
+                        }
+                    }
+                }
+                for child in *children {
+                    let _ = el.append_child(&self.create_template_node(child));
+                }
+                el.dyn_into().unwrap()
+            }
+            Text { text } => self.document.create_text_node(text).dyn_into().unwrap(),
+            DynamicText { .. } => self.document.create_text_node("p").dyn_into().unwrap(),
+            Dynamic { .. } => {
+                let el = self.document.create_element("pre").unwrap();
+                let _ = el.toggle_attribute("hidden");
+                el.dyn_into().unwrap()
+            }
         }
         }
     }
     }
 
 
     pub fn apply_edits(&mut self, mut edits: Vec<Mutation>) {
     pub fn apply_edits(&mut self, mut edits: Vec<Mutation>) {
         use Mutation::*;
         use Mutation::*;
-        let i = &self.interpreter;
-        for edit in edits.drain(..) {
+        let i = &mut self.interpreter;
+        for edit in &edits {
             match edit {
             match edit {
-                AppendChildren { id, m } => i.AppendChildren(m as u32, id.0 as u32),
-                AssignId { path, id } => i.AssignId(path, id.0 as u32),
-                CreatePlaceholder { id } => i.CreatePlaceholder(id.0 as u32),
-                CreateTextNode { value, id } => i.CreateTextNode(value.into(), id.0 as u32),
-                HydrateText { path, value, id } => i.HydrateText(path, value, id.0 as u32),
-                LoadTemplate { name, index, id } => i.LoadTemplate(name, index as u32, id.0 as u32),
-                ReplaceWith { id, m } => i.ReplaceWith(id.0 as u32, m as u32),
-                ReplacePlaceholder { path, m } => i.ReplacePlaceholder(path, m as u32),
-                InsertAfter { id, m } => i.InsertAfter(id.0 as u32, m as u32),
-                InsertBefore { id, m } => i.InsertBefore(id.0 as u32, m as u32),
+                AppendChildren { id, m } => i.append_children(id.0 as u32, *m as u32),
+                AssignId { path, id } => {
+                    i.assign_id(path.as_ptr() as u32, path.len() as u8, id.0 as u32)
+                }
+                CreatePlaceholder { id } => i.create_placeholder(id.0 as u32),
+                CreateTextNode { value, id } => i.create_text_node(value, id.0 as u32),
+                HydrateText { path, value, id } => {
+                    i.hydrate_text(path.as_ptr() as u32, path.len() as u8, value, id.0 as u32)
+                }
+                LoadTemplate { name, index, id } => {
+                    if let Some(tmpl_id) = self.templates.get(*name) {
+                        i.load_template(*tmpl_id, *index as u32, id.0 as u32)
+                    }
+                }
+                ReplaceWith { id, m } => i.replace_with(id.0 as u32, *m as u32),
+                ReplacePlaceholder { path, m } => {
+                    i.replace_placeholder(path.as_ptr() as u32, path.len() as u8, *m as u32)
+                }
+                InsertAfter { id, m } => i.insert_after(id.0 as u32, *m as u32),
+                InsertBefore { id, m } => i.insert_before(id.0 as u32, *m as u32),
                 SetAttribute {
                 SetAttribute {
                     name,
                     name,
                     value,
                     value,
                     id,
                     id,
                     ns,
                     ns,
-                } => i.SetAttribute(id.0 as u32, name, value.into(), ns),
+                } => i.set_attribute(id.0 as u32, name, value, ns.unwrap_or_default()),
                 SetBoolAttribute { name, value, id } => {
                 SetBoolAttribute { name, value, id } => {
-                    i.SetBoolAttribute(id.0 as u32, name, value)
+                    i.set_attribute(id.0 as u32, name, if *value { "true" } else { "false" }, "")
                 }
                 }
-                SetText { value, id } => i.SetText(id.0 as u32, value.into()),
+                SetText { value, id } => i.set_text(id.0 as u32, value),
                 NewEventListener { name, id, .. } => {
                 NewEventListener { name, id, .. } => {
-                    self.interpreter.NewEventListener(
-                        name,
-                        id.0 as u32,
-                        event_bubbles(&name[2..]),
-                        self.handler.as_ref().unchecked_ref(),
-                    );
+                    i.new_event_listener(name, id.0 as u32, event_bubbles(&name[2..]) as u8);
+                }
+                RemoveEventListener { name, id } => {
+                    i.remove_event_listener(name, id.0 as u32, event_bubbles(&name[2..]) as u8)
                 }
                 }
-                RemoveEventListener { name, id } => i.RemoveEventListener(name, id.0 as u32),
-                Remove { id } => i.Remove(id.0 as u32),
-                PushRoot { id } => i.PushRoot(id.0 as u32),
+                Remove { id } => i.remove(id.0 as u32),
+                PushRoot { id } => i.push_root(id.0 as u32),
             }
             }
         }
         }
+        edits.clear();
+        i.flush();
     }
     }
 }
 }
 
 

+ 6 - 5
packages/web/src/lib.rs

@@ -55,7 +55,7 @@
 
 
 pub use crate::cfg::Config;
 pub use crate::cfg::Config;
 use crate::dom::virtual_event_from_websys_event;
 use crate::dom::virtual_event_from_websys_event;
-pub use crate::util::use_eval;
+pub use crate::util::{use_eval, EvalResult};
 use dioxus_core::{Element, ElementId, Scope, VirtualDom};
 use dioxus_core::{Element, ElementId, Scope, VirtualDom};
 use futures_util::{pin_mut, FutureExt, StreamExt};
 use futures_util::{pin_mut, FutureExt, StreamExt};
 
 
@@ -195,7 +195,7 @@ pub async fn run_with_props<T: 'static>(root: fn(Scope<T>) -> Element, root_prop
     // the mutations come back with nothing - we need to actually mount them
     // the mutations come back with nothing - we need to actually mount them
     websys_dom.mount();
     websys_dom.mount();
 
 
-    let mut work_loop = ric_raf::RafLoop::new();
+    let _work_loop = ric_raf::RafLoop::new();
 
 
     loop {
     loop {
         log::debug!("waiting for work");
         log::debug!("waiting for work");
@@ -228,6 +228,7 @@ pub async fn run_with_props<T: 'static>(root: fn(Scope<T>) -> Element, root_prop
             res = rx.try_next().transpose().unwrap().ok();
             res = rx.try_next().transpose().unwrap().ok();
         }
         }
 
 
+        // Todo: This is currently disabled because it has a negative impact on responce times for events but it could be re-enabled for tasks
         // Jank free rendering
         // Jank free rendering
         //
         //
         // 1. wait for the browser to give us "idle" time
         // 1. wait for the browser to give us "idle" time
@@ -236,13 +237,13 @@ pub async fn run_with_props<T: 'static>(root: fn(Scope<T>) -> Element, root_prop
         // 4. Wait for the animation frame to patch the dom
         // 4. Wait for the animation frame to patch the dom
 
 
         // wait for the mainthread to schedule us in
         // wait for the mainthread to schedule us in
-        let deadline = work_loop.wait_for_idle_time().await;
+        // let deadline = work_loop.wait_for_idle_time().await;
 
 
         // run the virtualdom work phase until the frame deadline is reached
         // run the virtualdom work phase until the frame deadline is reached
-        let edits = dom.render_with_deadline(deadline).await;
+        let edits = dom.render_immediate();
 
 
         // wait for the animation frame to fire so we can apply our changes
         // wait for the animation frame to fire so we can apply our changes
-        work_loop.wait_for_raf().await;
+        // work_loop.wait_for_raf().await;
 
 
         websys_dom.load_templates(&edits.templates);
         websys_dom.load_templates(&edits.templates);
         websys_dom.apply_edits(edits.edits);
         websys_dom.apply_edits(edits.edits);

+ 50 - 4
packages/web/src/util.rs

@@ -1,6 +1,13 @@
 //! Utilities specific to websys
 //! Utilities specific to websys
 
 
+use std::{
+    future::{IntoFuture, Ready},
+    str::FromStr,
+};
+
 use dioxus_core::*;
 use dioxus_core::*;
+use serde::de::Error;
+use serde_json::Value;
 
 
 /// Get a closure that executes any JavaScript in the webpage.
 /// Get a closure that executes any JavaScript in the webpage.
 ///
 ///
@@ -15,12 +22,51 @@ use dioxus_core::*;
 ///
 ///
 /// The closure will panic if the provided script is not valid JavaScript code
 /// The closure will panic if the provided script is not valid JavaScript code
 /// or if it returns an uncaught error.
 /// or if it returns an uncaught error.
-pub fn use_eval<S: std::string::ToString>(cx: &ScopeState) -> &dyn Fn(S) {
+pub fn use_eval<S: std::string::ToString>(cx: &ScopeState) -> &dyn Fn(S) -> EvalResult {
     cx.use_hook(|| {
     cx.use_hook(|| {
         |script: S| {
         |script: S| {
-            js_sys::Function::new_no_args(&script.to_string())
-                .call0(&wasm_bindgen::JsValue::NULL)
-                .expect("failed to eval script");
+            let body = script.to_string();
+            EvalResult {
+                value: if let Ok(value) =
+                    js_sys::Function::new_no_args(&body).call0(&wasm_bindgen::JsValue::NULL)
+                {
+                    if let Ok(stringified) = js_sys::JSON::stringify(&value) {
+                        if !stringified.is_undefined() && stringified.is_valid_utf16() {
+                            let string: String = stringified.into();
+                            Value::from_str(&string)
+                        } else {
+                            Err(serde_json::Error::custom("Failed to stringify result"))
+                        }
+                    } else {
+                        Err(serde_json::Error::custom("Failed to stringify result"))
+                    }
+                } else {
+                    Err(serde_json::Error::custom("Failed to execute script"))
+                },
+            }
         }
         }
     })
     })
 }
 }
+
+/// A wrapper around the result of a JavaScript evaluation.
+/// This implements IntoFuture to be compatible with the desktop renderer's EvalResult.
+pub struct EvalResult {
+    value: Result<Value, serde_json::Error>,
+}
+
+impl EvalResult {
+    /// Get the result of the Javascript execution.
+    pub fn get(self) -> Result<Value, serde_json::Error> {
+        self.value
+    }
+}
+
+impl IntoFuture for EvalResult {
+    type Output = Result<Value, serde_json::Error>;
+
+    type IntoFuture = Ready<Result<Value, serde_json::Error>>;
+
+    fn into_future(self) -> Self::IntoFuture {
+        std::future::ready(self.value)
+    }
+}