Merge pull request #11 from jkelleyrtp/jk/constify
change vnode structure
This commit is contained in:
41 files changed
+1979
-1479
No files matched your search
+4
-4
@@ -20,14 +20,14 @@ dioxus-ssr = { path = "./packages/ssr", optional = true }
|
|||||||
[features]
|
[features]
|
||||||
default = [
|
default = [
|
||||||
"core",
|
"core",
|
||||||
"atoms",
|
|
||||||
"macro",
|
"macro",
|
||||||
"ssr",
|
|
||||||
"hooks",
|
"hooks",
|
||||||
"router",
|
|
||||||
"html",
|
"html",
|
||||||
|
"ssr",
|
||||||
"web",
|
"web",
|
||||||
"desktop",
|
"desktop",
|
||||||
|
# "atoms",
|
||||||
|
# "router",
|
||||||
]
|
]
|
||||||
atoms = []
|
atoms = []
|
||||||
macro = ["dioxus-core-macro"]
|
macro = ["dioxus-core-macro"]
|
||||||
@@ -63,8 +63,8 @@ members = [
|
|||||||
"packages/html",
|
"packages/html",
|
||||||
"packages/hooks",
|
"packages/hooks",
|
||||||
"packages/web",
|
"packages/web",
|
||||||
"packages/webview",
|
|
||||||
"packages/ssr",
|
"packages/ssr",
|
||||||
|
"packages/webview",
|
||||||
# "packages/atoms",
|
# "packages/atoms",
|
||||||
# "packages/docsite",
|
# "packages/docsite",
|
||||||
]
|
]
|
||||||
@@ -41,10 +41,6 @@
|
|||||||
<a href="https://docs.rs/async-imap">
|
<a href="https://docs.rs/async-imap">
|
||||||
Examples
|
Examples
|
||||||
</a>
|
</a>
|
||||||
<span> | </span>
|
|
||||||
<a href="https://github.com/async-email/async-imap/releases">
|
|
||||||
Releases
|
|
||||||
</a>
|
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -170,10 +166,10 @@ Dioxus is heavily inspired by React, but we want your transition to feel like an
|
|||||||
| Suspense | 🛠 | ✅ | schedule future render from future/promise |
|
| Suspense | 🛠 | ✅ | schedule future render from future/promise |
|
||||||
| Cooperative Scheduling | 🛠 | ✅ | Prioritize important events over non-important events |
|
| Cooperative Scheduling | 🛠 | ✅ | Prioritize important events over non-important events |
|
||||||
| Fine-grained reactivity | 🛠 | ❓ | Skip diffing for fine-grain updates |
|
| Fine-grained reactivity | 🛠 | ❓ | Skip diffing for fine-grain updates |
|
||||||
| Compile-time correct | ✅ | ❓ | Throw errors on invalid template layouts |
|
|
||||||
| Runs natively | ✅ | ❓ | runs as a portable binary w/o a runtime (Node) |
|
| Runs natively | ✅ | ❓ | runs as a portable binary w/o a runtime (Node) |
|
||||||
| 1st class global state | ✅ | ❓ | redux/recoil/mobx on top of context |
|
| 1st class global state | ✅ | ❓ | redux/recoil/mobx on top of context |
|
||||||
| Subtree Memoization | ✅ | ❓ | skip diffing static element subtrees |
|
| Subtree Memoization | ✅ | ❓ | skip diffing static element subtrees |
|
||||||
|
| Compile-time correct | ✅ | ❓ | Throw errors on invalid template layouts |
|
||||||
| Heuristic Engine | 🛠 | ❓ | track component memory usage to minimize future allocations |
|
| Heuristic Engine | 🛠 | ❓ | track component memory usage to minimize future allocations |
|
||||||
| NodeRef | 🛠 | ✅ | gain direct access to nodes [1] |
|
| NodeRef | 🛠 | ✅ | gain direct access to nodes [1] |
|
||||||
|
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ static Example: FC<()> = |cx| {
|
|||||||
|
|
||||||
// Expressions can be used in element position too:
|
// Expressions can be used in element position too:
|
||||||
{rsx!(p { "More templating!" })}
|
{rsx!(p { "More templating!" })}
|
||||||
{html!(<p>"Even HTML templating!!"</p>)}
|
// {html!(<p>"Even HTML templating!!"</p>)}
|
||||||
|
|
||||||
// Iterators
|
// Iterators
|
||||||
{(0..10).map(|i| rsx!(li { "{i}" }))}
|
{(0..10).map(|i| rsx!(li { "{i}" }))}
|
||||||
|
|||||||
+4
-1
@@ -2,7 +2,7 @@ use std::cell::Cell;
|
|||||||
|
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
use dioxus_core::{
|
use dioxus_core::{
|
||||||
nodes::{NodeKey, VElement, VText},
|
nodes::{VElement, VText},
|
||||||
RealDomNode,
|
RealDomNode,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -23,6 +23,9 @@ const Example: FC<()> = |cx| {
|
|||||||
Fragment {
|
Fragment {
|
||||||
Fragment {
|
Fragment {
|
||||||
"h1"
|
"h1"
|
||||||
|
div {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
"h2"
|
"h2"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,3 +50,24 @@ div {
|
|||||||
## Subtree memoization
|
## Subtree memoization
|
||||||
|
|
||||||
The rsx! macro needs to be *really* smart. If it detects that no dynamics are pumped into the macro, then it opts to use the "const" flavors of the element build functions we know and love. This has to be done at build time rather than runtime since components may return basically anything. Using the const flavor enables is_static which encourages Dioxus to do a ptr compare instead of a value compare to short circuit through the diffing. Due to const folding in Rust, entire subtrees can be ruled out at compile time.
|
The rsx! macro needs to be *really* smart. If it detects that no dynamics are pumped into the macro, then it opts to use the "const" flavors of the element build functions we know and love. This has to be done at build time rather than runtime since components may return basically anything. Using the const flavor enables is_static which encourages Dioxus to do a ptr compare instead of a value compare to short circuit through the diffing. Due to const folding in Rust, entire subtrees can be ruled out at compile time.
|
||||||
|
|
||||||
|
It would be interesting to fix the issue of dynamic subtrees by hashing each structure (or just the const structures) or the macro call itself. That way, each call gets its own identifier and we can make sure that two unique structures have different IDs and aren't just opaque to dioxus.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let s1 = LazyNodes::new("1", move |_| {
|
||||||
|
if rand() {
|
||||||
|
f.element()
|
||||||
|
} else {
|
||||||
|
f.element()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let s2 = LazyNodes::new("1", move |f| {
|
||||||
|
if rand() {
|
||||||
|
f.element()
|
||||||
|
} else {
|
||||||
|
f.element()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// produces the same ID with different structures
|
||||||
|
// perhaps just make this
|
||||||
|
```
|
||||||
@@ -188,7 +188,7 @@ impl ToTokens for Component {
|
|||||||
#name,
|
#name,
|
||||||
#builder,
|
#builder,
|
||||||
#key_token,
|
#key_token,
|
||||||
#children
|
__cx.bump().alloc(#children)
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,37 +23,18 @@ pub struct Element {
|
|||||||
impl ToTokens for Element {
|
impl ToTokens for Element {
|
||||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||||
let name = &self.name;
|
let name = &self.name;
|
||||||
|
let attr = &self.attributes;
|
||||||
|
let childs = &self.children;
|
||||||
|
let listeners = &self.listeners;
|
||||||
|
|
||||||
tokens.append_all(quote! {
|
tokens.append_all(quote! {
|
||||||
__cx.element(dioxus_elements::#name)
|
__cx.element(
|
||||||
});
|
dioxus_elements::#name,
|
||||||
|
__cx.bump().alloc([ #(#listeners),* ]),
|
||||||
// By gating these methods, we can keep the output of `cargo expand` readable.
|
__cx.bump().alloc([ #(#attr),* ]),
|
||||||
// We also prevent the issue where zero-sized arrays fail to have propery type inference.
|
__cx.bump().alloc([ #(#childs),* ]),
|
||||||
|
None,
|
||||||
if self.attributes.len() > 0 {
|
)
|
||||||
let attr = &self.attributes;
|
|
||||||
tokens.append_all(quote! {
|
|
||||||
.attributes([ #(#attr),* ])
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.children.len() > 0 {
|
|
||||||
let childs = &self.children;
|
|
||||||
tokens.append_all(quote! {
|
|
||||||
.children([ #(#childs),* ])
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.listeners.len() > 0 {
|
|
||||||
let listeners = &self.listeners;
|
|
||||||
tokens.append_all(quote! {
|
|
||||||
.listeners([ #(#listeners),* ])
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
tokens.append_all(quote! {
|
|
||||||
.finish()
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -83,7 +64,13 @@ impl Parse for Element {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if content.peek(Ident) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
|
if content.peek(Ident) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
|
||||||
parse_element_body(&content, &mut attributes, &mut listeners, &mut key)?;
|
parse_element_body(
|
||||||
|
&content,
|
||||||
|
&mut attributes,
|
||||||
|
&mut listeners,
|
||||||
|
&mut key,
|
||||||
|
name.clone(),
|
||||||
|
)?;
|
||||||
} else {
|
} else {
|
||||||
children.push(content.parse::<Node>()?);
|
children.push(content.parse::<Node>()?);
|
||||||
}
|
}
|
||||||
@@ -110,6 +97,7 @@ impl Parse for Element {
|
|||||||
/// Parse a VElement's Attributes
|
/// Parse a VElement's Attributes
|
||||||
/// =======================================
|
/// =======================================
|
||||||
struct ElementAttr {
|
struct ElementAttr {
|
||||||
|
element_name: Ident,
|
||||||
name: Ident,
|
name: Ident,
|
||||||
value: AttrType,
|
value: AttrType,
|
||||||
namespace: Option<String>,
|
namespace: Option<String>,
|
||||||
@@ -130,6 +118,7 @@ fn parse_element_body(
|
|||||||
attrs: &mut Vec<ElementAttr>,
|
attrs: &mut Vec<ElementAttr>,
|
||||||
listeners: &mut Vec<ElementAttr>,
|
listeners: &mut Vec<ElementAttr>,
|
||||||
key: &mut Option<AttrType>,
|
key: &mut Option<AttrType>,
|
||||||
|
element_name: Ident,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let mut name = Ident::parse_any(stream)?;
|
let mut name = Ident::parse_any(stream)?;
|
||||||
let name_str = name.to_string();
|
let name_str = name.to_string();
|
||||||
@@ -159,6 +148,7 @@ fn parse_element_body(
|
|||||||
name,
|
name,
|
||||||
value: ty,
|
value: ty,
|
||||||
namespace: None,
|
namespace: None,
|
||||||
|
element_name: element_name.clone(),
|
||||||
});
|
});
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@@ -186,6 +176,7 @@ fn parse_element_body(
|
|||||||
name,
|
name,
|
||||||
value: ty,
|
value: ty,
|
||||||
namespace: Some("style".to_string()),
|
namespace: Some("style".to_string()),
|
||||||
|
element_name: element_name.clone(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -227,13 +218,15 @@ fn parse_element_body(
|
|||||||
name,
|
name,
|
||||||
value: ty,
|
value: ty,
|
||||||
namespace: None,
|
namespace: None,
|
||||||
|
element_name,
|
||||||
});
|
});
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToTokens for ElementAttr {
|
impl ToTokens for ElementAttr {
|
||||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||||
let name = self.name.to_string();
|
let el_name = &self.element_name;
|
||||||
|
let name_str = self.name.to_string();
|
||||||
let nameident = &self.name;
|
let nameident = &self.name;
|
||||||
|
|
||||||
let namespace = match &self.namespace {
|
let namespace = match &self.namespace {
|
||||||
@@ -243,12 +236,22 @@ impl ToTokens for ElementAttr {
|
|||||||
|
|
||||||
match &self.value {
|
match &self.value {
|
||||||
AttrType::BumpText(value) => tokens.append_all(quote! {
|
AttrType::BumpText(value) => tokens.append_all(quote! {
|
||||||
__cx.attr(#name, format_args_f!(#value), #namespace)
|
dioxus_elements::#el_name.#nameident(__cx, format_args_f!(#value))
|
||||||
}),
|
}),
|
||||||
|
// __cx.attr(#name, format_args_f!(#value), #namespace, false)
|
||||||
|
//
|
||||||
|
// AttrType::BumpText(value) => tokens.append_all(quote! {
|
||||||
|
// __cx.attr(#name, format_args_f!(#value), #namespace, false)
|
||||||
|
// }),
|
||||||
AttrType::FieldTokens(exp) => tokens.append_all(quote! {
|
AttrType::FieldTokens(exp) => tokens.append_all(quote! {
|
||||||
__cx.attr(#name, #exp, #namespace)
|
dioxus_elements::#el_name.#nameident(__cx, #exp)
|
||||||
}),
|
}),
|
||||||
|
// __cx.attr(#name_str, #exp, #namespace, false)
|
||||||
|
|
||||||
|
// AttrType::FieldTokens(exp) => tokens.append_all(quote! {
|
||||||
|
// dioxus_elements::#el_name.#nameident(__cx, format_args_f!(#value))
|
||||||
|
// __cx.attr(#name_str, #exp, #namespace, false)
|
||||||
|
// }),
|
||||||
|
|
||||||
// todo: move event handlers on to the elements or onto the nodefactory
|
// todo: move event handlers on to the elements or onto the nodefactory
|
||||||
AttrType::Event(event) => tokens.append_all(quote! {
|
AttrType::Event(event) => tokens.append_all(quote! {
|
||||||
|
|||||||
@@ -97,12 +97,16 @@ impl ToTokens for RsxRender {
|
|||||||
// The `in cx` pattern allows directly rendering
|
// The `in cx` pattern allows directly rendering
|
||||||
Some(ident) => out_tokens.append_all(quote! {
|
Some(ident) => out_tokens.append_all(quote! {
|
||||||
#ident.render(dioxus::prelude::LazyNodes::new(move |__cx: NodeFactory|{
|
#ident.render(dioxus::prelude::LazyNodes::new(move |__cx: NodeFactory|{
|
||||||
|
use dioxus_elements::GlobalAttributes;
|
||||||
|
|
||||||
#inner
|
#inner
|
||||||
}))
|
}))
|
||||||
}),
|
}),
|
||||||
// Otherwise we just build the LazyNode wrapper
|
// Otherwise we just build the LazyNode wrapper
|
||||||
None => out_tokens.append_all(quote! {
|
None => out_tokens.append_all(quote! {
|
||||||
dioxus::prelude::LazyNodes::new(move |__cx: NodeFactory|{
|
dioxus::prelude::LazyNodes::new(move |__cx: NodeFactory|{
|
||||||
|
use dioxus_elements::GlobalAttributes;
|
||||||
|
|
||||||
#inner
|
#inner
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
|
|||||||
Vendored
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"rust-analyzer.inlayHints.enable": false
|
"rust-analyzer.inlayHints.enable": true
|
||||||
}
|
}
|
||||||
@@ -41,5 +41,5 @@ smallvec = "1.6.1"
|
|||||||
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = ["serialize"]
|
||||||
serialize = ["slotmap/serde", "serde"]
|
serialize = ["slotmap/serde", "serde"]
|
||||||
@@ -1,20 +1,20 @@
|
|||||||
fn main() {}
|
fn main() {}
|
||||||
|
|
||||||
use dioxus::*;
|
// use dioxus::*;
|
||||||
use dioxus_core as dioxus;
|
// use dioxus_core as dioxus;
|
||||||
use dioxus_core::prelude::*;
|
// use dioxus_core::prelude::*;
|
||||||
|
|
||||||
static Example: FC<()> = |cx| {
|
// static Example: FC<()> = |cx| {
|
||||||
let list = (0..10).map(|f| LazyNodes::new(move |f| todo!()));
|
// let list = (0..10).map(|f| LazyNodes::new(move |f| todo!()));
|
||||||
|
|
||||||
cx.render(LazyNodes::new(move |cx| {
|
// cx.render(LazyNodes::new(move |cx| {
|
||||||
let bump = cx.bump();
|
// let bump = cx.bump();
|
||||||
cx.raw_element("div")
|
// cx.raw_element("div")
|
||||||
.children([
|
// .children([
|
||||||
cx.text(format_args!("hello")),
|
// cx.text(format_args!("hello")),
|
||||||
cx.text(format_args!("hello")),
|
// cx.text(format_args!("hello")),
|
||||||
cx.fragment_from_iter(list),
|
// cx.fragment_from_iter(list),
|
||||||
])
|
// ])
|
||||||
.finish()
|
// .finish()
|
||||||
}))
|
// }))
|
||||||
};
|
// };
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
use std::pin::Pin;
|
|
||||||
|
|
||||||
use dioxus_core::prelude::*;
|
use dioxus_core::prelude::*;
|
||||||
use std::future::Future;
|
|
||||||
|
|
||||||
fn main() {}
|
fn main() {}
|
||||||
|
|
||||||
const App: FC<()> = |cx| {
|
const App: FC<()> = |cx| {
|
||||||
// create a new future
|
// create a new future
|
||||||
let mut fut = cx.use_hook(
|
let _fut = cx.use_hook(
|
||||||
|| {
|
|| {
|
||||||
//
|
//
|
||||||
async { loop {} }
|
async { loop {} }
|
||||||
@@ -22,3 +22,15 @@ const App: FC<()> = |cx| {
|
|||||||
|
|
||||||
todo!()
|
todo!()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const Task: FC<()> = |cx| {
|
||||||
|
//
|
||||||
|
|
||||||
|
let _s = cx.use_task(|| async { "hello world".to_string() });
|
||||||
|
|
||||||
|
todo!()
|
||||||
|
};
|
||||||
|
|
||||||
|
fn use_mut<P, T>(_cx: Context<P>, _f: impl FnOnce() -> T) -> &mut T {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
fn main() {}
|
fn main() {}
|
||||||
|
|
||||||
use dioxus_core::prelude::*;
|
use dioxus_core::prelude::*;
|
||||||
use dioxus_core::*;
|
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
struct AppProps {
|
struct AppProps {
|
||||||
@@ -61,7 +61,7 @@ fn ChildItem<'a>(cx: Context<'a, ChildProps>) -> VNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for ChildProps {
|
impl PartialEq for ChildProps {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, _other: &Self) -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use dioxus_core::prelude::*;
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ impl BasicElement for div {
|
|||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new(factory: Factory) -> Self {
|
fn new(_factory: Factory) -> Self {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,8 +59,8 @@ impl SharedArena {
|
|||||||
|
|
||||||
pub fn with_scope<'b, O: 'static>(
|
pub fn with_scope<'b, O: 'static>(
|
||||||
&'b self,
|
&'b self,
|
||||||
id: ScopeIdx,
|
_id: ScopeIdx,
|
||||||
f: impl FnOnce(&'b mut Scope) -> O,
|
_f: impl FnOnce(&'b mut Scope) -> O,
|
||||||
) -> Result<O> {
|
) -> Result<O> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
@@ -69,8 +69,8 @@ impl SharedArena {
|
|||||||
// this is useful for merging lifetimes
|
// this is useful for merging lifetimes
|
||||||
pub fn with_scope_vnode<'b>(
|
pub fn with_scope_vnode<'b>(
|
||||||
&self,
|
&self,
|
||||||
id: ScopeIdx,
|
_id: ScopeIdx,
|
||||||
f: impl FnOnce(&mut Scope) -> &VNode<'b>,
|
_f: impl FnOnce(&mut Scope) -> &VNode<'b>,
|
||||||
) -> Result<&VNode<'b>> {
|
) -> Result<&VNode<'b>> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,7 @@
|
|||||||
use crate::hooklist::HookList;
|
use crate::innerlude::*;
|
||||||
use crate::{arena::SharedArena, innerlude::*};
|
|
||||||
use appendlist::AppendList;
|
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
use slotmap::DefaultKey;
|
|
||||||
use slotmap::SlotMap;
|
use std::cell::RefCell;
|
||||||
use std::marker::PhantomData;
|
|
||||||
use std::{
|
|
||||||
any::{Any, TypeId},
|
|
||||||
cell::{Cell, RefCell},
|
|
||||||
collections::{HashMap, HashSet, VecDeque},
|
|
||||||
fmt::Debug,
|
|
||||||
future::Future,
|
|
||||||
ops::Deref,
|
|
||||||
pin::Pin,
|
|
||||||
rc::{Rc, Weak},
|
|
||||||
};
|
|
||||||
pub struct ActiveFrame {
|
pub struct ActiveFrame {
|
||||||
// We use a "generation" for users of contents in the bump frames to ensure their data isn't broken
|
// We use a "generation" for users of contents in the bump frames to ensure their data isn't broken
|
||||||
pub generation: RefCell<usize>,
|
pub generation: RefCell<usize>,
|
||||||
@@ -33,11 +20,11 @@ impl ActiveFrame {
|
|||||||
Self::from_frames(
|
Self::from_frames(
|
||||||
BumpFrame {
|
BumpFrame {
|
||||||
bump: Bump::new(),
|
bump: Bump::new(),
|
||||||
head_node: VNode::static_text(""),
|
head_node: NodeFactory::static_text(""),
|
||||||
},
|
},
|
||||||
BumpFrame {
|
BumpFrame {
|
||||||
bump: Bump::new(),
|
bump: Bump::new(),
|
||||||
head_node: VNode::static_text(""),
|
head_node: NodeFactory::static_text(""),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
//! if the type suppports PartialEq. The Properties trait is used by the rsx! and html! macros to generate the type-safe builder
|
//! if the type suppports PartialEq. The Properties trait is used by the rsx! and html! macros to generate the type-safe builder
|
||||||
//! that ensures compile-time required and optional fields on cx.
|
//! that ensures compile-time required and optional fields on cx.
|
||||||
|
|
||||||
use crate::innerlude::FC;
|
use crate::innerlude::{Context, LazyNodes, VNode, FC};
|
||||||
|
|
||||||
pub trait Properties: Sized {
|
pub trait Properties: Sized {
|
||||||
type Builder;
|
type Builder;
|
||||||
@@ -50,7 +50,6 @@ pub fn fc_to_builder<T: Properties>(_: FC<T>) -> T::Builder {
|
|||||||
///
|
///
|
||||||
/// Fragments are incredibly useful when necessary, but *do* add cost in the diffing phase.
|
/// Fragments are incredibly useful when necessary, but *do* add cost in the diffing phase.
|
||||||
/// Try to avoid nesting fragments if you can. Infinitely nested Fragments *will* cause diffing to crash.
|
/// Try to avoid nesting fragments if you can. Infinitely nested Fragments *will* cause diffing to crash.
|
||||||
use crate::prelude::*;
|
|
||||||
#[allow(non_upper_case_globals, non_snake_case)]
|
#[allow(non_upper_case_globals, non_snake_case)]
|
||||||
pub fn Fragment<'a>(cx: Context<'a, ()>) -> VNode<'a> {
|
pub fn Fragment<'a>(cx: Context<'a, ()>) -> VNode<'a> {
|
||||||
cx.render(LazyNodes::new(move |f| f.fragment_from_iter(cx.children())))
|
cx.render(LazyNodes::new(move |f| f.fragment_from_iter(cx.children())))
|
||||||
|
|||||||
+105
-37
@@ -1,15 +1,12 @@
|
|||||||
use crate::hooklist::HookList;
|
use crate::innerlude::*;
|
||||||
use crate::{arena::SharedArena, innerlude::*};
|
|
||||||
use appendlist::AppendList;
|
use futures_util::FutureExt;
|
||||||
use bumpalo::Bump;
|
|
||||||
use slotmap::DefaultKey;
|
|
||||||
use slotmap::SlotMap;
|
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
any::{Any, TypeId},
|
any::TypeId,
|
||||||
cell::{Cell, RefCell},
|
cell::RefCell,
|
||||||
collections::{HashMap, HashSet, VecDeque},
|
|
||||||
fmt::Debug,
|
|
||||||
future::Future,
|
future::Future,
|
||||||
ops::Deref,
|
ops::Deref,
|
||||||
pin::Pin,
|
pin::Pin,
|
||||||
@@ -39,9 +36,8 @@ use std::{
|
|||||||
pub struct Context<'src, T> {
|
pub struct Context<'src, T> {
|
||||||
pub props: &'src T,
|
pub props: &'src T,
|
||||||
pub scope: &'src Scope,
|
pub scope: &'src Scope,
|
||||||
pub tasks: &'src RefCell<Vec<&'src mut PinnedTask>>,
|
|
||||||
}
|
}
|
||||||
pub type PinnedTask = Pin<Box<dyn Future<Output = ()>>>;
|
// pub type PinnedTask = Pin<Box<dyn Future<Output = ()>>>;
|
||||||
|
|
||||||
impl<'src, T> Copy for Context<'src, T> {}
|
impl<'src, T> Copy for Context<'src, T> {}
|
||||||
impl<'src, T> Clone for Context<'src, T> {
|
impl<'src, T> Clone for Context<'src, T> {
|
||||||
@@ -49,7 +45,6 @@ impl<'src, T> Clone for Context<'src, T> {
|
|||||||
Self {
|
Self {
|
||||||
props: self.props,
|
props: self.props,
|
||||||
scope: self.scope,
|
scope: self.scope,
|
||||||
tasks: self.tasks,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -134,7 +129,7 @@ impl<'src, P> Context<'src, P> {
|
|||||||
self,
|
self,
|
||||||
initializer: Init,
|
initializer: Init,
|
||||||
runner: Run,
|
runner: Run,
|
||||||
cleanup: Cleanup,
|
_cleanup: Cleanup,
|
||||||
) -> Output
|
) -> Output
|
||||||
where
|
where
|
||||||
State: 'static,
|
State: 'static,
|
||||||
@@ -263,31 +258,13 @@ Any function prefixed with "use" should not be called conditionally.
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn suspend<Output: 'src, Fut: FnOnce(SuspendedContext, Output) -> VNode<'src> + 'src>(
|
|
||||||
&'src self,
|
|
||||||
fut: &'src mut Pin<Box<dyn Future<Output = Output> + 'static>>,
|
|
||||||
callback: Fut,
|
|
||||||
) -> VNode<'src> {
|
|
||||||
use futures_util::FutureExt;
|
|
||||||
match fut.now_or_never() {
|
|
||||||
Some(out) => {
|
|
||||||
let suspended_cx = SuspendedContext {};
|
|
||||||
let nodes = callback(suspended_cx, out);
|
|
||||||
return nodes;
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
// we need to register this task
|
|
||||||
VNode::Suspended {
|
|
||||||
real: Cell::new(RealDomNode::empty()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `submit_task` will submit the future to be polled.
|
/// `submit_task` will submit the future to be polled.
|
||||||
///
|
///
|
||||||
/// This is useful when you have some async task that needs to be progressed.
|
/// This is useful when you have some async task that needs to be progressed.
|
||||||
///
|
///
|
||||||
|
/// This method takes ownership over the task you've provided, and must return (). This means any work that needs to
|
||||||
|
/// happen must occur within the future or scheduled for after the future completes (through schedule_update )
|
||||||
|
///
|
||||||
/// ## Explanation
|
/// ## Explanation
|
||||||
/// Dioxus will step its internal event loop if the future returns if the future completes while waiting.
|
/// Dioxus will step its internal event loop if the future returns if the future completes while waiting.
|
||||||
///
|
///
|
||||||
@@ -298,11 +275,102 @@ Any function prefixed with "use" should not be called conditionally.
|
|||||||
///
|
///
|
||||||
///
|
///
|
||||||
///
|
///
|
||||||
pub fn submit_task(&self, task: &'src mut PinnedTask) -> TaskHandle {
|
pub fn submit_task(&self, task: FiberTask) -> TaskHandle {
|
||||||
self.tasks.borrow_mut().push(task);
|
let r = (self.scope.task_submitter)(task);
|
||||||
|
// self.scope.submit_task(task);
|
||||||
|
// let r = task.then(|f| async {
|
||||||
|
// //
|
||||||
|
// });
|
||||||
|
// self.use_hook(|| Box::new(r), |_| {}, |_| {});
|
||||||
|
// *task = task.then(|f| async {
|
||||||
|
// //
|
||||||
|
// t
|
||||||
|
// // ()
|
||||||
|
// });
|
||||||
|
// let new_fut = task.then(|f| async {
|
||||||
|
// //
|
||||||
|
// ()
|
||||||
|
// });
|
||||||
|
// self.tasks.borrow_mut().push(new_fut);
|
||||||
|
|
||||||
TaskHandle { _p: PhantomData {} }
|
TaskHandle { _p: PhantomData {} }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Awaits the given task, forcing the component to re-render when the value is ready.
|
||||||
|
///
|
||||||
|
///
|
||||||
|
pub fn use_task<Out, Fut, Init>(&self, task_initializer: Init) -> &mut Option<Out>
|
||||||
|
where
|
||||||
|
Out: 'static,
|
||||||
|
Fut: Future<Output = Out>,
|
||||||
|
Fut: 'static,
|
||||||
|
Init: FnOnce() -> Fut + 'src,
|
||||||
|
{
|
||||||
|
struct TaskHook<T> {
|
||||||
|
task_dump: Rc<RefCell<Option<T>>>,
|
||||||
|
value: Option<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// whenever the task is complete, save it into th
|
||||||
|
self.use_hook(
|
||||||
|
move || {
|
||||||
|
let task_fut = task_initializer();
|
||||||
|
|
||||||
|
let task_dump = Rc::new(RefCell::new(None));
|
||||||
|
|
||||||
|
let slot = task_dump.clone();
|
||||||
|
let update = self.schedule_update();
|
||||||
|
let originator = self.scope.arena_idx.clone();
|
||||||
|
|
||||||
|
self.submit_task(Box::pin(task_fut.then(move |output| async move {
|
||||||
|
*slot.as_ref().borrow_mut() = Some(output);
|
||||||
|
update();
|
||||||
|
EventTrigger {
|
||||||
|
event: VirtualEvent::FiberEvent,
|
||||||
|
originator,
|
||||||
|
priority: EventPriority::Low,
|
||||||
|
real_node_id: None,
|
||||||
|
}
|
||||||
|
})));
|
||||||
|
|
||||||
|
TaskHook {
|
||||||
|
task_dump,
|
||||||
|
value: None,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|hook| {
|
||||||
|
if let Some(val) = hook.task_dump.as_ref().borrow_mut().take() {
|
||||||
|
hook.value = Some(val);
|
||||||
|
}
|
||||||
|
&mut hook.value
|
||||||
|
},
|
||||||
|
|_| {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Asynchronously render new nodes once the given future has completed.
|
||||||
|
///
|
||||||
|
/// # Easda
|
||||||
|
///
|
||||||
|
///
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
///
|
||||||
|
pub fn use_suspense<Out, Fut: 'static>(
|
||||||
|
&'src self,
|
||||||
|
_task_initializer: impl FnOnce() -> Fut,
|
||||||
|
_callback: impl FnOnce(SuspendedContext, Out) -> VNode<'src> + 'src,
|
||||||
|
) -> VNode<'src>
|
||||||
|
where
|
||||||
|
Out: 'src,
|
||||||
|
Fut: Future<Output = Out>,
|
||||||
|
{
|
||||||
|
// self.use_hook(|| , runner, cleanup)
|
||||||
|
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct TaskHandle<'src> {
|
pub struct TaskHandle<'src> {
|
||||||
|
|||||||
+95
-95
@@ -49,7 +49,7 @@
|
|||||||
//! - https://hacks.mozilla.org/2019/03/fast-bump-allocated-virtual-doms-with-rust-and-wasm/
|
//! - https://hacks.mozilla.org/2019/03/fast-bump-allocated-virtual-doms-with-rust-and-wasm/
|
||||||
|
|
||||||
use crate::{arena::SharedArena, innerlude::*, tasks::TaskQueue};
|
use crate::{arena::SharedArena, innerlude::*, tasks::TaskQueue};
|
||||||
use fxhash::{FxHashMap, FxHashSet};
|
use fxhash::FxHashSet;
|
||||||
|
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
|
|
||||||
@@ -107,18 +107,6 @@ pub trait RealDom<'a> {
|
|||||||
fn raw_node_as_any_mut(&self) -> &mut dyn Any;
|
fn raw_node_as_any_mut(&self) -> &mut dyn Any;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The DiffState is a cursor internal to the VirtualDOM's diffing algorithm that allows persistence of state while
|
|
||||||
/// diffing trees of components. This means we can "re-enter" a subtree of a component by queuing a "NeedToDiff" event.
|
|
||||||
///
|
|
||||||
/// By re-entering via NodeDiff, we can connect disparate edits together into a single EditList. This batching of edits
|
|
||||||
/// leads to very fast re-renders (all done in a single animation frame).
|
|
||||||
///
|
|
||||||
/// It also means diffing two trees is only ever complex as diffing a single smaller tree, and then re-entering at a
|
|
||||||
/// different cursor position.
|
|
||||||
///
|
|
||||||
/// The order of these re-entrances is stored in the DiffState itself. The DiffState comes pre-loaded with a set of components
|
|
||||||
/// that were modified by the eventtrigger. This prevents doubly evaluating components if they were both updated via
|
|
||||||
/// subscriptions and props changes.
|
|
||||||
pub struct DiffMachine<'real, 'bump, Dom: RealDom<'bump>> {
|
pub struct DiffMachine<'real, 'bump, Dom: RealDom<'bump>> {
|
||||||
pub dom: &'real mut Dom,
|
pub dom: &'real mut Dom,
|
||||||
pub components: &'bump SharedArena,
|
pub components: &'bump SharedArena,
|
||||||
@@ -158,50 +146,48 @@ where
|
|||||||
//
|
//
|
||||||
// each function call assumes the stack is fresh (empty).
|
// each function call assumes the stack is fresh (empty).
|
||||||
pub fn diff_node(&mut self, old_node: &'bump VNode<'bump>, new_node: &'bump VNode<'bump>) {
|
pub fn diff_node(&mut self, old_node: &'bump VNode<'bump>, new_node: &'bump VNode<'bump>) {
|
||||||
match (old_node, new_node) {
|
match (&old_node.kind, &new_node.kind) {
|
||||||
// Handle the "sane" cases first.
|
// Handle the "sane" cases first.
|
||||||
// The rsx and html macros strongly discourage dynamic lists not encapsulated by a "Fragment".
|
// The rsx and html macros strongly discourage dynamic lists not encapsulated by a "Fragment".
|
||||||
// So the sane (and fast!) cases are where the virtual structure stays the same and is easily diffable.
|
// So the sane (and fast!) cases are where the virtual structure stays the same and is easily diffable.
|
||||||
(VNode::Text(old), VNode::Text(new)) => {
|
(VNodeKind::Text(old), VNodeKind::Text(new)) => {
|
||||||
if old.is_static {
|
let root = old_node.dom_id.get();
|
||||||
log::debug!("encountered static text node: {:?}", old.text);
|
|
||||||
}
|
|
||||||
|
|
||||||
if old.text != new.text {
|
if old.text != new.text {
|
||||||
self.dom.push(old.dom_id.get());
|
self.dom.push(root);
|
||||||
log::debug!("Text has changed {}, {}", old.text, new.text);
|
log::debug!("Text has changed {}, {}", old.text, new.text);
|
||||||
self.dom.set_text(new.text);
|
self.dom.set_text(new.text);
|
||||||
self.dom.pop();
|
self.dom.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
new.dom_id.set(old.dom_id.get());
|
new_node.dom_id.set(root);
|
||||||
}
|
}
|
||||||
|
|
||||||
(VNode::Element(old), VNode::Element(new)) => {
|
(VNodeKind::Element(old), VNodeKind::Element(new)) => {
|
||||||
// If the element type is completely different, the element needs to be re-rendered completely
|
// If the element type is completely different, the element needs to be re-rendered completely
|
||||||
// This is an optimization React makes due to how users structure their code
|
// This is an optimization React makes due to how users structure their code
|
||||||
//
|
//
|
||||||
// In Dioxus, this is less likely to occur unless through a fragment
|
// In Dioxus, this is less likely to occur unless through a fragment
|
||||||
|
let root = old_node.dom_id.get();
|
||||||
if new.tag_name != old.tag_name || new.namespace != old.namespace {
|
if new.tag_name != old.tag_name || new.namespace != old.namespace {
|
||||||
self.dom.push(old.dom_id.get());
|
self.dom.push(root);
|
||||||
let meta = self.create(new_node);
|
let meta = self.create(new_node);
|
||||||
self.dom.replace_with(meta.added_to_stack);
|
self.dom.replace_with(meta.added_to_stack);
|
||||||
self.dom.pop();
|
self.dom.pop();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let oldid = old.dom_id.get();
|
new_node.dom_id.set(root);
|
||||||
new.dom_id.set(oldid);
|
|
||||||
|
|
||||||
// push it just in case
|
// push it just in case
|
||||||
self.dom.push(oldid);
|
// TODO: remove this - it clogs up things and is inefficient
|
||||||
|
self.dom.push(root);
|
||||||
self.diff_listeners(old.listeners, new.listeners);
|
self.diff_listeners(old.listeners, new.listeners);
|
||||||
self.diff_attr(old.attributes, new.attributes, new.namespace);
|
self.diff_attr(old.attributes, new.attributes, new.namespace);
|
||||||
self.diff_children(old.children, new.children);
|
self.diff_children(old.children, new.children);
|
||||||
self.dom.pop();
|
self.dom.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
(VNode::Component(old), VNode::Component(new)) => {
|
(VNodeKind::Component(old), VNodeKind::Component(new)) => {
|
||||||
log::warn!("diffing components? {:#?}", new.user_fc);
|
log::warn!("diffing components? {:#?}", new.user_fc);
|
||||||
if old.user_fc == new.user_fc {
|
if old.user_fc == new.user_fc {
|
||||||
// Make sure we're dealing with the same component (by function pointer)
|
// Make sure we're dealing with the same component (by function pointer)
|
||||||
@@ -257,7 +243,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
(VNode::Fragment(old), VNode::Fragment(new)) => {
|
(VNodeKind::Fragment(old), VNodeKind::Fragment(new)) => {
|
||||||
// This is the case where options or direct vnodes might be used.
|
// This is the case where options or direct vnodes might be used.
|
||||||
// In this case, it's faster to just skip ahead to their diff
|
// In this case, it's faster to just skip ahead to their diff
|
||||||
if old.children.len() == 1 && new.children.len() == 1 {
|
if old.children.len() == 1 && new.children.len() == 1 {
|
||||||
@@ -278,15 +264,19 @@ where
|
|||||||
// We also walk the "real node" list to make sure all latent roots are claened up
|
// We also walk the "real node" list to make sure all latent roots are claened up
|
||||||
// This covers the case any time a fragment or component shows up with pretty much anything else
|
// This covers the case any time a fragment or component shows up with pretty much anything else
|
||||||
(
|
(
|
||||||
VNode::Component(_) | VNode::Fragment(_) | VNode::Text(_) | VNode::Element(_),
|
VNodeKind::Component(_)
|
||||||
VNode::Component(_) | VNode::Fragment(_) | VNode::Text(_) | VNode::Element(_),
|
| VNodeKind::Fragment(_)
|
||||||
|
| VNodeKind::Text(_)
|
||||||
|
| VNodeKind::Element(_),
|
||||||
|
VNodeKind::Component(_)
|
||||||
|
| VNodeKind::Fragment(_)
|
||||||
|
| VNodeKind::Text(_)
|
||||||
|
| VNodeKind::Element(_),
|
||||||
) => {
|
) => {
|
||||||
// Choose the node to use as the placeholder for replacewith
|
// Choose the node to use as the placeholder for replacewith
|
||||||
let back_node = match old_node {
|
let back_node = match old_node.kind {
|
||||||
// We special case these two types to avoid allocating the small-vecs
|
// We special case these two types to avoid allocating the small-vecs
|
||||||
VNode::Element(_) | VNode::Text(_) => old_node
|
VNodeKind::Element(_) | VNodeKind::Text(_) => old_node.dom_id.get(),
|
||||||
.get_mounted_id(&self.components)
|
|
||||||
.expect("Element and text always have a real node"),
|
|
||||||
|
|
||||||
_ => {
|
_ => {
|
||||||
let mut old_iter = RealChildIterator::new(old_node, &self.components);
|
let mut old_iter = RealChildIterator::new(old_node, &self.components);
|
||||||
@@ -314,17 +304,17 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
(VNode::Suspended { .. }, _) => todo!(),
|
(VNodeKind::Suspended { .. }, _) => todo!(),
|
||||||
(_, VNode::Suspended { .. }) => todo!(),
|
(_, VNodeKind::Suspended { .. }) => todo!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// When we create new nodes, we need to propagate some information back up the call chain.
|
// When we create new nodes, we need to propagate some information back up the call chain.
|
||||||
// This gives the caller some information on how to handle things like insertins, appending, and subtree discarding.
|
// This gives the caller some information on how to handle things like insertins, appending, and subtree discarding.
|
||||||
struct CreateMeta {
|
pub struct CreateMeta {
|
||||||
is_static: bool,
|
pub is_static: bool,
|
||||||
added_to_stack: u32,
|
pub added_to_stack: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CreateMeta {
|
impl CreateMeta {
|
||||||
@@ -349,15 +339,15 @@ where
|
|||||||
// When this function returns, the new node is on top of the change list stack:
|
// When this function returns, the new node is on top of the change list stack:
|
||||||
//
|
//
|
||||||
// [... node]
|
// [... node]
|
||||||
fn create(&mut self, node: &'bump VNode<'bump>) -> CreateMeta {
|
pub fn create(&mut self, node: &'bump VNode<'bump>) -> CreateMeta {
|
||||||
log::warn!("Creating node! ... {:#?}", node);
|
log::warn!("Creating node! ... {:#?}", node);
|
||||||
match node {
|
match &node.kind {
|
||||||
VNode::Text(text) => {
|
VNodeKind::Text(text) => {
|
||||||
let real_id = self.dom.create_text_node(text.text);
|
let real_id = self.dom.create_text_node(text.text);
|
||||||
text.dom_id.set(real_id);
|
node.dom_id.set(real_id);
|
||||||
CreateMeta::new(text.is_static, 1)
|
CreateMeta::new(text.is_static, 1)
|
||||||
}
|
}
|
||||||
VNode::Element(el) => {
|
VNodeKind::Element(el) => {
|
||||||
// we have the potential to completely eliminate working on this node in the future(!)
|
// we have the potential to completely eliminate working on this node in the future(!)
|
||||||
//
|
//
|
||||||
// This can only be done if all of the elements properties (attrs, children, listeners, etc) are static
|
// This can only be done if all of the elements properties (attrs, children, listeners, etc) are static
|
||||||
@@ -366,14 +356,14 @@ where
|
|||||||
let mut is_static: bool = true;
|
let mut is_static: bool = true;
|
||||||
|
|
||||||
let VElement {
|
let VElement {
|
||||||
key,
|
|
||||||
tag_name,
|
tag_name,
|
||||||
listeners,
|
listeners,
|
||||||
attributes,
|
attributes,
|
||||||
children,
|
children,
|
||||||
namespace,
|
namespace,
|
||||||
dom_id,
|
static_attrs: _,
|
||||||
is_static: el_is_static,
|
static_children: _,
|
||||||
|
static_listeners: _,
|
||||||
} = el;
|
} = el;
|
||||||
|
|
||||||
let real_id = if let Some(namespace) = namespace {
|
let real_id = if let Some(namespace) = namespace {
|
||||||
@@ -381,9 +371,10 @@ where
|
|||||||
} else {
|
} else {
|
||||||
self.dom.create_element(tag_name, None)
|
self.dom.create_element(tag_name, None)
|
||||||
};
|
};
|
||||||
dom_id.set(real_id);
|
node.dom_id.set(real_id);
|
||||||
|
|
||||||
listeners.iter().enumerate().for_each(|(idx, listener)| {
|
listeners.iter().enumerate().for_each(|(idx, listener)| {
|
||||||
|
log::info!("setting listener id to {:#?}", real_id);
|
||||||
listener.mounted_node.set(real_id);
|
listener.mounted_node.set(real_id);
|
||||||
self.dom
|
self.dom
|
||||||
.new_event_listener(listener.event, listener.scope, idx, real_id);
|
.new_event_listener(listener.event, listener.scope, idx, real_id);
|
||||||
@@ -408,9 +399,9 @@ where
|
|||||||
//
|
//
|
||||||
// TODO move over
|
// TODO move over
|
||||||
// if children.len() == 1 {
|
// if children.len() == 1 {
|
||||||
// if let VNode::Text(text) = &children[0] {
|
// if let VNodeKind::Text(text) = &children[0].kind {
|
||||||
// self.dom.set_text(text.text);
|
// self.dom.set_text(text.text);
|
||||||
// return;
|
// return CreateMeta::new(is_static, 1);
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
@@ -422,17 +413,17 @@ where
|
|||||||
self.dom.append_children(child_meta.added_to_stack);
|
self.dom.append_children(child_meta.added_to_stack);
|
||||||
}
|
}
|
||||||
|
|
||||||
if is_static {
|
// if is_static {
|
||||||
log::debug!("created a static node {:#?}", node);
|
// log::debug!("created a static node {:#?}", node);
|
||||||
} else {
|
// } else {
|
||||||
log::debug!("created a dynamic node {:#?}", node);
|
// log::debug!("created a dynamic node {:#?}", node);
|
||||||
}
|
// }
|
||||||
|
|
||||||
el_is_static.set(is_static);
|
// el_is_static.set(is_static);
|
||||||
CreateMeta::new(is_static, 1)
|
CreateMeta::new(is_static, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
VNode::Component(vcomponent) => {
|
VNodeKind::Component(vcomponent) => {
|
||||||
log::debug!("Mounting a new component");
|
log::debug!("Mounting a new component");
|
||||||
let caller = vcomponent.caller.clone();
|
let caller = vcomponent.caller.clone();
|
||||||
|
|
||||||
@@ -493,7 +484,7 @@ where
|
|||||||
// Fragments are the only nodes that can contain dynamic content (IE through curlies or iterators).
|
// Fragments are the only nodes that can contain dynamic content (IE through curlies or iterators).
|
||||||
// We can never ignore their contents, so the prescence of a fragment indicates that we need always diff them.
|
// We can never ignore their contents, so the prescence of a fragment indicates that we need always diff them.
|
||||||
// Fragments will just put all their nodes onto the stack after creation
|
// Fragments will just put all their nodes onto the stack after creation
|
||||||
VNode::Fragment(frag) => {
|
VNodeKind::Fragment(frag) => {
|
||||||
let mut nodes_added = 0;
|
let mut nodes_added = 0;
|
||||||
for child in frag.children.iter().rev() {
|
for child in frag.children.iter().rev() {
|
||||||
// different types of nodes will generate different amounts on the stack
|
// different types of nodes will generate different amounts on the stack
|
||||||
@@ -508,9 +499,9 @@ where
|
|||||||
CreateMeta::new(false, nodes_added)
|
CreateMeta::new(false, nodes_added)
|
||||||
}
|
}
|
||||||
|
|
||||||
VNode::Suspended { real } => {
|
VNodeKind::Suspended => {
|
||||||
let id = self.dom.create_placeholder();
|
let id = self.dom.create_placeholder();
|
||||||
real.set(id);
|
node.dom_id.set(id);
|
||||||
CreateMeta::new(false, 1)
|
CreateMeta::new(false, 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -570,7 +561,7 @@ impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> {
|
|||||||
// if any characteristics changed, remove and then re-add
|
// if any characteristics changed, remove and then re-add
|
||||||
|
|
||||||
// if nothing changed, then just move on
|
// if nothing changed, then just move on
|
||||||
let event_type = new_l.event;
|
let _event_type = new_l.event;
|
||||||
|
|
||||||
for old_l in old {
|
for old_l in old {
|
||||||
if new_l.event == old_l.event {
|
if new_l.event == old_l.event {
|
||||||
@@ -612,7 +603,6 @@ impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> {
|
|||||||
old: &'bump [Attribute<'bump>],
|
old: &'bump [Attribute<'bump>],
|
||||||
new: &'bump [Attribute<'bump>],
|
new: &'bump [Attribute<'bump>],
|
||||||
namespace: Option<&'bump str>,
|
namespace: Option<&'bump str>,
|
||||||
// is_namespaced: bool,
|
|
||||||
) {
|
) {
|
||||||
// Do O(n^2) passes to add/update and remove attributes, since
|
// Do O(n^2) passes to add/update and remove attributes, since
|
||||||
// there are almost always very few attributes.
|
// there are almost always very few attributes.
|
||||||
@@ -620,7 +610,7 @@ impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> {
|
|||||||
// The "fast" path is when the list of attributes name is identical and in the same order
|
// The "fast" path is when the list of attributes name is identical and in the same order
|
||||||
// With the Rsx and Html macros, this will almost always be the case
|
// With the Rsx and Html macros, this will almost always be the case
|
||||||
'outer: for new_attr in new {
|
'outer: for new_attr in new {
|
||||||
if new_attr.is_volatile() {
|
if new_attr.is_volatile {
|
||||||
// self.dom.commit_traversal();
|
// self.dom.commit_traversal();
|
||||||
self.dom
|
self.dom
|
||||||
.set_attribute(new_attr.name, new_attr.value, namespace);
|
.set_attribute(new_attr.name, new_attr.value, namespace);
|
||||||
@@ -675,13 +665,13 @@ impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> {
|
|||||||
|
|
||||||
if new.len() == 1 {
|
if new.len() == 1 {
|
||||||
match (&old.first(), &new[0]) {
|
match (&old.first(), &new[0]) {
|
||||||
(Some(VNode::Text(old_vtext)), VNode::Text(new_vtext))
|
// (Some(VNodeKind::Text(old_vtext)), VNodeKind::Text(new_vtext))
|
||||||
if old_vtext.text == new_vtext.text =>
|
// if old_vtext.text == new_vtext.text =>
|
||||||
{
|
// {
|
||||||
// Don't take this fast path...
|
// // Don't take this fast path...
|
||||||
}
|
// }
|
||||||
|
|
||||||
// (_, VNode::Text(text)) => {
|
// (_, VNodeKind::Text(text)) => {
|
||||||
// // self.dom.commit_traversal();
|
// // self.dom.commit_traversal();
|
||||||
// log::debug!("using optimized text set");
|
// log::debug!("using optimized text set");
|
||||||
// self.dom.set_text(text.text);
|
// self.dom.set_text(text.text);
|
||||||
@@ -701,15 +691,15 @@ impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let new_is_keyed = new[0].key().is_some();
|
let new_is_keyed = new[0].key.is_some();
|
||||||
let old_is_keyed = old[0].key().is_some();
|
let old_is_keyed = old[0].key.is_some();
|
||||||
|
|
||||||
debug_assert!(
|
debug_assert!(
|
||||||
new.iter().all(|n| n.key().is_some() == new_is_keyed),
|
new.iter().all(|n| n.key.is_some() == new_is_keyed),
|
||||||
"all siblings must be keyed or all siblings must be non-keyed"
|
"all siblings must be keyed or all siblings must be non-keyed"
|
||||||
);
|
);
|
||||||
debug_assert!(
|
debug_assert!(
|
||||||
old.iter().all(|o| o.key().is_some() == old_is_keyed),
|
old.iter().all(|o| o.key.is_some() == old_is_keyed),
|
||||||
"all siblings must be keyed or all siblings must be non-keyed"
|
"all siblings must be keyed or all siblings must be non-keyed"
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -754,7 +744,7 @@ impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> {
|
|||||||
let mut assert_unique_keys = |children: &'bump [VNode<'bump>]| {
|
let mut assert_unique_keys = |children: &'bump [VNode<'bump>]| {
|
||||||
keys.clear();
|
keys.clear();
|
||||||
for child in children {
|
for child in children {
|
||||||
let key = child.key();
|
let key = child.key;
|
||||||
debug_assert!(
|
debug_assert!(
|
||||||
key.is_some(),
|
key.is_some(),
|
||||||
"if any sibling is keyed, all siblings must be keyed"
|
"if any sibling is keyed, all siblings must be keyed"
|
||||||
@@ -797,7 +787,7 @@ impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> {
|
|||||||
.iter()
|
.iter()
|
||||||
.rev()
|
.rev()
|
||||||
.zip(new[shared_prefix_count..].iter().rev())
|
.zip(new[shared_prefix_count..].iter().rev())
|
||||||
.take_while(|&(old, new)| old.key() == new.key())
|
.take_while(|&(old, new)| old.key == new.key)
|
||||||
.count();
|
.count();
|
||||||
|
|
||||||
let old_shared_suffix_start = old.len() - shared_suffix_count;
|
let old_shared_suffix_start = old.len() - shared_suffix_count;
|
||||||
@@ -833,8 +823,8 @@ impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> {
|
|||||||
// Upon exit, the change list stack is the same.
|
// Upon exit, the change list stack is the same.
|
||||||
fn diff_keyed_prefix(
|
fn diff_keyed_prefix(
|
||||||
&self,
|
&self,
|
||||||
old: &'bump [VNode<'bump>],
|
_old: &'bump [VNode<'bump>],
|
||||||
new: &'bump [VNode<'bump>],
|
_new: &'bump [VNode<'bump>],
|
||||||
) -> KeyedPrefixResult {
|
) -> KeyedPrefixResult {
|
||||||
todo!()
|
todo!()
|
||||||
// self.dom.go_down();
|
// self.dom.go_down();
|
||||||
@@ -922,11 +912,11 @@ impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> {
|
|||||||
// Upon exit from this function, it will be restored to that same state.
|
// Upon exit from this function, it will be restored to that same state.
|
||||||
fn diff_keyed_middle(
|
fn diff_keyed_middle(
|
||||||
&self,
|
&self,
|
||||||
old: &[VNode<'bump>],
|
_old: &[VNode<'bump>],
|
||||||
mut new: &[VNode<'bump>],
|
_new: &[VNode<'bump>],
|
||||||
shared_prefix_count: usize,
|
_shared_prefix_count: usize,
|
||||||
shared_suffix_count: usize,
|
_shared_suffix_count: usize,
|
||||||
old_shared_suffix_start: usize,
|
_old_shared_suffix_start: usize,
|
||||||
) {
|
) {
|
||||||
todo!()
|
todo!()
|
||||||
// // Should have already diffed the shared-key prefixes and suffixes.
|
// // Should have already diffed the shared-key prefixes and suffixes.
|
||||||
@@ -1150,9 +1140,9 @@ impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> {
|
|||||||
// When this function exits, the change list stack remains the same.
|
// When this function exits, the change list stack remains the same.
|
||||||
fn diff_keyed_suffix(
|
fn diff_keyed_suffix(
|
||||||
&self,
|
&self,
|
||||||
old: &[VNode<'bump>],
|
_old: &[VNode<'bump>],
|
||||||
new: &[VNode<'bump>],
|
_new: &[VNode<'bump>],
|
||||||
new_shared_suffix_start: usize,
|
_new_shared_suffix_start: usize,
|
||||||
) {
|
) {
|
||||||
todo!()
|
todo!()
|
||||||
// debug_assert_eq!(old.len(), new.len());
|
// debug_assert_eq!(old.len(), new.len());
|
||||||
@@ -1190,7 +1180,7 @@ impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> {
|
|||||||
// [... parent child]
|
// [... parent child]
|
||||||
|
|
||||||
// todo!()
|
// todo!()
|
||||||
for (i, (new_child, old_child)) in new.iter().zip(old.iter()).enumerate() {
|
for (_i, (new_child, old_child)) in new.iter().zip(old.iter()).enumerate() {
|
||||||
// [... parent prev_child]
|
// [... parent prev_child]
|
||||||
// self.dom.go_to_sibling(i);
|
// self.dom.go_to_sibling(i);
|
||||||
// [... parent this_child]
|
// [... parent this_child]
|
||||||
@@ -1259,7 +1249,7 @@ impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> {
|
|||||||
pub fn remove_self_and_next_siblings(&self, old: &[VNode<'bump>]) {
|
pub fn remove_self_and_next_siblings(&self, old: &[VNode<'bump>]) {
|
||||||
// debug_assert!(self.dom.traversal_is_committed());
|
// debug_assert!(self.dom.traversal_is_committed());
|
||||||
for child in old {
|
for child in old {
|
||||||
if let VNode::Component(vcomp) = child {
|
if let VNodeKind::Component(_vcomp) = child.kind {
|
||||||
// dom
|
// dom
|
||||||
// .create_text_node("placeholder for vcomponent");
|
// .create_text_node("placeholder for vcomponent");
|
||||||
|
|
||||||
@@ -1328,23 +1318,23 @@ impl<'a> Iterator for RealChildIterator<'a> {
|
|||||||
|
|
||||||
while returned_node.is_none() {
|
while returned_node.is_none() {
|
||||||
if let Some((count, node)) = self.stack.last_mut() {
|
if let Some((count, node)) = self.stack.last_mut() {
|
||||||
match node {
|
match &node.kind {
|
||||||
// We can only exit our looping when we get "real" nodes
|
// We can only exit our looping when we get "real" nodes
|
||||||
// This includes fragments and components when they're empty (have a single root)
|
// This includes fragments and components when they're empty (have a single root)
|
||||||
VNode::Element(_) | VNode::Text(_) => {
|
VNodeKind::Element(_) | VNodeKind::Text(_) => {
|
||||||
// We've recursed INTO an element/text
|
// We've recursed INTO an element/text
|
||||||
// We need to recurse *out* of it and move forward to the next
|
// We need to recurse *out* of it and move forward to the next
|
||||||
should_pop = true;
|
should_pop = true;
|
||||||
returned_node = node.get_mounted_id(&self.scopes);
|
returned_node = Some(node.dom_id.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we get a fragment we push the next child
|
// If we get a fragment we push the next child
|
||||||
VNode::Fragment(frag) => {
|
VNodeKind::Fragment(frag) => {
|
||||||
let subcount = *count as usize;
|
let subcount = *count as usize;
|
||||||
|
|
||||||
if frag.children.len() == 0 {
|
if frag.children.len() == 0 {
|
||||||
should_pop = true;
|
should_pop = true;
|
||||||
returned_node = node.get_mounted_id(&self.scopes);
|
returned_node = Some(node.dom_id.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
if subcount >= frag.children.len() {
|
if subcount >= frag.children.len() {
|
||||||
@@ -1355,11 +1345,11 @@ impl<'a> Iterator for RealChildIterator<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Immediately abort suspended nodes - can't do anything with them yet
|
// Immediately abort suspended nodes - can't do anything with them yet
|
||||||
// VNode::Suspended => should_pop = true,
|
// VNodeKind::Suspended => should_pop = true,
|
||||||
VNode::Suspended { real } => todo!(),
|
VNodeKind::Suspended => todo!(),
|
||||||
|
|
||||||
// For components, we load their root and push them onto the stack
|
// For components, we load their root and push them onto the stack
|
||||||
VNode::Component(sc) => {
|
VNodeKind::Component(sc) => {
|
||||||
let scope = self.scopes.try_get(sc.ass_scope.get().unwrap()).unwrap();
|
let scope = self.scopes.try_get(sc.ass_scope.get().unwrap()).unwrap();
|
||||||
|
|
||||||
// Simply swap the current node on the stack with the root of the component
|
// Simply swap the current node on the stack with the root of the component
|
||||||
@@ -1388,3 +1378,13 @@ impl<'a> Iterator for RealChildIterator<'a> {
|
|||||||
returned_node
|
returned_node
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn compare_strs(a: &str, b: &str) -> bool {
|
||||||
|
// Check by pointer, optimizing for static strs
|
||||||
|
if !std::ptr::eq(a, b) {
|
||||||
|
// If the pointers are different then check by value
|
||||||
|
a == b
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
//! 3rd party renderers are responsible for converting their native events into these virtual event types. Events might
|
//! 3rd party renderers are responsible for converting their native events into these virtual event types. Events might
|
||||||
//! be heavy or need to interact through FFI, so the events themselves are designed to be lazy.
|
//! be heavy or need to interact through FFI, so the events themselves are designed to be lazy.
|
||||||
|
|
||||||
use std::{cell::Cell, ops::Deref, rc::Rc};
|
|
||||||
|
|
||||||
use crate::innerlude::{RealDomNode, ScopeIdx};
|
use crate::innerlude::{RealDomNode, ScopeIdx};
|
||||||
|
|
||||||
@@ -125,7 +125,6 @@ pub mod on {
|
|||||||
use std::{fmt::Debug, ops::Deref, rc::Rc};
|
use std::{fmt::Debug, ops::Deref, rc::Rc};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
innerlude::ElementBuilder,
|
|
||||||
innerlude::NodeFactory,
|
innerlude::NodeFactory,
|
||||||
innerlude::{Attribute, Listener, RealDomNode, VNode},
|
innerlude::{Attribute, Listener, RealDomNode, VNode},
|
||||||
};
|
};
|
||||||
|
|||||||
+32
-39
@@ -1,3 +1,4 @@
|
|||||||
|
#![allow(non_snake_case, dead_code, unused_must_use, unreachable_code)]
|
||||||
//! Dioxus Core
|
//! Dioxus Core
|
||||||
//! ----------
|
//! ----------
|
||||||
//!
|
//!
|
||||||
@@ -8,25 +9,20 @@
|
|||||||
//!
|
//!
|
||||||
//!
|
//!
|
||||||
|
|
||||||
#[cfg(feature = "serialize")]
|
pub use crate::innerlude::{
|
||||||
pub mod serialize;
|
format_args_f, html, rsx, DioxusElement, DomEdit, EventTrigger, LazyNodes, NodeFactory,
|
||||||
|
Properties, RealDom, RealDomNode, ScopeIdx, VNode, VNodeKind, VirtualDom, VirtualEvent, FC,
|
||||||
|
};
|
||||||
|
|
||||||
pub mod arena;
|
pub mod prelude {
|
||||||
pub mod bumpframe;
|
pub use crate::component::{fc_to_builder, Fragment, Properties};
|
||||||
pub mod component;
|
pub use crate::context::Context;
|
||||||
pub mod context;
|
pub use crate::innerlude::DioxusElement;
|
||||||
pub mod diff;
|
pub use crate::innerlude::{LazyNodes, NodeFactory, FC};
|
||||||
pub mod error;
|
pub use crate::nodes::VNode;
|
||||||
pub mod events;
|
pub use crate::VirtualDom;
|
||||||
pub mod hooklist;
|
pub use dioxus_core_macro::{format_args_f, html, rsx, Props};
|
||||||
pub mod nodebuilder;
|
}
|
||||||
pub mod nodes;
|
|
||||||
pub mod scope;
|
|
||||||
pub mod signals;
|
|
||||||
pub mod styles;
|
|
||||||
pub mod tasks;
|
|
||||||
pub mod util;
|
|
||||||
pub mod virtual_dom;
|
|
||||||
|
|
||||||
// types used internally that are important
|
// types used internally that are important
|
||||||
pub(crate) mod innerlude {
|
pub(crate) mod innerlude {
|
||||||
@@ -36,7 +32,6 @@ pub(crate) mod innerlude {
|
|||||||
pub use crate::diff::*;
|
pub use crate::diff::*;
|
||||||
pub use crate::error::*;
|
pub use crate::error::*;
|
||||||
pub use crate::events::*;
|
pub use crate::events::*;
|
||||||
pub use crate::nodebuilder::*;
|
|
||||||
pub use crate::nodes::*;
|
pub use crate::nodes::*;
|
||||||
pub use crate::scope::*;
|
pub use crate::scope::*;
|
||||||
pub use crate::serialize::*;
|
pub use crate::serialize::*;
|
||||||
@@ -46,28 +41,26 @@ pub(crate) mod innerlude {
|
|||||||
|
|
||||||
pub type FC<P> = fn(Context<P>) -> VNode;
|
pub type FC<P> = fn(Context<P>) -> VNode;
|
||||||
|
|
||||||
// Re-export the FC macro
|
pub use dioxus_core_macro::{format_args_f, html, rsx};
|
||||||
pub use crate::nodebuilder as builder;
|
|
||||||
pub use dioxus_core_macro::{html, rsx};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub use crate::{
|
|
||||||
innerlude::{
|
|
||||||
DioxusElement, DomEdit, LazyNodes, NodeFactory, RealDom, RealDomNode, ScopeIdx, FC,
|
|
||||||
},
|
|
||||||
virtual_dom::VirtualDom,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub mod prelude {
|
|
||||||
pub use crate::component::{fc_to_builder, Fragment, Properties};
|
|
||||||
pub use crate::context::Context;
|
|
||||||
pub use crate::innerlude::{LazyNodes, NodeFactory, FC};
|
|
||||||
pub use crate::nodebuilder::DioxusElement;
|
|
||||||
pub use crate::nodes::VNode;
|
|
||||||
pub use crate::VirtualDom;
|
|
||||||
pub use dioxus_core_macro::{format_args_f, html, rsx, Props};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod exports {
|
pub mod exports {
|
||||||
// export important things here
|
// export important things here
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub mod arena;
|
||||||
|
pub mod bumpframe;
|
||||||
|
pub mod component;
|
||||||
|
pub mod context;
|
||||||
|
pub mod diff;
|
||||||
|
pub mod error;
|
||||||
|
pub mod events;
|
||||||
|
pub mod hooklist;
|
||||||
|
pub mod nodes;
|
||||||
|
pub mod scope;
|
||||||
|
#[cfg(feature = "serialize")]
|
||||||
|
pub mod serialize;
|
||||||
|
pub mod signals;
|
||||||
|
pub mod tasks;
|
||||||
|
pub mod util;
|
||||||
|
pub mod virtual_dom;
|
||||||
+357
-398
@@ -4,225 +4,70 @@
|
|||||||
//! These VNodes should be *very* cheap and *very* fast to construct - building a full tree should be insanely quick.
|
//! These VNodes should be *very* cheap and *very* fast to construct - building a full tree should be insanely quick.
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
arena::SharedArena,
|
|
||||||
events::VirtualEvent,
|
events::VirtualEvent,
|
||||||
innerlude::{Context, Properties, RealDom, RealDomNode, Scope, ScopeIdx, FC},
|
innerlude::{Context, Properties, RealDomNode, Scope, ScopeIdx, FC},
|
||||||
nodebuilder::NodeFactory,
|
|
||||||
};
|
};
|
||||||
use appendlist::AppendList;
|
|
||||||
use bumpalo::Bump;
|
|
||||||
use std::{
|
use std::{
|
||||||
cell::{Cell, RefCell},
|
cell::{Cell, RefCell},
|
||||||
fmt::{Arguments, Debug, Formatter},
|
fmt::{Arguments, Debug, Formatter},
|
||||||
|
marker::PhantomData,
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub struct VNode<'src> {
|
||||||
|
pub kind: VNodeKind<'src>,
|
||||||
|
pub dom_id: Cell<RealDomNode>,
|
||||||
|
pub key: Option<&'src str>,
|
||||||
|
}
|
||||||
|
|
||||||
/// Tools for the base unit of the virtual dom - the VNode
|
/// Tools for the base unit of the virtual dom - the VNode
|
||||||
/// VNodes are intended to be quickly-allocated, lightweight enum values.
|
/// VNodes are intended to be quickly-allocated, lightweight enum values.
|
||||||
///
|
///
|
||||||
/// Components will be generating a lot of these very quickly, so we want to
|
/// Components will be generating a lot of these very quickly, so we want to
|
||||||
/// limit the amount of heap allocations / overly large enum sizes.
|
/// limit the amount of heap allocations / overly large enum sizes.
|
||||||
pub enum VNode<'src> {
|
pub enum VNodeKind<'src> {
|
||||||
/// An element node (node type `ELEMENT_NODE`).
|
|
||||||
Element(&'src VElement<'src>),
|
|
||||||
|
|
||||||
/// A text node (node type `TEXT_NODE`).
|
|
||||||
Text(VText<'src>),
|
Text(VText<'src>),
|
||||||
|
Element(&'src VElement<'src>),
|
||||||
/// A fragment is a list of elements that might have a dynamic order.
|
Fragment(VFragment<'src>),
|
||||||
/// Normally, children will have a fixed order. However, Fragments allow a dynamic order and must be diffed differently.
|
|
||||||
///
|
|
||||||
/// Fragments don't have a single mount into the dom, so their position is characterized by the head and tail nodes.
|
|
||||||
///
|
|
||||||
/// Fragments may have children and keys
|
|
||||||
Fragment(&'src VFragment<'src>),
|
|
||||||
|
|
||||||
/// A "suspended component"
|
|
||||||
/// This is a masqeurade over an underlying future that needs to complete
|
|
||||||
/// When the future is completed, the VNode will then trigger a render and the `real` field gets populated
|
|
||||||
Suspended { real: Cell<RealDomNode> },
|
|
||||||
|
|
||||||
/// A User-defined componen node (node type COMPONENT_NODE)
|
|
||||||
Component(&'src VComponent<'src>),
|
Component(&'src VComponent<'src>),
|
||||||
|
Suspended,
|
||||||
}
|
}
|
||||||
|
|
||||||
// it's okay to clone because vnodes are just references to places into the bump
|
|
||||||
impl<'a> Clone for VNode<'a> {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
match self {
|
|
||||||
VNode::Element(element) => VNode::Element(element),
|
|
||||||
VNode::Text(old) => VNode::Text(old.clone()),
|
|
||||||
VNode::Fragment(fragment) => VNode::Fragment(fragment),
|
|
||||||
VNode::Component(component) => VNode::Component(component),
|
|
||||||
VNode::Suspended { real } => VNode::Suspended { real: real.clone() },
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'old, 'new> VNode<'old> {
|
|
||||||
// performs a somewhat costly clone of this vnode into another bump
|
|
||||||
// this is used when you want to drag nodes from an old frame into a new frame
|
|
||||||
// There is no way to safely drag listeners over (no way to clone a closure)
|
|
||||||
//
|
|
||||||
// This method will only be called if a component was once a real node and then becomes suspended
|
|
||||||
fn deep_clone_to_new_bump(&self, new: &'new Bump) -> VNode<'new> {
|
|
||||||
match self {
|
|
||||||
VNode::Element(el) => {
|
|
||||||
let new_el: VElement<'new> = VElement {
|
|
||||||
key: NodeKey::NONE,
|
|
||||||
// key: el.key.clone(),
|
|
||||||
tag_name: el.tag_name,
|
|
||||||
// wipe listeners on deep clone, there's no way to know what other bump material they might be referencing (nodes, etc)
|
|
||||||
listeners: &[],
|
|
||||||
attributes: {
|
|
||||||
let attr_vec = bumpalo::collections::Vec::new_in(new);
|
|
||||||
attr_vec.into_bump_slice()
|
|
||||||
},
|
|
||||||
children: {
|
|
||||||
let attr_vec = bumpalo::collections::Vec::new_in(new);
|
|
||||||
attr_vec.into_bump_slice()
|
|
||||||
},
|
|
||||||
namespace: el.namespace.clone(),
|
|
||||||
dom_id: el.dom_id.clone(),
|
|
||||||
is_static: el.is_static.clone(),
|
|
||||||
};
|
|
||||||
|
|
||||||
VNode::Element(new.alloc_with(move || new_el))
|
|
||||||
}
|
|
||||||
VNode::Text(_) => todo!(),
|
|
||||||
VNode::Fragment(_) => todo!(),
|
|
||||||
VNode::Suspended { real } => todo!(),
|
|
||||||
VNode::Component(_) => todo!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> VNode<'a> {
|
|
||||||
/// Low-level constructor for making a new `Node` of type element with given
|
|
||||||
/// parts.
|
|
||||||
///
|
|
||||||
/// This is primarily intended for JSX and templating proc-macros to compile
|
|
||||||
/// down into. If you are building nodes by-hand, prefer using the
|
|
||||||
/// `dodrio::builder::*` APIs.
|
|
||||||
#[inline]
|
|
||||||
pub fn element(
|
|
||||||
bump: &'a Bump,
|
|
||||||
key: NodeKey<'a>,
|
|
||||||
tag_name: &'static str,
|
|
||||||
listeners: &'a [Listener<'a>],
|
|
||||||
attributes: &'a [Attribute<'a>],
|
|
||||||
children: &'a [VNode<'a>],
|
|
||||||
namespace: Option<&'static str>,
|
|
||||||
) -> VNode<'a> {
|
|
||||||
let element = bump.alloc_with(|| VElement {
|
|
||||||
key,
|
|
||||||
tag_name,
|
|
||||||
listeners,
|
|
||||||
attributes,
|
|
||||||
children,
|
|
||||||
namespace,
|
|
||||||
dom_id: Cell::new(RealDomNode::empty()),
|
|
||||||
is_static: Cell::new(false),
|
|
||||||
});
|
|
||||||
VNode::Element(element)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn static_text(text: &'static str) -> VNode {
|
|
||||||
VNode::Text(VText {
|
|
||||||
text,
|
|
||||||
is_static: true,
|
|
||||||
dom_id: Cell::new(RealDomNode::empty()),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/// Construct a new text node with the given text.
|
|
||||||
pub fn text(bump: &'a Bump, args: Arguments) -> VNode<'a> {
|
|
||||||
match args.as_str() {
|
|
||||||
Some(text) => VNode::static_text(text),
|
|
||||||
None => {
|
|
||||||
use bumpalo::core_alloc::fmt::Write;
|
|
||||||
let mut s = bumpalo::collections::String::new_in(bump);
|
|
||||||
s.write_fmt(args).unwrap();
|
|
||||||
VNode::Text(VText {
|
|
||||||
text: s.into_bump_str(),
|
|
||||||
is_static: false,
|
|
||||||
dom_id: Cell::new(RealDomNode::empty()),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn key(&self) -> NodeKey {
|
|
||||||
match &self {
|
|
||||||
VNode::Text { .. } => NodeKey::NONE,
|
|
||||||
VNode::Element(e) => e.key,
|
|
||||||
VNode::Fragment(frag) => frag.key,
|
|
||||||
VNode::Component(c) => c.key,
|
|
||||||
|
|
||||||
// todo suspend should be allowed to have keys
|
|
||||||
VNode::Suspended { .. } => NodeKey::NONE,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_child(&self, id: u32) -> Option<&'a VNode<'a>> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_real(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
VNode::Element(_) => true,
|
|
||||||
VNode::Text(_) => true,
|
|
||||||
VNode::Fragment(_) => false,
|
|
||||||
VNode::Suspended { .. } => false,
|
|
||||||
VNode::Component(_) => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_mounted_id(&self, components: &SharedArena) -> Option<RealDomNode> {
|
|
||||||
match self {
|
|
||||||
VNode::Element(el) => Some(el.dom_id.get()),
|
|
||||||
VNode::Text(te) => Some(te.dom_id.get()),
|
|
||||||
VNode::Fragment(frag) => frag.void_root.get(),
|
|
||||||
VNode::Suspended { .. } => todo!(),
|
|
||||||
VNode::Component(el) => todo!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Debug for VNode<'_> {
|
|
||||||
fn fmt(&self, s: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
|
||||||
match self {
|
|
||||||
VNode::Element(el) => write!(s, "element, {}", el.tag_name),
|
|
||||||
VNode::Text(t) => write!(s, "text, {}", t.text),
|
|
||||||
VNode::Fragment(_) => write!(s, "fragment"),
|
|
||||||
VNode::Suspended { .. } => write!(s, "suspended"),
|
|
||||||
VNode::Component(_) => write!(s, "component"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct VText<'src> {
|
pub struct VText<'src> {
|
||||||
pub text: &'src str,
|
pub text: &'src str,
|
||||||
pub is_static: bool,
|
pub is_static: bool,
|
||||||
pub dom_id: Cell<RealDomNode>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========================================================
|
pub struct VFragment<'src> {
|
||||||
// VElement (div, h1, etc), attrs, keys, listener handle
|
pub children: &'src [VNode<'src>],
|
||||||
// ========================================================
|
pub is_static: bool,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
pub trait DioxusElement {
|
||||||
|
const TAG_NAME: &'static str;
|
||||||
|
const NAME_SPACE: Option<&'static str>;
|
||||||
|
#[inline]
|
||||||
|
fn tag_name(&self) -> &'static str {
|
||||||
|
Self::TAG_NAME
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
fn namespace(&self) -> Option<&'static str> {
|
||||||
|
Self::NAME_SPACE
|
||||||
|
}
|
||||||
|
}
|
||||||
pub struct VElement<'a> {
|
pub struct VElement<'a> {
|
||||||
/// Elements have a tag name, zero or more attributes, and zero or more
|
// tag is always static
|
||||||
pub key: NodeKey<'a>,
|
|
||||||
pub tag_name: &'static str,
|
pub tag_name: &'static str,
|
||||||
pub listeners: &'a [Listener<'a>],
|
|
||||||
pub attributes: &'a [Attribute<'a>],
|
|
||||||
pub children: &'a [VNode<'a>],
|
|
||||||
pub namespace: Option<&'static str>,
|
pub namespace: Option<&'static str>,
|
||||||
pub dom_id: Cell<RealDomNode>,
|
|
||||||
pub is_static: Cell<bool>,
|
pub static_listeners: bool,
|
||||||
|
pub listeners: &'a [Listener<'a>],
|
||||||
|
|
||||||
|
pub static_attrs: bool,
|
||||||
|
pub attributes: &'a [Attribute<'a>],
|
||||||
|
|
||||||
|
pub static_children: bool,
|
||||||
|
pub children: &'a [VNode<'a>],
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An attribute on a DOM node, such as `id="my-thing"` or
|
/// An attribute on a DOM node, such as `id="my-thing"` or
|
||||||
@@ -232,175 +77,185 @@ pub struct Attribute<'a> {
|
|||||||
pub name: &'static str,
|
pub name: &'static str,
|
||||||
pub value: &'a str,
|
pub value: &'a str,
|
||||||
pub is_static: bool,
|
pub is_static: bool,
|
||||||
|
pub is_volatile: bool,
|
||||||
/// If an attribute is "namespaced", then it belongs to a group
|
// Doesn't exist in the html spec, mostly used to denote "style" tags - could be for any type of group
|
||||||
/// The most common namespace is the "style" namespace
|
|
||||||
// pub is_dynamic: bool,
|
|
||||||
pub namespace: Option<&'static str>,
|
pub namespace: Option<&'static str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Attribute<'a> {
|
|
||||||
/// Get this attribute's name, such as `"id"` in `<div id="my-thing" />`.
|
|
||||||
#[inline]
|
|
||||||
pub fn name(&self) -> &'a str {
|
|
||||||
self.name
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The attribute value, such as `"my-thing"` in `<div id="my-thing" />`.
|
|
||||||
#[inline]
|
|
||||||
pub fn value(&self) -> &'a str {
|
|
||||||
self.value
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Certain attributes are considered "volatile" and can change via user
|
|
||||||
/// input that we can't see when diffing against the old virtual DOM. For
|
|
||||||
/// these attributes, we want to always re-set the attribute on the physical
|
|
||||||
/// DOM node, even if the old and new virtual DOM nodes have the same value.
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn is_volatile(&self) -> bool {
|
|
||||||
match self.name {
|
|
||||||
"value" | "checked" | "selected" => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ListenerHandle {
|
|
||||||
pub event: &'static str,
|
|
||||||
pub scope: ScopeIdx,
|
|
||||||
pub id: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An event listener.
|
/// An event listener.
|
||||||
|
/// IE onclick, onkeydown, etc
|
||||||
pub struct Listener<'bump> {
|
pub struct Listener<'bump> {
|
||||||
/// The type of event to listen for.
|
/// The type of event to listen for.
|
||||||
pub(crate) event: &'static str,
|
pub(crate) event: &'static str,
|
||||||
|
|
||||||
/// Which scope?
|
|
||||||
/// This might not actually be relevant
|
|
||||||
pub scope: ScopeIdx,
|
pub scope: ScopeIdx,
|
||||||
|
|
||||||
pub mounted_node: &'bump Cell<RealDomNode>,
|
pub mounted_node: &'bump Cell<RealDomNode>,
|
||||||
|
|
||||||
/// The callback to invoke when the event happens.
|
|
||||||
pub(crate) callback: &'bump dyn FnMut(VirtualEvent),
|
pub(crate) callback: &'bump dyn FnMut(VirtualEvent),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The key for keyed children.
|
|
||||||
///
|
|
||||||
/// Keys must be unique among siblings.
|
|
||||||
///
|
|
||||||
/// If any sibling is keyed, then they all must be keyed.
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
|
||||||
pub struct NodeKey<'a>(pub(crate) Option<&'a str>);
|
|
||||||
|
|
||||||
impl<'a> Default for NodeKey<'a> {
|
|
||||||
fn default() -> NodeKey<'a> {
|
|
||||||
NodeKey::NONE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'a> NodeKey<'a> {
|
|
||||||
/// The default, lack of a key.
|
|
||||||
pub const NONE: NodeKey<'a> = NodeKey(None);
|
|
||||||
|
|
||||||
/// Is this key `NodeKey::NONE`?
|
|
||||||
#[inline]
|
|
||||||
pub fn is_none(&self) -> bool {
|
|
||||||
*self == Self::NONE
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Is this key not `NodeKey::NONE`?
|
|
||||||
#[inline]
|
|
||||||
pub fn is_some(&self) -> bool {
|
|
||||||
!self.is_none()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a new `NodeKey`.
|
|
||||||
///
|
|
||||||
/// `key` must not be `u32::MAX`.
|
|
||||||
#[inline]
|
|
||||||
pub fn new(key: &'a str) -> Self {
|
|
||||||
NodeKey(Some(key))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn new_opt(key: Option<&'a str>) -> Self {
|
|
||||||
NodeKey(key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==============================
|
|
||||||
// Custom components
|
|
||||||
// ==============================
|
|
||||||
|
|
||||||
/// Virtual Components for custom user-defined components
|
/// Virtual Components for custom user-defined components
|
||||||
/// Only supports the functional syntax
|
/// Only supports the functional syntax
|
||||||
pub type StableScopeAddres = Option<u32>;
|
|
||||||
pub type VCompAssociatedScope = Option<ScopeIdx>;
|
|
||||||
|
|
||||||
pub struct VComponent<'src> {
|
pub struct VComponent<'src> {
|
||||||
pub key: NodeKey<'src>,
|
pub ass_scope: Cell<Option<ScopeIdx>>,
|
||||||
|
pub(crate) caller: Rc<dyn Fn(&Scope) -> VNode>,
|
||||||
// pub void_root: Cell<Option<RealDomNode>>,
|
pub(crate) children: &'src [VNode<'src>],
|
||||||
pub ass_scope: Cell<VCompAssociatedScope>,
|
pub(crate) comparator: Option<&'src dyn Fn(&VComponent) -> bool>,
|
||||||
|
|
||||||
// todo: swap the RC out with
|
|
||||||
pub caller: Rc<dyn Fn(&Scope) -> VNode>,
|
|
||||||
|
|
||||||
pub children: &'src [VNode<'src>],
|
|
||||||
|
|
||||||
pub comparator: Option<&'src dyn Fn(&VComponent) -> bool>,
|
|
||||||
|
|
||||||
pub is_static: bool,
|
pub is_static: bool,
|
||||||
|
|
||||||
// a pointer into the bump arena (given by the 'src lifetime)
|
// a pointer into the bump arena (given by the 'src lifetime)
|
||||||
// raw_props: Box<dyn Any>,
|
pub(crate) raw_props: *const (),
|
||||||
raw_props: *const (),
|
|
||||||
|
|
||||||
// a pointer to the raw fn typ
|
// a pointer to the raw fn typ
|
||||||
pub user_fc: *const (),
|
pub(crate) user_fc: *const (),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> VComponent<'a> {
|
/// This struct provides an ergonomic API to quickly build VNodes.
|
||||||
/// When the rsx! macro is called, it will check if the CanMemo flag is set to true (from the Props impl)
|
///
|
||||||
/// If it is set to true, then this method will be called which implements automatic memoization.
|
/// NodeFactory is used to build VNodes in the component's memory space.
|
||||||
///
|
/// This struct adds metadata to the final VNode about listeners, attributes, and children
|
||||||
/// If the CanMemo is `false`, then the macro will call the backup method which always defaults to "false"
|
#[derive(Copy, Clone)]
|
||||||
pub fn new<P: Properties + 'a>(
|
pub struct NodeFactory<'a> {
|
||||||
cx: &NodeFactory<'a>,
|
pub scope_ref: &'a Scope,
|
||||||
|
pub listener_id: &'a Cell<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> NodeFactory<'a> {
|
||||||
|
#[inline]
|
||||||
|
pub fn bump(&self) -> &'a bumpalo::Bump {
|
||||||
|
&self.scope_ref.cur_frame().bump
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn const_text(&self, text: &'static str) -> VNodeKind<'static> {
|
||||||
|
VNodeKind::Text(VText {
|
||||||
|
is_static: true,
|
||||||
|
text,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn const_fragment(&self, children: &'static [VNode<'static>]) -> VNodeKind<'static> {
|
||||||
|
VNodeKind::Fragment(VFragment {
|
||||||
|
children,
|
||||||
|
is_static: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn static_text(text: &'static str) -> VNode {
|
||||||
|
VNode {
|
||||||
|
dom_id: RealDomNode::empty_cell(),
|
||||||
|
key: None,
|
||||||
|
kind: VNodeKind::Text(VText {
|
||||||
|
text,
|
||||||
|
is_static: true,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn raw_text(&self, args: Arguments) -> (&'a str, bool) {
|
||||||
|
match args.as_str() {
|
||||||
|
Some(static_str) => (static_str, true),
|
||||||
|
None => {
|
||||||
|
use bumpalo::core_alloc::fmt::Write;
|
||||||
|
let mut s = bumpalo::collections::String::new_in(self.bump());
|
||||||
|
s.write_fmt(args).unwrap();
|
||||||
|
(s.into_bump_str(), false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create some text that's allocated along with the other vnodes
|
||||||
|
pub fn text(&self, args: Arguments) -> VNode<'a> {
|
||||||
|
let (text, is_static) = self.raw_text(args);
|
||||||
|
VNode {
|
||||||
|
dom_id: RealDomNode::empty_cell(),
|
||||||
|
key: None,
|
||||||
|
kind: VNodeKind::Text(VText { text, is_static }),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn raw_element(
|
||||||
|
&self,
|
||||||
|
_tag: &'static str,
|
||||||
|
_listeners: &[Listener],
|
||||||
|
_attributes: &[Attribute],
|
||||||
|
_children: &'a [VNode<'a>],
|
||||||
|
) -> VNode<'a> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn element(
|
||||||
|
&self,
|
||||||
|
el: impl DioxusElement,
|
||||||
|
listeners: &'a [Listener<'a>],
|
||||||
|
attributes: &'a [Attribute<'a>],
|
||||||
|
children: &'a [VNode<'a>],
|
||||||
|
key: Option<&'a str>,
|
||||||
|
) -> VNode<'a> {
|
||||||
|
let mut queue = self.scope_ref.listeners.borrow_mut();
|
||||||
|
for listener in listeners {
|
||||||
|
let mounted = listener.mounted_node as *const _ as *mut _;
|
||||||
|
let callback = listener.callback as *const _ as *mut _;
|
||||||
|
queue.push((mounted, callback))
|
||||||
|
}
|
||||||
|
|
||||||
|
VNode {
|
||||||
|
dom_id: RealDomNode::empty_cell(),
|
||||||
|
key,
|
||||||
|
kind: VNodeKind::Element(self.bump().alloc(VElement {
|
||||||
|
tag_name: el.tag_name(),
|
||||||
|
namespace: el.namespace(),
|
||||||
|
static_listeners: false,
|
||||||
|
listeners,
|
||||||
|
static_attrs: false,
|
||||||
|
attributes,
|
||||||
|
static_children: false,
|
||||||
|
children,
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn suspended() -> VNode<'static> {
|
||||||
|
VNode {
|
||||||
|
dom_id: RealDomNode::empty_cell(),
|
||||||
|
key: None,
|
||||||
|
kind: VNodeKind::Suspended,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn attr(
|
||||||
|
&self,
|
||||||
|
name: &'static str,
|
||||||
|
val: Arguments,
|
||||||
|
namespace: Option<&'static str>,
|
||||||
|
is_volatile: bool,
|
||||||
|
) -> Attribute<'a> {
|
||||||
|
let (value, is_static) = self.raw_text(val);
|
||||||
|
Attribute {
|
||||||
|
name,
|
||||||
|
value,
|
||||||
|
is_static,
|
||||||
|
namespace,
|
||||||
|
is_volatile,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn virtual_child<P>(
|
||||||
|
&self,
|
||||||
component: FC<P>,
|
component: FC<P>,
|
||||||
props: P,
|
props: P,
|
||||||
key: Option<&'a str>,
|
key: Option<&'a str>, // key: NodeKey<'a>,
|
||||||
children: &'a [VNode<'a>],
|
children: &'a [VNode<'a>],
|
||||||
) -> Self {
|
) -> VNode<'a>
|
||||||
let bump = cx.bump();
|
where
|
||||||
let user_fc = component as *const ();
|
P: Properties + 'a,
|
||||||
|
{
|
||||||
let props = bump.alloc(props);
|
// We don't want the fat part of the fat pointer
|
||||||
|
// This function does static dispatch so we don't need any VTable stuff
|
||||||
|
let props = self.bump().alloc(props);
|
||||||
let raw_props = props as *const P as *const ();
|
let raw_props = props as *const P as *const ();
|
||||||
|
|
||||||
let comparator: Option<&dyn Fn(&VComponent) -> bool> = Some(bump.alloc_with(|| {
|
let user_fc = component as *const ();
|
||||||
|
|
||||||
|
let comparator: Option<&dyn Fn(&VComponent) -> bool> = Some(self.bump().alloc_with(|| {
|
||||||
move |other: &VComponent| {
|
move |other: &VComponent| {
|
||||||
// Safety:
|
|
||||||
// ------
|
|
||||||
//
|
|
||||||
// Invariants:
|
|
||||||
// - Component function pointers are the same
|
|
||||||
// - Generic properties on the same function pointer are the same
|
|
||||||
// - Lifetime of P borrows from its parent
|
|
||||||
// - The parent scope still exists when method is called
|
|
||||||
// - Casting from T to *const () is portable
|
|
||||||
// - Casting raw props to P can only happen when P is static
|
|
||||||
//
|
|
||||||
// Explanation:
|
|
||||||
// We are guaranteed that the props will be of the same type because
|
|
||||||
// there is no way to create a VComponent other than this `new` method.
|
|
||||||
//
|
|
||||||
// Therefore, if the render functions are identical (by address), then so will be
|
|
||||||
// props type paramter (because it is the same render function). Therefore, we can be
|
|
||||||
// sure that it is safe to interperet the previous props raw pointer as the same props
|
|
||||||
// type. From there, we can call the props' "memoize" method to see if we can
|
|
||||||
// avoid re-rendering the component.
|
|
||||||
if user_fc == other.user_fc {
|
if user_fc == other.user_fc {
|
||||||
let real_other = unsafe { &*(other.raw_props as *const _ as *const P) };
|
let real_other = unsafe { &*(other.raw_props as *const _ as *const P) };
|
||||||
let props_memoized = unsafe { props.memoize(&real_other) };
|
let props_memoized = unsafe { props.memoize(&real_other) };
|
||||||
@@ -414,81 +269,185 @@ impl<'a> VComponent<'a> {
|
|||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let key = match key {
|
VNode {
|
||||||
Some(key) => NodeKey::new(key),
|
|
||||||
None => NodeKey(None),
|
|
||||||
};
|
|
||||||
|
|
||||||
let caller = create_component_caller(component, raw_props);
|
|
||||||
|
|
||||||
// If the component does not have children, has no props (we can't memoize props), and has no no key, then we don't
|
|
||||||
// need to bother diffing it in the future
|
|
||||||
//
|
|
||||||
// This is more of an optimization to prevent unnecessary descending through the tree during diffing, rather than
|
|
||||||
// actually speeding up the diff process itself
|
|
||||||
let is_static = children.len() == 0 && P::IS_STATIC && key.is_none();
|
|
||||||
|
|
||||||
Self {
|
|
||||||
user_fc,
|
|
||||||
comparator,
|
|
||||||
raw_props,
|
|
||||||
children,
|
|
||||||
ass_scope: Cell::new(None),
|
|
||||||
key,
|
key,
|
||||||
caller,
|
dom_id: Cell::new(RealDomNode::empty()),
|
||||||
is_static,
|
kind: VNodeKind::Component(self.bump().alloc_with(|| VComponent {
|
||||||
// void_root: Cell::new(None),
|
user_fc,
|
||||||
|
comparator,
|
||||||
|
raw_props,
|
||||||
|
children,
|
||||||
|
caller: NodeFactory::create_component_caller(component, raw_props),
|
||||||
|
is_static: children.len() == 0 && P::IS_STATIC && key.is_none(),
|
||||||
|
ass_scope: Cell::new(None),
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_component_caller<'g, P: 'g>(
|
||||||
|
component: FC<P>,
|
||||||
|
raw_props: *const (),
|
||||||
|
) -> Rc<dyn for<'r> Fn(&'r Scope) -> VNode<'r>> {
|
||||||
|
type Captured<'a> = Rc<dyn for<'r> Fn(&'r Scope) -> VNode<'r> + 'a>;
|
||||||
|
let caller: Captured = Rc::new(move |scp: &Scope| -> VNode {
|
||||||
|
// cast back into the right lifetime
|
||||||
|
let safe_props: &'_ P = unsafe { &*(raw_props as *const P) };
|
||||||
|
let cx: Context<P> = Context {
|
||||||
|
props: safe_props,
|
||||||
|
scope: scp,
|
||||||
|
};
|
||||||
|
|
||||||
|
let res = component(cx);
|
||||||
|
|
||||||
|
let g2 = unsafe { std::mem::transmute(res) };
|
||||||
|
|
||||||
|
g2
|
||||||
|
});
|
||||||
|
unsafe { std::mem::transmute::<_, Captured<'static>>(caller) }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fragment_from_iter(
|
||||||
|
self,
|
||||||
|
node_iter: impl IntoIterator<Item = impl IntoVNode<'a>>,
|
||||||
|
) -> VNode<'a> {
|
||||||
|
let mut nodes = bumpalo::collections::Vec::new_in(self.bump());
|
||||||
|
// TODO throw an error if there are nodes without keys
|
||||||
|
for node in node_iter.into_iter() {
|
||||||
|
nodes.push(node.into_vnode(self));
|
||||||
|
}
|
||||||
|
VNode {
|
||||||
|
dom_id: RealDomNode::empty_cell(),
|
||||||
|
key: None,
|
||||||
|
kind: VNodeKind::Fragment(VFragment {
|
||||||
|
children: nodes.into_bump_slice(),
|
||||||
|
is_static: false,
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Captured<'a> = Rc<dyn for<'r> Fn(&'r Scope) -> VNode<'r> + 'a>;
|
impl<'a> IntoIterator for VNode<'a> {
|
||||||
|
type Item = VNode<'a>;
|
||||||
pub fn create_component_caller<'a, P: 'a>(
|
type IntoIter = std::iter::Once<Self::Item>;
|
||||||
user_component: FC<P>,
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
raw_props: *const (),
|
std::iter::once(self)
|
||||||
) -> Rc<dyn for<'r> Fn(&'r Scope) -> VNode<'r>> {
|
}
|
||||||
let g: Captured = Rc::new(move |scp: &Scope| -> VNode {
|
}
|
||||||
// cast back into the right lifetime
|
impl<'a> IntoVNode<'a> for VNode<'a> {
|
||||||
let safe_props: &'_ P = unsafe { &*(raw_props as *const P) };
|
fn into_vnode(self, _: NodeFactory<'a>) -> VNode<'a> {
|
||||||
let tasks = RefCell::new(Vec::new());
|
self
|
||||||
let cx: Context<P> = Context {
|
}
|
||||||
props: safe_props,
|
|
||||||
scope: scp,
|
|
||||||
tasks: &tasks,
|
|
||||||
};
|
|
||||||
|
|
||||||
let g = user_component(cx);
|
|
||||||
|
|
||||||
for task in tasks.borrow_mut().drain(..) {
|
|
||||||
scp.submit_task(task);
|
|
||||||
}
|
|
||||||
|
|
||||||
let g2 = unsafe { std::mem::transmute(g) };
|
|
||||||
|
|
||||||
g2
|
|
||||||
});
|
|
||||||
let r: Captured<'static> = unsafe { std::mem::transmute(g) };
|
|
||||||
r
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct VFragment<'src> {
|
impl<'a> IntoVNode<'a> for &VNode<'a> {
|
||||||
pub key: NodeKey<'src>,
|
fn into_vnode(self, _: NodeFactory<'a>) -> VNode<'a> {
|
||||||
pub children: &'src [VNode<'src>],
|
self.clone()
|
||||||
pub void_root: Cell<Option<RealDomNode>>,
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> VFragment<'a> {
|
pub trait IntoVNode<'a> {
|
||||||
pub fn new(key: Option<&'a str>, children: &'a [VNode<'a>]) -> Self {
|
fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a>;
|
||||||
let key = match key {
|
}
|
||||||
Some(key) => NodeKey::new(key),
|
|
||||||
None => NodeKey(None),
|
|
||||||
};
|
|
||||||
|
|
||||||
|
// Wrap the the node-builder closure in a concrete type.
|
||||||
|
// ---
|
||||||
|
// This is a bit of a hack to implement the IntoVNode trait for closure types.
|
||||||
|
pub struct LazyNodes<'a, G>
|
||||||
|
where
|
||||||
|
G: FnOnce(NodeFactory<'a>) -> VNode<'a>,
|
||||||
|
{
|
||||||
|
inner: G,
|
||||||
|
_p: PhantomData<&'a ()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, G> LazyNodes<'a, G>
|
||||||
|
where
|
||||||
|
G: FnOnce(NodeFactory<'a>) -> VNode<'a>,
|
||||||
|
{
|
||||||
|
pub fn new(f: G) -> Self {
|
||||||
Self {
|
Self {
|
||||||
key,
|
inner: f,
|
||||||
children,
|
_p: PhantomData {},
|
||||||
void_root: Cell::new(None),
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cover the cases where nodes are used by macro.
|
||||||
|
// Likely used directly.
|
||||||
|
// ---
|
||||||
|
// let nodes = rsx!{ ... };
|
||||||
|
// rsx! { {nodes } }
|
||||||
|
impl<'a, G> IntoVNode<'a> for LazyNodes<'a, G>
|
||||||
|
where
|
||||||
|
G: FnOnce(NodeFactory<'a>) -> VNode<'a>,
|
||||||
|
{
|
||||||
|
fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> {
|
||||||
|
(self.inner)(cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Required because anything that enters brackets in the rsx! macro needs to implement IntoIterator
|
||||||
|
impl<'a, G> IntoIterator for LazyNodes<'a, G>
|
||||||
|
where
|
||||||
|
G: FnOnce(NodeFactory<'a>) -> VNode<'a>,
|
||||||
|
{
|
||||||
|
type Item = Self;
|
||||||
|
type IntoIter = std::iter::Once<Self::Item>;
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
std::iter::once(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoVNode<'_> for () {
|
||||||
|
fn into_vnode<'a>(self, cx: NodeFactory<'a>) -> VNode<'a> {
|
||||||
|
cx.fragment_from_iter(None as Option<VNode>)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoVNode<'_> for Option<()> {
|
||||||
|
fn into_vnode<'a>(self, cx: NodeFactory<'a>) -> VNode<'a> {
|
||||||
|
cx.fragment_from_iter(None as Option<VNode>)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for NodeFactory<'_> {
|
||||||
|
fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// it's okay to clone because vnodes are just references to places into the bump
|
||||||
|
impl<'a> Clone for VNode<'a> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
let kind = match &self.kind {
|
||||||
|
VNodeKind::Element(element) => VNodeKind::Element(element),
|
||||||
|
VNodeKind::Text(old) => VNodeKind::Text(VText {
|
||||||
|
text: old.text,
|
||||||
|
is_static: old.is_static,
|
||||||
|
}),
|
||||||
|
VNodeKind::Fragment(fragment) => VNodeKind::Fragment(VFragment {
|
||||||
|
children: fragment.children,
|
||||||
|
is_static: fragment.is_static,
|
||||||
|
}),
|
||||||
|
VNodeKind::Component(component) => VNodeKind::Component(component),
|
||||||
|
VNodeKind::Suspended => VNodeKind::Suspended,
|
||||||
|
};
|
||||||
|
VNode {
|
||||||
|
kind,
|
||||||
|
dom_id: self.dom_id.clone(),
|
||||||
|
key: self.key.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for VNode<'_> {
|
||||||
|
fn fmt(&self, s: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
||||||
|
match &self.kind {
|
||||||
|
VNodeKind::Element(el) => write!(s, "element, {}", el.tag_name),
|
||||||
|
VNodeKind::Text(t) => write!(s, "text, {}", t.text),
|
||||||
|
VNodeKind::Fragment(_) => write!(s, "fragment"),
|
||||||
|
VNodeKind::Suspended { .. } => write!(s, "suspended"),
|
||||||
|
VNodeKind::Component(_) => write!(s, "component"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,25 +1,3 @@
|
|||||||
//! Helpers for building virtual DOM VNodes.
|
|
||||||
|
|
||||||
use std::{
|
|
||||||
any::Any,
|
|
||||||
borrow::BorrowMut,
|
|
||||||
cell::{Cell, RefCell},
|
|
||||||
fmt::Arguments,
|
|
||||||
intrinsics::transmute,
|
|
||||||
marker::PhantomData,
|
|
||||||
u128,
|
|
||||||
};
|
|
||||||
|
|
||||||
use bumpalo::Bump;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
events::VirtualEvent,
|
|
||||||
innerlude::{Properties, RealDomNode, Scope, VComponent, VFragment, VText, FC},
|
|
||||||
nodes::{Attribute, Listener, NodeKey, VNode},
|
|
||||||
};
|
|
||||||
|
|
||||||
/// A virtual DOM element builder.
|
|
||||||
///
|
|
||||||
/// Typically constructed with element-specific constructors, eg the `div`
|
/// Typically constructed with element-specific constructors, eg the `div`
|
||||||
/// function for building `<div>` elements or the `button` function for building
|
/// function for building `<div>` elements or the `button` function for building
|
||||||
/// `<button>` elements.
|
/// `<button>` elements.
|
||||||
@@ -504,246 +482,3 @@ To help you identify where this error is coming from, we've generated a backtrac
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> IntoIterator for VNode<'a> {
|
|
||||||
type Item = VNode<'a>;
|
|
||||||
type IntoIter = std::iter::Once<Self::Item>;
|
|
||||||
fn into_iter(self) -> Self::IntoIter {
|
|
||||||
std::iter::once(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'a> IntoVNode<'a> for VNode<'a> {
|
|
||||||
fn into_vnode(self, _: NodeFactory<'a>) -> VNode<'a> {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> IntoVNode<'a> for &VNode<'a> {
|
|
||||||
fn into_vnode(self, _: NodeFactory<'a>) -> VNode<'a> {
|
|
||||||
self.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait IntoVNode<'a> {
|
|
||||||
fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a>;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wrap the the node-builder closure in a concrete type.
|
|
||||||
// ---
|
|
||||||
// This is a bit of a hack to implement the IntoVNode trait for closure types.
|
|
||||||
pub struct LazyNodes<'a, G>
|
|
||||||
where
|
|
||||||
G: FnOnce(NodeFactory<'a>) -> VNode<'a>,
|
|
||||||
{
|
|
||||||
inner: G,
|
|
||||||
_p: PhantomData<&'a ()>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, G> LazyNodes<'a, G>
|
|
||||||
where
|
|
||||||
G: FnOnce(NodeFactory<'a>) -> VNode<'a>,
|
|
||||||
{
|
|
||||||
pub fn new(f: G) -> Self {
|
|
||||||
Self {
|
|
||||||
inner: f,
|
|
||||||
_p: PhantomData {},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cover the cases where nodes are used by macro.
|
|
||||||
// Likely used directly.
|
|
||||||
// ---
|
|
||||||
// let nodes = rsx!{ ... };
|
|
||||||
// rsx! { {nodes } }
|
|
||||||
impl<'a, G> IntoVNode<'a> for LazyNodes<'a, G>
|
|
||||||
where
|
|
||||||
G: FnOnce(NodeFactory<'a>) -> VNode<'a>,
|
|
||||||
{
|
|
||||||
fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> {
|
|
||||||
(self.inner)(cx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Required because anything that enters brackets in the rsx! macro needs to implement IntoIterator
|
|
||||||
impl<'a, G> IntoIterator for LazyNodes<'a, G>
|
|
||||||
where
|
|
||||||
G: FnOnce(NodeFactory<'a>) -> VNode<'a>,
|
|
||||||
{
|
|
||||||
type Item = Self;
|
|
||||||
type IntoIter = std::iter::Once<Self::Item>;
|
|
||||||
fn into_iter(self) -> Self::IntoIter {
|
|
||||||
std::iter::once(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IntoVNode<'_> for () {
|
|
||||||
fn into_vnode<'a>(self, cx: NodeFactory<'a>) -> VNode<'a> {
|
|
||||||
cx.fragment_from_iter(None as Option<VNode>)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IntoVNode<'_> for Option<()> {
|
|
||||||
fn into_vnode<'a>(self, cx: NodeFactory<'a>) -> VNode<'a> {
|
|
||||||
cx.fragment_from_iter(None as Option<VNode>)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn raw_text<'a>(bump: &'a bumpalo::Bump, args: std::fmt::Arguments) -> (&'a str, bool) {
|
|
||||||
match args.as_str() {
|
|
||||||
Some(static_str) => (static_str, true),
|
|
||||||
None => {
|
|
||||||
use bumpalo::core_alloc::fmt::Write;
|
|
||||||
let mut s = bumpalo::collections::String::new_in(bump);
|
|
||||||
s.write_fmt(args).unwrap();
|
|
||||||
(s.into_bump_str(), false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn virtual_child<'a, T: Properties + 'a>(
|
|
||||||
cx: NodeFactory<'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(
|
|
||||||
cx.bump()
|
|
||||||
.alloc(crate::nodes::VComponent::new(&cx, f, props, key, children)),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn vfragment<'a>(
|
|
||||||
cx: NodeFactory<'a>,
|
|
||||||
key: Option<&'a str>, // key: NodeKey<'a>,
|
|
||||||
children: &'a [VNode<'a>],
|
|
||||||
) -> VNode<'a> {
|
|
||||||
VNode::Fragment(cx.bump().alloc(VFragment::new(key, children)))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This struct provides an ergonomic API to quickly build VNodes.
|
|
||||||
///
|
|
||||||
/// NodeFactory 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(Copy, Clone)]
|
|
||||||
pub struct NodeFactory<'a> {
|
|
||||||
pub scope_ref: &'a Scope,
|
|
||||||
pub listener_id: &'a Cell<usize>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> NodeFactory<'a> {
|
|
||||||
#[inline]
|
|
||||||
pub fn bump(&self) -> &'a bumpalo::Bump {
|
|
||||||
&self.scope_ref.cur_frame().bump
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create some text that's allocated along with the other vnodes
|
|
||||||
pub fn text(&self, args: Arguments) -> VNode<'a> {
|
|
||||||
VNode::text(self.bump(), args)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create an element builder
|
|
||||||
pub fn raw_element<'b>(
|
|
||||||
&'b self,
|
|
||||||
tag: &'static str,
|
|
||||||
) -> ElementBuilder<
|
|
||||||
'a,
|
|
||||||
'b,
|
|
||||||
bumpalo::collections::Vec<'a, Listener<'a>>,
|
|
||||||
bumpalo::collections::Vec<'a, Attribute<'a>>,
|
|
||||||
bumpalo::collections::Vec<'a, VNode<'a>>,
|
|
||||||
> {
|
|
||||||
ElementBuilder::new(self, tag)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create an element builder
|
|
||||||
pub fn element<'b>(
|
|
||||||
&'b self,
|
|
||||||
tag: impl DioxusElement,
|
|
||||||
) -> ElementBuilder<
|
|
||||||
'a,
|
|
||||||
'b,
|
|
||||||
bumpalo::collections::Vec<'a, Listener<'a>>,
|
|
||||||
bumpalo::collections::Vec<'a, Attribute<'a>>,
|
|
||||||
bumpalo::collections::Vec<'a, VNode<'a>>,
|
|
||||||
> {
|
|
||||||
ElementBuilder::new(self, tag.tag_name())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn attr(
|
|
||||||
&self,
|
|
||||||
name: &'static str,
|
|
||||||
val: Arguments,
|
|
||||||
namespace: Option<&'static str>,
|
|
||||||
) -> Attribute<'a> {
|
|
||||||
let (value, is_static) = raw_text(self.bump(), val);
|
|
||||||
Attribute {
|
|
||||||
name,
|
|
||||||
value,
|
|
||||||
is_static,
|
|
||||||
namespace,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn fragment(&self, children: &'a [VNode<'a>], key: Option<&'a str>) -> VNode<'a> {
|
|
||||||
VNode::Fragment(self.bump().alloc(VFragment {
|
|
||||||
children,
|
|
||||||
key: NodeKey::new_opt(key),
|
|
||||||
void_root: Cell::new(None),
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn virtual_child<T, C>(
|
|
||||||
&self,
|
|
||||||
f: FC<T>,
|
|
||||||
props: T,
|
|
||||||
key: Option<&'a str>, // key: NodeKey<'a>,
|
|
||||||
children: C,
|
|
||||||
) -> VNode<'a>
|
|
||||||
where
|
|
||||||
C: 'a + AsRef<[VNode<'a>]>,
|
|
||||||
T: Properties + 'a,
|
|
||||||
{
|
|
||||||
let children: &'a C = self.bump().alloc(children);
|
|
||||||
VNode::Component(self.bump().alloc(crate::nodes::VComponent::new(
|
|
||||||
self,
|
|
||||||
f,
|
|
||||||
props,
|
|
||||||
key,
|
|
||||||
children.as_ref(),
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn fragment_from_iter(
|
|
||||||
self,
|
|
||||||
node_iter: impl IntoIterator<Item = impl IntoVNode<'a>>,
|
|
||||||
) -> VNode<'a> {
|
|
||||||
let mut nodes = bumpalo::collections::Vec::new_in(self.bump());
|
|
||||||
for node in node_iter.into_iter() {
|
|
||||||
nodes.push(node.into_vnode(self));
|
|
||||||
}
|
|
||||||
VNode::Fragment(
|
|
||||||
self.bump()
|
|
||||||
.alloc(VFragment::new(None, nodes.into_bump_slice())),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
use std::fmt::Debug;
|
|
||||||
impl Debug for NodeFactory<'_> {
|
|
||||||
fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait DioxusElement {
|
|
||||||
const TAG_NAME: &'static str;
|
|
||||||
const NAME_SPACE: Option<&'static str>;
|
|
||||||
fn tag_name(&self) -> &'static str {
|
|
||||||
Self::TAG_NAME
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+27
-12
@@ -1,18 +1,11 @@
|
|||||||
use crate::hooklist::HookList;
|
use crate::hooklist::HookList;
|
||||||
use crate::{arena::SharedArena, innerlude::*};
|
use crate::{arena::SharedArena, innerlude::*};
|
||||||
use appendlist::AppendList;
|
|
||||||
use bumpalo::Bump;
|
|
||||||
use slotmap::DefaultKey;
|
|
||||||
use slotmap::SlotMap;
|
|
||||||
use std::marker::PhantomData;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use std::{
|
use std::{
|
||||||
any::{Any, TypeId},
|
any::{Any, TypeId},
|
||||||
cell::{Cell, RefCell},
|
cell::{Cell, RefCell},
|
||||||
collections::{HashMap, HashSet, VecDeque},
|
collections::{HashMap, HashSet},
|
||||||
fmt::Debug,
|
|
||||||
future::Future,
|
future::Future,
|
||||||
ops::Deref,
|
|
||||||
pin::Pin,
|
pin::Pin,
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
};
|
};
|
||||||
@@ -78,6 +71,8 @@ pub struct Scope {
|
|||||||
pub(crate) suspended_tasks: Vec<*mut Pin<Box<dyn Future<Output = VNode<'static>>>>>,
|
pub(crate) suspended_tasks: Vec<*mut Pin<Box<dyn Future<Output = VNode<'static>>>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub type FiberTask = Pin<Box<dyn Future<Output = EventTrigger>>>;
|
||||||
|
|
||||||
impl Scope {
|
impl Scope {
|
||||||
// we are being created in the scope of an existing component (where the creator_node lifetime comes into play)
|
// we are being created in the scope of an existing component (where the creator_node lifetime comes into play)
|
||||||
// we are going to break this lifetime by force in order to save it on ourselves.
|
// we are going to break this lifetime by force in order to save it on ourselves.
|
||||||
@@ -155,6 +150,17 @@ impl Scope {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Progress a suspended node
|
||||||
|
pub fn progress_suspended(&mut self) -> Result<()> {
|
||||||
|
// load the hook
|
||||||
|
// downcast to our special state
|
||||||
|
// run just this hook
|
||||||
|
// create a new vnode
|
||||||
|
// diff this new vnode with the original suspended vnode
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
// this is its own function so we can preciesly control how lifetimes flow
|
// this is its own function so we can preciesly control how lifetimes flow
|
||||||
unsafe fn call_user_component<'a>(&'a self, caller: &WrappedCaller) -> VNode<'static> {
|
unsafe fn call_user_component<'a>(&'a self, caller: &WrappedCaller) -> VNode<'static> {
|
||||||
let new_head: VNode<'a> = caller(self);
|
let new_head: VNode<'a> = caller(self);
|
||||||
@@ -171,6 +177,11 @@ impl Scope {
|
|||||||
..
|
..
|
||||||
} = trigger;
|
} = trigger;
|
||||||
|
|
||||||
|
if let &VirtualEvent::FiberEvent = &event {
|
||||||
|
log::info!("arrived a fiber event");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
// todo: implement scanning for outdated events
|
// todo: implement scanning for outdated events
|
||||||
|
|
||||||
// Convert the raw ptr into an actual object
|
// Convert the raw ptr into an actual object
|
||||||
@@ -182,7 +193,7 @@ impl Scope {
|
|||||||
self.arena_idx
|
self.arena_idx
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut listners = self.listeners.borrow_mut();
|
let listners = self.listeners.borrow_mut();
|
||||||
|
|
||||||
// let listener = listners.get(trigger);
|
// let listener = listners.get(trigger);
|
||||||
let raw_listener = listners.iter().find(|(domptr, _)| {
|
let raw_listener = listners.iter().find(|(domptr, _)| {
|
||||||
@@ -207,23 +218,27 @@ impl Scope {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn submit_task(&self, task: &mut Pin<Box<dyn Future<Output = ()>>>) {
|
pub fn submit_task(&self, task: FiberTask) {
|
||||||
log::debug!("Task submitted into scope");
|
log::debug!("Task submitted into scope");
|
||||||
(self.task_submitter)(DTask::new(task, self.arena_idx));
|
(self.task_submitter)(task);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub(crate) fn next_frame<'bump>(&'bump self) -> &'bump VNode<'bump> {
|
pub(crate) fn next_frame<'bump>(&'bump self) -> &'bump VNode<'bump> {
|
||||||
self.frames.current_head_node()
|
self.frames.current_head_node()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub(crate) fn old_frame<'bump>(&'bump self) -> &'bump VNode<'bump> {
|
pub(crate) fn old_frame<'bump>(&'bump self) -> &'bump VNode<'bump> {
|
||||||
self.frames.prev_head_node()
|
self.frames.prev_head_node()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub(crate) fn cur_frame(&self) -> &BumpFrame {
|
pub(crate) fn cur_frame(&self) -> &BumpFrame {
|
||||||
self.frames.cur_frame()
|
self.frames.cur_frame()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn root<'a>(&'a self) -> &'a VNode<'a> {
|
pub fn root<'a>(&'a self) -> &'a VNode<'a> {
|
||||||
&self.frames.cur_frame().head_node
|
&self.frames.cur_frame().head_node
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,116 +0,0 @@
|
|||||||
//! Dedicated styling system for Components
|
|
||||||
//! ---------------------------------------
|
|
||||||
//!
|
|
||||||
//! In the future, we'd like to move this out of Dioxus core or build a better, more general abstraction. For now, dedicated
|
|
||||||
//! styling is more-or-less hardcoded into Dioxus.
|
|
||||||
//!
|
|
||||||
//!
|
|
||||||
//!
|
|
||||||
//!
|
|
||||||
//!
|
|
||||||
//!
|
|
||||||
//!
|
|
||||||
//!
|
|
||||||
|
|
||||||
use crate::{innerlude::Attribute, nodebuilder::NodeFactory};
|
|
||||||
|
|
||||||
pub struct StyleBuilder;
|
|
||||||
pub trait AsAttr<'a> {
|
|
||||||
fn to_attr(self, field: &'static str, fac: &NodeFactory<'a>) -> Attribute<'a>;
|
|
||||||
}
|
|
||||||
impl<'a> AsAttr<'a> for std::fmt::Arguments<'a> {
|
|
||||||
fn to_attr(self, field: &'static str, fac: &NodeFactory<'a>) -> Attribute<'a> {
|
|
||||||
fac.attr(field, self, Some("style"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! build_styles {
|
|
||||||
($ ($name:ident: $lit:literal,)* ) => {
|
|
||||||
impl StyleBuilder {
|
|
||||||
$(
|
|
||||||
pub fn $name<'a>(f: &NodeFactory<'a>, args: impl AsAttr<'a>) -> Attribute<'a> {
|
|
||||||
args.to_attr($lit, f)
|
|
||||||
}
|
|
||||||
)*
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
build_styles! {
|
|
||||||
background: "background",
|
|
||||||
background_attachment: "background-attachment",
|
|
||||||
background_color: "background-color",
|
|
||||||
background_image: "background-image",
|
|
||||||
background_position: "background-position",
|
|
||||||
background_repeat: "background-repeat",
|
|
||||||
border: "border",
|
|
||||||
border_bottom: "border-bottom",
|
|
||||||
border_bottom_color: "border-bottom-color",
|
|
||||||
border_bottom_style: "border-bottom-style",
|
|
||||||
border_bottom_width: "border-bottom-width",
|
|
||||||
border_color: "border-color",
|
|
||||||
border_left: "border-left",
|
|
||||||
border_left_color: "border-left-color",
|
|
||||||
border_left_style: "border-left-style",
|
|
||||||
border_left_width: "border-left-width",
|
|
||||||
border_right: "border-right",
|
|
||||||
border_right_color: "border-right-color",
|
|
||||||
border_right_style: "border-right-style",
|
|
||||||
border_right_width: "border-right-width",
|
|
||||||
border_style: "border-style",
|
|
||||||
border_top: "border-top",
|
|
||||||
border_top_color: "border-top-color",
|
|
||||||
border_top_style: "border-top-style",
|
|
||||||
border_top_width: "border-top-width",
|
|
||||||
border_width: "border-width",
|
|
||||||
clear: "clear",
|
|
||||||
clip: "clip",
|
|
||||||
color: "color",
|
|
||||||
cursor: "cursor",
|
|
||||||
display: "display",
|
|
||||||
filter: "filter",
|
|
||||||
css_float: "css-float",
|
|
||||||
font: "font",
|
|
||||||
font_family: "font-family",
|
|
||||||
font_size: "font-size",
|
|
||||||
font_variant: "font-variant",
|
|
||||||
font_weight: "font-weight",
|
|
||||||
height: "height",
|
|
||||||
left: "left",
|
|
||||||
letter_spacing: "letter-spacing",
|
|
||||||
line_height: "line-height",
|
|
||||||
list_style: "list-style",
|
|
||||||
list_style_image: "list-style-image",
|
|
||||||
list_style_position: "list-style-position",
|
|
||||||
list_style_type: "list-style-type",
|
|
||||||
margin: "margin",
|
|
||||||
margin_bottom: "margin-bottom",
|
|
||||||
margin_left: "margin-left",
|
|
||||||
margin_right: "margin-right",
|
|
||||||
margin_top: "margin-top",
|
|
||||||
overflow: "overflow",
|
|
||||||
padding: "padding",
|
|
||||||
padding_bottom: "padding-bottom",
|
|
||||||
padding_left: "padding-left",
|
|
||||||
padding_right: "padding-right",
|
|
||||||
padding_top: "padding-top",
|
|
||||||
page_break_after: "page-break-after",
|
|
||||||
page_break_before: "page-break-before",
|
|
||||||
position: "position",
|
|
||||||
stroke_dasharray: "stroke-dasharray",
|
|
||||||
stroke_dashoffset: "stroke-dashoffset",
|
|
||||||
text_align: "text-align",
|
|
||||||
text_decoration: "text-decoration",
|
|
||||||
text_indent: "text-indent",
|
|
||||||
text_transform: "text-transform",
|
|
||||||
top: "top",
|
|
||||||
vertical_align: "vertical-align",
|
|
||||||
visibility: "visibility",
|
|
||||||
width: "width",
|
|
||||||
z_index: "z-index",
|
|
||||||
}
|
|
||||||
|
|
||||||
fn example(f: &NodeFactory) {
|
|
||||||
let style_list = &[("width", "10"), ("text-decoration", "")];
|
|
||||||
let styles = &[StyleBuilder::background(f, format_args!("10"))];
|
|
||||||
}
|
|
||||||
+62
-57
@@ -16,28 +16,28 @@ use std::{
|
|||||||
task::{Context, Poll},
|
task::{Context, Poll},
|
||||||
};
|
};
|
||||||
|
|
||||||
use futures_util::{Future, Stream, StreamExt};
|
use futures_util::{stream::FuturesUnordered, Future, Stream, StreamExt};
|
||||||
use slotmap::{DefaultKey, SlotMap};
|
use slotmap::{DefaultKey, SlotMap};
|
||||||
|
|
||||||
use crate::{events::EventTrigger, innerlude::ScopeIdx};
|
use crate::innerlude::{EventTrigger, FiberTask, ScopeIdx};
|
||||||
|
|
||||||
pub type TaskSubmitter = Arc<dyn Fn(DTask)>;
|
pub type TaskSubmitter = Arc<dyn Fn(FiberTask)>;
|
||||||
|
|
||||||
pub struct TaskQueue {
|
pub struct TaskQueue {
|
||||||
slots: Arc<RwLock<SlotMap<DefaultKey, DTask>>>,
|
slots: Arc<RwLock<FuturesUnordered<FiberTask>>>,
|
||||||
|
// slots: Arc<RwLock<SlotMap<DefaultKey, DTask>>>,
|
||||||
submitter: TaskSubmitter,
|
submitter: TaskSubmitter,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TaskQueue {
|
impl TaskQueue {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let slots = Arc::new(RwLock::new(SlotMap::new()));
|
let slots = Arc::new(RwLock::new(FuturesUnordered::new()));
|
||||||
|
|
||||||
let slots2 = slots.clone();
|
let slots2 = slots.clone();
|
||||||
|
|
||||||
let submitter = Arc::new(move |task| {
|
let submitter = Arc::new(move |task| {
|
||||||
let mut slots = slots2.write().unwrap();
|
let mut slots = slots2.write().unwrap();
|
||||||
log::debug!("Task submitted into global task queue");
|
log::debug!("Task submitted into global task queue");
|
||||||
slots.insert(task);
|
slots.push(task);
|
||||||
});
|
});
|
||||||
Self { slots, submitter }
|
Self { slots, submitter }
|
||||||
}
|
}
|
||||||
@@ -46,9 +46,9 @@ impl TaskQueue {
|
|||||||
self.submitter.clone()
|
self.submitter.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn submit_task(&mut self, task: DTask) -> TaskHandle {
|
pub fn submit_task(&mut self, task: FiberTask) {
|
||||||
let key = self.slots.write().unwrap().insert(task);
|
self.slots.write().unwrap().push(task);
|
||||||
TaskHandle { key }
|
// TaskHandle { key }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
@@ -57,73 +57,78 @@ impl TaskQueue {
|
|||||||
pub fn len(&self) -> usize {
|
pub fn len(&self) -> usize {
|
||||||
self.slots.read().unwrap().len()
|
self.slots.read().unwrap().len()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl Stream for TaskQueue {
|
|
||||||
type Item = EventTrigger;
|
|
||||||
|
|
||||||
/// We can never be finished polling
|
|
||||||
fn poll_next(
|
|
||||||
mut self: Pin<&mut Self>,
|
|
||||||
cx: &mut std::task::Context<'_>,
|
|
||||||
) -> std::task::Poll<Option<Self::Item>> {
|
|
||||||
// let yield_every = self.len();
|
|
||||||
// let mut polled = 0;
|
|
||||||
|
|
||||||
|
pub async fn next(&mut self) -> Option<EventTrigger> {
|
||||||
let mut slots = self.slots.write().unwrap();
|
let mut slots = self.slots.write().unwrap();
|
||||||
for (key, slot) in slots.iter_mut() {
|
slots.next().await
|
||||||
if slot.dead.get() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let r = slot.fut;
|
|
||||||
let mut fut = unsafe { &mut *r };
|
|
||||||
// use futures::{future::Future, poll, FutureExt};
|
|
||||||
|
|
||||||
let f2 = fut.as_mut();
|
|
||||||
let w = cx.waker();
|
|
||||||
let mut cx = Context::from_waker(&w);
|
|
||||||
|
|
||||||
// Pin::new_unchecked(pointer)
|
|
||||||
// use std::future::Future;
|
|
||||||
match f2.poll(&mut cx) {
|
|
||||||
Poll::Ready(_) => {
|
|
||||||
let trigger = EventTrigger::new_from_task(slot.originator);
|
|
||||||
slot.dead.set(true);
|
|
||||||
return Poll::Ready(Some(trigger));
|
|
||||||
}
|
|
||||||
Poll::Pending => continue,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// we tried polling every active task.
|
|
||||||
// give up and relinquish controlto the parent
|
|
||||||
|
|
||||||
// We have polled a large number of futures in a row without yielding.
|
|
||||||
// To ensure we do not starve other tasks waiting on the executor,
|
|
||||||
// we yield here, but immediately wake ourselves up to continue.
|
|
||||||
// cx.waker().wake_by_ref();
|
|
||||||
return Poll::Pending;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// impl Stream for TaskQueue {
|
||||||
|
// type Item = EventTrigger;
|
||||||
|
|
||||||
|
// /// We can never be finished polling
|
||||||
|
// fn poll_next(
|
||||||
|
// self: Pin<&mut Self>,
|
||||||
|
// cx: &mut std::task::Context<'_>,
|
||||||
|
// ) -> std::task::Poll<Option<Self::Item>> {
|
||||||
|
// // let yield_every = self.len();
|
||||||
|
// // let mut polled = 0;
|
||||||
|
|
||||||
|
// let mut slots = self.slots.write().unwrap();
|
||||||
|
// for (_key, slot) in slots.iter_mut() {
|
||||||
|
// if slot.dead.get() {
|
||||||
|
// continue;
|
||||||
|
// }
|
||||||
|
// let r = slot.fut;
|
||||||
|
// // let fut = unsafe { &mut *r };
|
||||||
|
// // use futures::{future::Future, poll, FutureExt};
|
||||||
|
|
||||||
|
// let f2 = fut.as_mut();
|
||||||
|
// let w = cx.waker();
|
||||||
|
// let mut cx = Context::from_waker(&w);
|
||||||
|
|
||||||
|
// // Pin::new_unchecked(pointer)
|
||||||
|
// // use std::future::Future;
|
||||||
|
// match f2.poll(&mut cx) {
|
||||||
|
// Poll::Ready(_) => {
|
||||||
|
// let trigger = EventTrigger::new_from_task(slot.originator);
|
||||||
|
// slot.dead.set(true);
|
||||||
|
// return Poll::Ready(Some(trigger));
|
||||||
|
// }
|
||||||
|
// Poll::Pending => continue,
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // we tried polling every active task.
|
||||||
|
// // give up and relinquish controlto the parent
|
||||||
|
|
||||||
|
// // We have polled a large number of futures in a row without yielding.
|
||||||
|
// // To ensure we do not starve other tasks waiting on the executor,
|
||||||
|
// // we yield here, but immediately wake ourselves up to continue.
|
||||||
|
// // cx.waker().wake_by_ref();
|
||||||
|
// return Poll::Pending;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
pub struct TaskHandle {
|
pub struct TaskHandle {
|
||||||
key: DefaultKey,
|
key: DefaultKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct DTask {
|
pub struct DTask {
|
||||||
fut: *mut Pin<Box<dyn Future<Output = ()>>>,
|
fut: FiberTask,
|
||||||
originator: ScopeIdx,
|
originator: ScopeIdx,
|
||||||
dead: Cell<bool>,
|
dead: Cell<bool>,
|
||||||
}
|
}
|
||||||
impl DTask {
|
impl DTask {
|
||||||
pub fn new(fut: &mut Pin<Box<dyn Future<Output = ()>>>, originator: ScopeIdx) -> Self {
|
pub fn new(fut: FiberTask, originator: ScopeIdx) -> Self {
|
||||||
Self {
|
Self {
|
||||||
fut,
|
fut,
|
||||||
originator,
|
originator,
|
||||||
dead: Cell::new(false),
|
dead: Cell::new(false),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn debug_new(fut: &mut Pin<Box<dyn Future<Output = ()>>>) -> Self {
|
pub fn debug_new(fut: FiberTask) -> Self {
|
||||||
let originator = ScopeIdx::default();
|
let originator = ScopeIdx::default();
|
||||||
Self {
|
Self {
|
||||||
fut,
|
fut,
|
||||||
|
|||||||
+17
-15
@@ -1,7 +1,6 @@
|
|||||||
use std::{
|
use std::{
|
||||||
cell::{RefCell, RefMut},
|
cell::{Cell, RefCell, RefMut},
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
vec::Drain,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use futures_util::StreamExt;
|
use futures_util::StreamExt;
|
||||||
@@ -65,6 +64,9 @@ impl RealDomNode {
|
|||||||
pub const fn empty() -> Self {
|
pub const fn empty() -> Self {
|
||||||
Self(u64::MIN)
|
Self(u64::MIN)
|
||||||
}
|
}
|
||||||
|
pub const fn empty_cell() -> Cell<Self> {
|
||||||
|
Cell::new(Self::empty())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct DebugDom {
|
pub struct DebugDom {
|
||||||
@@ -86,23 +88,23 @@ impl DebugDom {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<'a> RealDom<'a> for DebugDom {
|
impl<'a> RealDom<'a> for DebugDom {
|
||||||
fn push(&mut self, root: RealDomNode) {}
|
fn push(&mut self, _root: RealDomNode) {}
|
||||||
fn pop(&mut self) {}
|
fn pop(&mut self) {}
|
||||||
|
|
||||||
fn append_children(&mut self, many: u32) {}
|
fn append_children(&mut self, _many: u32) {}
|
||||||
|
|
||||||
fn replace_with(&mut self, many: u32) {}
|
fn replace_with(&mut self, _many: u32) {}
|
||||||
|
|
||||||
fn remove(&mut self) {}
|
fn remove(&mut self) {}
|
||||||
|
|
||||||
fn remove_all_children(&mut self) {}
|
fn remove_all_children(&mut self) {}
|
||||||
|
|
||||||
fn create_text_node(&mut self, text: &str) -> RealDomNode {
|
fn create_text_node(&mut self, _text: &str) -> RealDomNode {
|
||||||
self.counter += 1;
|
self.counter += 1;
|
||||||
RealDomNode::new(self.counter)
|
RealDomNode::new(self.counter)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_element(&mut self, tag: &str, ns: Option<&'a str>) -> RealDomNode {
|
fn create_element(&mut self, _tag: &str, _ns: Option<&'a str>) -> RealDomNode {
|
||||||
self.counter += 1;
|
self.counter += 1;
|
||||||
RealDomNode::new(self.counter)
|
RealDomNode::new(self.counter)
|
||||||
}
|
}
|
||||||
@@ -114,20 +116,20 @@ impl<'a> RealDom<'a> for DebugDom {
|
|||||||
|
|
||||||
fn new_event_listener(
|
fn new_event_listener(
|
||||||
&mut self,
|
&mut self,
|
||||||
event: &str,
|
_event: &str,
|
||||||
scope: ScopeIdx,
|
_scope: ScopeIdx,
|
||||||
element_id: usize,
|
_element_id: usize,
|
||||||
realnode: RealDomNode,
|
_realnode: RealDomNode,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_event_listener(&mut self, event: &str) {}
|
fn remove_event_listener(&mut self, _event: &str) {}
|
||||||
|
|
||||||
fn set_text(&mut self, text: &str) {}
|
fn set_text(&mut self, _text: &str) {}
|
||||||
|
|
||||||
fn set_attribute(&mut self, name: &str, value: &str, namespace: Option<&str>) {}
|
fn set_attribute(&mut self, _name: &str, _value: &str, _namespace: Option<&str>) {}
|
||||||
|
|
||||||
fn remove_attribute(&mut self, name: &str) {}
|
fn remove_attribute(&mut self, _name: &str) {}
|
||||||
|
|
||||||
fn raw_node_as_any_mut(&self) -> &mut dyn std::any::Any {
|
fn raw_node_as_any_mut(&self) -> &mut dyn std::any::Any {
|
||||||
todo!()
|
todo!()
|
||||||
|
|||||||
@@ -21,13 +21,13 @@
|
|||||||
|
|
||||||
use crate::tasks::TaskQueue;
|
use crate::tasks::TaskQueue;
|
||||||
use crate::{arena::SharedArena, innerlude::*};
|
use crate::{arena::SharedArena, innerlude::*};
|
||||||
use appendlist::AppendList;
|
|
||||||
use slotmap::DefaultKey;
|
use slotmap::DefaultKey;
|
||||||
use slotmap::SlotMap;
|
use slotmap::SlotMap;
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use std::cell::RefCell;
|
|
||||||
|
use std::any::TypeId;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::{any::TypeId, fmt::Debug, rc::Rc};
|
|
||||||
|
|
||||||
pub type ScopeIdx = DefaultKey;
|
pub type ScopeIdx = DefaultKey;
|
||||||
|
|
||||||
@@ -146,7 +146,7 @@ impl VirtualDom {
|
|||||||
.with(|arena| {
|
.with(|arena| {
|
||||||
arena.insert_with_key(move |myidx| {
|
arena.insert_with_key(move |myidx| {
|
||||||
let event_channel = _event_queue.new_channel(0, myidx);
|
let event_channel = _event_queue.new_channel(0, myidx);
|
||||||
let caller = crate::nodes::create_component_caller(root, props_ptr as *const _);
|
let caller = NodeFactory::create_component_caller(root, props_ptr as *const _);
|
||||||
Scope::new(caller, myidx, None, 0, event_channel, link, &[], submitter)
|
Scope::new(caller, myidx, None, 0, event_channel, link, &[], submitter)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -191,7 +191,10 @@ impl VirtualDom {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom rom scratch
|
/// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom rom scratch
|
||||||
|
///
|
||||||
/// Currently this doesn't do what we want it to do
|
/// Currently this doesn't do what we want it to do
|
||||||
|
///
|
||||||
|
/// The diff machine expects the RealDom's stack to be the root of the application
|
||||||
pub fn rebuild<'s, Dom: RealDom<'s>>(&'s mut self, realdom: &mut Dom) -> Result<()> {
|
pub fn rebuild<'s, Dom: RealDom<'s>>(&'s mut self, realdom: &mut Dom) -> Result<()> {
|
||||||
let mut diff_machine = DiffMachine::new(
|
let mut diff_machine = DiffMachine::new(
|
||||||
realdom,
|
realdom,
|
||||||
@@ -201,15 +204,26 @@ impl VirtualDom {
|
|||||||
&self.tasks,
|
&self.tasks,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let cur_component = self.components.try_get_mut(self.base_scope).unwrap();
|
||||||
|
|
||||||
|
cur_component.run_scope()?;
|
||||||
|
|
||||||
|
let meta = diff_machine.create(cur_component.next_frame());
|
||||||
|
log::info!(
|
||||||
|
"nodes created! appending to body {:#?}",
|
||||||
|
meta.added_to_stack
|
||||||
|
);
|
||||||
|
diff_machine.dom.append_children(meta.added_to_stack);
|
||||||
|
|
||||||
// Schedule an update and then immediately call it on the root component
|
// Schedule an update and then immediately call it on the root component
|
||||||
// This is akin to a hook being called from a listener and requring a re-render
|
// This is akin to a hook being called from a listener and requring a re-render
|
||||||
// Instead, this is done on top-level component
|
// Instead, this is done on top-level component
|
||||||
let base = self.components.try_get(self.base_scope)?;
|
// let base = self.components.try_get(self.base_scope)?;
|
||||||
|
|
||||||
let update = &base.event_channel;
|
// let update = &base.event_channel;
|
||||||
update();
|
// update();
|
||||||
|
|
||||||
self.progress_completely(&mut diff_machine)?;
|
// self.progress_completely(&mut diff_machine)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use dioxus_core::prelude::*;
|
|
||||||
// type VirtualNode = VNode;
|
// type VirtualNode = VNode;
|
||||||
|
|
||||||
/// Test a basic usage of a virtual dom + text renderer combo
|
/// Test a basic usage of a virtual dom + text renderer combo
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ pub fn use_state<'a, 'c, T: 'static, F: FnOnce() -> T, P>(
|
|||||||
move || UseStateInner {
|
move || UseStateInner {
|
||||||
current_val: initial_state_fn(),
|
current_val: initial_state_fn(),
|
||||||
callback: cx.schedule_update(),
|
callback: cx.schedule_update(),
|
||||||
wip: RefCell::new(None),
|
wip: Rc::new(RefCell::new(None)),
|
||||||
update_scheuled: Cell::new(false),
|
update_scheuled: Cell::new(false),
|
||||||
},
|
},
|
||||||
move |hook| {
|
move |hook| {
|
||||||
@@ -76,7 +76,7 @@ struct UseStateInner<T: 'static> {
|
|||||||
current_val: T,
|
current_val: T,
|
||||||
update_scheuled: Cell<bool>,
|
update_scheuled: Cell<bool>,
|
||||||
callback: Rc<dyn Fn()>,
|
callback: Rc<dyn Fn()>,
|
||||||
wip: RefCell<Option<T>>,
|
wip: Rc<RefCell<Option<T>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct UseState<'a, T: 'static> {
|
pub struct UseState<'a, T: 'static> {
|
||||||
@@ -118,6 +118,11 @@ impl<'a, T: 'static> UseState<'a, T> {
|
|||||||
pub fn classic(self) -> (&'a T, &'a Rc<dyn Fn(T)>) {
|
pub fn classic(self) -> (&'a T, &'a Rc<dyn Fn(T)>) {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn setter(&self) -> Rc<dyn Fn(T)> {
|
||||||
|
let slot = self.inner.wip.clone();
|
||||||
|
Rc::new(move |new| *slot.borrow_mut() = Some(new))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
impl<'a, T: 'static + ToOwned<Owned = T>> UseState<'a, T> {
|
impl<'a, T: 'static + ToOwned<Owned = T>> UseState<'a, T> {
|
||||||
pub fn get_mut(self) -> RefMut<'a, T> {
|
pub fn get_mut(self) -> RefMut<'a, T> {
|
||||||
|
|||||||
+86
-1
@@ -1,6 +1,6 @@
|
|||||||
# Html (and SVG) Namespace for Dioxus
|
# Html (and SVG) Namespace for Dioxus
|
||||||
|
|
||||||
The Dioxus `rsx!` and `html!` macros can accept any compile-time correct namespace on top of NodeBuilder. This crate provides the HTML (and SVG) namespaces which get imported in the Dioxus prelude.
|
The Dioxus `rsx!` and `html!` macros can accept any compile-time correct namespace on top of NodeFactory. This crate provides the HTML (and SVG) namespaces which get imported in the Dioxus prelude.
|
||||||
|
|
||||||
However, this abstraction enables you to add any namespace of elements, provided they're in scope when rsx! is called. For an example, a UI that is designed for Augmented Reality might use different primitives than HTML:
|
However, this abstraction enables you to add any namespace of elements, provided they're in scope when rsx! is called. For an example, a UI that is designed for Augmented Reality might use different primitives than HTML:
|
||||||
|
|
||||||
@@ -20,3 +20,88 @@ rsx! {
|
|||||||
```
|
```
|
||||||
|
|
||||||
This is currently a not-very-explored part of Dioxus. However, the namespacing system does make it possible to provide syntax highlighting, documentation, "go to definition" and compile-time correctness, so it's worth having it abstracted.
|
This is currently a not-very-explored part of Dioxus. However, the namespacing system does make it possible to provide syntax highlighting, documentation, "go to definition" and compile-time correctness, so it's worth having it abstracted.
|
||||||
|
|
||||||
|
## How it works:
|
||||||
|
|
||||||
|
Elements for dioxus must implement the (simple) DioxusElement trait to be used in the rsx! macro.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
struct div;
|
||||||
|
impl DioxusElement for div {
|
||||||
|
const TAG_NAME: &'static str = "div";
|
||||||
|
const NAME_SPACE: Option<&'static str> = None;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
All elements should be defined as a zero-sized-struct (also known as unit struct). These structs are zero-cost and just provide the type-level trickery to Rust for compile-time correct templates.
|
||||||
|
|
||||||
|
Attributes would then be implemented as methods on these unit structs.
|
||||||
|
|
||||||
|
The HTML namespace is defined mostly with macros. However, the expanded form would look something like this:
|
||||||
|
```rust
|
||||||
|
struct base;
|
||||||
|
impl DioxusElement for base {
|
||||||
|
const TAG_NAME: &'static str = "base";
|
||||||
|
const NAME_SPACE: Option<&'static str> = None;
|
||||||
|
}
|
||||||
|
impl base {
|
||||||
|
#[inline]
|
||||||
|
fn href<'a>(&self, f: NodeFactory<'a>, v: Arguments) -> Attribute<'a> {
|
||||||
|
f.attr("href", v, None, false)
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
fn target<'a>(&self, f: NodeFactory<'a>, v: Arguments) -> Attribute<'a> {
|
||||||
|
f.attr("target", v, None, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Because attributes are defined as methods on the unit struct, they guard the attribute creation behind a compile-time correct interface.
|
||||||
|
|
||||||
|
|
||||||
|
## How to extend it:
|
||||||
|
|
||||||
|
Whenever the rsx! macro is called, it relies on a module `dioxus_elements` to be in scope. When you enable the `html` feature in dioxus, this module gets imported in the prelude. However, you can extend this with your own set of custom elements by making your own `dioxus_elements` module and re-exporting the html namespace.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
mod dioxus_elements {
|
||||||
|
use dioxus::prelude::dioxus_elements::*;
|
||||||
|
struct my_element;
|
||||||
|
impl DioxusElement for my_element {
|
||||||
|
const TAG_NAME: &'static str = "base";
|
||||||
|
const NAME_SPACE: Option<&'static str> = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Limitations:
|
||||||
|
-
|
||||||
|
|
||||||
|
## How to work around it:
|
||||||
|
If an attribute in Dioxus is invalid (defined incorrectly) - first, make an issue - but then, you can work around it. The raw builder API is actually somewhat ergonomic to work with, and the NodeFactory type exposes a bunch of methods to make any type of tree - even invalid ones! So obviously, be careful, but there's basically anything you can do.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
cx.render(rsx!{
|
||||||
|
div {
|
||||||
|
h1 {}
|
||||||
|
// Oh no! I need a super custom element
|
||||||
|
{LazyNodes::new(move |f| {
|
||||||
|
f.raw_element(
|
||||||
|
// tag name
|
||||||
|
"custom_element",
|
||||||
|
|
||||||
|
// attributes
|
||||||
|
&[f.attr("billy", format_args!("goat"))],
|
||||||
|
|
||||||
|
// listeners
|
||||||
|
&[f.listener(onclick(move |_| {}))],
|
||||||
|
|
||||||
|
// children
|
||||||
|
&[cx.render(rsx!(div {} ))],
|
||||||
|
|
||||||
|
// key
|
||||||
|
None
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
+922
-113
File diff suppressed because it is too large.
Load diff
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
use std::fmt::{Display, Formatter};
|
use std::fmt::{Display, Formatter};
|
||||||
|
|
||||||
use dioxus_core::{nodes::VNode, virtual_dom::VirtualDom};
|
use dioxus_core::*;
|
||||||
|
|
||||||
pub fn render_root(vdom: &VirtualDom) -> String {
|
pub fn render_root(vdom: &VirtualDom) -> String {
|
||||||
format!("{:}", TextRenderer::new(vdom))
|
format!("{:}", TextRenderer::new(vdom))
|
||||||
@@ -69,8 +69,8 @@ impl<'a> TextRenderer<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn html_render(&self, node: &VNode, f: &mut std::fmt::Formatter, il: u16) -> std::fmt::Result {
|
fn html_render(&self, node: &VNode, f: &mut std::fmt::Formatter, il: u16) -> std::fmt::Result {
|
||||||
match node {
|
match &node.kind {
|
||||||
VNode::Text(text) => {
|
VNodeKind::Text(text) => {
|
||||||
if self.cfg.indent {
|
if self.cfg.indent {
|
||||||
for _ in 0..il {
|
for _ in 0..il {
|
||||||
write!(f, " ")?;
|
write!(f, " ")?;
|
||||||
@@ -78,7 +78,7 @@ impl<'a> TextRenderer<'a> {
|
|||||||
}
|
}
|
||||||
write!(f, "{}", text.text)?
|
write!(f, "{}", text.text)?
|
||||||
}
|
}
|
||||||
VNode::Element(el) => {
|
VNodeKind::Element(el) => {
|
||||||
if self.cfg.indent {
|
if self.cfg.indent {
|
||||||
for _ in 0..il {
|
for _ in 0..il {
|
||||||
write!(f, " ")?;
|
write!(f, " ")?;
|
||||||
@@ -112,12 +112,12 @@ impl<'a> TextRenderer<'a> {
|
|||||||
write!(f, "\n")?;
|
write!(f, "\n")?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
VNode::Fragment(frag) => {
|
VNodeKind::Fragment(frag) => {
|
||||||
for child in frag.children {
|
for child in frag.children {
|
||||||
self.html_render(child, f, il + 1)?;
|
self.html_render(child, f, il + 1)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
VNode::Component(vcomp) => {
|
VNodeKind::Component(vcomp) => {
|
||||||
let idx = vcomp.ass_scope.get().unwrap();
|
let idx = vcomp.ass_scope.get().unwrap();
|
||||||
|
|
||||||
let new_node = self
|
let new_node = self
|
||||||
@@ -130,7 +130,7 @@ impl<'a> TextRenderer<'a> {
|
|||||||
|
|
||||||
self.html_render(new_node, f, il + 1)?;
|
self.html_render(new_node, f, il + 1)?;
|
||||||
}
|
}
|
||||||
VNode::Suspended { .. } => todo!(),
|
VNodeKind::Suspended => todo!(),
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -151,6 +151,7 @@ mod tests {
|
|||||||
use dioxus_core as dioxus;
|
use dioxus_core as dioxus;
|
||||||
use dioxus_core::prelude::*;
|
use dioxus_core::prelude::*;
|
||||||
use dioxus_html as dioxus_elements;
|
use dioxus_html as dioxus_elements;
|
||||||
|
use dioxus_html::GlobalAttributes;
|
||||||
|
|
||||||
const SIMPLE_APP: FC<()> = |cx| {
|
const SIMPLE_APP: FC<()> = |cx| {
|
||||||
cx.render(rsx!(div {
|
cx.render(rsx!(div {
|
||||||
|
|||||||
+11
-7
@@ -8,10 +8,10 @@ license = "MIT/Apache-2.0"
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
dioxus-core = { path="../core", version="0.1.2" }
|
dioxus-core = { path = "../core", version = "0.1.2" }
|
||||||
dioxus-html = { path="../html" }
|
dioxus-html = { path = "../html" }
|
||||||
js-sys = "0.3"
|
js-sys = "0.3"
|
||||||
wasm-bindgen = { version="0.2.71", features=["enable-interning"] }
|
wasm-bindgen = { version = "0.2.71", features = ["enable-interning"] }
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
wasm-bindgen-futures = "0.4.20"
|
wasm-bindgen-futures = "0.4.20"
|
||||||
wasm-logger = "0.2.0"
|
wasm-logger = "0.2.0"
|
||||||
@@ -22,7 +22,7 @@ console_error_panic_hook = "0.1.6"
|
|||||||
generational-arena = "0.2.8"
|
generational-arena = "0.2.8"
|
||||||
wasm-bindgen-test = "0.3.21"
|
wasm-bindgen-test = "0.3.21"
|
||||||
once_cell = "1.7.2"
|
once_cell = "1.7.2"
|
||||||
atoms = { path="../atoms" }
|
atoms = { path = "../atoms" }
|
||||||
async-channel = "1.6.1"
|
async-channel = "1.6.1"
|
||||||
anyhow = "1.0.41"
|
anyhow = "1.0.41"
|
||||||
slotmap = "1.0.3"
|
slotmap = "1.0.3"
|
||||||
@@ -75,7 +75,11 @@ crate-type = ["cdylib", "rlib"]
|
|||||||
im-rc = "15.0.0"
|
im-rc = "15.0.0"
|
||||||
# rand = { version="0.8.4", features=["small_rng"] }
|
# rand = { version="0.8.4", features=["small_rng"] }
|
||||||
separator = "0.4.1"
|
separator = "0.4.1"
|
||||||
uuid = { version="0.8.2", features=["v4", "wasm-bindgen"] }
|
uuid = { version = "0.8.2", features = ["v4", "wasm-bindgen"] }
|
||||||
dioxus-hooks = { path="../hooks" }
|
dioxus-hooks = { path = "../hooks" }
|
||||||
gloo-timers = { version="0.2.1", features=["futures"] }
|
gloo-timers = { version = "0.2.1", features = ["futures"] }
|
||||||
|
serde = { version = "1.0.126", features = ["derive"] }
|
||||||
|
surf = { version = "2.2.0", default-features = false, features = [
|
||||||
|
"wasm-client",
|
||||||
|
] }
|
||||||
# wasm-timer = "0.2.5"
|
# wasm-timer = "0.2.5"
|
||||||
@@ -17,34 +17,47 @@ use dioxus_web::*;
|
|||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
// Setup logging
|
// Setup logging
|
||||||
wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
|
// wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
|
||||||
console_error_panic_hook::set_once();
|
console_error_panic_hook::set_once();
|
||||||
|
|
||||||
// Run the app
|
// Run the app
|
||||||
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App));
|
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
struct DogApi {
|
||||||
|
message: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
const ENDPOINT: &str = "https://dog.ceo/api/breeds/image/random/";
|
||||||
|
|
||||||
static App: FC<()> = |cx| {
|
static App: FC<()> = |cx| {
|
||||||
// let mut count = use_state(cx, || 0);
|
// let mut count = use_state(cx, || 0);
|
||||||
let fut = cx.use_hook(
|
|
||||||
move || {
|
|
||||||
Box::pin(async {
|
|
||||||
let mut tick: i32 = 0;
|
|
||||||
log::debug!("yeet!");
|
|
||||||
loop {
|
|
||||||
gloo_timers::future::TimeoutFuture::new(250).await;
|
|
||||||
log::debug!("ticking forward... {}", tick);
|
|
||||||
tick += 1;
|
|
||||||
}
|
|
||||||
}) as Pin<Box<dyn Future<Output = ()> + 'static>>
|
|
||||||
},
|
|
||||||
|h| h,
|
|
||||||
|_| {},
|
|
||||||
);
|
|
||||||
|
|
||||||
cx.submit_task(fut);
|
|
||||||
|
|
||||||
let state = use_state(cx, || 0);
|
let state = use_state(cx, || 0);
|
||||||
|
let set_val = state.setter();
|
||||||
|
|
||||||
|
let g = cx.use_task(|| async move {
|
||||||
|
let mut tick: i32 = 0;
|
||||||
|
log::debug!("yeet!");
|
||||||
|
// loop {
|
||||||
|
// gloo_timers::future::TimeoutFuture::new(250).await;
|
||||||
|
// log::debug!("ticking forward... {}", tick);
|
||||||
|
// tick += 1;
|
||||||
|
// if tick > 10 {
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
set_val(10);
|
||||||
|
surf::get(ENDPOINT).recv_json::<DogApi>().await
|
||||||
|
// String::from("Huzza!")
|
||||||
|
});
|
||||||
|
|
||||||
|
let dog_node = match g.as_ref().and_then(|f| f.as_ref().ok()) {
|
||||||
|
Some(res) => rsx!(in cx, img { src: "{res.message}" }),
|
||||||
|
None => rsx!(in cx, div { "No doggos for you :(" }),
|
||||||
|
};
|
||||||
|
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
div {
|
div {
|
||||||
section { class: "py-12 px-4 text-center"
|
section { class: "py-12 px-4 text-center"
|
||||||
@@ -64,6 +77,10 @@ static App: FC<()> = |cx| {
|
|||||||
"decr"
|
"decr"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
div {
|
||||||
|
h1{"doggo!"}
|
||||||
|
{dog_node}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+47
-34
@@ -62,20 +62,27 @@ impl WebsysRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn run(&mut self) -> dioxus_core::error::Result<()> {
|
pub async fn run(&mut self) -> dioxus_core::error::Result<()> {
|
||||||
let body_element = prepare_websys_dom();
|
use wasm_bindgen::JsCast;
|
||||||
|
|
||||||
let root_node = body_element.first_child().unwrap();
|
let root = prepare_websys_dom();
|
||||||
|
let root_node = root.clone().dyn_into::<Node>().unwrap();
|
||||||
|
|
||||||
let mut websys_dom = crate::new::WebsysDom::new(body_element.clone());
|
let mut websys_dom = crate::new::WebsysDom::new(root.clone());
|
||||||
|
|
||||||
|
websys_dom.stack.push(root_node.clone());
|
||||||
websys_dom.stack.push(root_node);
|
websys_dom.stack.push(root_node);
|
||||||
|
|
||||||
self.internal_dom.rebuild(&mut websys_dom)?;
|
self.internal_dom.rebuild(&mut websys_dom)?;
|
||||||
|
|
||||||
log::info!("Going into event loop");
|
log::info!("Going into event loop");
|
||||||
loop {
|
// loop {
|
||||||
let trigger = {
|
let trigger = {
|
||||||
let real_queue = websys_dom.wait_for_event();
|
let real_queue = websys_dom.wait_for_event();
|
||||||
|
if self.internal_dom.tasks.is_empty() {
|
||||||
|
log::info!("tasks is empty, waiting for dom event to trigger soemthing");
|
||||||
|
real_queue.await
|
||||||
|
} else {
|
||||||
|
log::info!("tasks is not empty, waiting for either tasks or event system");
|
||||||
let task_queue = (&mut self.internal_dom.tasks).next();
|
let task_queue = (&mut self.internal_dom.tasks).next();
|
||||||
|
|
||||||
pin_mut!(real_queue);
|
pin_mut!(real_queue);
|
||||||
@@ -85,26 +92,30 @@ impl WebsysRenderer {
|
|||||||
futures_util::future::Either::Left((trigger, _)) => trigger,
|
futures_util::future::Either::Left((trigger, _)) => trigger,
|
||||||
futures_util::future::Either::Right((trigger, _)) => trigger,
|
futures_util::future::Either::Right((trigger, _)) => trigger,
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(real_trigger) = trigger {
|
||||||
log::info!("event received");
|
log::info!("event received");
|
||||||
let root_node = body_element.first_child().unwrap();
|
// let root_node = body_element.first_child().unwrap();
|
||||||
websys_dom.stack.push(root_node.clone());
|
// websys_dom.stack.push(root_node.clone());
|
||||||
self.internal_dom
|
|
||||||
.progress_with_event(&mut websys_dom, trigger.unwrap())?;
|
|
||||||
|
|
||||||
// let t2 = self.internal_dom.tasks.next();
|
self.internal_dom
|
||||||
// futures::select! {
|
.progress_with_event(&mut websys_dom, real_trigger)?;
|
||||||
// trigger = t1 => {
|
|
||||||
// log::info!("event received");
|
|
||||||
// let root_node = body_element.first_child().unwrap();
|
|
||||||
// websys_dom.stack.push(root_node.clone());
|
|
||||||
// self.internal_dom
|
|
||||||
// .progress_with_event(&mut websys_dom, trigger)?;
|
|
||||||
// },
|
|
||||||
// () = t2 => {}
|
|
||||||
// };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// let t2 = self.internal_dom.tasks.next();
|
||||||
|
// futures::select! {
|
||||||
|
// trigger = t1 => {
|
||||||
|
// log::info!("event received");
|
||||||
|
// let root_node = body_element.first_child().unwrap();
|
||||||
|
// websys_dom.stack.push(root_node.clone());
|
||||||
|
// self.internal_dom
|
||||||
|
// .progress_with_event(&mut websys_dom, trigger)?;
|
||||||
|
// },
|
||||||
|
// () = t2 => {}
|
||||||
|
// };
|
||||||
|
// }
|
||||||
// while let Some(trigger) = websys_dom.wait_for_event().await {
|
// while let Some(trigger) = websys_dom.wait_for_event().await {
|
||||||
// }
|
// }
|
||||||
|
|
||||||
@@ -119,21 +130,23 @@ fn prepare_websys_dom() -> Element {
|
|||||||
let document = window
|
let document = window
|
||||||
.document()
|
.document()
|
||||||
.expect("should have access to the Document");
|
.expect("should have access to the Document");
|
||||||
let body = document.body().unwrap();
|
|
||||||
|
// let body = document.body().unwrap();
|
||||||
|
let el = document.get_element_by_id("dioxusroot").unwrap();
|
||||||
|
|
||||||
// Build a dummy div
|
// Build a dummy div
|
||||||
let container: &Element = body.as_ref();
|
// let container: &Element = body.as_ref();
|
||||||
// container.set_inner_html("");
|
// container.set_inner_html("");
|
||||||
container
|
// container
|
||||||
.append_child(
|
// .append_child(
|
||||||
document
|
// document
|
||||||
.create_element("div")
|
// .create_element("div")
|
||||||
.expect("should create element OK")
|
// .expect("should create element OK")
|
||||||
.as_ref(),
|
// .as_ref(),
|
||||||
)
|
// )
|
||||||
.expect("should append child OK");
|
// .expect("should append child OK");
|
||||||
|
el
|
||||||
container.clone()
|
// container.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Progress the mount of the root component
|
// Progress the mount of the root component
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ impl WebsysDom {
|
|||||||
let mut nodes = slotmap::SlotMap::with_capacity(1000);
|
let mut nodes = slotmap::SlotMap::with_capacity(1000);
|
||||||
|
|
||||||
let root_id = nodes.insert(root.clone().dyn_into::<Node>().unwrap());
|
let root_id = nodes.insert(root.clone().dyn_into::<Node>().unwrap());
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
stack: Stack::with_capacity(10),
|
stack: Stack::with_capacity(10),
|
||||||
nodes,
|
nodes,
|
||||||
@@ -72,9 +73,13 @@ impl WebsysDom {
|
|||||||
|
|
||||||
impl<'a> dioxus_core::diff::RealDom<'a> for WebsysDom {
|
impl<'a> dioxus_core::diff::RealDom<'a> for WebsysDom {
|
||||||
fn push(&mut self, root: RealDomNode) {
|
fn push(&mut self, root: RealDomNode) {
|
||||||
log::debug!("Called [push_root] {:?}", root);
|
log::debug!("Called [push_root] {:#?}", root);
|
||||||
let key: DefaultKey = KeyData::from_ffi(root.0).into();
|
let key: DefaultKey = KeyData::from_ffi(root.0).into();
|
||||||
let domnode = self.nodes.get(key).expect("Failed to pop know root");
|
let domnode = self
|
||||||
|
.nodes
|
||||||
|
.get(key)
|
||||||
|
.expect(&format!("Failed to pop know root: {:#?}", key));
|
||||||
|
|
||||||
self.stack.push(domnode.clone());
|
self.stack.push(domnode.clone());
|
||||||
}
|
}
|
||||||
// drop the node off the stack
|
// drop the node off the stack
|
||||||
@@ -85,10 +90,10 @@ impl<'a> dioxus_core::diff::RealDom<'a> for WebsysDom {
|
|||||||
fn append_children(&mut self, many: u32) {
|
fn append_children(&mut self, many: u32) {
|
||||||
log::debug!("Called [`append_child`]");
|
log::debug!("Called [`append_child`]");
|
||||||
|
|
||||||
let mut root: Node = self
|
let root: Node = self
|
||||||
.stack
|
.stack
|
||||||
.list
|
.list
|
||||||
.get(self.stack.list.len() - many as usize)
|
.get(self.stack.list.len() - (1 + many as usize))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.clone();
|
.clone();
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
use dioxus_core as dioxus;
|
||||||
|
use dioxus_core::prelude::*;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
dioxus_webview::launch(App, |f| f.with_focus().with_maximized(true)).expect("Failed");
|
dioxus_webview::launch(App, |f| f.with_focus().with_maximized(true)).expect("Failed");
|
||||||
}
|
}
|
||||||
@@ -11,8 +14,6 @@ static App: FC<()> = |cx| {
|
|||||||
))
|
))
|
||||||
};
|
};
|
||||||
|
|
||||||
use dioxus_core as dioxus;
|
|
||||||
use dioxus_core::prelude::*;
|
|
||||||
mod dioxus_elements {
|
mod dioxus_elements {
|
||||||
use super::*;
|
use super::*;
|
||||||
pub struct div;
|
pub struct div;
|
||||||
@@ -20,4 +21,5 @@ mod dioxus_elements {
|
|||||||
const TAG_NAME: &'static str = "div";
|
const TAG_NAME: &'static str = "div";
|
||||||
const NAME_SPACE: Option<&'static str> = None;
|
const NAME_SPACE: Option<&'static str> = None;
|
||||||
}
|
}
|
||||||
|
pub trait GlobalAttributes {}
|
||||||
}
|
}
|
||||||
+9
-150
@@ -3,8 +3,12 @@ use std::ops::{Deref, DerefMut};
|
|||||||
use std::sync::mpsc::channel;
|
use std::sync::mpsc::channel;
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, RwLock};
|
||||||
|
|
||||||
use dioxus_core::virtual_dom::VirtualDom;
|
use dioxus_core::*;
|
||||||
use dioxus_core::{prelude::*, serialize::DomEdit};
|
|
||||||
|
use wry::application::event::{Event, WindowEvent};
|
||||||
|
use wry::application::event_loop::{ControlFlow, EventLoop};
|
||||||
|
use wry::application::window::Fullscreen;
|
||||||
|
use wry::webview::WebViewBuilder;
|
||||||
use wry::{
|
use wry::{
|
||||||
application::window::{Window, WindowBuilder},
|
application::window::{Window, WindowBuilder},
|
||||||
webview::{RpcRequest, RpcResponse},
|
webview::{RpcRequest, RpcResponse},
|
||||||
@@ -57,15 +61,6 @@ impl<T: Properties + 'static> WebviewRenderer<T> {
|
|||||||
user_builder: impl FnOnce(WindowBuilder) -> WindowBuilder,
|
user_builder: impl FnOnce(WindowBuilder) -> WindowBuilder,
|
||||||
redits: Option<Vec<DomEdit<'static>>>,
|
redits: Option<Vec<DomEdit<'static>>>,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
use wry::{
|
|
||||||
application::{
|
|
||||||
event::{Event, StartCause, WindowEvent},
|
|
||||||
event_loop::{ControlFlow, EventLoop},
|
|
||||||
window::WindowBuilder,
|
|
||||||
},
|
|
||||||
webview::WebViewBuilder,
|
|
||||||
};
|
|
||||||
|
|
||||||
let event_loop = EventLoop::new();
|
let event_loop = EventLoop::new();
|
||||||
|
|
||||||
let window = user_builder(WindowBuilder::new()).build(&event_loop)?;
|
let window = user_builder(WindowBuilder::new()).build(&event_loop)?;
|
||||||
@@ -78,7 +73,7 @@ impl<T: Properties + 'static> WebviewRenderer<T> {
|
|||||||
|
|
||||||
let webview = WebViewBuilder::new(window)?
|
let webview = WebViewBuilder::new(window)?
|
||||||
.with_url(&format!("data:text/html,{}", HTML_CONTENT))?
|
.with_url(&format!("data:text/html,{}", HTML_CONTENT))?
|
||||||
.with_rpc_handler(move |window: &Window, mut req: RpcRequest| {
|
.with_rpc_handler(move |_window: &Window, mut req: RpcRequest| {
|
||||||
match req.method.as_str() {
|
match req.method.as_str() {
|
||||||
"initiate" => {
|
"initiate" => {
|
||||||
let edits = if let Some(edits) = &redits {
|
let edits = if let Some(edits) = &redits {
|
||||||
@@ -144,45 +139,6 @@ impl<T: Properties + 'static> WebviewRenderer<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// let mut view = web_view::builder()
|
|
||||||
// .invoke_handler(|view, arg| {
|
|
||||||
// let handle = view.handle();
|
|
||||||
// sender
|
|
||||||
// .send(InnerEvent::Initiate(handle))
|
|
||||||
// .expect("should not fail");
|
|
||||||
|
|
||||||
// Ok(())
|
|
||||||
// })
|
|
||||||
// .content(web_view::Content::Html(HTML_CONTENT))
|
|
||||||
// .user_data(())
|
|
||||||
// .title(title)
|
|
||||||
// .size(width, height)
|
|
||||||
// .resizable(resizable)
|
|
||||||
// .debug(debug)
|
|
||||||
// .frameless(frameless)
|
|
||||||
// .visible(visible)
|
|
||||||
// .min_size(min_width, min_height)
|
|
||||||
// .build()
|
|
||||||
// .unwrap();
|
|
||||||
// loop {
|
|
||||||
// view.step()
|
|
||||||
// .expect("should not fail")
|
|
||||||
// .expect("should not fail");
|
|
||||||
// std::thread::sleep(std::time::Duration::from_millis(15));
|
|
||||||
|
|
||||||
// if let Ok(event) = receiver.try_recv() {
|
|
||||||
// if let InnerEvent::Initiate(handle) = event {
|
|
||||||
// let editlist = ref_edits.clone();
|
|
||||||
// handle
|
|
||||||
// .dispatch(move |view| {
|
|
||||||
// let escaped = escape(&editlist);
|
|
||||||
// view.eval(&format!("EditListReceived({});", escaped))
|
|
||||||
// })
|
|
||||||
// .expect("Dispatch failed");
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new text-renderer instance from a functional component root.
|
/// Create a new text-renderer instance from a functional component root.
|
||||||
@@ -200,12 +156,12 @@ impl<T: Properties + 'static> WebviewRenderer<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Pass new args to the root function
|
/// Pass new args to the root function
|
||||||
pub fn update(&mut self, new_val: T) {
|
pub fn update(&mut self, _new_val: T) {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Modify the root function in place, forcing a re-render regardless if the props changed
|
/// Modify the root function in place, forcing a re-render regardless if the props changed
|
||||||
pub fn update_mut(&mut self, modifier: impl Fn(&mut T)) {
|
pub fn update_mut(&mut self, _modifier: impl Fn(&mut T)) {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -221,15 +177,6 @@ struct MessageParameters {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn HANDLER(window: &Window, mut req: RpcRequest) -> Option<RpcResponse> {
|
fn HANDLER(window: &Window, mut req: RpcRequest) -> Option<RpcResponse> {
|
||||||
use wry::{
|
|
||||||
application::{
|
|
||||||
event::{Event, WindowEvent},
|
|
||||||
event_loop::{ControlFlow, EventLoop},
|
|
||||||
window::{Fullscreen, Window, WindowBuilder},
|
|
||||||
},
|
|
||||||
webview::{RpcRequest, RpcResponse, WebViewBuilder},
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut response = None;
|
let mut response = None;
|
||||||
if &req.method == "fullscreen" {
|
if &req.method == "fullscreen" {
|
||||||
if let Some(params) = req.params.take() {
|
if let Some(params) = req.params.take() {
|
||||||
@@ -262,91 +209,3 @@ fn HANDLER(window: &Window, mut req: RpcRequest) -> Option<RpcResponse> {
|
|||||||
|
|
||||||
response
|
response
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct DioxusWebviewBuilder<'a> {
|
|
||||||
pub(crate) title: &'a str,
|
|
||||||
pub(crate) width: i32,
|
|
||||||
pub(crate) height: i32,
|
|
||||||
pub(crate) resizable: bool,
|
|
||||||
pub(crate) debug: bool,
|
|
||||||
pub(crate) frameless: bool,
|
|
||||||
pub(crate) visible: bool,
|
|
||||||
pub(crate) min_width: i32,
|
|
||||||
pub(crate) min_height: i32,
|
|
||||||
}
|
|
||||||
impl<'a> DioxusWebviewBuilder<'a> {
|
|
||||||
fn new() -> Self {
|
|
||||||
#[cfg(debug_assertions)]
|
|
||||||
let debug = true;
|
|
||||||
#[cfg(not(debug_assertions))]
|
|
||||||
let debug = false;
|
|
||||||
|
|
||||||
DioxusWebviewBuilder {
|
|
||||||
title: "Application",
|
|
||||||
width: 800,
|
|
||||||
height: 600,
|
|
||||||
resizable: true,
|
|
||||||
debug,
|
|
||||||
frameless: false,
|
|
||||||
visible: true,
|
|
||||||
min_width: 300,
|
|
||||||
min_height: 300,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Sets the title of the WebView window.
|
|
||||||
///
|
|
||||||
/// Defaults to `"Application"`.
|
|
||||||
pub fn title(mut self, title: &'a str) -> Self {
|
|
||||||
self.title = title;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the size of the WebView window.
|
|
||||||
///
|
|
||||||
/// Defaults to 800 x 600.
|
|
||||||
pub fn size(mut self, width: i32, height: i32) -> Self {
|
|
||||||
self.width = width;
|
|
||||||
self.height = height;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the resizability of the WebView window. If set to false, the window cannot be resized.
|
|
||||||
///
|
|
||||||
/// Defaults to `true`.
|
|
||||||
pub fn resizable(mut self, resizable: bool) -> Self {
|
|
||||||
self.resizable = resizable;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Enables or disables debug mode.
|
|
||||||
///
|
|
||||||
/// Defaults to `true` for debug builds, `false` for release builds.
|
|
||||||
pub fn debug(mut self, debug: bool) -> Self {
|
|
||||||
self.debug = debug;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
/// The window crated will be frameless
|
|
||||||
///
|
|
||||||
/// defaults to `false`
|
|
||||||
pub fn frameless(mut self, frameless: bool) -> Self {
|
|
||||||
self.frameless = frameless;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the visibility of the WebView window.
|
|
||||||
///
|
|
||||||
/// defaults to `true`
|
|
||||||
pub fn visible(mut self, visible: bool) -> Self {
|
|
||||||
self.visible = visible;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the minimum size of the WebView window.
|
|
||||||
///
|
|
||||||
/// Defaults to 300 x 300.
|
|
||||||
pub fn min_size(mut self, width: i32, height: i32) -> Self {
|
|
||||||
self.min_width = width;
|
|
||||||
self.min_height = height;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -188,6 +188,7 @@ pub use dioxus_webview as desktop;
|
|||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
//! A glob import that includes helper types like FC, rsx!, html!, and required traits
|
//! A glob import that includes helper types like FC, rsx!, html!, and required traits
|
||||||
pub use dioxus_core::prelude::*;
|
pub use dioxus_core::prelude::*;
|
||||||
|
pub use dioxus_elements::GlobalAttributes;
|
||||||
pub use dioxus_hooks::*;
|
pub use dioxus_hooks::*;
|
||||||
pub use dioxus_html as dioxus_elements;
|
pub use dioxus_html as dioxus_elements;
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user