styleable_helpers.rs

  1use proc_macro::TokenStream;
  2use proc_macro2::TokenStream as TokenStream2;
  3use quote::{format_ident, quote};
  4use syn::{
  5    parse::{Parse, ParseStream, Result},
  6    parse_macro_input,
  7};
  8
  9struct StyleableMacroInput;
 10
 11impl Parse for StyleableMacroInput {
 12    fn parse(_input: ParseStream) -> Result<Self> {
 13        Ok(StyleableMacroInput)
 14    }
 15}
 16
 17pub fn styleable_helpers(input: TokenStream) -> TokenStream {
 18    let _ = parse_macro_input!(input as StyleableMacroInput);
 19    let methods = generate_methods();
 20    let output = quote! {
 21        #(#methods)*
 22    };
 23
 24    output.into()
 25}
 26
 27fn generate_methods() -> Vec<TokenStream2> {
 28    let mut methods = Vec::new();
 29
 30    for (prefix, auto_allowed, fields) in box_prefixes() {
 31        methods.push(generate_custom_value_setter(
 32            prefix,
 33            if auto_allowed {
 34                quote! { Length }
 35            } else {
 36                quote! { DefiniteLength }
 37            },
 38            &fields,
 39        ));
 40
 41        for (suffix, length_tokens, doc_string) in box_suffixes() {
 42            if suffix != "auto" || auto_allowed {
 43                methods.push(generate_predefined_setter(
 44                    prefix,
 45                    suffix,
 46                    &fields,
 47                    &length_tokens,
 48                    false,
 49                    doc_string,
 50                ));
 51            }
 52
 53            if suffix != "auto" {
 54                methods.push(generate_predefined_setter(
 55                    prefix,
 56                    suffix,
 57                    &fields,
 58                    &length_tokens,
 59                    true,
 60                    doc_string,
 61                ));
 62            }
 63        }
 64    }
 65
 66    for (prefix, fields) in corner_prefixes() {
 67        methods.push(generate_custom_value_setter(
 68            prefix,
 69            quote! { AbsoluteLength },
 70            &fields,
 71        ));
 72
 73        for (suffix, radius_tokens, doc_string) in corner_suffixes() {
 74            methods.push(generate_predefined_setter(
 75                prefix,
 76                suffix,
 77                &fields,
 78                &radius_tokens,
 79                false,
 80                doc_string,
 81            ));
 82        }
 83    }
 84
 85    for (prefix, fields) in border_prefixes() {
 86        for (suffix, width_tokens, doc_string) in border_suffixes() {
 87            methods.push(generate_predefined_setter(
 88                prefix,
 89                suffix,
 90                &fields,
 91                &width_tokens,
 92                false,
 93                doc_string,
 94            ));
 95        }
 96    }
 97    methods
 98}
 99
