Skip to main content

fui_macros/
lib.rs

1extern crate proc_macro;
2
3use proc_macro::TokenStream;
4
5use quote::quote;
6use syn::punctuated::Punctuated;
7use syn::{parse_macro_input, Ident, Token};
8
9mod parser;
10use crate::parser::Ctrl;
11use crate::parser::CtrlParam;
12use crate::parser::CtrlProperty;
13
14//
15// let control: Rc<RefCell<dyn ControlObject>> = ...;
16//
17// ui!(
18//     Horizontal {
19//         Row: 1,
20//         spacing: 4,
21//
22//         Button { Text { text: "Button".to_string() } },
23//         Text { text: "Label".to_string() },
24//         control,
25//     }
26// )
27//
28// translates to:
29//
30// <Horizontal>::builder().spacing(4.into()).build().to_view(None, ViewContext {
31//     attached_values: { let mut map = TypeMap::new(); map.insert::<Row>(1.into()); map },
32//     children: Children::from(vec![
33//
34//         (<Button>::builder().build().to_view(None, ViewContext {
35//             attached_values: TypeMap::new(),
36//             children: Children::from(vec![
37//
38//                 (<Text>::builder()
39//                     .text("Button".to_string().into())
40//                     .build().to_view(None, ViewContext {
41//                         attached_values: TypeMap::new(),
42//                         children: Box::new(Vec::<Rc<RefCell<dyn ControlObject>>>::new()),
43//                     }) as Rc<RefCell<dyn ControlObject>>).into(),
44//
45//             )]),
46//         })).into(),
47//
48//         <Text>::builder()
49//             .text("Label".to_string().into())
50//             .build().to_view(None, ViewContext {
51//                 attached_values: TypeMap::new(),
52//                 children: Children::from(vec![]),
53//             }),
54//         ),
55//
56//         control,
57//     ]),
58// })
59//
60// ui!(
61//     Vertical {
62//         &vm.items,
63//     }
64// )
65//
66// translates to:
67//
68// <Vertical>::builder().build().to_view(None, ViewContext {
69//     attached_values: TypeMap::new(),
70//     children: Children::from(vec![(&vm.items).into()]),
71// })
72//
73// ui!(
74//     Text {
75//         Style: Default {
76//             color: [1.0, 0.0, 0.0, 1.0],
77//         },
78//         text: "Text",
79//     }
80// )
81//
82// translates to:
83//
84// <Text>::builder().text("Text".into()).build().to_view(
85//     Some(Box::new(<DefaultTextStyle>::new(<DefaultTextStyleParams>::builder()
86//         color([1.0, 0.0, 0.0, 1.0].into()).build()))),
87//     ViewContext {
88//         attached_values: TypeMap::new(),
89//         children: Children::from(vec![]),
90//     }
91// )
92//
93#[proc_macro]
94pub fn ui(input: TokenStream) -> TokenStream {
95    let ctrl = parse_macro_input!(input as Ctrl);
96    let x = quote_control(ctrl);
97
98    x.into()
99
100    // for debug purposes - evaluate token stream to string
101    //quote!(stringify!(#x)).into()
102}
103
104fn quote_control(ctrl: Ctrl) -> proc_macro2::TokenStream {
105    let Ctrl {
106        name: control_name,
107        params,
108    } = ctrl;
109
110    let (style, properties, attached_values, children) = decouple_params(params);
111
112    let properties_builder = get_properties_builder(control_name.clone(), properties);
113    let style_builder = get_style_builder(control_name, style);
114    let attached_values_typemap = get_attached_values_typemap(attached_values);
115    let children_source = get_children_source(children);
116
117    quote! { #properties_builder.to_view(#style_builder, ViewContext {
118        attached_values: #attached_values_typemap,
119        children: #children_source,
120    }) }
121}
122
123fn get_properties_builder(
124    struct_name: Ident,
125    properties: Vec<CtrlProperty>,
126) -> proc_macro2::TokenStream {
127    let mut method_calls = Vec::new();
128    for property in properties {
129        let name = property.name;
130        let expr = property.expr;
131        method_calls.push(quote!(.#name((#expr).into())))
132    }
133    quote!(<#struct_name>::builder()#(#method_calls)*.build())
134}
135
136fn get_style_builder(control_name: Ident, style: Option<Ctrl>) -> proc_macro2::TokenStream {
137    match style {
138        None => quote!(None),
139        Some(style) => {
140            let name = style.name;
141
142            let style_name = Ident::new(&format!("{}{}Style", name, control_name), name.span());
143            let params_name =
144                Ident::new(&format!("{}{}StyleParams", name, control_name), name.span());
145
146            let (_style, properties, _attached_values, _children) = decouple_params(style.params);
147
148            let properties_builder = get_properties_builder(params_name, properties);
149
150            quote!(Some(Box::new(<#style_name>::new(#properties_builder))))
151        }
152    }
153}
154
155fn get_attached_values_typemap(attached_values: Vec<CtrlProperty>) -> proc_macro2::TokenStream {
156    let mut insert_statements = Vec::new();
157    for attached_value in attached_values {
158        let name = attached_value.name;
159        let expr = attached_value.expr;
160        insert_statements.push(quote!(map.insert::<#name>((#expr).into());))
161    }
162    quote!({ let mut map = TypeMap::new(); #(#insert_statements)* map })
163}
164
165fn get_children_source(children: Vec<CtrlParam>) -> proc_macro2::TokenStream {
166    let mut sources = Vec::new();
167
168    for child in children {
169        if let CtrlParam::ChildCtrl(static_child) = child {
170            sources.push(quote_control(static_child));
171        } else if let CtrlParam::ChildExpr(expression) = child {
172            sources.push(quote!(#expression));
173        }
174    }
175
176    quote!({ Children::from(vec![#((#sources).into()),*]) })
177}
178
179fn decouple_params(
180    params: Punctuated<CtrlParam, Token![,]>,
181) -> (
182    Option<Ctrl>,
183    Vec<CtrlProperty>,
184    Vec<CtrlProperty>,
185    Vec<CtrlParam>,
186) {
187    let mut style = None;
188    let mut properties = Vec::new();
189    let mut attached_values = Vec::new();
190    let mut children = Vec::new();
191
192    for el in params.into_pairs() {
193        let el = el.into_value();
194
195        if let CtrlParam::Style(s) = el {
196            style = Some(s);
197        } else if let CtrlParam::Property(property) = el {
198            if let Some(first_char) = property.name.to_string().chars().next() {
199                if first_char.is_uppercase() {
200                    attached_values.push(property);
201                } else {
202                    properties.push(property);
203                }
204            }
205        } else if let CtrlParam::ChildCtrl(control) = el {
206            children.push(CtrlParam::ChildCtrl(control))
207        } else if let CtrlParam::ChildExpr(expression) = el {
208            children.push(CtrlParam::ChildExpr(expression))
209        }
210    }
211
212    (style, properties, attached_values, children)
213}