Rsx children

This commit is contained in:
2026-01-30 22:54:56 +01:00
parent 91f0878edc
commit 03191b0a57
5 changed files with 147 additions and 11 deletions
+21
View File
@@ -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
View File
@@ -3,7 +3,11 @@ use proc_macro2::TokenStream;
use quote::quote;
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! {{
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);
quote! {
r.static_scope(move |scope| {#emit});
@@ -125,9 +129,38 @@ fn emit_node(node: &RsxNode) -> TokenStream {
}));
},
RsxNode::Component(expr) => quote! {
scope.child(#expr, None);
},
RsxNode::Component {
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! {
#m.mount();
+85 -4
View File
@@ -1,7 +1,8 @@
use syn::braced;
use syn::{
Expr, Ident, LitStr, Pat, Result, Token,
Expr, Ident, LitStr, Pat, Path, Result, Token,
parse::{Parse, ParseStream},
token::Brace,
};
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 {
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),
If {
deps: Vec<Dep>,
@@ -116,11 +128,80 @@ impl Parse for RsxNode {
return Ok(RsxNode::Text(input.parse()?));
}
// Component: path (e.g. HelloWorld) or struct init (e.g. HelloWorld { t: 10 })
Ok(RsxNode::Component(input.parse()?))
// Component: Path or Path { props, children }
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>> {
let mut nodes = Vec::new();
while !input.is_empty() {
+1
View File
@@ -11,6 +11,7 @@ pub mod state;
pub mod prelude {
pub use crate::component::*;
pub use crate::engine::*;
pub use crate::frontend::*;
pub use crate::render::*;
pub use crate::state::*;
pub use crate::View;
+2 -2
View File
@@ -115,11 +115,11 @@ impl<T> DerefMut for Inner<'_, T> {
}
}
pub trait HookDependency {
pub trait HookDependency: Send + Sync {
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) {
self.dependents.lock().unwrap().push(hook);
}