1use core::ops::{BitOr, BitOrAssign};
5
6#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
10pub struct ThemeSlot(pub u8);
11
12impl ThemeSlot {
13 pub const FG: Self = Self(0);
15 pub const BG: Self = Self(1);
17 pub const RECEDE_FG: Self = Self(2);
19 pub const RECEDE_BG: Self = Self(3);
21 pub const ACCENT: Self = Self(4);
23}
24
25#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
35pub enum Colour {
36 #[default]
37 Default,
38 Rgb(u8, u8, u8),
39 Indexed(u8),
40 Theme(ThemeSlot),
41}
42
43impl Colour {
44 pub const FG: Self = Self::Theme(ThemeSlot::FG);
45 pub const BG: Self = Self::Theme(ThemeSlot::BG);
46 pub const RECEDE_FG: Self = Self::Theme(ThemeSlot::RECEDE_FG);
47 pub const RECEDE_BG: Self = Self::Theme(ThemeSlot::RECEDE_BG);
48 pub const ACCENT: Self = Self::Theme(ThemeSlot::ACCENT);
49
50 pub const fn hex(rgb: u32) -> Self {
52 Self::Rgb((rgb >> 16) as u8, (rgb >> 8) as u8, rgb as u8)
53 }
54
55 pub const fn encode(self) -> u32 {
57 match self {
58 Self::Default => 0,
59 Self::Rgb(r, g, b) => 0x0100_0000 | ((r as u32) << 16) | ((g as u32) << 8) | b as u32,
60 Self::Indexed(index) => 0x0200_0000 | index as u32,
61 Self::Theme(slot) => 0x0300_0000 | slot.0 as u32,
62 }
63 }
64
65 pub const fn decode(word: u32) -> Self {
69 match word >> 24 {
70 1 => Self::Rgb((word >> 16) as u8, (word >> 8) as u8, word as u8),
71 2 => Self::Indexed(word as u8),
72 3 => Self::Theme(ThemeSlot(word as u8)),
73 _ => Self::Default,
74 }
75 }
76}
77
78pub const fn recede(rgb: u32, toward: u32, percent: u16) -> u32 {
82 let percent = if percent > 100 { 100 } else { percent } as u32;
83 blend_channel(rgb, toward, percent, 16)
84 | blend_channel(rgb, toward, percent, 8)
85 | blend_channel(rgb, toward, percent, 0)
86}
87
88const fn blend_channel(rgb: u32, toward: u32, percent: u32, shift: u32) -> u32 {
89 let from = (rgb >> shift) & 0xff;
90 let to = (toward >> shift) & 0xff;
91 ((from * (100 - percent) + to * percent) / 100) << shift
92}
93
94#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
96pub struct Attrs(pub u16);
97
98impl Attrs {
99 pub const NONE: Self = Self(0);
100 pub const BOLD: Self = Self(1);
101 pub const DIM: Self = Self(1 << 1);
102 pub const ITALIC: Self = Self(1 << 2);
103 pub const UNDERLINE: Self = Self(1 << 3);
104 pub const REVERSE: Self = Self(1 << 4);
105 pub const STRIKETHROUGH: Self = Self(1 << 5);
106
107 pub const fn contains(self, other: Self) -> bool {
108 self.0 & other.0 == other.0
109 }
110}
111
112impl BitOr for Attrs {
113 type Output = Self;
114
115 fn bitor(self, rhs: Self) -> Self {
116 Self(self.0 | rhs.0)
117 }
118}
119
120impl BitOrAssign for Attrs {
121 fn bitor_assign(&mut self, rhs: Self) {
122 self.0 |= rhs.0;
123 }
124}
125
126#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
128pub struct Style {
129 pub fg: Colour,
130 pub bg: Colour,
131 pub attrs: Attrs,
132}
133
134impl Style {
135 pub const DEFAULT: Self = Self {
137 fg: Colour::Default,
138 bg: Colour::Default,
139 attrs: Attrs::NONE,
140 };
141
142 pub const fn new() -> Self {
143 Self::DEFAULT
144 }
145
146 pub const fn fg(colour: Colour) -> Self {
148 Self {
149 fg: colour,
150 ..Self::DEFAULT
151 }
152 }
153
154 pub const fn with_fg(mut self, colour: Colour) -> Self {
155 self.fg = colour;
156 self
157 }
158
159 pub const fn with_bg(mut self, colour: Colour) -> Self {
160 self.bg = colour;
161 self
162 }
163
164 pub const fn with_attrs(mut self, attrs: Attrs) -> Self {
165 self.attrs = Attrs(self.attrs.0 | attrs.0);
166 self
167 }
168
169 pub const fn bold(self) -> Self {
170 self.with_attrs(Attrs::BOLD)
171 }
172
173 pub const fn dim(self) -> Self {
174 self.with_attrs(Attrs::DIM)
175 }
176
177 pub const fn italic(self) -> Self {
178 self.with_attrs(Attrs::ITALIC)
179 }
180
181 pub const fn underline(self) -> Self {
182 self.with_attrs(Attrs::UNDERLINE)
183 }
184}
185
186#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
189#[repr(C)]
190pub struct Rgba {
191 pub r: u8,
192 pub g: u8,
193 pub b: u8,
194 pub a: u8,
195}
196
197impl Rgba {
198 pub const TRANSPARENT: Self = Self::new(0, 0, 0, 0);
199 pub const BLACK: Self = Self::rgb(0, 0, 0);
200 pub const WHITE: Self = Self::rgb(255, 255, 255);
201
202 pub const fn new(r: u8, g: u8, b: u8, a: u8) -> Self {
203 Self { r, g, b, a }
204 }
205
206 pub const fn rgb(r: u8, g: u8, b: u8) -> Self {
208 Self::new(r, g, b, 255)
209 }
210
211 pub const fn hex(rgb: u32) -> Self {
213 Self::rgb((rgb >> 16) as u8, (rgb >> 8) as u8, rgb as u8)
214 }
215
216 pub const fn with_alpha(mut self, a: u8) -> Self {
217 self.a = a;
218 self
219 }
220
221 pub const fn to_word(self) -> u32 {
223 u32::from_le_bytes([self.r, self.g, self.b, self.a])
224 }
225
226 pub const fn from_word(word: u32) -> Self {
227 let [r, g, b, a] = word.to_le_bytes();
228 Self { r, g, b, a }
229 }
230
231 pub fn over(self, below: Self, coverage: u8) -> Self {
234 let alpha = u32::from(self.a) * u32::from(coverage) / 255;
235 if alpha == 255 {
236 return self;
237 }
238 if alpha == 0 {
239 return below;
240 }
241 let below_alpha = u32::from(below.a) * (255 - alpha) / 255;
242 let out_alpha = alpha + below_alpha;
243 let channel = |top: u8, bottom: u8| {
244 ((u32::from(top) * alpha + u32::from(bottom) * below_alpha + out_alpha / 2) / out_alpha)
245 as u8
246 };
247 Self {
248 r: channel(self.r, below.r),
249 g: channel(self.g, below.g),
250 b: channel(self.b, below.b),
251 a: out_alpha as u8,
252 }
253 }
254}
255
256#[cfg(test)]
257mod tests {
258 use super::*;
259
260 #[test]
261 fn receding_blends_toward_the_background_and_keeps_the_hue() {
262 assert_eq!(recede(0xff8000, 0x000000, 55), 0x723900);
264 assert_eq!(recede(0x123456, 0xffffff, 0), 0x123456);
265 assert_eq!(recede(0x123456, 0xffffff, 100), 0xffffff);
266 assert_eq!(recede(0x123456, 0xffffff, 250), 0xffffff, "clamped");
267 }
268
269 #[test]
270 fn colours_round_trip_through_their_buffer_words() {
271 let mut colours = alloc::vec![Colour::Default];
272 for value in [0u8, 1, 127, 128, 254, 255] {
273 colours.push(Colour::Indexed(value));
274 colours.push(Colour::Theme(ThemeSlot(value)));
275 colours.push(Colour::Rgb(value, 255 - value, value / 2));
276 }
277 for colour in colours {
278 assert_eq!(Colour::decode(colour.encode()), colour, "{colour:?}");
279 }
280 assert_eq!(Colour::Rgb(0x12, 0x34, 0x56).encode(), 0x0112_3456);
282 assert_eq!(Colour::Indexed(9).encode(), 0x0200_0009);
283 assert_eq!(Colour::ACCENT.encode(), 0x0300_0004);
284 assert_eq!(Colour::hex(0xabcdef), Colour::Rgb(0xab, 0xcd, 0xef));
285 assert_eq!(Colour::decode(0x02ff_ff07), Colour::Indexed(7));
287 assert_eq!(Colour::decode(0x0412_3456), Colour::Default);
288 assert_eq!(Colour::decode(0x00ab_cdef), Colour::Default);
289 for word in [0u32, 0x0100_0000, 0x01ff_ffff, 0x0200_00ff, 0x0300_0004] {
290 assert_eq!(Colour::decode(word).encode(), word);
291 }
292 let pixel = Rgba::new(1, 2, 3, 4);
293 assert_eq!(Rgba::from_word(pixel.to_word()), pixel);
294 assert_eq!(pixel.to_word().to_le_bytes(), [1, 2, 3, 4]);
295 }
296}