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