bernardo-tui 0.2.7

A keyboard-only, distraction-free TUI widget library
Documentation
use std::fmt::Formatter;

use serde::de::{Error, Visitor};
use serde::{Deserialize, Deserializer, Serialize, Serializer};

#[derive(Clone, Copy, Eq, PartialOrd, PartialEq, Hash, Debug)]
pub struct Color {
    pub r: u8,
    pub g: u8,
    pub b: u8,
}

impl Color {
    pub const fn new(r: u8, g: u8, b: u8) -> Self {
        Color { r, g, b }
    }

    pub fn half(&self) -> Self {
        Color {
            r: self.r / 2,
            g: self.r / 2,
            b: self.b / 2,
        }
    }

    pub fn interpolate(a: Color, b: Color) -> Color {
        let r: u16 = (a.r as u16 + b.r as u16) / 2;
        let g: u16 = (a.b as u16 + b.b as u16) / 2;
        let b: u16 = (a.g as u16 + b.g as u16) / 2;
        Color {
            r: r as u8,
            g: g as u8,
            b: b as u8,
        }
    }
}

impl Serialize for Color {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.serialize_str(&format!("#{:02X}{:02X}{:02X}", self.r, self.g, self.b))
    }
}

struct ColorVisitor;

impl<'de> Visitor<'de> for ColorVisitor {
    type Value = Color;

    fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
        formatter.write_str("a color written in \"#(r)(g)(b)\" format")
    }

    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
    where
        E: Error,
    {
        if v.len() != 7 {
            return Err(E::custom(format!("length should be 7 and is {}", v.len())));
        }

        if v.chars().next() != Some('#') {
            return Err(E::custom(format!(
                "expected first character to be \"#\", got \"{}\"",
                v.chars().next().unwrap()
            )));
        }

        let mut decoded: [u8; 3] = [0; 3];
        match hex::decode_to_slice(&v[1..], &mut decoded) {
            Ok(()) => Ok(Color {
                r: decoded[0],
                g: decoded[1],
                b: decoded[2],
            }),
            Err(e) => Err(E::custom(format!("failed hex decoding: {:?}", e))),
        }
    }
}

impl<'a> Deserialize<'a> for Color {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'a>,
    {
        deserializer.deserialize_str(ColorVisitor)
    }
}

impl From<(u8, u8, u8)> for Color {
    fn from(tuple: (u8, u8, u8)) -> Self {
        Color {
            r: tuple.0,
            g: tuple.1,
            b: tuple.2,
        }
    }
}

pub const BLACK: Color = Color { r: 0, g: 0, b: 0 };
pub const WHITE: Color = Color { r: 255, g: 255, b: 255 };

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_serialize() {
        assert_eq!(ron::to_string(&Color::new(0, 0, 0)), Ok("\"#000000\"".to_string()));
        assert_eq!(ron::to_string(&Color::new(0, 100, 0)), Ok("\"#006400\"".to_string()));
        assert_eq!(ron::to_string(&Color::new(0, 255, 16)), Ok("\"#00FF10\"".to_string()));
    }

    #[test]
    fn test_deserialize() {
        assert_eq!(ron::from_str("\"#00FF0F\""), Ok(Color::new(0, 255, 15)));
        assert_eq!(ron::from_str("\"#006400\""), Ok(Color::new(0, 100, 0)));
    }
}