1use crate::{AnsiColor, AnsiColor3, AnsiColor8};
7
8#[doc = crate::_tags!(term color)]
9#[doc = crate::_doc_meta!{location("sys/os/term")}]
11#[repr(u8)]
12#[must_use]
13#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
14pub enum TermColorKind {
15 #[default]
17 Default = 0,
18 Indexed = 1,
20 Rgb = 2,
22 Reserved = 3,
24}
25impl TermColorKind {
26 pub const fn from_u8(value: u8) -> Self {
28 match value {
29 0 => Self::Default,
30 1 => Self::Indexed,
31 2 => Self::Rgb,
32 _ => Self::Reserved,
33 }
34 }
35}
36
37#[doc = crate::_tags!(term color)]
38#[doc = crate::_doc_meta!{location("sys/os/term")}]
40#[repr(u8)]
41#[must_use]
42#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
43pub enum TermColorMode {
44 #[default]
46 Opaque = 0,
47 Blend = 1,
49 Transparent = 2,
51 Contrast = 3,
53}
54impl TermColorMode {
55 pub const fn from_u8(value: u8) -> Self {
57 match value {
58 0 => Self::Opaque,
59 1 => Self::Blend,
60 2 => Self::Transparent,
61 _ => Self::Contrast,
62 }
63 }
64}
65
66crate::bitfield! {
67 #[doc = crate::_tags!(term color bit)]
68 #[doc = crate::_doc_meta!{
70 location("sys/os/term"),
71 test_size_of(TermColor = 4|32),
72 }]
73 #[must_use]
77 pub struct TermColor(u32) {
78 VALUE = 0..=23;
80 KIND = 24..=25;
82 MODE = 26..=27;
84 }
85 impl {
86 pub const DEFAULT: Self = Self::new();
88
89 pub const fn indexed(index: u8) -> Self {
91 Self::new().with_value(index as u32).with_kind(TermColorKind::Indexed as u32)
92 }
93 pub const fn rgb(rgb: [u8; 3]) -> Self {
95 let [r, g, b] = rgb;
96 let value = ((r as u32) << 16) | ((g as u32) << 8) | b as u32;
97 Self::new().with_value(value).with_kind(TermColorKind::Rgb as u32)
98 }
99 pub const fn default_with_mode(mode: TermColorMode) -> Self {
101 Self::new().with_mode(mode as u32)
102 }
103 pub const fn kind(self) -> TermColorKind {
105 TermColorKind::from_u8(self.get_kind() as u8)
106 }
107 pub const fn mode(self) -> TermColorMode {
109 TermColorMode::from_u8(self.get_mode() as u8)
110 }
111 pub const fn with_color_mode(self, mode: TermColorMode) -> Self {
113 self.with_mode(mode as u32)
114 }
115 #[must_use]
117 pub const fn is_default(self) -> bool { matches!(self.kind(), TermColorKind::Default) }
118
119 #[must_use]
121 pub const fn is_indexed(self) -> bool { matches!(self.kind(), TermColorKind::Indexed) }
122
123 #[must_use]
125 pub const fn is_rgb(self) -> bool { matches!(self.kind(), TermColorKind::Rgb) }
126
127 #[must_use]
129 pub const fn is_opaque(self) -> bool { matches!(self.mode(), TermColorMode::Opaque) }
130
131 #[must_use]
133 pub const fn is_transparent(self) -> bool {
134 matches!(self.mode(), TermColorMode::Transparent)
135 }
136 #[must_use]
138 pub const fn index(self) -> Option<u8> {
139 if self.is_indexed() { Some(self.get_value() as u8) } else { None }
140 }
141 #[must_use]
143 pub const fn rgb_components(self) -> Option<[u8; 3]> {
144 if self.is_rgb() {
145 let value = self.get_value();
146 Some([(value >> 16) as u8, (value >> 8) as u8, value as u8])
147 } else {
148 None
149 }
150 }
151 #[must_use]
153 pub const fn is_canonical(self) -> bool {
154 self.bits() & !Self::mask() == 0 && !matches!(self.kind(), TermColorKind::Reserved)
155 }
156 pub const fn canonicalized(self) -> Self { Self::from_bits(self.bits() & Self::mask()) }
158
159 #[must_use]
164 pub const fn from_ansi(color: AnsiColor) -> Option<Self> {
165 match color {
166 AnsiColor::None => None,
167 AnsiColor::Default => Some(Self::DEFAULT),
168 AnsiColor::Dark(color) => Some(Self::indexed(color as u8)),
169 AnsiColor::Bright(color) => Some(Self::indexed(color as u8 + 8)),
170 AnsiColor::Palette(color) => Some(Self::indexed(color.0)),
171 AnsiColor::Rgb(rgb) => Some(Self::rgb(rgb)),
172 }
173 }
174 #[must_use]
178 pub const fn to_ansi(self) -> Option<AnsiColor> {
179 if !self.is_opaque() { return None; }
180 match self.kind() {
181 TermColorKind::Default => Some(AnsiColor::Default),
182 TermColorKind::Indexed => {
183 let index = self.get_value() as u8;
184 match index {
185 0..=7 => Some(AnsiColor::Dark(AnsiColor3::from_u8(index))),
186 8..=15 => {
187 Some(AnsiColor::Bright(AnsiColor3::from_u8(index - 8)))
188 }
189 _ => Some(AnsiColor::Palette(AnsiColor8(index))),
190 }
191 }
192 TermColorKind::Rgb => match self.rgb_components() {
193 Some(rgb) => Some(AnsiColor::Rgb(rgb)),
194 None => None,
195 },
196 TermColorKind::Reserved => None,
197 }
198 }
199 }
200}
201
202#[doc = crate::_tags!(term color)]
203#[doc = crate::_doc_meta!{
205 location("sys/os/term"),
206 test_size_of(TermColors = 8|64),
207}]
208#[must_use]
211#[repr(transparent)]
212#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
213pub struct TermColors {
214 bits: u64,
215}
216#[rustfmt::skip]
217impl TermColors {
218 pub const DEFAULT: Self = Self::new(TermColor::DEFAULT, TermColor::DEFAULT);
220
221 pub const fn new(fg: TermColor, bg: TermColor) -> Self {
223 Self { bits: fg.bits() as u64 | ((bg.bits() as u64) << 32) }
224 }
225 pub const fn from_bits(bits: u64) -> Self { Self { bits } }
227
228 #[must_use]
230 pub const fn bits(self) -> u64 { self.bits }
231
232 pub const fn fg(self) -> TermColor { TermColor::from_bits(self.bits as u32) }
234 pub const fn bg(self) -> TermColor { TermColor::from_bits((self.bits >> 32) as u32) }
236
237 pub const fn with_fg(self, fg: TermColor) -> Self { Self::new(fg, self.bg()) }
239
240 pub const fn with_bg(self, bg: TermColor) -> Self { Self::new(self.fg(), bg) }
242
243 pub const fn set_fg(&mut self, fg: TermColor) { self.bits = Self::new(fg, self.bg()).bits; }
245 pub const fn set_bg(&mut self, bg: TermColor) { self.bits = Self::new(self.fg(), bg).bits; }
247
248 pub const fn swapped(self) -> Self { Self::new(self.bg(), self.fg()) }
250
251 pub const fn swap(&mut self) { self.bits = self.bits.rotate_left(32); }
253
254 #[must_use]
256 pub const fn is_canonical(self) -> bool { self.fg().is_canonical() && self.bg().is_canonical() }
257
258 pub const fn canonicalized(self) -> Self {
260 Self::new(self.fg().canonicalized(), self.bg().canonicalized())
261 }
262}
263
264#[cfg(test)]
265mod tests {
266 use super::*;
267
268 #[test]
269 fn default_representation_is_zero() {
270 assert_eq!(TermColor::DEFAULT.bits(), 0);
271 assert_eq!(TermColors::DEFAULT.bits(), 0);
272 assert!(TermColor::DEFAULT.is_default());
273 assert!(TermColor::DEFAULT.is_opaque());
274 }
275 #[test]
276 fn indexed_roundtrip() {
277 let color = TermColor::indexed(173);
278 assert!(color.is_indexed());
279 assert_eq!(color.index(), Some(173));
280 assert_eq!(color.rgb_components(), None);
281 assert!(color.is_canonical());
282 }
283 #[test]
284 fn rgb_roundtrip() {
285 let color = TermColor::rgb([0x12, 0x34, 0x56]);
286 assert!(color.is_rgb());
287 assert_eq!(color.rgb_components(), Some([0x12, 0x34, 0x56]));
288 assert_eq!(color.get_value(), 0x12_34_56);
289 assert!(color.is_canonical());
290 }
291 #[test]
292 fn composition_mode_preserves_color() {
293 let color = TermColor::rgb([10, 20, 30]).with_color_mode(TermColorMode::Transparent);
294 assert_eq!(color.rgb_components(), Some([10, 20, 30]));
295 assert!(color.is_transparent());
296 }
297 #[test]
298 fn paired_colors() {
299 let fg = TermColor::indexed(7);
300 let bg = TermColor::rgb([1, 2, 3]);
301 let colors = TermColors::new(fg, bg);
302 assert_eq!(colors.fg(), fg);
303 assert_eq!(colors.bg(), bg);
304 assert_eq!(colors.swapped(), TermColors::new(bg, fg));
305 }
306 #[test]
307 fn ansi_roundtrip() {
308 let values = [
309 AnsiColor::Default,
310 AnsiColor::Dark(AnsiColor3::Red),
311 AnsiColor::Bright(AnsiColor3::Blue),
312 AnsiColor::Palette(AnsiColor8(203)),
313 AnsiColor::Rgb([10, 20, 30]),
314 ];
315 for ansi in values {
316 let color = TermColor::from_ansi(ansi).unwrap();
317 assert_eq!(color.to_ansi(), Some(ansi));
318 }
319 assert_eq!(TermColor::from_ansi(AnsiColor::None), None);
320 }
321 #[test]
322 fn reserved_bits_are_not_canonical() {
323 let color = TermColor::from_bits(0xF000_0000);
324 assert!(!color.is_canonical());
325 assert_eq!(color.canonicalized(), TermColor::DEFAULT);
326 }
327}