1use crate::graphics::Color;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum FontWeight {
12 Normal,
13 Bold,
14}
15
16#[derive(Debug, Clone)]
18pub struct DashboardTheme {
19 pub colors: ThemeColors,
21 pub typography: Typography,
23 pub spacing: ThemeSpacing,
25 pub borders: ThemeBorders,
27 pub backgrounds: ThemeBackgrounds,
29}
30
31impl DashboardTheme {
32 pub fn new(colors: ThemeColors, typography: Typography) -> Self {
34 Self {
35 colors,
36 typography,
37 spacing: ThemeSpacing::default(),
38 borders: ThemeBorders::default(),
39 backgrounds: ThemeBackgrounds::default(),
40 }
41 }
42
43 pub fn corporate() -> Self {
45 let colors = ThemeColors {
46 primary: Color::hex("#1f4788"),
47 secondary: Color::hex("#4a90a4"),
48 accent: Color::hex("#87ceeb"),
49 success: Color::hex("#28a745"),
50 warning: Color::hex("#ffc107"),
51 danger: Color::hex("#dc3545"),
52 info: Color::hex("#17a2b8"),
53 light: Color::hex("#f8f9fa"),
54 dark: Color::hex("#343a40"),
55 muted: Color::hex("#6c757d"),
56 background: Color::white(),
57 surface: Color::hex("#f0f4f8"), text_primary: Color::hex("#212529"),
59 text_secondary: Color::hex("#6c757d"),
60 text_muted: Color::hex("#adb5bd"),
61 border: Color::hex("#dee2e6"),
62 };
63
64 let typography = Typography {
65 title_font: "Helvetica-Bold".to_string(),
66 title_size: 24.0,
67 title_color: colors.text_primary,
68 heading_font: "Helvetica-Bold".to_string(),
69 heading_size: 18.0,
70 heading_color: colors.text_primary,
71 body_font: "Helvetica".to_string(),
72 body_size: 12.0,
73 body_color: colors.text_primary,
74 caption_font: "Helvetica".to_string(),
75 caption_size: 10.0,
76 caption_color: colors.text_secondary,
77 line_height: 1.4,
78 };
79
80 Self {
81 colors,
82 typography,
83 spacing: ThemeSpacing::corporate(),
84 borders: ThemeBorders::corporate(),
85 backgrounds: ThemeBackgrounds::corporate(),
86 }
87 }
88
89 pub fn minimal() -> Self {
91 let colors = ThemeColors {
92 primary: Color::hex("#000000"),
93 secondary: Color::hex("#666666"),
94 accent: Color::hex("#999999"),
95 success: Color::hex("#4caf50"),
96 warning: Color::hex("#ff9800"),
97 danger: Color::hex("#f44336"),
98 info: Color::hex("#2196f3"),
99 light: Color::hex("#fafafa"),
100 dark: Color::hex("#212121"),
101 muted: Color::hex("#757575"),
102 background: Color::white(),
103 surface: Color::hex("#ffffff"),
104 text_primary: Color::hex("#212121"),
105 text_secondary: Color::hex("#757575"),
106 text_muted: Color::hex("#bdbdbd"),
107 border: Color::hex("#e0e0e0"),
108 };
109
110 let typography = Typography::minimal();
111
112 Self {
113 colors,
114 typography,
115 spacing: ThemeSpacing::minimal(),
116 borders: ThemeBorders::minimal(),
117 backgrounds: ThemeBackgrounds::minimal(),
118 }
119 }
120
121 pub fn dark() -> Self {
123 let colors = ThemeColors {
124 primary: Color::hex("#bb86fc"),
125 secondary: Color::hex("#03dac6"),
126 accent: Color::hex("#cf6679"),
127 success: Color::hex("#4caf50"),
128 warning: Color::hex("#ff9800"),
129 danger: Color::hex("#f44336"),
130 info: Color::hex("#2196f3"),
131 light: Color::hex("#424242"),
132 dark: Color::hex("#121212"),
133 muted: Color::hex("#757575"),
134 background: Color::hex("#121212"),
135 surface: Color::hex("#1e1e1e"),
136 text_primary: Color::hex("#ffffff"),
137 text_secondary: Color::hex("#b3b3b3"),
138 text_muted: Color::hex("#666666"),
139 border: Color::hex("#333333"),
140 };
141
142 let typography = Typography::dark();
143
144 Self {
145 colors,
146 typography,
147 spacing: ThemeSpacing::default(),
148 borders: ThemeBorders::dark(),
149 backgrounds: ThemeBackgrounds::dark(),
150 }
151 }
152
153 pub fn colorful() -> Self {
155 let colors = ThemeColors {
156 primary: Color::hex("#e91e63"),
157 secondary: Color::hex("#9c27b0"),
158 accent: Color::hex("#ff5722"),
159 success: Color::hex("#4caf50"),
160 warning: Color::hex("#ff9800"),
161 danger: Color::hex("#f44336"),
162 info: Color::hex("#2196f3"),
163 light: Color::hex("#fce4ec"),
164 dark: Color::hex("#880e4f"),
165 muted: Color::hex("#ad1457"),
166 background: Color::hex("#fafafa"),
167 surface: Color::white(),
168 text_primary: Color::hex("#212121"),
169 text_secondary: Color::hex("#757575"),
170 text_muted: Color::hex("#bdbdbd"),
171 border: Color::hex("#e1bee7"),
172 };
173
174 let typography = Typography::colorful();
175
176 Self {
177 colors,
178 typography,
179 spacing: ThemeSpacing::colorful(),
180 borders: ThemeBorders::colorful(),
181 backgrounds: ThemeBackgrounds::colorful(),
182 }
183 }
184
185 pub fn set_color_palette(&mut self, colors: Vec<Color>) {
187 if !colors.is_empty() {
188 self.colors.primary = colors[0];
189 if colors.len() > 1 {
190 self.colors.secondary = colors[1];
191 }
192 if colors.len() > 2 {
193 self.colors.accent = colors[2];
194 }
195 }
196 }
197
198 pub fn set_typography(&mut self, typography: Typography) {
200 self.typography = typography;
201 }
202
203 pub fn title_style(&self) -> TextStyle {
205 TextStyle {
206 font_name: self.typography.title_font.clone(),
207 font_size: self.typography.title_size,
208 color: self.typography.title_color,
209 weight: FontWeight::Bold,
210 alignment: TextAlignment::Center,
211 }
212 }
213
214 pub fn subtitle_style(&self) -> TextStyle {
216 TextStyle {
217 font_name: self.typography.heading_font.clone(),
218 font_size: self.typography.heading_size,
219 color: self.typography.heading_color,
220 weight: FontWeight::Normal,
221 alignment: TextAlignment::Center,
222 }
223 }
224
225 pub fn footer_style(&self) -> TextStyle {
227 TextStyle {
228 font_name: self.typography.caption_font.clone(),
229 font_size: self.typography.caption_size,
230 color: self.typography.caption_color,
231 weight: FontWeight::Normal,
232 alignment: TextAlignment::Left,
233 }
234 }
235
236 pub fn title_height(&self) -> f64 {
238 self.typography.title_size * self.typography.line_height
239 }
240
241 pub fn footer_height(&self) -> f64 {
243 self.typography.caption_size * self.typography.line_height
244 }
245}
246
247impl Default for DashboardTheme {
248 fn default() -> Self {
249 Self::corporate()
250 }
251}
252
253#[derive(Debug, Clone)]
255pub struct ThemeColors {
256 pub primary: Color,
258 pub secondary: Color,
260 pub accent: Color,
262 pub success: Color,
264 pub warning: Color,
266 pub danger: Color,
268 pub info: Color,
270 pub light: Color,
272 pub dark: Color,
274 pub muted: Color,
276 pub background: Color,
278 pub surface: Color,
280 pub text_primary: Color,
282 pub text_secondary: Color,
284 pub text_muted: Color,
286 pub border: Color,
288}
289
290#[derive(Debug, Clone)]
292pub struct Typography {
293 pub title_font: String,
295 pub title_size: f64,
297 pub title_color: Color,
299 pub heading_font: String,
301 pub heading_size: f64,
303 pub heading_color: Color,
305 pub body_font: String,
307 pub body_size: f64,
309 pub body_color: Color,
311 pub caption_font: String,
313 pub caption_size: f64,
315 pub caption_color: Color,
317 pub line_height: f64,
319}
320
321impl Typography {
322 pub fn professional() -> Self {
324 Self {
325 title_font: "Helvetica-Bold".to_string(),
326 title_size: 24.0,
327 title_color: Color::hex("#212529"),
328 heading_font: "Helvetica-Bold".to_string(),
329 heading_size: 18.0,
330 heading_color: Color::hex("#212529"),
331 body_font: "Helvetica".to_string(),
332 body_size: 12.0,
333 body_color: Color::hex("#212529"),
334 caption_font: "Helvetica".to_string(),
335 caption_size: 10.0,
336 caption_color: Color::hex("#6c757d"),
337 line_height: 1.4,
338 }
339 }
340
341 pub fn minimal() -> Self {
343 Self {
344 title_font: "Helvetica-Light".to_string(),
345 title_size: 28.0,
346 title_color: Color::hex("#212121"),
347 heading_font: "Helvetica".to_string(),
348 heading_size: 16.0,
349 heading_color: Color::hex("#212121"),
350 body_font: "Helvetica".to_string(),
351 body_size: 11.0,
352 body_color: Color::hex("#212121"),
353 caption_font: "Helvetica".to_string(),
354 caption_size: 9.0,
355 caption_color: Color::hex("#757575"),
356 line_height: 1.5,
357 }
358 }
359
360 pub fn dark() -> Self {
362 Self {
363 title_font: "Helvetica-Bold".to_string(),
364 title_size: 24.0,
365 title_color: Color::white(),
366 heading_font: "Helvetica-Bold".to_string(),
367 heading_size: 18.0,
368 heading_color: Color::white(),
369 body_font: "Helvetica".to_string(),
370 body_size: 12.0,
371 body_color: Color::white(),
372 caption_font: "Helvetica".to_string(),
373 caption_size: 10.0,
374 caption_color: Color::hex("#b3b3b3"),
375 line_height: 1.4,
376 }
377 }
378
379 pub fn colorful() -> Self {
381 Self {
382 title_font: "Helvetica-Bold".to_string(),
383 title_size: 26.0,
384 title_color: Color::hex("#880e4f"),
385 heading_font: "Helvetica-Bold".to_string(),
386 heading_size: 18.0,
387 heading_color: Color::hex("#ad1457"),
388 body_font: "Helvetica".to_string(),
389 body_size: 12.0,
390 body_color: Color::hex("#212121"),
391 caption_font: "Helvetica".to_string(),
392 caption_size: 10.0,
393 caption_color: Color::hex("#757575"),
394 line_height: 1.4,
395 }
396 }
397}
398
399impl Default for Typography {
400 fn default() -> Self {
401 Self::professional()
402 }
403}
404
405#[derive(Debug, Clone)]
407pub struct TextStyle {
408 pub font_name: String,
410 pub font_size: f64,
412 pub color: Color,
414 pub weight: FontWeight,
416 pub alignment: TextAlignment,
418}
419
420#[derive(Debug, Clone, Copy, PartialEq, Eq)]
422pub enum TextAlignment {
423 Left,
424 Center,
425 Right,
426}
427
428#[derive(Debug, Clone)]
430pub struct ThemeSpacing {
431 pub xs: f64,
433 pub sm: f64,
435 pub md: f64,
437 pub lg: f64,
439 pub xl: f64,
441}
442
443impl ThemeSpacing {
444 pub fn corporate() -> Self {
445 Self {
446 xs: 4.0,
447 sm: 8.0,
448 md: 16.0,
449 lg: 24.0,
450 xl: 32.0,
451 }
452 }
453
454 pub fn minimal() -> Self {
455 Self {
456 xs: 2.0,
457 sm: 6.0,
458 md: 12.0,
459 lg: 20.0,
460 xl: 28.0,
461 }
462 }
463
464 pub fn colorful() -> Self {
465 Self {
466 xs: 6.0,
467 sm: 10.0,
468 md: 18.0,
469 lg: 28.0,
470 xl: 36.0,
471 }
472 }
473}
474
475impl Default for ThemeSpacing {
476 fn default() -> Self {
477 Self::corporate()
478 }
479}
480
481#[derive(Debug, Clone)]
483pub struct ThemeBorders {
484 pub thin: f64,
486 pub medium: f64,
488 pub thick: f64,
490 pub radius: f64,
492}
493
494impl ThemeBorders {
495 pub fn corporate() -> Self {
496 Self {
497 thin: 0.5,
498 medium: 1.0,
499 thick: 2.0,
500 radius: 4.0,
501 }
502 }
503
504 pub fn minimal() -> Self {
505 Self {
506 thin: 0.25,
507 medium: 0.5,
508 thick: 1.0,
509 radius: 2.0,
510 }
511 }
512
513 pub fn dark() -> Self {
514 Self {
515 thin: 0.5,
516 medium: 1.0,
517 thick: 2.0,
518 radius: 6.0,
519 }
520 }
521
522 pub fn colorful() -> Self {
523 Self {
524 thin: 1.0,
525 medium: 2.0,
526 thick: 3.0,
527 radius: 8.0,
528 }
529 }
530}
531
532impl Default for ThemeBorders {
533 fn default() -> Self {
534 Self::corporate()
535 }
536}
537
538#[derive(Debug, Clone)]
540pub struct ThemeBackgrounds {
541 pub primary: Color,
543 pub secondary: Color,
545 pub surface: Color,
547 pub hover: Color,
549}
550
551impl ThemeBackgrounds {
552 pub fn corporate() -> Self {
553 Self {
554 primary: Color::white(),
555 secondary: Color::hex("#f8f9fa"),
556 surface: Color::white(),
557 hover: Color::hex("#f5f5f5"),
558 }
559 }
560
561 pub fn minimal() -> Self {
562 Self {
563 primary: Color::white(),
564 secondary: Color::hex("#fafafa"),
565 surface: Color::white(),
566 hover: Color::hex("#f0f0f0"),
567 }
568 }
569
570 pub fn dark() -> Self {
571 Self {
572 primary: Color::hex("#121212"),
573 secondary: Color::hex("#1e1e1e"),
574 surface: Color::hex("#2d2d2d"),
575 hover: Color::hex("#3d3d3d"),
576 }
577 }
578
579 pub fn colorful() -> Self {
580 Self {
581 primary: Color::hex("#fafafa"),
582 secondary: Color::hex("#fce4ec"),
583 surface: Color::white(),
584 hover: Color::hex("#f8bbd9"),
585 }
586 }
587}
588
589impl Default for ThemeBackgrounds {
590 fn default() -> Self {
591 Self::corporate()
592 }
593}
594
595#[cfg(test)]
596mod tests {
597 use super::*;
598
599 #[test]
600 fn test_theme_creation() {
601 let theme = DashboardTheme::corporate();
602 assert_eq!(theme.typography.title_size, 24.0);
603 assert_eq!(theme.colors.primary, Color::hex("#1f4788"));
604 }
605
606 #[test]
607 fn test_theme_variants() {
608 let corporate = DashboardTheme::corporate();
609 let minimal = DashboardTheme::minimal();
610 let dark = DashboardTheme::dark();
611 let colorful = DashboardTheme::colorful();
612
613 assert_ne!(corporate.colors.primary, minimal.colors.primary);
615 assert_ne!(minimal.colors.background, dark.colors.background);
616 assert_ne!(dark.colors.accent, colorful.colors.accent);
617 }
618
619 #[test]
620 fn test_typography_variants() {
621 let professional = Typography::professional();
622 let minimal = Typography::minimal();
623
624 assert_eq!(professional.title_font, "Helvetica-Bold");
625 assert_eq!(minimal.title_font, "Helvetica-Light");
626 assert_ne!(professional.title_size, minimal.title_size);
627 }
628
629 #[test]
630 fn test_theme_spacing() {
631 let spacing = ThemeSpacing::corporate();
632 assert_eq!(spacing.xs, 4.0);
633 assert_eq!(spacing.sm, 8.0);
634 assert_eq!(spacing.md, 16.0);
635 assert_eq!(spacing.lg, 24.0);
636 assert_eq!(spacing.xl, 32.0);
637 }
638
639 #[test]
640 fn test_text_style_creation() {
641 let theme = DashboardTheme::default();
642 let title_style = theme.title_style();
643
644 assert_eq!(title_style.alignment, TextAlignment::Center);
645 assert_eq!(title_style.weight, FontWeight::Bold);
646 }
647}