From b0eaa763984607b2c1387a1e887f3672b1600e0d Mon Sep 17 00:00:00 2001 From: Leo dev Date: Sun, 1 Feb 2026 16:34:27 +0100 Subject: [PATCH] View plugins --- examples/hello_world.rs | 7 +++++++ macros/src/emit.rs | 27 ++++++++++++++++++++++++-- macros/src/parse.rs | 42 ++++++++++++++++++++++++++++++++++++++++- 3 files changed, 73 insertions(+), 3 deletions(-) diff --git a/examples/hello_world.rs b/examples/hello_world.rs index f3616dd..f81be34 100644 --- a/examples/hello_world.rs +++ b/examples/hello_world.rs @@ -8,6 +8,7 @@ pub fn main() { #[component] fn App(cx: &Arc) -> View { rsx! { + impl my_center(11, 1) Card { content: "Hello World".to_string() } } .view(&cx) @@ -22,3 +23,9 @@ fn Card(cx: &Arc, content: String) -> View { } .view(&cx) } + +fn my_center(ctx: &mut DrawContext, view: View, w: u16, h: u16) { + let (x, y) = ((ctx.area.width - w) / 2, (ctx.area.height - h) / 2); + let area = ctx.allocate(x, y, w, h); + ctx.draw_view(area, view); +} diff --git a/macros/src/emit.rs b/macros/src/emit.rs index 09a7f97..e861b20 100644 --- a/macros/src/emit.rs +++ b/macros/src/emit.rs @@ -51,6 +51,20 @@ fn emit_deps_vec(deps: &[Dep]) -> TokenStream { } } +/// Emits a Vec of plugins +fn emit_plugins(deps: &[ViewPlugin]) -> TokenStream { + deps.iter() + .map(|d| { + let path = &d.path; + if let Some(args) = &d.args { + quote!( #path(ctx, view #(,#args)*); ) + } else { + quote!( #path(ctx, view); ) + } + }) + .collect() +} + /// Emits code for a single node within a scope fn emit_node_scope(node: &RsxNode) -> TokenStream { match node { @@ -144,6 +158,7 @@ fn emit_node(node: &RsxNode) -> TokenStream { } RsxNode::Component { + plugins, path, props, children, @@ -171,8 +186,16 @@ fn emit_node(node: &RsxNode) -> TokenStream { } }; - quote! { - scope.child(#component_expr, None); + if plugins.len() == 0 { + quote! { + scope.child(#component_expr, None); + } + } else { + let plugins_emit = emit_plugins(plugins); + + quote! { + scope.child(#component_expr, Some(std::sync::Arc::new(|ctx, view| {#plugins_emit}))); + } } } diff --git a/macros/src/parse.rs b/macros/src/parse.rs index 2cfc37c..2da9553 100644 --- a/macros/src/parse.rs +++ b/macros/src/parse.rs @@ -5,13 +5,14 @@ use std::collections::HashSet; use proc_macro2::Span; -use syn::braced; use syn::parse::discouraged::Speculative; +use syn::punctuated::Punctuated; use syn::{ Expr, Ident, LitStr, Pat, Path, Result, Token, parse::{Parse, ParseStream}, token::Brace, }; +use syn::{braced, parenthesized}; /// Root of an RSX expression pub struct RsxRoot { @@ -45,6 +46,8 @@ pub enum RsxNode { Expr(Expr), /// Component instantiation: `Component { prop: value, ... }` Component { + /// View plugins + plugins: Vec, /// Component path (e.g., `my_module::MyComponent`) path: Path, /// Component properties @@ -82,6 +85,11 @@ pub struct Dep { pub is_dep: bool, } +pub struct ViewPlugin { + pub path: Path, + pub args: Option>, +} + fn parse_deps(input: ParseStream) -> Result> { let mut deps = Vec::new(); @@ -122,6 +130,35 @@ fn parse_deps(input: ParseStream) -> Result> { Ok(deps) } +fn parse_plugins(input: ParseStream) -> Result> { + let mut plugins = Vec::new(); + + if input.peek(Token![impl]) { + input.parse::()?; + loop { + let path: Path = input.parse()?; + + let args = if input.peek(syn::token::Paren) { + let content; + parenthesized!(content in input); + let exprs = Punctuated::::parse_terminated(&content)?; + Some(exprs.into_iter().collect()) + } else { + None + }; + + plugins.push(ViewPlugin { path, args }); + + if !input.peek(Token![,]) { + break; + } + input.parse::()?; + } + } + + Ok(plugins) +} + impl Parse for RsxNode { fn parse(input: ParseStream) -> Result { // @{ $expr } @@ -201,10 +238,12 @@ impl Parse for RsxNode { /// Parses Path or Path { ... } into RsxNode::Component. fn parse_component_invocation(input: ParseStream) -> Result { + let plugins = parse_plugins(input)?; let path: Path = input.parse()?; if !input.peek(Brace) { return Ok(RsxNode::Component { + plugins, path, props: Vec::new(), children: Vec::new(), @@ -217,6 +256,7 @@ fn parse_component_invocation(input: ParseStream) -> Result { let (props, children) = parse_props_and_children(&content)?; Ok(RsxNode::Component { + plugins, path, props, children,