1extern crate proc_macro;
2
3mod generate;
4mod interpolation;
5mod style;
6mod types;
7
8use proc_macro::TokenStream;
9use std::collections::HashMap;
10use std::path::PathBuf;
11
12use iced_layout_core::{
13 ButtonStyle, CheckboxStyle, ContainerStyle, FontDef, TextInputStyle, TextStyle,
14};
15use quote::{format_ident, quote};
16use syn::{LitStr, parse_macro_input};
17
18use crate::style::{
19 generate_button_style_closure, generate_checkbox_style_closure,
20 generate_container_style, generate_text_input_style_closure,
21};
22use crate::types::generate_font_def;
23
24pub(crate) struct StyleMaps<'a> {
25 pub container: HashMap<&'a str, &'a ContainerStyle>,
26 pub text: HashMap<&'a str, &'a TextStyle>,
27 pub button: HashMap<&'a str, &'a ButtonStyle>,
28 pub checkbox: HashMap<&'a str, &'a CheckboxStyle>,
29 pub text_input: HashMap<&'a str, &'a TextInputStyle>,
30 pub font: HashMap<&'a str, &'a FontDef>,
31}
32
33pub(crate) fn style_var_name(prefix: &str, name: &str) -> syn::Ident {
34 let sanitized: String = name
35 .chars()
36 .map(|c| if c.is_alphanumeric() { c } else { '_' })
37 .collect();
38 format_ident!("__style_{}_{}", prefix, sanitized)
39}
40
41#[proc_macro]
50pub fn layout(input: TokenStream) -> TokenStream {
51 let lit = parse_macro_input!(input as LitStr);
52 let rel_path = lit.value();
53
54 let manifest_dir = std::env::var("CARGO_MANIFEST_DIR")
55 .expect("CARGO_MANIFEST_DIR not set");
56 let file_path = PathBuf::from(&manifest_dir).join(&rel_path);
57
58 let xml = std::fs::read_to_string(&file_path).unwrap_or_else(|e| {
59 panic!("failed to read {}: {}", file_path.display(), e)
60 });
61
62 let layout = iced_layout_xml::parse(&xml);
63
64 let style_maps = StyleMaps {
65 container: layout.container_styles.iter().map(|(k, v)| (k.as_str(), v)).collect(),
66 text: layout.text_styles.iter().map(|(k, v)| (k.as_str(), v)).collect(),
67 button: layout.button_styles.iter().map(|(k, v)| (k.as_str(), v)).collect(),
68 checkbox: layout.checkbox_styles.iter().map(|(k, v)| (k.as_str(), v)).collect(),
69 text_input: layout.text_input_styles.iter().map(|(k, v)| (k.as_str(), v)).collect(),
70 font: layout.font_defs.iter().map(|(k, v)| (k.as_str(), v)).collect(),
71 };
72
73 let mut style_bindings = Vec::new();
74
75 for (name, cs) in &style_maps.container {
76 let var = style_var_name("container", name);
77 let closure = generate_container_style(cs);
78 style_bindings.push(quote! { let #var = #closure; });
79 }
80 for (name, bs) in &style_maps.button {
81 let var = style_var_name("button", name);
82 let closure = generate_button_style_closure(bs);
83 style_bindings.push(quote! { let #var = #closure; });
84 }
85 for (name, cs) in &style_maps.checkbox {
86 let var = style_var_name("checkbox", name);
87 let closure = generate_checkbox_style_closure(cs);
88 style_bindings.push(quote! { let #var = #closure; });
89 }
90 for (name, tis) in &style_maps.text_input {
91 let var = style_var_name("text_input", name);
92 let closure = generate_text_input_style_closure(tis);
93 style_bindings.push(quote! { let #var = #closure; });
94 }
95 for (name, fd) in &style_maps.font {
96 let var = style_var_name("font", name);
97 let font_expr = generate_font_def(fd);
98 style_bindings.push(quote! { let #var = #font_expr; });
99 }
100
101 let ctx = generate::GenerateContext::default();
102 let tokens = generate::generate(&layout.root, &style_maps, &ctx).into_widget();
103
104 let expanded = if style_bindings.is_empty() {
105 quote! { #tokens.into() }
106 } else {
107 quote! { { #(#style_bindings)* #tokens.into() } }
108 };
109 expanded.into()
110}