1use std::{fmt::Display, str::FromStr};
6
7use strum_macros::FromRepr;
8use thiserror::Error;
9
10use crate::ParseError;
11
12#[derive(Clone, Default, Debug, Copy, PartialEq, Eq, FromRepr)]
13#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
14#[repr(u8)]
20pub enum DisplayMode {
21 #[default]
22 Hz75 = 0,
24 Hz90 = 1,
26}
27
28impl Display for DisplayMode {
29 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30 match self {
31 Self::Hz75 => write!(f, "75Hz"),
32 Self::Hz90 => write!(f, "90Hz"),
33 }
34 }
35}
36
37impl FromStr for DisplayMode {
38 type Err = String;
40
41 fn from_str(s: &str) -> Result<Self, Self::Err> {
42 match s.to_lowercase().as_str() {
43 "75" | "75hz" | "75 hz" => Ok(Self::Hz75),
44 "90" | "90hz" | "90 hz" => Ok(Self::Hz90),
45 _ => Err("Unknown display option. Only 75 & 90 are avalible.".into()),
46 }
47 }
48}
49
50impl DisplayMode {
51 pub const fn id(&self) -> u8 {
52 *self as u8
53 }
54 pub const fn from_id(v: u8) -> Option<Self> {
55 Self::from_repr(v)
56 }
57}
58
59#[derive(Debug, Error, PartialEq, Eq)]
60pub enum ColorStrParseError {
61 #[error("string is not a hexadecimal number")]
62 NonHex,
63 #[error("string is of unknown length {0}")]
64 UnknownSize(usize),
65}
66
67#[derive(Clone, Debug, Default, PartialEq, Eq)]
69#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
70pub struct RgbColor {
71 pub red: u8,
72 pub green: u8,
73 pub blue: u8,
74}
75
76impl RgbColor {
77 pub const fn new(red: u8, green: u8, blue: u8) -> Self {
78 Self { red, green, blue }
79 }
80
81 pub const fn as_bytes(&self) -> [u8; 3] {
82 [self.red, self.green, self.blue]
83 }
84 pub fn from_buf(buf: &[u8]) -> Result<Self, ParseError> {
85 Ok(RgbColor {
86 red: *buf.first().ok_or(ParseError::OutOfData)?,
87 green: *buf.get(1).ok_or(ParseError::OutOfData)?,
88 blue: *buf.get(2).ok_or(ParseError::OutOfData)?,
89 })
90 }
91}
92
93impl Display for RgbColor {
94 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95 write!(f, "#{:02X}{:02X}{:02X}", self.red, self.green, self.blue)
96 }
97}
98
99impl FromStr for RgbColor {
100 type Err = ColorStrParseError;
101
102 fn from_str(s: &str) -> Result<Self, Self::Err> {
103 let s = s.strip_prefix("#").unwrap_or(s);
104 let v = u64::from_str_radix(s, 16).map_err(|_| ColorStrParseError::NonHex)?;
105
106 let c = match s.len() {
107 1 => RgbColor::new(
109 (v & 0xF) as u8 * 0x11,
110 (v & 0xF) as u8 * 0x11,
111 (v & 0xF) as u8 * 0x11,
112 ),
113 2 => RgbColor::new(
115 (v & 0xFF) as u8, (v & 0xFF) as u8, (v & 0xFF) as u8, ),
119 3 => RgbColor::new(
121 ((v >> 8) & 0xF) as u8 * 0x11,
122 ((v >> 4) & 0xF) as u8 * 0x11,
123 (v & 0xF) as u8 * 0x11,
124 ),
125 6 => RgbColor::new(
129 ((v >> 16) & 0xFF) as u8,
130 ((v >> 8) & 0xFF) as u8,
131 (v & 0xFF) as u8,
132 ),
133 l => return Err(ColorStrParseError::UnknownSize(l)),
136 };
137
138 Ok(c)
139 }
140}
141
142#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
149pub struct Brightness(pub u16);
150
151impl Display for Brightness {
152 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
153 write!(f, "{}", self.0)
154 }
155}
156
157impl From<u16> for Brightness {
158 fn from(value: u16) -> Self {
159 Self(value)
160 }
161}
162
163impl From<Brightness> for u16 {
164 fn from(val: Brightness) -> Self {
165 val.0
166 }
167}
168
169impl Brightness {
170 pub fn try_from_utilfmt(value: f32) -> Result<Self, ()> {
179 let brightness = if value < 100.0 {
180 (value * 2.16) + 50.0
182 } else {
183 (value * 5.53) - 287.0
185 };
186
187 if brightness < 0.0 || brightness > u16::MAX.into() {
188 return Err(());
189 }
190
191 Ok(Self(unsafe { brightness.round().to_int_unchecked() }))
193 }
194
195 pub fn to_utilfmt(self) -> f32 {
197 let v = self.0;
198
199 if v < 270 {
200 (f32::from(v) - 50.0) / 2.16
202 } else {
203 (f32::from(v) + 287.0) / 5.53
205 }
206 }
207}
208
209#[cfg(test)]
210mod tests {
211 use super::*;
212
213 #[test]
214 fn display_mode() {
215 assert_eq!(DisplayMode::from_id(0), Some(DisplayMode::Hz75));
216 assert_eq!(DisplayMode::Hz75.id(), 0);
217
218 assert_eq!(DisplayMode::from_id(1), Some(DisplayMode::Hz90));
219 assert_eq!(DisplayMode::Hz90.id(), 1);
220
221 for i in 2..=0xFF {
222 assert_eq!(DisplayMode::from_id(i), None);
223 }
224 }
225
226 #[test]
227 fn rgb_color_value() {
228 assert_eq!(
229 RgbColor::from_buf(&[10, 20, 30]),
230 Ok(RgbColor::new(10, 20, 30))
231 );
232 assert_eq!(RgbColor::new(10, 20, 30).as_bytes(), [10, 20, 30]);
233
234 assert_eq!(RgbColor::from_buf(&[0]), Err(ParseError::OutOfData));
235 assert_eq!(RgbColor::from_buf(&[0, 0]), Err(ParseError::OutOfData));
236 assert_eq!(
237 RgbColor::from_buf(&[0, 0, 0, 0]),
238 Ok(RgbColor::new(0, 0, 0))
239 );
240 }
241
242 #[test]
243 fn rgb_color_string() {
244 assert_eq!(
245 RgbColor::new(0x12, 0x34, 0x56).to_string(),
246 "#123456".to_owned(),
247 );
248 assert_eq!(
249 RgbColor::new(0xbe, 0xef, 0xf0).to_string(),
250 "#BEEFF0".to_owned(),
251 );
252
253 assert_eq!(
254 RgbColor::from_str("BEEFF0"),
255 Ok(RgbColor::new(0xbe, 0xef, 0xf0)),
256 );
257 assert_eq!(
258 RgbColor::from_str("beeff0"),
259 Ok(RgbColor::new(0xbe, 0xef, 0xf0)),
260 );
261 assert_eq!(
262 RgbColor::from_str("#123456"),
263 Ok(RgbColor::new(0x12, 0x34, 0x56)),
264 );
265
266 assert_eq!(
267 RgbColor::from_str("123"),
268 Ok(RgbColor::new(0x11, 0x22, 0x33)),
269 );
270 assert_eq!(
271 RgbColor::from_str("#7"),
272 Ok(RgbColor::new(0x77, 0x77, 0x77)),
273 );
274 assert_eq!(
275 RgbColor::from_str("f0"),
276 Ok(RgbColor::new(0xf0, 0xf0, 0xf0)),
277 );
278
279 assert_eq!(
280 RgbColor::from_str("eeeeeeeee"),
281 Err(ColorStrParseError::UnknownSize(9)),
282 );
283 assert_eq!(RgbColor::from_str("meow!"), Err(ColorStrParseError::NonHex),);
284 }
285
286 }