1use std::str::FromStr;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4pub struct Rgba {
5 pub r: u8,
6 pub g: u8,
7 pub b: u8,
8 pub a: u8,
9}
10
11pub fn parse_bool(name: &str, s: &str) -> Result<bool, String> {
12 match s.trim().to_ascii_lowercase().as_str() {
13 "true" => Ok(true),
14 "false" => Ok(false),
15 other => Err(format!(
16 "efx: attribute `{}` expects boolean (true/false), got `{}`",
17 name, other
18 )),
19 }
20}
21
22pub fn parse_u8(name: &str, s: &str) -> Result<u8, String> {
23 let raw = s.trim();
24 if raw.is_empty() {
25 return Err(format!("efx: attribute `{}` expects u8, got empty", name));
26 }
27 match u8::from_str(raw) {
28 Ok(v) => Ok(v),
29 Err(_) => Err(format!(
30 "efx: attribute `{}` must be an integer in 0..=255 (u8), got `{}`",
31 name, s
32 )),
33 }
34}
35
36pub fn parse_f32(name: &str, s: &str) -> Result<f32, String> {
37 s.trim().parse::<f32>().map_err(|_| {
38 format!(
39 "efx: attribute `{}` expects number (f32), got `{}`",
40 name, s
41 )
42 })
43}
44
45pub fn parse_enum(name: &str, s: &str, allowed: &[&str]) -> Result<usize, String> {
47 let val = s.trim().to_ascii_lowercase();
48 if let Some((idx, _)) = allowed
49 .iter()
50 .enumerate()
51 .find(|(_, v)| v.eq_ignore_ascii_case(&val))
52 {
53 Ok(idx)
54 } else {
55 Err(format!(
56 "efx: attribute `{}` invalid value `{}` (allowed: {})",
57 name,
58 s,
59 allowed.join("|")
60 ))
61 }
62}
63
64pub fn parse_color_rgba(name: &str, s: &str) -> Result<Rgba, String> {
66 let v = s.trim();
67 if let Some(rgba) = named_color_rgba(v) {
68 return Ok(rgba);
69 }
70 if let Some(rgba) = hex_color_rgba(v) {
71 return Ok(rgba);
72 }
73 Err(format!(
74 "efx: attribute `{}` expects color name (e.g. red, light_gray) or hex #RRGGBB[AA], got `{}`",
75 name, s
76 ))
77}
78
79fn named_color_rgba(s: &str) -> Option<Rgba> {
80 let (r, g, b, a) = match s.to_ascii_lowercase().as_str() {
81 "red" => (255, 0, 0, 255),
82 "green" => (0, 255, 0, 255),
83 "blue" => (0, 0, 255, 255),
84 "white" => (255, 255, 255, 255),
85 "black" => (0, 0, 0, 255),
86 "gray" | "grey" => (128, 128, 128, 255),
87 "dark_gray" | "darkgrey" | "dark_grey" => (64, 64, 64, 255),
88 "light_gray" | "lightgrey" | "light_grey" => (192, 192, 192, 255),
89 "yellow" => (255, 255, 0, 255),
90 "transparent" => (0, 0, 0, 0),
91 _ => return None,
92 };
93 Some(Rgba { r, g, b, a })
94}
95
96fn hex_color_rgba(s: &str) -> Option<Rgba> {
97 let hs = s.strip_prefix('#')?;
98 if hs.len() != 6 && hs.len() != 8 {
99 return None;
100 }
101
102 let r = u8::from_str_radix(&hs[0..2], 16).ok()?;
103 let g = u8::from_str_radix(&hs[2..4], 16).ok()?;
104 let b = u8::from_str_radix(&hs[4..6], 16).ok()?;
105 let a = if hs.len() == 8 {
106 u8::from_str_radix(&hs[6..8], 16).ok()?
107 } else {
108 0xFF
109 };
110
111 Some(Rgba { r, g, b, a })
112}