1use jag_draw::ColorLinPremul;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
10pub enum ThemeMode {
11 Dark,
12 Light,
13}
14
15#[derive(Debug, Clone, Copy)]
17pub struct ElementColors {
18 pub text: ColorLinPremul,
20 pub input_bg: ColorLinPremul,
22 pub input_border: ColorLinPremul,
24 pub button_bg: ColorLinPremul,
26 pub button_fg: ColorLinPremul,
28 pub focus_ring: ColorLinPremul,
30 pub error: ColorLinPremul,
32}
33
34impl ElementColors {
35 pub fn for_theme(mode: ThemeMode) -> Self {
37 match mode {
38 ThemeMode::Dark => Self {
39 text: ColorLinPremul::from_srgba_u8([255, 255, 255, 255]),
40 input_bg: ColorLinPremul::from_srgba_u8([40, 40, 40, 255]),
41 input_border: ColorLinPremul::from_srgba_u8([80, 80, 80, 255]),
42 button_bg: ColorLinPremul::from_srgba_u8([59, 130, 246, 255]),
43 button_fg: ColorLinPremul::from_srgba_u8([255, 255, 255, 255]),
44 focus_ring: ColorLinPremul::from_srgba_u8([59, 130, 246, 255]),
45 error: ColorLinPremul::from_srgba_u8([239, 68, 68, 255]),
46 },
47 ThemeMode::Light => Self {
48 text: ColorLinPremul::from_srgba_u8([15, 23, 42, 255]),
49 input_bg: ColorLinPremul::from_srgba_u8([248, 250, 252, 255]),
50 input_border: ColorLinPremul::from_srgba_u8([203, 213, 225, 255]),
51 button_bg: ColorLinPremul::from_srgba_u8([37, 99, 235, 255]),
52 button_fg: ColorLinPremul::from_srgba_u8([255, 255, 255, 255]),
53 focus_ring: ColorLinPremul::from_srgba_u8([37, 99, 235, 255]),
54 error: ColorLinPremul::from_srgba_u8([220, 38, 38, 255]),
55 },
56 }
57 }
58}
59
60#[derive(Debug, Clone, Copy)]
62pub struct Theme {
63 pub mode: ThemeMode,
65 pub colors: ElementColors,
67 pub font_size: f32,
69 pub border_radius: f32,
71 pub spacing: f32,
73}
74
75impl Theme {
76 pub fn dark() -> Self {
78 Self {
79 mode: ThemeMode::Dark,
80 colors: ElementColors::for_theme(ThemeMode::Dark),
81 font_size: 14.0,
82 border_radius: 6.0,
83 spacing: 8.0,
84 }
85 }
86
87 pub fn light() -> Self {
89 Self {
90 mode: ThemeMode::Light,
91 colors: ElementColors::for_theme(ThemeMode::Light),
92 font_size: 14.0,
93 border_radius: 6.0,
94 spacing: 8.0,
95 }
96 }
97}
98
99impl Default for Theme {
100 fn default() -> Self {
101 Self::dark()
102 }
103}
104
105#[cfg(test)]
110mod tests {
111 use super::*;
112
113 #[test]
114 fn dark_theme_has_dark_mode() {
115 let t = Theme::dark();
116 assert_eq!(t.mode, ThemeMode::Dark);
117 }
118
119 #[test]
120 fn light_theme_has_light_mode() {
121 let t = Theme::light();
122 assert_eq!(t.mode, ThemeMode::Light);
123 }
124
125 #[test]
126 fn default_is_dark() {
127 let t = Theme::default();
128 assert_eq!(t.mode, ThemeMode::Dark);
129 }
130
131 #[test]
132 fn element_colors_for_dark() {
133 let c = ElementColors::for_theme(ThemeMode::Dark);
134 let white = ColorLinPremul::from_srgba_u8([255, 255, 255, 255]);
136 assert_eq!(c.text, white);
137 }
138
139 #[test]
140 fn element_colors_for_light() {
141 let c = ElementColors::for_theme(ThemeMode::Light);
142 let dark_text = ColorLinPremul::from_srgba_u8([15, 23, 42, 255]);
144 assert_eq!(c.text, dark_text);
145 }
146
147 #[test]
148 fn theme_defaults_reasonable() {
149 let t = Theme::dark();
150 assert!(t.font_size > 0.0);
151 assert!(t.border_radius >= 0.0);
152 assert!(t.spacing > 0.0);
153 }
154}