Rsx children
This commit is contained in:
@@ -0,0 +1,21 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use osui::prelude::*;
|
||||||
|
|
||||||
|
pub fn main() {
|
||||||
|
let engine = Console::new();
|
||||||
|
engine.run(App {}).expect("Failed to run engine");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
fn App(cx: &Arc<Context>) -> View {
|
||||||
|
rsx! {
|
||||||
|
MyComponent { "Hello world!" }
|
||||||
|
}
|
||||||
|
.view(cx.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
fn MyComponent(cx: &Arc<Context>, children: &Rsx) -> View {
|
||||||
|
children.view(cx.clone())
|
||||||
|
}
|
||||||
+38
-5
@@ -3,7 +3,11 @@ use proc_macro2::TokenStream;
|
|||||||
use quote::quote;
|
use quote::quote;
|
||||||
|
|
||||||
pub fn emit_rsx(root: RsxRoot) -> TokenStream {
|
pub fn emit_rsx(root: RsxRoot) -> TokenStream {
|
||||||
let nodes = root.nodes.iter().map(emit_node_scope);
|
emit_rsx_vec(&root.nodes)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn emit_rsx_vec(nodes: &Vec<RsxNode>) -> TokenStream {
|
||||||
|
let nodes = nodes.iter().map(emit_node_scope);
|
||||||
|
|
||||||
quote! {{
|
quote! {{
|
||||||
let mut r = osui::frontend::Rsx::new();
|
let mut r = osui::frontend::Rsx::new();
|
||||||
@@ -55,7 +59,7 @@ fn emit_node_scope(node: &RsxNode) -> TokenStream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RsxNode::Component(_) => {
|
RsxNode::Component { .. } => {
|
||||||
let emit = emit_node(node);
|
let emit = emit_node(node);
|
||||||
quote! {
|
quote! {
|
||||||
r.static_scope(move |scope| {#emit});
|
r.static_scope(move |scope| {#emit});
|
||||||
@@ -125,9 +129,38 @@ fn emit_node(node: &RsxNode) -> TokenStream {
|
|||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
RsxNode::Component(expr) => quote! {
|
RsxNode::Component {
|
||||||
scope.child(#expr, None);
|
path,
|
||||||
},
|
props,
|
||||||
|
children,
|
||||||
|
} => {
|
||||||
|
let prop_inits = props.iter().map(|p| {
|
||||||
|
let name = &p.name;
|
||||||
|
let value = &p.value;
|
||||||
|
quote! { #name: #value }
|
||||||
|
});
|
||||||
|
|
||||||
|
let emit_children = emit_rsx_vec(children);
|
||||||
|
|
||||||
|
let component_expr = if children.len() > 0 {
|
||||||
|
quote! {
|
||||||
|
#path {
|
||||||
|
#(#prop_inits,)*
|
||||||
|
children: #emit_children
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
quote! {
|
||||||
|
#path {
|
||||||
|
#(#prop_inits,)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
scope.child(#component_expr, None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
RsxNode::Mount(m) => quote! {
|
RsxNode::Mount(m) => quote! {
|
||||||
#m.mount();
|
#m.mount();
|
||||||
|
|||||||
+85
-4
@@ -1,7 +1,8 @@
|
|||||||
use syn::braced;
|
use syn::braced;
|
||||||
use syn::{
|
use syn::{
|
||||||
Expr, Ident, LitStr, Pat, Result, Token,
|
Expr, Ident, LitStr, Pat, Path, Result, Token,
|
||||||
parse::{Parse, ParseStream},
|
parse::{Parse, ParseStream},
|
||||||
|
token::Brace,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct RsxRoot {
|
pub struct RsxRoot {
|
||||||
@@ -18,9 +19,20 @@ impl Parse for RsxRoot {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A single prop: `ident: expr`.
|
||||||
|
pub struct RsxProp {
|
||||||
|
pub name: Ident,
|
||||||
|
pub value: Expr,
|
||||||
|
}
|
||||||
|
|
||||||
pub enum RsxNode {
|
pub enum RsxNode {
|
||||||
Text(LitStr),
|
Text(LitStr),
|
||||||
Component(Expr),
|
/// Component with optional props and children: `Path { prop: expr, "child", ... }` or bare `Path`.
|
||||||
|
Component {
|
||||||
|
path: Path,
|
||||||
|
props: Vec<RsxProp>,
|
||||||
|
children: Vec<RsxNode>,
|
||||||
|
},
|
||||||
Mount(Ident),
|
Mount(Ident),
|
||||||
If {
|
If {
|
||||||
deps: Vec<Dep>,
|
deps: Vec<Dep>,
|
||||||
@@ -116,11 +128,80 @@ impl Parse for RsxNode {
|
|||||||
return Ok(RsxNode::Text(input.parse()?));
|
return Ok(RsxNode::Text(input.parse()?));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Component: path (e.g. HelloWorld) or struct init (e.g. HelloWorld { t: 10 })
|
// Component: Path or Path { props, children }
|
||||||
Ok(RsxNode::Component(input.parse()?))
|
parse_component_invocation(input)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parses Path or Path { ... } into RsxNode::Component.
|
||||||
|
fn parse_component_invocation(input: ParseStream) -> Result<RsxNode> {
|
||||||
|
let path: Path = input.parse()?;
|
||||||
|
|
||||||
|
if !input.peek(Brace) {
|
||||||
|
return Ok(RsxNode::Component {
|
||||||
|
path,
|
||||||
|
props: Vec::new(),
|
||||||
|
children: Vec::new(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let content;
|
||||||
|
braced!(content in input);
|
||||||
|
|
||||||
|
let (props, children) = parse_props_and_children(&content)?;
|
||||||
|
|
||||||
|
Ok(RsxNode::Component {
|
||||||
|
path,
|
||||||
|
props,
|
||||||
|
children,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parses comma-separated "ident: expr" (props) and RsxNode (children) inside braces.
|
||||||
|
fn parse_props_and_children(input: ParseStream) -> Result<(Vec<RsxProp>, Vec<RsxNode>)> {
|
||||||
|
let mut props = Vec::new();
|
||||||
|
let mut children = Vec::new();
|
||||||
|
|
||||||
|
while !input.is_empty() {
|
||||||
|
if input.peek(LitStr) {
|
||||||
|
children.push(input.parse()?);
|
||||||
|
} else {
|
||||||
|
let path: Path = input.parse()?;
|
||||||
|
|
||||||
|
if input.peek(Token![:]) {
|
||||||
|
let name = path
|
||||||
|
.get_ident()
|
||||||
|
.cloned()
|
||||||
|
.ok_or_else(|| input.error("prop name must be a single identifier"))?;
|
||||||
|
input.parse::<Token![:]>()?;
|
||||||
|
let value: Expr = input.parse()?;
|
||||||
|
props.push(RsxProp { name, value });
|
||||||
|
} else if input.peek(Brace) {
|
||||||
|
let nested_content;
|
||||||
|
braced!(nested_content in input);
|
||||||
|
let (nested_props, nested_children) = parse_props_and_children(&nested_content)?;
|
||||||
|
children.push(RsxNode::Component {
|
||||||
|
path,
|
||||||
|
props: nested_props,
|
||||||
|
children: nested_children,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
children.push(RsxNode::Component {
|
||||||
|
path,
|
||||||
|
props: Vec::new(),
|
||||||
|
children: Vec::new(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !input.is_empty() {
|
||||||
|
input.parse::<Token![,]>()?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((props, children))
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_children(input: ParseStream) -> Result<Vec<RsxNode>> {
|
fn parse_children(input: ParseStream) -> Result<Vec<RsxNode>> {
|
||||||
let mut nodes = Vec::new();
|
let mut nodes = Vec::new();
|
||||||
while !input.is_empty() {
|
while !input.is_empty() {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ pub mod state;
|
|||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
pub use crate::component::*;
|
pub use crate::component::*;
|
||||||
pub use crate::engine::*;
|
pub use crate::engine::*;
|
||||||
|
pub use crate::frontend::*;
|
||||||
pub use crate::render::*;
|
pub use crate::render::*;
|
||||||
pub use crate::state::*;
|
pub use crate::state::*;
|
||||||
pub use crate::View;
|
pub use crate::View;
|
||||||
|
|||||||
+2
-2
@@ -115,11 +115,11 @@ impl<T> DerefMut for Inner<'_, T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait HookDependency {
|
pub trait HookDependency: Send + Sync {
|
||||||
fn on_update(&self, hook: HookEffect);
|
fn on_update(&self, hook: HookEffect);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> HookDependency for State<T> {
|
impl<T: Send + Sync> HookDependency for State<T> {
|
||||||
fn on_update(&self, hook: HookEffect) {
|
fn on_update(&self, hook: HookEffect) {
|
||||||
self.dependents.lock().unwrap().push(hook);
|
self.dependents.lock().unwrap().push(hook);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user