1#![allow(clippy::module_name_repetitions)]
2
3use std::{fs::File, io::BufReader, num::ParseIntError, path::Path};
4
5use serde::{Deserialize, Serialize};
6use tuirealm::props::Color;
7
8use crate::config::yaml_theme::{
9 YAMLTheme, YAMLThemeBright, YAMLThemeCursor, YAMLThemeNormal, YAMLThemePrimary,
10};
11
12use styles::ColorTermusic;
13
14pub mod styles;
15
16#[derive(Debug, Clone, Deserialize, Serialize, Default, PartialEq)]
19#[serde(default)] pub struct ThemeWrap {
21 pub style: styles::Styles,
22 #[serde(default = "ThemeColors::full_default")]
25 pub theme: ThemeColors,
26}
27
28impl ThemeWrap {
29 #[must_use]
30 pub fn get_color_from_theme(&self, color: ColorTermusic) -> Color {
31 let val = match color {
33 ColorTermusic::Reset => return Color::Reset,
34 ColorTermusic::Foreground => &self.theme.primary.foreground,
35 ColorTermusic::Background => &self.theme.primary.background,
36 ColorTermusic::Black => &self.theme.normal.black,
37 ColorTermusic::Red => &self.theme.normal.red,
38 ColorTermusic::Green => &self.theme.normal.green,
39 ColorTermusic::Yellow => &self.theme.normal.yellow,
40 ColorTermusic::Blue => &self.theme.normal.blue,
41 ColorTermusic::Magenta => &self.theme.normal.magenta,
42 ColorTermusic::Cyan => &self.theme.normal.cyan,
43 ColorTermusic::White => &self.theme.normal.white,
44 ColorTermusic::LightBlack => &self.theme.bright.black,
45 ColorTermusic::LightRed => &self.theme.bright.red,
46 ColorTermusic::LightGreen => &self.theme.bright.green,
47 ColorTermusic::LightYellow => &self.theme.bright.yellow,
48 ColorTermusic::LightBlue => &self.theme.bright.blue,
49 ColorTermusic::LightMagenta => &self.theme.bright.magenta,
50 ColorTermusic::LightCyan => &self.theme.bright.cyan,
51 ColorTermusic::LightWhite => &self.theme.bright.white,
52 };
53
54 val.resolve_color(color)
56 }
57
58 #[inline]
59 #[must_use]
60 pub fn library_foreground(&self) -> Color {
61 self.get_color_from_theme(self.style.library.foreground_color)
62 }
63
64 #[inline]
65 #[must_use]
66 pub fn library_background(&self) -> Color {
67 self.get_color_from_theme(self.style.library.background_color)
68 }
69
70 #[inline]
71 #[must_use]
72 pub fn library_highlight(&self) -> Color {
73 self.get_color_from_theme(self.style.library.highlight_color)
74 }
75
76 #[inline]
77 #[must_use]
78 pub fn library_border(&self) -> Color {
79 self.get_color_from_theme(self.style.library.border_color)
80 }
81
82 #[inline]
83 #[must_use]
84 pub fn playlist_foreground(&self) -> Color {
85 self.get_color_from_theme(self.style.playlist.foreground_color)
86 }
87
88 #[inline]
89 #[must_use]
90 pub fn playlist_background(&self) -> Color {
91 self.get_color_from_theme(self.style.playlist.background_color)
92 }
93
94 #[inline]
95 #[must_use]
96 pub fn playlist_highlight(&self) -> Color {
97 self.get_color_from_theme(self.style.playlist.highlight_color)
98 }
99
100 #[inline]
101 #[must_use]
102 pub fn playlist_border(&self) -> Color {
103 self.get_color_from_theme(self.style.playlist.border_color)
104 }
105
106 #[inline]
107 #[must_use]
108 pub fn progress_foreground(&self) -> Color {
109 self.get_color_from_theme(self.style.progress.foreground_color)
110 }
111
112 #[inline]
113 #[must_use]
114 pub fn progress_background(&self) -> Color {
115 self.get_color_from_theme(self.style.progress.background_color)
116 }
117
118 #[inline]
119 #[must_use]
120 pub fn progress_border(&self) -> Color {
121 self.get_color_from_theme(self.style.progress.border_color)
122 }
123
124 #[inline]
125 #[must_use]
126 pub fn lyric_foreground(&self) -> Color {
127 self.get_color_from_theme(self.style.lyric.foreground_color)
128 }
129
130 #[inline]
131 #[must_use]
132 pub fn lyric_background(&self) -> Color {
133 self.get_color_from_theme(self.style.lyric.background_color)
134 }
135
136 #[inline]
137 #[must_use]
138 pub fn lyric_border(&self) -> Color {
139 self.get_color_from_theme(self.style.lyric.border_color)
140 }
141
142 #[inline]
143 #[must_use]
144 pub fn important_popup_foreground(&self) -> Color {
145 self.get_color_from_theme(self.style.important_popup.foreground_color)
146 }
147
148 #[inline]
149 #[must_use]
150 pub fn important_popup_background(&self) -> Color {
151 self.get_color_from_theme(self.style.important_popup.background_color)
152 }
153
154 #[inline]
155 #[must_use]
156 pub fn important_popup_border(&self) -> Color {
157 self.get_color_from_theme(self.style.important_popup.border_color)
158 }
159
160 #[inline]
161 #[must_use]
162 pub fn fallback_foreground(&self) -> Color {
163 self.get_color_from_theme(self.style.fallback.foreground_color)
164 }
165
166 #[inline]
167 #[must_use]
168 pub fn fallback_background(&self) -> Color {
169 self.get_color_from_theme(self.style.fallback.background_color)
170 }
171
172 #[inline]
173 #[must_use]
174 pub fn fallback_highlight(&self) -> Color {
175 self.get_color_from_theme(self.style.fallback.highlight_color)
176 }
177
178 #[inline]
179 #[must_use]
180 pub fn fallback_border(&self) -> Color {
181 self.get_color_from_theme(self.style.fallback.border_color)
182 }
183}
184
185#[derive(Debug, Clone, PartialEq, thiserror::Error)]
187pub enum ThemeColorParseError {
188 #[error("Failed to parse hex color: {0}")]
189 HexParseError(#[from] ThemeColorHexParseError),
190 #[error("Failed to parse color, expected prefix \"#\" or \"0x\" and length 6 or \"native\"")]
191 UnknownValue(String),
192}
193
194#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq)]
195#[serde(try_from = "String")]
196#[serde(into = "String")]
197pub enum ThemeColor {
198 Native,
200 Hex(ThemeColorHex),
202}
203
204impl ThemeColor {
205 #[must_use]
209 pub const fn new_hex(r: u8, g: u8, b: u8) -> Self {
210 Self::Hex(ThemeColorHex::new(r, g, b))
211 }
212
213 #[must_use]
215 pub const fn new_native() -> Self {
216 Self::Native
217 }
218
219 fn from_string(val: &str) -> Result<Self, ThemeColorParseError> {
221 if val == "native" {
222 return Ok(Self::Native);
223 }
224
225 let res = match ThemeColorHex::try_from(val) {
226 Ok(v) => v,
227 Err(err) => {
228 return Err(match err {
229 ThemeColorHexParseError::UnknownPrefix(_) => {
231 ThemeColorParseError::UnknownValue(val.to_string())
232 }
233 v => ThemeColorParseError::HexParseError(v),
234 });
235 }
236 };
237
238 Ok(Self::Hex(res))
239 }
240
241 #[allow(clippy::inherent_to_string)] fn to_string(self) -> String {
244 match self {
245 ThemeColor::Native => "native".to_string(),
246 ThemeColor::Hex(theme_color_hex) => theme_color_hex.to_hex(),
247 }
248 }
249
250 #[must_use]
252 pub fn resolve_color(&self, style: ColorTermusic) -> Color {
253 let hex = match self {
254 ThemeColor::Native => return style.into(),
255 ThemeColor::Hex(theme_color_hex) => theme_color_hex,
256 };
257
258 (*hex).into()
259 }
260}
261
262impl TryFrom<String> for ThemeColor {
263 type Error = ThemeColorParseError;
264
265 fn try_from(value: String) -> Result<Self, Self::Error> {
266 Self::from_string(&value)
267 }
268}
269
270impl TryFrom<&str> for ThemeColor {
271 type Error = ThemeColorParseError;
272
273 fn try_from(value: &str) -> Result<Self, Self::Error> {
274 Self::from_string(value)
275 }
276}
277
278impl From<ThemeColor> for String {
279 fn from(val: ThemeColor) -> Self {
280 ThemeColor::to_string(val)
281 }
282}
283
284#[derive(Debug, Clone, PartialEq, thiserror::Error)]
286pub enum ThemeColorHexParseError {
287 #[error("Failed to parse color because of {0}")]
288 ParseIntError(#[from] ParseIntError),
289 #[error(
290 "Failed to parse color because of incorrect length {0}, expected prefix \"#\" or \"0x\" and length 6"
291 )]
292 IncorrectLength(usize),
293 #[error("Failed to parse color becazse of unknown prefix \"{0}\", expected \"#\" or \"0x\"")]
294 UnknownPrefix(String),
295}
296
297#[derive(Debug, Copy, Clone, Deserialize, Serialize, PartialEq, Eq)]
299#[serde(try_from = "String")]
300#[serde(into = "String")]
301pub struct ThemeColorHex {
302 pub r: u8,
303 pub g: u8,
304 pub b: u8,
305}
306
307impl ThemeColorHex {
308 #[must_use]
310 pub const fn new(r: u8, g: u8, b: u8) -> Self {
311 Self { r, g, b }
312 }
313
314 pub fn from_hex(val: &str) -> Result<Self, ThemeColorHexParseError> {
316 let Some(without_prefix) = val.strip_prefix('#').or(val.strip_prefix("0x")) else {
317 return Err(ThemeColorHexParseError::UnknownPrefix(val.to_string()));
318 };
319
320 if without_prefix.len() != 6 {
322 return Err(ThemeColorHexParseError::IncorrectLength(
323 without_prefix.len(),
324 ));
325 }
326
327 let r = u8::from_str_radix(&without_prefix[0..=1], 16)
328 .map_err(ThemeColorHexParseError::ParseIntError)?;
329 let g = u8::from_str_radix(&without_prefix[2..=3], 16)
330 .map_err(ThemeColorHexParseError::ParseIntError)?;
331 let b = u8::from_str_radix(&without_prefix[4..=5], 16)
332 .map_err(ThemeColorHexParseError::ParseIntError)?;
333
334 Ok(Self { r, g, b })
335 }
336
337 #[inline]
339 #[must_use]
340 pub fn to_hex(&self) -> String {
341 format!("#{:02x}{:02x}{:02x}", self.r, self.g, self.b)
342 }
343}
344
345impl TryFrom<String> for ThemeColorHex {
346 type Error = ThemeColorHexParseError;
347
348 fn try_from(value: String) -> Result<Self, Self::Error> {
349 Self::from_hex(&value)
350 }
351}
352
353impl TryFrom<&str> for ThemeColorHex {
354 type Error = ThemeColorHexParseError;
355
356 fn try_from(value: &str) -> Result<Self, Self::Error> {
357 Self::from_hex(value)
358 }
359}
360
361impl From<ThemeColorHex> for String {
362 fn from(val: ThemeColorHex) -> Self {
363 ThemeColorHex::to_hex(&val)
364 }
365}
366
367impl From<ThemeColorHex> for Color {
368 fn from(val: ThemeColorHex) -> Self {
369 Color::Rgb(val.r, val.g, val.b)
370 }
371}
372
373#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
374#[serde(default)] pub struct ThemeColors {
376 #[serde(skip_serializing_if = "Option::is_none")]
381 pub file_name: Option<String>,
382 pub name: String,
383 pub author: String,
384 pub primary: ThemePrimary,
385 pub cursor: ThemeCursor,
386 pub normal: ThemeNormal,
387 pub bright: ThemeBright,
388}
389
390impl Default for ThemeColors {
391 fn default() -> Self {
392 Self {
393 file_name: None,
394 name: default_name(),
395 author: default_author(),
396 primary: ThemePrimary::default(),
397 cursor: ThemeCursor::default(),
398 normal: ThemeNormal::default(),
399 bright: ThemeBright::default(),
400 }
401 }
402}
403
404impl ThemeColors {
405 #[must_use]
409 pub fn full_default() -> Self {
410 Self {
411 name: "Termusic Default".to_string(),
412 author: "Termusic Developers".to_string(),
413 ..Default::default()
414 }
415 }
416
417 #[must_use]
419 pub fn full_native() -> Self {
420 Self {
421 file_name: None,
422 name: "Native".to_string(),
423 author: "Termusic Developers".to_string(),
424 primary: ThemePrimary::native(),
425 cursor: ThemeCursor::native(),
426 normal: ThemeNormal::native(),
427 bright: ThemeBright::native(),
428 }
429 }
430}
431
432#[derive(Debug, Clone, PartialEq, thiserror::Error)]
434pub enum ThemeColorsParseError {
435 #[error("Failed to parse Theme: {0}")]
436 ThemeColor(#[from] ThemeColorParseError),
437}
438
439impl TryFrom<YAMLTheme> for ThemeColors {
440 type Error = ThemeColorsParseError;
441
442 fn try_from(value: YAMLTheme) -> Result<Self, Self::Error> {
443 let colors = value.colors;
444 Ok(Self {
445 file_name: None,
446 name: colors.name.unwrap_or_else(default_name),
447 author: colors.author.unwrap_or_else(default_author),
448 primary: colors.primary.try_into()?,
449 cursor: colors.cursor.try_into()?,
450 normal: colors.normal.try_into()?,
451 bright: colors.bright.try_into()?,
452 })
453 }
454}
455
456impl ThemeColors {
457 pub fn from_yaml_file(path: &Path) -> anyhow::Result<Self> {
459 let parsed: YAMLTheme = serde_yaml::from_reader(BufReader::new(File::open(path)?))?;
460
461 let mut theme = Self::try_from(parsed)?;
462
463 let file_name = path.file_stem();
464 theme.file_name = file_name.map(|v| v.to_string_lossy().to_string());
465
466 Ok(theme)
467 }
468}
469
470#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
471pub struct ThemePrimary {
472 pub background: ThemeColor,
473 pub foreground: ThemeColor,
474}
475
476impl Default for ThemePrimary {
477 fn default() -> Self {
478 Self {
479 background: ThemeColor::new_hex(0x10, 0x14, 0x21),
480 foreground: ThemeColor::new_hex(0xff, 0xfb, 0xf6),
481 }
482 }
483}
484
485impl ThemePrimary {
486 fn native() -> Self {
487 Self {
488 background: ThemeColor::new_native(),
489 foreground: ThemeColor::new_native(),
490 }
491 }
492}
493
494impl TryFrom<YAMLThemePrimary> for ThemePrimary {
495 type Error = ThemeColorsParseError;
496
497 fn try_from(value: YAMLThemePrimary) -> Result<Self, Self::Error> {
498 Ok(Self {
499 background: value.background.try_into()?,
500 foreground: value.foreground.try_into()?,
501 })
502 }
503}
504
505#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
506#[serde(default)] pub struct ThemeCursor {
508 pub text: ThemeColor,
509 pub cursor: ThemeColor,
510}
511
512impl Default for ThemeCursor {
513 fn default() -> Self {
514 Self {
515 text: ThemeColor::new_hex(0x1e, 0x1e, 0x1e),
516 cursor: default_fff(),
517 }
518 }
519}
520
521impl ThemeCursor {
522 fn native() -> Self {
523 Self {
524 text: ThemeColor::new_native(),
525 cursor: ThemeColor::new_native(),
526 }
527 }
528}
529
530impl TryFrom<YAMLThemeCursor> for ThemeCursor {
531 type Error = ThemeColorsParseError;
532
533 fn try_from(value: YAMLThemeCursor) -> Result<Self, Self::Error> {
534 Ok(Self {
535 text: value.text.try_into()?,
536 cursor: value.cursor.try_into()?,
537 })
538 }
539}
540
541#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
542#[serde(default)] pub struct ThemeNormal {
544 pub black: ThemeColor,
545 pub red: ThemeColor,
546 pub green: ThemeColor,
547 pub yellow: ThemeColor,
548 pub blue: ThemeColor,
549 pub magenta: ThemeColor,
550 pub cyan: ThemeColor,
551 pub white: ThemeColor,
552}
553
554impl Default for ThemeNormal {
555 fn default() -> Self {
556 Self {
557 black: ThemeColor::new_hex(0x2e, 0x2e, 0x2e),
558 red: ThemeColor::new_hex(0xeb, 0x41, 0x29),
559 green: ThemeColor::new_hex(0xab, 0xe0, 0x47),
560 yellow: ThemeColor::new_hex(0xf6, 0xc7, 0x44),
561 blue: ThemeColor::new_hex(0x47, 0xa0, 0xf3),
562 magenta: ThemeColor::new_hex(0x7b, 0x5c, 0xb0),
563 cyan: ThemeColor::new_hex(0x64, 0xdb, 0xed),
564 white: ThemeColor::new_hex(0xe5, 0xe9, 0xf0),
565 }
566 }
567}
568
569impl ThemeNormal {
570 fn native() -> Self {
571 Self {
572 black: ThemeColor::new_native(),
573 red: ThemeColor::new_native(),
574 green: ThemeColor::new_native(),
575 yellow: ThemeColor::new_native(),
576 blue: ThemeColor::new_native(),
577 magenta: ThemeColor::new_native(),
578 cyan: ThemeColor::new_native(),
579 white: ThemeColor::new_native(),
580 }
581 }
582}
583
584impl TryFrom<YAMLThemeNormal> for ThemeNormal {
585 type Error = ThemeColorsParseError;
586
587 fn try_from(value: YAMLThemeNormal) -> Result<Self, Self::Error> {
588 Ok(Self {
589 black: value.black.try_into()?,
590 red: value.red.try_into()?,
591 green: value.green.try_into()?,
592 yellow: value.yellow.try_into()?,
593 blue: value.blue.try_into()?,
594 magenta: value.magenta.try_into()?,
595 cyan: value.cyan.try_into()?,
596 white: value.white.try_into()?,
597 })
598 }
599}
600
601#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
602#[serde(default)] pub struct ThemeBright {
604 pub black: ThemeColor,
605 pub red: ThemeColor,
606 pub green: ThemeColor,
607 pub yellow: ThemeColor,
608 pub blue: ThemeColor,
609 pub magenta: ThemeColor,
610 pub cyan: ThemeColor,
611 pub white: ThemeColor,
612}
613
614impl Default for ThemeBright {
615 fn default() -> Self {
616 Self {
617 black: ThemeColor::new_hex(0x56, 0x56, 0x56),
618 red: ThemeColor::new_hex(0xec, 0x53, 0x57),
619 green: ThemeColor::new_hex(0xc0, 0xe1, 0x7d),
620 yellow: ThemeColor::new_hex(0xf9, 0xda, 0x6a),
621 blue: ThemeColor::new_hex(0x49, 0xa4, 0xf8),
622 magenta: ThemeColor::new_hex(0xa4, 0x7d, 0xe9),
623 cyan: ThemeColor::new_hex(0x99, 0xfa, 0xf2),
624 white: default_fff(),
625 }
626 }
627}
628
629impl ThemeBright {
630 fn native() -> Self {
631 Self {
632 black: ThemeColor::new_native(),
633 red: ThemeColor::new_native(),
634 green: ThemeColor::new_native(),
635 yellow: ThemeColor::new_native(),
636 blue: ThemeColor::new_native(),
637 magenta: ThemeColor::new_native(),
638 cyan: ThemeColor::new_native(),
639 white: ThemeColor::new_native(),
640 }
641 }
642}
643
644impl TryFrom<YAMLThemeBright> for ThemeBright {
645 type Error = ThemeColorsParseError;
646
647 fn try_from(value: YAMLThemeBright) -> Result<Self, Self::Error> {
648 Ok(Self {
649 black: value.black.try_into()?,
650 red: value.red.try_into()?,
651 green: value.green.try_into()?,
652 yellow: value.yellow.try_into()?,
653 blue: value.blue.try_into()?,
654 magenta: value.magenta.try_into()?,
655 cyan: value.cyan.try_into()?,
656 white: value.white.try_into()?,
657 })
658 }
659}
660
661#[inline]
662fn default_name() -> String {
663 "empty name".to_string()
664}
665
666#[inline]
667fn default_author() -> String {
668 "empty author".to_string()
669}
670
671#[inline]
672fn default_fff() -> ThemeColor {
673 ThemeColor::new_hex(0xFF, 0xFF, 0xFF)
674}
675
676mod v1_interop {
677 use super::{
678 ThemeBright, ThemeColor, ThemeColorHex, ThemeColors, ThemeCursor, ThemeNormal,
679 ThemePrimary, ThemeWrap,
680 };
681 use crate::config::v1;
682
683 impl From<v1::AlacrittyColor> for ThemeColorHex {
684 fn from(value: v1::AlacrittyColor) -> Self {
685 Self {
686 r: value.r,
687 g: value.g,
688 b: value.b,
689 }
690 }
691 }
692
693 impl From<v1::AlacrittyColor> for ThemeColor {
694 fn from(value: v1::AlacrittyColor) -> Self {
695 Self::Hex(value.into())
696 }
697 }
698
699 impl From<&v1::Alacritty> for ThemeColors {
700 fn from(value: &v1::Alacritty) -> Self {
701 Self {
702 file_name: None,
703 name: value.name.clone(),
704 author: value.author.clone(),
705 primary: ThemePrimary {
706 background: value.background.into(),
707 foreground: value.foreground.into(),
708 },
709 cursor: ThemeCursor {
710 text: value.text.into(),
711 cursor: value.cursor.into(),
712 },
713 normal: ThemeNormal {
714 black: value.black.into(),
715 red: value.red.into(),
716 green: value.green.into(),
717 yellow: value.yellow.into(),
718 blue: value.blue.into(),
719 magenta: value.magenta.into(),
720 cyan: value.cyan.into(),
721 white: value.white.into(),
722 },
723 bright: ThemeBright {
724 black: value.light_black.into(),
725 red: value.light_red.into(),
726 green: value.light_green.into(),
727 yellow: value.light_yellow.into(),
728 blue: value.light_blue.into(),
729 magenta: value.light_magenta.into(),
730 cyan: value.light_cyan.into(),
731 white: value.light_white.into(),
732 },
733 }
734 }
735 }
736
737 impl From<&v1::Settings> for ThemeWrap {
738 fn from(value: &v1::Settings) -> Self {
739 Self {
740 theme: (&value.style_color_symbol.alacritty_theme).into(),
741 style: value.into(),
742 }
743 }
744 }
745
746 #[cfg(test)]
747 mod tests {
748 use super::*;
749
750 #[test]
751 fn should_convert_default_without_error() {
752 let converted: ThemeColors = (&v1::StyleColorSymbol::default().alacritty_theme).into();
753
754 assert_eq!(
755 converted,
756 ThemeColors {
757 file_name: None,
758 name: "default".into(),
759 author: "Larry Hao".into(),
760 primary: ThemePrimary {
761 background: "#101421".try_into().unwrap(),
762 foreground: "#fffbf6".try_into().unwrap()
763 },
764 cursor: ThemeCursor {
765 text: "#1E1E1E".try_into().unwrap(),
766 cursor: "#FFFFFF".try_into().unwrap()
767 },
768 normal: ThemeNormal {
769 black: "#2e2e2e".try_into().unwrap(),
770 red: "#eb4129".try_into().unwrap(),
771 green: "#abe047".try_into().unwrap(),
772 yellow: "#f6c744".try_into().unwrap(),
773 blue: "#47a0f3".try_into().unwrap(),
774 magenta: "#7b5cb0".try_into().unwrap(),
775 cyan: "#64dbed".try_into().unwrap(),
776 white: "#e5e9f0".try_into().unwrap()
777 },
778 bright: ThemeBright {
779 black: "#565656".try_into().unwrap(),
780 red: "#ec5357".try_into().unwrap(),
781 green: "#c0e17d".try_into().unwrap(),
782 yellow: "#f9da6a".try_into().unwrap(),
783 blue: "#49a4f8".try_into().unwrap(),
784 magenta: "#a47de9".try_into().unwrap(),
785 cyan: "#99faf2".try_into().unwrap(),
786 white: "#ffffff".try_into().unwrap()
787 }
788 }
789 );
790 }
791 }
792}
793
794#[cfg(test)]
795mod tests {
796 use super::ThemeColors;
797
798 mod theme_color {
799 use super::super::ThemeColor;
800
801 #[test]
802 fn should_parse_hex() {
803 assert_eq!(
804 ThemeColor::new_hex(1, 2, 3),
805 ThemeColor::try_from("#010203").unwrap()
806 );
807 assert_eq!(
808 ThemeColor::new_hex(1, 2, 3),
809 ThemeColor::try_from("0x010203").unwrap()
810 );
811 }
812
813 #[test]
814 fn should_parse_native() {
815 assert_eq!(
816 ThemeColor::new_native(),
817 ThemeColor::try_from("native").unwrap()
818 );
819 }
820
821 #[test]
822 fn should_serialize() {
823 assert_eq!(ThemeColor::new_hex(1, 2, 3).to_string(), "#010203");
824 assert_eq!(ThemeColor::new_native().to_string(), "native");
825 }
826 }
827
828 mod theme_color_hex {
829 use super::super::ThemeColorHex;
830
831 #[test]
832 fn should_parse_hashtag() {
833 assert_eq!(
834 ThemeColorHex::new(1, 2, 3),
835 ThemeColorHex::from_hex("#010203").unwrap()
836 );
837 assert_eq!(
838 ThemeColorHex::new(255, 255, 255),
839 ThemeColorHex::from_hex("#ffffff").unwrap()
840 );
841 assert_eq!(
842 ThemeColorHex::new(0, 0, 0),
843 ThemeColorHex::from_hex("#000000").unwrap()
844 );
845 }
846
847 #[test]
848 fn should_parse_0x() {
849 assert_eq!(
850 ThemeColorHex::new(1, 2, 3),
851 ThemeColorHex::from_hex("0x010203").unwrap()
852 );
853 assert_eq!(
854 ThemeColorHex::new(255, 255, 255),
855 ThemeColorHex::from_hex("0xffffff").unwrap()
856 );
857 assert_eq!(
858 ThemeColorHex::new(0, 0, 0),
859 ThemeColorHex::from_hex("0x000000").unwrap()
860 );
861 }
862 }
863
864 #[test]
865 fn should_default() {
866 let _ = ThemeColors::default();
868 }
869}