From cda759c659dfc4b1dde17e3896c35525005026df Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sat, 25 Dec 2021 17:18:05 -0500 Subject: [PATCH] examples: upgrade to new version of dioxus core. also add the inline_props macro --- examples/async.rs | 2 +- examples/borrowed.rs | 4 +- examples/calculator.rs | 2 +- examples/coroutine.rs | 6 +- examples/file_explorer.rs | 2 +- examples/framework_benchmark.rs | 2 +- examples/hello_world.rs | 4 +- examples/hydration.rs | 4 +- examples/manual_edits.rs | 10 +- examples/pattern_model.rs | 14 ++- examples/router.rs | 26 +---- examples/rsx_usage.rs | 16 ++- examples/ssr.rs | 15 +-- examples/tailwind.rs | 2 +- examples/tasks.rs | 21 ++-- examples/todomvc.rs | 12 +- examples/web_tick.rs | 4 +- packages/core-macro/Cargo.toml | 2 +- packages/core-macro/examples/inline_props.rs | 22 ++++ packages/core-macro/src/inlineprops.rs | 102 +++++++++++++++++ packages/core-macro/src/lib.rs | 36 +++++- packages/core/Cargo.toml | 1 - packages/core/README.md | 4 +- packages/core/examples/borrowed.rs | 8 +- packages/core/src/diff.rs | 10 +- packages/core/src/lazynodes.rs | 19 ++-- packages/core/src/lib.rs | 12 +- packages/core/src/mutations.rs | 3 + packages/core/src/nodes.rs | 14 ++- packages/core/src/scopes.rs | 53 ++++++--- packages/core/src/virtual_dom.rs | 14 +-- packages/core/tests/create_dom.rs | 12 +- packages/core/tests/diffing.rs | 65 +++++++++++ packages/desktop/examples/async.rs | 5 +- packages/desktop/src/lib.rs | 71 +++++++----- packages/hooks/src/usecoroutine.rs | 112 +++++++++++++------ packages/hooks/src/usemodel.rs | 6 +- packages/hooks/src/usestate.rs | 6 +- packages/router/src/lib.rs | 45 ++++++-- packages/web/examples/suspense.rs | 39 +++---- packages/web/src/lib.rs | 2 +- 41 files changed, 556 insertions(+), 253 deletions(-) create mode 100644 packages/core-macro/examples/inline_props.rs create mode 100644 packages/core-macro/src/inlineprops.rs diff --git a/examples/async.rs b/examples/async.rs index 16f28809e..8a7027882 100644 --- a/examples/async.rs +++ b/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; diff --git a/examples/borrowed.rs b/examples/borrowed.rs index cf497bbc4..3b05b6f9f 100644 --- a/examples/borrowed.rs +++ b/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 } }) } diff --git a/examples/calculator.rs b/examples/calculator.rs index 6a83bfa63..1b464f361 100644 --- a/examples/calculator.rs +++ b/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} }) } diff --git a/examples/coroutine.rs b/examples/coroutine.rs index e112c2995..0f647fde1 100644 --- a/examples/coroutine.rs +++ b/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} } } }) diff --git a/examples/file_explorer.rs b/examples/file_explorer.rs index 98b729896..025347b00 100644 --- a/examples/file_explorer.rs +++ b/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( diff --git a/examples/framework_benchmark.rs b/examples/framework_benchmark.rs index fada80ad4..bf5b7da24 100644 --- a/examples/framework_benchmark.rs +++ b/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}" } }) diff --git a/examples/hello_world.rs b/examples/hello_world.rs index f788f9791..24f54883a 100644 --- a/examples/hello_world.rs +++ b/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!" } )) diff --git a/examples/hydration.rs b/examples/hydration.rs index 8189f7cdd..a456c6468 100644 --- a/examples/hydration.rs +++ b/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| { diff --git a/examples/manual_edits.rs b/examples/manual_edits.rs index 7b4f9aaf2..ace529ee6 100644 --- a/examples/manual_edits.rs +++ b/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)); +} diff --git a/examples/pattern_model.rs b/examples/pattern_model.rs index f59a548cb..7b827542e 100644 --- a/examples/pattern_model.rs +++ b/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), + 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::().unwrap(); self.waiting_for_operand = true; } - fn handle_keydown(&mut self, evt: KeyboardEvent) { + fn handle_keydown(&mut self, evt: Arc) { match evt.key_code { KeyCode::Backspace => self.backspace(), KeyCode::Num0 => self.input_digit(0), diff --git a/examples/router.rs b/examples/router.rs index be055fe9e..b3e6d60b5 100644 --- a/examples/router.rs +++ b/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 { - todo!() - } - - fn to_path(&self) -> String { - todo!() - } - - fn routes() -> Vec<&'static str> { - todo!() - } - - fn not_found_route() -> Option { - todo!() - } - - fn recognize(pathname: &str) -> Option { - todo!() - } -} - fn main() {} diff --git a/examples/rsx_usage.rs b/examples/rsx_usage.rs index 9b6283d92..a7205b8dd 100644 --- a/examples/rsx_usage.rs +++ b/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) -> Element { + pub fn Baller(_: Scope) -> 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!() } diff --git a/examples/ssr.rs b/examples/ssr.rs index 542417c00..55402f579 100644 --- a/examples/ssr.rs +++ b/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 -} diff --git a/examples/tailwind.rs b/examples/tailwind.rs index 70a0448fd..91365ead3 100644 --- a/examples/tailwind.rs +++ b/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) diff --git a/examples/tasks.rs b/examples/tasks.rs index 3b14faff1..e22026589 100644 --- a/examples/tasks.rs +++ b/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!" + } } }) -}; +} diff --git a/examples/todomvc.rs b/examples/todomvc.rs index b73d4ef49..ca0df7c5b 100644 --- a/examples/todomvc.rs +++ b/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::>::new()); - let mut filter = use_state(&cx, || FilterState::All); + let draft = use_state(&cx, || "".to_string()); + let todos = use_state(&cx, || HashMap::>::new()); + let filter = use_state(&cx, || FilterState::All); let todolist = todos .iter() @@ -86,9 +86,9 @@ pub struct TodoEntryProps { } pub fn TodoEntry(cx: Scope) -> 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}" diff --git a/examples/web_tick.rs b/examples/web_tick.rs index 098bfc1f3..a9a297221 100644 --- a/examples/web_tick.rs +++ b/examples/web_tick.rs @@ -45,8 +45,8 @@ struct RowProps { row_id: usize, label: Label, } -fn Row((cx, props): ScopeState) -> Element { - let [adj, col, noun] = props.label.0; +fn Row(cx: Scope) -> Element { + let [adj, col, noun] = cx.props.label.0; cx.render(rsx! { tr { td { class:"col-md-1", "{cx.props.row_id}" } diff --git a/packages/core-macro/Cargo.toml b/packages/core-macro/Cargo.toml index dd18287b0..dee8b0a36 100644 --- a/packages/core-macro/Cargo.toml +++ b/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/" diff --git a/packages/core-macro/examples/inline_props.rs b/packages/core-macro/examples/inline_props.rs new file mode 100644 index 000000000..5be5f575f --- /dev/null +++ b/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(); +} diff --git a/packages/core-macro/src/inlineprops.rs b/packages/core-macro/src/inlineprops.rs new file mode 100644 index 000000000..ece53622f --- /dev/null +++ b/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, + // pub fields: FieldsNamed, + pub output: ReturnType, + pub block: Box, +} + +/// The custom rusty variant of parsing rsx! +impl Parse for InlinePropsBody { + fn parse(input: ParseStream) -> Result { + 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 = 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 + } + }); + } +} diff --git a/packages/core-macro/src/lib.rs b/packages/core-macro/src/lib.rs index 268c38276..85a2fa602 100644 --- a/packages/core-macro/src/lib.rs +++ b/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

`, +/// 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) -> Element { +/// cx.render(rsx!("hello, {bob}")) +/// } +/// ``` +#[proc_macro_attribute] +pub fn inline_props(_args: proc_macro::TokenStream, s: TokenStream) -> TokenStream { + match syn::parse::(s) { + Err(e) => e.to_compile_error().into(), + Ok(s) => s.to_token_stream().into(), + } +} diff --git a/packages/core/Cargo.toml b/packages/core/Cargo.toml index edb841a54..21724e005 100644 --- a/packages/core/Cargo.toml +++ b/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" diff --git a/packages/core/README.md b/packages/core/README.md index e439ab44b..672a1dc78 100644 --- a/packages/core/README.md +++ b/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: diff --git a/packages/core/examples/borrowed.rs b/packages/core/examples/borrowed.rs index f4dc7d955..cc376789c 100644 --- a/packages/core/examples/borrowed.rs +++ b/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, "") } diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 2de598796..7f852a6d0 100644 --- a/packages/core/src/diff.rs +++ b/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/ +//! - 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 }); diff --git a/packages/core/src/lazynodes.rs b/packages/core/src/lazynodes.rs index 2d10622e3..bd551da50 100644 --- a/packages/core/src/lazynodes.rs +++ b/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 . 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(_val: F) -> Option + pub fn new_some(_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(_val: F) -> Option + pub fn new_boxed(_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(_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); diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index 6d8129eed..dcc14268d 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -57,19 +57,25 @@ pub(crate) mod innerlude { /// // ... /// }; /// ``` - pub type Component

= fn(Scope

) -> Element; + pub type Component

= fn(Scope

) -> 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}; diff --git a/packages/core/src/mutations.rs b/packages/core/src/mutations.rs index 2e6333386..aef3d0561 100644 --- a/packages/core/src/mutations.rs +++ b/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}; diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 0d7d76792..fc0e38f59 100644 --- a/packages/core/src/nodes.rs +++ b/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> { } } +impl<'a, 'b> IntoIterator for LazyNodes<'a, 'b> { + type Item = LazyNodes<'a, 'b>; + type IntoIter = std::iter::Once; + 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) diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index 0b024f011..e957240f7 100644 --- a/packages/core/src/scopes.rs +++ b/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

