1use serde::{Deserialize, Serialize};
4
5use crate::Rgba;
6
7#[serde_with::skip_serializing_none]
13#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
14#[serde(default)]
15#[non_exhaustive]
16pub struct ThemeColors {
17 pub accent: Option<Rgba>,
20 pub background: Option<Rgba>,
22 pub foreground: Option<Rgba>,
24 pub surface: Option<Rgba>,
26 pub border: Option<Rgba>,
28 pub muted: Option<Rgba>,
30 pub shadow: Option<Rgba>,
32 pub primary_background: Option<Rgba>,
35 pub primary_foreground: Option<Rgba>,
37 pub secondary_background: Option<Rgba>,
40 pub secondary_foreground: Option<Rgba>,
42 pub danger: Option<Rgba>,
45 pub danger_foreground: Option<Rgba>,
47 pub warning: Option<Rgba>,
49 pub warning_foreground: Option<Rgba>,
51 pub success: Option<Rgba>,
53 pub success_foreground: Option<Rgba>,
55 pub info: Option<Rgba>,
57 pub info_foreground: Option<Rgba>,
59 pub selection: Option<Rgba>,
62 pub selection_foreground: Option<Rgba>,
64 pub link: Option<Rgba>,
66 pub focus_ring: Option<Rgba>,
68 pub sidebar: Option<Rgba>,
71 pub sidebar_foreground: Option<Rgba>,
73 pub tooltip: Option<Rgba>,
75 pub tooltip_foreground: Option<Rgba>,
77 pub popover: Option<Rgba>,
79 pub popover_foreground: Option<Rgba>,
81 pub button: Option<Rgba>,
84 pub button_foreground: Option<Rgba>,
86 pub input: Option<Rgba>,
88 pub input_foreground: Option<Rgba>,
90 pub disabled: Option<Rgba>,
92 pub separator: Option<Rgba>,
94 pub alternate_row: Option<Rgba>,
96}
97
98impl_merge!(ThemeColors {
99 option {
100 accent, background, foreground, surface, border, muted, shadow,
101 primary_background, primary_foreground,
102 secondary_background, secondary_foreground,
103 danger, danger_foreground, warning, warning_foreground,
104 success, success_foreground, info, info_foreground,
105 selection, selection_foreground, link, focus_ring,
106 sidebar, sidebar_foreground, tooltip, tooltip_foreground,
107 popover, popover_foreground,
108 button, button_foreground, input, input_foreground,
109 disabled, separator, alternate_row
110 }
111});
112
113#[cfg(test)]
114mod tests {
115 use super::*;
116 use crate::Rgba;
117
118 #[test]
121 fn total_color_roles_is_36() {
122 assert_eq!(7 + 2 + 2 + 8 + 4 + 6 + 7, 36);
125 }
126
127 #[test]
130 fn theme_colors_default_is_empty() {
131 assert!(ThemeColors::default().is_empty());
132 }
133
134 #[test]
135 fn theme_colors_not_empty_when_field_set() {
136 let tc = ThemeColors {
137 accent: Some(Rgba::rgb(255, 0, 0)),
138 ..Default::default()
139 };
140 assert!(!tc.is_empty());
141 }
142
143 #[test]
146 fn merge_some_replaces_none() {
147 let mut base = ThemeColors::default();
148 let overlay = ThemeColors {
149 accent: Some(Rgba::rgb(255, 0, 0)),
150 ..Default::default()
151 };
152 base.merge(&overlay);
153 assert_eq!(base.accent, Some(Rgba::rgb(255, 0, 0)));
154 }
155
156 #[test]
157 fn merge_none_preserves_base() {
158 let mut base = ThemeColors {
159 accent: Some(Rgba::rgb(0, 0, 255)),
160 ..Default::default()
161 };
162 let overlay = ThemeColors::default(); base.merge(&overlay);
164 assert_eq!(base.accent, Some(Rgba::rgb(0, 0, 255)));
165 }
166
167 #[test]
168 fn merge_some_replaces_some() {
169 let mut base = ThemeColors {
170 accent: Some(Rgba::rgb(0, 0, 255)),
171 ..Default::default()
172 };
173 let overlay = ThemeColors {
174 accent: Some(Rgba::rgb(255, 0, 0)),
175 ..Default::default()
176 };
177 base.merge(&overlay);
178 assert_eq!(base.accent, Some(Rgba::rgb(255, 0, 0)));
179 }
180
181 #[test]
182 fn merge_flat_fields_across_groups() {
183 let mut base = ThemeColors {
184 background: Some(Rgba::rgb(255, 255, 255)),
185 danger: Some(Rgba::rgb(200, 0, 0)),
186 ..Default::default()
187 };
188
189 let overlay = ThemeColors {
190 accent: Some(Rgba::rgb(0, 120, 215)),
191 danger: Some(Rgba::rgb(255, 0, 0)), ..Default::default()
193 };
194
195 base.merge(&overlay);
196
197 assert_eq!(base.accent, Some(Rgba::rgb(0, 120, 215)));
199 assert_eq!(base.background, Some(Rgba::rgb(255, 255, 255)));
201 assert_eq!(base.danger, Some(Rgba::rgb(255, 0, 0)));
203 }
204
205 #[test]
206 fn merge_primary_and_secondary_fields() {
207 let mut base = ThemeColors {
208 primary_background: Some(Rgba::rgb(0, 0, 255)),
209 ..Default::default()
210 };
211
212 let overlay = ThemeColors {
213 secondary_foreground: Some(Rgba::rgb(255, 255, 255)),
214 ..Default::default()
215 };
216
217 base.merge(&overlay);
218
219 assert_eq!(base.primary_background, Some(Rgba::rgb(0, 0, 255)));
221 assert_eq!(base.secondary_foreground, Some(Rgba::rgb(255, 255, 255)));
223 }
224}