use std::ops::BitOr;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(transparent)]
pub struct CanvasColor(u8);
impl CanvasColor {
pub const NORMAL: Self = Self(0);
pub const BLUE: Self = Self(1);
pub const RED: Self = Self(2);
pub const MAGENTA: Self = Self(3);
pub const GREEN: Self = Self(4);
pub const CYAN: Self = Self(5);
pub const YELLOW: Self = Self(6);
pub const WHITE: Self = Self(7);
#[must_use]
pub const fn new(value: u8) -> Option<Self> {
if value <= Self::WHITE.0 {
Some(Self(value))
} else {
None
}
}
#[must_use]
pub const fn as_u8(self) -> u8 {
self.0
}
}
impl Default for CanvasColor {
fn default() -> Self {
Self::NORMAL
}
}
impl BitOr for CanvasColor {
type Output = Self;
fn bitor(self, rhs: Self) -> Self::Output {
Self((self.0 | rhs.0) & Self::WHITE.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum TermColor {
Named(NamedColor),
Ansi256(u8),
Rgb(u8, u8, u8),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum NamedColor {
Black,
Red,
Green,
Yellow,
Blue,
Magenta,
Cyan,
White,
LightBlack,
Gray,
LightRed,
LightGreen,
LightYellow,
LightBlue,
LightMagenta,
LightCyan,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[non_exhaustive]
pub enum ColorMode {
#[default]
Auto,
Always,
Never,
}
#[must_use]
pub(crate) fn canvas_color_from_term(color: TermColor) -> CanvasColor {
match color {
TermColor::Named(NamedColor::Blue) => CanvasColor::BLUE,
TermColor::Named(NamedColor::Red) => CanvasColor::RED,
TermColor::Named(NamedColor::Magenta) => CanvasColor::MAGENTA,
TermColor::Named(NamedColor::Green) => CanvasColor::GREEN,
TermColor::Named(NamedColor::Cyan) => CanvasColor::CYAN,
TermColor::Named(NamedColor::Yellow) => CanvasColor::YELLOW,
TermColor::Named(NamedColor::White) => CanvasColor::WHITE,
_ => CanvasColor::NORMAL,
}
}
pub const AUTO_SERIES_COLORS: [NamedColor; 6] = [
NamedColor::Green,
NamedColor::Blue,
NamedColor::Red,
NamedColor::Magenta,
NamedColor::Yellow,
NamedColor::Cyan,
];
#[cfg(test)]
mod tests {
use super::{AUTO_SERIES_COLORS, CanvasColor, NamedColor};
#[test]
fn canvas_color_additive_blending_matches_bitwise_or() {
for lhs in 0_u8..=7 {
for rhs in 0_u8..=7 {
let left = CanvasColor::new(lhs);
let right = CanvasColor::new(rhs);
assert!(left.is_some() && right.is_some(), "lhs={lhs} rhs={rhs}");
if let (Some(left), Some(right)) = (left, right) {
let blended = left | right;
assert_eq!(blended.as_u8(), lhs | rhs, "lhs={lhs} rhs={rhs}");
}
}
}
}
#[test]
fn canvas_color_constants_match_reference_indices() {
assert_eq!(CanvasColor::NORMAL.as_u8(), 0);
assert_eq!(CanvasColor::BLUE.as_u8(), 1);
assert_eq!(CanvasColor::RED.as_u8(), 2);
assert_eq!(CanvasColor::MAGENTA.as_u8(), 3);
assert_eq!(CanvasColor::GREEN.as_u8(), 4);
assert_eq!(CanvasColor::CYAN.as_u8(), 5);
assert_eq!(CanvasColor::YELLOW.as_u8(), 6);
assert_eq!(CanvasColor::WHITE.as_u8(), 7);
}
#[test]
fn canvas_color_new_rejects_out_of_range_values() {
assert_eq!(CanvasColor::new(8), None);
assert_eq!(CanvasColor::new(u8::MAX), None);
}
#[test]
fn canvas_color_semantic_blending_examples() {
assert_eq!(CanvasColor::BLUE | CanvasColor::RED, CanvasColor::MAGENTA);
assert_eq!(CanvasColor::NORMAL | CanvasColor::GREEN, CanvasColor::GREEN);
}
#[test]
fn auto_series_colors_match_reference_cycle() {
assert_eq!(
AUTO_SERIES_COLORS,
[
NamedColor::Green,
NamedColor::Blue,
NamedColor::Red,
NamedColor::Magenta,
NamedColor::Yellow,
NamedColor::Cyan,
]
);
}
}