use super::ParseColorError;
use crate::color256::Color256;
use crate::rgbcolor::RgbColor;
use crate::style::Style;
use std::fmt;
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum Color {
#[default]
Default,
Color256(Color256),
Rgb(RgbColor),
}
impl Color {
pub fn as_foreground(self) -> Style {
Style::new().foreground(Some(self))
}
pub fn as_background(self) -> Style {
Style::new().background(Some(self))
}
pub fn on<C: Into<Color>>(self, bg: C) -> Style {
Style::new()
.foreground(Some(self))
.background(Some(bg.into()))
}
}
impl From<Color256> for Color {
fn from(value: Color256) -> Color {
Color::Color256(value)
}
}
impl From<RgbColor> for Color {
fn from(value: RgbColor) -> Color {
Color::Rgb(value)
}
}
impl From<u8> for Color {
fn from(value: u8) -> Color {
Color::Color256(Color256::from(value))
}
}
impl From<(u8, u8, u8)> for Color {
fn from(value: (u8, u8, u8)) -> Color {
Color::Rgb(RgbColor::from(value))
}
}
#[cfg(feature = "anstyle")]
#[cfg_attr(docsrs, doc(cfg(feature = "anstyle")))]
impl TryFrom<Color> for anstyle::Color {
type Error = crate::ConversionError;
fn try_from(value: Color) -> Result<anstyle::Color, crate::ConversionError> {
match value {
Color::Default => Err(crate::ConversionError),
Color::Color256(c) => Ok(anstyle::Color::Ansi256(c.into())),
Color::Rgb(c) => Ok(anstyle::Color::Rgb(c.into())),
}
}
}
#[cfg(feature = "anstyle")]
#[cfg_attr(docsrs, doc(cfg(feature = "anstyle")))]
impl From<anstyle::Color> for Color {
fn from(value: anstyle::Color) -> Color {
match value {
anstyle::Color::Ansi(c) => Color::Color256(c.into()),
anstyle::Color::Ansi256(c) => Color::Color256(c.into()),
anstyle::Color::Rgb(c) => Color::Rgb(c.into()),
}
}
}
#[cfg(feature = "crossterm")]
#[cfg_attr(docsrs, doc(cfg(feature = "crossterm")))]
impl From<Color> for crossterm::style::Color {
fn from(value: Color) -> crossterm::style::Color {
match value {
Color::Default => crossterm::style::Color::Reset,
Color::Color256(c) => c.into(),
Color::Rgb(c) => c.into(),
}
}
}
#[cfg(feature = "crossterm")]
#[cfg_attr(docsrs, doc(cfg(feature = "crossterm")))]
impl From<crossterm::style::Color> for Color {
fn from(value: crossterm::style::Color) -> Color {
match value {
crossterm::style::Color::Reset => Color::Default,
crossterm::style::Color::Black => Color256::BRIGHT_BLACK.into(),
crossterm::style::Color::DarkGrey => Color256::BLACK.into(),
crossterm::style::Color::Red => Color256::BRIGHT_RED.into(),
crossterm::style::Color::DarkRed => Color256::RED.into(),
crossterm::style::Color::Green => Color256::BRIGHT_GREEN.into(),
crossterm::style::Color::DarkGreen => Color256::GREEN.into(),
crossterm::style::Color::Yellow => Color256::BRIGHT_YELLOW.into(),
crossterm::style::Color::DarkYellow => Color256::YELLOW.into(),
crossterm::style::Color::Blue => Color256::BRIGHT_BLUE.into(),
crossterm::style::Color::DarkBlue => Color256::BLUE.into(),
crossterm::style::Color::Magenta => Color256::BRIGHT_MAGENTA.into(),
crossterm::style::Color::DarkMagenta => Color256::MAGENTA.into(),
crossterm::style::Color::Cyan => Color256::BRIGHT_CYAN.into(),
crossterm::style::Color::DarkCyan => Color256::CYAN.into(),
crossterm::style::Color::White => Color256::BRIGHT_WHITE.into(),
crossterm::style::Color::Grey => Color256::WHITE.into(),
crossterm::style::Color::Rgb { r, g, b } => RgbColor(r, g, b).into(),
crossterm::style::Color::AnsiValue(index) => Color256(index).into(),
}
}
}
#[cfg(feature = "ratatui")]
#[cfg_attr(docsrs, doc(cfg(feature = "ratatui")))]
impl From<Color> for ratatui_core::style::Color {
fn from(value: Color) -> ratatui_core::style::Color {
match value {
Color::Default => ratatui_core::style::Color::Reset,
Color::Color256(c) => c.into(),
Color::Rgb(c) => c.into(),
}
}
}
#[cfg(feature = "ratatui")]
#[cfg_attr(docsrs, doc(cfg(feature = "ratatui")))]
impl From<ratatui_core::style::Color> for Color {
fn from(value: ratatui_core::style::Color) -> Color {
match value {
ratatui_core::style::Color::Reset => Color::Default,
ratatui_core::style::Color::Black => Color256::BLACK.into(),
ratatui_core::style::Color::Red => Color256::RED.into(),
ratatui_core::style::Color::Green => Color256::GREEN.into(),
ratatui_core::style::Color::Yellow => Color256::YELLOW.into(),
ratatui_core::style::Color::Blue => Color256::BLUE.into(),
ratatui_core::style::Color::Magenta => Color256::MAGENTA.into(),
ratatui_core::style::Color::Cyan => Color256::CYAN.into(),
ratatui_core::style::Color::Gray => Color256::WHITE.into(),
ratatui_core::style::Color::DarkGray => Color256::BRIGHT_BLACK.into(),
ratatui_core::style::Color::LightRed => Color256::BRIGHT_RED.into(),
ratatui_core::style::Color::LightGreen => Color256::BRIGHT_GREEN.into(),
ratatui_core::style::Color::LightYellow => Color256::BRIGHT_YELLOW.into(),
ratatui_core::style::Color::LightBlue => Color256::BRIGHT_BLUE.into(),
ratatui_core::style::Color::LightMagenta => Color256::BRIGHT_MAGENTA.into(),
ratatui_core::style::Color::LightCyan => Color256::BRIGHT_CYAN.into(),
ratatui_core::style::Color::White => Color256::BRIGHT_WHITE.into(),
ratatui_core::style::Color::Rgb(r, g, b) => RgbColor(r, g, b).into(),
ratatui_core::style::Color::Indexed(index) => Color256(index).into(),
}
}
}
impl fmt::Display for Color {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Color::Default => write!(f, "default"),
Color::Color256(c) => write!(f, "{c}"),
Color::Rgb(c) => write!(f, "{c}"),
}
}
}
impl std::str::FromStr for Color {
type Err = ParseColorError;
fn from_str(s: &str) -> Result<Color, ParseColorError> {
if s.eq_ignore_ascii_case("default") {
Ok(Color::Default)
} else {
s.parse::<Color256>()
.map(Color::from)
.or_else(|_| s.parse::<RgbColor>().map(Color::from))
}
}
}
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
impl serde::Serialize for Color {
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 Color {
fn deserialize<D: serde::de::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
struct Visitor;
impl serde::de::Visitor<'_> for Visitor {
type Value = Color;
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(r##"a color word or a string of the form "color(INT)", "rgb(INT,INT,INT)", or "#xxxxxx""##)
}
fn visit_str<E>(self, input: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
input
.parse::<Color>()
.map_err(|_| E::invalid_value(serde::de::Unexpected::Str(input), &self))
}
}
deserializer.deserialize_str(Visitor)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_display_default() {
assert_eq!(Color::Default.to_string(), "default");
}
#[test]
fn test_display_color256() {
assert_eq!(Color::from(118).to_string(), "chartreuse1");
}
#[test]
fn test_display_rgbcolor() {
assert_eq!(Color::from((111, 120, 189)).to_string(), "#6f78bd");
}
#[test]
fn test_parse_default() {
assert_eq!("default".parse::<Color>().unwrap(), Color::Default);
}
#[test]
fn test_parse_color256() {
assert_eq!(
"chartreuse1".parse::<Color>().unwrap(),
Color::Color256(Color256(118))
);
}
#[test]
fn test_parse_rgbcolor() {
assert_eq!(
"#6f78bd".parse::<Color>().unwrap(),
Color::Rgb(RgbColor(111, 120, 189))
);
}
#[test]
fn test_parse_err() {
assert!("mauve".parse::<Color>().is_err());
}
}