diff --git a/Cargo.toml b/Cargo.toml index 624473388..cd6fed8b1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,11 +5,11 @@ members = [ "packages/core", "packages/web", "packages/dioxus", - "packages/recoil", - "packages/docsite", "packages/ssr", - "packages/webview", ] +# "packages/recoil", +# "packages/docsite", +# "packages/webview", # "packages/cli", # "packages/webview", diff --git a/README.md b/README.md index 7210abde4..c38143995 100644 --- a/README.md +++ b/README.md @@ -8,15 +8,13 @@ Dioxus is a portable, performant, and ergonomic framework for building cross-platform user experiences in Rust. ```rust -fn Example(ctx: Context<()>) -> VNode { +fn Example(ctx: Context<()>) -> VNodes { let (selection, set_selection) = use_state(&ctx, move || "..?"); ctx.render(rsx! { - div { - h1 { "Hello, {selection}" } - button { "?", onclick: move |_| set_selection("world!")} - button { "?", onclick: move |_| set_selection("Dioxus 🎉")} - } + h1 { "Hello, {selection}" } + button { "?", onclick: move |_| set_selection("world!")} + button { "?", onclick: move |_| set_selection("Dioxus 🎉")} }) }; ``` diff --git a/notes/Parity.md b/notes/Parity.md index 1fb0b6f28..f87ac7b06 100644 --- a/notes/Parity.md +++ b/notes/Parity.md @@ -2,41 +2,41 @@ Sorted by priority -| Feature | Dioxus | React | Notes | -| ---------------------- | ------ | ----- | ------------------------------------ | -| ----- Phase 1 ----- | ----- | ----- | ----- | -| Conditional Rendering | ✅ | ✅ | if/then to hide/show component | -| Map, Iterator | ✅ | ✅ | map/filter/reduce rsx! | -| Keyed Components | ✅ | ✅ | advanced diffing with keys | -| Web | ✅ | ✅ | renderer for web browser | -| Desktop (webview) | ✅ | ✅ | renderer for desktop | -| Context | ✅ | ✅ | share state through the tree | -| Hook | ✅ | ✅ | memory cells in components | -| SSR | ✅ | ✅ | render directly to string | -| Runs natively | ✅ | 👀 | runs as a sharable binary | -| Null components | 👀 | ✅ | allow returning None | -| Fragments | 👀 | ✅ | no requirement on main root | -| Component Children | 👀 | ✅ | ctx.children() as a list of nodes | -| NodeRef | 👀 | ✅ | gain direct access to nodes | -| Controlled Inputs | 👀 | ✅ | stateful wrappers around inputs | -| No-div components | 👀 | ✅ | components that render components | -| CSS/Inline Styles | 🛠 | ✅ | syntax for inline/conditional styles | -| 1st class global state | 🛠 | ✅ | redux/recoil/mobx on top of context | -| ----- Phase 2 ----- | ----- | ----- | ----- | -| 1st class router | 👀 | ✅ | Hook built on top of history | -| Assets | 👀 | ✅ | include css/svg/img url statically | -| Integrated classnames | 🛠 | 👀 | built-in `classnames` | -| Suspense | 👀 | 👀 | schedule future render from future | -| Transition | 👀 | 👀 | High-level control over suspense | -| Animation | 👀 | ✅ | Spring-style animations | -| Mobile | 👀 | ✅ | Render with cacao | -| Desktop (native) | 👀 | ✅ | Render with native desktop | -| 3D Renderer | 👀 | ✅ | react-three-fiber | -| ----- Phase 3 ----- | ----- | ----- | ----- | -| Portal | 👀 | ✅ | cast elements through tree | -| Error/Panic boundary | 👀 | ✅ | catch panics and display BSOD | -| Code-splitting | 👀 | ✅ | Make bundle smaller/lazy | -| LiveView | 👀 | 👀 | Example for SSR + WASM apps | +| Feature | Dioxus | React | Notes | +| ---------------------- | ------ | ----- | ------------------------------------------------ | +| ----- Phase 1 ----- | ----- | ----- | ----- | +| Conditional Rendering | ✅ | ✅ | if/then to hide/show component | +| Map, Iterator | ✅ | ✅ | map/filter/reduce rsx! | +| Keyed Components | ✅ | ✅ | advanced diffing with keys | +| Web | ✅ | ✅ | renderer for web browser | +| Desktop (webview) | ✅ | ✅ | renderer for desktop | +| Context | ✅ | ✅ | share state through the tree | +| Hook | ✅ | ✅ | memory cells in components | +| SSR | ✅ | ✅ | render directly to string | +| Runs natively | ✅ | 👀 | runs as a sharable binary | +| Component Children | ✅ | ✅ | ctx.children() as a list of nodes | +| Null components | ✅ | ✅ | allow returning no components | +| No-div components | ✅ | ✅ | components that render components | +| Fragments | ✅ | ✅ | rsx! can return multiple elements without a root | +| NodeRef | 👀 | ✅ | gain direct access to nodes | +| Controlled Inputs | 👀 | ✅ | stateful wrappers around inputs | +| CSS/Inline Styles | 🛠 | ✅ | syntax for inline/conditional styles | +| 1st class global state | 🛠 | ✅ | redux/recoil/mobx on top of context | +| ----- Phase 2 ----- | ----- | ----- | ----- | +| 1st class router | 👀 | ✅ | Hook built on top of history | +| Assets | 👀 | ✅ | include css/svg/img url statically | +| Integrated classnames | 🛠 | 👀 | built-in `classnames` | +| Suspense | 👀 | 👀 | schedule future render from future | +| Transition | 👀 | 👀 | High-level control over suspense | +| Animation | 👀 | ✅ | Spring-style animations | +| Mobile | 👀 | ✅ | Render with cacao | +| Desktop (native) | 👀 | ✅ | Render with native desktop | +| 3D Renderer | 👀 | ✅ | react-three-fiber | +| ----- Phase 3 ----- | ----- | ----- | ----- | +| Portal | 👀 | ✅ | cast elements through tree | +| Error/Panic boundary | 👀 | ✅ | catch panics and display BSOD | +| Code-splitting | 👀 | ✅ | Make bundle smaller/lazy | +| LiveView | 👀 | 👀 | Example for SSR + WASM apps | ## Required services: diff --git a/packages/core-macro/src/rsx/ambiguous.rs b/packages/core-macro/src/rsx/ambiguous.rs index caaf7b0bc..15a30aa06 100644 --- a/packages/core-macro/src/rsx/ambiguous.rs +++ b/packages/core-macro/src/rsx/ambiguous.rs @@ -50,15 +50,9 @@ impl Parse for AmbiguousElement { false => { let first_char = name_str.chars().next().unwrap(); if first_char.is_ascii_uppercase() { - if name_str == "Fragment" { - input - .parse::() - .map(|c| AmbiguousElement::Fragment(c)) - } else { - input - .parse::() - .map(|c| AmbiguousElement::Component(c)) - } + input + .parse::() + .map(|c| AmbiguousElement::Component(c)) } else { let name = input.parse::().unwrap(); Err(Error::new( diff --git a/packages/core-macro/src/rsx/component.rs b/packages/core-macro/src/rsx/component.rs index 4269df013..ceb4bef05 100644 --- a/packages/core-macro/src/rsx/component.rs +++ b/packages/core-macro/src/rsx/component.rs @@ -26,7 +26,7 @@ pub struct Component { // accept any path-like argument name: syn::Path, body: Vec, - _children: Vec, + children: Vec, } impl Parse for Component { @@ -41,7 +41,7 @@ impl Parse for Component { syn::braced!(content in s); let mut body: Vec = Vec::new(); - let _children: Vec = Vec::new(); + let mut children: Vec = Vec::new(); 'parsing: loop { // [1] Break if empty @@ -49,7 +49,7 @@ impl Parse for Component { break 'parsing; } - if content.peek(token::Brace) { + if content.peek(token::Brace) && content.peek2(Token![...]) { let inner: ParseBuffer; syn::braced!(inner in content); if inner.peek(Token![...]) { @@ -57,7 +57,11 @@ impl Parse for Component { } } - body.push(content.parse::()?); + if content.peek(Ident) && content.peek2(Token![:]) { + body.push(content.parse::()?); + } else { + children.push(content.parse::()?); + } // consume comma if it exists // we don't actually care if there *are* commas between attrs @@ -66,13 +70,10 @@ impl Parse for Component { } } - // todo: add support for children - let children: Vec = vec![]; - Ok(Self { name, body, - _children: children, + children, }) } } @@ -109,9 +110,32 @@ impl ToTokens for Component { None => quote! {None}, }; - let _toks = tokens.append_all(quote! { - dioxus::builder::virtual_child(__ctx, #name, #builder, #key_token) - }); + let childs = &self.children; + let children = quote! { + ChildrenList::new(__ctx) + #( .add_child(#childs) )* + .finish() + }; + let name_str = name.segments.last().unwrap().ident.to_string(); + match name_str.as_str() { + "Fragment" => tokens.append_all(quote! { + dioxus::builder::vfragment( + __ctx, + #key_token, + #children + ) + }), + _ => tokens.append_all(quote! { + dioxus::builder::virtual_child( + __ctx, + #name, + #builder, + #key_token, + #children + ) + }), + } + // } } } diff --git a/packages/core-macro/src/rsx/element.rs b/packages/core-macro/src/rsx/element.rs index f4ef28f02..23b545cb9 100644 --- a/packages/core-macro/src/rsx/element.rs +++ b/packages/core-macro/src/rsx/element.rs @@ -133,6 +133,10 @@ impl Parse for ElementAttr { } } else { match name_str.as_str() { + "key" => { + // todo: better error here + AttrType::BumpText(s.parse::()?) + } "style" => { // todo!("inline style not yet supported") @@ -196,10 +200,23 @@ impl ToTokens for ElementAttr { let name = self.name.to_string(); let nameident = &self.name; let _attr_stream = TokenStream2::new(); + match &self.ty { - AttrType::BumpText(value) => { + AttrType::BumpText(value) => match name.as_str() { + "key" => { + tokens.append_all(quote! { + .key2(format_args_f!(#value)) + }); + } + _ => { + tokens.append_all(quote! { + .attr(#name, format_args_f!(#value)) + }); + } + }, + AttrType::FieldTokens(exp) => { tokens.append_all(quote! { - .attr(#name, format_args_f!(#value)) + .attr(#name, #exp) }); } AttrType::Event(event) => { @@ -207,16 +224,11 @@ impl ToTokens for ElementAttr { .add_listener(dioxus::events::on::#nameident(__ctx, #event)) }); } - AttrType::FieldTokens(exp) => { - tokens.append_all(quote! { - .attr(#name, #exp) - }); - } AttrType::EventTokens(event) => { // tokens.append_all(quote! { .add_listener(dioxus::events::on::#nameident(__ctx, #event)) - }) + }); } } } diff --git a/packages/core/examples/nested.rs b/packages/core/examples/nested.rs index 92c11f7b3..6da3fae8c 100644 --- a/packages/core/examples/nested.rs +++ b/packages/core/examples/nested.rs @@ -15,6 +15,7 @@ static Header: FC<()> = |ctx| { ctx.render(dioxus::prelude::LazyNodes::new(|nodectx| { builder::ElementBuilder::new(nodectx, "div") .child(VNode::Component(nodectx.bump().alloc(VComponent::new( + nodectx, Bottom, (), None, diff --git a/packages/core/src/component.rs b/packages/core/src/component.rs index ed11d308a..487e6e483 100644 --- a/packages/core/src/component.rs +++ b/packages/core/src/component.rs @@ -7,21 +7,12 @@ use crate::innerlude::FC; -pub type ScopeIdx = generational_arena::Index; - pub unsafe trait Properties: PartialEq + Sized { type Builder; const CAN_BE_MEMOIZED: bool; fn builder() -> Self::Builder; } -pub struct EmptyBuilder; -impl EmptyBuilder { - pub fn build(self) -> () { - () - } -} - unsafe impl Properties for () { const CAN_BE_MEMOIZED: bool = true; type Builder = EmptyBuilder; @@ -31,151 +22,14 @@ unsafe impl Properties for () { } } -pub fn fc_to_builder(_f: FC) -> T::Builder { +pub struct EmptyBuilder; +impl EmptyBuilder { + #[inline] + pub fn build(self) -> () { + () + } +} + +pub fn fc_to_builder(_: FC) -> T::Builder { T::builder() } - -mod testing { - use std::{any::Any, ops::Deref}; - - use crate::innerlude::VNode; - - // trait PossibleProps { - // type POut: PartialEq; - // fn as_partial_eq(&self) -> Option<&Self::POut> { - // None - // } - // } - - // impl PossibleProps for T { - // type POut = Self; - // } - - // struct SomeProps2<'a> { - // inner: &'a str, - // } - - // Fallback trait for to all types to default to `false`. - trait NotEq { - const IS_EQ: bool = false; - } - impl NotEq for T {} - - // Concrete wrapper type where `IS_COPY` becomes `true` if `T: Copy`. - struct IsEq(std::marker::PhantomData<(G, T)>); - - impl> IsEq { - // Because this is implemented directly on `IsCopy`, it has priority over - // the `NotCopy` trait impl. - // - // Note: this is a *totally different* associated constant from that in - // `NotCopy`. This does not specialize the `NotCopy` trait impl on `IsCopy`. - const IS_EQ: bool = true; - } - - #[derive(PartialEq)] - struct SomeProps { - inner: &'static str, - } - - struct SomeProps2 { - inner: &'static str, - } - - #[test] - fn test() { - let g = IsEq::::IS_EQ; - - // let g = IsEq::>::IS_COPY; - // let g = IsEq::::IS_COPY; - // dbg!(g); - - // let props = SomeProps { inner: "asd" }; - - // let intermediate: Box> = Box::new(props); - // let as_any: Box = Box::new(intermediate); - - // let as_partialeq = as_any - // .downcast_ref::>>() - // .unwrap(); - } - - // struct blah {} - // #[reorder_args] - pub fn blah(a: i32, b: &str, c: &str) {} - - // pub mod blah { - // pub const a: u8 = 0; - // pub const b: u8 = 1; - // } - - trait Eat {} - impl Eat for fn() {} - impl Eat for fn(T) {} - impl Eat for fn(T, K) {} - - mod other { - use super::blah; - fn test2() { - // rsx!{ - // div { - // Ele { - // a: 10, - // b: "asd" - // c: impl Fn() -> () - // } - // } - // } - - // becomes - - // const reorder: fn(_, _) = |a, b| {}; - // blah::META; - // let a = 10; - // let b = "asd"; - // let g = [10, 10.0]; - // let c = g.a; - - // blah(10, "asd"); - } - } - - struct Inner<'a> { - a: String, - b: i32, - c: &'a str, - } - - struct Custom<'a, P: 'a> { - inner: &'a P, - // inner: *const (), - _p: std::marker::PhantomData<&'a P>, - } - - impl<'a, P> Custom<'a, P> { - fn props(&self) -> &P { - todo!() - } - } - - // impl

