dampen_core/codegen/
application.rs

1//! Application trait generation
2
3use proc_macro2::TokenStream;
4use quote::quote;
5
6use super::CodegenError;
7use super::theme::generate_theme_code;
8use crate::ir::theme::ThemeDocument;
9
10/// Generate Application trait implementation
11pub fn generate_application_trait(
12    model_name: &str,
13    message_name: &str,
14) -> Result<TokenStream, super::CodegenError> {
15    let model_ident = syn::Ident::new(model_name, proc_macro2::Span::call_site());
16    let message_ident = syn::Ident::new(message_name, proc_macro2::Span::call_site());
17
18    Ok(quote! {
19        impl iced::Application for #model_ident {
20            type Executor = iced::executor::Default;
21            type Flags = ();
22            type Message = #message_ident;
23
24            fn new(_flags: ()) -> (Self, iced::Task<Self::Message>) {
25                (Self::default(), iced::Task::none())
26            }
27
28            fn title(&self) -> String {
29                "Dampen Application".to_string()
30            }
31        }
32    })
33}
34
35/// Generate theme code and add it to the application
36pub fn generate_application_with_theme(
37    model_name: &str,
38    message_name: &str,
39    theme_document: Option<&ThemeDocument>,
40) -> Result<TokenStream, CodegenError> {
41    let model_ident = syn::Ident::new(model_name, proc_macro2::Span::call_site());
42    let message_ident = syn::Ident::new(message_name, proc_macro2::Span::call_site());
43
44    let theme_code = if let Some(doc) = theme_document {
45        match generate_theme_code(doc, "app") {
46            Ok(generated) => generated.code,
47            Err(e) => {
48                return Err(CodegenError::ThemeError(e));
49            }
50        }
51    } else {
52        String::new()
53    };
54
55    Ok(quote! {
56        #theme_code
57
58        impl iced::Application for #model_ident {
59            type Executor = iced::executor::Default;
60            type Flags = ();
61            type Message = #message_ident;
62
63            fn new(_flags: ()) -> (Self, iced::Task<Self::Message>) {
64                (Self::default(), iced::Task::none())
65            }
66
67            fn title(&self) -> String {
68                "Dampen Application".to_string()
69            }
70
71            fn theme(&self) -> iced::Theme {
72                app_theme()
73            }
74        }
75    })
76}
77
78#[cfg(test)]
79mod tests {
80    use super::*;
81    use crate::ir::style::Color;
82    use crate::ir::theme::{SpacingScale, Theme, ThemeDocument, ThemePalette, Typography};
83
84    fn create_test_palette() -> ThemePalette {
85        ThemePalette {
86            primary: Some(Color::from_hex("#3498db").unwrap()),
87            secondary: Some(Color::from_hex("#2ecc71").unwrap()),
88            success: Some(Color::from_hex("#27ae60").unwrap()),
89            warning: Some(Color::from_hex("#f39c12").unwrap()),
90            danger: Some(Color::from_hex("#e74c3c").unwrap()),
91            background: Some(Color::from_hex("#ecf0f1").unwrap()),
92            surface: Some(Color::from_hex("#ffffff").unwrap()),
93            text: Some(Color::from_hex("#2c3e50").unwrap()),
94            text_secondary: Some(Color::from_hex("#7f8c8d").unwrap()),
95        }
96    }
97
98    fn create_test_theme(name: &str) -> Theme {
99        Theme {
100            name: name.to_string(),
101            palette: create_test_palette(),
102            typography: Typography {
103                font_family: Some("sans-serif".to_string()),
104                font_size_base: Some(16.0),
105                font_size_small: Some(12.0),
106                font_size_large: Some(24.0),
107                font_weight: crate::ir::theme::FontWeight::Normal,
108                line_height: Some(1.5),
109            },
110            spacing: SpacingScale { unit: Some(8.0) },
111            base_styles: std::collections::HashMap::new(),
112            extends: None,
113        }
114    }
115
116    #[test]
117    fn test_application_trait_generation() {
118        let result = generate_application_trait("MyModel", "MyMessage").unwrap();
119        let code = result.to_string();
120
121        assert!(code.contains("impl") && code.contains("Application") && code.contains("MyModel"));
122        assert!(code.contains("Message") && code.contains("MyMessage"));
123    }
124
125    #[test]
126    fn test_application_with_theme_generation() {
127        let doc = ThemeDocument {
128            themes: std::collections::HashMap::from([(
129                "light".to_string(),
130                create_test_theme("light"),
131            )]),
132            default_theme: Some("light".to_string()),
133            follow_system: true,
134        };
135
136        let result = generate_application_with_theme("MyModel", "MyMessage", Some(&doc));
137        assert!(result.is_ok());
138        let code = result.unwrap().to_string();
139
140        assert!(code.contains("impl") && code.contains("Application"));
141        assert!(code.contains("app_theme()"));
142        assert!(code.contains("fn app_light()"));
143    }
144
145    #[test]
146    fn test_application_without_theme() {
147        let result = generate_application_with_theme("MyModel", "MyMessage", None);
148        assert!(result.is_ok());
149        let code = result.unwrap().to_string();
150
151        assert!(code.contains("impl") && code.contains("Application"));
152    }
153}