1use frust::ColorScheme;
41use peniko::Color;
42
43pub fn color_scheme_light() -> ColorScheme {
52 ColorScheme {
53 primary: Color::from_rgb8(0x67, 0x50, 0xA4),
54 on_primary: Color::from_rgb8(0xFF, 0xFF, 0xFF),
55 primary_container: Color::from_rgb8(0xEA, 0xDD, 0xFF),
56 on_primary_container: Color::from_rgb8(0x21, 0x00, 0x5D),
57 primary_fixed: Color::from_rgb8(0xEA, 0xDD, 0xFF),
58 primary_fixed_dim: Color::from_rgb8(0xD0, 0xBC, 0xFF),
59 on_primary_fixed: Color::from_rgb8(0x21, 0x00, 0x5D),
60 on_primary_fixed_variant: Color::from_rgb8(0x4F, 0x37, 0x8B),
61
62 secondary: Color::from_rgb8(0x62, 0x5B, 0x71),
63 on_secondary: Color::from_rgb8(0xFF, 0xFF, 0xFF),
64 secondary_container: Color::from_rgb8(0xE8, 0xDE, 0xF8),
65 on_secondary_container: Color::from_rgb8(0x1D, 0x19, 0x2B),
66 secondary_fixed: Color::from_rgb8(0xE8, 0xDE, 0xF8),
67 secondary_fixed_dim: Color::from_rgb8(0xCC, 0xC2, 0xDC),
68 on_secondary_fixed: Color::from_rgb8(0x1D, 0x19, 0x2B),
69 on_secondary_fixed_variant: Color::from_rgb8(0x4A, 0x44, 0x58),
70
71 tertiary: Color::from_rgb8(0x7D, 0x52, 0x60),
72 on_tertiary: Color::from_rgb8(0xFF, 0xFF, 0xFF),
73 tertiary_container: Color::from_rgb8(0xFF, 0xD8, 0xE4),
74 on_tertiary_container: Color::from_rgb8(0x31, 0x11, 0x1D),
75 tertiary_fixed: Color::from_rgb8(0xFF, 0xD8, 0xE4),
76 tertiary_fixed_dim: Color::from_rgb8(0xEF, 0xB8, 0xC8),
77 on_tertiary_fixed: Color::from_rgb8(0x31, 0x11, 0x1D),
78 on_tertiary_fixed_variant: Color::from_rgb8(0x63, 0x3B, 0x48),
79
80 error: Color::from_rgb8(0xB3, 0x26, 0x1E),
81 on_error: Color::from_rgb8(0xFF, 0xFF, 0xFF),
82 error_container: Color::from_rgb8(0xF9, 0xDE, 0xDC),
83 on_error_container: Color::from_rgb8(0x41, 0x0E, 0x0B),
84
85 surface: Color::from_rgb8(0xFE, 0xF7, 0xFF),
86 on_surface: Color::from_rgb8(0x1D, 0x1B, 0x20),
87 on_surface_variant: Color::from_rgb8(0x49, 0x45, 0x4F),
88 surface_dim: Color::from_rgb8(0xDE, 0xD8, 0xE1),
89 surface_bright: Color::from_rgb8(0xFE, 0xF7, 0xFF),
90 surface_container_lowest: Color::from_rgb8(0xFF, 0xFF, 0xFF),
91 surface_container_low: Color::from_rgb8(0xF7, 0xF2, 0xFA),
92 surface_container: Color::from_rgb8(0xF3, 0xED, 0xF7),
93 surface_container_high: Color::from_rgb8(0xEC, 0xE6, 0xF0),
94 surface_container_highest: Color::from_rgb8(0xE6, 0xE0, 0xE9),
95
96 outline: Color::from_rgb8(0x79, 0x74, 0x7E),
97 outline_variant: Color::from_rgb8(0xCA, 0xC4, 0xD0),
98 shadow: Color::from_rgb8(0x00, 0x00, 0x00),
99 scrim: Color::from_rgb8(0x00, 0x00, 0x00),
100 inverse_surface: Color::from_rgb8(0x32, 0x2F, 0x35),
101 inverse_on_surface: Color::from_rgb8(0xF5, 0xEF, 0xF7),
102 inverse_primary: Color::from_rgb8(0xD0, 0xBC, 0xFF),
103 surface_tint: Color::from_rgb8(0x67, 0x50, 0xA4),
104 }
105}
106
107pub fn color_scheme_dark() -> ColorScheme {
116 ColorScheme {
117 primary: Color::from_rgb8(0xD0, 0xBC, 0xFF),
118 on_primary: Color::from_rgb8(0x38, 0x1E, 0x72),
119 primary_container: Color::from_rgb8(0x4F, 0x37, 0x8B),
120 on_primary_container: Color::from_rgb8(0xEA, 0xDD, 0xFF),
121 primary_fixed: Color::from_rgb8(0xEA, 0xDD, 0xFF),
122 primary_fixed_dim: Color::from_rgb8(0xD0, 0xBC, 0xFF),
123 on_primary_fixed: Color::from_rgb8(0x21, 0x00, 0x5D),
124 on_primary_fixed_variant: Color::from_rgb8(0x4F, 0x37, 0x8B),
125
126 secondary: Color::from_rgb8(0xCC, 0xC2, 0xDC),
127 on_secondary: Color::from_rgb8(0x33, 0x2D, 0x41),
128 secondary_container: Color::from_rgb8(0x4A, 0x44, 0x58),
129 on_secondary_container: Color::from_rgb8(0xE8, 0xDE, 0xF8),
130 secondary_fixed: Color::from_rgb8(0xE8, 0xDE, 0xF8),
131 secondary_fixed_dim: Color::from_rgb8(0xCC, 0xC2, 0xDC),
132 on_secondary_fixed: Color::from_rgb8(0x1D, 0x19, 0x2B),
133 on_secondary_fixed_variant: Color::from_rgb8(0x4A, 0x44, 0x58),
134
135 tertiary: Color::from_rgb8(0xEF, 0xB8, 0xC8),
136 on_tertiary: Color::from_rgb8(0x49, 0x25, 0x32),
137 tertiary_container: Color::from_rgb8(0x63, 0x3B, 0x48),
138 on_tertiary_container: Color::from_rgb8(0xFF, 0xD8, 0xE4),
139 tertiary_fixed: Color::from_rgb8(0xFF, 0xD8, 0xE4),
140 tertiary_fixed_dim: Color::from_rgb8(0xEF, 0xB8, 0xC8),
141 on_tertiary_fixed: Color::from_rgb8(0x31, 0x11, 0x1D),
142 on_tertiary_fixed_variant: Color::from_rgb8(0x63, 0x3B, 0x48),
143
144 error: Color::from_rgb8(0xF2, 0xB8, 0xB5),
145 on_error: Color::from_rgb8(0x60, 0x14, 0x10),
146 error_container: Color::from_rgb8(0x8C, 0x1D, 0x18),
147 on_error_container: Color::from_rgb8(0xF9, 0xDE, 0xDC),
148
149 surface: Color::from_rgb8(0x14, 0x12, 0x18),
150 on_surface: Color::from_rgb8(0xE6, 0xE0, 0xE9),
151 on_surface_variant: Color::from_rgb8(0xCA, 0xC4, 0xD0),
152 surface_dim: Color::from_rgb8(0x14, 0x12, 0x18),
153 surface_bright: Color::from_rgb8(0x3B, 0x38, 0x3E),
154 surface_container_lowest: Color::from_rgb8(0x0F, 0x0D, 0x13),
155 surface_container_low: Color::from_rgb8(0x1D, 0x1B, 0x20),
156 surface_container: Color::from_rgb8(0x21, 0x1F, 0x26),
157 surface_container_high: Color::from_rgb8(0x2B, 0x29, 0x30),
158 surface_container_highest: Color::from_rgb8(0x36, 0x34, 0x3B),
159
160 outline: Color::from_rgb8(0x93, 0x8F, 0x99),
161 outline_variant: Color::from_rgb8(0x49, 0x45, 0x4F),
162 shadow: Color::from_rgb8(0x00, 0x00, 0x00),
163 scrim: Color::from_rgb8(0x00, 0x00, 0x00),
164 inverse_surface: Color::from_rgb8(0xE6, 0xE0, 0xE9),
165 inverse_on_surface: Color::from_rgb8(0x32, 0x2F, 0x35),
166 inverse_primary: Color::from_rgb8(0x67, 0x50, 0xA4),
167 surface_tint: Color::from_rgb8(0xD0, 0xBC, 0xFF),
168 }
169}
170
171#[derive(Clone, Copy, Debug, PartialEq)]
175pub struct MaterialSemanticColors {
176 pub emphasis: Color,
178 pub on_emphasis: Color,
180 pub info: Color,
186 pub success: Color,
191 pub warning: Color,
195 pub danger: Color,
197 pub surface_strong: Color,
200 pub on_surface_strong: Color,
203 pub outline_strong: Color,
206}
207
208pub fn semantic_light() -> MaterialSemanticColors {
211 let scheme = color_scheme_light();
212 MaterialSemanticColors {
213 emphasis: scheme.primary,
214 on_emphasis: scheme.on_primary,
215 info: scheme.tertiary,
216 success: Color::from_rgb8(0x2E, 0x7D, 0x32),
218 warning: Color::from_rgb8(0xEF, 0x6C, 0x00),
220 danger: scheme.error,
221 surface_strong: Color::from_rgb8(0xF5, 0xED, 0xFA),
224 on_surface_strong: scheme.on_surface,
225 outline_strong: scheme.outline,
226 }
227}
228
229pub fn semantic_dark() -> MaterialSemanticColors {
232 let scheme = color_scheme_dark();
233 MaterialSemanticColors {
234 emphasis: scheme.primary,
235 on_emphasis: scheme.on_primary,
236 info: scheme.tertiary,
237 success: Color::from_rgb8(0x81, 0xC7, 0x84),
239 warning: Color::from_rgb8(0xFF, 0xB7, 0x4D),
241 danger: scheme.error,
242 surface_strong: Color::from_rgb8(0x1F, 0x1C, 0x26),
243 on_surface_strong: scheme.on_surface,
244 outline_strong: scheme.outline,
245 }
246}
247
248#[cfg(test)]
249mod tests {
250 use super::*;
251
252 #[test]
258 fn all_43_m3e_roles_are_reachable() {
259 let scheme = color_scheme_light();
260 let baseline_roles: [Color; 34] = [
261 scheme.primary,
262 scheme.on_primary,
263 scheme.primary_container,
264 scheme.on_primary_container,
265 scheme.secondary,
266 scheme.on_secondary,
267 scheme.secondary_container,
268 scheme.on_secondary_container,
269 scheme.tertiary,
270 scheme.on_tertiary,
271 scheme.tertiary_container,
272 scheme.on_tertiary_container,
273 scheme.error,
274 scheme.on_error,
275 scheme.error_container,
276 scheme.on_error_container,
277 scheme.surface,
278 scheme.on_surface,
279 scheme.on_surface_variant,
280 scheme.surface_container_lowest,
281 scheme.surface_container_low,
282 scheme.surface_container,
283 scheme.surface_container_high,
284 scheme.surface_container_highest,
285 scheme.surface_dim,
286 scheme.surface_bright,
287 scheme.inverse_surface,
288 scheme.inverse_on_surface,
289 scheme.inverse_primary,
290 scheme.outline,
291 scheme.outline_variant,
292 scheme.shadow,
293 scheme.scrim,
294 scheme.surface_tint,
295 ];
296 let semantic = semantic_light();
297 let semantic_roles: [Color; 9] = [
298 semantic.emphasis,
299 semantic.on_emphasis,
300 semantic.info,
301 semantic.success,
302 semantic.warning,
303 semantic.danger,
304 semantic.surface_strong,
305 semantic.on_surface_strong,
306 semantic.outline_strong,
307 ];
308 assert_eq!(baseline_roles.len() + semantic_roles.len(), 43);
309 }
310
311 #[test]
312 fn semantic_light_and_dark_are_distinct() {
313 assert_ne!(semantic_light(), semantic_dark());
314 assert_ne!(semantic_light().emphasis, semantic_dark().emphasis);
315 assert_ne!(
316 semantic_light().surface_strong,
317 semantic_dark().surface_strong
318 );
319 }
320
321 #[test]
322 fn semantic_derived_roles_mirror_the_baseline_scheme() {
323 let scheme = color_scheme_light();
324 let semantic = semantic_light();
325 assert_eq!(semantic.emphasis, scheme.primary);
326 assert_eq!(semantic.on_emphasis, scheme.on_primary);
327 assert_eq!(semantic.info, scheme.tertiary);
328 assert_eq!(semantic.danger, scheme.error);
329 assert_eq!(semantic.on_surface_strong, scheme.on_surface);
330 assert_eq!(semantic.outline_strong, scheme.outline);
331
332 let dark_scheme = color_scheme_dark();
333 let dark_semantic = semantic_dark();
334 assert_eq!(dark_semantic.emphasis, dark_scheme.primary);
335 assert_eq!(dark_semantic.on_emphasis, dark_scheme.on_primary);
336 assert_eq!(dark_semantic.info, dark_scheme.tertiary);
337 assert_eq!(dark_semantic.danger, dark_scheme.error);
338 assert_eq!(dark_semantic.on_surface_strong, dark_scheme.on_surface);
339 assert_eq!(dark_semantic.outline_strong, dark_scheme.outline);
340 }
341
342 #[test]
343 fn semantic_independent_constants_match_the_reference() {
344 assert_eq!(semantic_light().success, Color::from_rgb8(0x2E, 0x7D, 0x32));
345 assert_eq!(semantic_light().warning, Color::from_rgb8(0xEF, 0x6C, 0x00));
346 assert_eq!(semantic_dark().success, Color::from_rgb8(0x81, 0xC7, 0x84));
347 assert_eq!(semantic_dark().warning, Color::from_rgb8(0xFF, 0xB7, 0x4D));
348 }
349
350 #[test]
351 fn surface_strong_matches_the_alpha_blend_formula() {
352 fn blend(accent: Color, surface: Color, alpha: f64) -> Color {
358 let [ar, ag, ab, _] = accent.to_rgba8().to_u8_array();
359 let [sr, sg, sb, _] = surface.to_rgba8().to_u8_array();
360 let mix = |a: u8, s: u8| -> u8 {
361 ((a as f64 / 255.0 * alpha + s as f64 / 255.0 * (1.0 - alpha)) * 255.0).round()
362 as u8
363 };
364 Color::from_rgb8(mix(ar, sr), mix(ag, sg), mix(ab, sb))
365 }
366
367 let light = color_scheme_light();
368 assert_eq!(
369 semantic_light().surface_strong,
370 blend(light.primary, light.surface, 0.06)
371 );
372 let dark = color_scheme_dark();
373 assert_eq!(
374 semantic_dark().surface_strong,
375 blend(dark.primary, dark.surface, 0.06)
376 );
377 }
378}