windjammer_ui_macro/
lib.rs

1//! Procedural macros for windjammer-ui
2//!
3//! Provides the `#[component]` attribute macro for defining UI components.
4
5use proc_macro::TokenStream;
6use quote::quote;
7use syn::{parse_macro_input, Data, DeriveInput, Fields};
8
9/// Attribute macro for defining a UI component
10///
11/// # Example
12///
13/// ```ignore
14/// use windjammer_ui::prelude::*;
15///
16/// #[component]
17/// struct Counter {
18///     count: i32,
19/// }
20///
21/// impl Counter {
22///     fn render(&self) -> VNode {
23///         // ... render implementation
24///     }
25/// }
26/// ```
27#[proc_macro_attribute]
28pub fn component(_attr: TokenStream, item: TokenStream) -> TokenStream {
29    let input = parse_macro_input!(item as DeriveInput);
30
31    let name = &input.ident;
32    let generics = &input.generics;
33    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
34
35    // Extract fields for state management
36    let fields = match &input.data {
37        Data::Struct(data) => match &data.fields {
38            Fields::Named(fields) => &fields.named,
39            _ => {
40                return syn::Error::new_spanned(
41                    &input,
42                    "Component must be a struct with named fields",
43                )
44                .to_compile_error()
45                .into();
46            }
47        },
48        _ => {
49            return syn::Error::new_spanned(&input, "Component must be a struct")
50                .to_compile_error()
51                .into();
52        }
53    };
54
55    // Generate field accessors
56    let field_names: Vec<_> = fields.iter().filter_map(|f| f.ident.as_ref()).collect();
57    let field_types: Vec<_> = fields.iter().map(|f| &f.ty).collect();
58
59    // Generate the component implementation
60    let expanded = quote! {
61        #[derive(Clone)]
62        #input
63
64        impl #impl_generics #name #ty_generics #where_clause {
65            /// Create a new component instance
66            pub fn new() -> Self {
67                Self {
68                    #(#field_names: Default::default()),*
69                }
70            }
71
72            /// Create a component with custom initial values
73            pub fn with_state(#(#field_names: #field_types),*) -> Self {
74                Self {
75                    #(#field_names),*
76                }
77            }
78        }
79
80        impl #impl_generics Default for #name #ty_generics #where_clause {
81            fn default() -> Self {
82                Self::new()
83            }
84        }
85
86        // Note: Component trait implementation must be done manually by the user
87        // with the render() method in the impl Component for #name block
88
89        // Implement Send + Sync for cross-platform use
90        unsafe impl #impl_generics Send for #name #ty_generics #where_clause {}
91        unsafe impl #impl_generics Sync for #name #ty_generics #where_clause {}
92    };
93
94    TokenStream::from(expanded)
95}
96
97/// Derive macro for component props
98///
99/// # Example
100///
101/// ```ignore
102/// #[derive(Props)]
103/// struct ButtonProps {
104///     text: String,
105///     onClick: Box<dyn Fn()>,
106/// }
107/// ```
108#[proc_macro_derive(Props)]
109pub fn derive_props(input: TokenStream) -> TokenStream {
110    let input = parse_macro_input!(input as DeriveInput);
111    let name = &input.ident;
112    let generics = &input.generics;
113    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
114
115    let expanded = quote! {
116        impl #impl_generics windjammer_ui::component::ComponentProps for #name #ty_generics #where_clause {}
117    };
118
119    TokenStream::from(expanded)
120}
121
122#[cfg(test)]
123mod tests {
124    // Tests for proc macros are typically done with trybuild in tests/ directory
125}