1use std::str::FromStr;
2
3use serde::de::Deserializer;
4use serde::ser::Serializer;
5use serde::{Deserialize, Serialize};
6
7pub static PUBLIC_OBJECT_LIBS_KEY: &str = "public.objectLibs";
8
9pub type Plist = plist::Dictionary;
11
12#[derive(Debug, Clone, PartialEq, Copy)]
16pub struct Color {
17 red: f64,
19 green: f64,
21 blue: f64,
23 alpha: f64,
25}
26
27impl Color {
28 pub fn new(red: f64, green: f64, blue: f64, alpha: f64) -> Result<Self, ColorError> {
32 if [red, green, blue, alpha].iter().all(|v| (0.0..=1.0).contains(v)) {
33 Ok(Self { red, green, blue, alpha })
34 } else {
35 Err(ColorError::Value)
36 }
37 }
38
39 pub fn channels(&self) -> (f64, f64, f64, f64) {
41 (self.red, self.green, self.blue, self.alpha)
42 }
43}
44
45#[derive(Debug, thiserror::Error)]
49pub enum ColorError {
50 #[error("failed to parse color string '{0}'")]
52 Parse(String),
53 #[error("color channel values must be between 0 and 1, inclusive")]
55 Value,
56}
57
58impl FromStr for Color {
59 type Err = ColorError;
60
61 fn from_str(s: &str) -> Result<Self, Self::Err> {
62 let mut iter =
63 s.split(',').map(|v| v.parse::<f64>().map_err(|_| ColorError::Parse(s.to_owned())));
64 let red = iter.next().unwrap_or_else(|| Err(ColorError::Parse(s.to_owned())))?;
65 let green = iter.next().unwrap_or_else(|| Err(ColorError::Parse(s.to_owned())))?;
66 let blue = iter.next().unwrap_or_else(|| Err(ColorError::Parse(s.to_owned())))?;
67 let alpha = iter.next().unwrap_or_else(|| Err(ColorError::Parse(s.to_owned())))?;
68 if iter.next().is_some() {
69 Err(ColorError::Parse(s.to_owned()))
70 } else {
71 Color::new(red, green, blue, alpha)
72 }
73 }
74}
75
76impl Serialize for Color {
77 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
78 where
79 S: Serializer,
80 {
81 let color_string = self.to_rgba_string();
82 serializer.serialize_str(&color_string)
83 }
84}
85
86impl<'de> Deserialize<'de> for Color {
87 fn deserialize<D>(deserializer: D) -> Result<Color, D::Error>
88 where
89 D: Deserializer<'de>,
90 {
91 let string = String::deserialize(deserializer)?;
92 Color::from_str(&string).map_err(serde::de::Error::custom)
93 }
94}
95
96#[cfg(test)]
97mod tests {
98 use serde_test::{assert_de_tokens, assert_ser_tokens, assert_tokens, Token};
99
100 use super::*;
101
102 #[test]
103 fn color_parsing() {
104 let c1 = Color { red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0 };
105 assert_tokens(&c1, &[Token::Str("1,0,0,1")]);
106
107 let c2 = Color { red: 0.0, green: 0.5, blue: 0.0, alpha: 0.5 };
108 assert_tokens(&c2, &[Token::Str("0,0.5,0,0.5")]);
109
110 let c3 = Color { red: 0.0, green: 0.0, blue: 0.0, alpha: 0.0 };
111 assert_tokens(&c3, &[Token::Str("0,0,0,0")]);
112
113 let c4 = Color { red: 0.123, green: 0.456, blue: 0.789, alpha: 0.159 };
114 assert_tokens(&c4, &[Token::Str("0.123,0.456,0.789,0.159")]);
115
116 #[allow(clippy::excessive_precision)]
117 let c5 = Color { red: 0.123456789, green: 0.456789123, blue: 0.789123456, alpha: 0.1 };
118 assert_ser_tokens(&c5, &[Token::Str("0.123,0.457,0.789,0.1")]);
119
120 #[allow(clippy::excessive_precision)]
121 let c6 = Color { red: 0.123456789, green: 0.456789123, blue: 0.789123456, alpha: 0.1 };
122 assert_de_tokens(&c6, &[Token::Str("0.123456789,0.456789123,0.789123456,0.1")]);
123 }
124}