1use super::ParseColorError;
2use crate::color256::Color256;
3use crate::rgbcolor::RgbColor;
4use crate::style::Style;
5use std::fmt;
6
7#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
9pub enum Color {
10 #[default]
12 Default,
13 Color256(Color256),
14 Rgb(RgbColor),
15}
16
17impl Color {
18 pub fn as_foreground(self) -> Style {
20 Style::new().foreground(Some(self))
21 }
22
23 pub fn as_background(self) -> Style {
25 Style::new().background(Some(self))
26 }
27
28 pub fn on<C: Into<Color>>(self, bg: C) -> Style {
31 Style::new()
32 .foreground(Some(self))
33 .background(Some(bg.into()))
34 }
35}
36
37impl From<Color256> for Color {
38 fn from(value: Color256) -> Color {
39 Color::Color256(value)
40 }
41}
42
43impl From<RgbColor> for Color {
44 fn from(value: RgbColor) -> Color {
45 Color::Rgb(value)
46 }
47}
48
49impl From<u8> for Color {
50 fn from(value: u8) -> Color {
51 Color::Color256(Color256::from(value))
52 }
53}
54
55impl From<(u8, u8, u8)> for Color {
56 fn from(value: (u8, u8, u8)) -> Color {
57 Color::Rgb(RgbColor::from(value))
58 }
59}
60
61#[cfg(feature = "anstyle")]
62#[cfg_attr(docsrs, doc(cfg(feature = "anstyle")))]
63impl TryFrom<Color> for anstyle::Color {
64 type Error = crate::ConversionError;
65
66 fn try_from(value: Color) -> Result<anstyle::Color, crate::ConversionError> {
73 match value {
74 Color::Default => Err(crate::ConversionError),
75 Color::Color256(c) => Ok(anstyle::Color::Ansi256(c.into())),
76 Color::Rgb(c) => Ok(anstyle::Color::Rgb(c.into())),
77 }
78 }
79}
80
81#[cfg(feature = "anstyle")]
82#[cfg_attr(docsrs, doc(cfg(feature = "anstyle")))]
83impl From<anstyle::Color> for Color {
84 fn from(value: anstyle::Color) -> Color {
86 match value {
87 anstyle::Color::Ansi(c) => Color::Color256(c.into()),
88 anstyle::Color::Ansi256(c) => Color::Color256(c.into()),
89 anstyle::Color::Rgb(c) => Color::Rgb(c.into()),
90 }
91 }
92}
93
94#[cfg(feature = "crossterm")]
95#[cfg_attr(docsrs, doc(cfg(feature = "crossterm")))]
96impl From<Color> for crossterm::style::Color {
97 fn from(value: Color) -> crossterm::style::Color {
99 match value {
100 Color::Default => crossterm::style::Color::Reset,
101 Color::Color256(c) => c.into(),
102 Color::Rgb(c) => c.into(),
103 }
104 }
105}
106
107#[cfg(feature = "crossterm")]
108#[cfg_attr(docsrs, doc(cfg(feature = "crossterm")))]
109impl From<crossterm::style::Color> for Color {
110 fn from(value: crossterm::style::Color) -> Color {
112 match value {
113 crossterm::style::Color::Reset => Color::Default,
114 crossterm::style::Color::Black => Color256::BRIGHT_BLACK.into(),
115 crossterm::style::Color::DarkGrey => Color256::BLACK.into(),
116 crossterm::style::Color::Red => Color256::BRIGHT_RED.into(),
117 crossterm::style::Color::DarkRed => Color256::RED.into(),
118 crossterm::style::Color::Green => Color256::BRIGHT_GREEN.into(),
119 crossterm::style::Color::DarkGreen => Color256::GREEN.into(),
120 crossterm::style::Color::Yellow => Color256::BRIGHT_YELLOW.into(),
121 crossterm::style::Color::DarkYellow => Color256::YELLOW.into(),
122 crossterm::style::Color::Blue => Color256::BRIGHT_BLUE.into(),
123 crossterm::style::Color::DarkBlue => Color256::BLUE.into(),
124 crossterm::style::Color::Magenta => Color256::BRIGHT_MAGENTA.into(),
125 crossterm::style::Color::DarkMagenta => Color256::MAGENTA.into(),
126 crossterm::style::Color::Cyan => Color256::BRIGHT_CYAN.into(),
127 crossterm::style::Color::DarkCyan => Color256::CYAN.into(),
128 crossterm::style::Color::White => Color256::BRIGHT_WHITE.into(),
129 crossterm::style::Color::Grey => Color256::WHITE.into(),
130 crossterm::style::Color::Rgb { r, g, b } => RgbColor(r, g, b).into(),
131 crossterm::style::Color::AnsiValue(index) => Color256(index).into(),
132 }
133 }
134}
135
136#[cfg(feature = "ratatui")]
137#[cfg_attr(docsrs, doc(cfg(feature = "ratatui")))]
138impl From<Color> for ratatui::style::Color {
139 fn from(value: Color) -> ratatui::style::Color {
141 match value {
142 Color::Default => ratatui::style::Color::Reset,
143 Color::Color256(c) => c.into(),
144 Color::Rgb(c) => c.into(),
145 }
146 }
147}
148
149#[cfg(feature = "ratatui")]
150#[cfg_attr(docsrs, doc(cfg(feature = "ratatui")))]
151impl From<ratatui::style::Color> for Color {
152 fn from(value: ratatui::style::Color) -> Color {
154 match value {
155 ratatui::style::Color::Reset => Color::Default,
156 ratatui::style::Color::Black => Color256::BLACK.into(),
157 ratatui::style::Color::Red => Color256::RED.into(),
158 ratatui::style::Color::Green => Color256::GREEN.into(),
159 ratatui::style::Color::Yellow => Color256::YELLOW.into(),
160 ratatui::style::Color::Blue => Color256::BLUE.into(),
161 ratatui::style::Color::Magenta => Color256::MAGENTA.into(),
162 ratatui::style::Color::Cyan => Color256::CYAN.into(),
163 ratatui::style::Color::Gray => Color256::WHITE.into(),
164 ratatui::style::Color::DarkGray => Color256::BRIGHT_BLACK.into(),
165 ratatui::style::Color::LightRed => Color256::BRIGHT_RED.into(),
166 ratatui::style::Color::LightGreen => Color256::BRIGHT_GREEN.into(),
167 ratatui::style::Color::LightYellow => Color256::BRIGHT_YELLOW.into(),
168 ratatui::style::Color::LightBlue => Color256::BRIGHT_BLUE.into(),
169 ratatui::style::Color::LightMagenta => Color256::BRIGHT_MAGENTA.into(),
170 ratatui::style::Color::LightCyan => Color256::BRIGHT_CYAN.into(),
171 ratatui::style::Color::White => Color256::BRIGHT_WHITE.into(),
172 ratatui::style::Color::Rgb(r, g, b) => RgbColor(r, g, b).into(),
173 ratatui::style::Color::Indexed(index) => Color256(index).into(),
174 }
175 }
176}
177
178impl fmt::Display for Color {
179 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
180 match self {
181 Color::Default => write!(f, "default"),
182 Color::Color256(c) => write!(f, "{c}"),
183 Color::Rgb(c) => write!(f, "{c}"),
184 }
185 }
186}
187
188impl std::str::FromStr for Color {
189 type Err = ParseColorError;
190
191 fn from_str(s: &str) -> Result<Color, ParseColorError> {
192 if s.eq_ignore_ascii_case("default") {
193 Ok(Color::Default)
194 } else {
195 s.parse::<Color256>()
196 .map(Color::from)
197 .or_else(|_| s.parse::<RgbColor>().map(Color::from))
198 }
199 }
200}
201
202#[cfg(feature = "serde")]
203#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
204impl serde::Serialize for Color {
205 fn serialize<S: serde::ser::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
206 serializer.collect_str(self)
207 }
208}
209
210#[cfg(feature = "serde")]
211#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
212impl<'de> serde::Deserialize<'de> for Color {
213 fn deserialize<D: serde::de::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
214 struct Visitor;
215
216 impl serde::de::Visitor<'_> for Visitor {
217 type Value = Color;
218
219 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
220 f.write_str(r##"a color word or a string of the form "color(INT)", "rgb(INT,INT,INT)", or "#xxxxxx""##)
221 }
222
223 fn visit_str<E>(self, input: &str) -> Result<Self::Value, E>
224 where
225 E: serde::de::Error,
226 {
227 input
228 .parse::<Color>()
229 .map_err(|_| E::invalid_value(serde::de::Unexpected::Str(input), &self))
230 }
231 }
232
233 deserializer.deserialize_str(Visitor)
234 }
235}
236
237#[cfg(test)]
238mod tests {
239 use super::*;
240
241 #[test]
242 fn test_display_default() {
243 assert_eq!(Color::Default.to_string(), "default");
244 }
245
246 #[test]
247 fn test_display_color256() {
248 assert_eq!(Color::from(118).to_string(), "chartreuse1");
249 }
250
251 #[test]
252 fn test_display_rgbcolor() {
253 assert_eq!(Color::from((111, 120, 189)).to_string(), "#6f78bd");
254 }
255
256 #[test]
257 fn test_parse_default() {
258 assert_eq!("default".parse::<Color>().unwrap(), Color::Default);
259 }
260
261 #[test]
262 fn test_parse_color256() {
263 assert_eq!(
264 "chartreuse1".parse::<Color>().unwrap(),
265 Color::Color256(Color256(118))
266 );
267 }
268
269 #[test]
270 fn test_parse_rgbcolor() {
271 assert_eq!(
272 "#6f78bd".parse::<Color>().unwrap(),
273 Color::Rgb(RgbColor(111, 120, 189))
274 );
275 }
276
277 #[test]
278 fn test_parse_err() {
279 assert!("mauve".parse::<Color>().is_err());
280 }
281}