Skip to main content

iced_layout_macro/
lib.rs

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/// Reads an XML layout file at compile time and generates iced widget code.
42///
43/// The path is relative to `CARGO_MANIFEST_DIR`.
44///
45/// # Example
46/// ```ignore
47/// layout!("src/page/test-layout.xml")
48/// ```
49#[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}