Feat: major overhaul to diffing
This commit is contained in:
1 parent
c809095124
commit
9810feebf5
37 files changed
+1470
-1129
No files matched your search
+14
-5
@@ -1,18 +1,28 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
|
# members = ["packages/core-macro"]
|
||||||
members = [
|
members = [
|
||||||
"packages/dioxus",
|
|
||||||
"packages/core-macro",
|
"packages/core-macro",
|
||||||
"packages/core",
|
"packages/core",
|
||||||
"packages/web",
|
"packages/web",
|
||||||
"packages/docsite",
|
"packages/webview",
|
||||||
"packages/ssr",
|
"packages/dioxus",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# "packages/docsite",
|
||||||
|
# "packages/ssr",
|
||||||
|
# "packages/cli",
|
||||||
|
# "packages/webview",
|
||||||
|
# "packages/hooks",
|
||||||
|
# "packages/ios",
|
||||||
|
|
||||||
|
[profile.dev]
|
||||||
|
split-debuginfo = "unpacked"
|
||||||
|
|
||||||
# "packages/liveview",
|
# "packages/liveview",
|
||||||
# "packages/3d",
|
# "packages/3d",
|
||||||
|
|
||||||
# Built-in
|
# Built-in
|
||||||
# "packages/webview/client",
|
# "packages/webview/client",
|
||||||
# "packages/webview",
|
|
||||||
# "packages/router",
|
# "packages/router",
|
||||||
# "packages/webview",
|
# "packages/webview",
|
||||||
# "packages/livehost",
|
# "packages/livehost",
|
||||||
@@ -22,7 +32,6 @@ members = [
|
|||||||
# "packages/macro",
|
# "packages/macro",
|
||||||
# TODO @Jon, share the validation code
|
# TODO @Jon, share the validation code
|
||||||
# "packages/web",
|
# "packages/web",
|
||||||
# "packages/hooks",
|
|
||||||
# "packages/cli",
|
# "packages/cli",
|
||||||
# "examples",
|
# "examples",
|
||||||
# "packages/html-macro",
|
# "packages/html-macro",
|
||||||
|
|||||||
+7
-6
@@ -24,7 +24,7 @@ Welcome to the first iteration of the Dioxus Virtual DOM! This release brings su
|
|||||||
> Make it easier to write components
|
> Make it easier to write components
|
||||||
- [x] (Macro) Tweak event syntax to not be dependent on wasm32 target (just return regular closures which get boxed/alloced)
|
- [x] (Macro) Tweak event syntax to not be dependent on wasm32 target (just return regular closures which get boxed/alloced)
|
||||||
- [x] (Macro) Tweak component syntax to accept a new custom element
|
- [x] (Macro) Tweak component syntax to accept a new custom element
|
||||||
- [ ] (Macro) Allow components to specify their props as function args (not going to do)
|
- [ ] (Macro) Allow components to specify their props as function args
|
||||||
|
|
||||||
## Project: Hooks + Context + Subscriptions (TBD)
|
## Project: Hooks + Context + Subscriptions (TBD)
|
||||||
> Implement the foundations for state management
|
> Implement the foundations for state management
|
||||||
@@ -80,20 +80,21 @@ Welcome to the first iteration of the Dioxus Virtual DOM! This release brings su
|
|||||||
- [x] Allow paths for components
|
- [x] Allow paths for components
|
||||||
- [x] todo mvc
|
- [x] todo mvc
|
||||||
- [ ] Make events lazy (use traits + Box<dyn>)
|
- [ ] Make events lazy (use traits + Box<dyn>)
|
||||||
- [ ] attrs on elements should implement format_args
|
- [ ] Attributes on elements should implement format_args
|
||||||
- [ ] Beef up the dioxus CLI tool
|
- [ ] Beef up the dioxus CLI tool
|
||||||
- [ ] Tweak macro parsing for better errors
|
- [ ] Tweak macro parsing for better errors
|
||||||
- [ ] make ssr follow HTML spec (ish)
|
- [ ] make SSR follow HTML spec
|
||||||
- [ ] dirty tagging, compression
|
- [ ] dirty tagging, compression
|
||||||
- [ ] fix keys on elements
|
- [ ] fix keys on elements
|
||||||
- [ ] miri tests
|
- [ ] MIRI tests
|
||||||
- [ ] code health
|
- [ ] code health
|
||||||
- [ ] all synthetic events filled out
|
- [ ] all synthetic events filled out
|
||||||
- [ ] doublecheck event targets and stuff
|
- [ ] double check event targets and stuff
|
||||||
- [ ] Documentation overhaul
|
- [ ] Documentation overhaul
|
||||||
- [ ] Website
|
- [ ] Website
|
||||||
|
= [ ] controlled components
|
||||||
|
|
||||||
lower priorty features
|
lower priority features
|
||||||
- [ ] fragments
|
- [ ] fragments
|
||||||
- [ ] node refs (postpone for future release?)
|
- [ ] node refs (postpone for future release?)
|
||||||
- [ ] styling built-in (future release?)
|
- [ ] styling built-in (future release?)
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ description = "CLI tool for developing, testing, and publishing Dioxus apps"
|
|||||||
thiserror = "1.0.23"
|
thiserror = "1.0.23"
|
||||||
log = "0.4.13"
|
log = "0.4.13"
|
||||||
fern = { version = "0.6.0", features = ["colored"] }
|
fern = { version = "0.6.0", features = ["colored"] }
|
||||||
wasm-bindgen-cli-support = "0.2.71"
|
wasm-bindgen-cli-support = "0.2.73"
|
||||||
anyhow = "1.0.38"
|
anyhow = "1.0.38"
|
||||||
argh = "0.1.4"
|
argh = "0.1.4"
|
||||||
serde = "1.0.120"
|
serde = "1.0.120"
|
||||||
|
|||||||
+205
-191
@@ -1,5 +1,7 @@
|
|||||||
use syn::parse::{discouraged::Speculative, ParseBuffer};
|
use syn::parse::{discouraged::Speculative, ParseBuffer};
|
||||||
|
|
||||||
|
use crate::util::is_valid_html_tag;
|
||||||
|
|
||||||
use {
|
use {
|
||||||
proc_macro::TokenStream,
|
proc_macro::TokenStream,
|
||||||
proc_macro2::{Span, TokenStream as TokenStream2},
|
proc_macro2::{Span, TokenStream as TokenStream2},
|
||||||
@@ -15,77 +17,105 @@ use {
|
|||||||
// Parse any stream coming from the rsx! macro
|
// Parse any stream coming from the rsx! macro
|
||||||
// ==============================================
|
// ==============================================
|
||||||
pub struct RsxRender {
|
pub struct RsxRender {
|
||||||
custom_context: Option<Ident>,
|
// custom_context: Option<Ident>,
|
||||||
root: RootOption,
|
root: AmbiguousElement,
|
||||||
}
|
|
||||||
|
|
||||||
enum RootOption {
|
|
||||||
// for lists of components
|
|
||||||
Fragment(),
|
|
||||||
Element(Element),
|
|
||||||
Component(Component),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToTokens for RootOption {
|
|
||||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
|
||||||
match self {
|
|
||||||
RootOption::Fragment() => todo!(),
|
|
||||||
RootOption::Element(el) => el.to_tokens(tokens),
|
|
||||||
RootOption::Component(comp) => comp.to_tokens(tokens),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parse for RsxRender {
|
impl Parse for RsxRender {
|
||||||
fn parse(input: ParseStream) -> Result<Self> {
|
fn parse(input: ParseStream) -> Result<Self> {
|
||||||
let fork = input.fork();
|
let root = { input.parse::<AmbiguousElement>() }?;
|
||||||
|
if !input.is_empty() {
|
||||||
|
return Err(Error::new(
|
||||||
|
input.span(),
|
||||||
|
"Currently only one element is allowed per component",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
let custom_context = fork
|
Ok(Self { root })
|
||||||
.parse::<Ident>()
|
|
||||||
.and_then(|ident| {
|
|
||||||
fork.parse::<Token![,]>().map(|_| {
|
|
||||||
input.advance_to(&fork);
|
|
||||||
ident
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
|
|
||||||
let forked = input.fork();
|
|
||||||
let name = forked.parse::<Ident>()?;
|
|
||||||
|
|
||||||
let root = match crate::util::is_valid_tag(&name.to_string()) {
|
|
||||||
true => input.parse::<Element>().map(|el| RootOption::Element(el)),
|
|
||||||
false => input.parse::<Component>().map(|c| RootOption::Component(c)),
|
|
||||||
}?;
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
root,
|
|
||||||
custom_context,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToTokens for RsxRender {
|
impl ToTokens for RsxRender {
|
||||||
fn to_tokens(&self, out_tokens: &mut TokenStream2) {
|
fn to_tokens(&self, out_tokens: &mut TokenStream2) {
|
||||||
let new_toks = (&self.root).to_token_stream();
|
|
||||||
|
|
||||||
// create a lazy tree that accepts a bump allocator
|
// create a lazy tree that accepts a bump allocator
|
||||||
let final_tokens = match &self.custom_context {
|
// Currently disabled
|
||||||
Some(ident) => quote! {
|
//
|
||||||
#ident.render(dioxus::prelude::LazyNodes::new(move |ctx|{
|
// let final_tokens = match &self.custom_context {
|
||||||
let bump = ctx.bump;
|
// Some(ident) => quote! {
|
||||||
#new_toks
|
// #ident.render(dioxus::prelude::LazyNodes::new(move |ctx|{
|
||||||
}))
|
// let bump = ctx.bump;
|
||||||
},
|
// #new_toks
|
||||||
None => quote! {
|
// }))
|
||||||
dioxus::prelude::LazyNodes::new(move |ctx|{
|
// },
|
||||||
let bump = ctx.bump;
|
|
||||||
#new_toks
|
|
||||||
})
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
final_tokens.to_tokens(out_tokens);
|
let inner = &self.root;
|
||||||
|
let output = quote! {
|
||||||
|
dioxus::prelude::LazyNodes::new(move |ctx|{
|
||||||
|
let bump = ctx.bump;
|
||||||
|
#inner
|
||||||
|
})
|
||||||
|
};
|
||||||
|
output.to_tokens(out_tokens)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum AmbiguousElement {
|
||||||
|
Element(Element),
|
||||||
|
Component(Component),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for AmbiguousElement {
|
||||||
|
fn parse(input: ParseStream) -> Result<Self> {
|
||||||
|
// Try to parse as an absolute path and immediately defer to the componetn
|
||||||
|
if input.peek(Token![::]) {
|
||||||
|
return input
|
||||||
|
.parse::<Component>()
|
||||||
|
.map(|c| AmbiguousElement::Component(c));
|
||||||
|
}
|
||||||
|
|
||||||
|
// If not an absolute path, then parse the ident and check if it's a valid tag
|
||||||
|
|
||||||
|
if let Ok(pat) = input.fork().parse::<syn::Path>() {
|
||||||
|
if pat.segments.len() > 1 {
|
||||||
|
return input
|
||||||
|
.parse::<Component>()
|
||||||
|
.map(|c| AmbiguousElement::Component(c));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(name) = input.fork().parse::<Ident>() {
|
||||||
|
let name_str = name.to_string();
|
||||||
|
|
||||||
|
match is_valid_html_tag(&name_str) {
|
||||||
|
true => input
|
||||||
|
.parse::<Element>()
|
||||||
|
.map(|c| AmbiguousElement::Element(c)),
|
||||||
|
false => {
|
||||||
|
let first_char = name_str.chars().next().unwrap();
|
||||||
|
if first_char.is_ascii_uppercase() {
|
||||||
|
input
|
||||||
|
.parse::<Component>()
|
||||||
|
.map(|c| AmbiguousElement::Component(c))
|
||||||
|
} else {
|
||||||
|
Err(Error::new(
|
||||||
|
name.span(),
|
||||||
|
"Components must be uppercased, perhaps you mispelled a html tag",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(Error::new(input.span(), "Not a valid Html tag"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for AmbiguousElement {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||||
|
match self {
|
||||||
|
AmbiguousElement::Element(el) => el.to_tokens(tokens),
|
||||||
|
AmbiguousElement::Component(comp) => comp.to_tokens(tokens),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,9 +123,8 @@ impl ToTokens for RsxRender {
|
|||||||
// Parse any div {} as a VElement
|
// Parse any div {} as a VElement
|
||||||
// ==============================================
|
// ==============================================
|
||||||
enum Node {
|
enum Node {
|
||||||
Element(Element),
|
Element(AmbiguousElement),
|
||||||
Text(TextNode),
|
Text(TextNode),
|
||||||
Component(Component),
|
|
||||||
RawExpr(Expr),
|
RawExpr(Expr),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,7 +133,6 @@ impl ToTokens for &Node {
|
|||||||
match &self {
|
match &self {
|
||||||
Node::Element(el) => el.to_tokens(tokens),
|
Node::Element(el) => el.to_tokens(tokens),
|
||||||
Node::Text(txt) => txt.to_tokens(tokens),
|
Node::Text(txt) => txt.to_tokens(tokens),
|
||||||
Node::Component(c) => c.to_tokens(tokens),
|
|
||||||
Node::RawExpr(exp) => exp.to_tokens(tokens),
|
Node::RawExpr(exp) => exp.to_tokens(tokens),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -115,31 +143,17 @@ impl Parse for Node {
|
|||||||
// Supposedly this approach is discouraged due to inability to return proper errors
|
// Supposedly this approach is discouraged due to inability to return proper errors
|
||||||
// TODO: Rework this to provide more informative errors
|
// TODO: Rework this to provide more informative errors
|
||||||
|
|
||||||
let fork = stream.fork();
|
if stream.peek(token::Brace) {
|
||||||
if let Ok(text) = fork.parse::<TextNode>() {
|
let content;
|
||||||
stream.advance_to(&fork);
|
syn::braced!(content in stream);
|
||||||
return Ok(Self::Text(text));
|
return Ok(Node::RawExpr(content.parse::<Expr>()?));
|
||||||
}
|
}
|
||||||
|
|
||||||
let fork = stream.fork();
|
if stream.peek(LitStr) {
|
||||||
if let Ok(element) = fork.parse::<Element>() {
|
return Ok(Node::Text(stream.parse::<TextNode>()?));
|
||||||
stream.advance_to(&fork);
|
|
||||||
return Ok(Self::Element(element));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let fork = stream.fork();
|
Ok(Node::Element(stream.parse::<AmbiguousElement>()?))
|
||||||
if let Ok(comp) = fork.parse::<Component>() {
|
|
||||||
stream.advance_to(&fork);
|
|
||||||
return Ok(Self::Component(comp));
|
|
||||||
}
|
|
||||||
|
|
||||||
let fork = stream.fork();
|
|
||||||
if let Ok(tok) = try_parse_bracketed(&fork) {
|
|
||||||
stream.advance_to(&fork);
|
|
||||||
return Ok(Node::RawExpr(tok));
|
|
||||||
}
|
|
||||||
|
|
||||||
return Err(Error::new(stream.span(), "Failed to parse as a valid node"));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,6 +170,7 @@ impl Parse for Component {
|
|||||||
// todo: look into somehow getting the crate/super/etc
|
// todo: look into somehow getting the crate/super/etc
|
||||||
|
|
||||||
let name = syn::Path::parse_mod_style(s)?;
|
let name = syn::Path::parse_mod_style(s)?;
|
||||||
|
|
||||||
// parse the guts
|
// parse the guts
|
||||||
let content: ParseBuffer;
|
let content: ParseBuffer;
|
||||||
syn::braced!(content in s);
|
syn::braced!(content in s);
|
||||||
@@ -169,9 +184,7 @@ impl Parse for Component {
|
|||||||
break 'parsing;
|
break 'parsing;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Ok(field) = content.parse::<ComponentField>() {
|
body.push(content.parse::<ComponentField>()?);
|
||||||
body.push(field);
|
|
||||||
}
|
|
||||||
|
|
||||||
// consume comma if it exists
|
// consume comma if it exists
|
||||||
// we don't actually care if there *are* commas between attrs
|
// we don't actually care if there *are* commas between attrs
|
||||||
@@ -228,6 +241,7 @@ impl ToTokens for &Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// the struct's fields info
|
// the struct's fields info
|
||||||
pub struct ComponentField {
|
pub struct ComponentField {
|
||||||
name: Ident,
|
name: Ident,
|
||||||
@@ -258,10 +272,56 @@ impl ToTokens for &ComponentField {
|
|||||||
// =======================================
|
// =======================================
|
||||||
struct Element {
|
struct Element {
|
||||||
name: Ident,
|
name: Ident,
|
||||||
attrs: Vec<Attr>,
|
attrs: Vec<ElementAttr>,
|
||||||
children: Vec<Node>,
|
children: Vec<Node>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Parse for Element {
|
||||||
|
fn parse(stream: ParseStream) -> Result<Self> {
|
||||||
|
//
|
||||||
|
let name = Ident::parse(stream)?;
|
||||||
|
|
||||||
|
if !crate::util::is_valid_html_tag(&name.to_string()) {
|
||||||
|
return Err(Error::new(name.span(), "Not a valid Html tag"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse the guts
|
||||||
|
let content: ParseBuffer;
|
||||||
|
syn::braced!(content in stream);
|
||||||
|
|
||||||
|
let mut attrs: Vec<ElementAttr> = vec![];
|
||||||
|
let mut children: Vec<Node> = vec![];
|
||||||
|
'parsing: loop {
|
||||||
|
// [1] Break if empty
|
||||||
|
if content.is_empty() {
|
||||||
|
break 'parsing;
|
||||||
|
}
|
||||||
|
|
||||||
|
let forked = content.fork();
|
||||||
|
if forked.call(Ident::parse_any).is_ok()
|
||||||
|
&& forked.parse::<Token![:]>().is_ok()
|
||||||
|
&& forked.parse::<Expr>().is_ok()
|
||||||
|
{
|
||||||
|
attrs.push(content.parse::<ElementAttr>()?);
|
||||||
|
} else {
|
||||||
|
children.push(content.parse::<Node>()?);
|
||||||
|
}
|
||||||
|
|
||||||
|
// consume comma if it exists
|
||||||
|
// we don't actually care if there *are* commas after elements/text
|
||||||
|
if content.peek(Token![,]) {
|
||||||
|
let _ = content.parse::<Token![,]>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
name,
|
||||||
|
attrs,
|
||||||
|
children,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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.to_string();
|
let name = &self.name.to_string();
|
||||||
@@ -288,86 +348,22 @@ impl ToTokens for &Element {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parse for Element {
|
|
||||||
fn parse(s: ParseStream) -> Result<Self> {
|
|
||||||
let name = Ident::parse_any(s)?;
|
|
||||||
|
|
||||||
if !crate::util::is_valid_tag(&name.to_string()) {
|
|
||||||
return Err(Error::new(name.span(), "Not a valid Html tag"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse the guts
|
|
||||||
let content: ParseBuffer;
|
|
||||||
syn::braced!(content in s);
|
|
||||||
|
|
||||||
let mut attrs: Vec<Attr> = vec![];
|
|
||||||
let mut children: Vec<Node> = vec![];
|
|
||||||
parse_element_content(content, &mut attrs, &mut children)?;
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
name,
|
|
||||||
attrs,
|
|
||||||
children,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// used by both vcomponet and velement to parse the contents of the elements into attras and children
|
|
||||||
fn parse_element_content(
|
|
||||||
stream: ParseBuffer,
|
|
||||||
attrs: &mut Vec<Attr>,
|
|
||||||
children: &mut Vec<Node>,
|
|
||||||
) -> Result<()> {
|
|
||||||
'parsing: loop {
|
|
||||||
// consume comma if it exists
|
|
||||||
// we don't actually care if there *are* commas after elements/text
|
|
||||||
if stream.peek(Token![,]) {
|
|
||||||
let _ = stream.parse::<Token![,]>();
|
|
||||||
}
|
|
||||||
|
|
||||||
// [1] Break if empty
|
|
||||||
if stream.is_empty() {
|
|
||||||
break 'parsing;
|
|
||||||
}
|
|
||||||
|
|
||||||
// [2] Try to consume an attr
|
|
||||||
let fork = stream.fork();
|
|
||||||
if let Ok(attr) = fork.parse::<Attr>() {
|
|
||||||
// make sure to advance or your computer will become a space heater :)
|
|
||||||
stream.advance_to(&fork);
|
|
||||||
attrs.push(attr);
|
|
||||||
continue 'parsing;
|
|
||||||
}
|
|
||||||
|
|
||||||
// [3] Try to consume a child node
|
|
||||||
let fork = stream.fork();
|
|
||||||
if let Ok(node) = fork.parse::<Node>() {
|
|
||||||
// make sure to advance or your computer will become a space heater :)
|
|
||||||
stream.advance_to(&fork);
|
|
||||||
children.push(node);
|
|
||||||
continue 'parsing;
|
|
||||||
}
|
|
||||||
|
|
||||||
// todo: pass a descriptive error onto the offending tokens
|
|
||||||
panic!("Entry is not an attr or element\n {}", stream)
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
fn try_parse_bracketed(stream: &ParseBuffer) -> Result<Expr> {
|
|
||||||
let content;
|
|
||||||
syn::braced!(content in stream);
|
|
||||||
content.parse()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// =======================================
|
/// =======================================
|
||||||
/// Parse a VElement's Attributes
|
/// Parse a VElement's Attributes
|
||||||
/// =======================================
|
/// =======================================
|
||||||
struct Attr {
|
struct ElementAttr {
|
||||||
name: Ident,
|
name: Ident,
|
||||||
ty: AttrType,
|
ty: AttrType,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parse for Attr {
|
enum AttrType {
|
||||||
|
Value(LitStr),
|
||||||
|
FieldTokens(Expr),
|
||||||
|
EventTokens(Expr),
|
||||||
|
Event(ExprClosure),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for ElementAttr {
|
||||||
fn parse(s: ParseStream) -> Result<Self> {
|
fn parse(s: ParseStream) -> Result<Self> {
|
||||||
let mut name = Ident::parse_any(s)?;
|
let mut name = Ident::parse_any(s)?;
|
||||||
let name_str = name.to_string();
|
let name_str = name.to_string();
|
||||||
@@ -389,32 +385,39 @@ impl Parse for Attr {
|
|||||||
content.advance_to(&fork);
|
content.advance_to(&fork);
|
||||||
AttrType::Event(event)
|
AttrType::Event(event)
|
||||||
} else {
|
} else {
|
||||||
AttrType::Tok(content.parse()?)
|
AttrType::EventTokens(content.parse()?)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
AttrType::Event(s.parse()?)
|
AttrType::Event(s.parse()?)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let lit_str = if name_str == "style" && s.peek(token::Brace) {
|
let fork = s.fork();
|
||||||
// special-case to deal with literal styles.
|
if let Ok(rawtext) = fork.parse::<LitStr>() {
|
||||||
let outer;
|
s.advance_to(&fork);
|
||||||
syn::braced!(outer in s);
|
AttrType::Value(rawtext)
|
||||||
// double brace for inline style.
|
|
||||||
// todo!("Style support not ready yet");
|
|
||||||
|
|
||||||
// if outer.peek(token::Brace) {
|
|
||||||
// let inner;
|
|
||||||
// syn::braced!(inner in outer);
|
|
||||||
// let styles: Styles = inner.parse()?;
|
|
||||||
// MaybeExpr::Literal(LitStr::new(&styles.to_string(), Span::call_site()))
|
|
||||||
// } else {
|
|
||||||
// just parse as an expression
|
|
||||||
outer.parse()?
|
|
||||||
// }
|
|
||||||
} else {
|
} else {
|
||||||
s.parse()?
|
let toks = s.parse::<Expr>()?;
|
||||||
};
|
AttrType::FieldTokens(toks)
|
||||||
AttrType::Value(lit_str)
|
}
|
||||||
|
// let lit_str = if name_str == "style" && s.peek(token::Brace) {
|
||||||
|
// // special-case to deal with literal styles.
|
||||||
|
// let outer;
|
||||||
|
// syn::braced!(outer in s);
|
||||||
|
// // double brace for inline style.
|
||||||
|
// // todo!("Style support not ready yet");
|
||||||
|
|
||||||
|
// // if outer.peek(token::Brace) {
|
||||||
|
// // let inner;
|
||||||
|
// // syn::braced!(inner in outer);
|
||||||
|
// // let styles: Styles = inner.parse()?;
|
||||||
|
// // MaybeExpr::Literal(LitStr::new(&styles.to_string(), Span::call_site()))
|
||||||
|
// // } else {
|
||||||
|
// // just parse as an expression
|
||||||
|
// outer.parse()?
|
||||||
|
// // }
|
||||||
|
// } else {
|
||||||
|
// s.parse()?
|
||||||
|
// };
|
||||||
};
|
};
|
||||||
|
|
||||||
// consume comma if it exists
|
// consume comma if it exists
|
||||||
@@ -423,11 +426,11 @@ impl Parse for Attr {
|
|||||||
let _ = s.parse::<Token![,]>();
|
let _ = s.parse::<Token![,]>();
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Attr { name, ty })
|
Ok(ElementAttr { name, ty })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToTokens for &Attr {
|
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 name = self.name.to_string();
|
||||||
let nameident = &self.name;
|
let nameident = &self.name;
|
||||||
@@ -435,7 +438,12 @@ impl ToTokens for &Attr {
|
|||||||
match &self.ty {
|
match &self.ty {
|
||||||
AttrType::Value(value) => {
|
AttrType::Value(value) => {
|
||||||
tokens.append_all(quote! {
|
tokens.append_all(quote! {
|
||||||
.attr(#name, #value)
|
.attr(#name, {
|
||||||
|
use bumpalo::core_alloc::fmt::Write;
|
||||||
|
let mut s = bumpalo::collections::String::new_in(bump);
|
||||||
|
s.write_fmt(format_args_f!(#value)).unwrap();
|
||||||
|
s.into_bump_str()
|
||||||
|
})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
AttrType::Event(event) => {
|
AttrType::Event(event) => {
|
||||||
@@ -443,21 +451,21 @@ impl ToTokens for &Attr {
|
|||||||
.add_listener(dioxus::events::on::#nameident(ctx, #event))
|
.add_listener(dioxus::events::on::#nameident(ctx, #event))
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
AttrType::Tok(exp) => {
|
AttrType::FieldTokens(exp) => {
|
||||||
tokens.append_all(quote! {
|
tokens.append_all(quote! {
|
||||||
.add_listener(dioxus::events::on::#nameident(ctx, #exp))
|
.attr(#name, #exp)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
AttrType::EventTokens(event) => {
|
||||||
|
//
|
||||||
|
tokens.append_all(quote! {
|
||||||
|
.add_listener(dioxus::events::on::#nameident(ctx, #event))
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum AttrType {
|
|
||||||
Value(LitStr),
|
|
||||||
Event(ExprClosure),
|
|
||||||
Tok(Expr),
|
|
||||||
}
|
|
||||||
|
|
||||||
// =======================================
|
// =======================================
|
||||||
// Parse just plain text
|
// Parse just plain text
|
||||||
// =======================================
|
// =======================================
|
||||||
@@ -483,3 +491,9 @@ impl ToTokens for TextNode {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn try_parse_bracketed(stream: &ParseBuffer) -> Result<Expr> {
|
||||||
|
let content;
|
||||||
|
syn::braced!(content in stream);
|
||||||
|
content.parse()
|
||||||
|
}
|
||||||
@@ -117,6 +117,9 @@ static VALID_TAGS: Lazy<HashSet<&'static str>> = Lazy::new(|| {
|
|||||||
"var",
|
"var",
|
||||||
"video",
|
"video",
|
||||||
"wbr",
|
"wbr",
|
||||||
|
// SVTG
|
||||||
|
"svg",
|
||||||
|
"path",
|
||||||
]
|
]
|
||||||
.iter()
|
.iter()
|
||||||
.cloned()
|
.cloned()
|
||||||
@@ -132,6 +135,6 @@ static VALID_TAGS: Lazy<HashSet<&'static str>> = Lazy::new(|| {
|
|||||||
///
|
///
|
||||||
/// assert_eq!(is_valid_tag("random"), false);
|
/// assert_eq!(is_valid_tag("random"), false);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn is_valid_tag(tag: &str) -> bool {
|
pub fn is_valid_html_tag(tag: &str) -> bool {
|
||||||
VALID_TAGS.contains(tag)
|
VALID_TAGS.contains(tag)
|
||||||
}
|
}
|
||||||
@@ -34,4 +34,4 @@ log = "0.4.14"
|
|||||||
serde = { version = "1.0.123", features = ["derive"], optional = true }
|
serde = { version = "1.0.123", features = ["derive"], optional = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = ["serde"]
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
fn main() {}
|
fn main() {}
|
||||||
|
|
||||||
use std::borrow::Borrow;
|
use std::{borrow::Borrow, rc::Rc};
|
||||||
|
|
||||||
use dioxus_core::prelude::*;
|
use dioxus_core::prelude::*;
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ fn app<'a>(ctx: Context<'a>, props: &Props) -> DomTree {
|
|||||||
// create the props with nothing but the fc<T>
|
// create the props with nothing but the fc<T>
|
||||||
fc_to_builder(ChildItem)
|
fc_to_builder(ChildItem)
|
||||||
.item(child)
|
.item(child)
|
||||||
.item_handler(set_val)
|
.item_handler(set_val.clone())
|
||||||
.build(),
|
.build(),
|
||||||
None,
|
None,
|
||||||
));
|
));
|
||||||
@@ -52,7 +52,7 @@ struct ChildProps<'a> {
|
|||||||
item: &'a ListItem,
|
item: &'a ListItem,
|
||||||
|
|
||||||
// Even pass down handlers!
|
// Even pass down handlers!
|
||||||
item_handler: &'a dyn Fn(i32),
|
item_handler: Rc<dyn Fn(i32)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for ChildProps<'_> {
|
impl PartialEq for ChildProps<'_> {
|
||||||
|
|||||||
@@ -1,102 +0,0 @@
|
|||||||
// #![allow(unused, non_upper_case_globals)]
|
|
||||||
// use bumpalo::Bump;
|
|
||||||
// use dioxus_core::nodebuilder::*;
|
|
||||||
// use dioxus_core::prelude::VNode;
|
|
||||||
// use dioxus_core::prelude::*;
|
|
||||||
// use once_cell::sync::{Lazy, OnceCell};
|
|
||||||
|
|
||||||
use std::ops::Deref;
|
|
||||||
|
|
||||||
/*
|
|
||||||
A guard over underlying T that provides access in callbacks via "Copy"
|
|
||||||
*/
|
|
||||||
|
|
||||||
// #[derive(Clone)]
|
|
||||||
struct ContextGuard2<T> {
|
|
||||||
_val: std::marker::PhantomData<T>,
|
|
||||||
}
|
|
||||||
impl<T> Clone for ContextGuard2<T> {
|
|
||||||
// we aren't cloning the underlying data so clone isn't necessary
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<T> Copy for ContextGuard2<T> {}
|
|
||||||
|
|
||||||
impl<T> ContextGuard2<T> {
|
|
||||||
fn get<'a>(&'a self) -> ContextLock<'a, T> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ContextLock<'a, T> {
|
|
||||||
_val: std::marker::PhantomData<&'a T>,
|
|
||||||
}
|
|
||||||
impl<'a, T: 'a + 'static> Deref for ContextLock<'a, T> {
|
|
||||||
type Target = T;
|
|
||||||
|
|
||||||
fn deref<'b>(&'b self) -> &'b T {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
The source of the data that gives out context guards
|
|
||||||
*/
|
|
||||||
struct Context<'a> {
|
|
||||||
_p: std::marker::PhantomData<&'a ()>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Context<'a> {
|
|
||||||
fn use_context<'b, I, O: 'b>(&self, _f: fn(&'b I) -> O) -> ContextGuard2<O> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn add_listener(&self, _f: impl Fn(()) + 'a) {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render(self, _f: impl FnOnce(&'a String) + 'a) {}
|
|
||||||
// fn view(self, f: impl for<'b> FnOnce(&'a String) + 'a) {}
|
|
||||||
// fn view(self, f: impl for<'b> FnOnce(&'b String) + 'a) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Example {
|
|
||||||
value: String,
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
Example compiling
|
|
||||||
*/
|
|
||||||
fn t<'a>(ctx: Context<'a>) {
|
|
||||||
let value = ctx.use_context(|b: &Example| &b.value);
|
|
||||||
|
|
||||||
// Works properly, value is moved by copy into the closure
|
|
||||||
let refed = value.get();
|
|
||||||
println!("Value is {}", refed.as_str());
|
|
||||||
let r2 = refed.as_str();
|
|
||||||
|
|
||||||
ctx.add_listener(move |_| {
|
|
||||||
// let val = value.get().as_str();
|
|
||||||
let _val2 = r2.as_bytes();
|
|
||||||
println!("v2 is {}", r2);
|
|
||||||
// println!("refed is {}", refed);
|
|
||||||
});
|
|
||||||
|
|
||||||
// let refed = value.deref();
|
|
||||||
// returns &String
|
|
||||||
|
|
||||||
// returns &String
|
|
||||||
// let refed = value.deref(); // returns &String
|
|
||||||
// let refed = value.deref(); // returns &String
|
|
||||||
|
|
||||||
// Why does this work? This closure should be static but is holding a reference to refed
|
|
||||||
// The context guard is meant to prevent any references moving into the closure
|
|
||||||
// if the references move they might become invalid due to mutlithreading issues
|
|
||||||
ctx.add_listener(move |_| {
|
|
||||||
// let val = value.as_str();
|
|
||||||
// let val2 = refed.as_bytes();
|
|
||||||
});
|
|
||||||
|
|
||||||
ctx.render(move |_b| {});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {}
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
// #[macro_use]
|
|
||||||
|
|
||||||
// use dioxus_core::ifmt;
|
|
||||||
// use fstrings::format_args_f;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let bump = bumpalo::Bump::new();
|
|
||||||
let _b = ≎
|
|
||||||
let _world = "123";
|
|
||||||
// dioxus_core::ifmt!(in b; "Hello {world}";);
|
|
||||||
}
|
|
||||||
|
|
||||||
// let mut s = bumpalo::collections::String::new_in(b);
|
|
||||||
// fstrings::write_f!(s, "Hello {world}");
|
|
||||||
// dbg!(s);
|
|
||||||
// let p = {
|
|
||||||
// println!("hello, {}", &world);
|
|
||||||
// ()
|
|
||||||
// };
|
|
||||||
// let g = format_args!("hello {world}", world = world);
|
|
||||||
// let g = dioxus_core::ifmt!(in b, "Hello {world}");
|
|
||||||
// let g = ifmt!(in b, "hhello {world}");
|
|
||||||
// let g = ::core::fmt::Arguments::new_v1(
|
|
||||||
// &["hello "],
|
|
||||||
// &match (&world,) {
|
|
||||||
// (arg0,) => [::core::fmt::ArgumentV1::new(
|
|
||||||
// arg0,
|
|
||||||
// ::core::fmt::Display::fmt,
|
|
||||||
// )],
|
|
||||||
// },
|
|
||||||
// );
|
|
||||||
// fn main() {
|
|
||||||
// let bump = bumpalo::Bump::new();
|
|
||||||
// let b = ≎
|
|
||||||
// let world = "123";
|
|
||||||
// let world = 123;
|
|
||||||
// let g = {
|
|
||||||
// use bumpalo::core_alloc::fmt::Write;
|
|
||||||
// use ::dioxus_core::prelude::bumpalo;
|
|
||||||
// use ::dioxus_core::prelude::format_args_f;
|
|
||||||
// let bump = b;
|
|
||||||
// let mut s = bumpalo::collections::String::new_in(bump);
|
|
||||||
// let _ = (&mut s).write_fmt(::core::fmt::Arguments::new_v1(
|
|
||||||
// &[""],
|
|
||||||
// &match (&::core::fmt::Arguments::new_v1(
|
|
||||||
// &["hhello "],
|
|
||||||
// &match (&world,) {
|
|
||||||
// (arg0,) => [::core::fmt::ArgumentV1::new(
|
|
||||||
// arg0,
|
|
||||||
// ::core::fmt::Display::fmt,
|
|
||||||
// )],
|
|
||||||
// },
|
|
||||||
// ),)
|
|
||||||
// {
|
|
||||||
// (arg0,) => [::core::fmt::ArgumentV1::new(
|
|
||||||
// arg0,
|
|
||||||
// ::core::fmt::Display::fmt,
|
|
||||||
// )],
|
|
||||||
// },
|
|
||||||
// ));
|
|
||||||
// s
|
|
||||||
// };
|
|
||||||
// }
|
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
use baller::Baller;
|
||||||
|
use dioxus_core::prelude::*;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let g = rsx! {
|
||||||
|
div {
|
||||||
|
crate::baller::Baller {}
|
||||||
|
baller::Baller {
|
||||||
|
}
|
||||||
|
Taller {
|
||||||
|
a: "asd"
|
||||||
|
}
|
||||||
|
baller::Baller {}
|
||||||
|
baller::Baller {}
|
||||||
|
Baller {}
|
||||||
|
div {
|
||||||
|
a: "asd",
|
||||||
|
a: "asd",
|
||||||
|
a: "asd",
|
||||||
|
a: "asd",
|
||||||
|
div {
|
||||||
|
"asdas",
|
||||||
|
"asdas",
|
||||||
|
"asdas",
|
||||||
|
"asdas",
|
||||||
|
div {
|
||||||
|
|
||||||
|
},
|
||||||
|
div {
|
||||||
|
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
mod baller {
|
||||||
|
use super::*;
|
||||||
|
pub struct BallerProps {}
|
||||||
|
|
||||||
|
pub fn Baller(ctx: Context, props: &()) -> DomTree {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Props)]
|
||||||
|
pub struct TallerProps {
|
||||||
|
a: &'static str,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn Taller(ctx: Context, props: &TallerProps) -> DomTree {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::{nodebuilder::IntoDomTree, prelude::*, scope::Scope};
|
use crate::{innerlude::*, nodebuilder::IntoDomTree};
|
||||||
use crate::{nodebuilder::LazyNodes, nodes::VNode};
|
use crate::{nodebuilder::LazyNodes, nodes::VNode};
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
use hooks::Hook;
|
use hooks::Hook;
|
||||||
@@ -97,9 +97,12 @@ impl<'a> Context<'a> {
|
|||||||
&self,
|
&self,
|
||||||
lazy_nodes: LazyNodes<'a, F>,
|
lazy_nodes: LazyNodes<'a, F>,
|
||||||
) -> DomTree {
|
) -> DomTree {
|
||||||
|
// let idx = self.idx.borrow();
|
||||||
let ctx = NodeCtx {
|
let ctx = NodeCtx {
|
||||||
bump: &self.scope.cur_frame().bump,
|
bump: &self.scope.cur_frame().bump,
|
||||||
scope: self.scope.myidx,
|
scope: self.scope.myidx,
|
||||||
|
|
||||||
|
// hmmmmmmmm not sure if this is right
|
||||||
idx: 0.into(),
|
idx: 0.into(),
|
||||||
listeners: &self.scope.listeners,
|
listeners: &self.scope.listeners,
|
||||||
};
|
};
|
||||||
@@ -151,11 +154,14 @@ pub mod hooks {
|
|||||||
let new_state = initializer();
|
let new_state = initializer();
|
||||||
hooks.push(Hook::new(new_state));
|
hooks.push(Hook::new(new_state));
|
||||||
}
|
}
|
||||||
*self.idx.borrow_mut() = 1;
|
|
||||||
|
*self.idx.borrow_mut() += 1;
|
||||||
|
|
||||||
let stable_ref = hooks.get_mut(idx).unwrap().0.as_mut();
|
let stable_ref = hooks.get_mut(idx).unwrap().0.as_mut();
|
||||||
let v = unsafe { Pin::get_unchecked_mut(stable_ref) };
|
let v = unsafe { Pin::get_unchecked_mut(stable_ref) };
|
||||||
let internal_state = v.downcast_mut::<InternalHookState>().unwrap();
|
let internal_state = v
|
||||||
|
.downcast_mut::<InternalHookState>()
|
||||||
|
.expect("couldn't find the hook state");
|
||||||
|
|
||||||
// we extend the lifetime from borrowed in this scope to borrowed from self.
|
// we extend the lifetime from borrowed in this scope to borrowed from self.
|
||||||
// This is okay because the hook is pinned
|
// This is okay because the hook is pinned
|
||||||
|
|||||||
+20
-13
@@ -32,7 +32,7 @@
|
|||||||
//!
|
//!
|
||||||
//! More info on how to improve this diffing algorithm:
|
//! More info on how to improve this diffing algorithm:
|
||||||
//! - https://hacks.mozilla.org/2019/03/fast-bump-allocated-virtual-doms-with-rust-and-wasm/
|
//! - https://hacks.mozilla.org/2019/03/fast-bump-allocated-virtual-doms-with-rust-and-wasm/
|
||||||
use crate::{innerlude::*, scope::Scope};
|
use crate::innerlude::*;
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
use fxhash::{FxHashMap, FxHashSet};
|
use fxhash::{FxHashMap, FxHashSet};
|
||||||
use generational_arena::Arena;
|
use generational_arena::Arena;
|
||||||
@@ -66,26 +66,29 @@ pub struct DiffMachine<'a> {
|
|||||||
pub enum LifeCycleEvent<'a> {
|
pub enum LifeCycleEvent<'a> {
|
||||||
Mount {
|
Mount {
|
||||||
caller: Weak<dyn for<'r> Fn(Context<'r>) -> DomTree + 'a>,
|
caller: Weak<dyn for<'r> Fn(Context<'r>) -> DomTree + 'a>,
|
||||||
scope: Weak<VCompAssociatedScope>,
|
stable_scope_addr: Weak<VCompAssociatedScope>,
|
||||||
id: u32,
|
root_id: u32,
|
||||||
},
|
},
|
||||||
PropsChanged {
|
PropsChanged {
|
||||||
caller: Weak<dyn for<'r> Fn(Context<'r>) -> DomTree + 'a>,
|
caller: Weak<dyn for<'r> Fn(Context<'r>) -> DomTree + 'a>,
|
||||||
scope: Weak<VCompAssociatedScope>,
|
stable_scope_addr: Weak<VCompAssociatedScope>,
|
||||||
id: u32,
|
root_id: u32,
|
||||||
},
|
},
|
||||||
SameProps {
|
SameProps {
|
||||||
caller: Weak<dyn for<'r> Fn(Context<'r>) -> DomTree + 'a>,
|
caller: Weak<dyn for<'r> Fn(Context<'r>) -> DomTree + 'a>,
|
||||||
scope: Weak<VCompAssociatedScope>,
|
stable_scope_addr: Weak<VCompAssociatedScope>,
|
||||||
id: u32,
|
root_id: u32,
|
||||||
},
|
},
|
||||||
Replace {
|
Replace {
|
||||||
caller: Weak<dyn for<'r> Fn(Context<'r>) -> DomTree + 'a>,
|
caller: Weak<dyn for<'r> Fn(Context<'r>) -> DomTree + 'a>,
|
||||||
old_scope: Weak<VCompAssociatedScope>,
|
old_scope: Weak<VCompAssociatedScope>,
|
||||||
new_scope: Weak<VCompAssociatedScope>,
|
new_scope: Weak<VCompAssociatedScope>,
|
||||||
id: u32,
|
root_id: u32,
|
||||||
|
},
|
||||||
|
Remove {
|
||||||
|
stable_scope_addr: Weak<VCompAssociatedScope>,
|
||||||
|
root_id: u32,
|
||||||
},
|
},
|
||||||
Remove,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static COUNTER: AtomicU32 = AtomicU32::new(1);
|
static COUNTER: AtomicU32 = AtomicU32::new(1);
|
||||||
@@ -159,7 +162,11 @@ impl<'a> DiffMachine<'a> {
|
|||||||
|
|
||||||
let scope = Rc::downgrade(&cold.ass_scope);
|
let scope = Rc::downgrade(&cold.ass_scope);
|
||||||
self.lifecycle_events
|
self.lifecycle_events
|
||||||
.push_back(LifeCycleEvent::PropsChanged { caller, id, scope });
|
.push_back(LifeCycleEvent::PropsChanged {
|
||||||
|
caller,
|
||||||
|
root_id: id,
|
||||||
|
stable_scope_addr: scope,
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
let caller = Rc::downgrade(&cnew.caller);
|
let caller = Rc::downgrade(&cnew.caller);
|
||||||
let id = cold.stable_addr.borrow().unwrap();
|
let id = cold.stable_addr.borrow().unwrap();
|
||||||
@@ -168,7 +175,7 @@ impl<'a> DiffMachine<'a> {
|
|||||||
|
|
||||||
self.lifecycle_events.push_back(LifeCycleEvent::Replace {
|
self.lifecycle_events.push_back(LifeCycleEvent::Replace {
|
||||||
caller,
|
caller,
|
||||||
id,
|
root_id: id,
|
||||||
old_scope,
|
old_scope,
|
||||||
new_scope,
|
new_scope,
|
||||||
});
|
});
|
||||||
@@ -262,8 +269,8 @@ impl<'a> DiffMachine<'a> {
|
|||||||
let scope = Rc::downgrade(&component.ass_scope);
|
let scope = Rc::downgrade(&component.ass_scope);
|
||||||
self.lifecycle_events.push_back(LifeCycleEvent::Mount {
|
self.lifecycle_events.push_back(LifeCycleEvent::Mount {
|
||||||
caller: Rc::downgrade(&component.caller),
|
caller: Rc::downgrade(&component.caller),
|
||||||
id,
|
root_id: id,
|
||||||
scope,
|
stable_scope_addr: scope,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
VNode::Suspended => {
|
VNode::Suspended => {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ mod use_state_def {
|
|||||||
struct UseState<T: 'static> {
|
struct UseState<T: 'static> {
|
||||||
new_val: Rc<RefCell<Option<T>>>,
|
new_val: Rc<RefCell<Option<T>>>,
|
||||||
current_val: T,
|
current_val: T,
|
||||||
caller: Box<dyn Fn(T) + 'static>,
|
caller: Rc<dyn Fn(T) + 'static>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Store state between component renders!
|
/// Store state between component renders!
|
||||||
@@ -49,12 +49,12 @@ mod use_state_def {
|
|||||||
pub fn use_state<'a, 'c, T: 'static, F: FnOnce() -> T>(
|
pub fn use_state<'a, 'c, T: 'static, F: FnOnce() -> T>(
|
||||||
ctx: &'c Context<'a>,
|
ctx: &'c Context<'a>,
|
||||||
initial_state_fn: F,
|
initial_state_fn: F,
|
||||||
) -> (&'a T, &'a impl Fn(T)) {
|
) -> (&'a T, &'a Rc<dyn Fn(T)>) {
|
||||||
ctx.use_hook(
|
ctx.use_hook(
|
||||||
move || UseState {
|
move || UseState {
|
||||||
new_val: Rc::new(RefCell::new(None)),
|
new_val: Rc::new(RefCell::new(None)),
|
||||||
current_val: initial_state_fn(),
|
current_val: initial_state_fn(),
|
||||||
caller: Box::new(|_| println!("setter called!")),
|
caller: Rc::new(|_| println!("setter called!")),
|
||||||
},
|
},
|
||||||
move |hook| {
|
move |hook| {
|
||||||
log::debug!("Use_state set called");
|
log::debug!("Use_state set called");
|
||||||
@@ -69,7 +69,7 @@ mod use_state_def {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// todo: swap out the caller with a subscription call and an internal update
|
// todo: swap out the caller with a subscription call and an internal update
|
||||||
hook.caller = Box::new(move |new_val| {
|
hook.caller = Rc::new(move |new_val| {
|
||||||
// update the setter with the new value
|
// update the setter with the new value
|
||||||
let mut new_inner = inner.as_ref().borrow_mut();
|
let mut new_inner = inner.as_ref().borrow_mut();
|
||||||
*new_inner = Some(new_val);
|
*new_inner = Some(new_val);
|
||||||
|
|||||||
+13
-35
@@ -68,18 +68,14 @@
|
|||||||
pub mod component; // Logic for extending FC
|
pub mod component; // Logic for extending FC
|
||||||
pub mod context; // Logic for providing hook + context functionality to user components
|
pub mod context; // Logic for providing hook + context functionality to user components
|
||||||
pub mod debug_renderer;
|
pub mod debug_renderer;
|
||||||
pub mod patch; // An "edit phase" described by transitions and edit operations // Test harness for validating that lifecycles and diffs work appropriately
|
|
||||||
// pub mod diff;
|
|
||||||
// pub mod patch; // The diffing algorithm that builds the ChangeList
|
|
||||||
pub mod diff;
|
pub mod diff;
|
||||||
// the diffing algorithm that builds the ChangeList
|
pub mod patch; // An "edit phase" described by transitions and edit operations // Test harness for validating that lifecycles and diffs work appropriately
|
||||||
|
// the diffing algorithm that builds the ChangeList
|
||||||
pub mod error; // Error type we expose to the renderers
|
pub mod error; // Error type we expose to the renderers
|
||||||
pub mod events; // Manages the synthetic event API
|
pub mod events; // Manages the synthetic event API
|
||||||
pub mod hooks; // Built-in hooks
|
pub mod hooks; // Built-in hooks
|
||||||
pub mod nodebuilder; // Logic for building VNodes with a direct syntax
|
pub mod nodebuilder; // Logic for building VNodes with a direct syntax
|
||||||
pub mod nodes; // Logic for the VNodes
|
pub mod nodes; // Logic for the VNodes
|
||||||
pub mod scope; // Logic for single components
|
|
||||||
// pub mod validation; // Logic for validating trees
|
|
||||||
pub mod virtual_dom; // Most fun logic starts here, manages the lifecycle and suspense
|
pub mod virtual_dom; // Most fun logic starts here, manages the lifecycle and suspense
|
||||||
|
|
||||||
pub mod builder {
|
pub mod builder {
|
||||||
@@ -88,37 +84,20 @@ pub mod builder {
|
|||||||
|
|
||||||
// types used internally that are important
|
// types used internally that are important
|
||||||
pub(crate) mod innerlude {
|
pub(crate) mod innerlude {
|
||||||
// pub(crate) use crate::component::Properties;
|
pub use crate::component::*;
|
||||||
pub(crate) use crate::component::Properties;
|
pub use crate::context::*;
|
||||||
pub(crate) use crate::context::Context;
|
pub use crate::debug_renderer::*;
|
||||||
pub use crate::diff::LifeCycleEvent;
|
pub use crate::diff::*;
|
||||||
pub(crate) use crate::error::Result;
|
pub use crate::error::*;
|
||||||
pub use crate::events::{EventTrigger, VirtualEvent};
|
pub use crate::events::*;
|
||||||
use crate::nodes;
|
pub use crate::hooks::*;
|
||||||
|
pub use crate::nodebuilder::*;
|
||||||
pub use crate::component::ScopeIdx;
|
pub use crate::nodes::*;
|
||||||
pub use crate::context::hooks::Hook;
|
pub use crate::patch::*;
|
||||||
pub use crate::nodes::VNode;
|
pub use crate::virtual_dom::*;
|
||||||
|
|
||||||
pub(crate) use nodes::*;
|
|
||||||
|
|
||||||
pub use crate::context::NodeCtx;
|
|
||||||
pub use crate::diff::DiffMachine;
|
|
||||||
pub use crate::patch::{EditList, EditMachine};
|
|
||||||
// pub use crate::patchdx;
|
|
||||||
// pub use crate::patchtList;
|
|
||||||
// pub use nodes::iterables::IterableNodes;
|
|
||||||
/// This type alias is an internal way of abstracting over the static functions that represent components.
|
|
||||||
|
|
||||||
pub type FC<P> = for<'scope> fn(Context<'scope>, &'scope P) -> DomTree;
|
pub type FC<P> = for<'scope> fn(Context<'scope>, &'scope P) -> DomTree;
|
||||||
|
|
||||||
mod fc2 {}
|
|
||||||
// pub type FC<'a, P: 'a> = for<'scope> fn(Context<'scope>, &'scope P) -> DomTree;
|
|
||||||
// pub type FC<P> = for<'scope, 'r> fn(Context<'scope>, &'scope P) -> DomTree;
|
|
||||||
// pub type FC<P> = for<'scope, 'r> fn(Context<'scope>, &'r P) -> VNode<'scope>;
|
|
||||||
// pub type FC<P> = for<'scope, 'r> fn(Context<'scope>, &'r P) -> VNode<'scope>;
|
|
||||||
// pub type FC<P> = for<'a> fn(Context<'a, P>) -> VNode<'a>;
|
|
||||||
|
|
||||||
// TODO @Jon, fix this
|
// TODO @Jon, fix this
|
||||||
// hack the VNode type until VirtualNode is fixed in the macro crate
|
// hack the VNode type until VirtualNode is fixed in the macro crate
|
||||||
pub type VirtualNode<'a> = VNode<'a>;
|
pub type VirtualNode<'a> = VNode<'a>;
|
||||||
@@ -127,7 +106,6 @@ pub(crate) mod innerlude {
|
|||||||
pub use crate as dioxus;
|
pub use crate as dioxus;
|
||||||
pub use crate::nodebuilder as builder;
|
pub use crate::nodebuilder as builder;
|
||||||
pub use dioxus_core_macro::{html, rsx};
|
pub use dioxus_core_macro::{html, rsx};
|
||||||
// pub use dioxus_core_macro::{fc, html, rsx};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Re-export common types for ease of development use.
|
/// Re-export common types for ease of development use.
|
||||||
|
|||||||
@@ -21,9 +21,14 @@
|
|||||||
//! ----
|
//! ----
|
||||||
//! - stack machine approach does not work when 3rd party extensions inject elements (breaking our view of the dom)
|
//! - stack machine approach does not work when 3rd party extensions inject elements (breaking our view of the dom)
|
||||||
|
|
||||||
|
use std::cell::Ref;
|
||||||
|
|
||||||
use crate::innerlude::ScopeIdx;
|
use crate::innerlude::ScopeIdx;
|
||||||
|
|
||||||
pub type EditList<'src> = Vec<Edit<'src>>;
|
pub type EditList<'src> = Vec<Edit<'src>>;
|
||||||
|
// pub struct EditList<'src> {
|
||||||
|
// edits: Vec<Edit<'src>>,
|
||||||
|
// }
|
||||||
|
|
||||||
/// The `Edit` represents a single modifcation of the renderer tree.
|
/// The `Edit` represents a single modifcation of the renderer tree.
|
||||||
/// todo @jon, go through and make certain fields static. tag names should be known at compile time
|
/// todo @jon, go through and make certain fields static. tag names should be known at compile time
|
||||||
@@ -146,7 +151,7 @@ pub struct EditMachine<'lock> {
|
|||||||
pub traversal: Traversal,
|
pub traversal: Traversal,
|
||||||
next_temporary: u32,
|
next_temporary: u32,
|
||||||
forcing_new_listeners: bool,
|
forcing_new_listeners: bool,
|
||||||
|
|
||||||
// // if the current node is a "known" node
|
// // if the current node is a "known" node
|
||||||
// // any actions that modify this node should update the mapping
|
// // any actions that modify this node should update the mapping
|
||||||
// current_known: Option<u32>,
|
// current_known: Option<u32>,
|
||||||
@@ -338,7 +343,7 @@ impl<'a> EditMachine<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_element(&mut self, tag_name: &'a str) {
|
pub fn create_element(&mut self, tag_name: &'a str) {
|
||||||
debug_assert!(self.traversal_is_committed());;
|
debug_assert!(self.traversal_is_committed());
|
||||||
self.emitter.push(Edit::CreateElement { tag_name });
|
self.emitter.push(Edit::CreateElement { tag_name });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -389,7 +394,6 @@ impl<'a> EditMachine<'a> {
|
|||||||
// self.current_known = Some(id);
|
// self.current_known = Some(id);
|
||||||
self.emitter.push(Edit::TraverseToKnown { node: id })
|
self.emitter.push(Edit::TraverseToKnown { node: id })
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keeps track of where we are moving in a DOM tree, and shortens traversal
|
// Keeps track of where we are moving in a DOM tree, and shortens traversal
|
||||||
|
|||||||
@@ -1,382 +0,0 @@
|
|||||||
use crate::{innerlude::*, virtual_dom::OpaqueComponent};
|
|
||||||
use bumpalo::Bump;
|
|
||||||
|
|
||||||
use std::{
|
|
||||||
cell::RefCell,
|
|
||||||
collections::HashSet,
|
|
||||||
marker::PhantomData,
|
|
||||||
rc::{Rc, Weak},
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Every component in Dioxus is represented by a `Scope`.
|
|
||||||
///
|
|
||||||
/// Scopes contain the state for hooks, the component's props, and other lifecycle information.
|
|
||||||
///
|
|
||||||
/// Scopes are allocated in a generational arena. As components are mounted/unmounted, they will replace slots of dead components.
|
|
||||||
/// The actual contents of the hooks, though, will be allocated with the standard allocator. These should not allocate as frequently.
|
|
||||||
pub struct Scope {
|
|
||||||
// Map to the parent
|
|
||||||
pub parent: Option<ScopeIdx>,
|
|
||||||
|
|
||||||
// our own index
|
|
||||||
pub myidx: ScopeIdx,
|
|
||||||
|
|
||||||
//
|
|
||||||
pub children: HashSet<ScopeIdx>,
|
|
||||||
|
|
||||||
pub caller: Weak<OpaqueComponent<'static>>,
|
|
||||||
|
|
||||||
// ==========================
|
|
||||||
// slightly unsafe stuff
|
|
||||||
// ==========================
|
|
||||||
// an internal, highly efficient storage of vnodes
|
|
||||||
pub frames: ActiveFrame,
|
|
||||||
|
|
||||||
// These hooks are actually references into the hook arena
|
|
||||||
// These two could be combined with "OwningRef" to remove unsafe usage
|
|
||||||
// or we could dedicate a tiny bump arena just for them
|
|
||||||
// could also use ourborous
|
|
||||||
pub hooks: RefCell<Vec<Hook>>,
|
|
||||||
|
|
||||||
pub hook_arena: Vec<Hook>,
|
|
||||||
|
|
||||||
// Unsafety:
|
|
||||||
// - is self-refenrential and therefore needs to point into the bump
|
|
||||||
// Stores references into the listeners attached to the vnodes
|
|
||||||
// NEEDS TO BE PRIVATE
|
|
||||||
pub(crate) listeners: RefCell<Vec<*const dyn Fn(VirtualEvent)>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Scope {
|
|
||||||
// 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.
|
|
||||||
// To make sure that the lifetime isn't truly broken, we receive a Weak RC so we can't keep it around after the parent dies.
|
|
||||||
// This should never happen, but is a good check to keep around
|
|
||||||
pub fn new<'creator_node>(
|
|
||||||
caller: Weak<OpaqueComponent<'creator_node>>,
|
|
||||||
myidx: ScopeIdx,
|
|
||||||
parent: Option<ScopeIdx>,
|
|
||||||
) -> Self {
|
|
||||||
// Caller has been broken free
|
|
||||||
// However, it's still weak, so if the original Rc gets killed, we can't touch it
|
|
||||||
let broken_caller: Weak<OpaqueComponent<'static>> = unsafe { std::mem::transmute(caller) };
|
|
||||||
|
|
||||||
Self {
|
|
||||||
caller: broken_caller,
|
|
||||||
hook_arena: Vec::new(),
|
|
||||||
hooks: RefCell::new(Vec::new()),
|
|
||||||
frames: ActiveFrame::new(),
|
|
||||||
children: HashSet::new(),
|
|
||||||
listeners: Default::default(),
|
|
||||||
parent,
|
|
||||||
myidx,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_caller<'creator_node>(&mut self, caller: Weak<OpaqueComponent<'creator_node>>) {
|
|
||||||
let broken_caller: Weak<OpaqueComponent<'static>> = unsafe { std::mem::transmute(caller) };
|
|
||||||
|
|
||||||
self.caller = broken_caller;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a new context and run the component with references from the Virtual Dom
|
|
||||||
/// This function downcasts the function pointer based on the stored props_type
|
|
||||||
///
|
|
||||||
/// Props is ?Sized because we borrow the props and don't need to know the size. P (sized) is used as a marker (unsized)
|
|
||||||
pub fn run_scope<'b>(&'b mut self) -> Result<()> {
|
|
||||||
// cycle to the next frame and then reset it
|
|
||||||
// this breaks any latent references
|
|
||||||
self.frames.next().bump.reset();
|
|
||||||
|
|
||||||
let ctx = Context {
|
|
||||||
idx: 0.into(),
|
|
||||||
_p: PhantomData {},
|
|
||||||
scope: self,
|
|
||||||
};
|
|
||||||
|
|
||||||
let caller = self.caller.upgrade().expect("Failed to get caller");
|
|
||||||
|
|
||||||
/*
|
|
||||||
SAFETY ALERT
|
|
||||||
|
|
||||||
DO NOT USE THIS VNODE WITHOUT THE APPOPRIATE ACCESSORS.
|
|
||||||
KEEPING THIS STATIC REFERENCE CAN LEAD TO UB.
|
|
||||||
|
|
||||||
Some things to note:
|
|
||||||
- The VNode itself is bound to the lifetime, but it itself is owned by scope.
|
|
||||||
- The VNode has a private API and can only be used from accessors.
|
|
||||||
- Public API cannot drop or destructure VNode
|
|
||||||
*/
|
|
||||||
let new_head = unsafe {
|
|
||||||
// use the same type, just manipulate the lifetime
|
|
||||||
type ComComp<'c> = Rc<OpaqueComponent<'c>>;
|
|
||||||
let caller = std::mem::transmute::<ComComp<'static>, ComComp<'b>>(caller);
|
|
||||||
(caller.as_ref())(ctx)
|
|
||||||
};
|
|
||||||
|
|
||||||
self.frames.cur_frame_mut().head_node = new_head.root;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// A safe wrapper around calling listeners
|
|
||||||
// calling listeners will invalidate the list of listeners
|
|
||||||
// The listener list will be completely drained because the next frame will write over previous listeners
|
|
||||||
pub fn call_listener(&mut self, trigger: EventTrigger) {
|
|
||||||
let EventTrigger {
|
|
||||||
listener_id,
|
|
||||||
event: source,
|
|
||||||
..
|
|
||||||
} = trigger;
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
let listener = self
|
|
||||||
.listeners
|
|
||||||
.borrow()
|
|
||||||
.get(listener_id as usize)
|
|
||||||
.expect("Listener should exist if it was triggered")
|
|
||||||
.as_ref()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Run the callback with the user event
|
|
||||||
log::debug!("Running listener");
|
|
||||||
listener(source);
|
|
||||||
log::debug!("Listener finished");
|
|
||||||
|
|
||||||
// drain all the event listeners
|
|
||||||
// if we don't, then they'll stick around and become invalid
|
|
||||||
// big big big big safety issue
|
|
||||||
self.listeners.borrow_mut().drain(..);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_frame<'bump>(&'bump self) -> &'bump VNode<'bump> {
|
|
||||||
self.frames.current_head_node()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn old_frame<'bump>(&'bump self) -> &'bump VNode<'bump> {
|
|
||||||
self.frames.prev_head_node()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn cur_frame(&self) -> &BumpFrame {
|
|
||||||
self.frames.cur_frame()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==========================
|
|
||||||
// Active-frame related code
|
|
||||||
// ==========================
|
|
||||||
// todo, do better with the active frame stuff
|
|
||||||
// somehow build this vnode with a lifetime tied to self
|
|
||||||
// This root node has "static" lifetime, but it's really not static.
|
|
||||||
// It's goverened by the oldest of the two frames and is switched every time a new render occurs
|
|
||||||
// Use this node as if it were static is unsafe, and needs to be fixed with ourborous or owning ref
|
|
||||||
// ! do not copy this reference are things WILL break !
|
|
||||||
pub struct ActiveFrame {
|
|
||||||
pub idx: RefCell<usize>,
|
|
||||||
pub frames: [BumpFrame; 2],
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct BumpFrame {
|
|
||||||
pub bump: Bump,
|
|
||||||
pub head_node: VNode<'static>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ActiveFrame {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self::from_frames(
|
|
||||||
BumpFrame {
|
|
||||||
bump: Bump::new(),
|
|
||||||
head_node: VNode::text(""),
|
|
||||||
},
|
|
||||||
BumpFrame {
|
|
||||||
bump: Bump::new(),
|
|
||||||
head_node: VNode::text(""),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn from_frames(a: BumpFrame, b: BumpFrame) -> Self {
|
|
||||||
Self {
|
|
||||||
idx: 0.into(),
|
|
||||||
frames: [a, b],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cur_frame(&self) -> &BumpFrame {
|
|
||||||
match *self.idx.borrow() & 1 == 0 {
|
|
||||||
true => &self.frames[0],
|
|
||||||
false => &self.frames[1],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn cur_frame_mut(&mut self) -> &mut BumpFrame {
|
|
||||||
match *self.idx.borrow() & 1 == 0 {
|
|
||||||
true => &mut self.frames[0],
|
|
||||||
false => &mut self.frames[1],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn current_head_node<'b>(&'b self) -> &'b VNode<'b> {
|
|
||||||
let raw_node = match *self.idx.borrow() & 1 == 0 {
|
|
||||||
true => &self.frames[0],
|
|
||||||
false => &self.frames[1],
|
|
||||||
};
|
|
||||||
|
|
||||||
// Give out our self-referential item with our own borrowed lifetime
|
|
||||||
unsafe {
|
|
||||||
let unsafe_head = &raw_node.head_node;
|
|
||||||
let safe_node = std::mem::transmute::<&VNode<'static>, &VNode<'b>>(unsafe_head);
|
|
||||||
safe_node
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn prev_head_node<'b>(&'b self) -> &'b VNode<'b> {
|
|
||||||
let raw_node = match *self.idx.borrow() & 1 != 0 {
|
|
||||||
true => &self.frames[0],
|
|
||||||
false => &self.frames[1],
|
|
||||||
};
|
|
||||||
|
|
||||||
// Give out our self-referential item with our own borrowed lifetime
|
|
||||||
unsafe {
|
|
||||||
let unsafe_head = &raw_node.head_node;
|
|
||||||
let safe_node = std::mem::transmute::<&VNode<'static>, &VNode<'b>>(unsafe_head);
|
|
||||||
safe_node
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn next(&mut self) -> &mut BumpFrame {
|
|
||||||
*self.idx.borrow_mut() += 1;
|
|
||||||
|
|
||||||
if *self.idx.borrow() % 2 == 0 {
|
|
||||||
&mut self.frames[0]
|
|
||||||
} else {
|
|
||||||
&mut self.frames[1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(old)]
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use crate::prelude::*;
|
|
||||||
|
|
||||||
// static ListenerTest: FC<()> = |ctx, props| {
|
|
||||||
// ctx.render(html! {
|
|
||||||
// <div onclick={|_| println!("Hell owlrld")}>
|
|
||||||
// "hello"
|
|
||||||
// </div>
|
|
||||||
// })
|
|
||||||
// };
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_scope() {
|
|
||||||
#[derive(PartialEq)]
|
|
||||||
struct Example {}
|
|
||||||
impl FC for Example {
|
|
||||||
fn render(ctx: Context<'_>, _: &Self) -> DomTree {
|
|
||||||
use crate::builder::*;
|
|
||||||
ctx.render(|ctx| {
|
|
||||||
builder::ElementBuilder::new(ctx, "div")
|
|
||||||
.child(text("a"))
|
|
||||||
.finish()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut nodes = generational_arena::Arena::new();
|
|
||||||
nodes.insert_with(|myidx| {
|
|
||||||
let scope = create_scoped(Example {}, myidx, None);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
use crate::{builder::*, hooks::use_ref};
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
struct EmptyProps<'src> {
|
|
||||||
name: &'src String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FC for EmptyProps<'_> {
|
|
||||||
fn render(ctx: Context, props: &Self) -> DomTree {
|
|
||||||
let (content, _): (&String, _) = crate::hooks::use_state(&ctx, || "abcd".to_string());
|
|
||||||
|
|
||||||
let childprops: ExampleProps<'_> = ExampleProps { name: content };
|
|
||||||
todo!()
|
|
||||||
// ctx.render(move |c| {
|
|
||||||
// builder::ElementBuilder::new(c, "div")
|
|
||||||
// .child(text(props.name))
|
|
||||||
// .child(virtual_child(c, childprops))
|
|
||||||
// .finish()
|
|
||||||
// })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
struct ExampleProps<'src> {
|
|
||||||
name: &'src String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FC for ExampleProps<'_> {
|
|
||||||
fn render(ctx: Context, props: &Self) -> DomTree {
|
|
||||||
ctx.render(move |ctx| {
|
|
||||||
builder::ElementBuilder::new(ctx, "div")
|
|
||||||
.child(text(props.name))
|
|
||||||
.finish()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_borrowed_scope() {
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
struct Example {
|
|
||||||
name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FC for Example {
|
|
||||||
fn render(ctx: Context, props: &Self) -> DomTree {
|
|
||||||
todo!()
|
|
||||||
// ctx.render(move |c| {
|
|
||||||
// builder::ElementBuilder::new(c, "div")
|
|
||||||
// .child(virtual_child(c, ExampleProps { name: &props.name }))
|
|
||||||
// .finish()
|
|
||||||
// })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let source_text = "abcd123".to_string();
|
|
||||||
let props = ExampleProps { name: &source_text };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(asd)]
|
|
||||||
mod old {
|
|
||||||
|
|
||||||
/// The ComponentCaller struct is an opaque object that encapsultes the memoization and running functionality for FC
|
|
||||||
///
|
|
||||||
/// It's opaque because during the diffing mechanism, the type of props is sealed away in a closure. This makes it so
|
|
||||||
/// scope doesn't need to be generic
|
|
||||||
pub struct ComponentCaller {
|
|
||||||
// used as a memoization strategy
|
|
||||||
comparator: Box<dyn Fn(&Box<dyn Any>) -> bool>,
|
|
||||||
|
|
||||||
// used to actually run the component
|
|
||||||
// encapsulates props
|
|
||||||
runner: Box<dyn Fn(Context) -> DomTree>,
|
|
||||||
|
|
||||||
props_type: TypeId,
|
|
||||||
|
|
||||||
// the actual FC<T>
|
|
||||||
raw: *const (),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ComponentCaller {
|
|
||||||
fn new<P>(props: P) -> Self {
|
|
||||||
let comparator = Box::new(|f| false);
|
|
||||||
todo!();
|
|
||||||
// Self { comparator }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_props<P>(props: P) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+546
-149
@@ -1,13 +1,12 @@
|
|||||||
// use crate::{changelist::EditList, nodes::VNode};
|
|
||||||
|
|
||||||
use crate::{error::Error, innerlude::*};
|
use crate::{error::Error, innerlude::*};
|
||||||
use crate::{patch::Edit, scope::Scope};
|
use crate::{innerlude::hooks::Hook, patch::Edit};
|
||||||
|
use bumpalo::Bump;
|
||||||
use generational_arena::Arena;
|
use generational_arena::Arena;
|
||||||
use std::{
|
use std::{
|
||||||
any::TypeId,
|
any::TypeId,
|
||||||
borrow::{Borrow, BorrowMut},
|
borrow::{Borrow, BorrowMut},
|
||||||
cell::RefCell,
|
cell::{Ref, RefCell, UnsafeCell},
|
||||||
collections::{BTreeMap, BTreeSet},
|
collections::{BTreeMap, BTreeSet, BinaryHeap, HashSet},
|
||||||
rc::{Rc, Weak},
|
rc::{Rc, Weak},
|
||||||
};
|
};
|
||||||
use thiserror::private::AsDynError;
|
use thiserror::private::AsDynError;
|
||||||
@@ -21,23 +20,30 @@ pub(crate) type OpaqueComponent<'a> = dyn for<'b> Fn(Context<'b>) -> DomTree + '
|
|||||||
pub struct VirtualDom {
|
pub struct VirtualDom {
|
||||||
/// All mounted components are arena allocated to make additions, removals, and references easy to work with
|
/// All mounted components are arena allocated to make additions, removals, and references easy to work with
|
||||||
/// A generational arena is used to re-use slots of deleted scopes without having to resize the underlying arena.
|
/// A generational arena is used to re-use slots of deleted scopes without having to resize the underlying arena.
|
||||||
pub components: Arena<Scope>,
|
///
|
||||||
|
/// This is wrapped in an UnsafeCell because we will need to get mutable access to unique values in unique bump arenas
|
||||||
|
/// and rusts's guartnees cannot prove that this is safe. We will need to maintain the safety guarantees manually.
|
||||||
|
components: UnsafeCell<Arena<Scope>>,
|
||||||
|
|
||||||
/// The index of the root component.
|
/// The index of the root component.\
|
||||||
/// Will not be ready if the dom is fresh
|
/// Should always be the first
|
||||||
pub base_scope: ScopeIdx,
|
pub base_scope: ScopeIdx,
|
||||||
|
|
||||||
|
/// All components dump their updates into a queue to be processed
|
||||||
pub(crate) update_schedule: UpdateFunnel,
|
pub(crate) update_schedule: UpdateFunnel,
|
||||||
|
|
||||||
// a strong allocation to the "caller" for the original props
|
// a strong allocation to the "caller" for the original props
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
_root_caller: Rc<OpaqueComponent<'static>>,
|
root_caller: Rc<OpaqueComponent<'static>>,
|
||||||
|
|
||||||
// Type of the original props. This is done so VirtualDom does not need to be generic.
|
// Type of the original props. This is done so VirtualDom does not need to be generic.
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
_root_prop_type: std::any::TypeId,
|
_root_prop_type: std::any::TypeId,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ======================================
|
||||||
|
// Public Methods for the VirtualDOM
|
||||||
|
// ======================================
|
||||||
impl VirtualDom {
|
impl VirtualDom {
|
||||||
/// Create a new instance of the Dioxus Virtual Dom with no properties for the root component.
|
/// Create a new instance of the Dioxus Virtual Dom with no properties for the root component.
|
||||||
///
|
///
|
||||||
@@ -55,109 +61,50 @@ impl VirtualDom {
|
|||||||
pub fn new_with_props<P: Properties + 'static>(root: FC<P>, root_props: P) -> Self {
|
pub fn new_with_props<P: Properties + 'static>(root: FC<P>, root_props: P) -> Self {
|
||||||
let mut components = Arena::new();
|
let mut components = Arena::new();
|
||||||
|
|
||||||
// let prr = Rc::new(root_props);
|
// The user passes in a "root" component (IE the function)
|
||||||
|
// When components are used in the rsx! syntax, the parent assumes ownership
|
||||||
|
// Here, the virtual dom needs to own the function, wrapping it with a `context caller`
|
||||||
|
// The RC holds this component with a hard allocation
|
||||||
|
let root_caller: Rc<OpaqueComponent> = Rc::new(move |ctx| root(ctx, &root_props));
|
||||||
|
|
||||||
// the root is kept around with a "hard" allocation
|
// To make it easier to pass the root around, we just leak it
|
||||||
let root_caller: Rc<OpaqueComponent> = Rc::new(move |ctx| {
|
// When the virtualdom is dropped, we unleak it, so that unsafe isn't here, but it's important to remember
|
||||||
//
|
let leaked_caller = Rc::downgrade(&root_caller);
|
||||||
// let p2 = &root_props;
|
|
||||||
// let p2 = prr.clone();
|
|
||||||
root(ctx, &root_props)
|
|
||||||
});
|
|
||||||
|
|
||||||
// we then expose this to the component with a weak allocation
|
|
||||||
let weak_caller: Weak<OpaqueComponent> = Rc::downgrade(&root_caller);
|
|
||||||
|
|
||||||
let base_scope = components.insert_with(move |myidx| Scope::new(weak_caller, myidx, None));
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
components,
|
root_caller,
|
||||||
_root_caller: root_caller,
|
base_scope: components
|
||||||
base_scope,
|
.insert_with(move |myidx| Scope::new(leaked_caller, myidx, None, 0)),
|
||||||
|
components: UnsafeCell::new(components),
|
||||||
update_schedule: UpdateFunnel::default(),
|
update_schedule: UpdateFunnel::default(),
|
||||||
_root_prop_type: TypeId::of::<P>(),
|
_root_prop_type: TypeId::of::<P>(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// consume the top of the diff machine event cycle and dump edits into the edit list
|
/// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom. from scratch
|
||||||
pub fn step(&mut self, event: LifeCycleEvent) -> Result<()> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom.
|
|
||||||
pub fn rebuild<'s>(&'s mut self) -> Result<EditList<'s>> {
|
pub fn rebuild<'s>(&'s mut self) -> Result<EditList<'s>> {
|
||||||
// Diff from the top
|
todo!()
|
||||||
let mut diff_machine = DiffMachine::new();
|
|
||||||
|
|
||||||
let very_unsafe_components = &mut self.components as *mut generational_arena::Arena<Scope>;
|
|
||||||
let component = self
|
|
||||||
.components
|
|
||||||
.get_mut(self.base_scope)
|
|
||||||
.ok_or_else(|| Error::FatalInternal("Acquring base component should never fail"))?;
|
|
||||||
component.run_scope()?;
|
|
||||||
diff_machine.diff_node(component.old_frame(), component.new_frame());
|
|
||||||
|
|
||||||
// log::debug!("New events generated: {:#?}", diff_machine.lifecycle_events);
|
|
||||||
// chew down the the lifecycle events until all dirty nodes are computed
|
|
||||||
while let Some(event) = diff_machine.lifecycle_events.pop_front() {
|
|
||||||
match event {
|
|
||||||
// A new component has been computed from the diffing algorithm
|
|
||||||
// create a new component in the arena, run it, move the diffing machine to this new spot, and then diff it
|
|
||||||
// this will flood the lifecycle queue with new updates
|
|
||||||
LifeCycleEvent::Mount { caller, id, scope } => {
|
|
||||||
log::debug!("Mounting a new component");
|
|
||||||
|
|
||||||
// We're modifying the component arena while holding onto references into the assoiated bump arenas of its children
|
|
||||||
// those references are stable, even if the component arena moves around in memory, thanks to the bump arenas.
|
|
||||||
// However, there is no way to convey this to rust, so we need to use unsafe to pierce through the lifetime.
|
|
||||||
unsafe {
|
|
||||||
let p = &mut *(very_unsafe_components);
|
|
||||||
|
|
||||||
// todo, hook up the parent/child indexes properly
|
|
||||||
let idx = p.insert_with(|f| Scope::new(caller, f, None));
|
|
||||||
let c = p.get_mut(idx).unwrap();
|
|
||||||
|
|
||||||
let real_scope = scope.upgrade().unwrap();
|
|
||||||
*real_scope.as_ref().borrow_mut() = Some(idx);
|
|
||||||
c.run_scope()?;
|
|
||||||
diff_machine.change_list.load_known_root(id);
|
|
||||||
diff_machine.diff_node(c.old_frame(), c.new_frame());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LifeCycleEvent::PropsChanged { caller, id, scope } => {
|
|
||||||
log::debug!("PROPS CHANGED");
|
|
||||||
let idx = scope.upgrade().unwrap().as_ref().borrow().unwrap();
|
|
||||||
unsafe {
|
|
||||||
let p = &mut *(very_unsafe_components);
|
|
||||||
let c = p.get_mut(idx).unwrap();
|
|
||||||
c.update_caller(caller);
|
|
||||||
c.run_scope()?;
|
|
||||||
diff_machine.change_list.load_known_root(id);
|
|
||||||
diff_machine.diff_node(c.old_frame(), c.new_frame());
|
|
||||||
}
|
|
||||||
// break 'render;
|
|
||||||
}
|
|
||||||
LifeCycleEvent::SameProps { caller, id, scope } => {
|
|
||||||
log::debug!("SAME PROPS RECEIVED");
|
|
||||||
//
|
|
||||||
// break 'render;
|
|
||||||
}
|
|
||||||
LifeCycleEvent::Remove => {
|
|
||||||
//
|
|
||||||
// break 'render;
|
|
||||||
}
|
|
||||||
LifeCycleEvent::Replace { caller, id, .. } => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
// } else {
|
|
||||||
// break 'render;
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
let edits: Vec<Edit<'s>> = diff_machine.consume();
|
|
||||||
Ok(edits)
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
struct HeightMarker {
|
||||||
|
idx: ScopeIdx,
|
||||||
|
height: u32,
|
||||||
|
}
|
||||||
|
impl Ord for HeightMarker {
|
||||||
|
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||||
|
self.height.cmp(&other.height)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialOrd for HeightMarker {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||||
|
Some(self.cmp(other))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VirtualDom {
|
||||||
/// This method is the most sophisticated way of updating the virtual dom after an external event has been triggered.
|
/// This method is the most sophisticated way of updating the virtual dom after an external event has been triggered.
|
||||||
///
|
///
|
||||||
/// Given a synthetic event, the component that triggered the event, and the index of the callback, this runs the virtual
|
/// Given a synthetic event, the component that triggered the event, and the index of the callback, this runs the virtual
|
||||||
@@ -165,7 +112,16 @@ impl VirtualDom {
|
|||||||
/// change list.
|
/// change list.
|
||||||
///
|
///
|
||||||
/// If implementing an external renderer, this is the perfect method to combine with an async event loop that waits on
|
/// If implementing an external renderer, this is the perfect method to combine with an async event loop that waits on
|
||||||
/// listeners.
|
/// listeners, something like this:
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// while let Ok(event) = receiver.recv().await {
|
||||||
|
/// let edits = self.internal_dom.progress_with_event(event)?;
|
||||||
|
/// for edit in &edits {
|
||||||
|
/// patch_machine.handle_edit(edit);
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// Note: this method is not async and does not provide suspense-like functionality. It is up to the renderer to provide the
|
/// Note: this method is not async and does not provide suspense-like functionality. It is up to the renderer to provide the
|
||||||
/// executor and handlers for suspense as show in the example.
|
/// executor and handlers for suspense as show in the example.
|
||||||
@@ -182,67 +138,508 @@ impl VirtualDom {
|
|||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
pub fn progress_with_event(&mut self, event: EventTrigger) -> Result<EditList<'_>> {
|
//
|
||||||
let component = self
|
// Developer notes:
|
||||||
.components
|
// ----
|
||||||
.get_mut(event.component_id)
|
// This method has some pretty complex safety guarantees to uphold.
|
||||||
.expect("Borrowing should not fail");
|
// We interact with bump arenas, raw pointers, and use UnsafeCell to get a partial borrow of the arena.
|
||||||
|
// The final EditList has edits that pull directly from the Bump Arenas which add significant complexity
|
||||||
|
// in crafting a 100% safe solution with traditional lifetimes. Consider this method to be internally unsafe
|
||||||
|
// but the guarantees provide a safe, fast, and efficient abstraction for the VirtualDOM updating framework.
|
||||||
|
pub fn progress_with_event(&mut self, event: EventTrigger) -> Result<EditList> {
|
||||||
|
let id = event.component_id.clone();
|
||||||
|
|
||||||
component.call_listener(event);
|
unsafe {
|
||||||
|
(&mut *self.components.get())
|
||||||
|
.get_mut(id)
|
||||||
|
.expect("Borrowing should not fail")
|
||||||
|
.call_listener(event)?;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
// Add this component to the list of components that need to be difed
|
||||||
-> call listener
|
let mut diff_machine = DiffMachine::new();
|
||||||
-> sort through accumulated queue by the top
|
let mut cur_height = 0;
|
||||||
-> run each component, running its children
|
|
||||||
-> add new updates to the tree of updates (these are dirty)
|
|
||||||
->
|
|
||||||
*/
|
|
||||||
// component.run_scope()?;
|
|
||||||
|
|
||||||
// let mut diff_machine = DiffMachine::new();
|
// Now, there are events in the queue
|
||||||
// let mut diff_machine = DiffMachine::new(&self.diff_bump);
|
let mut seen_nodes = HashSet::<ScopeIdx>::new();
|
||||||
|
let mut updates = self.update_schedule.0.as_ref().borrow_mut();
|
||||||
|
|
||||||
// diff_machine.diff_node(component.old_frame(), component.new_frame());
|
// Order the nodes by their height, we want the biggest nodes on the top
|
||||||
|
// This prevents us from running the same component multiple times
|
||||||
|
updates.sort_unstable();
|
||||||
|
|
||||||
// Ok(diff_machine.consume())
|
// Iterate through the triggered nodes (sorted by height) and begin to diff them
|
||||||
self.rebuild()
|
for update in updates.drain(..) {
|
||||||
|
// Make sure this isn't a node we've already seen, we don't want to double-render anything
|
||||||
|
// If we double-renderer something, this would cause memory safety issues
|
||||||
|
if seen_nodes.contains(&update.idx) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now, all the "seen nodes" are nodes that got notified by running this listener
|
||||||
|
seen_nodes.insert(update.idx.clone());
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
// Start a new mutable borrow to components
|
||||||
|
|
||||||
|
// We are guaranteeed that this scope is unique because we are tracking which nodes have modified
|
||||||
|
let component = (&mut *self.components.get())
|
||||||
|
.get_mut(update.idx)
|
||||||
|
.expect("msg");
|
||||||
|
|
||||||
|
component.run_scope()?;
|
||||||
|
|
||||||
|
diff_machine.diff_node(component.old_frame(), component.next_frame());
|
||||||
|
|
||||||
|
cur_height = component.height + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now, the entire subtree has been invalidated. We need to descend depth-first and process
|
||||||
|
// any updates that the diff machine has proprogated into the component lifecycle queue
|
||||||
|
while let Some(event) = diff_machine.lifecycle_events.pop_front() {
|
||||||
|
match event {
|
||||||
|
// A new component has been computed from the diffing algorithm
|
||||||
|
// create a new component in the arena, run it, move the diffing machine to this new spot, and then diff it
|
||||||
|
// this will flood the lifecycle queue with new updates to build up the subtree
|
||||||
|
LifeCycleEvent::Mount {
|
||||||
|
caller,
|
||||||
|
root_id: id,
|
||||||
|
stable_scope_addr,
|
||||||
|
} => {
|
||||||
|
log::debug!("Mounting a new component");
|
||||||
|
|
||||||
|
// We're modifying the component arena while holding onto references into the assoiated bump arenas of its children
|
||||||
|
// those references are stable, even if the component arena moves around in memory, thanks to the bump arenas.
|
||||||
|
// However, there is no way to convey this to rust, so we need to use unsafe to pierce through the lifetime.
|
||||||
|
let components: &mut _ = unsafe { &mut *self.components.get() };
|
||||||
|
|
||||||
|
// Insert a new scope into our component list
|
||||||
|
let idx =
|
||||||
|
components.insert_with(|f| Scope::new(caller, f, None, cur_height));
|
||||||
|
|
||||||
|
// Grab out that component
|
||||||
|
let component = components.get_mut(idx).unwrap();
|
||||||
|
|
||||||
|
// Actually initialize the caller's slot with the right address
|
||||||
|
*stable_scope_addr.upgrade().unwrap().as_ref().borrow_mut() = Some(idx);
|
||||||
|
|
||||||
|
// Run the scope for one iteration to initialize it
|
||||||
|
component.run_scope()?;
|
||||||
|
|
||||||
|
// Navigate the diff machine to the right point in the output dom
|
||||||
|
diff_machine.change_list.load_known_root(id);
|
||||||
|
|
||||||
|
// And then run the diff algorithm
|
||||||
|
diff_machine.diff_node(component.old_frame(), component.next_frame());
|
||||||
|
|
||||||
|
// Finally, insert this node as a seen node.
|
||||||
|
seen_nodes.insert(idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
// A component has remained in the same location but its properties have changed
|
||||||
|
// We need to process this component and then dump the output lifecycle events into the queue
|
||||||
|
LifeCycleEvent::PropsChanged {
|
||||||
|
caller,
|
||||||
|
root_id,
|
||||||
|
stable_scope_addr,
|
||||||
|
} => {
|
||||||
|
log::debug!("Updating a component after its props have changed");
|
||||||
|
|
||||||
|
let components: &mut _ = unsafe { &mut *self.components.get() };
|
||||||
|
|
||||||
|
// Get the stable index to the target component
|
||||||
|
// This *should* exist due to guarantees in the diff algorithm
|
||||||
|
let idx = stable_scope_addr
|
||||||
|
.upgrade()
|
||||||
|
.unwrap()
|
||||||
|
.as_ref()
|
||||||
|
.borrow()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Grab out that component
|
||||||
|
let component = components.get_mut(idx).unwrap();
|
||||||
|
|
||||||
|
// We have to move the caller over or running the scope will fail
|
||||||
|
component.update_caller(caller);
|
||||||
|
|
||||||
|
// Run the scope
|
||||||
|
component.run_scope()?;
|
||||||
|
|
||||||
|
// Navigate the diff machine to the right point in the output dom
|
||||||
|
diff_machine.change_list.load_known_root(root_id);
|
||||||
|
|
||||||
|
// And then run the diff algorithm
|
||||||
|
diff_machine.diff_node(component.old_frame(), component.next_frame());
|
||||||
|
|
||||||
|
// Finally, insert this node as a seen node.
|
||||||
|
seen_nodes.insert(idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
// A component's parent has updated, but its properties did not change.
|
||||||
|
// This means the caller ptr is invalidated and needs to be updated, but the component itself does not need to be re-ran
|
||||||
|
LifeCycleEvent::SameProps {
|
||||||
|
caller,
|
||||||
|
root_id,
|
||||||
|
stable_scope_addr,
|
||||||
|
} => {
|
||||||
|
// In this case, the parent made a new DomTree that resulted in the same props for us
|
||||||
|
// However, since our caller is located in a Bump frame, we need to update the caller pointer (which is now invalid)
|
||||||
|
log::debug!("Received the same props");
|
||||||
|
|
||||||
|
let components: &mut _ = unsafe { &mut *self.components.get() };
|
||||||
|
|
||||||
|
// Get the stable index to the target component
|
||||||
|
// This *should* exist due to guarantees in the diff algorithm
|
||||||
|
let idx = stable_scope_addr
|
||||||
|
.upgrade()
|
||||||
|
.unwrap()
|
||||||
|
.as_ref()
|
||||||
|
.borrow()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Grab out that component
|
||||||
|
let component = components.get_mut(idx).unwrap();
|
||||||
|
|
||||||
|
// We have to move the caller over or running the scope will fail
|
||||||
|
component.update_caller(caller);
|
||||||
|
|
||||||
|
// This time, we will not add it to our seen nodes since we did not actually run it
|
||||||
|
}
|
||||||
|
|
||||||
|
LifeCycleEvent::Remove {
|
||||||
|
root_id,
|
||||||
|
stable_scope_addr,
|
||||||
|
} => {
|
||||||
|
unimplemented!("This feature (Remove) is unimplemented")
|
||||||
|
}
|
||||||
|
LifeCycleEvent::Replace {
|
||||||
|
caller,
|
||||||
|
root_id: id,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
unimplemented!("This feature (Replace) is unimplemented")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(diff_machine.consume())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
|
impl Drop for VirtualDom {
|
||||||
pub struct HierarchyMarker {
|
fn drop(&mut self) {
|
||||||
source: ScopeIdx,
|
// Drop all the components first
|
||||||
}
|
// self.components.drain();
|
||||||
|
|
||||||
#[derive(Debug, Default, PartialEq, Clone)]
|
// Finally, drop the root caller
|
||||||
pub struct UpdateFunnel(Rc<RefCell<Vec<HierarchyMarker>>>);
|
unsafe {
|
||||||
|
// let root: Box<OpaqueComponent<'static>> =
|
||||||
|
// Box::from_raw(self.root_caller as *const OpaqueComponent<'static> as *mut _);
|
||||||
|
|
||||||
impl UpdateFunnel {
|
// std::mem::drop(root);
|
||||||
fn schedule_update(&self, source: ScopeIdx) -> impl Fn() {
|
|
||||||
let inner = self.clone();
|
|
||||||
move || {
|
|
||||||
inner
|
|
||||||
.0
|
|
||||||
.as_ref()
|
|
||||||
.borrow_mut()
|
|
||||||
.push(HierarchyMarker { source })
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! UpdateFunnel {
|
#[derive(Debug, Default, Clone)]
|
||||||
(root: $root:expr) => {
|
pub struct UpdateFunnel(Rc<RefCell<Vec<HeightMarker>>>);
|
||||||
VirtualDom::new($root)
|
|
||||||
};
|
impl UpdateFunnel {
|
||||||
|
fn schedule_update(&self, source: &Scope) -> impl Fn() {
|
||||||
|
let inner = self.clone();
|
||||||
|
let marker = HeightMarker {
|
||||||
|
height: source.height,
|
||||||
|
idx: source.myidx,
|
||||||
|
};
|
||||||
|
move || inner.0.as_ref().borrow_mut().push(marker)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Every component in Dioxus is represented by a `Scope`.
|
||||||
|
///
|
||||||
|
/// Scopes contain the state for hooks, the component's props, and other lifecycle information.
|
||||||
|
///
|
||||||
|
/// Scopes are allocated in a generational arena. As components are mounted/unmounted, they will replace slots of dead components.
|
||||||
|
/// The actual contents of the hooks, though, will be allocated with the standard allocator. These should not allocate as frequently.
|
||||||
|
pub struct Scope {
|
||||||
|
// The parent's scope ID
|
||||||
|
pub parent: Option<ScopeIdx>,
|
||||||
|
|
||||||
|
// Our own ID accessible from the component map
|
||||||
|
pub myidx: ScopeIdx,
|
||||||
|
|
||||||
|
pub height: u32,
|
||||||
|
|
||||||
|
// A list of children
|
||||||
|
// TODO, repalce the hashset with a faster hash function
|
||||||
|
pub children: HashSet<ScopeIdx>,
|
||||||
|
|
||||||
|
// caller: &'static OpaqueComponent<'static>,
|
||||||
|
pub caller: Weak<OpaqueComponent<'static>>,
|
||||||
|
|
||||||
|
// ==========================
|
||||||
|
// slightly unsafe stuff
|
||||||
|
// ==========================
|
||||||
|
// an internal, highly efficient storage of vnodes
|
||||||
|
pub(crate) frames: ActiveFrame,
|
||||||
|
|
||||||
|
// These hooks are actually references into the hook arena
|
||||||
|
// These two could be combined with "OwningRef" to remove unsafe usage
|
||||||
|
// or we could dedicate a tiny bump arena just for them
|
||||||
|
// could also use ourborous
|
||||||
|
pub(crate) hooks: RefCell<Vec<Hook>>,
|
||||||
|
|
||||||
|
// Unsafety:
|
||||||
|
// - is self-refenrential and therefore needs to point into the bump
|
||||||
|
// Stores references into the listeners attached to the vnodes
|
||||||
|
// NEEDS TO BE PRIVATE
|
||||||
|
pub(crate) listeners: RefCell<Vec<*const dyn Fn(VirtualEvent)>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[test]
|
impl Scope {
|
||||||
// fn test_new_vdom() {
|
// we are being created in the scope of an existing component (where the creator_node lifetime comes into play)
|
||||||
// let dom = UpdateFunnel! {
|
// we are going to break this lifetime by force in order to save it on ourselves.
|
||||||
// root: |ctx, props| {
|
// To make sure that the lifetime isn't truly broken, we receive a Weak RC so we can't keep it around after the parent dies.
|
||||||
// ctx.render(rsx!{
|
// This should never happen, but is a good check to keep around
|
||||||
|
//
|
||||||
|
// Scopes cannot be made anywhere else except for this file
|
||||||
|
// Therefore, their lifetimes are connected exclusively to the virtual dom
|
||||||
|
fn new<'creator_node>(
|
||||||
|
caller: Weak<OpaqueComponent<'creator_node>>,
|
||||||
|
myidx: ScopeIdx,
|
||||||
|
parent: Option<ScopeIdx>,
|
||||||
|
height: u32,
|
||||||
|
) -> Self {
|
||||||
|
log::debug!(
|
||||||
|
"New scope created, height is {}, idx is {:?}",
|
||||||
|
height,
|
||||||
|
myidx
|
||||||
|
);
|
||||||
|
// Caller has been broken free
|
||||||
|
// However, it's still weak, so if the original Rc gets killed, we can't touch it
|
||||||
|
let broken_caller: Weak<OpaqueComponent<'static>> = unsafe { std::mem::transmute(caller) };
|
||||||
|
|
||||||
// })
|
Self {
|
||||||
// }
|
caller: broken_caller,
|
||||||
// };
|
hooks: RefCell::new(Vec::new()),
|
||||||
// }
|
frames: ActiveFrame::new(),
|
||||||
|
children: HashSet::new(),
|
||||||
|
listeners: Default::default(),
|
||||||
|
parent,
|
||||||
|
myidx,
|
||||||
|
height,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn update_caller<'creator_node>(&mut self, caller: Weak<OpaqueComponent<'creator_node>>) {
|
||||||
|
let broken_caller: Weak<OpaqueComponent<'static>> = unsafe { std::mem::transmute(caller) };
|
||||||
|
|
||||||
|
self.caller = broken_caller;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new context and run the component with references from the Virtual Dom
|
||||||
|
/// This function downcasts the function pointer based on the stored props_type
|
||||||
|
///
|
||||||
|
/// Props is ?Sized because we borrow the props and don't need to know the size. P (sized) is used as a marker (unsized)
|
||||||
|
pub fn run_scope<'b>(&'b mut self) -> Result<()> {
|
||||||
|
// cycle to the next frame and then reset it
|
||||||
|
// this breaks any latent references
|
||||||
|
self.frames.next().bump.reset();
|
||||||
|
|
||||||
|
let ctx = Context {
|
||||||
|
idx: 0.into(),
|
||||||
|
_p: std::marker::PhantomData {},
|
||||||
|
scope: self,
|
||||||
|
};
|
||||||
|
|
||||||
|
let caller = self.caller.upgrade().expect("Failed to get caller");
|
||||||
|
|
||||||
|
/*
|
||||||
|
SAFETY ALERT
|
||||||
|
|
||||||
|
DO NOT USE THIS VNODE WITHOUT THE APPOPRIATE ACCESSORS.
|
||||||
|
KEEPING THIS STATIC REFERENCE CAN LEAD TO UB.
|
||||||
|
|
||||||
|
Some things to note:
|
||||||
|
- The VNode itself is bound to the lifetime, but it itself is owned by scope.
|
||||||
|
- The VNode has a private API and can only be used from accessors.
|
||||||
|
- Public API cannot drop or destructure VNode
|
||||||
|
*/
|
||||||
|
let new_head = unsafe {
|
||||||
|
// use the same type, just manipulate the lifetime
|
||||||
|
type ComComp<'c> = Rc<OpaqueComponent<'c>>;
|
||||||
|
let caller = std::mem::transmute::<ComComp<'static>, ComComp<'b>>(caller);
|
||||||
|
(caller.as_ref())(ctx)
|
||||||
|
};
|
||||||
|
|
||||||
|
self.frames.cur_frame_mut().head_node = new_head.root;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// A safe wrapper around calling listeners
|
||||||
|
// calling listeners will invalidate the list of listeners
|
||||||
|
// The listener list will be completely drained because the next frame will write over previous listeners
|
||||||
|
pub fn call_listener(&mut self, trigger: EventTrigger) -> Result<()> {
|
||||||
|
let EventTrigger {
|
||||||
|
listener_id,
|
||||||
|
event: source,
|
||||||
|
..
|
||||||
|
} = trigger;
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
// Convert the raw ptr into an actual object
|
||||||
|
// This operation is assumed to be safe
|
||||||
|
|
||||||
|
log::debug!("Running listener");
|
||||||
|
|
||||||
|
self.listeners
|
||||||
|
.borrow()
|
||||||
|
.get(listener_id as usize)
|
||||||
|
.ok_or(Error::FatalInternal("Event should exist if it was triggered"))?
|
||||||
|
.as_ref()
|
||||||
|
.ok_or(Error::FatalInternal("Raw event ptr is invalid"))?
|
||||||
|
// Run the callback with the user event
|
||||||
|
(source);
|
||||||
|
|
||||||
|
log::debug!("Listener finished");
|
||||||
|
|
||||||
|
// drain all the event listeners
|
||||||
|
// if we don't, then they'll stick around and become invalid
|
||||||
|
// big big big big safety issue
|
||||||
|
self.listeners.borrow_mut().drain(..);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next_frame<'bump>(&'bump self) -> &'bump VNode<'bump> {
|
||||||
|
self.frames.current_head_node()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn old_frame<'bump>(&'bump self) -> &'bump VNode<'bump> {
|
||||||
|
self.frames.prev_head_node()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cur_frame(&self) -> &BumpFrame {
|
||||||
|
self.frames.cur_frame()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==========================
|
||||||
|
// Active-frame related code
|
||||||
|
// ==========================
|
||||||
|
// todo, do better with the active frame stuff
|
||||||
|
// somehow build this vnode with a lifetime tied to self
|
||||||
|
// This root node has "static" lifetime, but it's really not static.
|
||||||
|
// It's goverened by the oldest of the two frames and is switched every time a new render occurs
|
||||||
|
// Use this node as if it were static is unsafe, and needs to be fixed with ourborous or owning ref
|
||||||
|
// ! do not copy this reference are things WILL break !
|
||||||
|
pub struct ActiveFrame {
|
||||||
|
pub idx: RefCell<usize>,
|
||||||
|
pub frames: [BumpFrame; 2],
|
||||||
|
|
||||||
|
// We use a "generation" for users of contents in the bump frames to ensure their data isn't broken
|
||||||
|
pub generation: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct BumpFrame {
|
||||||
|
pub bump: Bump,
|
||||||
|
pub head_node: VNode<'static>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActiveFrame {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::from_frames(
|
||||||
|
BumpFrame {
|
||||||
|
bump: Bump::new(),
|
||||||
|
head_node: VNode::text(""),
|
||||||
|
},
|
||||||
|
BumpFrame {
|
||||||
|
bump: Bump::new(),
|
||||||
|
head_node: VNode::text(""),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_frames(a: BumpFrame, b: BumpFrame) -> Self {
|
||||||
|
Self {
|
||||||
|
idx: 0.into(),
|
||||||
|
frames: [a, b],
|
||||||
|
generation: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cur_frame(&self) -> &BumpFrame {
|
||||||
|
match *self.idx.borrow() & 1 == 0 {
|
||||||
|
true => &self.frames[0],
|
||||||
|
false => &self.frames[1],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn cur_frame_mut(&mut self) -> &mut BumpFrame {
|
||||||
|
match *self.idx.borrow() & 1 == 0 {
|
||||||
|
true => &mut self.frames[0],
|
||||||
|
false => &mut self.frames[1],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn current_head_node<'b>(&'b self) -> &'b VNode<'b> {
|
||||||
|
let raw_node = match *self.idx.borrow() & 1 == 0 {
|
||||||
|
true => &self.frames[0],
|
||||||
|
false => &self.frames[1],
|
||||||
|
};
|
||||||
|
|
||||||
|
// Give out our self-referential item with our own borrowed lifetime
|
||||||
|
unsafe {
|
||||||
|
let unsafe_head = &raw_node.head_node;
|
||||||
|
let safe_node = std::mem::transmute::<&VNode<'static>, &VNode<'b>>(unsafe_head);
|
||||||
|
safe_node
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn prev_head_node<'b>(&'b self) -> &'b VNode<'b> {
|
||||||
|
let raw_node = match *self.idx.borrow() & 1 != 0 {
|
||||||
|
true => &self.frames[0],
|
||||||
|
false => &self.frames[1],
|
||||||
|
};
|
||||||
|
|
||||||
|
// Give out our self-referential item with our own borrowed lifetime
|
||||||
|
unsafe {
|
||||||
|
let unsafe_head = &raw_node.head_node;
|
||||||
|
let safe_node = std::mem::transmute::<&VNode<'static>, &VNode<'b>>(unsafe_head);
|
||||||
|
safe_node
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next(&mut self) -> &mut BumpFrame {
|
||||||
|
*self.idx.borrow_mut() += 1;
|
||||||
|
|
||||||
|
if *self.idx.borrow() % 2 == 0 {
|
||||||
|
&mut self.frames[0]
|
||||||
|
} else {
|
||||||
|
&mut self.frames[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn simulate() {
|
||||||
|
let dom = VirtualDom::new(|ctx, props| {
|
||||||
|
//
|
||||||
|
ctx.render(rsx! {
|
||||||
|
div {
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
// let root = dom.components.get(dom.base_scope).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
// // This marker is designed to ensure resources shared from one bump to another are handled properly
|
||||||
|
// // The underlying T may be already freed if there is an issue with our crate
|
||||||
|
// pub(crate) struct BumpResource<T: 'static> {
|
||||||
|
// resource: T,
|
||||||
|
// scope: ScopeIdx,
|
||||||
|
// gen: u32,
|
||||||
|
// }
|
||||||
|
}
|
||||||
+118
-9
@@ -1,10 +1,119 @@
|
|||||||
// mod hooks;
|
use std::collections::HashMap;
|
||||||
// pub use hooks::use_context;
|
|
||||||
|
|
||||||
// pub mod prelude {
|
use dioxus_core::prelude::*;
|
||||||
// use dioxus_core::prelude::Context;
|
|
||||||
// pub fn use_state<T, G>(ctx: &Context<G>, init: impl Fn() -> T) -> (T, impl Fn(T)) {
|
/*
|
||||||
// let g = init();
|
a form of use_state explicitly for map-style collections (BTreeMap, HashMap, etc).
|
||||||
// (g, |_| {})
|
|
||||||
// }
|
Why?
|
||||||
// }
|
---
|
||||||
|
Traditionally, it's possible to use the "use_state" hook for collections in the React world.
|
||||||
|
Adding a new entry would look something similar to:
|
||||||
|
|
||||||
|
```js
|
||||||
|
let (map, set_map) = useState({});
|
||||||
|
set_map({ ...map, [key]: value });
|
||||||
|
```
|
||||||
|
The new value then causes the appropriate update when passed into children.
|
||||||
|
|
||||||
|
This is moderately efficient because the fields of the map are moved, but the data itself is not cloned.
|
||||||
|
However, if you used similar approach with Dioxus:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let (map, set_map) = use_state(ctx, || HashMap::new());
|
||||||
|
set_map({
|
||||||
|
let mut newmap = map.clone();
|
||||||
|
newmap.set(key, value);
|
||||||
|
newmap
|
||||||
|
})
|
||||||
|
```
|
||||||
|
Unfortunately, you'd be cloning the entire state every time a value is changed. The obvious solution is to
|
||||||
|
wrap every element in the HashMap with an Rc. That way, cloning the HashMap is on par with its JS equivalent.
|
||||||
|
|
||||||
|
Fortunately, we can make this operation even more efficient in Dioxus, leveraging the borrow rules of Rust.
|
||||||
|
|
||||||
|
This hook provides a memoized collection, memoized setters, and memoized getters. This particular hook is
|
||||||
|
extremely powerful for implementing lists and supporting core state management needs for small apps.
|
||||||
|
|
||||||
|
If you need something even more powerful, check out the dedicated atomic state management Dioxus Dataflow, which
|
||||||
|
uses the same memoization on top of the use_context API.
|
||||||
|
|
||||||
|
Here's a fully-functional todo app using the use_map API:
|
||||||
|
```rust
|
||||||
|
static TodoList: FC<()> = |ctx, props| {
|
||||||
|
let todos = use_map(ctx, || HashMap::new());
|
||||||
|
let input = use_ref(|| None);
|
||||||
|
|
||||||
|
ctx.render(rsx!{
|
||||||
|
div {
|
||||||
|
button {
|
||||||
|
"Add todo"
|
||||||
|
onclick: move |_| {
|
||||||
|
let new_todo = TodoItem::new(input.contents());
|
||||||
|
todos.insert(new_todo.id.clone(), new_todo);
|
||||||
|
input.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
"Clear todos"
|
||||||
|
onclick: move |_| todos.clear()
|
||||||
|
}
|
||||||
|
input {
|
||||||
|
placeholder: "What needs to be done?"
|
||||||
|
ref: input
|
||||||
|
}
|
||||||
|
ul {
|
||||||
|
{todos.iter().map(|todo| rsx!(
|
||||||
|
li {
|
||||||
|
key: todo.id
|
||||||
|
span { "{todo.content}" }
|
||||||
|
button {"x", onclick: move |_| todos.remove(todo.key.clone())}
|
||||||
|
}
|
||||||
|
))}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
*/
|
||||||
|
fn use_map() {}
|
||||||
|
|
||||||
|
// a form of "use_state" that allows collection memoization
|
||||||
|
// Elements are received as Rc<T> in case the underlying collection is shuffled around
|
||||||
|
// Setters/getters can be generated
|
||||||
|
fn use_collection<'a, T: Collection>(
|
||||||
|
ctx: &Context<'a>,
|
||||||
|
f: impl Fn() -> T,
|
||||||
|
) -> CollectionHandle<'a, T> {
|
||||||
|
ctx.use_hook(
|
||||||
|
|| {},
|
||||||
|
|h| {
|
||||||
|
//
|
||||||
|
CollectionHandle {
|
||||||
|
_p: Default::default(),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|h| {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CollectionMemo {}
|
||||||
|
|
||||||
|
struct CollectionHandle<'a, T: Collection> {
|
||||||
|
_p: std::marker::PhantomData<&'a T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
trait Collection {}
|
||||||
|
impl<K, V> Collection for std::collections::HashMap<K, V> {}
|
||||||
|
|
||||||
|
struct MapCollection<K, V> {
|
||||||
|
inner: HashMap<K, V>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<K, V> MapCollection<K, V> {
|
||||||
|
fn set(&self, key: K, val: V) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
[package]
|
||||||
|
name = "dioxus-ios"
|
||||||
|
version = "0.0.0"
|
||||||
|
authors = ["Jonathan Kelley <jkelleyrtp@gmail.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
cacao = { git = "https://github.com/ryanmcgrath/cacao" }
|
||||||
|
dioxus-core = { path = "../core", version = "0.1.2" }
|
||||||
|
log = "0.4.14"
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# Dioxus for iOS
|
||||||
|
|
||||||
|
This crate implements a renderer of the Dioxus DomTree to an iOS app
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
use dioxus::virtual_dom::VirtualDom;
|
||||||
|
pub use dioxus_core as dioxus;
|
||||||
|
use dioxus_core::{events::EventTrigger, prelude::FC};
|
||||||
|
|
||||||
|
pub struct IosRenderer {
|
||||||
|
internal_dom: VirtualDom,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IosRenderer {
|
||||||
|
pub async fn run(&mut self) -> dioxus_core::error::Result<()> {
|
||||||
|
let (sender, mut receiver) = async_channel::unbounded::<EventTrigger>();
|
||||||
|
|
||||||
|
// let body_element = prepare_websys_dom();
|
||||||
|
|
||||||
|
let mut patch_machine = interpreter::PatchMachine::new(body_element.clone(), move |ev| {
|
||||||
|
log::debug!("Event trigger! {:#?}", ev);
|
||||||
|
let mut c = sender.clone();
|
||||||
|
// wasm_bindgen_futures::spawn_local(async move {
|
||||||
|
// c.send(ev).await.unwrap();
|
||||||
|
// });
|
||||||
|
});
|
||||||
|
let root_node = body_element.first_child().unwrap();
|
||||||
|
patch_machine.stack.push(root_node.clone());
|
||||||
|
|
||||||
|
// todo: initialize the event registry properly on the root
|
||||||
|
|
||||||
|
let edits = self.internal_dom.rebuild()?;
|
||||||
|
log::debug!("Received edits: {:#?}", edits);
|
||||||
|
edits.iter().for_each(|edit| {
|
||||||
|
log::debug!("patching with {:?}", edit);
|
||||||
|
patch_machine.handle_edit(edit);
|
||||||
|
});
|
||||||
|
|
||||||
|
patch_machine.reset();
|
||||||
|
let root_node = body_element.first_child().unwrap();
|
||||||
|
patch_machine.stack.push(root_node.clone());
|
||||||
|
|
||||||
|
// log::debug!("patch stack size {:?}", patch_machine.stack);
|
||||||
|
|
||||||
|
// Event loop waits for the receiver to finish up
|
||||||
|
// TODO! Connect the sender to the virtual dom's suspense system
|
||||||
|
// Suspense is basically an external event that can force renders to specific nodes
|
||||||
|
while let Ok(event) = receiver.recv().await {
|
||||||
|
log::debug!("Stack before entrance {:#?}", patch_machine.stack.top());
|
||||||
|
// log::debug!("patch stack size before {:#?}", patch_machine.stack);
|
||||||
|
// patch_machine.reset();
|
||||||
|
// patch_machine.stack.push(root_node.clone());
|
||||||
|
let edits = self.internal_dom.progress_with_event(event)?;
|
||||||
|
log::debug!("Received edits: {:#?}", edits);
|
||||||
|
|
||||||
|
for edit in &edits {
|
||||||
|
log::debug!("edit stream {:?}", edit);
|
||||||
|
// log::debug!("Stream stack {:#?}", patch_machine.stack.top());
|
||||||
|
patch_machine.handle_edit(edit);
|
||||||
|
}
|
||||||
|
|
||||||
|
// log::debug!("patch stack size after {:#?}", patch_machine.stack);
|
||||||
|
patch_machine.reset();
|
||||||
|
// our root node reference gets invalidated
|
||||||
|
// not sure why
|
||||||
|
// for now, just select the first child again.
|
||||||
|
// eventually, we'll just make our own root element instead of using body
|
||||||
|
// or just use body directly IDEK
|
||||||
|
let root_node = body_element.first_child().unwrap();
|
||||||
|
patch_machine.stack.push(root_node.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(()) // should actually never return from this, should be an error, rustc just cant see it
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,7 +29,7 @@ async-channel = "1.6.1"
|
|||||||
# futures-lite = "1.11.3"
|
# futures-lite = "1.11.3"
|
||||||
|
|
||||||
[dependencies.web-sys]
|
[dependencies.web-sys]
|
||||||
version = "0.3"
|
version = "0.3.50"
|
||||||
features = [
|
features = [
|
||||||
"Comment",
|
"Comment",
|
||||||
"Document",
|
"Document",
|
||||||
@@ -71,7 +71,7 @@ crate-type = ["cdylib", "rlib"]
|
|||||||
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
uuid = { version = "0.8.2", features = ["v4"] }
|
uuid = { version = "0.8.2", features = ["v4", "wasm-bindgen"] }
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "todomvc"
|
name = "todomvc"
|
||||||
|
|||||||
@@ -7,8 +7,6 @@ fn main() {
|
|||||||
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(CustomA))
|
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(CustomA))
|
||||||
}
|
}
|
||||||
|
|
||||||
use components::CustomB;
|
|
||||||
|
|
||||||
fn CustomA<'a>(ctx: Context<'a>, props: &'a ()) -> DomTree {
|
fn CustomA<'a>(ctx: Context<'a>, props: &'a ()) -> DomTree {
|
||||||
let (val, set_val) = use_state(&ctx, || "abcdef".to_string());
|
let (val, set_val) = use_state(&ctx, || "abcdef".to_string());
|
||||||
|
|
||||||
@@ -24,7 +22,7 @@ fn CustomA<'a>(ctx: Context<'a>, props: &'a ()) -> DomTree {
|
|||||||
"Lower"
|
"Lower"
|
||||||
onclick: move |_| set_val(val.to_ascii_lowercase())
|
onclick: move |_| set_val(val.to_ascii_lowercase())
|
||||||
}
|
}
|
||||||
CustomB {
|
components::CustomB {
|
||||||
val: val.as_ref()
|
val: val.as_ref()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
fn main() {}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
use dioxus_web::{dioxus::prelude::*, WebsysRenderer};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
|
||||||
|
console_error_panic_hook::set_once();
|
||||||
|
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn App(ctx: Context, props: &()) -> DomTree {
|
||||||
|
ctx.render(rsx! {
|
||||||
|
main { class: "dark:bg-gray-800 bg-white relative h-screen"
|
||||||
|
NavBar {}
|
||||||
|
{(0..10).map(|f| rsx!{ Landing {} })}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn NavBar(ctx: Context, props: &()) -> DomTree {
|
||||||
|
ctx.render(rsx!{
|
||||||
|
header { class: "h-24 sm:h-32 flex items-center z-30 w-full"
|
||||||
|
div { class: "container mx-auto px-6 flex items-center justify-between"
|
||||||
|
div { class: "uppercase text-gray-800 dark:text-white font-black text-3xl"
|
||||||
|
svg { focusable:"false" width:"100" height:"100" viewBox: "0 0 512 309"
|
||||||
|
path { fill: "#000"
|
||||||
|
d: "M120.81 80.561h96.568v7.676h-87.716v57.767h82.486v7.675h-82.486v63.423h88.722v7.675H120.81V80.561zm105.22 0h10.26l45.467 63.423L328.23 80.56L391.441 0l-103.85 150.65l53.515 74.127h-10.663l-48.686-67.462l-48.888 67.462h-10.461l53.917-74.128l-50.296-70.088zm118.898 7.676V80.56h110.048v7.676h-50.699v136.54h-8.852V88.237h-50.497zM0 80.56h11.065l152.58 228.323l-63.053-84.107L9.254 91.468l-.402 133.31H0V80.56zm454.084 134.224c-1.809 0-3.165-1.4-3.165-3.212c0-1.81 1.356-3.212 3.165-3.212c1.83 0 3.165 1.401 3.165 3.212c0 1.811-1.335 3.212-3.165 3.212zm8.698-8.45h4.737c.064 2.565 1.937 4.29 4.693 4.29c3.079 0 4.823-1.854 4.823-5.325v-21.99h4.823v22.011c0 6.252-3.617 9.853-9.603 9.853c-5.62 0-9.473-3.493-9.473-8.84zm25.384-.28h4.78c.409 2.953 3.294 4.828 7.45 4.828c3.875 0 6.717-2.005 6.717-4.764c0-2.371-1.809-3.794-5.921-4.764l-4.005-.97c-5.62-1.316-8.181-4.032-8.181-8.602c0-5.54 4.521-9.227 11.303-9.227c6.308 0 10.916 3.686 11.196 8.925h-4.694c-.452-2.867-2.95-4.657-6.567-4.657c-3.81 0-6.35 1.833-6.35 4.635c0 2.22 1.635 3.493 5.683 4.441l3.423.841c6.373 1.488 9 4.075 9 8.753c0 5.95-4.607 9.68-11.97 9.68c-6.89 0-11.52-3.558-11.864-9.12z"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
div { class:"flex items-center"
|
||||||
|
nav { class: "font-sen text-gray-800 dark:text-white uppercase text-lg lg:flex items-center hidden"
|
||||||
|
a { href: "#", class:"py-2 px-6 flex text-indigo-500 border-b-2 border-indigo-500"
|
||||||
|
"Home"
|
||||||
|
}
|
||||||
|
a { href: "#", class: "py-2 px-6 flex hover:text-indigo-500"
|
||||||
|
"Watch"
|
||||||
|
}
|
||||||
|
a { href: "#", class: "py-2 px-6 flex hover:text-indigo-500"
|
||||||
|
"Product"
|
||||||
|
}
|
||||||
|
a { href: "#", class: "py-2 px-6 flex hover:text-indigo-500"
|
||||||
|
"Contact"
|
||||||
|
}
|
||||||
|
a { href: "#", class: "py-2 px-6 flex hover:text-indigo-500"
|
||||||
|
"Career"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
button { class: "lg:hidden flex flex-col ml-4"
|
||||||
|
span { class: "w-6 h-1 bg-gray-800 dark:bg-white mb-1" }
|
||||||
|
span { class: "w-6 h-1 bg-gray-800 dark:bg-white mb-1" }
|
||||||
|
span { class: "w-6 h-1 bg-gray-800 dark:bg-white mb-1" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn Landing(ctx: Context, props: &()) -> DomTree {
|
||||||
|
ctx.render(rsx!{
|
||||||
|
div { class: "bg-white dark:bg-gray-800 flex relative z-20 items-center"
|
||||||
|
div { class: "container mx-auto px-6 flex flex-col justify-between items-center relative py-8"
|
||||||
|
div { class: "flex flex-col"
|
||||||
|
h1 { class: "font-light w-full uppercase text-center text-4xl sm:text-5xl dark:text-white text-gray-800"
|
||||||
|
"The React Framework for Production"
|
||||||
|
}
|
||||||
|
h2{ class: "font-light max-w-2xl mx-auto w-full text-xl dark:text-white text-gray-500 text-center py-8"
|
||||||
|
"Next.js gives you the best developer experience with all the features you need for production: \n
|
||||||
|
hybrid static & server rendering, TypeScript support, smart bundling, route pre-fetching, and \n
|
||||||
|
more. No config needed."
|
||||||
|
}
|
||||||
|
div { class: "flex items-center justify-center mt-4"
|
||||||
|
a { href: "#" class: "uppercase py-2 px-4 bg-gray-800 border-2 border-transparent text-white text-md mr-4 hover:bg-gray-900"
|
||||||
|
"Get started"
|
||||||
|
}
|
||||||
|
a{ href: "#" class: "uppercase py-2 px-4 bg-transparent border-2 border-gray-800 text-gray-800 dark:text-white hover:bg-gray-800 hover:text-white text-md"
|
||||||
|
"Documentation"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
div { class: "block w-full mx-auto mt-6 md:mt-0 relative"
|
||||||
|
img { src: "/images/object/12.svg" class: "max-w-xs md:max-w-2xl m-auto" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
//! Basic example that renders a simple domtree to the browser.
|
||||||
|
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use dioxus_core::prelude::*;
|
||||||
|
use dioxus_web::*;
|
||||||
|
fn main() {
|
||||||
|
// Setup logging
|
||||||
|
wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
|
||||||
|
console_error_panic_hook::set_once();
|
||||||
|
// Run the app
|
||||||
|
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App));
|
||||||
|
}
|
||||||
|
|
||||||
|
static App: FC<()> = |ctx, _| {
|
||||||
|
let (contents, set_contents) = use_state(&ctx, || "asd");
|
||||||
|
|
||||||
|
ctx.render(rsx! {
|
||||||
|
div {
|
||||||
|
class: "flex items-center justify-center flex-col"
|
||||||
|
div {
|
||||||
|
class: "flex items-center justify-center"
|
||||||
|
div {
|
||||||
|
class: "flex flex-col bg-white rounded p-4 w-full max-w-xs"
|
||||||
|
div { class: "font-bold text-xl", "Example cloud app" }
|
||||||
|
div { class: "text-sm text-gray-500", "This is running in the cloud!!" }
|
||||||
|
div {
|
||||||
|
class: "flex flex-row items-center justify-center mt-6"
|
||||||
|
div { class: "font-medium text-6xl", "100%" }
|
||||||
|
}
|
||||||
|
div {
|
||||||
|
class: "flex flex-row justify-between mt-6"
|
||||||
|
a {
|
||||||
|
href: "https://www.dioxuslabs.com"
|
||||||
|
class: "underline"
|
||||||
|
"Made with dioxus"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
//! Basic example that renders a simple domtree to the browser.
|
||||||
|
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use dioxus_core::prelude::*;
|
||||||
|
use dioxus_web::*;
|
||||||
|
fn main() {
|
||||||
|
// Setup logging
|
||||||
|
wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
|
||||||
|
console_error_panic_hook::set_once();
|
||||||
|
// Run the app
|
||||||
|
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App));
|
||||||
|
}
|
||||||
|
|
||||||
|
static App: FC<()> = |ctx, _| {
|
||||||
|
let (contents, set_contents) = use_state(&ctx, || "asd");
|
||||||
|
|
||||||
|
ctx.render(rsx! {
|
||||||
|
div {
|
||||||
|
class: "flex items-center justify-center flex-col"
|
||||||
|
div {
|
||||||
|
class: "flex items-center justify-center"
|
||||||
|
div {
|
||||||
|
class: "flex flex-col bg-white rounded p-4 w-full max-w-xs"
|
||||||
|
div { class: "font-bold text-xl", "Example Web app" }
|
||||||
|
div { class: "text-sm text-gray-500", "This is running in your browser!" }
|
||||||
|
div {
|
||||||
|
class: "flex flex-row items-center justify-center mt-6"
|
||||||
|
div { class: "font-medium text-6xl", "100%" }
|
||||||
|
}
|
||||||
|
div {
|
||||||
|
class: "flex flex-row justify-between mt-6"
|
||||||
|
a {
|
||||||
|
href: "https://www.dioxuslabs.com"
|
||||||
|
class: "underline"
|
||||||
|
"Made with dioxus"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
+158
-98
@@ -1,125 +1,185 @@
|
|||||||
use std::collections::{BTreeMap, BTreeSet, HashMap};
|
|
||||||
|
|
||||||
use dioxus::{events::on::MouseEvent, prelude::*};
|
|
||||||
use dioxus_core as dioxus;
|
use dioxus_core as dioxus;
|
||||||
|
use dioxus_core::prelude::*;
|
||||||
use dioxus_web::WebsysRenderer;
|
use dioxus_web::WebsysRenderer;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
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();
|
||||||
wasm_bindgen_futures::spawn_local(async move {
|
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App));
|
||||||
WebsysRenderer::new_with_props(App, ())
|
|
||||||
.run()
|
|
||||||
.await
|
|
||||||
.expect("major crash");
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
use lazy_static::lazy_static;
|
static APP_STYLE: &'static str = include_str!("./todomvc/style.css");
|
||||||
lazy_static! {
|
|
||||||
static ref DummyData: BTreeMap<usize, String> = {
|
#[derive(PartialEq, Clone, Copy)]
|
||||||
let vals = vec![
|
pub enum FilterState {
|
||||||
"abc123", //
|
All,
|
||||||
"abc124", //
|
Active,
|
||||||
"abc125", //
|
Completed,
|
||||||
"abc126", //
|
}
|
||||||
"abc127", //
|
|
||||||
"abc128", //
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
"abc129", //
|
pub struct TodoItem {
|
||||||
"abc1210", //
|
pub id: uuid::Uuid,
|
||||||
"abc1211", //
|
pub checked: bool,
|
||||||
"abc1212", //
|
pub contents: String,
|
||||||
"abc1213", //
|
|
||||||
"abc1214", //
|
|
||||||
"abc1215", //
|
|
||||||
"abc1216", //
|
|
||||||
"abc1217", //
|
|
||||||
"abc1218", //
|
|
||||||
"abc1219", //
|
|
||||||
"abc1220", //
|
|
||||||
"abc1221", //
|
|
||||||
"abc1222", //
|
|
||||||
];
|
|
||||||
vals.into_iter()
|
|
||||||
.map(ToString::to_string)
|
|
||||||
.enumerate()
|
|
||||||
.collect()
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static App: FC<()> = |ctx, _| {
|
static App: FC<()> = |ctx, _| {
|
||||||
let items = use_state_new(&ctx, || DummyData.clone());
|
let (draft, set_draft) = use_state(&ctx, || "".to_string());
|
||||||
|
let (filter, set_filter) = use_state(&ctx, || FilterState::All);
|
||||||
|
let (is_editing, set_is_editing) = use_state(&ctx, || false);
|
||||||
|
// let (todos, set_todos) = use_state(&ctx, || BTreeMap::<String, TodoItem>::new());
|
||||||
|
|
||||||
// handle new elements
|
// let todos = use_ref(&ctx, || BTreeMap::<String, TodoItem>::new());
|
||||||
let add_new = move |_| {
|
let todos = use_state_new(&ctx, || BTreeMap::<uuid::Uuid, TodoItem>::new());
|
||||||
items.modify(|m| {
|
|
||||||
let k = m.len();
|
|
||||||
let v = match (k % 3, k % 5) {
|
|
||||||
(0, 0) => "FizzBuzz".to_string(),
|
|
||||||
(0, _) => "Fizz".to_string(),
|
|
||||||
(_, 0) => "Buzz".to_string(),
|
|
||||||
_ => k.to_string(),
|
|
||||||
};
|
|
||||||
m.insert(k, v);
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
let elements = items.iter().map(|(k, v)| {
|
|
||||||
rsx! {
|
|
||||||
ListHelper {
|
|
||||||
name: k,
|
|
||||||
value: v
|
|
||||||
onclick: move |_| {
|
|
||||||
let key = k.clone();
|
|
||||||
items.modify(move |m| { m.remove(&key); } )
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
// let blah = "{draft}"
|
||||||
ctx.render(rsx!(
|
ctx.render(rsx!(
|
||||||
div {
|
div {
|
||||||
h1 {"Some list"}
|
id: "app"
|
||||||
button {
|
style { "{APP_STYLE}" }
|
||||||
"Remove all"
|
|
||||||
onclick: move |_| items.set(BTreeMap::new())
|
div {
|
||||||
|
header {
|
||||||
|
class: "header"
|
||||||
|
h1 {"todos"}
|
||||||
|
button {
|
||||||
|
"press me"
|
||||||
|
onclick: move |evt| {
|
||||||
|
let contents = draft.clone();
|
||||||
|
todos.modify(|f| {
|
||||||
|
let id = uuid::Uuid::new_v4();
|
||||||
|
f.insert(id.clone(), TodoItem {
|
||||||
|
id,
|
||||||
|
checked: false,
|
||||||
|
contents
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
input {
|
||||||
|
class: "new-todo"
|
||||||
|
placeholder: "What needs to be done?"
|
||||||
|
// value: "{draft}"
|
||||||
|
oninput: move |evt| set_draft(evt.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{ // list
|
||||||
|
todos
|
||||||
|
.iter()
|
||||||
|
.filter(|(id, item)| match filter {
|
||||||
|
FilterState::All => true,
|
||||||
|
FilterState::Active => !item.checked,
|
||||||
|
FilterState::Completed => item.checked,
|
||||||
|
})
|
||||||
|
.map(|(id, todo)| {
|
||||||
|
rsx!{
|
||||||
|
li {
|
||||||
|
"{todo.contents}"
|
||||||
|
input {
|
||||||
|
class: "toggle"
|
||||||
|
type: "checkbox"
|
||||||
|
"{todo.checked}"
|
||||||
|
}
|
||||||
|
// {is_editing.then(|| rsx!(
|
||||||
|
// input {
|
||||||
|
// value: "{contents}"
|
||||||
|
// }
|
||||||
|
// ))}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// filter toggle (show only if the list isn't empty)
|
||||||
|
{(!todos.is_empty()).then(||
|
||||||
|
rsx!{
|
||||||
|
footer {
|
||||||
|
span {
|
||||||
|
strong {"10"}
|
||||||
|
span {"0 items left"}
|
||||||
|
}
|
||||||
|
ul {
|
||||||
|
class: "filters"
|
||||||
|
{[
|
||||||
|
("All", "", FilterState::All),
|
||||||
|
("Active", "active", FilterState::Active),
|
||||||
|
("Completed", "completed", FilterState::Completed),
|
||||||
|
]
|
||||||
|
.iter()
|
||||||
|
.map(|(name, path, filter)| {
|
||||||
|
rsx!(
|
||||||
|
li {
|
||||||
|
class: "{name}"
|
||||||
|
a {
|
||||||
|
href: "{path}"
|
||||||
|
onclick: move |_| set_filter(filter.clone())
|
||||||
|
"{name}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)}
|
||||||
}
|
}
|
||||||
button {
|
|
||||||
"add new"
|
|
||||||
onclick: {add_new}
|
footer {
|
||||||
}
|
class: "info"
|
||||||
ul {
|
p {"Double-click to edit a todo"}
|
||||||
{elements}
|
p {
|
||||||
|
"Created by "
|
||||||
|
a { "jkelleyrtp", href: "http://github.com/jkelleyrtp/" }
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
"Part of "
|
||||||
|
a { "TodoMVC", href: "http://todomvc.com" }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
))
|
))
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Props)]
|
pub fn FilterToggles(ctx: Context, props: &()) -> DomTree {
|
||||||
struct ListProps<'a, F: Fn(MouseEvent) + 'a> {
|
// let reducer = recoil::use_callback(&ctx, || ());
|
||||||
name: &'a usize,
|
// let items_left = recoil::use_atom_family(&ctx, &TODOS, uuid::Uuid::new_v4());
|
||||||
value: &'a str,
|
|
||||||
onclick: F,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<F: Fn(MouseEvent)> PartialEq for ListProps<'_, F> {
|
let toggles = [
|
||||||
fn eq(&self, other: &Self) -> bool {
|
("All", "", FilterState::All),
|
||||||
// no references are ever the same
|
("Active", "active", FilterState::Active),
|
||||||
false
|
("Completed", "completed", FilterState::Completed),
|
||||||
}
|
]
|
||||||
}
|
.iter()
|
||||||
|
.map(|(name, path, _filter)| {
|
||||||
|
rsx!(
|
||||||
|
li {
|
||||||
|
class: "{name}"
|
||||||
|
a {
|
||||||
|
href: "{path}"
|
||||||
|
// onclick: move |_| reducer.set_filter(&filter)
|
||||||
|
"{name}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
// todo
|
||||||
|
let item_text = "";
|
||||||
|
let items_left = "";
|
||||||
|
|
||||||
fn ListHelper<F: Fn(MouseEvent)>(ctx: Context, props: &ListProps<F>) -> DomTree {
|
|
||||||
let k = props.name;
|
|
||||||
let v = props.value;
|
|
||||||
ctx.render(rsx! {
|
ctx.render(rsx! {
|
||||||
li {
|
footer {
|
||||||
class: "flex items-center text-xl"
|
span {
|
||||||
key: "{k}"
|
strong {"{items_left}"}
|
||||||
span { "{k}: {v}" }
|
span {"{item_text} left"}
|
||||||
button {
|
}
|
||||||
"__ Remove"
|
ul {
|
||||||
onclick: {&props.onclick}
|
class: "filters"
|
||||||
|
{toggles}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -13,16 +13,14 @@ pub fn FilterToggles(ctx: Context, props: &()) -> DomTree {
|
|||||||
]
|
]
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(name, path, filter)| {
|
.map(|(name, path, filter)| {
|
||||||
rsx!(
|
rsx!(li {
|
||||||
li {
|
class: "{name}"
|
||||||
class: "{name}"
|
a {
|
||||||
a {
|
"{name}"
|
||||||
href: "{path}"
|
href: "{path}"
|
||||||
onclick: move |_| reducer.set_filter(&filter)
|
onclick: move |_| reducer.set_filter(&filter)
|
||||||
"{name}"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
// todo
|
// todo
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ pub fn TodoEntry(ctx: Context, props: &TodoEntryProps) -> DomTree {
|
|||||||
}
|
}
|
||||||
{is_editing.then(|| rsx!(
|
{is_editing.then(|| rsx!(
|
||||||
input {
|
input {
|
||||||
value: "{contents}"
|
value: "{todo.contents}"
|
||||||
}
|
}
|
||||||
))}
|
))}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,13 +101,6 @@ pub struct TodoEntryProps {
|
|||||||
item: Rc<TodoItem>,
|
item: Rc<TodoItem>,
|
||||||
}
|
}
|
||||||
|
|
||||||
mod mac {
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! TodoEntry {
|
|
||||||
() => {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// pub fn TodoEntry(ctx: Context, props: &TodoEntryProps) -> DomTree {
|
// pub fn TodoEntry(ctx: Context, props: &TodoEntryProps) -> DomTree {
|
||||||
// #[inline_props]
|
// #[inline_props]
|
||||||
pub fn TodoEntry(
|
pub fn TodoEntry(
|
||||||
|
|||||||
@@ -115,6 +115,7 @@ pub struct TodoEntryProps {
|
|||||||
pub fn TodoEntry(ctx: Context, props: &TodoEntryProps) -> DomTree {
|
pub fn TodoEntry(ctx: Context, props: &TodoEntryProps) -> DomTree {
|
||||||
let (is_editing, set_is_editing) = use_state(&ctx, || false);
|
let (is_editing, set_is_editing) = use_state(&ctx, || false);
|
||||||
let todo = use_atom_family(&ctx, &TODOS, props.id);
|
let todo = use_atom_family(&ctx, &TODOS, props.id);
|
||||||
|
let contents = "";
|
||||||
|
|
||||||
ctx.render(rsx! (
|
ctx.render(rsx! (
|
||||||
li {
|
li {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ fn main() {
|
|||||||
<div class="flex flex-col bg-white rounded p-4 w-full max-w-xs">
|
<div class="flex flex-col bg-white rounded p-4 w-full max-w-xs">
|
||||||
// Title
|
// Title
|
||||||
<div class="font-bold text-xl">
|
<div class="font-bold text-xl">
|
||||||
"Jon's awesome site!!11"
|
"Jon's awesome site!!"
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
// Subtext / description
|
// Subtext / description
|
||||||
@@ -45,4 +45,3 @@ fn main() {
|
|||||||
})
|
})
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -542,8 +542,14 @@ fn virtual_event_from_websys_event(event: &web_sys::Event) -> VirtualEvent {
|
|||||||
|
|
||||||
"change" | "input" | "invalid" | "reset" | "submit" => {
|
"change" | "input" | "invalid" | "reset" | "submit" => {
|
||||||
// is a special react events
|
// is a special react events
|
||||||
// let evt: web_sys::FormEvent = event.clone().dyn_into().unwrap();
|
let evt: web_sys::InputEvent = event.clone().dyn_into().expect("wrong event type");
|
||||||
todo!()
|
let value: Option<String> = (&evt).data();
|
||||||
|
let value = value.unwrap_or_default();
|
||||||
|
// let value = (&evt).data().expect("No data to unwrap");
|
||||||
|
|
||||||
|
// todo - this needs to be a "controlled" event
|
||||||
|
// these events won't carry the right data with them
|
||||||
|
VirtualEvent::FormEvent(FormEvent { value })
|
||||||
}
|
}
|
||||||
|
|
||||||
"click" | "contextmenu" | "doubleclick" | "drag" | "dragend" | "dragenter" | "dragexit"
|
"click" | "contextmenu" | "doubleclick" | "drag" | "dragend" | "dragenter" | "dragexit"
|
||||||
|
|||||||
@@ -5,11 +5,11 @@
|
|||||||
use dioxus::prelude::Properties;
|
use dioxus::prelude::Properties;
|
||||||
use fxhash::FxHashMap;
|
use fxhash::FxHashMap;
|
||||||
use web_sys::{window, Document, Element, Event, Node};
|
use web_sys::{window, Document, Element, Event, Node};
|
||||||
|
// use futures::{channel::mpsc, SinkExt, StreamExt};
|
||||||
|
|
||||||
use dioxus::virtual_dom::VirtualDom;
|
use dioxus::virtual_dom::VirtualDom;
|
||||||
pub use dioxus_core as dioxus;
|
pub use dioxus_core as dioxus;
|
||||||
use dioxus_core::{events::EventTrigger, prelude::FC};
|
use dioxus_core::{events::EventTrigger, prelude::FC};
|
||||||
// use futures::{channel::mpsc, SinkExt, StreamExt};
|
|
||||||
|
|
||||||
pub use dioxus_core::prelude;
|
pub use dioxus_core::prelude;
|
||||||
pub mod interpreter;
|
pub mod interpreter;
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ license = "MIT/Apache-2.0"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
# web-view = { git = "https://github.com/Boscop/web-view" }
|
# web-view = { git = "https://github.com/Boscop/web-view" }
|
||||||
web-view = "0.7.3"
|
web-view = "0.7.3"
|
||||||
dioxus-core = { path = "../core", version = "0.1.2" }
|
dioxus-core = { path = "../core", version = "0.1.2", features = ["serde"] }
|
||||||
anyhow = "1.0.38"
|
anyhow = "1.0.38"
|
||||||
argh = "0.1.4"
|
argh = "0.1.4"
|
||||||
serde = "1.0.120"
|
serde = "1.0.120"
|
||||||
|
|||||||
@@ -4,50 +4,43 @@ use dioxus_core::prelude::*;
|
|||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
dioxus_webview::launch(
|
dioxus_webview::launch(
|
||||||
// Customize the webview
|
|
||||||
|builder| {
|
|builder| {
|
||||||
builder
|
builder
|
||||||
.title("Test Dioxus App")
|
.title("Test Dioxus App")
|
||||||
.size(320, 480)
|
.size(320, 480)
|
||||||
.resizable(true)
|
.resizable(false)
|
||||||
.debug(true)
|
.debug(true)
|
||||||
},
|
},
|
||||||
// Props
|
|
||||||
(),
|
(),
|
||||||
// Draw the root component
|
|
||||||
Example,
|
Example,
|
||||||
)
|
)
|
||||||
.expect("Webview finished");
|
.expect("Webview finished");
|
||||||
}
|
}
|
||||||
|
|
||||||
static Example: FC<()> = |ctx, _props| {
|
static Example: FC<()> = |ctx, _props| {
|
||||||
ctx.render(html! {
|
ctx.render(rsx! {
|
||||||
<div>
|
div {
|
||||||
<div class="flex items-center justify-center flex-col">
|
class: "flex items-center justify-center flex-col"
|
||||||
<div class="flex items-center justify-center">
|
div {
|
||||||
<div class="flex flex-col bg-white rounded p-4 w-full max-w-xs">
|
class: "flex items-center justify-center"
|
||||||
// Title
|
div {
|
||||||
<div class="font-bold text-xl"> "Jon's awesome site!!11" </div>
|
class: "flex flex-col bg-white rounded p-4 w-full max-w-xs"
|
||||||
|
div { class: "font-bold text-xl", "Example desktop app" }
|
||||||
// Subtext / description
|
div { class: "text-sm text-gray-500", "This is running natively" }
|
||||||
<div class="text-sm text-gray-500"> "He worked so hard on it :)" </div>
|
div {
|
||||||
|
class: "flex flex-row items-center justify-center mt-6"
|
||||||
<div class="flex flex-row items-center justify-center mt-6">
|
div { class: "font-medium text-6xl", "100%" }
|
||||||
// Main number
|
}
|
||||||
<div class="font-medium text-6xl">
|
div {
|
||||||
"1337"
|
class: "flex flex-row justify-between mt-6"
|
||||||
</div>
|
a {
|
||||||
</div>
|
href: "https://www.dioxuslabs.com"
|
||||||
|
class: "underline"
|
||||||
// Try another
|
"Made with dioxus"
|
||||||
<div class="flex flex-row justify-between mt-6">
|
}
|
||||||
<a href="http://localhost:8080/fib/{}" class="underline">
|
}
|
||||||
"Legit made my own React"
|
}
|
||||||
</a>
|
}
|
||||||
</div>
|
}
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
Reference in new issue
Block a user