iocraft_macros/
lib.rs

1//! This crate defines the macros used by `iocraft`.
2
3#![warn(missing_docs)]
4
5use proc_macro::TokenStream;
6use proc_macro2::Span;
7use quote::{quote, ToTokens};
8use syn::{
9    braced, parenthesized,
10    parse::{Parse, ParseStream, Parser},
11    parse_macro_input,
12    punctuated::Punctuated,
13    spanned::Spanned,
14    token::{Brace, Comma, Paren},
15    DeriveInput, Error, Expr, FieldValue, FnArg, GenericParam, Generics, Ident, ItemFn, ItemStruct,
16    Lifetime, Lit, Member, Pat, Result, Token, Type, TypePath, WhereClause, WherePredicate,
17};
18use uuid::Uuid;
19
20enum ParsedElementChild {
21    Element(ParsedElement),
22    Expr(Expr),
23}
24
25struct ParsedElement {
26    ty: TypePath,
27    props: Punctuated<FieldValue, Comma>,
28    children: Vec<ParsedElementChild>,
29}
30
31impl Parse for ParsedElement {
32    /// Parses a single element of the form:
33    ///
34    /// MyComponent(my_prop: "foo") {
35    ///     // children
36    /// }
37    fn parse(input: ParseStream) -> Result<Self> {
38        let ty: TypePath = input.parse()?;
39
40        let props = if input.peek(Paren) {
41            let props_input;
42            parenthesized!(props_input in input);
43            Punctuated::parse_terminated(&props_input)?
44        } else {
45            Punctuated::new()
46        };
47
48        let mut children = Vec::new();
49        if input.peek(Brace) {
50            let children_input;
51            braced!(children_input in input);
52            while !children_input.is_empty() {
53                if children_input.peek(Token![#]) {
54                    children_input.parse::<Token![#]>()?;
55                    let child_input;
56                    parenthesized!(child_input in children_input);
57                    children.push(ParsedElementChild::Expr(child_input.parse()?));
58                } else {
59                    children.push(ParsedElementChild::Element(children_input.parse()?));
60                }
61            }
62        }
63
64        Ok(Self {
65            props,
66            ty,
67            children,
68        })
69    }
70}
71
72impl ToTokens for ParsedElement {
73    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
74        let ty = &self.ty;
75
76        let decl_key = Uuid::new_v4().as_u128();
77
78        let key = self
79            .props
80            .iter()
81            .find_map(|FieldValue { member, expr, .. }| match member {
82                Member::Named(ident) if ident == "key" => Some(quote!((#decl_key, #expr))),
83                _ => None,
84            })
85            .unwrap_or_else(|| quote!(#decl_key));
86
87        let prop_assignments = self
88            .props
89            .iter()
90            .filter_map(|FieldValue { member, expr, .. }| match member {
91                Member::Named(ident) if ident == "key" => None,
92                _ => Some(match expr {
93                    Expr::Lit(lit) => match &lit.lit {
94                        Lit::Int(lit) if lit.suffix() == "pct" => {
95                            let value = lit.base10_parse::<f32>().unwrap();
96                            quote!(_iocraft_props.#member = ::iocraft::Percent(#value).into())
97                        }
98                        Lit::Float(lit) if lit.suffix() == "pct" => {
99                            let value = lit.base10_parse::<f32>().unwrap();
100                            quote!(_iocraft_props.#member = ::iocraft::Percent(#value).into())
101                        }
102                        _ => quote!(_iocraft_props.#member = (#expr).into()),
103                    },
104                    _ => quote!(_iocraft_props.#member = (#expr).into()),
105                }),
106            })
107            .collect::<Vec<_>>();
108
109        let set_children = if !self.children.is_empty() {
110            let children = self.children.iter().map(|child| match child {
111                ParsedElementChild::Element(child) => quote!(#child),
112                ParsedElementChild::Expr(expr) => quote!(#expr),
113            });
114            Some(quote! {
115                #(::iocraft::extend_with_elements(&mut _iocraft_element.props.children, #children);)*
116            })
117        } else {
118            None
119        };
120
121        tokens.extend(quote! {
122            {
123                type Props<'a> = <#ty as ::iocraft::ElementType>::Props<'a>;
124                let mut _iocraft_props: Props = Default::default();
125                #(#prop_assignments;)*
126                let mut _iocraft_element = ::iocraft::Element::<#ty>{
127                    key: ::iocraft::ElementKey::new(#key),
128                    props: _iocraft_props,
129                };
130                #set_children
131                _iocraft_element
132            }
133        });
134    }
135}
136
137/// Used to declare an element and its properties.
138///
139/// Elements are declared starting with their type. All properties are optional, so the simplest
140/// use of this macro is just a type name:
141///
142/// ```
143/// # use iocraft::prelude::*;
144/// # fn my_element() -> Element<'static, View> {
145/// element!(View)
146/// # }
147/// ```
148///
149/// This will evaluate to an `Element<'static, View>` with no properties set.
150///
151/// To specify properties, you can add them in a parenthesized block after the type name:
152///
153/// ```
154/// # use iocraft::prelude::*;
155/// # fn my_element() -> Element<'static, View> {
156/// element! {
157///     View(width: 80, height: 24, background_color: Color::Green)
158/// }
159/// # }
160/// ```
161///
162/// If the element has a `children` property, you can pass one or more child elements in braces like so:
163///
164/// ```
165/// # use iocraft::prelude::*;
166/// # fn my_element() -> Element<'static, View> {
167/// element! {
168///     View {
169///         Text(content: "Hello, world!")
170///     }
171/// }
172/// # }
173/// ```
174///
175/// Lastly, you can use Rust to conditionally add child elements via `#()` blocks that evaluate
176/// to any iterator type:
177///
178/// ```
179/// # use iocraft::prelude::*;
180/// # fn my_element(show_greeting: bool) -> Element<'static, View> {
181/// element! {
182///     View {
183///         #(if show_greeting {
184///             Some(element! {
185///                 Text(content: "Hello, world!")
186///             })
187///         } else {
188///             None
189///         })
190///     }
191/// }
192/// # }
193/// ```
194///
195/// If you're rendering a dynamic UI, you will want to ensure that when adding multiple
196/// elements via an iterator a unique key is specified for each one. Otherwise, the elements
197/// may not correctly maintain their state across renders. This is done using the special `key`
198/// property, which can be given to any element:
199///
200/// ```
201/// # use iocraft::prelude::*;
202/// # struct User { id: i32, name: String }
203/// # fn my_element(users: Vec<User>) -> Element<'static, View> {
204/// element! {
205///     View {
206///         #(users.iter().map(|user| element! {
207///             View(key: user.id, flex_direction: FlexDirection::Column) {
208///                 Text(content: format!("Hello, {}!", user.name))
209///             }
210///         }))
211///     }
212/// }
213/// # }
214/// ```
215#[proc_macro]
216pub fn element(input: TokenStream) -> TokenStream {
217    let element = parse_macro_input!(input as ParsedElement);
218    quote!(#element).into()
219}
220
221struct ParsedProps {
222    def: ItemStruct,
223}
224
225impl Parse for ParsedProps {
226    fn parse(input: ParseStream) -> Result<Self> {
227        let def: ItemStruct = input.parse()?;
228
229        // Make sure there are no props named "key", as that's a reserved name.
230        for field in &def.fields {
231            if let Some(ident) = &field.ident {
232                if ident == "key" {
233                    return Err(Error::new(
234                        ident.span(),
235                        "the `key` property name is reserved",
236                    ));
237                }
238            }
239        }
240
241        Ok(Self { def })
242    }
243}
244
245impl ToTokens for ParsedProps {
246    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
247        let def = &self.def;
248        let name = &def.ident;
249        let where_clause = &def.generics.where_clause;
250
251        let has_generics = !def.generics.params.is_empty();
252        let lifetime_generic_count = def
253            .generics
254            .params
255            .iter()
256            .filter(|param| matches!(param, GenericParam::Lifetime(_)))
257            .count();
258
259        let generics = &def.generics;
260
261        let generics_names = def.generics.params.iter().map(|param| match param {
262            GenericParam::Type(ty) => {
263                let name = &ty.ident;
264                quote!(#name)
265            }
266            GenericParam::Lifetime(lt) => {
267                let name = &lt.lifetime;
268                quote!(#name)
269            }
270            GenericParam::Const(c) => {
271                let name = &c.ident;
272                quote!(#name)
273            }
274        });
275        let bracketed_generic_names = match has_generics {
276            true => quote!(<#(#generics_names),*>),
277            false => quote!(),
278        };
279
280        // If the struct is generic over lifetimes, emit code that will break things at compile
281        // time when the struct is not covariant with respect to its lifetimes.
282        if lifetime_generic_count > 0 {
283            let generic_decls = {
284                let mut lifetime_index = 0;
285                def.generics.params.iter().map(move |param| match param {
286                    GenericParam::Lifetime(_) => {
287                        let a = Lifetime::new(
288                            format!("'a{}", lifetime_index).as_str(),
289                            Span::call_site(),
290                        );
291                        let b = Lifetime::new(
292                            format!("'b{}", lifetime_index).as_str(),
293                            Span::call_site(),
294                        );
295                        lifetime_index += 1;
296                        quote!(#a, #b: #a)
297                    }
298                    _ => quote!(#param),
299                })
300            };
301
302            let test_args = ["a", "b"].iter().map(|arg| {
303                let mut lifetime_index = 0;
304                let generic_params = def.generics.params.iter().map(|param| match param {
305                    GenericParam::Type(ty) => {
306                        let name = &ty.ident;
307                        quote!(#name)
308                    }
309                    GenericParam::Lifetime(_) => {
310                        let lt = Lifetime::new(
311                            format!("'{}{}", arg, lifetime_index).as_str(),
312                            Span::call_site(),
313                        );
314                        lifetime_index += 1;
315                        quote!(#lt)
316                    }
317                    GenericParam::Const(c) => {
318                        let name = &c.ident;
319                        quote!(#name)
320                    }
321                });
322                let arg_ident = Ident::new(arg, Span::call_site());
323                quote!(#arg_ident: &#name<#(#generic_params),*>)
324            });
325
326            tokens.extend(quote! {
327                const _: () = {
328                    fn take_two<T>(_a: T, _b: T) {}
329
330                    fn test_type_covariance<#(#generic_decls),*>(#(#test_args),*) {
331                        take_two(a, b)
332                    }
333                };
334            });
335        }
336
337        tokens.extend(quote! {
338            unsafe impl #generics ::iocraft::Props for #name #bracketed_generic_names #where_clause {}
339        });
340    }
341}
342
343/// Makes a struct available for use as component properties.
344///
345/// Most importantly, this marks a struct as being
346/// [covariant](https://doc.rust-lang.org/nomicon/subtyping.html). If the struct is not actually
347/// covariant, compilation will fail.
348///
349/// Compilation will also fail if the struct contains a "key" field, as that name is reserved to
350/// facilitate tracking state across renders:
351///
352/// ```compile_fail
353/// # use iocraft::prelude::*;
354/// #[derive(Default, Props)]
355/// struct MyProps {
356///     key: i32,
357/// }
358/// ```
359#[proc_macro_derive(Props)]
360pub fn derive_props(item: TokenStream) -> TokenStream {
361    let props = parse_macro_input!(item as ParsedProps);
362    quote!(#props).into()
363}
364
365struct ParsedComponent {
366    f: ItemFn,
367    props_type: Option<Box<Type>>,
368    impl_args: Vec<proc_macro2::TokenStream>,
369}
370
371impl Parse for ParsedComponent {
372    fn parse(input: ParseStream) -> Result<Self> {
373        let f: ItemFn = input.parse()?;
374
375        let mut props_type = None;
376        let mut impl_args = Vec::new();
377
378        for arg in &f.sig.inputs {
379            match arg {
380                FnArg::Typed(arg) => {
381                    let name = match &*arg.pat {
382                        Pat::Ident(arg) => arg.ident.to_string(),
383                        _ => return Err(Error::new(arg.pat.span(), "invalid argument")),
384                    };
385
386                    match name.as_str() {
387                        "props" | "_props" => {
388                            if props_type.is_some() {
389                                return Err(Error::new(arg.span(), "duplicate `props` argument"));
390                            }
391                            match &*arg.ty {
392                                Type::Reference(r) => {
393                                    props_type = Some(r.elem.clone());
394                                    impl_args.push(quote!(props));
395                                }
396                                _ => {
397                                    return Err(Error::new(
398                                        arg.ty.span(),
399                                        "invalid `props` type (must be a reference)",
400                                    ))
401                                }
402                            }
403                        }
404                        "hooks" | "_hooks" => match &*arg.ty {
405                            Type::Reference(_) => {
406                                impl_args.push(quote!(&mut hooks));
407                            }
408                            Type::Path(_) => {
409                                impl_args.push(quote!(hooks));
410                            }
411                            _ => {
412                                return Err(Error::new(
413                                    arg.ty.span(),
414                                    "invalid `hooks` type (must be a reference or a value)",
415                                ))
416                            }
417                        },
418                        _ => {
419                            return Err(Error::new(
420                                arg.span(),
421                                "unexpected argument (must be named `props` or `hooks`)",
422                            ))
423                        }
424                    }
425                }
426                _ => return Err(Error::new(arg.span(), "invalid argument")),
427            }
428        }
429
430        Ok(Self {
431            f,
432            props_type,
433            impl_args,
434        })
435    }
436}
437
438impl ToTokens for ParsedComponent {
439    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
440        let attrs = &self.f.attrs;
441        let vis = &self.f.vis;
442        let name = &self.f.sig.ident;
443        let args = &self.f.sig.inputs;
444        let block = &self.f.block;
445        let output = &self.f.sig.output;
446        let generics = &self.f.sig.generics;
447        let lifetime_generics = {
448            Generics {
449                params: generics
450                    .params
451                    .iter()
452                    .filter(|param| matches!(param, GenericParam::Lifetime(_)))
453                    .cloned()
454                    .collect(),
455                where_clause: generics
456                    .where_clause
457                    .as_ref()
458                    .map(|where_clause| WhereClause {
459                        where_token: where_clause.where_token,
460                        predicates: where_clause
461                            .predicates
462                            .iter()
463                            .filter(|predicate| matches!(predicate, WherePredicate::Lifetime(_)))
464                            .cloned()
465                            .collect(),
466                    }),
467                ..generics.clone()
468            }
469        };
470        let (lifetime_impl_generics, _lifetime_ty_generics, lifetime_where_clause) =
471            lifetime_generics.split_for_impl();
472        let type_generics = {
473            Generics {
474                params: generics
475                    .params
476                    .iter()
477                    .filter(|param| !matches!(param, GenericParam::Lifetime(_)))
478                    .cloned()
479                    .collect(),
480                where_clause: generics
481                    .where_clause
482                    .as_ref()
483                    .map(|where_clause| WhereClause {
484                        where_token: where_clause.where_token,
485                        predicates: where_clause
486                            .predicates
487                            .iter()
488                            .filter(|predicate| !matches!(predicate, WherePredicate::Lifetime(_)))
489                            .cloned()
490                            .collect(),
491                    }),
492                ..generics.clone()
493            }
494        };
495        let (impl_generics, ty_generics, where_clause) = type_generics.split_for_impl();
496        let ty_generic_names = type_generics.params.iter().filter_map(|param| match param {
497            GenericParam::Type(ty) => {
498                let name = &ty.ident;
499                Some(quote!(#name))
500            }
501            _ => None,
502        });
503        let impl_args = &self.impl_args;
504
505        let props_type_name = self
506            .props_type
507            .as_ref()
508            .map(|ty| quote!(#ty))
509            .unwrap_or_else(|| quote!(::iocraft::NoProps));
510
511        tokens.extend(quote! {
512            #(#attrs)*
513            #vis struct #name #impl_generics {
514                _marker: std::marker::PhantomData<fn(#(#ty_generic_names),*)>,
515            }
516
517            impl #impl_generics #name #ty_generics #where_clause {
518                fn implementation #lifetime_impl_generics (#args) #output #lifetime_where_clause #block
519            }
520
521            impl #impl_generics ::iocraft::Component for #name #ty_generics #where_clause {
522                type Props<'a> = #props_type_name;
523
524                fn new(_props: &Self::Props<'_>) -> Self {
525                    Self{
526                        _marker: std::marker::PhantomData,
527                    }
528                }
529
530                fn update(&mut self, props: &mut Self::Props<'_>, mut hooks: ::iocraft::Hooks, updater: &mut ::iocraft::ComponentUpdater) {
531                    let mut e = {
532                        let mut hooks = hooks.with_context_stack(updater.component_context_stack());
533                        Self::implementation(#(#impl_args),*).into()
534                    };
535                    updater.set_transparent_layout(true);
536                    updater.update_children([&mut e], None);
537                }
538            }
539        });
540    }
541}
542
543/// Defines a custom component type.
544///
545/// Custom components are defined by adding this macro to a function that returns an `Element`:
546///
547/// ```
548/// # use iocraft::prelude::*;
549/// # use std::time::Duration;
550/// #[component]
551/// fn Counter(mut hooks: Hooks) -> impl Into<AnyElement<'static>> {
552///     let mut count = hooks.use_state(|| 0);
553///
554///     hooks.use_future(async move {
555///         loop {
556///             smol::Timer::after(Duration::from_millis(100)).await;
557///             count += 1;
558///         }
559///     });
560///
561///     element! {
562///         Text(color: Color::Blue, content: format!("counter: {}", count))
563///     }
564/// }
565/// ```
566///
567/// The function is allowed to take up to two arguments, one named `props`, for the component's
568/// properties and one named `hooks`, for hooks.
569///
570/// Here is an example of a component that takes a reference to a `Vec` of `User` structs via properties:
571///
572/// ```
573/// # use iocraft::prelude::*;
574/// # struct User {
575/// #     id: i32,
576/// #     name: String,
577/// #     email: String,
578/// # }
579/// #[derive(Default, Props)]
580/// struct UsersTableProps<'a> {
581///     users: Option<&'a Vec<User>>,
582/// }
583///
584/// #[component]
585/// fn UsersTable<'a>(props: &UsersTableProps<'a>) -> impl Into<AnyElement<'a>> {
586///     element! {
587///         View(
588///             margin_top: 1,
589///             margin_bottom: 1,
590///             flex_direction: FlexDirection::Column,
591///             width: 60,
592///             border_style: BorderStyle::Round,
593///             border_color: Color::Cyan,
594///         ) {
595///             View(border_style: BorderStyle::Single, border_edges: Edges::Bottom, border_color: Color::Grey) {
596///                 View(width: 10pct, justify_content: JustifyContent::End, padding_right: 2) {
597///                     Text(content: "Id", weight: Weight::Bold, decoration: TextDecoration::Underline)
598///                 }
599///
600///                 View(width: 40pct) {
601///                     Text(content: "Name", weight: Weight::Bold, decoration: TextDecoration::Underline)
602///                 }
603///
604///                 View(width: 50pct) {
605///                     Text(content: "Email", weight: Weight::Bold, decoration: TextDecoration::Underline)
606///                 }
607///             }
608///
609///             #(props.users.map(|users| users.iter().enumerate().map(|(i, user)| element! {
610///                 View(background_color: if i % 2 == 0 { None } else { Some(Color::DarkGrey) }) {
611///                     View(width: 10pct, justify_content: JustifyContent::End, padding_right: 2) {
612///                         Text(content: user.id.to_string())
613///                     }
614///
615///                     View(width: 40pct) {
616///                         Text(content: user.name.clone())
617///                     }
618///
619///                     View(width: 50pct) {
620///                         Text(content: user.email.clone())
621///                     }
622///                 }
623///             })).into_iter().flatten())
624///         }
625///     }
626/// }
627/// ```
628///
629/// Components can also be generic:
630///
631/// ```
632/// # use iocraft::prelude::*;
633/// #[derive(Default, Props)]
634/// struct MyGenericComponentProps<T: Send + Sync> {
635///     items: Vec<T>,
636/// }
637///
638/// #[component]
639/// fn MyGenericComponent<T: Send + Sync + 'static>(
640///     _props: &MyGenericComponentProps<T>,
641/// ) -> impl Into<AnyElement<'static>> {
642///     element!(View)
643/// }
644/// ```
645///
646/// However, note that generic type parameters must be `'static`.
647#[proc_macro_attribute]
648pub fn component(_attr: TokenStream, item: TokenStream) -> TokenStream {
649    let component = parse_macro_input!(item as ParsedComponent);
650    quote!(#component).into()
651}
652
653#[doc(hidden)]
654#[proc_macro_attribute]
655pub fn with_layout_style_props(_attr: TokenStream, item: TokenStream) -> TokenStream {
656    let layout_style_fields = [
657        quote! {
658            /// Sets the display mode for the element. Defaults to [`Display::Flex`].
659            ///
660            /// See [the MDN documentation for display](https://developer.mozilla.org/en-US/docs/Web/CSS/display).
661            pub display: ::iocraft::Display
662        },
663        quote! {
664            /// Sets the width of the element.
665            pub width: ::iocraft::Size
666        },
667        quote! {
668            /// Sets the height of the element.
669            pub height: ::iocraft::Size
670        },
671        quote! {
672            /// Sets the minimum width of the element.
673            pub min_width: ::iocraft::Size
674        },
675        quote! {
676            /// Sets the minimum height of the element.
677            pub min_height: ::iocraft::Size
678        },
679        quote! {
680            /// Sets the maximum width of the element.
681            pub max_width: ::iocraft::Size
682        },
683        quote! {
684            /// Sets the maximum height of the element.
685            pub max_height: ::iocraft::Size
686        },
687        quote! {
688            /// Defines the gaps in between rows or columns of flex items.
689            ///
690            /// See [the MDN documentation for gap](https://developer.mozilla.org/en-US/docs/Web/CSS/gap).
691            pub gap: ::iocraft::Gap
692        },
693        quote! {
694            /// Defines the gaps in between columns of flex items.
695            ///
696            /// See [the MDN documentation for column-gap](https://developer.mozilla.org/en-US/docs/Web/CSS/column-gap).
697            pub column_gap: ::iocraft::Gap
698        },
699        quote! {
700            /// Defines the gaps in between rows of flex items.
701            ///
702            /// See [the MDN documentation for row-gap](https://developer.mozilla.org/en-US/docs/Web/CSS/row-gap).
703            pub row_gap: ::iocraft::Gap
704        },
705        quote! {
706            /// Defines the area to reserve around the element's content, but inside the border.
707            ///
708            /// See [the MDN documentation for padding](https://developer.mozilla.org/en-US/docs/Web/CSS/padding).
709            pub padding: ::iocraft::Padding
710        },
711        quote! {
712            /// Defines the area to reserve above the element's content, but inside the border.
713            ///
714            /// See [the MDN documentation for padding](https://developer.mozilla.org/en-US/docs/Web/CSS/padding).
715            pub padding_top: ::iocraft::Padding
716        },
717        quote! {
718            /// Defines the area to reserve to the right of the element's content, but inside the border.
719            ///
720            /// See [the MDN documentation for padding](https://developer.mozilla.org/en-US/docs/Web/CSS/padding).
721            pub padding_right: ::iocraft::Padding
722        },
723        quote! {
724            /// Defines the area to reserve below the element's content, but inside the border.
725            ///
726            /// See [the MDN documentation for padding](https://developer.mozilla.org/en-US/docs/Web/CSS/padding).
727            pub padding_bottom: ::iocraft::Padding
728        },
729        quote! {
730            /// Defines the area to reserve to the left of the element's content, but inside the border.
731            ///
732            /// See [the MDN documentation for padding](https://developer.mozilla.org/en-US/docs/Web/CSS/padding).
733            pub padding_left: ::iocraft::Padding
734        },
735        quote! {
736            /// Controls how the element is layed out and whether it will be controlled by the flexbox.
737            pub position: ::iocraft::Position
738        },
739        quote! {
740            /// Sets the position of a positioned element.
741            ///
742            /// See [the MDN documentation for inset](https://developer.mozilla.org/en-US/docs/Web/CSS/inset).
743            pub inset: ::iocraft::Inset
744        },
745        quote! {
746            /// Sets the vertical position of a positioned element.
747            ///
748            /// See [the MDN documentation for top](https://developer.mozilla.org/en-US/docs/Web/CSS/top).
749            pub top: ::iocraft::Inset
750        },
751        quote! {
752            /// Sets the horizontal position of a positioned element.
753            ///
754            /// See [the MDN documentation for right](https://developer.mozilla.org/en-US/docs/Web/CSS/right).
755            pub right: ::iocraft::Inset
756        },
757        quote! {
758            /// Sets the vertical position of a positioned element.
759            ///
760            /// See [the MDN documentation for bottom](https://developer.mozilla.org/en-US/docs/Web/CSS/bottom).
761            pub bottom: ::iocraft::Inset
762        },
763        quote! {
764            /// Sets the horizontal position of a positioned element.
765            ///
766            /// See [the MDN documentation for left](https://developer.mozilla.org/en-US/docs/Web/CSS/left).
767            pub left: ::iocraft::Inset
768        },
769        quote! {
770            /// Defines the area to reserve around the element's content, but outside the border.
771            ///
772            /// See [the MDN documentation for margin](https://developer.mozilla.org/en-US/docs/Web/CSS/margin).
773            pub margin: ::iocraft::Margin
774        },
775        quote! {
776            /// Defines the area to reserve above the element's content, but outside the border.
777            ///
778            /// See [the MDN documentation for margin](https://developer.mozilla.org/en-US/docs/Web/CSS/margin).
779            pub margin_top: ::iocraft::Margin
780        },
781        quote! {
782            /// Defines the area to reserve to the right of the element's content, but outside the border.
783            ///
784            /// See [the MDN documentation for margin](https://developer.mozilla.org/en-US/docs/Web/CSS/margin).
785            pub margin_right: ::iocraft::Margin
786        },
787        quote! {
788            /// Defines the area to reserve below the element's content, but outside the border.
789            ///
790            /// See [the MDN documentation for margin](https://developer.mozilla.org/en-US/docs/Web/CSS/margin).
791            pub margin_bottom: ::iocraft::Margin
792        },
793        quote! {
794            /// Defines the area to reserve to the left of the element's content, but outside the border.
795            ///
796            /// See [the MDN documentation for margin](https://developer.mozilla.org/en-US/docs/Web/CSS/margin).
797            pub margin_left: ::iocraft::Margin
798        },
799        quote! {
800            /// Defines how items are placed along the main axis of a flex container.
801            ///
802            /// See [the MDN documentation for flex-direction](https://developer.mozilla.org/en-US/docs/Web/CSS/flex-direction).
803            pub flex_direction: ::iocraft::FlexDirection
804        },
805        quote! {
806            /// Defines whether items are forced onto one line or can wrap into multiple lines.
807            ///
808            /// See [the MDN documentation for flex-wrap](https://developer.mozilla.org/en-US/docs/Web/CSS/flex-wrap).
809            pub flex_wrap: ::iocraft::FlexWrap
810        },
811        quote! {
812            /// Sets the initial main size of a flex item.
813            ///
814            /// See [the MDN documentation for flex-basis](https://developer.mozilla.org/en-US/docs/Web/CSS/flex-basis).
815            pub flex_basis: ::iocraft::FlexBasis
816        },
817        quote! {
818            /// Sets the flex grow factor, which specifies how much free space should be assigned
819            /// to the item's main size.
820            ///
821            /// See [the MDN documentation for flex-grow](https://developer.mozilla.org/en-US/docs/Web/CSS/flex-grow).
822            pub flex_grow: f32
823        },
824        quote! {
825            /// Sets the flex shrink factor, which specifies how the item should shrink when the
826            /// container doesn't have enough room for all flex items.
827            ///
828            /// See [the MDN documentation for flex-shrink](https://developer.mozilla.org/en-US/docs/Web/CSS/flex-shrink).
829            pub flex_shrink: Option<f32>
830        },
831        quote! {
832            /// Controls the alignment of items along the cross axis of a flex container.
833            ///
834            /// See [the MDN documentation for align-items](https://developer.mozilla.org/en-US/docs/Web/CSS/align-items).
835            pub align_items: Option<::iocraft::AlignItems>
836        },
837        quote! {
838            /// Controls the distribution of space between and around items along a flex container's cross axis.
839            ///
840            /// See [the MDN documentation for align-content](https://developer.mozilla.org/en-US/docs/Web/CSS/align-content).
841            pub align_content: Option<::iocraft::AlignContent>
842        },
843        quote! {
844            /// Controls the distribution of space between and around items along a flex container's main axis.
845            ///
846            /// See [the MDN documentation for justify-content](https://developer.mozilla.org/en-US/docs/Web/CSS/justify-content).
847            pub justify_content: Option<::iocraft::JustifyContent>
848        },
849        quote! {
850            /// Defines the behavior when content does not fit within the element's padding box.
851            ///
852            /// See [the MDN documentation for overflow](https://developer.mozilla.org/en-US/docs/Web/CSS/overflow).
853            pub overflow: Option<::iocraft::Overflow>
854        },
855        quote! {
856            /// Defines the behavior when content does not fit within the element's padding box in the horizontal direction.
857            ///
858            /// See [the MDN documentation for overflow](https://developer.mozilla.org/en-US/docs/Web/CSS/overflow).
859            pub overflow_x: Option<::iocraft::Overflow>
860        },
861        quote! {
862            /// Defines the behavior when content does not fit within the element's padding box in the vertical direction.
863            ///
864            /// See [the MDN documentation for overflow](https://developer.mozilla.org/en-US/docs/Web/CSS/overflow).
865            pub overflow_y: Option<::iocraft::Overflow>
866        },
867    ]
868    .map(|tokens| syn::Field::parse_named.parse2(tokens).unwrap());
869
870    let mut ast = parse_macro_input!(item as DeriveInput);
871    match &mut ast.data {
872        syn::Data::Struct(ref mut struct_data) => {
873            if let syn::Fields::Named(fields) = &mut struct_data.fields {
874                fields.named.extend(layout_style_fields.iter().cloned());
875            }
876
877            let struct_name = &ast.ident;
878            let field_assignments = layout_style_fields.iter().map(|field| {
879                let field_name = &field.ident;
880                quote! { ret.#field_name = self.#field_name; }
881            });
882
883            let where_clause = &ast.generics.where_clause;
884
885            let has_generics = !ast.generics.params.is_empty();
886            let generics = &ast.generics;
887
888            let generics_names = ast.generics.params.iter().map(|param| match param {
889                GenericParam::Type(ty) => {
890                    let name = &ty.ident;
891                    quote!(#name)
892                }
893                GenericParam::Lifetime(lt) => {
894                    let name = &lt.lifetime;
895                    quote!(#name)
896                }
897                GenericParam::Const(c) => {
898                    let name = &c.ident;
899                    quote!(#name)
900                }
901            });
902            let bracketed_generic_names = match has_generics {
903                true => quote!(<#(#generics_names),*>),
904                false => quote!(),
905            };
906
907            quote! {
908                #ast
909
910                impl #generics #struct_name #bracketed_generic_names #where_clause {
911                    /// Returns the layout style based on the layout-related fields of this struct.
912                    pub fn layout_style(&self) -> ::iocraft::LayoutStyle {
913                        let mut ret: ::iocraft::LayoutStyle = Default::default();
914                        #(#field_assignments)*
915                        ret
916                    }
917                }
918            }
919            .into()
920        }
921        _ => panic!("`with_layout_style_props` can only be used with structs "),
922    }
923}