Sfoglia il codice sorgente

examples: upgrade to new version of dioxus core.

also add the inline_props macro
Jonathan Kelley 3 anni fa
parent
commit
cda759c
41 ha cambiato i file con 558 aggiunte e 255 eliminazioni
  1. 1 1
      examples/async.rs
  2. 2 2
      examples/borrowed.rs
  3. 1 1
      examples/calculator.rs
  4. 3 3
      examples/coroutine.rs
  5. 1 1
      examples/file_explorer.rs
  6. 1 1
      examples/framework_benchmark.rs
  7. 2 2
      examples/hello_world.rs
  8. 2 2
      examples/hydration.rs
  9. 3 7
      examples/manual_edits.rs
  10. 8 6
      examples/pattern_model.rs
  11. 2 24
      examples/router.rs
  12. 7 9
      examples/rsx_usage.rs
  13. 3 12
      examples/ssr.rs
  14. 1 1
      examples/tailwind.rs
  15. 8 13
      examples/tasks.rs
  16. 6 6
      examples/todomvc.rs
  17. 2 2
      examples/web_tick.rs
  18. 1 1
      packages/core-macro/Cargo.toml
  19. 22 0
      packages/core-macro/examples/inline_props.rs
  20. 102 0
      packages/core-macro/src/inlineprops.rs
  21. 35 1
      packages/core-macro/src/lib.rs
  22. 0 1
      packages/core/Cargo.toml
  23. 1 3
      packages/core/README.md
  24. 4 4
      packages/core/examples/borrowed.rs
  25. 5 5
      packages/core/src/diff.rs
  26. 8 11
      packages/core/src/lazynodes.rs
  27. 9 3
      packages/core/src/lib.rs
  28. 3 0
      packages/core/src/mutations.rs
  29. 11 3
      packages/core/src/nodes.rs
  30. 37 16
      packages/core/src/scopes.rs
  31. 7 7
      packages/core/src/virtual_dom.rs
  32. 6 6
      packages/core/tests/create_dom.rs
  33. 65 0
      packages/core/tests/diffing.rs
  34. 3 2
      packages/desktop/examples/async.rs
  35. 46 27
      packages/desktop/src/lib.rs
  36. 77 37
      packages/hooks/src/usecoroutine.rs
  37. 3 3
      packages/hooks/src/usemodel.rs
  38. 5 1
      packages/hooks/src/usestate.rs
  39. 34 11
      packages/router/src/lib.rs
  40. 20 19
      packages/web/examples/suspense.rs
  41. 1 1
      packages/web/src/lib.rs

+ 1 - 1
examples/async.rs

