1use std::{fmt, str::FromStr};
5
6use rgb::Rgb;
7use strum::EnumCount;
8use tinyrand::{RandRange, StdRand};
9
10use crate::Prefix;
11
12use super::ExtendedColour;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumCount)]
16#[allow(missing_docs)]
17pub enum Brown {
18 SaddleBrown,
19 Sienna,
20 Chocolate,
21 Peru,
22 SandyBrown,
23 BurlyWood,
24 Tan,
25 RosyBrown,
26}
27
28impl fmt::Display for Brown {
29 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30 match self {
31 Self::SaddleBrown => write!(f, "#8B4513"),
32 Self::Sienna => write!(f, "#A0522D"),
33 Self::Chocolate => write!(f, "#D2691E"),
34 Self::Peru => write!(f, "#CD853F"),
35 Self::SandyBrown => write!(f, "#F4A460"),
36 Self::BurlyWood => write!(f, "#DEB887"),
37 Self::Tan => write!(f, "#D2B48C"),
38 Self::RosyBrown => write!(f, "#BC8F8F"),
39 }
40 }
41}
42
43impl Brown {
44 pub fn to_rgb(&self) -> Rgb<u8> {
60 let colour = self.to_string();
61
62 let r: u8 = u8::from_str_radix(&colour[1..3], 16).unwrap();
63 let g: u8 = u8::from_str_radix(&colour[3..5], 16).unwrap();
64 let b: u8 = u8::from_str_radix(&colour[5..7], 16).unwrap();
65
66 Rgb::new(r, g, b)
67 }
68
69 pub fn to_hex_triplet(&self, prefix: Prefix) -> String {
82 let rgb = self.to_rgb();
83
84 let prefix = match prefix {
85 Prefix::Hash => "#",
86 Prefix::None => "",
87 };
88
89 format!("{}{:02X}{:02X}{:02X}", prefix, rgb.r, rgb.g, rgb.b)
90 }
91
92 pub fn parse(name: &str) -> Option<Self> {
103 match name.to_lowercase().as_str() {
104 "#8b4513" | "8b4513" | "saddlebrown" => Some(Self::SaddleBrown),
105 "#a0522d" | "a0522d" | "sienna" => Some(Self::Sienna),
106 "#d2691e" | "d2691e" | "chocolate" => Some(Self::Chocolate),
107 "#cd853f" | "cd853f" | "peru" => Some(Self::Peru),
108 "#f4a460" | "f4a460" | "sandybrown" => Some(Self::SandyBrown),
109 "#deb887" | "deb887" | "burlywood" => Some(Self::BurlyWood),
110 "#d2b48c" | "d2b48c" | "tan" => Some(Self::Tan),
111 "#bc8f8f" | "bc8f8f" | "rosybrown" => Some(Self::RosyBrown),
112 _ => None,
113 }
114 }
115
116 pub fn random() -> Self {
128 let mut rand = StdRand::default();
129
130 match rand.next_range(0..Self::COUNT) {
131 0 => Self::SaddleBrown,
132 1 => Self::Sienna,
133 2 => Self::Chocolate,
134 3 => Self::Peru,
135 4 => Self::SandyBrown,
136 5 => Self::BurlyWood,
137 6 => Self::Tan,
138 7 => Self::RosyBrown,
139 _ => Self::Peru,
140 }
141 }
142}
143
144impl FromStr for Brown {
145 type Err = String;
146 fn from_str(s: &str) -> Result<Self, Self::Err> {
147 match Self::parse(s) {
148 Some(colour) => Ok(colour),
149 None => Err(format!("Invalid Colour: {s}")),
150 }
151 }
152}
153
154impl ExtendedColour for Brown {}
155
156#[cfg(test)]
157mod tests {
158 use super::*;
159 use rstest::rstest;
160
161 #[rstest]
162 #[case(Brown::SaddleBrown, "rgb(139,69,19)")]
163 #[case(Brown::Sienna, "rgb(160,82,45)")]
164 #[case(Brown::Chocolate, "rgb(210,105,30)")]
165 #[case(Brown::Peru, "rgb(205,133,63)")]
166 #[case(Brown::SandyBrown, "rgb(244,164,96)")]
167 #[case(Brown::BurlyWood, "rgb(222,184,135)")]
168 #[case(Brown::Tan, "rgb(210,180,140)")]
169 #[case(Brown::RosyBrown, "rgb(188,143,143)")]
170 fn test_rgb_string(#[case] colour: Brown, #[case] expected: String) {
171 let rgb_colour = colour.to_rgb();
172 let string = rgb_colour.to_string();
173
174 assert_eq!(expected, string);
175 }
176
177 #[rstest]
178 #[case(Brown::SaddleBrown, "8B4513")]
179 #[case(Brown::Sienna, "A0522D")]
180 #[case(Brown::Chocolate, "D2691E")]
181 #[case(Brown::Peru, "CD853F")]
182 #[case(Brown::SandyBrown, "F4A460")]
183 #[case(Brown::BurlyWood, "DEB887")]
184 #[case(Brown::Tan, "D2B48C")]
185 #[case(Brown::RosyBrown, "BC8F8F")]
186 fn test_hex_triplet_string(
187 #[case] colour: Brown,
188 #[values(Prefix::None, Prefix::Hash)] prefix: Prefix,
189 #[case] expected: String,
190 ) {
191 let prefix_string = match prefix {
192 Prefix::None => "".to_string(),
193 Prefix::Hash => "#".to_string(),
194 };
195
196 let expected = format!("{prefix_string}{expected}");
197
198 let hex_colour = colour.to_hex_triplet(prefix);
199
200 assert_eq!(expected, hex_colour);
201 }
202
203 #[rstest]
204 #[case("#8b4513", Brown::SaddleBrown)]
205 #[case("#a0522d", Brown::Sienna)]
206 #[case("#d2691e", Brown::Chocolate)]
207 #[case("#cd853f", Brown::Peru)]
208 #[case("#f4a460", Brown::SandyBrown)]
209 #[case("#deb887", Brown::BurlyWood)]
210 #[case("#d2b48c", Brown::Tan)]
211 #[case("#bc8f8f", Brown::RosyBrown)]
212 #[case("8b4513", Brown::SaddleBrown)]
213 #[case("a0522d", Brown::Sienna)]
214 #[case("d2691e", Brown::Chocolate)]
215 #[case("cd853f", Brown::Peru)]
216 #[case("f4a460", Brown::SandyBrown)]
217 #[case("deb887", Brown::BurlyWood)]
218 #[case("d2b48c", Brown::Tan)]
219 #[case("bc8f8f", Brown::RosyBrown)]
220 #[case("saddlebrown", Brown::SaddleBrown)]
221 #[case("sienna", Brown::Sienna)]
222 #[case("chocolate", Brown::Chocolate)]
223 #[case("peru", Brown::Peru)]
224 #[case("sandybrown", Brown::SandyBrown)]
225 #[case("burlywood", Brown::BurlyWood)]
226 #[case("tan", Brown::Tan)]
227 #[case("rosybrown", Brown::RosyBrown)]
228 fn test_from_str(#[case] input: &str, #[case] expected: Brown) {
229 assert_eq!(expected, Brown::from_str(input).unwrap())
230 }
231
232 #[rstest]
233 #[case("#8b4513", Some(Brown::SaddleBrown))]
234 #[case("#a0522d", Some(Brown::Sienna))]
235 #[case("#d2691e", Some(Brown::Chocolate))]
236 #[case("#cd853f", Some(Brown::Peru))]
237 #[case("#f4a460", Some(Brown::SandyBrown))]
238 #[case("#deb887", Some(Brown::BurlyWood))]
239 #[case("#d2b48c", Some(Brown::Tan))]
240 #[case("#bc8f8f", Some(Brown::RosyBrown))]
241 #[case("8b4513", Some(Brown::SaddleBrown))]
242 #[case("a0522d", Some(Brown::Sienna))]
243 #[case("d2691e", Some(Brown::Chocolate))]
244 #[case("cd853f", Some(Brown::Peru))]
245 #[case("f4a460", Some(Brown::SandyBrown))]
246 #[case("deb887", Some(Brown::BurlyWood))]
247 #[case("d2b48c", Some(Brown::Tan))]
248 #[case("bc8f8f", Some(Brown::RosyBrown))]
249 #[case("saddlebrown", Some(Brown::SaddleBrown))]
250 #[case("sienna", Some(Brown::Sienna))]
251 #[case("chocolate", Some(Brown::Chocolate))]
252 #[case("peru", Some(Brown::Peru))]
253 #[case("sandybrown", Some(Brown::SandyBrown))]
254 #[case("burlywood", Some(Brown::BurlyWood))]
255 #[case("tan", Some(Brown::Tan))]
256 #[case("rosybrown", Some(Brown::RosyBrown))]
257 #[case("012345", None)]
258 fn test_name_colour(#[case] input: &str, #[case] expected: Option<Brown>) {
259 assert_eq!(expected, Brown::name_colour(input))
260 }
261}