1use std::ops::BitOr;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8#[repr(transparent)]
9pub struct CanvasColor(u8);
10
11impl CanvasColor {
12 pub const NORMAL: Self = Self(0);
14 pub const BLUE: Self = Self(1);
16 pub const RED: Self = Self(2);
18 pub const MAGENTA: Self = Self(3);
20 pub const GREEN: Self = Self(4);
22 pub const CYAN: Self = Self(5);
24 pub const YELLOW: Self = Self(6);
26 pub const WHITE: Self = Self(7);
28
29 #[must_use]
31 pub const fn new(value: u8) -> Option<Self> {
32 if value <= Self::WHITE.0 {
33 Some(Self(value))
34 } else {
35 None
36 }
37 }
38
39 #[must_use]
41 pub const fn as_u8(self) -> u8 {
42 self.0
43 }
44}
45
46impl Default for CanvasColor {
47 fn default() -> Self {
48 Self::NORMAL
49 }
50}
51
52impl BitOr for CanvasColor {
53 type Output = Self;
54
55 fn bitor(self, rhs: Self) -> Self::Output {
56 Self((self.0 | rhs.0) & Self::WHITE.0)
57 }
58}
59
60#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
62#[non_exhaustive]
63pub enum TermColor {
64 Named(NamedColor),
66 Ansi256(u8),
68 Rgb(u8, u8, u8),
70}
71
72#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
74#[non_exhaustive]
75pub enum NamedColor {
76 Black,
77 Red,
78 Green,
79 Yellow,
80 Blue,
81 Magenta,
82 Cyan,
83 White,
84 LightBlack,
86 Gray,
88 LightRed,
89 LightGreen,
90 LightYellow,
91 LightBlue,
92 LightMagenta,
93 LightCyan,
94}
95
96#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
98#[non_exhaustive]
99pub enum ColorMode {
100 #[default]
102 Auto,
103 Always,
105 Never,
107}
108
109#[must_use]
119pub(crate) fn canvas_color_from_term(color: TermColor) -> CanvasColor {
120 match color {
121 TermColor::Named(NamedColor::Blue) => CanvasColor::BLUE,
122 TermColor::Named(NamedColor::Red) => CanvasColor::RED,
123 TermColor::Named(NamedColor::Magenta) => CanvasColor::MAGENTA,
124 TermColor::Named(NamedColor::Green) => CanvasColor::GREEN,
125 TermColor::Named(NamedColor::Cyan) => CanvasColor::CYAN,
126 TermColor::Named(NamedColor::Yellow) => CanvasColor::YELLOW,
127 TermColor::Named(NamedColor::White) => CanvasColor::WHITE,
128 _ => CanvasColor::NORMAL,
129 }
130}
131
132pub const AUTO_SERIES_COLORS: [NamedColor; 6] = [
137 NamedColor::Green,
138 NamedColor::Blue,
139 NamedColor::Red,
140 NamedColor::Magenta,
141 NamedColor::Yellow,
142 NamedColor::Cyan,
143];
144
145#[cfg(test)]
146mod tests {
147 use super::{AUTO_SERIES_COLORS, CanvasColor, NamedColor};
148
149 #[test]
150 fn canvas_color_additive_blending_matches_bitwise_or() {
151 for lhs in 0_u8..=7 {
152 for rhs in 0_u8..=7 {
153 let left = CanvasColor::new(lhs);
154 let right = CanvasColor::new(rhs);
155
156 assert!(left.is_some() && right.is_some(), "lhs={lhs} rhs={rhs}");
157
158 if let (Some(left), Some(right)) = (left, right) {
159 let blended = left | right;
160 assert_eq!(blended.as_u8(), lhs | rhs, "lhs={lhs} rhs={rhs}");
161 }
162 }
163 }
164 }
165
166 #[test]
167 fn canvas_color_constants_match_reference_indices() {
168 assert_eq!(CanvasColor::NORMAL.as_u8(), 0);
169 assert_eq!(CanvasColor::BLUE.as_u8(), 1);
170 assert_eq!(CanvasColor::RED.as_u8(), 2);
171 assert_eq!(CanvasColor::MAGENTA.as_u8(), 3);
172 assert_eq!(CanvasColor::GREEN.as_u8(), 4);
173 assert_eq!(CanvasColor::CYAN.as_u8(), 5);
174 assert_eq!(CanvasColor::YELLOW.as_u8(), 6);
175 assert_eq!(CanvasColor::WHITE.as_u8(), 7);
176 }
177
178 #[test]
179 fn canvas_color_new_rejects_out_of_range_values() {
180 assert_eq!(CanvasColor::new(8), None);
181 assert_eq!(CanvasColor::new(u8::MAX), None);
182 }
183
184 #[test]
185 fn canvas_color_semantic_blending_examples() {
186 assert_eq!(CanvasColor::BLUE | CanvasColor::RED, CanvasColor::MAGENTA);
187 assert_eq!(CanvasColor::NORMAL | CanvasColor::GREEN, CanvasColor::GREEN);
188 }
189
190 #[test]
191 fn auto_series_colors_match_reference_cycle() {
192 assert_eq!(
193 AUTO_SERIES_COLORS,
194 [
195 NamedColor::Green,
196 NamedColor::Blue,
197 NamedColor::Red,
198 NamedColor::Magenta,
199 NamedColor::Yellow,
200 NamedColor::Cyan,
201 ]
202 );
203 }
204}