1use crate::Rgba;
5
6fn get_color(ini: &configparser::ini::Ini, section: &str, key: &str) -> Option<Rgba> {
8 let value = ini.get(section, key)?;
9 super::parse_rgb(&value)
10}
11
12const BLEND_VALUE: f32 = 0.3;
16
17const FRAME_CONTRAST_DEFAULT: f32 = 0.2;
20
21fn translucent(color: Rgba, alpha: f32) -> Rgba {
23 let [r, g, b, _] = color.to_f32_array();
24 Rgba::from_f32(r, g, b, alpha)
25}
26
27fn over(top: Rgba, bottom: Rgba) -> Rgba {
29 let [tr, tg, tb, alpha] = top.to_f32_array();
30 let [br, bg, bb, _] = bottom.to_f32_array();
31 let channel = |t: f32, b: f32| t * alpha + b * (1.0 - alpha);
32 Rgba::from_f32(channel(tr, br), channel(tg, bg), channel(tb, bb), 1.0)
33}
34
35fn mix(a: Rgba, b: Rgba, bias: f32) -> Rgba {
38 let [ar, ag, ab, _] = a.to_f32_array();
39 let [br, bg, bb, _] = b.to_f32_array();
40 let channel = |from: f32, to: f32| from + (to - from) * bias;
41 Rgba::from_f32(channel(ar, br), channel(ag, bg), channel(ab, bb), 1.0)
42}
43
44const CHECKED_BUTTON_MIX: f32 = 0.125;
49
50const PROGRESS_FILL_ALPHA: f32 = 0.7;
54
55fn byte_mul(x: u64, a: u64) -> u64 {
58 let t = x.saturating_mul(a);
59 t.saturating_add(t.wrapping_shr(8))
60 .saturating_add(0x80)
61 .wrapping_shr(8)
62}
63
64fn mul_65535(x: u64, a: u64) -> u64 {
66 let t = x.saturating_mul(a);
67 t.saturating_add(t.wrapping_shr(16))
68 .saturating_add(0x8000)
69 .wrapping_shr(16)
70}
71
72fn div_257(x: u64) -> u64 {
74 let t = x.saturating_add(0x80);
75 t.saturating_sub(t.wrapping_shr(8)).wrapping_shr(8)
76}
77
78fn overlay_colors(base: Rgba, paint: Rgba, alpha: f32) -> Rgba {
86 let alpha16 = (alpha.clamp(0.0, 1.0) * 65535.0 + 0.5).floor() as u64;
88 let alpha8 = div_257(alpha16);
89 let inverse = 255_u64.saturating_sub(alpha8);
90 let channel = |p: u8, b: u8| {
91 let premultiplied = div_257(mul_65535(u64::from(p).saturating_mul(257), alpha16));
92 let v = premultiplied.saturating_add(byte_mul(u64::from(b), inverse));
93 u8::try_from(v).unwrap_or(u8::MAX)
94 };
95 Rgba::rgb(
96 channel(paint.r, base.r),
97 channel(paint.g, base.g),
98 channel(paint.b, base.b),
99 )
100}
101
102fn frame_contrast(ini: &configparser::ini::Ini) -> f32 {
105 ini.get("KDE", "frameContrast")
106 .and_then(|v| v.trim().parse::<f32>().ok())
107 .filter(|v| v.is_finite())
108 .map_or(FRAME_CONTRAST_DEFAULT, |v| v.clamp(0.0, 1.0))
109}
110
111pub(crate) fn populate_colors(ini: &configparser::ini::Ini, variant: &mut crate::ThemeMode) {
117 let window_fg = get_color(ini, "Colors:Window", "ForegroundNormal");
118
119 variant.defaults.accent_color = get_color(ini, "Colors:View", "DecorationFocus");
121 variant.defaults.background_color = get_color(ini, "Colors:Window", "BackgroundNormal");
122 variant.defaults.text_color = window_fg;
123 variant.defaults.surface_color = get_color(ini, "Colors:View", "BackgroundNormal");
124 variant.defaults.muted_color = get_color(ini, "Colors:Window", "ForegroundInactive");
127 variant.defaults.link_color = get_color(ini, "Colors:View", "ForegroundLink");
129 variant.defaults.focus_ring_color = get_color(ini, "Colors:View", "DecorationFocus");
130
131 variant.defaults.selection_background = get_color(ini, "Colors:Selection", "BackgroundNormal");
133 variant.defaults.selection_text_color = get_color(ini, "Colors:Selection", "ForegroundNormal");
134 variant.defaults.accent_text_color = get_color(ini, "Colors:Selection", "ForegroundNormal");
136
137 variant.defaults.danger_color = get_color(ini, "Colors:View", "ForegroundNegative");
139 variant.defaults.danger_text_color = window_fg;
140 variant.defaults.warning_color = get_color(ini, "Colors:View", "ForegroundNeutral");
141 variant.defaults.warning_text_color = window_fg;
142 variant.defaults.success_color = get_color(ini, "Colors:View", "ForegroundPositive");
143 variant.defaults.success_text_color = window_fg;
144 variant.defaults.info_color = get_color(ini, "Colors:View", "ForegroundActive");
145 variant.defaults.info_text_color = window_fg;
146
147 variant.defaults.disabled_text_color = get_color(ini, "Colors:View", "ForegroundInactive");
149
150 variant.button.background_color = get_color(ini, "Colors:Button", "BackgroundNormal");
154 if let Some(color) = get_color(ini, "Colors:Button", "ForegroundNormal") {
155 variant.button.font.get_or_insert_default().color = Some(color);
156 }
157 let button_fg = get_color(ini, "Colors:Button", "ForegroundNormal");
160 variant.button.checked_background = variant
161 .button
162 .background_color
163 .zip(button_fg)
164 .map(|(button, text)| mix(button, text, CHECKED_BUTTON_MIX));
165 variant.button.checked_text_color = button_fg;
166
167 variant.tooltip.background_color = get_color(ini, "Colors:Tooltip", "BackgroundNormal");
169 if let Some(color) = get_color(ini, "Colors:Tooltip", "ForegroundNormal") {
170 variant.tooltip.font.get_or_insert_default().color = Some(color);
171 }
172
173 variant.sidebar.background_color = get_color(ini, "Colors:Complementary", "BackgroundNormal");
175 if let Some(color) = get_color(ini, "Colors:Complementary", "ForegroundNormal") {
176 variant.sidebar.font.get_or_insert_default().color = Some(color);
177 }
178
179 variant.input.background_color = get_color(ini, "Colors:View", "BackgroundNormal");
181 if let Some(color) = get_color(ini, "Colors:View", "ForegroundNormal") {
182 variant.input.font.get_or_insert_default().color = Some(color);
183 }
184 variant.input.placeholder_color = get_color(ini, "Colors:View", "ForegroundInactive");
186 variant.input.caret_color = get_color(ini, "Colors:View", "DecorationFocus");
188
189 let button_bg = get_color(ini, "Colors:Button", "BackgroundNormal");
195 let selection_bg = get_color(ini, "Colors:Selection", "BackgroundNormal");
196 variant.checkbox.unchecked_background = button_bg;
197 variant.checkbox.checked_background = button_bg
198 .zip(selection_bg)
199 .map(|(button, selection)| over(translucent(selection, BLEND_VALUE), button));
200 variant.checkbox.indicator_color = get_color(ini, "Colors:View", "ForegroundNormal");
201 if let Some(color) = selection_bg {
202 variant.checkbox.border.get_or_insert_default().color = Some(color);
203 }
204 variant.checkbox.unchecked_border_color = get_color(ini, "Colors:Window", "BackgroundNormal")
205 .zip(window_fg)
206 .map(|(window, text)| mix(window, text, frame_contrast(ini)));
207
208 variant.tab.active_indicator_color = selection_bg;
211
212 let window_bg = get_color(ini, "Colors:Window", "BackgroundNormal");
217 variant.progress_bar.fill_color = selection_bg
218 .zip(window_bg)
219 .map(|(accent, window)| overlay_colors(window, accent, PROGRESS_FILL_ALPHA));
220 if let Some(text) = window_fg {
221 variant.progress_bar.border.get_or_insert_default().color =
222 Some(translucent(text, frame_contrast(ini)));
223 }
224
225 variant.popover.background_color = get_color(ini, "Colors:View", "BackgroundNormal");
227 if let Some(color) = get_color(ini, "Colors:View", "ForegroundNormal") {
228 variant.popover.font.get_or_insert_default().color = Some(color);
229 }
230
231 variant.separator.line_color = get_color(ini, "Colors:Window", "ForegroundInactive");
233
234 variant.list.background_color = get_color(ini, "Colors:View", "BackgroundNormal");
236 if let Some(color) = get_color(ini, "Colors:View", "ForegroundNormal") {
237 variant.list.item_font.get_or_insert_default().color = Some(color);
238 }
239 variant.list.alternate_row_background = get_color(ini, "Colors:View", "BackgroundAlternate");
240 variant.list.header_background = get_color(ini, "Colors:Header", "BackgroundNormal");
241 if let Some(color) = get_color(ini, "Colors:Header", "ForegroundNormal") {
242 variant.list.header_font.get_or_insert_default().color = Some(color);
243 }
244
245 variant.link.visited_text_color = get_color(ini, "Colors:View", "ForegroundVisited");
247 if let Some(color) = get_color(ini, "Colors:View", "ForegroundLink") {
249 variant.link.font.get_or_insert_default().color = Some(color);
250 }
251
252 variant.window.title_bar_background = get_color(ini, "WM", "activeBackground");
254 if let Some(color) = get_color(ini, "WM", "activeForeground") {
255 variant.window.title_bar_font.get_or_insert_default().color = Some(color);
256 }
257 variant.window.inactive_title_bar_background = get_color(ini, "WM", "inactiveBackground");
258 variant.window.inactive_title_bar_text_color = get_color(ini, "WM", "inactiveForeground");
259}
260
261#[cfg(test)]
262#[allow(clippy::unwrap_used, clippy::expect_used)]
263mod tests {
264 use super::*;
265 use crate::ThemeMode;
266
267 const BREEZE_DARK: &str = "\
269[Colors:View]
270BackgroundNormal=35,38,41
271BackgroundAlternate=30,33,36
272ForegroundNormal=252,252,252
273ForegroundInactive=161,169,177
274ForegroundActive=61,174,233
275ForegroundLink=29,153,243
276ForegroundNegative=218,68,83
277ForegroundNeutral=246,116,0
278ForegroundPositive=39,174,96
279ForegroundVisited=155,89,182
280DecorationFocus=61,174,233
281DecorationHover=29,153,243
282
283[Colors:Window]
284BackgroundNormal=49,54,59
285BackgroundAlternate=44,49,54
286ForegroundNormal=239,240,241
287ForegroundInactive=161,169,177
288ForegroundActive=61,174,233
289ForegroundLink=29,153,243
290ForegroundNegative=218,68,83
291ForegroundNeutral=246,116,0
292ForegroundPositive=39,174,96
293DecorationFocus=61,174,233
294DecorationHover=29,153,243
295
296[Colors:Button]
297BackgroundNormal=49,54,59
298BackgroundAlternate=44,49,54
299ForegroundNormal=239,240,241
300ForegroundInactive=161,169,177
301
302[Colors:Selection]
303BackgroundNormal=61,174,233
304BackgroundAlternate=29,153,243
305ForegroundNormal=252,252,252
306ForegroundInactive=161,169,177
307
308[Colors:Tooltip]
309BackgroundNormal=49,54,59
310ForegroundNormal=252,252,252
311
312[Colors:Complementary]
313BackgroundNormal=42,46,50
314ForegroundNormal=239,240,241
315
316[Colors:Header]
317BackgroundNormal=35,38,41
318ForegroundNormal=252,252,252
319
320[WM]
321activeBackground=49,54,59
322activeForeground=239,240,241
323inactiveBackground=42,46,50
324inactiveForeground=161,169,177
325";
326
327 fn populate_fixture(content: &str) -> ThemeMode {
328 let mut ini = super::super::create_kde_parser();
329 ini.read(content.to_string()).unwrap();
330 let mut variant = ThemeMode::default();
331 populate_colors(&ini, &mut variant);
332 variant
333 }
334
335 #[test]
338 fn test_accent_from_view_decoration_focus() {
339 let v = populate_fixture(BREEZE_DARK);
340 assert_eq!(v.defaults.accent_color, Some(Rgba::rgb(61, 174, 233)));
341 }
342
343 #[test]
344 fn test_background_from_window() {
345 let v = populate_fixture(BREEZE_DARK);
346 assert_eq!(v.defaults.background_color, Some(Rgba::rgb(49, 54, 59)));
347 }
348
349 #[test]
350 fn test_text_color_from_window() {
351 let v = populate_fixture(BREEZE_DARK);
352 assert_eq!(v.defaults.text_color, Some(Rgba::rgb(239, 240, 241)));
353 }
354
355 #[test]
356 fn test_surface_from_view() {
357 let v = populate_fixture(BREEZE_DARK);
358 assert_eq!(v.defaults.surface_color, Some(Rgba::rgb(35, 38, 41)));
359 }
360
361 #[test]
362 fn test_border_not_set_by_reader() {
363 let v = populate_fixture(BREEZE_DARK);
365 assert!(v.defaults.border.is_empty());
366 }
367
368 #[test]
369 fn test_muted_from_window_foreground_inactive() {
370 let v = populate_fixture(BREEZE_DARK);
371 assert_eq!(v.defaults.muted_color, Some(Rgba::rgb(161, 169, 177)));
372 }
373
374 #[test]
377 fn test_status_colors() {
378 let v = populate_fixture(BREEZE_DARK);
379 assert_eq!(v.defaults.danger_color, Some(Rgba::rgb(218, 68, 83)));
380 assert_eq!(v.defaults.warning_color, Some(Rgba::rgb(246, 116, 0)));
381 assert_eq!(v.defaults.success_color, Some(Rgba::rgb(39, 174, 96)));
382 assert_eq!(v.defaults.info_color, Some(Rgba::rgb(61, 174, 233)));
383 }
384
385 #[test]
388 fn test_selection_colors() {
389 let v = populate_fixture(BREEZE_DARK);
390 assert_eq!(
391 v.defaults.selection_background,
392 Some(Rgba::rgb(61, 174, 233))
393 );
394 assert_eq!(
395 v.defaults.selection_text_color,
396 Some(Rgba::rgb(252, 252, 252))
397 );
398 }
399
400 fn breeze_checkbox_groups(
405 window: &str,
406 window_fg: &str,
407 button: &str,
408 view_fg: &str,
409 ) -> String {
410 format!(
411 "[Colors:Window]\nBackgroundNormal={window}\nForegroundNormal={window_fg}\n\n\
412 [Colors:Button]\nBackgroundNormal={button}\n\n\
413 [Colors:View]\nForegroundNormal={view_fg}\n\n\
414 [Colors:Selection]\nBackgroundNormal=61,174,233\n"
415 )
416 }
417
418 #[test]
420 fn test_checkbox_colors_are_breezes() {
421 let cases = [
422 (
423 breeze_checkbox_groups("239,240,241", "35,38,41", "252,252,252", "35,38,41"),
424 [
425 (194, 228, 246),
426 (252, 252, 252),
427 (35, 38, 41),
428 (198, 200, 201),
429 ],
430 ),
431 (
432 breeze_checkbox_groups("32,35,38", "252,252,252", "41,44,48", "252,252,252"),
433 [(47, 83, 104), (41, 44, 48), (252, 252, 252), (76, 78, 81)],
434 ),
435 ];
436 for (content, [checked, unchecked, mark, unchecked_border]) in cases {
437 let v = populate_fixture(&content);
438 let rgb = |(r, g, b): (u8, u8, u8)| Some(Rgba::rgb(r, g, b));
439 assert_eq!(v.checkbox.checked_background, rgb(checked));
440 assert_eq!(v.checkbox.unchecked_background, rgb(unchecked));
441 assert_eq!(v.checkbox.indicator_color, rgb(mark));
442 assert_eq!(v.checkbox.unchecked_border_color, rgb(unchecked_border));
443 assert_eq!(
444 v.checkbox.border.as_ref().and_then(|b| b.color),
445 rgb((61, 174, 233))
446 );
447 }
448 }
449
450 #[test]
451 fn test_checkbox_unchecked_border_follows_frame_contrast() {
452 let content = breeze_checkbox_groups("0,0,0", "200,100,0", "0,0,0", "0,0,0");
453 let with = |contrast: &str| {
454 populate_fixture(&format!("{content}\n[KDE]\nframeContrast={contrast}\n"))
455 .checkbox
456 .unchecked_border_color
457 };
458 assert_eq!(with("0.5"), Some(Rgba::rgb(100, 50, 0)));
459 assert_eq!(with("2"), Some(Rgba::rgb(200, 100, 0)), "clamped to 1");
460 assert_eq!(with("none"), Some(Rgba::rgb(40, 20, 0)), "the 0.2 default");
461 }
462
463 #[test]
468 fn overlay_colors_is_qts_pixel() {
469 let cases = [
470 ((220, 4, 101), (170, 31, 173), 0.29, (205, 12, 122)),
471 ((90, 218, 229), (172, 27, 30), 0.95, (168, 37, 40)),
472 ((209, 238, 57), (16, 203, 72), 0.48, (117, 221, 65)),
473 ((116, 55, 154), (111, 144, 12), 0.19, (115, 72, 127)),
474 ((164, 249, 31), (161, 98, 185), 0.75, (162, 135, 147)),
475 ((148, 102, 144), (95, 160, 85), 0.04, (146, 104, 141)),
476 ];
477 for (base, paint, alpha, expected) in cases {
478 let rgb = |(r, g, b): (u8, u8, u8)| Rgba::rgb(r, g, b);
479 assert_eq!(
480 overlay_colors(rgb(base), rgb(paint), alpha),
481 rgb(expected),
482 "{base:?} under {paint:?} at {alpha}"
483 );
484 }
485 }
486
487 #[test]
491 fn test_progress_bar_colors_are_breezes() {
492 let cases = [
493 (
494 breeze_checkbox_groups("239,240,241", "35,38,41", "252,252,252", "35,38,41"),
495 Rgba::rgb(0x72, 0xc2, 0xeb),
496 Rgba::new(0x23, 0x26, 0x29, 0x33),
497 ),
498 (
499 breeze_checkbox_groups("32,35,38", "252,252,252", "41,44,48", "252,252,252"),
500 Rgba::rgb(0x35, 0x84, 0xae),
501 Rgba::new(0xfc, 0xfc, 0xfc, 0x33),
502 ),
503 ];
504 for (content, fill, outline) in cases {
505 let v = populate_fixture(&content);
506 assert_eq!(v.progress_bar.fill_color, Some(fill));
507 assert_eq!(
508 v.progress_bar.border.as_ref().and_then(|b| b.color),
509 Some(outline)
510 );
511 }
512 }
513
514 #[test]
518 fn test_checked_button_colors_are_breezes() {
519 let cases = [
520 ("252,252,252", "35,38,41", Rgba::rgb(0xe1, 0xe1, 0xe2)),
521 ("41,44,48", "252,252,252", Rgba::rgb(0x43, 0x46, 0x4a)),
522 ];
523 for (button, text, checked) in cases {
524 let v = populate_fixture(&format!(
525 "[Colors:Button]\nBackgroundNormal={button}\nForegroundNormal={text}\n"
526 ));
527 assert_eq!(v.button.checked_background, Some(checked), "{button}");
528 assert_eq!(
529 v.button.checked_text_color,
530 v.button.font.as_ref().and_then(|f| f.color)
531 );
532 }
533 }
534
535 #[test]
538 fn test_button_background_from_colors_button() {
539 let v = populate_fixture(BREEZE_DARK);
540 assert_eq!(v.button.background_color, Some(Rgba::rgb(49, 54, 59)));
541 }
542
543 #[test]
544 fn test_button_font_color_from_colors_button() {
545 let v = populate_fixture(BREEZE_DARK);
546 assert_eq!(
547 v.button.font.as_ref().and_then(|f| f.color),
548 Some(Rgba::rgb(239, 240, 241))
549 );
550 }
551
552 #[test]
555 fn test_tooltip_background_from_colors_tooltip() {
556 let v = populate_fixture(BREEZE_DARK);
557 assert_eq!(v.tooltip.background_color, Some(Rgba::rgb(49, 54, 59)));
558 }
559
560 #[test]
561 fn test_tooltip_font_color_from_colors_tooltip() {
562 let v = populate_fixture(BREEZE_DARK);
563 assert_eq!(
564 v.tooltip.font.as_ref().and_then(|f| f.color),
565 Some(Rgba::rgb(252, 252, 252))
566 );
567 }
568
569 #[test]
572 fn test_sidebar_background_from_complementary() {
573 let v = populate_fixture(BREEZE_DARK);
574 assert_eq!(v.sidebar.background_color, Some(Rgba::rgb(42, 46, 50)));
575 }
576
577 #[test]
578 fn test_sidebar_font_color_from_complementary() {
579 let v = populate_fixture(BREEZE_DARK);
580 assert_eq!(
581 v.sidebar.font.as_ref().and_then(|f| f.color),
582 Some(Rgba::rgb(239, 240, 241))
583 );
584 }
585
586 #[test]
589 fn test_title_bar_background_from_wm_active() {
590 let v = populate_fixture(BREEZE_DARK);
591 assert_eq!(v.window.title_bar_background, Some(Rgba::rgb(49, 54, 59)));
592 }
593
594 #[test]
595 fn test_title_bar_font_color_from_wm_active() {
596 let v = populate_fixture(BREEZE_DARK);
597 assert_eq!(
598 v.window.title_bar_font.as_ref().and_then(|f| f.color),
599 Some(Rgba::rgb(239, 240, 241))
600 );
601 }
602
603 #[test]
604 fn test_inactive_title_bar_background_from_wm() {
605 let v = populate_fixture(BREEZE_DARK);
606 assert_eq!(
607 v.window.inactive_title_bar_background,
608 Some(Rgba::rgb(42, 46, 50))
609 );
610 }
611
612 #[test]
613 fn test_inactive_title_bar_text_color_from_wm() {
614 let v = populate_fixture(BREEZE_DARK);
615 assert_eq!(
616 v.window.inactive_title_bar_text_color,
617 Some(Rgba::rgb(161, 169, 177))
618 );
619 }
620
621 #[test]
624 fn test_input_placeholder_from_view_foreground_inactive() {
625 let v = populate_fixture(BREEZE_DARK);
626 assert_eq!(v.input.placeholder_color, Some(Rgba::rgb(161, 169, 177)));
627 }
628
629 #[test]
630 fn test_input_caret_from_view_decoration_focus() {
631 let v = populate_fixture(BREEZE_DARK);
632 assert_eq!(v.input.caret_color, Some(Rgba::rgb(61, 174, 233)));
633 }
634
635 #[test]
638 fn test_accent_text_color_from_selection() {
639 let v = populate_fixture(BREEZE_DARK);
640 assert_eq!(v.defaults.accent_text_color, Some(Rgba::rgb(252, 252, 252)));
641 }
642
643 #[test]
646 fn test_list_background_from_view() {
647 let v = populate_fixture(BREEZE_DARK);
648 assert_eq!(v.list.background_color, Some(Rgba::rgb(35, 38, 41)));
649 }
650
651 #[test]
652 fn test_list_item_font_color_from_view() {
653 let v = populate_fixture(BREEZE_DARK);
654 assert_eq!(
655 v.list.item_font.as_ref().and_then(|f| f.color),
656 Some(Rgba::rgb(252, 252, 252))
657 );
658 }
659
660 #[test]
663 fn test_list_alternate_row_from_view() {
664 let v = populate_fixture(BREEZE_DARK);
665 assert_eq!(v.list.alternate_row_background, Some(Rgba::rgb(30, 33, 36)));
666 }
667
668 #[test]
671 fn test_list_header_background_from_header() {
672 let v = populate_fixture(BREEZE_DARK);
673 assert_eq!(v.list.header_background, Some(Rgba::rgb(35, 38, 41)));
674 }
675
676 #[test]
677 fn test_list_header_font_color_from_header() {
678 let v = populate_fixture(BREEZE_DARK);
679 assert_eq!(
680 v.list.header_font.as_ref().and_then(|f| f.color),
681 Some(Rgba::rgb(252, 252, 252))
682 );
683 }
684
685 #[test]
688 fn test_link_visited_from_view() {
689 let v = populate_fixture(BREEZE_DARK);
690 assert_eq!(v.link.visited_text_color, Some(Rgba::rgb(155, 89, 182)));
691 }
692
693 #[test]
696 fn test_sidebar_none_without_complementary() {
697 let content = "\
698[Colors:View]
699BackgroundNormal=35,38,41
700ForegroundNormal=252,252,252
701";
702 let v = populate_fixture(content);
703 assert!(v.sidebar.background_color.is_none());
704 assert!(v.sidebar.font.is_none());
705 }
706
707 #[test]
710 fn test_empty_ini() {
711 let ini = super::super::create_kde_parser();
712 let mut variant = ThemeMode::default();
713 populate_colors(&ini, &mut variant);
714 assert!(variant.defaults.accent_color.is_none());
715 assert!(variant.defaults.background_color.is_none());
716 assert!(variant.defaults.text_color.is_none());
717 assert!(variant.button.background_color.is_none());
718 }
719
720 #[test]
723 fn test_partial_sections() {
724 let content = "\
725[Colors:Window]
726BackgroundNormal=49,54,59
727ForegroundNormal=239,240,241
728ForegroundInactive=161,169,177
729DecorationFocus=61,174,233
730";
731 let v = populate_fixture(content);
732 assert_eq!(v.defaults.background_color, Some(Rgba::rgb(49, 54, 59)));
733 assert_eq!(v.defaults.text_color, Some(Rgba::rgb(239, 240, 241)));
734 assert_eq!(v.defaults.muted_color, Some(Rgba::rgb(161, 169, 177)));
735 assert!(v.defaults.border.is_empty()); assert!(v.defaults.accent_color.is_none());
737 assert!(v.defaults.surface_color.is_none());
738 assert!(v.button.background_color.is_none());
739 }
740
741 #[test]
744 fn test_malformed_values() {
745 let content = "\
746[Colors:View]
747BackgroundNormal=abc,def,ghi
748ForegroundNormal=252,252,252
749DecorationFocus=61,174,233
750";
751 let v = populate_fixture(content);
752 assert!(v.defaults.surface_color.is_none()); assert_eq!(v.defaults.accent_color, Some(Rgba::rgb(61, 174, 233)));
754 }
755
756 #[test]
759 fn test_full_mapping_completeness() {
760 let v = populate_fixture(BREEZE_DARK);
761
762 assert!(v.defaults.accent_color.is_some(), "accent_color missing");
764 assert!(
765 v.defaults.background_color.is_some(),
766 "background_color missing"
767 );
768 assert!(v.defaults.text_color.is_some(), "text_color missing");
769 assert!(v.defaults.surface_color.is_some(), "surface_color missing");
770 assert!(v.defaults.muted_color.is_some(), "muted_color missing");
772 assert!(v.defaults.link_color.is_some(), "link_color missing");
773 assert!(
774 v.defaults.selection_background.is_some(),
775 "selection_background missing"
776 );
777 assert!(
778 v.defaults.selection_text_color.is_some(),
779 "selection_text_color missing"
780 );
781 assert!(
782 v.defaults.accent_text_color.is_some(),
783 "accent_text_color missing"
784 );
785 assert!(v.defaults.danger_color.is_some(), "danger_color missing");
786 assert!(v.defaults.warning_color.is_some(), "warning_color missing");
787 assert!(v.defaults.success_color.is_some(), "success_color missing");
788 assert!(v.defaults.info_color.is_some(), "info_color missing");
789
790 assert!(
792 v.button.background_color.is_some(),
793 "button.background_color missing"
794 );
795 assert!(
796 v.button.font.as_ref().and_then(|f| f.color).is_some(),
797 "button.font.color missing"
798 );
799 assert!(
800 v.tooltip.background_color.is_some(),
801 "tooltip.background_color missing"
802 );
803 assert!(
804 v.tooltip.font.as_ref().and_then(|f| f.color).is_some(),
805 "tooltip.font.color missing"
806 );
807 assert!(
808 v.sidebar.background_color.is_some(),
809 "sidebar.background_color missing"
810 );
811 assert!(
812 v.sidebar.font.as_ref().and_then(|f| f.color).is_some(),
813 "sidebar.font.color missing"
814 );
815 assert!(
816 v.input.background_color.is_some(),
817 "input.background_color missing"
818 );
819 assert!(
820 v.input.font.as_ref().and_then(|f| f.color).is_some(),
821 "input.font.color missing"
822 );
823 assert!(
824 v.input.placeholder_color.is_some(),
825 "input.placeholder_color missing"
826 );
827 assert!(v.input.caret_color.is_some(), "input.caret_color missing");
828 assert!(
829 v.separator.line_color.is_some(),
830 "separator.line_color missing"
831 );
832 assert!(
833 v.list.background_color.is_some(),
834 "list.background_color missing"
835 );
836 assert!(
837 v.list.item_font.as_ref().and_then(|f| f.color).is_some(),
838 "list.item_font.color missing"
839 );
840 assert!(
841 v.list.alternate_row_background.is_some(),
842 "list.alternate_row_background missing"
843 );
844 assert!(
845 v.list.header_background.is_some(),
846 "list.header_background missing"
847 );
848 assert!(
849 v.list.header_font.as_ref().and_then(|f| f.color).is_some(),
850 "list.header_font.color missing"
851 );
852 assert!(
853 v.link.visited_text_color.is_some(),
854 "link.visited_text_color missing"
855 );
856
857 assert!(
859 v.window.title_bar_background.is_some(),
860 "window.title_bar_background missing"
861 );
862 assert!(
863 v.window
864 .title_bar_font
865 .as_ref()
866 .and_then(|f| f.color)
867 .is_some(),
868 "window.title_bar_font.color missing"
869 );
870 assert!(
871 v.window.inactive_title_bar_background.is_some(),
872 "window.inactive_title_bar_background missing"
873 );
874 assert!(
875 v.window.inactive_title_bar_text_color.is_some(),
876 "window.inactive_title_bar_text_color missing"
877 );
878 }
879}