100fn generate_predefined_setter(
101    name: &'static str,
102    length: &'static str,
103    fields: &Vec<TokenStream2>,
104    length_tokens: &TokenStream2,
105    negate: bool,
106    doc_string: &'static str,
107) -> TokenStream2 {
108    let (negation_prefix, negation_token) = if negate {
109        ("neg_", quote! { - })
110    } else {
111        ("", quote! {})
112    };
113
114    let method_name = if length.is_empty() {
115        format_ident!("{}{}", negation_prefix, name)
116    } else {
117        format_ident!("{}{}_{}", negation_prefix, name, length)
118    };
119
120    let field_assignments = fields
121        .iter()
122        .map(|field_tokens| {
123            quote! {
124                style.#field_tokens = Some((#negation_token gpui2::geometry::#length_tokens).into());
125            }
126        })
127        .collect::<Vec<_>>();
128
129    let method = quote! {
130        #[doc = #doc_string]
131        fn #method_name(mut self) -> Self where Self: std::marker::Sized {
132            let mut style = self.declared_style();
133            #(#field_assignments)*
134            self
135        }
136    };
137
138    if negate {
139        dbg!(method.to_string());
140    }
141
142    method
143}
144
145fn generate_custom_value_setter(
146    prefix: &'static str,
147    length_type: TokenStream2,
148    fields: &Vec<TokenStream2>,
149) -> TokenStream2 {
150    let method_name = format_ident!("{}", prefix);
151
152    let mut iter = fields.into_iter();
153    let last = iter.next_back().unwrap();
154    let field_assignments = iter
155        .map(|field_tokens| {
156            quote! {
157                style.#field_tokens = Some(length.clone().into());
158            }
159        })
160        .chain(std::iter::once(quote! {
161            style.#last = Some(length.into());
162        }))
163        .collect::<Vec<_>>();
164
165    let method = quote! {
166        fn #method_name(mut self, length: impl std::clone::Clone + Into<gpui2::geometry::#length_type>) -> Self where Self: std::marker::Sized {
167            let mut style = self.declared_style();
168            #(#field_assignments)*
169            self
170        }
171    };
172
173    method
174}
175
176fn box_prefixes() -> Vec<(&'static str, bool, Vec<TokenStream2>)> {
177    vec![
178        ("w", true, vec![quote! { size.width }]),
179        ("h", true, vec![quote! { size.height }]),
180        (
181            "size",
182            true,
183            vec![quote! {size.width}, quote! {size.height}],
184        ),
185        ("min_w", true, vec![quote! { min_size.width }]),
186        ("min_h", true, vec![quote! { min_size.height }]),
187        ("max_w", true, vec![quote! { max_size.width }]),
188        ("max_h", true, vec![quote! { max_size.height }]),
189        (
190            "m",
191            true,
192            vec![
193                quote! { margin.top },
194                quote! { margin.bottom },
195                quote! { margin.left },
196                quote! { margin.right },
197            ],
198        ),
199        ("mt", true, vec![quote! { margin.top }]),
200        ("mb", true, vec![quote! { margin.bottom }]),
201        (
202            "my",
203            true,
204            vec![quote! { margin.top }, quote! { margin.bottom }],
205        ),
206        (
207            "mx",
208            true,
209            vec![quote! { margin.left }, quote! { margin.right }],
210        ),
211        ("ml", true, vec![quote! { margin.left }]),
212        ("mr", true, vec![quote! { margin.right }]),
213        (
214            "p",
215            false,
216            vec![
217                quote! { padding.top },
218                quote! { padding.bottom },
219                quote! { padding.left },
220                quote! { padding.right },
221            ],
222        ),
223        ("pt", false, vec![quote! { padding.top }]),
224        ("pb", false, vec![quote! { padding.bottom }]),
225        (
226            "px",
227            false,
228            vec![quote! { padding.left }, quote! { padding.right }],
229        ),
230        (
231            "py",
232            false,
233            vec![quote! { padding.top }, quote! { padding.bottom }],
234        ),
235        ("pl", false, vec![quote! { padding.left }]),
236        ("pr", false, vec![quote! { padding.right }]),
237        ("top", true, vec![quote! { inset.top }]),
238        ("bottom", true, vec![quote! { inset.bottom }]),
239        ("left", true, vec![quote! { inset.left }]),
240        ("right", true, vec![quote! { inset.right }]),
241        (
242            "gap",
243            false,
244            vec![quote! { gap.width }, quote! { gap.height }],
245        ),
246        ("gap_x", false, vec![quote! { gap.width }]),
247        ("gap_y", false, vec![quote! { gap.height }]),
248    ]
249}
250
251fn box_suffixes() -> Vec<(&'static str, TokenStream2, &'static str)> {
252    vec![
253        ("0", quote! { pixels(0.) }, "0px"),
254        ("0p5", quote! { rems(0.125) }, "2px (0.125rem)"),
255        ("1", quote! { rems(0.25) }, "4px (0.25rem)"),
256        ("1p5", quote! { rems(0.375) }, "6px (0.375rem)"),
257        ("2", quote! { rems(0.5) }, "8px (0.5rem)"),
258        ("2p5", quote! { rems(0.625) }, "10px (0.625rem)"),
259        ("3", quote! { rems(0.75) }, "12px (0.75rem)"),
260        ("3p5", quote! { rems(0.875) }, "14px (0.875rem)"),
261        ("4", quote! { rems(1.) }, "16px (1rem)"),
262        ("5", quote! { rems(1.25) }, "20px (1.25rem)"),
263        ("6", quote! { rems(1.5) }, "24px (1.5rem)"),
264        ("7", quote! { rems(1.75) }, "28px (1.75rem)"),
265        ("8", quote! { rems(2.0) }, "32px (2rem)"),
266        ("9", quote! { rems(2.25) }, "36px (2.25rem)"),
267        ("10", quote! { rems(2.5) }, "40px (2.5rem)"),
268        ("11", quote! { rems(2.75) }, "44px (2.75rem)"),
269        ("12", quote! { rems(3.) }, "48px (3rem)"),
270        ("16", quote! { rems(4.) }, "64px (4rem)"),
271        ("20", quote! { rems(5.) }, "80px (5rem)"),
272        ("24", quote! { rems(6.) }, "96px (6rem)"),
273        ("32", quote! { rems(8.) }, "128px (8rem)"),
274        ("40", quote! { rems(10.) }, "160px (10rem)"),
275        ("48", quote! { rems(12.) }, "192px (12rem)"),
276        ("56", quote! { rems(14.) }, "224px (14rem)"),
277        ("64", quote! { rems(16.) }, "256px (16rem)"),
278        ("72", quote! { rems(18.) }, "288px (18rem)"),
279        ("80", quote! { rems(20.) }, "320px (20rem)"),
280        ("96", quote! { rems(24.) }, "384px (24rem)"),
281        ("auto", quote! { auto() }, "Auto"),
282        ("px", quote! { pixels(1.) }, "1px"),
283        ("full", quote! { relative(1.) }, "100%"),
284        ("1_2", quote! { relative(0.5) }, "50% (1/2)"),
285        ("1_3", quote! { relative(1./3.) }, "33% (1/3)"),
286        ("2_3", quote! { relative(2./3.) }, "66% (2/3)"),
287        ("1_4", quote! { relative(0.25) }, "25% (1/4)"),
288        ("2_4", quote! { relative(0.5) }, "50% (2/4)"),
289        ("3_4", quote! { relative(0.75) }, "75% (3/4)"),
290        ("1_5", quote! { relative(0.2) }, "20% (1/5)"),
291        ("2_5", quote! { relative(0.4) }, "40% (2/5)"),
292        ("3_5", quote! { relative(0.6) }, "60% (3/5)"),
293        ("4_5", quote! { relative(0.8) }, "80% (4/5)"),
294        ("1_6", quote! { relative(1./6.) }, "16% (1/6)"),
295        ("5_6", quote! { relative(5./6.) }, "80% (5/6)"),
296        ("1_12", quote! { relative(1./12.) }, "8% (1/12)"),
297    ]
298}
299
300fn corner_prefixes() -> Vec<(&'static str, Vec<TokenStream2>)> {
301    vec![
302        (
303            "rounded",
304            vec![
305                quote! { corner_radii.top_left },
306                quote! { corner_radii.top_right },
307                quote! { corner_radii.bottom_right },
308                quote! { corner_radii.bottom_left },
309            ],
310        ),
311        (
312            "rounded_t",
313            vec![
314                quote! { corner_radii.top_left },
315                quote! { corner_radii.top_right },
316            ],
317        ),
318        (
319            "rounded_b",
320            vec![
321                quote! { corner_radii.bottom_left },
322                quote! { corner_radii.bottom_right },
323            ],
324        ),
325        (
326            "rounded_r",
327            vec![
328                quote! { corner_radii.top_right },
329                quote! { corner_radii.bottom_right },
330            ],
331        ),
332        (
333            "rounded_l",
334            vec![
335                quote! { corner_radii.top_left },
336                quote! { corner_radii.bottom_left },
337            ],
338        ),
339        ("rounded_tl", vec![quote! { corner_radii.top_left }]),
340        ("rounded_tr", vec![quote! { corner_radii.top_right }]),
341        ("rounded_bl", vec![quote! { corner_radii.bottom_left }]),
342        ("rounded_br", vec![quote! { corner_radii.bottom_right }]),
343    ]
344}
345
346fn corner_suffixes() -> Vec<(&'static str, TokenStream2, &'static str)> {
347    vec![
348        ("none", quote! { pixels(0.) }, "0px"),
349        ("sm", quote! { rems(0.125) }, "2px (0.125rem)"),
350        ("md", quote! { rems(0.25) }, "4px (0.25rem)"),
351        ("lg", quote! { rems(0.5) }, "8px (0.5rem)"),
352        ("xl", quote! { rems(0.75) }, "12px (0.75rem)"),
353        ("2xl", quote! { rems(1.) }, "16px (1rem)"),
354        ("3xl", quote! { rems(1.5) }, "24px (1.5rem)"),
355        ("full", quote! {  pixels(9999.) }, "9999px"),
356    ]
357}
358
359fn border_prefixes() -> Vec<(&'static str, Vec<TokenStream2>)> {
360    vec![
361        (
362            "border",
363            vec![
364                quote! { border_widths.top },
365                quote! { border_widths.right },
366                quote! { border_widths.bottom },
367                quote! { border_widths.left },
368            ],
369        ),
370        ("border_t", vec![quote! { border_widths.top }]),
371        ("border_b", vec![quote! { border_widths.bottom }]),
372        ("border_r", vec![quote! { border_widths.right }]),
373        ("border_l", vec![quote! { border_widths.left }]),
374        (
375            "border_x",
376            vec![
377                quote! { border_widths.left },
378                quote! { border_widths.right },
379            ],
380        ),
381        (
382            "border_y",
383            vec![
384                quote! { border_widths.top },
385                quote! { border_widths.bottom },
386            ],
387        ),
388    ]
389}
390
391fn border_suffixes() -> Vec<(&'static str, TokenStream2, &'static str)> {
392    vec![
393        ("", quote! { pixels(1.)}, "1px"),
394        ("0", quote! { pixels(0.)}, "0px"),
395        ("1", quote! { pixels(1.) }, "1px"),
396        ("2", quote! { pixels(2.) }, "2px"),
397        ("3", quote! { pixels(3.) }, "3px"),
398        ("4", quote! { pixels(4.) }, "4px"),
399        ("5", quote! { pixels(5.) }, "5px"),
400        ("6", quote! { pixels(6.) }, "6px"),
401        ("7", quote! { pixels(7.) }, "7px"),
402        ("8", quote! { pixels(8.) }, "8px"),
403        ("9", quote! { pixels(9.) }, "9px"),
404        ("10", quote! { pixels(10.) }, "10px"),
405        ("11", quote! { pixels(11.) }, "11px"),
406        ("12", quote! { pixels(12.) }, "12px"),
407        ("16", quote! { pixels(16.) }, "16px"),
408        ("20", quote! { pixels(20.) }, "20px"),
409        ("24", quote! { pixels(24.) }, "24px"),
410        ("32", quote! { pixels(32.) }, "32px"),
411    ]
412}