1use super::ParseColorError;
2use crate::color::Color;
3use crate::style::Style;
4use crate::util::strip_nocase_prefix;
5use std::fmt;
6
7#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
28pub struct RgbColor(
29 pub u8,
31 pub u8,
33 pub u8,
35);
36
37impl RgbColor {
38 pub fn red(self) -> u8 {
40 self.0
41 }
42
43 pub fn green(self) -> u8 {
45 self.1
46 }
47
48 pub fn blue(self) -> u8 {
50 self.2
51 }
52
53 pub fn as_foreground(self) -> Style {
55 Style::new().foreground(Some(self.into()))
56 }
57
58 pub fn as_background(self) -> Style {
60 Style::new().background(Some(self.into()))
61 }
62
63 pub fn on<C: Into<Color>>(self, bg: C) -> Style {
66 Style::new()
67 .foreground(Some(self.into()))
68 .background(Some(bg.into()))
69 }
70}
71
72impl From<(u8, u8, u8)> for RgbColor {
73 fn from(value: (u8, u8, u8)) -> RgbColor {
74 RgbColor(value.0, value.1, value.2)
75 }
76}
77
78impl From<RgbColor> for (u8, u8, u8) {
79 fn from(value: RgbColor) -> (u8, u8, u8) {
80 (value.0, value.1, value.2)
81 }
82}
83
84#[cfg(feature = "anstyle")]
85#[cfg_attr(docsrs, doc(cfg(feature = "anstyle")))]
86impl From<RgbColor> for anstyle::RgbColor {
87 fn from(value: RgbColor) -> anstyle::RgbColor {
89 anstyle::RgbColor(value.0, value.1, value.2)
90 }
91}
92
93#[cfg(feature = "anstyle")]
94#[cfg_attr(docsrs, doc(cfg(feature = "anstyle")))]
95impl From<anstyle::RgbColor> for RgbColor {
96 fn from(value: anstyle::RgbColor) -> RgbColor {
98 RgbColor(value.0, value.1, value.2)
99 }
100}
101
102#[cfg(feature = "crossterm")]
103#[cfg_attr(docsrs, doc(cfg(feature = "crossterm")))]
104impl From<RgbColor> for crossterm::style::Color {
105 fn from(value: RgbColor) -> crossterm::style::Color {
107 crossterm::style::Color::Rgb {
108 r: value.0,
109 g: value.1,
110 b: value.2,
111 }
112 }
113}
114
115#[cfg(feature = "ratatui")]
116#[cfg_attr(docsrs, doc(cfg(feature = "ratatui")))]
117impl From<RgbColor> for ratatui::style::Color {
118 fn from(value: RgbColor) -> ratatui::style::Color {
120 ratatui::style::Color::Rgb(value.0, value.1, value.2)
121 }
122}
123
124impl fmt::Display for RgbColor {
125 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126 write!(f, "#{:02x}{:02x}{:02x}", self.0, self.1, self.2)
127 }
128}
129
130impl std::str::FromStr for RgbColor {
131 type Err = ParseColorError;
132
133 fn from_str(s: &str) -> Result<RgbColor, ParseColorError> {
134 if let Some(hex) = s
135 .strip_prefix('#')
136 .filter(|s| s.chars().all(|c| c.is_ascii_hexdigit()) && s.len() == 6)
137 {
138 let red = u8::from_str_radix(&hex[..2], 16).expect("should be valid hex string");
139 let green = u8::from_str_radix(&hex[2..4], 16).expect("should be valid hex string");
140 let blue = u8::from_str_radix(&hex[4..], 16).expect("should be valid hex string");
141 Ok(RgbColor(red, green, blue))
142 } else if let Some(dec) = strip_nocase_prefix(s, "rgb(").and_then(|s| s.strip_suffix(')')) {
143 let mut rgb = dec.split(',').map(str::parse::<u8>);
144 let red = rgb.next();
145 let green = rgb.next();
146 let blue = rgb.next();
147 let rest = rgb.next();
148 if let (Some(Ok(red)), Some(Ok(green)), Some(Ok(blue)), None) = (red, green, blue, rest)
149 {
150 Ok(RgbColor(red, green, blue))
151 } else {
152 Err(ParseColorError(s.to_owned()))
153 }
154 } else {
155 Err(ParseColorError(s.to_owned()))
156 }
157 }
158}
159
160#[cfg(feature = "serde")]
161#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
162impl serde::Serialize for RgbColor {
163 fn serialize<S: serde::ser::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
164 serializer.collect_str(self)
165 }
166}
167
168#[cfg(feature = "serde")]
169#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
170impl<'de> serde::Deserialize<'de> for RgbColor {
171 fn deserialize<D: serde::de::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
172 struct Visitor;
173
174 impl serde::de::Visitor<'_> for Visitor {
175 type Value = RgbColor;
176
177 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
178 f.write_str(r##"a string of the form "rgb(INT,INT,INT)" or "#xxxxxx""##)
179 }
180
181 fn visit_str<E>(self, input: &str) -> Result<Self::Value, E>
182 where
183 E: serde::de::Error,
184 {
185 input
186 .parse::<RgbColor>()
187 .map_err(|_| E::invalid_value(serde::de::Unexpected::Str(input), &self))
188 }
189 }
190
191 deserializer.deserialize_str(Visitor)
192 }
193}
194
195#[cfg(test)]
196mod tests {
197 use super::*;
198 use rstest::rstest;
199
200 #[test]
201 fn test_display() {
202 assert_eq!(RgbColor(0x7F, 0xFF, 0x00).to_string(), "#7fff00");
203 }
204
205 #[rstest]
206 #[case("#7fff00", RgbColor(0x7F, 0xFF, 0x00))]
207 #[case("#7FFF00", RgbColor(0x7F, 0xFF, 0x00))]
208 #[case("rgb(78,126,70)", RgbColor(78, 126, 70))]
209 #[case("RGB(78,126,70)", RgbColor(78, 126, 70))]
210 fn test_parse(#[case] s: &str, #[case] color: RgbColor) {
211 assert_eq!(s.parse::<RgbColor>().unwrap(), color);
212 }
213
214 #[rstest]
215 #[case("7fff00")]
216 #[case("# 7fff00")]
217 #[case("#000")]
218 #[case("rgb(78, 126, 70)")]
219 #[case("rgb(78,126)")]
220 #[case("rgb(78,126,70,0)")]
221 #[case("rgb(0x7f,0xff,0x00)")]
222 fn test_parse_err(#[case] s: &str) {
223 assert!(s.parse::<RgbColor>().is_err());
224 }
225}