dampen_core/codegen/
subscription.rs1use proc_macro2::TokenStream;
7use quote::quote;
8
9use crate::ir::theme::ThemeDocument;
10
11#[derive(Debug, Clone)]
13pub struct SubscriptionConfig {
14 pub system_theme: bool,
16 pub message_name: String,
18 pub system_theme_variant: Option<String>,
20}
21
22impl Default for SubscriptionConfig {
23 fn default() -> Self {
24 Self {
25 system_theme: false,
26 message_name: "Message".to_string(),
27 system_theme_variant: None,
28 }
29 }
30}
31
32impl SubscriptionConfig {
33 pub fn from_theme_document(theme_doc: Option<&ThemeDocument>, message_name: &str) -> Self {
38 let system_theme = theme_doc.map(|doc| doc.follow_system).unwrap_or(false);
39
40 Self {
41 system_theme,
42 message_name: message_name.to_string(),
43 system_theme_variant: if system_theme {
44 Some("SystemThemeChanged".to_string())
45 } else {
46 None
47 },
48 }
49 }
50
51 pub fn with_system_theme_variant(mut self, variant: impl Into<String>) -> Self {
53 self.system_theme_variant = Some(variant.into());
54 self.system_theme = true;
55 self
56 }
57}
58
59pub fn generate_subscription_function(config: &SubscriptionConfig) -> TokenStream {
85 let message_ident = syn::Ident::new(&config.message_name, proc_macro2::Span::call_site());
86
87 if let Some(ref variant_name) = config.system_theme_variant {
88 let variant_ident = syn::Ident::new(variant_name, proc_macro2::Span::call_site());
89
90 quote! {
91 pub fn subscription_model() -> iced::Subscription<#message_ident> {
96 if app_follows_system() {
97 dampen_iced::watch_system_theme()
98 .map(#message_ident::#variant_ident)
99 } else {
100 iced::Subscription::none()
101 }
102 }
103 }
104 } else {
105 quote! {
106 pub fn subscription_model() -> iced::Subscription<#message_ident> {
110 iced::Subscription::none()
111 }
112 }
113 }
114}
115
116pub fn generate_system_theme_variant(config: &SubscriptionConfig) -> Option<TokenStream> {
129 config.system_theme_variant.as_ref().map(|variant_name| {
130 let variant_ident = syn::Ident::new(variant_name, proc_macro2::Span::call_site());
131 quote! {
132 #variant_ident(String)
134 }
135 })
136}
137
138pub fn generate_system_theme_update_arm(config: &SubscriptionConfig) -> Option<TokenStream> {
161 let message_ident = syn::Ident::new(&config.message_name, proc_macro2::Span::call_site());
162
163 config.system_theme_variant.as_ref().map(|variant_name| {
164 let variant_ident = syn::Ident::new(variant_name, proc_macro2::Span::call_site());
165
166 quote! {
167 #message_ident::#variant_ident(theme_name) => {
168 app_set_current_theme(&theme_name);
170 iced::Task::none()
171 }
172 }
173 })
174}
175
176#[cfg(test)]
177mod tests {
178 use super::*;
179 use crate::ir::theme::{SpacingScale, Theme, ThemeDocument, ThemePalette, Typography};
180 use std::collections::HashMap;
181
182 fn create_test_theme_document(follow_system: bool) -> ThemeDocument {
183 let mut themes = HashMap::new();
184 themes.insert(
185 "light".to_string(),
186 Theme {
187 name: "light".to_string(),
188 palette: ThemePalette::light(),
189 typography: Typography {
190 font_family: None,
191 font_size_base: Some(16.0),
192 font_size_small: Some(12.0),
193 font_size_large: Some(24.0),
194 font_weight: crate::ir::theme::FontWeight::Normal,
195 line_height: Some(1.5),
196 },
197 spacing: SpacingScale { unit: Some(8.0) },
198 base_styles: HashMap::new(),
199 extends: None,
200 },
201 );
202
203 ThemeDocument {
204 themes,
205 default_theme: Some("light".to_string()),
206 follow_system,
207 }
208 }
209
210 #[test]
211 fn test_subscription_config_from_theme_document() {
212 let doc = create_test_theme_document(true);
213 let config = SubscriptionConfig::from_theme_document(Some(&doc), "Message");
214
215 assert!(config.system_theme);
216 assert_eq!(
217 config.system_theme_variant,
218 Some("SystemThemeChanged".to_string())
219 );
220 }
221
222 #[test]
223 fn test_subscription_config_no_follow_system() {
224 let doc = create_test_theme_document(false);
225 let config = SubscriptionConfig::from_theme_document(Some(&doc), "Message");
226
227 assert!(!config.system_theme);
228 assert_eq!(config.system_theme_variant, None);
229 }
230
231 #[test]
232 fn test_generate_subscription_function_with_system_theme() {
233 let config = SubscriptionConfig {
234 system_theme: true,
235 message_name: "Message".to_string(),
236 system_theme_variant: Some("SystemThemeChanged".to_string()),
237 };
238
239 let tokens = generate_subscription_function(&config);
240 let code = tokens.to_string();
241
242 assert!(code.contains("subscription_model"), "code: {}", code);
243 assert!(code.contains("app_follows_system"), "code: {}", code);
244 assert!(code.contains("watch_system_theme"), "code: {}", code);
246 assert!(code.contains("SystemThemeChanged"), "code: {}", code);
247 }
248
249 #[test]
250 fn test_generate_subscription_function_without_system_theme() {
251 let config = SubscriptionConfig::default();
252
253 let tokens = generate_subscription_function(&config);
254 let code = tokens.to_string();
255
256 assert!(code.contains("subscription_model"), "code: {}", code);
257 assert!(
259 code.contains("Subscription") && code.contains("none"),
260 "code: {}",
261 code
262 );
263 assert!(!code.contains("watch_system_theme"), "code: {}", code);
264 }
265
266 #[test]
267 fn test_generate_system_theme_variant() {
268 let config = SubscriptionConfig {
269 system_theme: true,
270 message_name: "Message".to_string(),
271 system_theme_variant: Some("SystemThemeChanged".to_string()),
272 };
273
274 let tokens = generate_system_theme_variant(&config);
275 assert!(tokens.is_some());
276
277 let code = tokens.unwrap().to_string();
278 assert!(code.contains("SystemThemeChanged"));
279 assert!(code.contains("String"));
280 }
281
282 #[test]
283 fn test_generate_system_theme_update_arm() {
284 let config = SubscriptionConfig {
285 system_theme: true,
286 message_name: "Message".to_string(),
287 system_theme_variant: Some("SystemThemeChanged".to_string()),
288 };
289
290 let tokens = generate_system_theme_update_arm(&config);
291 assert!(tokens.is_some());
292
293 let code = tokens.unwrap().to_string();
294 assert!(
296 code.contains("Message") && code.contains("SystemThemeChanged"),
297 "code: {}",
298 code
299 );
300 assert!(code.contains("theme_name"), "code: {}", code);
301 assert!(
303 code.contains("Task") && code.contains("none"),
304 "code: {}",
305 code
306 );
307 }
308}