@@ -17,7 +17,7 @@ pub static App: Component<()> = |cx| {
 
     let (async_count, dir) = (count.for_async(), *direction);
 
-    let (task, _) = use_coroutine(cx, move || async move {
+    let task = use_coroutine(&cx, move || async move {
         loop {
             TimeoutFuture::new(250).await;
             *async_count.get_mut() += dir;

+ 2 - 2
examples/borrowed.rs

@@ -40,7 +40,7 @@ struct C1Props<'a> {
 }
 
 fn Child1<'a>(cx: Scope<'a, C1Props<'a>>) -> Element {
-    let (left, right) = props.text.split_once("=").unwrap();
+    let (left, right) = cx.props.text.split_once("=").unwrap();
 
     cx.render(rsx! {
         div {
@@ -58,7 +58,7 @@ struct C2Props<'a> {
 fn Child2<'a>(cx: Scope<'a, C2Props<'a>>) -> Element {
     cx.render(rsx! {
         Child3 {
-            text: props.text
+            text: cx.props.text
         }
     })
 }

+ 1 - 1
examples/calculator.rs

@@ -124,6 +124,6 @@ fn CalculatorKey<'a>(cx: Scope<'a, CalculatorKeyProps<'a>>) -> Element {
     rsx!(cx, button {
         class: "calculator-key {cx.props.name}"
         onclick: {cx.props.onclick}
-        {&props.children}
+        {&cx.props.children}
     })
 }

+ 3 - 3
examples/coroutine.rs

@@ -58,15 +58,15 @@ static App: Component<()> = |cx| {
 #[derive(Props)]
 struct HorseyProps<'a> {
     pos: i32,
-    children: ScopeChildren<'a>,
+    children: Element<'a>,
 }
 
-fn Horsey<'a>((cx, props): ScopeState<'a, HorseyProps<'a>>) -> Element {
+fn Horsey<'a>(cx: Scope<'a, HorseyProps<'a>>) -> Element {
     cx.render(rsx! {
         div {
             button { "pause" }
             div {
-                {&props.children}
+                {&cx.props.children}
             }
         }
     })

+ 1 - 1
examples/file_explorer.rs

@@ -8,7 +8,7 @@
 use dioxus::prelude::*;
 
 fn main() {
-    simple_logger::init_with_level(log::Level::Debug);
+    // simple_logger::init_with_level(log::Level::Debug);
     dioxus::desktop::launch_cfg(App, |c| {
         c.with_window(|w| {
             w.with_resizable(true).with_inner_size(

+ 1 - 1
examples/framework_benchmark.rs

@@ -97,7 +97,7 @@ struct ActionButtonProps<'a> {
 
 fn ActionButton<'a>(cx: Scope<'a, ActionButtonProps<'a>>) -> Element {
     rsx!(cx, div { class: "col-sm-6 smallpad"
-        button { class:"btn btn-primary btn-block", r#type: "button", id: "{cx.props.id}",  onclick: move |_| (props.onclick)(),
+        button { class:"btn btn-primary btn-block", r#type: "button", id: "{cx.props.id}",  onclick: move |_| (cx.props.onclick)(),
             "{cx.props.name}"
         }
     })

+ 2 - 2
examples/hello_world.rs

@@ -1,10 +1,10 @@
 use dioxus::prelude::*;
 
 fn main() {
-    dioxus::desktop::launch(App);
+    dioxus::desktop::launch(app);
 }
 
-fn App((cx, props): ScopeState<()>) -> Element {
+fn app(cx: Scope<()>) -> Element {
     cx.render(rsx! (
         div { "Hello, world!" }
     ))

+ 2 - 2
examples/hydration.rs

@@ -14,9 +14,9 @@ use dioxus::ssr;
 
 fn main() {
     let vdom = VirtualDom::new(App);
-    let content = ssr::render_vdom(&vdom, |f| f.pre_render(true));
+    let content = ssr::render_vdom_cfg(&vdom, |f| f.pre_render(true));
 
-    dioxus::desktop::launch(App, |c| c.with_prerendered(content));
+    dioxus::desktop::launch_cfg(App, |c| c.with_prerendered(content));
 }
 
 static App: Component<()> = |cx| {

+ 3 - 7
examples/manual_edits.rs

@@ -33,11 +33,7 @@ fn main() {
         AppendChildren { many: 1 },
     ];
 
-    dioxus_desktop::run(APP, (), |c| c.with_edits(edits));
-}
+    let app: Component<()> = |cx| rsx!(cx, div { "some app" });
 
-const APP: Component<()> = |(cx, _props)| {
-    rsx!(cx, div {
-        "some app"
-    })
-};
+    dioxus_desktop::run(app, (), |c| c.with_edits(edits));
+}

+ 8 - 6
examples/pattern_model.rs

@@ -15,6 +15,8 @@
 //! the RefCell will panic and crash. You can use `try_get_mut` or `.modify` to avoid this problem, or just not hold two
 //! RefMuts at the same time.
 
+use std::sync::Arc;
+
 use dioxus::desktop::wry::application::dpi::LogicalSize;
 use dioxus::events::*;
 use dioxus::prelude::*;
@@ -73,16 +75,16 @@ static App: Component<()> = |cx| {
 #[derive(Props)]
 struct CalculatorKeyProps<'a> {
     name: &'static str,
-    onclick: &'a dyn Fn(MouseEvent),
-    children: ScopeChildren<'a>,
+    onclick: &'a dyn Fn(Arc<MouseEvent>),
+    children: Element<'a>,
 }
 
-fn CalculatorKey<'a>((cx, props): ScopeState<'a, CalculatorKeyProps<'a>>) -> Element<'a> {
+fn CalculatorKey<'a>(cx: Scope<'a, CalculatorKeyProps<'a>>) -> Element {
     cx.render(rsx! {
         button {
             class: "calculator-key {cx.props.name}"
-            onclick: {cx.props.onclick}
-            {&props.children}
+            onclick: move |e| (cx.props.onclick)(e)
+            {&cx.props.children}
         }
     })
 }
@@ -168,7 +170,7 @@ impl Calculator {
         self.cur_val = self.display_value.parse::<f64>().unwrap();
         self.waiting_for_operand = true;
     }
-    fn handle_keydown(&mut self, evt: KeyboardEvent) {
+    fn handle_keydown(&mut self, evt: Arc<KeyboardEvent>) {
         match evt.key_code {
             KeyCode::Backspace => self.backspace(),
             KeyCode::Num0 => self.input_digit(0),

+ 2 - 24
examples/router.rs

@@ -1,5 +1,5 @@
 use dioxus::prelude::*;
-use dioxus::router::Router;
+use dioxus_router::{use_router, Link};
 
 #[derive(PartialEq, Debug, Clone)]
 pub enum Route {
@@ -24,7 +24,7 @@ pub enum Route {
 }
 
 static App: Component<()> = |cx| {
-    let route = use_router(cx, Route::parse)?;
+    let route = use_router(&cx, Route::parse);
 
     cx.render(rsx! {
         nav {
@@ -61,26 +61,4 @@ impl Route {
     }
 }
 
-impl Routable for Route {
-    fn from_path(path: &str, params: &std::collections::HashMap<&str, &str>) -> Option<Self> {
-        todo!()
-    }
-
-    fn to_path(&self) -> String {
-        todo!()
-    }
-
-    fn routes() -> Vec<&'static str> {
-        todo!()
-    }
-
-    fn not_found_route() -> Option<Self> {
-        todo!()
-    }
-
-    fn recognize(pathname: &str) -> Option<Self> {
-        todo!()
-    }
-}
-
 fn main() {}

+ 7 - 9
examples/rsx_usage.rs

@@ -39,7 +39,7 @@
 //! - Allow top-level fragments
 //!
 fn main() {
-    dioxus::desktop::launch(Example);
+    dioxus::desktop::launch(EXAMPLE);
 }
 
 /// When trying to return "nothing" to Dioxus, you'll need to specify the type parameter or Rust will be sad.
@@ -49,7 +49,7 @@ const NONE_ELEMENT: Option<()> = None;
 use baller::Baller;
 use dioxus::prelude::*;
 
-pub static Example: Component<()> = |cx| {
+pub static EXAMPLE: Component<()> = |cx| {
     let formatting = "formatting!";
     let formatting_tuple = ("a", "b");
     let lazy_fmt = format_args!("lazily formatted text");
@@ -173,12 +173,12 @@ pub static Example: Component<()> = |cx| {
             Taller { a: "asd", div {"hello world!"} }
 
             // helper functions
-            {helper(cx, "hello world!")}
+            {helper(&cx, "hello world!")}
         }
     })
 };
 
-fn helper(cx: Scope, text: &str) -> Element {
+fn helper<'a>(cx: &'a ScopeState, text: &str) -> Element<'a> {
     rsx!(cx, p { "{text}" })
 }
 
@@ -188,7 +188,7 @@ mod baller {
     pub struct BallerProps {}
 
     /// This component totally balls
-    pub fn Baller(_: ScopeState<BallerProps>) -> Element {
+    pub fn Baller(_: Scope<BallerProps>) -> Element {
         todo!()
     }
 }
@@ -196,13 +196,11 @@ mod baller {
 #[derive(Props)]
 pub struct TallerProps<'a> {
     a: &'static str,
-
-    #[builder(default)]
-    children: ScopeChildren<'a>,
+    children: Element<'a>,
 }
 
 /// This component is taller than most :)
-pub fn Taller<'a>(_: ScopeState<'a, TallerProps<'a>>) -> Element {
+pub fn Taller<'a>(_: Scope<'a, TallerProps<'a>>) -> Element {
     let b = true;
     todo!()
 }

+ 3 - 12
examples/ssr.rs

@@ -1,15 +1,13 @@
-#![allow(non_upper_case_globals)]
-
 use dioxus::prelude::*;
 use dioxus::ssr;
 
 fn main() {
-    let mut vdom = VirtualDom::new(App);
-    vdom.rebuild_in_place().expect("Rebuilding failed");
+    let mut vdom = VirtualDom::new(APP);
+    let _ = vdom.rebuild();
     println!("{}", ssr::render_vdom(&vdom));
 }
 
-static App: Component<()> = |cx| {
+static APP: Component<()> = |cx| {
     cx.render(rsx!(
         div {
             h1 { "Title" }
@@ -17,10 +15,3 @@ static App: Component<()> = |cx| {
         }
     ))
 };
-
-struct MyProps<'a> {
-    text: &'a str,
-}
-fn App2<'a>(cx: Scope<'a, MyProps<'a>>) -> Element {
-    None
-}

+ 1 - 1
examples/tailwind.rs

@@ -2,7 +2,7 @@ use dioxus::prelude::*;
 
 fn main() {
     use dioxus::desktop::wry::application::platform::macos::*;
-    dioxus::desktop::launch(App, |c| {
+    dioxus::desktop::launch_cfg(App, |c| {
         c.with_window(|w| {
             w.with_fullsize_content_view(true)
                 .with_titlebar_buttons_hidden(false)

+ 8 - 13
examples/tasks.rs

@@ -6,29 +6,24 @@ use std::time::Duration;
 
 use dioxus::prelude::*;
 fn main() {
-    dioxus::desktop::launch(App);
+    dioxus::desktop::launch(app);
 }
 
-static App: Component<()> = |cx| {
+fn app(cx: Scope<()>) -> Element {
     let mut count = use_state(&cx, || 0);
 
-    cx.push_task(async move {
+    cx.push_task(|| async move {
         tokio::time::sleep(Duration::from_millis(100)).await;
-        println!("setting count");
         count += 1;
-        // count.set(10);
-        // *count += 1;
-        // let c = count.get() + 1;
-        // count.set(c);
     });
 
     cx.render(rsx! {
         div {
             h1 { "High-Five counter: {count}" }
-            // button {
-            //     onclick: move |_| count +=1 ,
-            //     "Click me!"
-            // }
+            button {
+                onclick: move |_| count +=1 ,
+                "Click me!"
+            }
         }
     })
-};
+}

+ 6 - 6
examples/todomvc.rs

@@ -23,9 +23,9 @@ pub struct TodoItem {
 
 const STYLE: &str = include_str!("./assets/todomvc.css");
 const App: Component<()> = |cx| {
-    let mut draft = use_state(&cx, || "".to_string());
-    let mut todos = use_state(&cx, || HashMap::<u32, Rc<TodoItem>>::new());
-    let mut filter = use_state(&cx, || FilterState::All);
+    let draft = use_state(&cx, || "".to_string());
+    let todos = use_state(&cx, || HashMap::<u32, Rc<TodoItem>>::new());
+    let filter = use_state(&cx, || FilterState::All);
 
     let todolist = todos
         .iter()
@@ -86,9 +86,9 @@ pub struct TodoEntryProps {
 }
 
 pub fn TodoEntry(cx: Scope<TodoEntryProps>) -> Element {
-    let mut is_editing = use_state(&cx, || false);
-    let mut contents = use_state(&cx, || String::from(""));
-    let todo = &props.todo;
+    let is_editing = use_state(&cx, || false);
+    let contents = use_state(&cx, || String::from(""));
+    let todo = &cx.props.todo;
 
     rsx!(cx, li {
         "{todo.id}"

+ 2 - 2
examples/web_tick.rs

@@ -45,8 +45,8 @@ struct RowProps {
     row_id: usize,
     label: Label,
 }
-fn Row((cx, props): ScopeState<RowProps>) -> Element {
-    let [adj, col, noun] = props.label.0;
+fn Row(cx: Scope<RowProps>) -> Element {
+    let [adj, col, noun] = cx.props.label.0;
     cx.render(rsx! {
         tr {
             td { class:"col-md-1", "{cx.props.row_id}" }

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

@@ -2,7 +2,7 @@
 name = "dioxus-core-macro"
 version = "0.1.2"
 authors = ["Jonathan Kelley"]
-edition = "2018"
+edition = "2021"
 description = "Core macro for Dioxus Virtual DOM"
 license = "MIT/Apache-2.0"
 repository = "https://github.com/DioxusLabs/dioxus/"

+ 22 - 0
packages/core-macro/examples/inline_props.rs

@@ -0,0 +1,22 @@
+use dioxus_core_macro::{inline_props, Props};
+
+fn main() {}
+
+type Element<'a> = ();
+
+pub struct Scope<'a, T> {
+    props: &'a T,
+}
+
+#[inline_props]
+pub fn component(
+    cx: Scope,
+    chkk: String,
+    chkk2: String,
+    r: u32,
+    cat: &'a str,
+    drd: String,
+    e: String,
+) -> Element {
+    let r = chkk.len();
+}

+ 102 - 0
packages/core-macro/src/inlineprops.rs

@@ -0,0 +1,102 @@
+use proc_macro2::{Span, TokenStream as TokenStream2};
+use quote::{quote, ToTokens, TokenStreamExt};
+use syn::{
+    parse::{Parse, ParseStream},
+    punctuated::Punctuated,
+    token, Block, FnArg, Generics, Ident, Result, ReturnType, Token, Visibility,
+};
+
+pub struct InlinePropsBody {
+    pub vis: syn::Visibility,
+    pub fn_token: Token![fn],
+    pub ident: Ident,
+    pub cx_token: Ident,
+    pub generics: Generics,
+    pub paren_token: token::Paren,
+    pub inputs: Punctuated<FnArg, Token![,]>,
+    // pub fields: FieldsNamed,
+    pub output: ReturnType,
+    pub block: Box<Block>,
+}
+
+/// The custom rusty variant of parsing rsx!
+impl Parse for InlinePropsBody {
+    fn parse(input: ParseStream) -> Result<Self> {
+        let vis: Visibility = input.parse()?;
+
+        let fn_token = input.parse()?;
+        let ident = input.parse()?;
+        let generics = input.parse()?;
+
+        let content;
+        let paren_token = syn::parenthesized!(content in input);
+
+        let cx_token: Ident = content.parse()?;
+        let _: Token![:] = content.parse()?;
+        let _: Ident = content.parse()?;
+        let _: Result<Token![,]> = content.parse();
+
+        let inputs = syn::punctuated::Punctuated::parse_terminated(&content)?;
+
+        let output = input.parse()?;
+
+        let block = input.parse()?;
+
+        Ok(Self {
+            vis,
+            fn_token,
+            ident,
+            generics,
+            paren_token,
+            inputs,
+            output,
+            block,
+            cx_token,
+        })
+    }
+}
+
+/// Serialize the same way, regardless of flavor
+impl ToTokens for InlinePropsBody {
+    fn to_tokens(&self, out_tokens: &mut TokenStream2) {
+        let Self {
+            vis,
+            ident,
+            generics,
+            inputs,
+            output,
+            block,
+            cx_token,
+            ..
+        } = self;
+
+        let fields = inputs.iter().map(|f| {
+            quote! { #vis #f }
+        });
+
+        let struct_name = Ident::new(&format!("{}Props", ident), Span::call_site());
+
+        let field_names = inputs.iter().filter_map(|f| match f {
+            FnArg::Receiver(_) => todo!(),
+            FnArg::Typed(t) => Some(&t.pat),
+        });
+
+        let modifiers = if generics.params.is_empty() {
+            quote! { #[derive(Props, PartialEq)] }
+        } else {
+            quote! { #[derive(PartialEq)] }
+        };
+
+        out_tokens.append_all(quote! {
+            #modifiers
+            #vis struct #struct_name #generics {
+                #(#fields),*
+            }
+
+            #vis fn #ident #generics (#cx_token: Scope<#struct_name>) #output {
+                let #struct_name { #(#field_names),* } = &cx.props;
+                #block
+            }
+        });
+    }
+}

+ 35 - 1
packages/core-macro/src/lib.rs

@@ -2,8 +2,8 @@ use proc_macro::TokenStream;
 use quote::ToTokens;
 use syn::parse_macro_input;
 
-pub(crate) mod htm;
 pub(crate) mod ifmt;
+pub(crate) mod inlineprops;
 pub(crate) mod props;
 pub(crate) mod router;
 pub(crate) mod rsx;
@@ -214,3 +214,37 @@ pub fn routable_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStrea
     let input = parse_macro_input!(input as Routable);
     routable_derive_impl(input).into()
 }
+
+/// Derive props for a component within the component definition.
+///
+/// This macro provides a simple transformation from `Scope<{}>` to `Scope<P>`,
+/// removing some boilerplate when defining props.
+///
+/// You don't *need* to use this macro at all, but it can be helpful in cases where
+/// you would be repeating a lot of the usual Rust boilerplate.
+///
+/// # Example
+/// ```
+/// #[inline_props]
+/// fn app(cx: Scope<{ bob: String }>) -> Element {
+///     cx.render(rsx!("hello, {bob}"))
+/// }  
+///
+/// // is equivalent to
+///
+/// #[derive(PartialEq, Props)]
+/// struct AppProps {
+///     bob: String,
+/// }  
+///
+/// fn app(cx: Scope<AppProps>) -> Element {
+///     cx.render(rsx!("hello, {bob}"))
+/// }  
+/// ```
+#[proc_macro_attribute]
+pub fn inline_props(_args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
+    match syn::parse::<inlineprops::InlinePropsBody>(s) {
+        Err(e) => e.to_compile_error().into(),
+        Ok(s) => s.to_token_stream().into(),
+    }
+}

+ 0 - 1
packages/core/Cargo.toml

@@ -52,7 +52,6 @@ fern = { version = "0.6.0", features = ["colored"] }
 rand = { version = "0.8.4", features = ["small_rng"] }
 simple_logger = "1.13.0"
 dioxus-core-macro = { path = "../core-macro", version = "^0.1.2" }
-# dioxus-hooks = { path = "../hooks" }
 criterion = "0.3.5"
 thiserror = "1.0.30"
 

+ 1 - 3
packages/core/README.md

@@ -46,11 +46,9 @@ fn main() {
         VNodes::Element(vel) => dbg!(vel),
         _ => todo!()
     }
-
 }
-
-
 ```
+
 ## Internals
 
 Dioxus-core builds off the many frameworks that came before it. Notably, Dioxus borrows these concepts:

+ 4 - 4
packages/core/examples/borrowed.rs

@@ -5,11 +5,11 @@ use dioxus_html as dioxus_elements;
 
 fn main() {}
 
-fn App(cx: Scope<()>) -> Element {
+fn app(cx: Scope) -> Element {
     cx.render(rsx!(div {
-        App2 {
+        app2 (
             p: "asd"
-        }
+        )
     }))
 }
 
@@ -18,7 +18,7 @@ struct Borrowed<'a> {
     p: &'a str,
 }
 
-fn App2<'a>(cx: Scope<'a, Borrowed<'a>>) -> Element {
+fn app2<'a>(cx: Scope<'a, Borrowed<'a>>) -> Element {
     let g = eat2(&cx);
     rsx!(cx, "")
 }

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

@@ -1,6 +1,6 @@
-//! This module contains the stateful DiffMachine and all methods to diff VNodes, their properties, and their children.
+//! This module contains the stateful DiffState and all methods to diff VNodes, their properties, and their children.
 //!
-//! The [`DiffMachine`] calculates the diffs between the old and new frames, updates the new nodes, and generates a set
+//! The [`DiffState`] calculates the diffs between the old and new frames, updates the new nodes, and generates a set
 //! of mutations for the RealDom to apply.
 //!
 //! ## Notice:
@@ -86,14 +86,14 @@
 //! - Certain web-dom-specific optimizations.
 //!
 //! More info on how to improve this diffing algorithm:
-//!  - https://hacks.mozilla.org/2019/03/fast-bump-allocated-virtual-doms-with-rust-and-wasm/
+//!  - <https://hacks.mozilla.org/2019/03/fast-bump-allocated-virtual-doms-with-rust-and-wasm/>
 
 use crate::innerlude::*;
 use fxhash::{FxHashMap, FxHashSet};
 use smallvec::{smallvec, SmallVec};
 use DomEdit::*;
 
-/// Our DiffMachine is an iterative tree differ.
+/// Our DiffState is an iterative tree differ.
 ///
 /// It uses techniques of a stack machine to allow pausing and restarting of the diff algorithm. This
 /// was originally implemented using recursive techniques, but Rust lacks the ability to call async functions recursively,
@@ -490,7 +490,7 @@ impl<'bump> DiffState<'bump> {
             (Fragment(old), Fragment(new)) => self.diff_fragment_nodes(old, new),
 
             // The normal pathway still works, but generates slightly weird instructions
-            // This pathway just ensures we get create and replace
+            // This pathway ensures uses the ReplaceAll, not the InsertAfter and remove
             (Placeholder(_), Fragment(new)) => {
                 self.stack
                     .create_children(new.children, MountType::Replace { old: old_node });

+ 8 - 11
packages/core/src/lazynodes.rs

@@ -8,10 +8,10 @@
 //! This can be done either through boxing directly, or by using dynamic-sized-types and a custom allocator. In our case,
 //! we build a tiny alloactor in the stack and allocate the closure into that.
 //!
-//! 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.
 
-use crate::prelude::{NodeFactory, VNode};
+use crate::innerlude::{NodeFactory, VNode};
 use std::mem;
 
 /// A concrete type provider for closures that build VNode structures.
@@ -35,15 +35,15 @@ enum StackNodeStorage<'a, 'b> {
 }
 
 impl<'a, 'b> LazyNodes<'a, 'b> {
-    pub fn new_some<F>(_val: F) -> Option<Self>
+    pub fn new_some<F>(_val: F) -> Self
     where
         F: FnOnce(NodeFactory<'a>) -> VNode<'a> + 'b,
     {
-        Some(Self::new(_val))
+        Self::new(_val)
     }
 
     /// force this call onto the stack
-    pub fn new_boxed<F>(_val: F) -> Option<Self>
+    pub fn new_boxed<F>(_val: F) -> Self
     where
         F: FnOnce(NodeFactory<'a>) -> VNode<'a> + 'b,
     {
@@ -55,9 +55,9 @@ impl<'a, 'b> LazyNodes<'a, 'b> {
             fac.map(inner)
         };
 
-        Some(Self {
+        Self {
             inner: StackNodeStorage::Heap(Box::new(val)),
-        })
+        }
     }
 
     pub fn new<F>(_val: F) -> Self
@@ -249,9 +249,7 @@ fn it_works() {
     let caller = LazyNodes::new_some(|f| {
         //
         f.text(format_args!("hello world!"))
-    })
-    .unwrap();
-
+    });
     let g = caller.call(factory);
 
     dbg!(g);
@@ -291,7 +289,6 @@ fn it_drops() {
             log::debug!("main closure");
             f.fragment_from_iter(it)
         })
-        .unwrap()
     };
 
     let _ = caller.call(factory);

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

@@ -57,19 +57,25 @@ pub(crate) mod innerlude {
     ///     // ...
     /// };
     /// ```
-    pub type Component<P> = fn(Scope<P>) -> Element;
+    pub type Component<P = ()> = fn(Scope<P>) -> Element;
+
+    /// A list of attributes
+    ///
+    pub type Attributes<'a> = Option<&'a [Attribute<'a>]>;
 }
 
 pub use crate::innerlude::{
     Attribute, Component, DioxusElement, DomEdit, Element, ElementId, EventHandler, EventPriority,
     IntoVNode, LazyNodes, Listener, Mutations, NodeFactory, Properties, SchedulerMsg, Scope,
-    ScopeId, ScopeState, UserEvent, VElement, VFragment, VNode, VirtualDom,
+    ScopeId, ScopeState, UserEvent, VComponent, VElement, VFragment, VNode, VPlaceholder, VText,
+    VirtualDom,
 };
 
 pub mod prelude {
     pub use crate::innerlude::Scope;
     pub use crate::innerlude::{
-        Component, DioxusElement, Element, EventHandler, LazyNodes, NodeFactory, ScopeState,
+        Attributes, Component, DioxusElement, Element, EventHandler, LazyNodes, NodeFactory,
+        ScopeState,
     };
     pub use crate::nodes::VNode;
     pub use crate::virtual_dom::{fc_to_builder, Fragment, Properties};

+ 3 - 0
packages/core/src/mutations.rs

@@ -1,6 +1,9 @@
 //! Instructions returned by the VirtualDOM on how to modify the Real DOM.
 //!
 //! This module contains an internal API to generate these instructions.
+//!
+//! Beware that changing code in this module will break compatibility with
+//! interpreters for these types of DomEdits.
 
 use crate::innerlude::*;
 use std::{any::Any, fmt::Debug};

+ 11 - 3
packages/core/src/nodes.rs

@@ -20,7 +20,7 @@ use std::{
 ///
 /// VNodes are designed to be lightweight and used with with a bump allocator. To create a VNode, you can use either of:
 ///
-/// - the [`rsx`] macro
+/// - the `rsx!` macro
 /// - the [`NodeFactory`] API
 pub enum VNode<'src> {
     /// Text VNodes simply bump-allocated (or static) string slices
@@ -51,7 +51,7 @@ pub enum VNode<'src> {
     ///         key: "a",
     ///         onclick: |e| log::info!("clicked"),
     ///         hidden: "true",
-    ///         style: { background_color: "red" }
+    ///         style: { background_color: "red" },
     ///         "hello"
     ///     }
     /// });
@@ -59,7 +59,7 @@ pub enum VNode<'src> {
     /// if let VNode::Element(velement) = node {
     ///     assert_eq!(velement.tag_name, "div");
     ///     assert_eq!(velement.namespace, None);
-    ///     assert_eq!(velement.key, Some("a));
+    ///     assert_eq!(velement.key, Some("a"));
     /// }
     /// ```
     Element(&'src VElement<'src>),
@@ -720,6 +720,14 @@ impl<'a> IntoVNode<'a> for Option<LazyNodes<'a, '_>> {
     }
 }
 
+impl<'a, 'b> IntoIterator for LazyNodes<'a, 'b> {
+    type Item = LazyNodes<'a, 'b>;
+    type IntoIter = std::iter::Once<Self::Item>;
+    fn into_iter(self) -> Self::IntoIter {
+        std::iter::once(self)
+    }
+}
+
 impl<'a, 'b> IntoVNode<'a> for LazyNodes<'a, 'b> {
     fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> {
         self.call(cx)

+ 37 - 16
packages/core/src/scopes.rs

@@ -351,7 +351,7 @@ impl ScopeArena {
 ///     cx.render(rsx!{ div {"Hello, {cx.props.name}"} })
 /// }
 /// ```
-pub struct Scope<'a, P> {
+pub struct Scope<'a, P = ()> {
     pub scope: &'a ScopeState,
     pub props: &'a P,
 }
@@ -366,6 +366,26 @@ impl<P> Clone for Scope<'_, P> {
     }
 }
 
+impl<'src, P> Scope<'src, P> {
+    pub fn register_task(self, task: impl Future<Output = ()> + 'src) {
+        let fut: &'src mut dyn Future<Output = ()> = self.scope.bump().alloc(task);
+        let boxed_fut: BumpBox<'src, dyn Future<Output = ()> + 'src> =
+            unsafe { BumpBox::from_raw(fut) };
+        let pinned_fut: Pin<BumpBox<'src, _>> = boxed_fut.into();
+
+        // erase the 'src lifetime for self-referential storage
+        // todo: provide a miri test around this
+        // concerned about segfaulting
+        let self_ref_fut = unsafe { std::mem::transmute(pinned_fut) };
+
+        self.sender
+            .unbounded_send(SchedulerMsg::NewTask(self.our_arena_idx))
+            .unwrap();
+
+        self.scope.items.borrow_mut().tasks.push(self_ref_fut);
+    }
+}
+
 impl<'a, P> std::ops::Deref for Scope<'a, P> {
     // rust will auto deref again to the original 'a lifetime at the call site
     type Target = &'a ScopeState;
@@ -578,7 +598,7 @@ impl ScopeState {
 
     /// Schedule an update for any component given its ScopeId.
     ///
-    /// A component's ScopeId can be obtained from `use_hook` or the [`Context::scope_id`] method.
+    /// A component's ScopeId can be obtained from `use_hook` or the [`ScopeState::scope_id`] method.
     ///
     /// This method should be used when you want to schedule an update for a component
     pub fn schedule_update_any(&self) -> Rc<dyn Fn(ScopeId)> {
@@ -662,29 +682,33 @@ impl ScopeState {
     /// Pushes the future onto the poll queue to be polled after the component renders.
     ///
     /// The future is forcibly dropped if the component is not ready by the next render
-    pub fn push_task<'src, F>(&'src self, fut: impl FnOnce() -> F + 'src) -> usize
+    pub fn push_task<'src, F>(&'src self, fut: F) -> usize
     where
         F: Future<Output = ()>,
         F::Output: 'src,
         F: 'src,
     {
-        self.sender
-            .unbounded_send(SchedulerMsg::NewTask(self.our_arena_idx))
-            .unwrap();
-
         // wrap it in a type that will actually drop the contents
         //
         // Safety: we just made the pointer above and will promise not to alias it!
         // The main reason we do this through from_raw is because Bumpalo's box does
         // not support unsized coercion
-        let fut: &mut dyn Future<Output = ()> = self.bump().alloc(fut());
-        let boxed_fut: BumpBox<dyn Future<Output = ()>> = unsafe { BumpBox::from_raw(fut) };
-        let pinned_fut: Pin<BumpBox<_>> = boxed_fut.into();
+
+        let fut: &'src mut dyn Future<Output = ()> = self.bump().alloc(fut);
+        let boxed_fut: BumpBox<'src, dyn Future<Output = ()> + 'src> =
+            unsafe { BumpBox::from_raw(fut) };
+        let pinned_fut: Pin<BumpBox<'src, _>> = boxed_fut.into();
 
         // erase the 'src lifetime for self-referential storage
         // todo: provide a miri test around this
         // concerned about segfaulting
-        let self_ref_fut = unsafe { std::mem::transmute(pinned_fut) };
+        let self_ref_fut = unsafe {
+            std::mem::transmute::<Pin<BumpBox<'src, _>>, Pin<BumpBox<'static, _>>>(pinned_fut)
+        };
+
+        self.sender
+            .unbounded_send(SchedulerMsg::NewTask(self.our_arena_idx))
+            .unwrap();
 
         // Push the future into the tasks
         let mut items = self.items.borrow_mut();
@@ -707,14 +731,11 @@ impl ScopeState {
     ///     cx.render(lazy_tree)
     /// }
     ///```
-    pub fn render<'src>(&'src self, rsx: Option<LazyNodes<'src, '_>>) -> Option<VNode<'src>> {
+    pub fn render<'src>(&'src self, rsx: LazyNodes<'src, '_>) -> Option<VNode<'src>> {
         let fac = NodeFactory {
             bump: &self.wip_frame().bump,
         };
-        match rsx {
-            Some(s) => Some(s.call(fac)),
-            None => todo!("oh no no nodes"),
-        }
+        Some(rsx.call(fac))
     }
 
     /// Store a value between renders

+ 7 - 7
packages/core/src/virtual_dom.rs

@@ -15,7 +15,7 @@ use std::{any::Any, collections::VecDeque, iter::FromIterator, pin::Pin, sync::A
 ///
 /// ## Guide
 ///
-/// Components are defined as simple functions that take [`Context`] and a [`Properties`] type and return an [`Element`].  
+/// Components are defined as simple functions that take [`Scope`] and return an [`Element`].  
 ///
 /// ```rust, ignore
 /// #[derive(Props, PartialEq)]
@@ -552,11 +552,11 @@ impl VirtualDom {
     /// let dom = VirtualDom::new(Base);
     /// let nodes = dom.render_nodes(rsx!("div"));
     /// ```
-    pub fn render_vnodes<'a>(&'a self, lazy_nodes: Option<LazyNodes<'a, '_>>) -> &'a VNode<'a> {
+    pub fn render_vnodes<'a>(&'a self, lazy_nodes: LazyNodes<'a, '_>) -> &'a VNode<'a> {
         let scope = self.scopes.get_scope(ScopeId(0)).unwrap();
         let frame = scope.wip_frame();
         let factory = NodeFactory { bump: &frame.bump };
-        let node = lazy_nodes.unwrap().call(factory);
+        let node = lazy_nodes.call(factory);
         frame.bump.alloc(node)
     }
 
@@ -594,7 +594,7 @@ impl VirtualDom {
     /// let dom = VirtualDom::new(Base);
     /// let nodes = dom.render_nodes(rsx!("div"));
     /// ```
-    pub fn create_vnodes<'a>(&'a self, nodes: Option<LazyNodes<'a, '_>>) -> Mutations<'a> {
+    pub fn create_vnodes<'a>(&'a self, nodes: LazyNodes<'a, '_>) -> Mutations<'a> {
         let mut machine = DiffState::new(&self.scopes);
         machine.stack.element_stack.push(ElementId(0));
         machine
@@ -619,8 +619,8 @@ impl VirtualDom {
     /// ```
     pub fn diff_lazynodes<'a>(
         &'a self,
-        left: Option<LazyNodes<'a, '_>>,
-        right: Option<LazyNodes<'a, '_>>,
+        left: LazyNodes<'a, '_>,
+        right: LazyNodes<'a, '_>,
     ) -> (Mutations<'a>, Mutations<'a>) {
         let (old, new) = (self.render_vnodes(left), self.render_vnodes(right));
 
@@ -947,7 +947,7 @@ impl<'a> Properties for FragmentProps<'a> {
 #[allow(non_upper_case_globals, non_snake_case)]
 pub fn Fragment<'a>(cx: Scope<'a, FragmentProps<'a>>) -> Element {
     let i = cx.props.0.as_ref().map(|f| f.decouple());
-    cx.render(Some(LazyNodes::new(|f| f.fragment_from_iter(i))))
+    cx.render(LazyNodes::new(|f| f.fragment_from_iter(i)))
 }
 
 /// Every "Props" used for a component must implement the `Properties` trait. This trait gives some hints to Dioxus

+ 6 - 6
packages/core/tests/create_dom.rs

@@ -21,7 +21,7 @@ fn new_dom<P: 'static + Send>(app: Component<P>, props: P) -> VirtualDom {
 
 #[test]
 fn test_original_diff() {
-    static APP: Component<()> = |cx| {
+    static APP: Component = |cx| {
         cx.render(rsx! {
             div {
                 div {
@@ -57,7 +57,7 @@ fn test_original_diff() {
 
 #[test]
 fn create() {
-    static APP: Component<()> = |cx| {
+    static APP: Component = |cx| {
         cx.render(rsx! {
             div {
                 div {
@@ -120,7 +120,7 @@ fn create() {
 
 #[test]
 fn create_list() {
-    static APP: Component<()> = |cx| {
+    static APP: Component = |cx| {
         cx.render(rsx! {
             {(0..3).map(|f| rsx!{ div {
                 "hello"
@@ -169,7 +169,7 @@ fn create_list() {
 
 #[test]
 fn create_simple() {
-    static APP: Component<()> = |cx| {
+    static APP: Component = |cx| {
         cx.render(rsx! {
             div {}
             div {}
@@ -207,7 +207,7 @@ fn create_simple() {
 }
 #[test]
 fn create_components() {
-    static App: Component<()> = |cx| {
+    static App: Component = |cx| {
         cx.render(rsx! {
             Child { "abc1" }
             Child { "abc2" }
@@ -273,7 +273,7 @@ fn create_components() {
 }
 #[test]
 fn anchors() {
-    static App: Component<()> = |cx| {
+    static App: Component = |cx| {
         cx.render(rsx! {
             {true.then(|| rsx!{ div { "hello" } })}
             {false.then(|| rsx!{ div { "goodbye" } })}

+ 65 - 0
packages/core/tests/diffing.rs

@@ -802,3 +802,68 @@ fn controlled_keyed_diffing_out_of_order_max_test() {
         ]
     );
 }
+
+// noticed some weird behavior in the desktop interpreter
+// just making sure it doesnt happen in the core implementation
+#[test]
+fn remove_list() {
+    let dom = new_dom();
+
+    let left = rsx!({
+        (0..10).rev().take(5).map(|i| {
+            rsx! { Fragment { key: "{i}", "{i}" }}
+        })
+    });
+
+    let right = rsx!({
+        (0..10).rev().take(2).map(|i| {
+            rsx! { Fragment { key: "{i}", "{i}" }}
+        })
+    });
+
+    let (create, changes) = dom.diff_lazynodes(left, right);
+
+    // dbg!(create);
+    // dbg!(changes);
+
+    assert_eq!(
+        changes.edits,
+        [
+            // remove 5, 4, 3
+            Remove { root: 3 },
+            Remove { root: 4 },
+            Remove { root: 5 },
+        ]
+    );
+}
+
+// noticed some weird behavior in the desktop interpreter
+// just making sure it doesnt happen in the core implementation
+#[test]
+fn remove_list_nokeyed() {
+    let dom = new_dom();
+
+    let left = rsx!({
+        (0..10).rev().take(5).map(|i| {
+            rsx! { Fragment { "{i}" }}
+        })
+    });
+
+    let right = rsx!({
+        (0..10).rev().take(2).map(|i| {
+            rsx! { Fragment { "{i}" }}
+        })
+    });
+
+    let (create, changes) = dom.diff_lazynodes(left, right);
+
+    assert_eq!(
+        changes.edits,
+        [
+            // remove 5, 4, 3
+            Remove { root: 3 },
+            Remove { root: 4 },
+            Remove { root: 5 },
+        ]
+    );
+}

+ 3 - 2
packages/desktop/examples/async.rs

@@ -11,7 +11,7 @@ use dioxus_hooks::*;
 use dioxus_html as dioxus_elements;
 
 fn main() {
-    simple_logger::init().unwrap();
+    // simple_logger::init().unwrap();
     dioxus_desktop::launch(app);
 }
 
@@ -21,6 +21,7 @@ fn app(cx: Scope<()>) -> Element {
 
     cx.push_task(|| async move {
         tokio::time::sleep(Duration::from_millis(1000)).await;
+        println!("count is now {:?}", count);
         count += 1;
     });
 
@@ -33,4 +34,4 @@ fn app(cx: Scope<()>) -> Element {
             }
         }
     })
-};
+}

+ 46 - 27
packages/desktop/src/lib.rs

@@ -23,6 +23,7 @@ use tao::{
 pub use wry;
 pub use wry::application as tao;
 use wry::{
+    application::event_loop::EventLoopProxy,
     webview::RpcRequest,
     webview::{WebView, WebViewBuilder},
 };
@@ -37,7 +38,7 @@ pub fn launch_cfg(
     launch_with_props(root, (), config_builder)
 }
 
-pub fn launch_with_props<P: Properties + 'static + Send + Sync>(
+pub fn launch_with_props<P: 'static + Send>(
     root: Component<P>,
     props: P,
     builder: impl for<'a, 'b> FnOnce(&'b mut DesktopConfig<'a>) -> &'b mut DesktopConfig<'a>,
@@ -51,7 +52,7 @@ struct Response<'a> {
     edits: Vec<DomEdit<'a>>,
 }
 
-pub fn run<T: 'static + Send + Sync>(
+pub fn run<T: 'static + Send>(
     root: Component<T>,
     props: T,
     user_builder: impl for<'a, 'b> FnOnce(&'b mut DesktopConfig<'a>) -> &'b mut DesktopConfig<'a>,
@@ -59,51 +60,61 @@ pub fn run<T: 'static + Send + Sync>(
     let mut desktop_cfg = DesktopConfig::new();
     user_builder(&mut desktop_cfg);
 
-    let mut state = DesktopController::new_on_tokio(root, props);
+    let event_loop = EventLoop::with_user_event();
+    let mut desktop = DesktopController::new_on_tokio(root, props, event_loop.create_proxy());
     let quit_hotkey = Accelerator::new(SysMods::Cmd, KeyCode::KeyQ);
     let modifiers = ModifiersState::default();
-    let event_loop = EventLoop::new();
-
-    log::debug!("Starting event loop");
 
     event_loop.run(move |window_event, event_loop, control_flow| {
         *control_flow = ControlFlow::Wait;
 
         match window_event {
-            Event::NewEvents(StartCause::Init) => state.new_window(&desktop_cfg, event_loop),
+            Event::NewEvents(StartCause::Init) => desktop.new_window(&desktop_cfg, event_loop),
 
             Event::WindowEvent {
                 event, window_id, ..
-            } => match event {
-                WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
-                WindowEvent::Destroyed { .. } => state.close_window(window_id, control_flow),
-
-                WindowEvent::KeyboardInput { event, .. } => {
-                    if quit_hotkey.matches(&modifiers, &event.physical_key) {
-                        state.close_window(window_id, control_flow);
+            } => {
+                match event {
+                    WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
+                    WindowEvent::Destroyed { .. } => desktop.close_window(window_id, control_flow),
+
+                    WindowEvent::KeyboardInput { event, .. } => {
+                        if quit_hotkey.matches(&modifiers, &event.physical_key) {
+                            desktop.close_window(window_id, control_flow);
+                        }
                     }
-                }
 
-                WindowEvent::Resized(_) | WindowEvent::Moved(_) => {
-                    if let Some(view) = state.webviews.get_mut(&window_id) {
-                        let _ = view.resize();
+                    WindowEvent::Resized(_) | WindowEvent::Moved(_) => {
+                        if let Some(view) = desktop.webviews.get_mut(&window_id) {
+                            let _ = view.resize();
+                        }
                     }
-                }
 
-                // TODO: we want to shuttle all of these events into the user's app or provide some handler
-                _ => {}
-            },
+                    // TODO: we want to shuttle all of these events into the user's app or provide some handler
+                    _ => {}
+                }
+            }
 
-            Event::MainEventsCleared => state.try_load_ready_webviews(),
+            Event::UserEvent(_evt) => {
+                desktop.try_load_ready_webviews();
+            }
+            Event::MainEventsCleared => {
+                desktop.try_load_ready_webviews();
+            }
             Event::Resumed => {}
             Event::Suspended => {}
             Event::LoopDestroyed => {}
-
+            Event::RedrawRequested(_id) => {}
             _ => {}
         }
     })
 }
 
+pub enum UserWindowEvent {
+    Start,
+    Update,
+}
+
 pub struct DesktopController {
     webviews: HashMap<WindowId, WebView>,
     sender: futures_channel::mpsc::UnboundedSender<SchedulerMsg>,
@@ -115,7 +126,11 @@ pub struct DesktopController {
 impl DesktopController {
     // Launch the virtualdom on its own thread managed by tokio
     // returns the desktop state
-    pub fn new_on_tokio<P: Send + 'static>(root: Component<P>, props: P) -> Self {
+    pub fn new_on_tokio<P: Send + 'static>(
+        root: Component<P>,
+        props: P,
+        evt: EventLoopProxy<UserWindowEvent>,
+    ) -> Self {
         let edit_queue = Arc::new(RwLock::new(VecDeque::new()));
         let pending_edits = edit_queue.clone();
 
@@ -130,7 +145,6 @@ impl DesktopController {
                 .unwrap();
 
             runtime.block_on(async move {
-                // LocalSet::new().block_on(&runtime, async move {
                 let mut dom =
                     VirtualDom::new_with_props_and_scheduler(root, props, (sender, receiver));
 
@@ -150,6 +164,7 @@ impl DesktopController {
                             .unwrap()
                             .push_front(serde_json::to_string(&edit.edits).unwrap());
                     }
+                    let _ = evt.send_event(UserWindowEvent::Update);
                 }
             })
         });
@@ -164,7 +179,11 @@ impl DesktopController {
         }
     }
 
-    pub fn new_window(&mut self, cfg: &DesktopConfig, event_loop: &EventLoopWindowTarget<()>) {
+    pub fn new_window(
+        &mut self,
+        cfg: &DesktopConfig,
+        event_loop: &EventLoopWindowTarget<UserWindowEvent>,
+    ) {
         let builder = cfg.window.clone().with_menu({
             // create main menubar menu
             let mut menu_bar_menu = MenuBar::new();

+ 77 - 37
packages/hooks/src/usecoroutine.rs

@@ -5,44 +5,71 @@ use std::{
     pin::Pin,
     rc::Rc,
 };
+/*
 
-pub fn use_coroutine<'a, F: Future<Output = ()> + 'static>(
+
+
+let g = use_coroutine(&cx, || {
+    // clone the items in
+    async move {
+
+    }
+})
+
+
+
+*/
+pub fn use_coroutine<'a, F>(
     cx: &'a ScopeState,
-    mut f: impl FnMut() -> F + 'a,
-) -> CoroutineHandle<'a> {
+    create_future: impl FnOnce() -> F,
+) -> CoroutineHandle<'a>
+where
+    F: Future<Output = ()>,
+    F: 'static,
+{
     cx.use_hook(
         move |_| State {
             running: Default::default(),
-            fut: Default::default(),
-            submit: Default::default(),
+            pending_fut: Default::default(),
+            running_fut: Default::default(),
         },
         |state| {
-            let fut_slot = state.fut.clone();
-            let running = state.running.clone();
-            let submit: Box<dyn FnOnce() + 'a> = Box::new(move || {
-                let g = async move {
-                    running.set(true);
-                    f().await;
-                    running.set(false);
-                };
-                let p: Pin<Box<dyn Future<Output = ()>>> = Box::pin(g);
-                fut_slot
-                    .borrow_mut()
-                    .replace(unsafe { std::mem::transmute(p) });
-            });
-
-            let submit = unsafe { std::mem::transmute(submit) };
-            state.submit.get_mut().replace(submit);
-
-            if state.running.get() {
-                let mut fut = state.fut.borrow_mut();
-                cx.push_task(|| fut.as_mut().unwrap().as_mut());
-            } else {
-                // make sure to drop the old future
-                if let Some(fut) = state.fut.borrow_mut().take() {
-                    drop(fut);
-                }
+            let f = create_future();
+            state.pending_fut.set(Some(Box::pin(f)));
+
+            if let Some(fut) = state.running_fut.as_mut() {
+                cx.push_task(fut);
             }
+
+            // if let Some(fut) = state.running_fut.take() {
+            // state.running.set(true);
+            // fut.resume();
+            // }
+
+            // let submit: Box<dyn FnOnce() + 'a> = Box::new(move || {
+            //     let g = async move {
+            //         running.set(true);
+            //         create_future().await;
+            //         running.set(false);
+            //     };
+            //     let p: Pin<Box<dyn Future<Output = ()>>> = Box::pin(g);
+            //     fut_slot
+            //         .borrow_mut()
+            //         .replace(unsafe { std::mem::transmute(p) });
+            // });
+
+            // let submit = unsafe { std::mem::transmute(submit) };
+            // state.submit.get_mut().replace(submit);
+
+            // if state.running.get() {
+            //     // let mut fut = state.fut.borrow_mut();
+            //     // cx.push_task(|| fut.as_mut().unwrap().as_mut());
+            // } else {
+            //     // make sure to drop the old future
+            //     if let Some(fut) = state.fut.borrow_mut().take() {
+            //         drop(fut);
+            //     }
+            // }
             CoroutineHandle { cx, inner: state }
         },
     )
@@ -50,14 +77,20 @@ pub fn use_coroutine<'a, F: Future<Output = ()> + 'static>(
 
 struct State {
     running: Rc<Cell<bool>>,
-    submit: RefCell<Option<Box<dyn FnOnce()>>>,
-    fut: Rc<RefCell<Option<Pin<Box<dyn Future<Output = ()>>>>>>,
+
+    // the way this is structure, you can toggle the coroutine without re-rendering the comppnent
+    // this means every render *generates* the future, which is a bit of a waste
+    // todo: allocate pending futures in the bump allocator and then have a true promotion
+    pending_fut: Cell<Option<Pin<Box<dyn Future<Output = ()> + 'static>>>>,
+    running_fut: Option<Pin<Box<dyn Future<Output = ()> + 'static>>>,
+    // running_fut: Rc<RefCell<Option<Pin<Box<dyn Future<Output = ()> + 'static>>>>>,
 }
 
 pub struct CoroutineHandle<'a> {
     cx: &'a ScopeState,
     inner: &'a State,
 }
+
 impl Clone for CoroutineHandle<'_> {
     fn clone(&self) -> Self {
         CoroutineHandle {
@@ -73,10 +106,11 @@ impl<'a> CoroutineHandle<'a> {
         if self.is_running() {
             return;
         }
-        if let Some(submit) = self.inner.submit.borrow_mut().take() {
-            submit();
-            let mut fut = self.inner.fut.borrow_mut();
-            self.cx.push_task(|| fut.as_mut().unwrap().as_mut());
+
+        if let Some(submit) = self.inner.pending_fut.take() {
+            // submit();
+            // let inner = self.inner;
+            // self.cx.push_task(submit());
         }
     }
 
@@ -84,5 +118,11 @@ impl<'a> CoroutineHandle<'a> {
         self.inner.running.get()
     }
 
-    pub fn resume(&self) {}
+    pub fn resume(&self) {
+        // self.cx.push_task(fut)
+    }
+
+    pub fn stop(&self) {}
+
+    pub fn restart(&self) {}
 }

+ 3 - 3
packages/hooks/src/usemodel.rs

@@ -89,9 +89,9 @@ pub fn use_model_coroutine<'a, T, F: Future<Output = ()> + 'static>(
             }
         },
         |inner| {
-            if let Some(task) = inner.task.get_mut() {
-                cx.push_task(|| task);
-            }
+            // if let Some(task) = inner.task.get_mut() {
+            //     cx.push_task(|| task);
+            // }
             //
             todo!()
         },

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

@@ -171,7 +171,11 @@ impl<'a, T: 'static> UseState<'a, T> {
 }
 
 impl<'a, T: 'static + ToOwned<Owned = T>> UseState<'a, T> {
-    /// Gain mutable access to the new value. This method is only available when the value is a `ToOwned` type.
+    /// Gain mutable access to the new value via RefMut.
+    ///
+    /// If `modify` is called, then the component will re-render.
+    ///
+    /// This method is only available when the value is a `ToOwned` type.
     ///
     /// Mutable access is derived by calling "ToOwned" (IE cloning) on the current value.
     ///

+ 34 - 11
packages/router/src/lib.rs

@@ -2,14 +2,28 @@ mod utils;
 
 use std::{cell::RefCell, rc::Rc};
 
+use dioxus::Attribute;
 use dioxus_core as dioxus;
+
 use dioxus_core::prelude::*;
-use dioxus_core_macro::{rsx, Props};
+use dioxus_core_macro::{format_args_f, rsx, Props};
 use dioxus_html as dioxus_elements;
 // use wasm_bindgen::{JsCast, JsValue};
 
 use crate::utils::strip_slash_suffix;
 
+/// Initialize the app's router service and provide access to `Link` components
+pub fn use_router<R: 'static>(cx: &ScopeState, f: impl Fn(&str) -> R) -> &R {
+    let r = f("/");
+    cx.use_hook(
+        |_| {
+            //
+            r
+        },
+        |f| f,
+    )
+}
+
 pub trait Routable: 'static + Send + Clone + PartialEq {}
 impl<T> Routable for T where T: 'static + Send + Clone + PartialEq {}
 
@@ -30,20 +44,29 @@ pub struct LinkProps<'a, R: Routable> {
     /// Link { to: Route::Home, href: Route::as_url }
     ///
     /// ```
-    href: fn(&R) -> String,
+    #[props(default, setter(strip_option))]
+    href: Option<&'a str>,
+
+    #[props(default, setter(strip_option))]
+    class: Option<&'a str>,
 
-    #[builder(default)]
     children: Element<'a>,
+
+    #[props(default)]
+    attributes: Option<&'a [Attribute<'a>]>,
 }
 
 pub fn Link<'a, R: Routable>(cx: Scope<'a, LinkProps<'a, R>>) -> Element {
-    let service = todo!();
+    // let service = todo!();
     // let service: todo!() = use_router_service::<R>(&cx)?;
-    // cx.render(rsx! {
-    //     a {
-    //         href: format_args!("{}", (cx.props.href)(&cx.props.to)),
-    //         onclick: move |_| service.push_route(cx.props.to.clone()),
-    //         // todo!() {&cx.props.children},
-    //     }
-    // })
+    let class = cx.props.class.unwrap_or("");
+    cx.render(rsx! {
+        a {
+            href: "#",
+            class: "{class}",
+            {&cx.props.children}
+            // onclick: move |_| service.push_route(cx.props.to.clone()),
+            // href: format_args!("{}", (cx.props.href)(&cx.props.to)),
+        }
+    })
 }

+ 20 - 19
packages/web/examples/suspense.rs

@@ -15,26 +15,27 @@ fn main() {
 }
 
 static App: Component<()> = |cx| {
-    let doggo = cx.suspend(|| async move {
-        #[derive(serde::Deserialize)]
-        struct Doggo {
-            message: String,
-        }
+    todo!("suspense is broken")
+    // let doggo = suspend(|| async move {
+    //     #[derive(serde::Deserialize)]
+    //     struct Doggo {
+    //         message: String,
+    //     }
 
-        let src = reqwest::get("https://dog.ceo/api/breeds/image/random")
-            .await
-            .expect("Failed to fetch doggo")
-            .json::<Doggo>()
-            .await
-            .expect("Failed to parse doggo")
-            .message;
+    //     let src = reqwest::get("https://dog.ceo/api/breeds/image/random")
+    //         .await
+    //         .expect("Failed to fetch doggo")
+    //         .json::<Doggo>()
+    //         .await
+    //         .expect("Failed to parse doggo")
+    //         .message;
 
-        rsx!(cx, img { src: "{src}" })
-    });
+    //     rsx!(cx, img { src: "{src}" })
+    // });
 
-    rsx!(cx, div {
-        h1 {"One doggo coming right up"}
-        button { onclick: move |_| cx.needs_update(), "Get a new doggo" }
-        {doggo}
-    })
+    // rsx!(cx, div {
+    //     h1 {"One doggo coming right up"}
+    //     button { onclick: move |_| cx.needs_update(), "Get a new doggo" }
+    //     {doggo}
+    // })
 };

+ 1 - 1
packages/web/src/lib.rs

@@ -89,7 +89,7 @@ mod ric_raf;
 /// }
 /// ```
 pub fn launch(root_component: Component<()>) {
-    launch_with_props(root_component, ());
+    launch_with_props(root_component, (), |c| c);
 }
 
 /// Launches the VirtualDOM from the specified component function and props.