use super::ParseColorError;
use crate::color::Color;
use crate::style::Style;
use crate::util::strip_nocase_prefix;
use std::fmt;
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct RgbColor(
pub u8,
pub u8,
pub u8,
);
impl RgbColor {
pub fn red(self) -> u8 {
self.0
}
pub fn green(self) -> u8 {
self.1
}
pub fn blue(self) -> u8 {
self.2
}
pub fn as_foreground(self) -> Style {
Style::new().foreground(Some(self.into()))
}
pub fn as_background(self) -> Style {
Style::new().background(Some(self.into()))
}
pub fn on<C: Into<Color>>(self, bg: C) -> Style {
Style::new()
.foreground(Some(self.into()))
.background(Some(bg.into()))
}
}
impl From<(u8, u8, u8)> for RgbColor {
fn from(value: (u8, u8, u8)) -> RgbColor {
RgbColor(value.0, value.1, value.2)
}
}
impl From<RgbColor> for (u8, u8, u8) {
fn from(value: RgbColor) -> (u8, u8, u8) {
(value.0, value.1, value.2)
}
}
#[cfg(feature = "anstyle")]
#[cfg_attr(docsrs, doc(cfg(feature = "anstyle")))]
impl From<RgbColor> for anstyle::RgbColor {
fn from(value: RgbColor) -> anstyle::RgbColor {
anstyle::RgbColor(value.0, value.1, value.2)
}
}
#[cfg(feature = "anstyle")]
#[cfg_attr(docsrs, doc(cfg(feature = "anstyle")))]
impl From<anstyle::RgbColor> for RgbColor {
fn from(value: anstyle::RgbColor) -> RgbColor {
RgbColor(value.0, value.1, value.2)
}
}
#[cfg(feature = "crossterm")]
#[cfg_attr(docsrs, doc(cfg(feature = "crossterm")))]
impl From<RgbColor> for crossterm::style::Color {
fn from(value: RgbColor) -> crossterm::style::Color {
crossterm::style::Color::Rgb {
r: value.0,
g: value.1,
b: value.2,
}
}
}
#[cfg(feature = "ratatui")]
#[cfg_attr(docsrs, doc(cfg(feature = "ratatui")))]
impl From<RgbColor> for ratatui_core::style::Color {
fn from(value: RgbColor) -> ratatui_core::style::Color {
ratatui_core::style::Color::Rgb(value.0, value.1, value.2)
}
}
impl fmt::Display for RgbColor {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "#{:02x}{:02x}{:02x}", self.0, self.1, self.2)
}
}
impl std::str::FromStr for RgbColor {
type Err = ParseColorError;
fn from_str(s: &str) -> Result<RgbColor, ParseColorError> {
if let Some(hex) = s
.strip_prefix('#')
.filter(|s| s.chars().all(|c| c.is_ascii_hexdigit()) && s.len() == 6)
{
let red = u8::from_str_radix(&hex[..2], 16).expect("should be valid hex string");
let green = u8::from_str_radix(&hex[2..4], 16).expect("should be valid hex string");
let blue = u8::from_str_radix(&hex[4..], 16).expect("should be valid hex string");
Ok(RgbColor(red, green, blue))
} else if let Some(dec) = strip_nocase_prefix(s, "rgb(").and_then(|s| s.strip_suffix(')')) {
let mut rgb = dec.split(',').map(str::parse::<u8>);
let red = rgb.next();
let green = rgb.next();
let blue = rgb.next();
let rest = rgb.next();
if let (Some(Ok(red)), Some(Ok(green)), Some(Ok(blue)), None) = (red, green, blue, rest)
{
Ok(RgbColor(red, green, blue))
} else {
Err(ParseColorError(s.to_owned()))
}
} else {
Err(ParseColorError(s.to_owned()))
}
}
}
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
impl serde::Serialize for RgbColor {
fn serialize<S: serde::ser::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.collect_str(self)
}
}
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
impl<'de> serde::Deserialize<'de> for RgbColor {
fn deserialize<D: serde::de::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
struct Visitor;
impl serde::de::Visitor<'_> for Visitor {
type Value = RgbColor;
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(r##"a string of the form "rgb(INT,INT,INT)" or "#xxxxxx""##)
}
fn visit_str<E>(self, input: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
input
.parse::<RgbColor>()
.map_err(|_| E::invalid_value(serde::de::Unexpected::Str(input), &self))
}
}
deserializer.deserialize_str(Visitor)
}
}
#[cfg(test)]
mod tests {
use super::*;
use rstest::rstest;
#[test]
fn test_display() {
assert_eq!(RgbColor(0x7F, 0xFF, 0x00).to_string(), "#7fff00");
}
#[rstest]
#[case("#7fff00", RgbColor(0x7F, 0xFF, 0x00))]
#[case("#7FFF00", RgbColor(0x7F, 0xFF, 0x00))]
#[case("rgb(78,126,70)", RgbColor(78, 126, 70))]
#[case("RGB(78,126,70)", RgbColor(78, 126, 70))]
fn test_parse(#[case] s: &str, #[case] color: RgbColor) {
assert_eq!(s.parse::<RgbColor>().unwrap(), color);
}
#[rstest]
#[case("7fff00")]
#[case("# 7fff00")]
#[case("#000")]
#[case("rgb(78, 126, 70)")]
#[case("rgb(78,126)")]
#[case("rgb(78,126,70,0)")]
#[case("rgb(0x7f,0xff,0x00)")]
fn test_parse_err(#[case] s: &str) {
assert!(s.parse::<RgbColor>().is_err());
}
}