1use core::panic;
5
6use proc_macro::TokenStream;
7use proc_macro2::Span;
8use quote::{format_ident, quote};
9use syn::{Data, DataEnum, DeriveInput, Meta, parse_macro_input};
10
11fn derive_base_prop(input: TokenStream, prop: &str, source: &str, result: &str) -> TokenStream {
12 let ast = parse_macro_input!(input as DeriveInput);
13
14 let result: syn::Path = syn::parse_str(result).unwrap();
15 let source: syn::Path = syn::parse_str(source).unwrap();
16 let prop = format_ident!("{}", prop);
17 let name = ast.ident;
18 quote! {
19 impl #source for #name {
20 fn #prop(&self) -> &#result {
21 &self.#prop
22 }
23 }
24 }
25 .into()
26}
27
28#[proc_macro_derive(Empty)]
29pub fn derive_empty(input: TokenStream) -> TokenStream {
30 let ast = parse_macro_input!(input as DeriveInput);
31
32 let sname = ast.ident;
33 quote! {
34 impl feather_ui::layout::base::Empty for #sname {}
35 }
36 .into()
37}
38
39#[proc_macro_derive(Area)]
40pub fn derive_area(input: TokenStream) -> TokenStream {
41 derive_base_prop(
42 input,
43 "area",
44 "feather_ui::layout::base::Area",
45 "feather_ui::DRect",
46 )
47}
48
49#[proc_macro_derive(Padding)]
50pub fn derive_padding(input: TokenStream) -> TokenStream {
51 derive_base_prop(
52 input,
53 "padding",
54 "feather_ui::layout::base::Padding",
55 "feather_ui::DAbsRect",
56 )
57}
58
59#[proc_macro_derive(Margin)]
60pub fn derive_margin(input: TokenStream) -> TokenStream {
61 derive_base_prop(
62 input,
63 "margin",
64 "feather_ui::layout::base::Margin",
65 "feather_ui::DRect",
66 )
67}
68
69#[proc_macro_derive(Limits)]
70pub fn derive_limits(input: TokenStream) -> TokenStream {
71 derive_base_prop(
72 input,
73 "limits",
74 "feather_ui::layout::base::Limits",
75 "feather_ui::DLimits",
76 )
77}
78
79#[proc_macro_derive(RLimits)]
80pub fn derive_rlimits(input: TokenStream) -> TokenStream {
81 derive_base_prop(
82 input,
83 "rlimits",
84 "feather_ui::layout::base::RLimits",
85 "feather_ui::RelLimits",
86 )
87}
88
89#[proc_macro_derive(Anchor)]
90pub fn derive_anchor(input: TokenStream) -> TokenStream {
91 derive_base_prop(
92 input,
93 "anchor",
94 "feather_ui::layout::base::Anchor",
95 "feather_ui::DPoint",
96 )
97}
98
99#[proc_macro_derive(TextEdit)]
100pub fn derive_textedit(input: TokenStream) -> TokenStream {
101 derive_base_prop(
102 input,
103 "textedit",
104 "feather_ui::layout::base::TextEdit",
105 "feather_ui::text::EditView",
106 )
107}
108
109#[proc_macro_derive(FlexProp)]
110pub fn derive_flex_prop(input: TokenStream) -> TokenStream {
111 let ast = parse_macro_input!(input as DeriveInput);
112
113 let name = ast.ident;
114 quote! {
115 impl feather_ui::layout::flex::Prop for #name {
116 fn wrap(&self) -> bool { self.wrap }
117 fn justify(&self) -> feather_ui::layout::flex::FlexJustify { self.justify }
118 fn align(&self) -> feather_ui::layout::flex::FlexJustify { self.align }
119 }
120 }
121 .into()
122}
123
124#[proc_macro_derive(FlexChild)]
125pub fn derive_flex_child(input: TokenStream) -> TokenStream {
126 let ast = parse_macro_input!(input as DeriveInput);
127
128 let name = ast.ident;
129 quote! {
130 impl feather_ui::layout::flex::Child for #name {
131 fn grow(&self) -> f32 { self.grow }
132 fn shrink(&self) -> f32 { self.shrink }
133 fn basis(&self) -> feather_ui::DValue { self.basis }
134 }
135 }
136 .into()
137}
138
139#[proc_macro_derive(ZIndex)]
140pub fn derive_zindex(input: TokenStream) -> TokenStream {
141 let ast = parse_macro_input!(input as DeriveInput);
142
143 let sname = ast.ident;
144 quote! {
145 impl feather_ui::layout::base::ZIndex for #sname {
146 fn zindex(&self) -> i32 {
147 self.zindex
148 }
149 }
150 }
151 .into()
152}
153
154#[proc_macro_derive(Direction)]
155pub fn derive_direction(input: TokenStream) -> TokenStream {
156 let ast = parse_macro_input!(input as DeriveInput);
157
158 let sname = ast.ident;
159 quote! {
160 impl feather_ui::layout::base::Direction for #sname {
161 fn direction(&self) -> feather_ui::RowDirection {
162 self.direction
163 }
164 }
165 }
166 .into()
167}
168
169#[proc_macro_derive(RootProp)]
170pub fn derive_root_prop(input: TokenStream) -> TokenStream {
171 derive_base_prop(
172 input,
173 "dim",
174 "feather_ui::layout::root::Prop",
175 "feather_ui::AbsDim",
176 )
177}
178
179fn data_enum(ast: &DeriveInput) -> &DataEnum {
180 if let Data::Enum(data_enum) = &ast.data {
181 data_enum
182 } else {
183 panic!("`Dispatch` derive can only be used on an enum.");
184 }
185}
186
187fn find_enum_module(attrs: &[syn::Attribute]) -> syn::Result<String> {
188 for attr in attrs.iter() {
190 if attr.path().is_ident("evt") {
191 let nested = attr
192 .parse_args_with(
193 syn::punctuated::Punctuated::<Meta, syn::Token![,]>::parse_terminated,
194 )
195 .unwrap();
196
197 for meta in nested {
198 if let Meta::NameValue(name_value) = meta {
199 if let (true, syn::Expr::Lit(lit_str)) =
200 (name_value.path.is_ident("module"), name_value.value)
201 {
202 if let syn::Lit::Str(s) = lit_str.lit {
203 return Ok(s.value());
204 } else {
205 return Err(syn::Error::new(Span::call_site(), ""));
206 }
207 } else {
208 return Err(syn::Error::new(Span::call_site(), ""));
209 }
210 }
211 }
212
213 }
224 }
225
226 Err(syn::Error::new(Span::call_site(), ""))
228}
229
230#[proc_macro_derive(Dispatch)]
231pub fn dispatchable(input: TokenStream) -> TokenStream {
232 let crate_name = std::env::var("CARGO_PKG_NAME").unwrap();
233
234 let crate_name = format_ident!(
235 "{}",
236 if crate_name == "feather-ui" {
237 "crate"
238 } else {
239 "feather_ui"
240 }
241 );
242
243 let ast = parse_macro_input!(input as DeriveInput);
244 let enum_module = format_ident!(
245 "{}",
246 find_enum_module(&ast.attrs).expect(
247 "Expected `evt` attribute argument in the form: `#[evt(module = \"some_module_name\")]`",
248 ));
249
250 let enum_name = &ast.ident;
251 let data_enum = data_enum(&ast);
252 let variants = &data_enum.variants;
253
254 let mut extract_declarations = proc_macro2::TokenStream::new();
255 let mut restore_declarations = proc_macro2::TokenStream::new();
256
257 for (counter, variant) in variants.iter().enumerate() {
258 let variant_name = &variant.ident;
259
260 let idx = (1_u64)
261 .checked_shl(counter as u32)
262 .expect("Too many variants! Can't handle more than 64!");
263
264 if variant.fields.is_empty() {
265 extract_declarations.extend(quote! {
266 #enum_name::#variant_name => (
267 #idx,
268 Box::new(#enum_module::#variant_name::try_from(self).unwrap()),
269 ),
270 });
271 } else if variant.fields.iter().next().unwrap().ident.is_none() {
272 let underscores = variant.fields.iter().map(|_| format_ident!("_"));
273 extract_declarations.extend(quote! {
274 #enum_name::#variant_name(#(#underscores),*) => (
275 #idx,
276 Box::new(#enum_module::#variant_name::try_from(self).unwrap()),
277 ),
278 });
279 } else {
280 extract_declarations.extend(quote! {
281 #enum_name::#variant_name { .. } => (
282 #idx,
283 Box::new(#enum_module::#variant_name::try_from(self).unwrap()),
284 ),
285 });
286 }
287
288 restore_declarations.extend(quote! {
289 #idx => Ok(#enum_name::from(
290 *pair
291 .1
292 .downcast::<#enum_module::#variant_name>()
293 .map_err(|_| {
294 #crate_name::Error::MismatchedEnumTag(
295 pair.0,
296 std::any::TypeId::of::<#enum_module::#variant_name>(),
297 typeid,
298 )
299 })?,
300 )),
301 });
302 }
303
304 let counter = variants.len();
305 quote! {
306 impl #crate_name::Dispatchable for #enum_name {
307 const SIZE: usize = #counter;
308
309 fn extract(self) -> #crate_name::DispatchPair {
310 match self {
311 #extract_declarations
312 }
313 }
314
315 fn restore(pair: #crate_name::DispatchPair) -> Result<Self, #crate_name::Error> {
316 let typeid = (*pair.1).type_id();
317 match pair.0 {
318 #restore_declarations
319 _ => Err(#crate_name::Error::InvalidEnumTag(pair.0)),
320 }
321 }
322 }
323 }
324 .into()
325}
326
327#[proc_macro_derive(StateMachineChild)]
328pub fn state_machine_child(input: TokenStream) -> TokenStream {
329 let crate_name = std::env::var("CARGO_PKG_NAME").unwrap();
330
331 let crate_name = format_ident!(
332 "{}",
333 if crate_name == "feather-ui" {
334 "crate"
335 } else {
336 "feather_ui"
337 }
338 );
339
340 let ast = parse_macro_input!(input as DeriveInput);
341 let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
342
343 let data = if let Data::Struct(data_enum) = &ast.data {
344 data_enum
345 } else {
346 panic!("`StateMachineChild` derive can only be used on a struct.");
347 };
348
349 let has_children = data.fields.members().any(|x| {
350 if let syn::Member::Named(f) = x {
351 f == "children"
352 } else {
353 false
354 }
355 });
356
357 let apply_children = if has_children {
358 quote! {
359 fn apply_children(
360 &self,
361 f: &mut dyn FnMut(&dyn #crate_name::StateMachineChild) -> eyre::Result<()>,
362 ) -> eyre::Result<()> {
363 self.children
364 .iter()
365 .try_for_each(|x| f(x.as_ref().unwrap().as_ref()))
366 }
367 }
368 } else {
369 quote! {}
370 };
371
372 let sname = ast.ident;
373 quote! {
374 impl #impl_generics #crate_name::StateMachineChild for #sname #ty_generics #where_clause {
375 fn id(&self) -> std::sync::Arc<SourceID> {
376 self.id.clone()
377 }
378
379 #apply_children
380 }
381 }
382 .into()
383}
384
385#[proc_macro_derive(UserData)]
386pub fn lua_user_data(input: TokenStream) -> TokenStream {
387 let crate_name = format_ident!("feather_ui");
398
399 let ast = parse_macro_input!(input as DeriveInput);
400 let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
401
402 let data = if let Data::Struct(data_enum) = &ast.data {
403 data_enum
404 } else {
405 panic!("`UserData` derive can only be used on a struct.");
406 };
407
408 let mut field_methods = proc_macro2::TokenStream::new();
409 for m in data.fields.members() {
410 match m {
411 syn::Member::Named(ident) => {
412 field_methods.extend(quote! {
413 f.add_field_method_get(stringify!(#ident), |_, this| Ok(this.#ident.clone()));
414 f.add_field_method_set(stringify!(#ident), |_, this, v| Ok(this.#ident = v));
415 });
416 }
417 syn::Member::Unnamed(_) => panic!(
418 "You can't use a UserData derive on a tuple, because mlua knows how to parse tuples already!"
419 ),
420 }
421 }
422
423 let sname = ast.ident;
424 quote! {
425 impl #impl_generics #crate_name::mlua::UserData for #sname #ty_generics #where_clause {
426 fn add_fields<F: #crate_name::mlua::UserDataFields<Self>>(f: &mut F) {
427 #field_methods
428 }
429 }
430
431 impl #impl_generics #crate_name::mlua::FromLua for #sname #ty_generics #where_clause {
432 #[inline]
433 fn from_lua(value: #crate_name::mlua::Value, _: &#crate_name::mlua::Lua) -> #crate_name::mlua::Result<Self> {
434 match value {
435 #crate_name::mlua::Value::UserData(ud) => Ok(ud.borrow::<Self>()?.clone()),
436 _ => Err(#crate_name::mlua::Error::FromLuaConversionError {
437 from: value.type_name(),
438 to: stringify!(#sname).to_string(),
439 message: None,
440 }),
441 }
442 }
443 }
444 }
445 .into()
446}