yofi 0.2.0

minimalistic menu for wayland
use std::convert::TryInto;

use raqote::SolidSource;
use serde::Deserialize;

#[derive(Deserialize, Debug, Clone, Copy)]
pub struct Color(#[serde(deserialize_with = "deserialize_color")] u32);

impl Color {
    pub const fn from_rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
        Self(u32::from_be_bytes([r, g, b, a]))
    }

    pub const fn to_rgba(self) -> [u8; 4] {
        self.0.to_be_bytes()
    }

    pub fn as_source(self) -> SolidSource {
        let [r, g, b, a] = self.to_rgba();
        SolidSource::from_unpremultiplied_argb(a, r, g, b)
    }
}

impl std::ops::Deref for Color {
    type Target = u32;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

fn deserialize_color<'de, D: serde::Deserializer<'de>>(d: D) -> Result<u32, D::Error> {
    struct ColorDeHelper;

    impl<'de> serde::de::Visitor<'de> for ColorDeHelper {
        type Value = u32;

        fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
            write!(
                formatter,
                "invalid color value, must be either numerical or css-like hex value with # prefix"
            )
        }

        fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E>
        where
            E: serde::de::Error,
        {
            value.try_into().map_err(serde::de::Error::custom)
        }

        fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
        where
            E: serde::de::Error,
        {
            let part = match value.chars().next() {
                None => return Err(serde::de::Error::custom("color cannot be empty")),
                Some('#') => value.split_at(1).1,
                Some(_) => {
                    return Err(serde::de::Error::custom(
                        "color can be either decimal or hex number prefixed with '#'",
                    ))
                }
            };

            let decoded = u32::from_str_radix(part, 16).map_err(serde::de::Error::custom);
            match part.len() {
                3 => {
                    let decoded = decoded?;
                    let (r, g, b) = ((decoded & 0xf00) >> 8, (decoded & 0xf0) >> 4, decoded & 0xf);
                    Ok((r << 4 | r) << 24 | (g << 4 | g) << 16 | (b << 4 | b) << 8 | 0xff)
                }
                6 => decoded.map(|d| d << 8 | 0xff),
                8 => decoded,
                _ => Err(serde::de::Error::custom(
                    "hex color can only be specified in #RGB, #RRGGBB, or #RRGGBBAA format",
                )),
            }
        }
    }

    d.deserialize_any(ColorDeHelper)
}