Skip to main content

efx_core/
attr.rs

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
45/// Returns the index of the matched option from allowed
46pub 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
64/// Named colors + #RRGGBB[AA] → RGBA
65pub 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}