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 = <.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 = <.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}