use std::env;
use super::Color;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum ColorMode {
#[default]
TrueColor,
Color256,
Basic16,
}
impl ColorMode {
pub fn detect() -> Self {
if let Ok(mode) = env::var("PHOTON_COLOR_MODE") {
match mode.as_str() {
| "truecolor" | "24bit" | "rgb" => return ColorMode::TrueColor,
| "256" | "256color" => return ColorMode::Color256,
| "16" | "basic" => return ColorMode::Basic16,
| _ => {},
}
}
if let Ok(ct) = env::var("COLORTERM") {
if ct == "truecolor" || ct == "24bit" {
return ColorMode::TrueColor;
}
}
if let Ok(term) = env::var("TERM") {
if term.contains("256color") {
return ColorMode::Color256;
}
}
ColorMode::TrueColor
}
}
pub fn fg(color: Color, mode: ColorMode) -> String {
match mode {
| ColorMode::TrueColor => format!("\x1b[38;2;{};{};{}m", color.0, color.1, color.2),
| ColorMode::Color256 => format!("\x1b[38;5;{}m", rgb_to_256(color)),
| ColorMode::Basic16 => format!("\x1b[{}m", rgb_to_16_fg(color)),
}
}
pub fn bg(color: Color, mode: ColorMode) -> String {
match mode {
| ColorMode::TrueColor => format!("\x1b[48;2;{};{};{}m", color.0, color.1, color.2),
| ColorMode::Color256 => format!("\x1b[48;5;{}m", rgb_to_256(color)),
| ColorMode::Basic16 => format!("\x1b[{}m", rgb_to_16_bg(color)),
}
}
pub const RESET: &str = "\x1b[0m";
fn rgb_to_256(color: Color) -> u8 {
let Color(r, g, b) = color;
if r == g && g == b {
if r < 8 {
return 16;
}
if r > 248 {
return 231;
}
return 232 + ((r - 8) / 10);
}
let r = closest_cube_level(r);
let g = closest_cube_level(g);
let b = closest_cube_level(b);
16 + 36 * r + 6 * g + b
}
fn closest_cube_level(v: u8) -> u8 {
if v < 48 {
0
} else if v < 115 {
1
} else if v < 155 {
2
} else if v < 195 {
3
} else if v < 235 {
4
} else {
5
}
}
fn rgb_to_16_fg(color: Color) -> u8 {
30 + rgb_to_16_index(color)
}
fn rgb_to_16_bg(color: Color) -> u8 {
40 + rgb_to_16_index(color)
}
fn rgb_to_16_index(color: Color) -> u8 {
let Color(r, g, b) = color;
let intensity = (r as u16 + g as u16 + b as u16) / 3;
let is_bright = intensity > 128;
let idx = if r > 128 && g < 128 && b < 128 {
1 } else if r < 128 && g > 128 && b < 128 {
2 } else if r > 128 && g > 128 && b < 128 {
3 } else if r < 128 && g < 128 && b > 128 {
4 } else if r > 128 && g < 128 && b > 128 {
5 } else if r < 128 && g > 128 && b > 128 {
6 } else {
0 };
if is_bright && idx == 0 {
7 } else if is_bright {
idx + 60 } else {
idx
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn truecolor_fg() {
let s = fg(Color::SUNBEAM_ORANGE, ColorMode::TrueColor);
assert_eq!(s, "\x1b[38;2;250;82;15m");
}
#[test]
fn truecolor_bg() {
let s = bg(Color::WARM_IVORY, ColorMode::TrueColor);
assert_eq!(s, "\x1b[48;2;255;250;237m");
}
#[test]
fn color256_produces_valid_codes() {
let s = fg(Color::SUNBEAM_ORANGE, ColorMode::Color256);
assert!(s.starts_with("\x1b[38;5;"));
assert!(s.ends_with('m'));
}
#[test]
fn basic16_produces_valid_codes() {
let s = fg(Color::WHITE, ColorMode::Basic16);
assert!(s.starts_with("\x1b["));
assert!(s.ends_with('m'));
}
}