wip: pre vnodes instead of vnode

This commit is contained in:
Jonathan Kelley 2021-06-08 14:00:29 -04:00
parent e3d9db0847
commit fe6938ceb3
21 changed files with 303 additions and 441 deletions

View File

@ -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",

View File

@ -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 🎉")}
})
};
```

View File

@ -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:

View File

@ -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::<Fragment>()
.map(|c| AmbiguousElement::Fragment(c))
} else {
input
.parse::<Component>()
.map(|c| AmbiguousElement::Component(c))
}
input
.parse::<Component>()
.map(|c| AmbiguousElement::Component(c))
} else {
let name = input.parse::<Ident>().unwrap();
Err(Error::new(

View File

@ -26,7 +26,7 @@ pub struct Component {
// accept any path-like argument
name: syn::Path,
body: Vec<ComponentField>,
_children: Vec<Node>,
children: Vec<Node>,
}
impl Parse for Component {
@ -41,7 +41,7 @@ impl Parse for Component {
syn::braced!(content in s);
let mut body: Vec<ComponentField> = Vec::new();
let _children: Vec<Node> = Vec::new();
let mut children: Vec<Node> = 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::<ComponentField>()?);
if content.peek(Ident) && content.peek2(Token![:]) {
body.push(content.parse::<ComponentField>()?);
} else {
children.push(content.parse::<Node>()?);
}
// 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<Node> = 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
)
}),
}
// }
}
}

View File

@ -133,6 +133,10 @@ impl Parse for ElementAttr {
}
} else {
match name_str.as_str() {
"key" => {
// todo: better error here
AttrType::BumpText(s.parse::<LitStr>()?)
}
"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))
})
});
}
}
}

View File

@ -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,

View File

@ -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<T: Properties>(_f: FC<T>) -> T::Builder {
pub struct EmptyBuilder;
impl EmptyBuilder {
#[inline]
pub fn build(self) -> () {
()
}
}
pub fn fc_to_builder<T: Properties>(_: FC<T>) -> 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<T: PartialEq> 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<T> NotEq for T {}
// Concrete wrapper type where `IS_COPY` becomes `true` if `T: Copy`.
struct IsEq<G, T>(std::marker::PhantomData<(G, T)>);
impl<G: PartialEq, T: PartialEq<G>> IsEq<G, T> {
// 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::<SomeProps, SomeProps>::IS_EQ;
// let g = IsEq::<Vec<u32>>::IS_COPY;
// let g = IsEq::<u32>::IS_COPY;
// dbg!(g);
// let props = SomeProps { inner: "asd" };
// let intermediate: Box<dyn PartialEq<SomeProps>> = Box::new(props);
// let as_any: Box<dyn Any> = Box::new(intermediate);
// let as_partialeq = as_any
// .downcast_ref::<Box<dyn PartialEq<SomeProps>>>()
// .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<T> Eat for fn(T) {}
impl<T, K> 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<P> Deref for Custom<P> {
// 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 {}

View File

@ -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")
}
}
}

View File

@ -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.")
}
};
}

View File

@ -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<T>,
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<Item = impl IntoVNode<'a>>) -> 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()
}
}

View File

@ -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<P: Properties + 'a>(
bump: &'a Bump,
// bump: &'a Bump,
ctx: &NodeCtx<'a>,
component: FC<P>,
// props: bumpalo::boxed::Box<'a, P>,
props: P,
key: Option<&'a str>,
children: &'a [VNode<'a>],
) -> Self {
// pub fn new<P: Properties + 'a>(component: FC<P>, 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 }
}
}

View File

@ -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<RefCell<Vec<HeightMarker>>>);
impl EventQueue {
pub fn new_channel(&self, height: u32, idx: ScopeIdx) -> Rc<dyn Fn()> {
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<std::cmp::Ordering> {
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<usize>,
}
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<usize>,
// 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]
}
}
}

View File

@ -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,

View File

@ -84,7 +84,7 @@ fn use_map() {}
// Elements are received as Rc<T> 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(

View File

@ -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,

View File

@ -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()}
}
})
};

View File

@ -52,7 +52,7 @@ struct ButtonProps {
id: u8,
}
fn CustomButton(ctx: Context, props: &ButtonProps) -> VNode {
fn CustomButton(ctx: Context<ButtonProps>) -> VNode {
let names = ctx.use_context::<CustomContext>();
let name = names.0[ctx.id as usize];

View File

@ -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!"
}
})
};

View File

@ -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"
}
}
})
};

View File

@ -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<F: Fn(MouseEvent)> PartialEq for ButtonProps<'_, F> {
struct PlaceholderProps {
val: &'static str
}
fn Placeholder(ctx: Context, props: &PlaceholderProps) -> VNode {
fn Placeholder(ctx: Context<PlaceholderProps>) -> VNode {
ctx.render(rsx!{
div {
"child: {ctx.val}"