1use anstyle::{AnsiColor, Color, Effects, RgbColor, Style};
7
8pub fn color_from_hex(hex: &str) -> Option<Color> {
10 let hex = hex.trim_start_matches('#');
11 if hex.len() != 6 {
12 return None;
13 }
14
15 let r = u8::from_str_radix(&hex[0..2], 16).ok()?;
16 let g = u8::from_str_radix(&hex[2..4], 16).ok()?;
17 let b = u8::from_str_radix(&hex[4..6], 16).ok()?;
18
19 Some(Color::Rgb(RgbColor(r, g, b)))
20}
21
22pub fn blend_colors(color1: &Color, color2: &Color, ratio: f32) -> Option<Color> {
24 let rgb1 = color_to_rgb(color1)?;
25 let rgb2 = color_to_rgb(color2)?;
26
27 let r = (rgb1.r() as f32 * (1.0 - ratio) + rgb2.r() as f32 * ratio) as u8;
28 let g = (rgb1.g() as f32 * (1.0 - ratio) + rgb2.g() as f32 * ratio) as u8;
29 let b = (rgb1.b() as f32 * (1.0 - ratio) + rgb2.b() as f32 * ratio) as u8;
30
31 Some(Color::Rgb(RgbColor(r, g, b)))
32}
33
34pub fn color_to_rgb(color: &Color) -> Option<RgbColor> {
36 match color {
37 Color::Rgb(rgb) => Some(*rgb),
38 Color::Ansi(ansi_color) => ansi_to_rgb(*ansi_color),
39 Color::Ansi256(ansi256_color) => ansi256_to_rgb(*ansi256_color),
40 }
41}
42
43fn ansi_to_rgb(ansi_color: anstyle::AnsiColor) -> Option<RgbColor> {
45 match ansi_color {
46 anstyle::AnsiColor::Black => Some(RgbColor(0, 0, 0)),
47 anstyle::AnsiColor::Red => Some(RgbColor(170, 0, 0)),
48 anstyle::AnsiColor::Green => Some(RgbColor(0, 170, 0)),
49 anstyle::AnsiColor::Yellow => Some(RgbColor(170, 85, 0)),
50 anstyle::AnsiColor::Blue => Some(RgbColor(0, 0, 170)),
51 anstyle::AnsiColor::Magenta => Some(RgbColor(170, 0, 170)),
52 anstyle::AnsiColor::Cyan => Some(RgbColor(0, 170, 170)),
53 anstyle::AnsiColor::White => Some(RgbColor(170, 170, 170)),
54 anstyle::AnsiColor::BrightBlack => Some(RgbColor(85, 85, 85)),
55 anstyle::AnsiColor::BrightRed => Some(RgbColor(255, 85, 85)),
56 anstyle::AnsiColor::BrightGreen => Some(RgbColor(85, 255, 85)),
57 anstyle::AnsiColor::BrightYellow => Some(RgbColor(255, 255, 85)),
58 anstyle::AnsiColor::BrightBlue => Some(RgbColor(85, 85, 255)),
59 anstyle::AnsiColor::BrightMagenta => Some(RgbColor(255, 85, 255)),
60 anstyle::AnsiColor::BrightCyan => Some(RgbColor(85, 255, 255)),
61 anstyle::AnsiColor::BrightWhite => Some(RgbColor(255, 255, 255)),
62 }
63}
64
65fn ansi256_to_rgb(ansi256_color: anstyle::Ansi256Color) -> Option<RgbColor> {
67 let code = ansi256_color.0;
68 match code {
69 0 => Some(RgbColor(0, 0, 0)),
70 1 => Some(RgbColor(170, 0, 0)),
71 2 => Some(RgbColor(0, 170, 0)),
72 3 => Some(RgbColor(170, 85, 0)),
73 4 => Some(RgbColor(0, 0, 170)),
74 5 => Some(RgbColor(170, 0, 170)),
75 6 => Some(RgbColor(0, 170, 170)),
76 7 => Some(RgbColor(170, 170, 170)),
77 8 => Some(RgbColor(85, 85, 85)),
78 9 => Some(RgbColor(255, 85, 85)),
79 10 => Some(RgbColor(85, 255, 85)),
80 11 => Some(RgbColor(255, 255, 85)),
81 12 => Some(RgbColor(85, 85, 255)),
82 13 => Some(RgbColor(255, 85, 255)),
83 14 => Some(RgbColor(85, 255, 255)),
84 15 => Some(RgbColor(255, 255, 255)),
85 n if (16..=231).contains(&n) => {
86 let adjusted = n - 16;
87 let r = adjusted / 36;
88 let g = (adjusted % 36) / 6;
89 let b = adjusted % 6;
90 let scale = |x: u8| -> u8 { if x == 0 { 0 } else { 55 + x * 40 } };
91 Some(RgbColor(scale(r), scale(g), scale(b)))
92 }
93 n if n >= 232 => {
94 let gray = 8 + (n - 232) * 10;
95 Some(RgbColor(gray, gray, gray))
96 }
97 _ => Some(RgbColor(128, 128, 128)),
98 }
99}
100
101pub fn is_light_color(color: &Color) -> bool {
103 let rgb = color_to_rgb(color);
104 if let Some(RgbColor(r, g, b)) = rgb {
105 let luminance = (0.299 * r as f32 + 0.587 * g as f32 + 0.114 * b as f32) / 255.0;
106 luminance > 0.5
107 } else {
108 false
109 }
110}
111
112pub fn contrasting_color(color: &Color) -> Color {
114 if is_light_color(color) {
115 Color::Ansi(anstyle::AnsiColor::Black)
116 } else {
117 Color::Ansi(anstyle::AnsiColor::White)
118 }
119}
120
121pub fn desaturate_color(color: &Color, amount: f32) -> Option<Color> {
123 let rgb = color_to_rgb(color)?;
124 let r = rgb.r() as f32;
125 let g = rgb.g() as f32;
126 let b = rgb.b() as f32;
127 let gray = 0.299 * r + 0.587 * g + 0.114 * b;
128 let r_new = r * (1.0 - amount) + gray * amount;
129 let g_new = g * (1.0 - amount) + gray * amount;
130 let b_new = b * (1.0 - amount) + gray * amount;
131 Some(Color::Rgb(RgbColor(r_new as u8, g_new as u8, b_new as u8)))
132}
133
134fn styled(text: &str, style: Style) -> String {
135 format!("{}{}{}", style.render(), text, style.render_reset())
136}
137
138pub fn style(text: impl std::fmt::Display) -> StyledString {
140 StyledString {
141 text: text.to_string(),
142 style: Style::new(),
143 }
144}
145
146pub struct StyledString {
147 text: String,
148 style: Style,
149}
150
151impl StyledString {
152 pub fn red(mut self) -> Self {
153 self.style = self.style.fg_color(Some(Color::Ansi(AnsiColor::Red)));
154 self
155 }
156
157 pub fn green(mut self) -> Self {
158 self.style = self.style.fg_color(Some(Color::Ansi(AnsiColor::Green)));
159 self
160 }
161
162 pub fn blue(mut self) -> Self {
163 self.style = self.style.fg_color(Some(Color::Ansi(AnsiColor::Blue)));
164 self
165 }
166
167 pub fn yellow(mut self) -> Self {
168 self.style = self.style.fg_color(Some(Color::Ansi(AnsiColor::Yellow)));
169 self
170 }
171
172 pub fn cyan(mut self) -> Self {
173 self.style = self.style.fg_color(Some(Color::Ansi(AnsiColor::Cyan)));
174 self
175 }
176
177 pub fn magenta(mut self) -> Self {
178 self.style = self.style.fg_color(Some(Color::Ansi(AnsiColor::Magenta)));
179 self
180 }
181
182 pub fn bold(mut self) -> Self {
183 self.style = self.style.effects(self.style.get_effects() | Effects::BOLD);
184 self
185 }
186
187 pub fn dimmed(mut self) -> Self {
188 self.style = self
189 .style
190 .effects(self.style.get_effects() | Effects::DIMMED);
191 self
192 }
193
194 pub fn dim(self) -> Self {
195 self.dimmed()
196 }
197
198 pub fn on_black(mut self) -> Self {
199 self.style = self.style.bg_color(Some(Color::Ansi(AnsiColor::Black)));
200 self
201 }
202}
203
204impl std::fmt::Display for StyledString {
205 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
206 write!(
207 f,
208 "{}{}{}",
209 self.style.render(),
210 self.text,
211 self.style.render_reset()
212 )
213 }
214}
215
216pub fn red(text: &str) -> String {
218 styled(
219 text,
220 Style::new().fg_color(Some(Color::Ansi(AnsiColor::Red))),
221 )
222}
223
224pub fn green(text: &str) -> String {
226 styled(
227 text,
228 Style::new().fg_color(Some(Color::Ansi(AnsiColor::Green))),
229 )
230}
231
232pub fn blue(text: &str) -> String {
234 styled(
235 text,
236 Style::new().fg_color(Some(Color::Ansi(AnsiColor::Blue))),
237 )
238}
239
240pub fn yellow(text: &str) -> String {
242 styled(
243 text,
244 Style::new().fg_color(Some(Color::Ansi(AnsiColor::Yellow))),
245 )
246}
247
248pub fn purple(text: &str) -> String {
250 styled(
251 text,
252 Style::new().fg_color(Some(Color::Ansi(AnsiColor::Magenta))),
253 )
254}
255
256pub fn cyan(text: &str) -> String {
258 styled(
259 text,
260 Style::new().fg_color(Some(Color::Ansi(AnsiColor::Cyan))),
261 )
262}
263
264pub fn white(text: &str) -> String {
266 styled(
267 text,
268 Style::new().fg_color(Some(Color::Ansi(AnsiColor::White))),
269 )
270}
271
272pub fn black(text: &str) -> String {
274 styled(
275 text,
276 Style::new().fg_color(Some(Color::Ansi(AnsiColor::Black))),
277 )
278}
279
280pub fn bold(text: &str) -> String {
282 styled(text, Style::new().effects(Effects::BOLD))
283}
284
285pub fn italic(text: &str) -> String {
287 styled(text, Style::new().effects(Effects::ITALIC))
288}
289
290pub fn underline(text: &str) -> String {
292 styled(text, Style::new().effects(Effects::UNDERLINE))
293}
294
295pub fn dimmed(text: &str) -> String {
297 styled(text, Style::new().effects(Effects::DIMMED))
298}
299
300pub fn blink(text: &str) -> String {
302 styled(text, Style::new().effects(Effects::BLINK))
303}
304
305pub fn reversed(text: &str) -> String {
307 styled(text, Style::new().effects(Effects::INVERT))
308}
309
310pub fn strikethrough(text: &str) -> String {
312 styled(text, Style::new().effects(Effects::STRIKETHROUGH))
313}
314
315pub fn rgb(text: &str, r: u8, g: u8, b: u8) -> String {
317 styled(
318 text,
319 Style::new().fg_color(Some(Color::Rgb(RgbColor(r, g, b)))),
320 )
321}
322
323pub fn custom_style(text: &str, styles: &[&str]) -> String {
325 let mut style = Style::new();
326
327 for style_str in styles {
328 match *style_str {
329 "red" => style = style.fg_color(Some(Color::Ansi(AnsiColor::Red))),
330 "green" => style = style.fg_color(Some(Color::Ansi(AnsiColor::Green))),
331 "blue" => style = style.fg_color(Some(Color::Ansi(AnsiColor::Blue))),
332 "yellow" => style = style.fg_color(Some(Color::Ansi(AnsiColor::Yellow))),
333 "purple" => style = style.fg_color(Some(Color::Ansi(AnsiColor::Magenta))),
334 "cyan" => style = style.fg_color(Some(Color::Ansi(AnsiColor::Cyan))),
335 "white" => style = style.fg_color(Some(Color::Ansi(AnsiColor::White))),
336 "black" => style = style.fg_color(Some(Color::Ansi(AnsiColor::Black))),
337 "bold" => style = style.effects(style.get_effects() | Effects::BOLD),
338 "italic" => style = style.effects(style.get_effects() | Effects::ITALIC),
339 "underline" => style = style.effects(style.get_effects() | Effects::UNDERLINE),
340 "dimmed" => style = style.effects(style.get_effects() | Effects::DIMMED),
341 "blink" => style = style.effects(style.get_effects() | Effects::BLINK),
342 "reversed" => style = style.effects(style.get_effects() | Effects::INVERT),
343 "strikethrough" => style = style.effects(style.get_effects() | Effects::STRIKETHROUGH),
344 _ => {}
345 }
346 }
347
348 styled(text, style)
349}