Clone for Scope<'_, P> { } } +impl<'src, P> Scope<'src, P> { + pub fn register_task(self, task: impl Future + 'src) { + let fut: &'src mut dyn Future = self.scope.bump().alloc(task); + let boxed_fut: BumpBox<'src, dyn Future + 'src> = + unsafe { BumpBox::from_raw(fut) }; + let pinned_fut: Pin> = 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 { @@ -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, 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 = self.bump().alloc(fut()); - let boxed_fut: BumpBox> = unsafe { BumpBox::from_raw(fut) }; - let pinned_fut: Pin> = boxed_fut.into(); + + let fut: &'src mut dyn Future = self.bump().alloc(fut); + let boxed_fut: BumpBox<'src, dyn Future + 'src> = + unsafe { BumpBox::from_raw(fut) }; + let pinned_fut: Pin> = 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>>(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>) -> Option> { + pub fn render<'src>(&'src self, rsx: LazyNodes<'src, '_>) -> Option> { 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 diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 1d3d360ce..6fe0221db 100644 --- a/packages/core/src/virtual_dom.rs +++ b/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>) -> &'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>) -> 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>, - right: Option>, + 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 diff --git a/packages/core/tests/create_dom.rs b/packages/core/tests/create_dom.rs index a37fd85c3..044be289c 100644 --- a/packages/core/tests/create_dom.rs +++ b/packages/core/tests/create_dom.rs @@ -21,7 +21,7 @@ fn new_dom(app: Component

, 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" } })} diff --git a/packages/core/tests/diffing.rs b/packages/core/tests/diffing.rs index 2125cb9fb..5b98ea80e 100644 --- a/packages/core/tests/diffing.rs +++ b/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 }, + ] + ); +} diff --git a/packages/desktop/examples/async.rs b/packages/desktop/examples/async.rs index 2626355e4..d15ae4473 100644 --- a/packages/desktop/examples/async.rs +++ b/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 { } } }) -}; +} diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index a4eee7b33..246360fad 100644 --- a/packages/desktop/src/lib.rs +++ b/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( +pub fn launch_with_props( root: Component

, 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>, } -pub fn run( +pub fn run( root: Component, props: T, user_builder: impl for<'a, 'b> FnOnce(&'b mut DesktopConfig<'a>) -> &'b mut DesktopConfig<'a>, @@ -59,51 +60,61 @@ pub fn run( 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), + } => { + 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) { - state.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, sender: futures_channel::mpsc::UnboundedSender, @@ -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(root: Component

, props: P) -> Self { + pub fn new_on_tokio( + root: Component

, + props: P, + evt: EventLoopProxy, + ) -> 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, + ) { let builder = cfg.window.clone().with_menu({ // create main menubar menu let mut menu_bar_menu = MenuBar::new(); diff --git a/packages/hooks/src/usecoroutine.rs b/packages/hooks/src/usecoroutine.rs index f593c2b77..e9c277506 100644 --- a/packages/hooks/src/usecoroutine.rs +++ b/packages/hooks/src/usecoroutine.rs @@ -5,44 +5,71 @@ use std::{ pin::Pin, rc::Rc, }; +/* -pub fn use_coroutine<'a, F: Future + '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, + 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 = Box::new(move || { - let g = async move { - running.set(true); - f().await; - running.set(false); - }; - let p: Pin>> = Box::pin(g); - fut_slot - .borrow_mut() - .replace(unsafe { std::mem::transmute(p) }); - }); + let f = create_future(); + state.pending_fut.set(Some(Box::pin(f))); - 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); - } + 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 = Box::new(move || { + // let g = async move { + // running.set(true); + // create_future().await; + // running.set(false); + // }; + // let p: Pin>> = 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 + 'static>( struct State { running: Rc>, - submit: RefCell>>, - fut: Rc>>>>>, + + // 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 + 'static>>>>, + running_fut: Option + 'static>>>, + // running_fut: Rc + '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) {} } diff --git a/packages/hooks/src/usemodel.rs b/packages/hooks/src/usemodel.rs index 642081cf8..d8ab70b7a 100644 --- a/packages/hooks/src/usemodel.rs +++ b/packages/hooks/src/usemodel.rs @@ -89,9 +89,9 @@ pub fn use_model_coroutine<'a, T, F: Future + '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!() }, diff --git a/packages/hooks/src/usestate.rs b/packages/hooks/src/usestate.rs index 1c4fe2d04..3286c95a6 100644 --- a/packages/hooks/src/usestate.rs +++ b/packages/hooks/src/usestate.rs @@ -171,7 +171,11 @@ impl<'a, T: 'static> UseState<'a, T> { } impl<'a, T: 'static + ToOwned> 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. /// diff --git a/packages/router/src/lib.rs b/packages/router/src/lib.rs index 0ce88d59b..1a61b5deb 100644 --- a/packages/router/src/lib.rs +++ b/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(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 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::(&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)), + } + }) } diff --git a/packages/web/examples/suspense.rs b/packages/web/examples/suspense.rs index 4dd1b4c33..84c3b5f83 100644 --- a/packages/web/examples/suspense.rs +++ b/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::() - .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::() + // .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} + // }) }; diff --git a/packages/web/src/lib.rs b/packages/web/src/lib.rs index 8796ba35e..73c9465b6 100644 --- a/packages/web/src/lib.rs +++ b/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.