ayyeve_ui_proc_macros/
lib.rs

1use proc_macro::TokenStream;
2use quote::*;
3use syn::*;
4
5/// allowed enum attribute values:
6/// - debug
7/// allowed variant attribute values:
8/// - ignore
9/// - display = String (default is variant name)
10#[proc_macro_derive(Dropdown, attributes(Dropdown))]
11pub fn dropdown(input: TokenStream) -> TokenStream {
12    // Parse the string representation
13    let ast:DeriveInput = syn::parse(input).unwrap();
14    let mut entries = Vec::new();
15    let mut debug = false;
16
17    struct DropdownVariant {
18        /// text to be displayed
19        display: String,
20        /// name of variant
21        name: String,
22    }
23
24    
25    for attr in &ast.attrs {
26        if !attr.path.is_ident("Dropdown") {continue}
27
28        if let Ok(Meta::List(list)) = attr.parse_meta() {
29            for i in list.nested {
30                if let NestedMeta::Meta(Meta::Path(p)) = &i {
31                    if p.is_ident("debug") {
32                        debug = true;
33                    } else {
34                        panic!("Unknown enum attribute {}", p.get_ident().unwrap())
35                    }
36                }
37            }
38        }
39    }
40
41
42    // find all the variant data
43    if let Data::Enum(data) = &ast.data {
44        for v in data.variants.iter() {
45            let mut variant = DropdownVariant {display: v.ident.to_string(), name: v.ident.to_string()};
46            let mut ignore = false;
47
48            // find the id of the packet
49            for a in v.attrs.iter() {
50                if !a.path.is_ident("Dropdown") {continue}
51                
52                if let Ok(Meta::List(list)) = a.parse_meta() {
53                    for i in list.nested {
54                        if let NestedMeta::Meta(Meta::NameValue(name_value)) = &i {
55                            // display
56                            if let Lit::Str(display) = &name_value.lit {
57                                variant.display = display.value()
58                            }
59                        }
60
61                        // ignore
62                        if let NestedMeta::Meta(Meta::Path(name)) = &i {
63                            if name.is_ident("ignore") {
64                                ignore = true;
65                                break
66                            }
67                        }
68                        
69                    }
70                }
71            }
72
73            // skip this variant if it should be ignored
74            if ignore {continue}
75
76            // create packet data
77            entries.push(variant)
78        }
79    }
80
81    // make sure the enum isnt empty
82    if entries.is_empty() {panic!("enum is empty or all variants are ignored")}
83
84    let enum_name = ast.ident.to_string();
85    let mut to_string = String::new();
86    let mut from_string = String::new();
87    let mut variants = String::new();
88
89    let t = "    ";
90
91    for variant in entries {
92        to_string += &format!("{t}{t}{t}{t}Self::{} => \"{}\".to_owned(), \n", variant.name, variant.display);
93        from_string += &format!("{t}{t}{t}{t}\"{}\" => Self::{}, \n", variant.display, variant.name);
94        variants += &format!("{t}{t}{t}{t}Self::{},\n", variant.name);
95    }
96    let variants = variants.trim();
97    let to_string = to_string.trim();
98    let from_string = from_string.trim();
99
100    let gen = format!(r#"
101    impl Dropdownable for {enum_name} {{
102        fn variants() -> Vec<Self> {{
103            vec![
104                {variants}
105            ]
106        }}
107        fn display_text(&self) -> String {{
108            match self {{
109                {to_string}
110                other => panic!("variant {{:?}} was ignored", other)
111            }}
112        }}
113        fn from_string(string: String) -> Self {{
114            match &*string {{
115                {from_string}
116                other => panic!("variant does not exist for enum {enum_name} and string {{}}", other)
117            }}
118        }}
119    }}"#);
120
121    if debug {println!("generated: \n{}", gen)}
122    
123    // Return the generated impl
124    let gen = gen.parse::<proc_macro2::TokenStream>().unwrap();
125    proc_macro::TokenStream::from(quote!{#gen})
126}
127
128
129/// allowed attribute values:
130/// - selectable=bool (default true)
131/// - multi_selectable=bool (default false)
132#[proc_macro_derive(ScrollableGettersSetters, attributes(Scrollable))]
133pub fn scrollable_getters_setters(input: TokenStream) -> TokenStream {
134    let ast:DeriveInput = syn::parse(input).unwrap();
135
136    let mut has_pos = false;
137    let mut has_tag = false;
138    let mut has_hover = false;
139    let mut has_selected = false;
140    let mut has_size = false;
141
142    let mut selectable = true;
143    let mut multi_selectable = false;
144
145    let mut generics = String::new();
146
147    for attr in &ast.attrs {
148        if !attr.path.is_ident("Scrollable") {continue}
149
150        if let Ok(Meta::List(list)) = attr.parse_meta() {
151            for i in list.nested {
152                if let NestedMeta::Meta(Meta::NameValue(name_value)) = &i {
153                    if let Lit::Bool(i) = &name_value.lit {
154                        if name_value.path.is_ident("selectable") {
155                            selectable = i.value()
156                        } else if name_value.path.is_ident("multi_selectable") {
157                            multi_selectable = i.value()
158                        } else {
159                            panic!("unknown attribute {}", name_value.path.get_ident().unwrap())
160                        }
161                    } else if let Lit::Str(i) = &name_value.lit {
162                        if name_value.path.is_ident("generics") {
163                            generics = i.value()
164                        } else {
165                            panic!("unknown attribute {}", name_value.path.get_ident().unwrap())
166                        }
167                    }
168                }
169            }
170        }
171    }
172
173    if let Data::Struct(s) = &ast.data {
174        for f in &s.fields {
175            if let Some(ident) = &f.ident {
176                has_size |= ident.to_string() == "size";
177                has_pos |= ident.to_string() == "pos";
178                has_tag |= ident.to_string() == "tag";
179                has_hover |= ident.to_string() == "hover";
180                has_selected |= ident.to_string() == "selected";
181            }
182            if has_pos && has_tag && has_hover && has_selected {break}
183        }
184    }
185    if !has_size {panic!("Scrollable does not have a size field")}
186
187    let generics = {
188        if generics.len() > 0 {format!("<{generics}>")}
189        else {String::new()}
190    };
191    let generics2 = generics.split(",").map(|g|g.split(":").next().unwrap()).collect::<Vec<_>>().join(",") + if generics.len() > 0 {">"} else {""};
192
193    let struct_name = ast.ident.to_string();
194
195    let mut str = String::new();
196    str += &format!("impl{generics} ScrollableItemGettersSetters for {struct_name}{generics2} {{\n");
197
198    // size (required)
199    str += "    fn size(&self) -> Vector2 {self.size}\n    fn set_size(&mut self, new_size: Vector2) {self.size = new_size}\n";
200
201    if has_pos {
202        str += "    fn get_pos(&self) -> Vector2 {self.pos}\n    fn set_pos(&mut self, pos:Vector2) {self.pos = pos}\n"
203    }
204    if has_tag {
205        str += "    fn get_tag(&self) -> String {self.tag.clone()}\n    fn set_tag(&mut self, tag:&str) {self.tag = tag.to_owned()}\n"
206    }
207    if has_hover {
208        str += "    fn get_hover(&self) -> bool {self.hover}\n    fn set_hover(&mut self, hover:bool) {self.hover = hover}\n"
209    }
210    if has_selected {
211        str += "    fn get_selected(&self) -> bool {self.selected}\n    fn set_selected(&mut self, selected:bool) {self.selected = selected}\n";
212    }
213
214    str += &format!("    fn get_selectable(&self) -> bool {{{selectable}}}\n    fn get_multi_selectable(&self) -> bool {{{multi_selectable}}}\n");
215
216    str += "}";
217
218
219    let tokens = str.parse::<proc_macro2::TokenStream>().unwrap();
220    proc_macro::TokenStream::from(quote! {#tokens})
221}
222