Deref for Custom

{ - // type Target = Inner; - - // fn deref(&self) -> &Self::Target { - // unsafe { &*self.inner } - // } - // } - - fn test2<'a>(a: Custom<'a, Inner<'a>>) -> VNode { - let r = a.inner; - - todo!() - // let g = a.props(); - // todo!() - // let g = &a.a; - } - - fn is_comp() {} -} - -mod style {} diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index b28c4724b..c4adbfb34 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -201,11 +201,13 @@ impl<'a> DiffMachine<'a> { todo!("Suspended components not currently available") } - // (VNode::Fragment(_), VNode::Fragment(_)) => { - // todo!("Fragments not currently supported in diffing") - // } // Fragments are special - (VNode::Fragment(_), _) | (_, VNode::Fragment(_)) => { + // we actually have to remove a bunch of nodes + (VNode::Fragment(_), _) => { + todo!("Fragments not currently supported in diffing") + } + + (_, VNode::Fragment(_)) => { todo!("Fragments not currently supported in diffing") } } @@ -227,7 +229,7 @@ impl<'a> DiffMachine<'a> { self.change_list.create_text_node(text); } VNode::Element(&VElement { - key: _, + key, tag_name, listeners, attributes, @@ -266,7 +268,12 @@ impl<'a> DiffMachine<'a> { for child in children { self.create(child); - self.change_list.append_child(); + if let VNode::Fragment(_) = child { + // do nothing + // fragments append themselves + } else { + self.change_list.append_child(); + } } } @@ -284,22 +291,23 @@ impl<'a> DiffMachine<'a> { // those references are stable, even if the component arena moves around in memory, thanks to the bump arenas. // However, there is no way to convey this to rust, so we need to use unsafe to pierce through the lifetime. + let parent_idx = self.cur_idx; + // Insert a new scope into our component list let idx = self .components .with(|components| { components.insert_with(|new_idx| { - let cur_idx = self.cur_idx; - let cur_scope = self.components.try_get(cur_idx).unwrap(); - let height = cur_scope.height + 1; + let parent_scope = self.components.try_get(parent_idx).unwrap(); + let height = parent_scope.height + 1; Scope::new( caller, new_idx, - Some(cur_idx), + Some(parent_idx), height, self.event_queue.new_channel(height, new_idx), self.components.clone(), - &[], + component.children, ) }) }) @@ -323,24 +331,24 @@ impl<'a> DiffMachine<'a> { // Run the scope for one iteration to initialize it new_component.run_scope().unwrap(); - // // Navigate the diff machine to the right point in the output dom - // self.change_list.load_known_root(id); - // let root_id = component.stable_addr - // And then run the diff algorithm self.diff_node(new_component.old_frame(), new_component.next_frame()); // Finally, insert this node as a seen node. self.seen_nodes.insert(idx); } - VNode::Suspended => { - todo!("Creation of VNode::Suspended not yet supported") - } // we go the the "known root" but only operate on a sibling basis VNode::Fragment(frag) => { - // - todo!("Cannot current create fragments") + // create the children directly in the space + for child in frag.children { + self.create(child); + self.change_list.append_child(); + } + } + + VNode::Suspended => { + todo!("Creation of VNode::Suspended not yet supported") } } } diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index f16b633f7..7299286a5 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -87,8 +87,6 @@ pub mod builder { pub use super::nodebuilder::*; } -pub mod support; - // types used internally that are important pub(crate) mod innerlude { pub use crate::component::*; @@ -122,14 +120,12 @@ pub mod prelude { pub use crate::nodebuilder::LazyNodes; + pub use crate::nodebuilder::ChildrenList; pub use crate::virtual_dom::NodeCtx; // pub use nodes::iterables::IterableNodes; /// This type alias is an internal way of abstracting over the static functions that represent components. pub use crate::innerlude::FC; - // TODO @Jon, fix this - // hack the VNode type until VirtualNode is fixed in the macro crate - // expose our bumpalo type pub use bumpalo; pub use bumpalo::Bump; @@ -141,19 +137,9 @@ pub mod prelude { pub use dioxus_core_macro::{format_args_f, html, rsx, Props}; - pub use crate::component::ScopeIdx; pub use crate::diff::DiffMachine; + pub use crate::virtual_dom::ScopeIdx; pub use crate::debug_renderer::DebugRenderer; - pub use crate::dioxus_main; pub use crate::hooks::*; } - -#[macro_export] -macro_rules! dioxus_main { - ($i:ident) => { - fn main() { - todo!("this macro is a placeholder for launching a dioxus app on different platforms. \nYou probably don't want to use this, but it's okay for small apps.") - } - }; -} diff --git a/packages/core/src/nodebuilder.rs b/packages/core/src/nodebuilder.rs index fd95a05bd..0819c2da0 100644 --- a/packages/core/src/nodebuilder.rs +++ b/packages/core/src/nodebuilder.rs @@ -1,12 +1,12 @@ //! Helpers for building virtual DOM VNodes. -use std::{any::Any, borrow::BorrowMut, intrinsics::transmute, u128}; +use std::{any::Any, borrow::BorrowMut, fmt::Arguments, intrinsics::transmute, u128}; use crate::{ events::VirtualEvent, innerlude::{Properties, VComponent, FC}, nodes::{Attribute, Listener, NodeKey, VNode}, - prelude::VElement, + prelude::{VElement, VFragment}, virtual_dom::NodeCtx, }; @@ -269,6 +269,20 @@ where self } + pub fn key2(mut self, args: Arguments) -> Self { + let key = match args.as_str() { + Some(static_str) => static_str, + None => { + use bumpalo::core_alloc::fmt::Write; + let mut s = bumpalo::collections::String::new_in(self.ctx.bump()); + s.write_fmt(args).unwrap(); + s.into_bump_str() + } + }; + self.key = NodeKey(Some(key)); + self + } + /// Create the virtual DOM VNode described by this builder. /// /// # Example @@ -655,14 +669,49 @@ pub fn virtual_child<'a, T: Properties + 'a>( f: FC, props: T, key: Option<&'a str>, // key: NodeKey<'a>, + children: &'a [VNode<'a>], ) -> VNode<'a> { // currently concerned about if props have a custom drop implementation // might override it with the props macro // todo!() VNode::Component( ctx.bump() - .alloc(crate::nodes::VComponent::new(ctx.bump(), f, props, key)), + .alloc(crate::nodes::VComponent::new(ctx, f, props, key, children)), // ctx.bump() // .alloc(crate::nodes::VComponent::new(f, props, key)), ) } + +pub fn vfragment<'a>( + ctx: &NodeCtx<'a>, + key: Option<&'a str>, // key: NodeKey<'a>, + children: &'a [VNode<'a>], +) -> VNode<'a> { + VNode::Fragment(ctx.bump().alloc(VFragment::new(key, children))) +} + +pub struct ChildrenList<'a, 'b> { + ctx: &'b NodeCtx<'a>, + children: bumpalo::collections::Vec<'a, VNode<'a>>, +} + +impl<'a, 'b> ChildrenList<'a, 'b> { + pub fn new(ctx: &'b NodeCtx<'a>) -> Self { + Self { + ctx, + children: bumpalo::collections::Vec::new_in(ctx.bump()), + } + } + + pub fn add_child(mut self, nodes: impl IntoIterator>) -> Self { + for item in nodes { + let child = item.into_vnode(&self.ctx); + self.children.push(child); + } + self + } + + pub fn finish(self) -> &'a [VNode<'a>] { + self.children.into_bump_slice() + } +} diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 44c32b4a1..dde21a7f7 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -7,6 +7,8 @@ use crate::{ events::VirtualEvent, innerlude::{Context, Properties, Scope, ScopeIdx, FC}, nodebuilder::text3, + virtual_dom::NodeCtx, + // support::NodeCtx, }; use bumpalo::Bump; use std::{ @@ -238,7 +240,6 @@ pub struct VComponent<'src> { // a pointer to the raw fn typ pub user_fc: *const (), - _p: PhantomData<&'src ()>, } impl<'a> VComponent<'a> { @@ -249,14 +250,17 @@ impl<'a> VComponent<'a> { // we want them to borrow references... maybe force implementing a "to_static_unsafe" trait pub fn new( - bump: &'a Bump, + // bump: &'a Bump, + ctx: &NodeCtx<'a>, component: FC

, // props: bumpalo::boxed::Box<'a, P>, props: P, key: Option<&'a str>, + children: &'a [VNode<'a>], ) -> Self { // pub fn new(component: FC

, props: P, key: Option<&'a str>) -> Self { // let bad_props = unsafe { transmogrify(props) }; + let bump = ctx.bump(); let caller_ref = component as *const (); let props = bump.alloc(props); @@ -319,8 +323,7 @@ impl<'a> VComponent<'a> { user_fc: caller_ref, comparator, raw_props, - _p: PhantomData, - children: &[], + children, caller, stable_addr: RefCell::new(None), } @@ -355,3 +358,14 @@ pub struct VFragment<'src> { pub key: NodeKey<'src>, pub children: &'src [VNode<'src>], } + +impl<'a> VFragment<'a> { + pub fn new(key: Option<&'a str>, children: &'a [VNode<'a>]) -> Self { + let key = match key { + Some(key) => NodeKey::new(key), + None => NodeKey(None), + }; + + Self { key, children } + } +} diff --git a/packages/core/src/support.rs b/packages/core/src/support.rs deleted file mode 100644 index a68ae57d4..000000000 --- a/packages/core/src/support.rs +++ /dev/null @@ -1,165 +0,0 @@ -use crate::{arena::ScopeArena, innerlude::*}; -use bumpalo::Bump; -use generational_arena::Arena; -use std::{ - any::{Any, TypeId}, - cell::RefCell, - collections::{HashMap, HashSet, VecDeque}, - fmt::Debug, - future::Future, - ops::Deref, - pin::Pin, - rc::{Rc, Weak}, -}; -// We actually allocate the properties for components in their parent's properties -// We then expose a handle to use those props for render in the form of "OpaqueComponent" -pub(crate) type OpaqueComponent<'e> = dyn Fn(&'e Scope) -> VNode<'e> + 'e; -// pub(crate) type OpaqueComponent<'e> = dyn for<'b> Fn(&'b Scope) -> VNode<'b> + 'e; - -#[derive(PartialEq, Debug, Clone, Default)] -pub(crate) struct EventQueue(pub Rc>>); - -impl EventQueue { - pub fn new_channel(&self, height: u32, idx: ScopeIdx) -> Rc { - let inner = self.clone(); - let marker = HeightMarker { height, idx }; - Rc::new(move || inner.0.as_ref().borrow_mut().push(marker)) - } -} - -/// A helper type that lets scopes be ordered by their height -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub(crate) struct HeightMarker { - pub idx: ScopeIdx, - pub height: u32, -} - -impl Ord for HeightMarker { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.height.cmp(&other.height) - } -} - -impl PartialOrd for HeightMarker { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -// NodeCtx is used to build VNodes in the component's memory space. -// This struct adds metadata to the final VNode about listeners, attributes, and children -#[derive(Clone)] -pub struct NodeCtx<'a> { - pub scope_ref: &'a Scope, - pub listener_id: RefCell, -} - -impl<'a> NodeCtx<'a> { - pub fn bump(&self) -> &'a Bump { - &self.scope_ref.cur_frame().bump - } -} - -impl Debug for NodeCtx<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - Ok(()) - } -} - -#[derive(Debug, PartialEq, Hash)] -pub struct ContextId { - // Which component is the scope in - original: ScopeIdx, - - // What's the height of the scope - height: u32, - - // Which scope is it (in order) - id: u32, -} - -pub struct ActiveFrame { - // We use a "generation" for users of contents in the bump frames to ensure their data isn't broken - pub generation: RefCell, - - // The double-buffering situation that we will use - pub frames: [BumpFrame; 2], -} - -pub struct BumpFrame { - pub bump: Bump, - pub head_node: VNode<'static>, -} - -impl ActiveFrame { - pub fn new() -> Self { - Self::from_frames( - BumpFrame { - bump: Bump::new(), - head_node: VNode::text(""), - }, - BumpFrame { - bump: Bump::new(), - head_node: VNode::text(""), - }, - ) - } - - pub(crate) fn from_frames(a: BumpFrame, b: BumpFrame) -> Self { - Self { - generation: 0.into(), - frames: [a, b], - } - } - - pub(crate) fn cur_frame(&self) -> &BumpFrame { - match *self.generation.borrow() & 1 == 0 { - true => &self.frames[0], - false => &self.frames[1], - } - } - pub(crate) fn cur_frame_mut(&mut self) -> &mut BumpFrame { - match *self.generation.borrow() & 1 == 0 { - true => &mut self.frames[0], - false => &mut self.frames[1], - } - } - - pub fn current_head_node<'b>(&'b self) -> &'b VNode<'b> { - let raw_node = match *self.generation.borrow() & 1 == 0 { - true => &self.frames[0], - false => &self.frames[1], - }; - - // Give out our self-referential item with our own borrowed lifetime - unsafe { - let unsafe_head = &raw_node.head_node; - let safe_node = std::mem::transmute::<&VNode<'static>, &VNode<'b>>(unsafe_head); - safe_node - } - } - - pub fn prev_head_node<'b>(&'b self) -> &'b VNode<'b> { - let raw_node = match *self.generation.borrow() & 1 != 0 { - true => &self.frames[0], - false => &self.frames[1], - }; - - // Give out our self-referential item with our own borrowed lifetime - unsafe { - let unsafe_head = &raw_node.head_node; - let safe_node = std::mem::transmute::<&VNode<'static>, &VNode<'b>>(unsafe_head); - safe_node - } - } - - pub(crate) fn next(&mut self) -> &mut BumpFrame { - *self.generation.borrow_mut() += 1; - - if *self.generation.borrow() % 2 == 0 { - &mut self.frames[0] - } else { - &mut self.frames[1] - } - } -} diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 9114780d0..5d0d62de8 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -32,6 +32,7 @@ use std::{ pin::Pin, rc::{Rc, Weak}, }; +pub type ScopeIdx = generational_arena::Index; /// An integrated virtual node system that progresses events and diffs UI trees. /// Differences are converted into patches which a renderer can use to draw the UI. @@ -420,8 +421,10 @@ impl Scope { >(caller) }; + let child_nodes = unsafe { std::mem::transmute(child_nodes) }; + Self { - child_nodes: &[], + child_nodes: child_nodes, caller, parent, arena_idx, diff --git a/packages/hooks/src/lib.rs b/packages/hooks/src/lib.rs index 847cf79e4..ddbe01699 100644 --- a/packages/hooks/src/lib.rs +++ b/packages/hooks/src/lib.rs @@ -84,7 +84,7 @@ fn use_map() {} // Elements are received as Rc in case the underlying collection is shuffled around // Setters/getters can be generated fn use_collection<'a, T: Collection>( - ctx: &Context<'a>, + ctx: &impl Scoped<'a>, f: impl Fn() -> T, ) -> CollectionHandle<'a, T> { ctx.use_hook( diff --git a/packages/recoil/examples/callback.rs b/packages/recoil/examples/callback.rs index 3d1c75b68..be67683b4 100644 --- a/packages/recoil/examples/callback.rs +++ b/packages/recoil/examples/callback.rs @@ -20,7 +20,7 @@ fn update_title(api: &RecoilApi) { } static App: FC<()> = |ctx| { - let title = use_read(ctx, &TITLE); + let title = use_read(&ctx, &TITLE); let next_light = use_recoil_api(ctx, |api| move |_| update_title(&api)); rsx! { in ctx, diff --git a/packages/web/examples/children.rs b/packages/web/examples/children.rs new file mode 100644 index 000000000..0c7bebb7f --- /dev/null +++ b/packages/web/examples/children.rs @@ -0,0 +1,36 @@ +//! Basic example that renders a simple VNode to the browser. + +use dioxus_core::prelude::*; +use dioxus_web::*; + +fn main() { + // Setup logging + wasm_logger::init(wasm_logger::Config::new(log::Level::Debug)); + console_error_panic_hook::set_once(); + + // Run the app + wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App)); +} + +static App: FC<()> = |ctx| { + ctx.render(rsx! { + Calcier { + h2 {"abc 1"} + h2 {"abc 2"} + h2 {"abc 3"} + h2 {"abc 4"} + h2 {"abc 5"} + } + }) +}; + +static Calcier: FC<()> = |ctx| { + ctx.render(rsx! { + div { + h1 { + "abc 0" + } + {ctx.children()} + } + }) +}; diff --git a/packages/web/examples/context.rs b/packages/web/examples/context.rs index 6653d61ca..17f2dea0e 100644 --- a/packages/web/examples/context.rs +++ b/packages/web/examples/context.rs @@ -52,7 +52,7 @@ struct ButtonProps { id: u8, } -fn CustomButton(ctx: Context, props: &ButtonProps) -> VNode { +fn CustomButton(ctx: Context) -> VNode { let names = ctx.use_context::(); let name = names.0[ctx.id as usize]; diff --git a/packages/web/examples/fragment.rs b/packages/web/examples/fragment.rs new file mode 100644 index 000000000..dd3bf7e86 --- /dev/null +++ b/packages/web/examples/fragment.rs @@ -0,0 +1,27 @@ +//! Basic example that renders a simple VNode to the browser. + +use dioxus_core::prelude::*; +use dioxus_web::*; + +fn main() { + // Setup logging + wasm_logger::init(wasm_logger::Config::new(log::Level::Debug)); + console_error_panic_hook::set_once(); + + // Run the app + wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App)); +} + +static App: FC<()> = |ctx| { + ctx.render(rsx! { + h2 { "abc 1" } + h2 { "abc 1" } + h2 { "abc 1" } + h2 { "abc 1" } + h2 { "abc 1" } + h2 { "abc 1" } + div { + "hello world!" + } + }) +}; diff --git a/packages/web/examples/many.rs b/packages/web/examples/many.rs new file mode 100644 index 000000000..d595468f1 --- /dev/null +++ b/packages/web/examples/many.rs @@ -0,0 +1,21 @@ +use dioxus_core::prelude::*; +use dioxus_web::WebsysRenderer; + +fn main() { + wasm_logger::init(wasm_logger::Config::new(log::Level::Debug)); + console_error_panic_hook::set_once(); + + log::info!("hello world"); + wasm_bindgen_futures::spawn_local(WebsysRenderer::start(Example)); +} + +static Example: FC<()> = |ctx| { + ctx.render(rsx! { + div { + span { + class: "px-2 py-1 flex w-36 mt-4 items-center text-xs rounded-md font-semibold text-yellow-500 bg-yellow-100" + "DUE DATE : 18 JUN" + } + } + }) +}; diff --git a/packages/web/examples/rsxt.rs b/packages/web/examples/rsxt.rs index dbe85d207..94fb9407d 100644 --- a/packages/web/examples/rsxt.rs +++ b/packages/web/examples/rsxt.rs @@ -53,7 +53,7 @@ struct ButtonProps<'src, F: Fn(MouseEvent)> { handler: F } -fn CustomButton<'b, 'a, F: Fn(MouseEvent)>(ctx: Context<'a>, props: &'b ButtonProps<'b, F>) -> VNode { +fn CustomButton<'a, F: Fn(MouseEvent)>(ctx: Context<'a, ButtonProps<'a, F>>) -> VNode { ctx.render(rsx!{ button { class: "inline-block py-4 px-8 mr-6 leading-none text-white bg-indigo-600 hover:bg-indigo-900 font-semibold rounded shadow" @@ -74,7 +74,7 @@ impl PartialEq for ButtonProps<'_, F> { struct PlaceholderProps { val: &'static str } -fn Placeholder(ctx: Context, props: &PlaceholderProps) -> VNode { +fn Placeholder(ctx: Context) -> VNode { ctx.render(rsx!{ div { "child: {ctx.val}"