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