Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_tui/src/lib_tui/term/cell.rs

10.5 KiB, 15 runs

created by r1870400018:20803, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1//! The contents of one screen cell: a character, the pen it was drawn with, and how many cells it
2//! claims.
3//!
4//! A [`Cell`] is small on purpose. A screen of 200 columns with ten thousand lines of scrollback is
5//! two million cells, so every byte in the struct is multiplied by two million. The pen is
6//! therefore packed into a copyable value rather than being reference counted or boxed, and the
7//! attribute set is a bit field rather than a collection.
8
9use crate::lib_tui::style::Colour;
10
11use oxedyne_fe2o3_core::prelude::*;
12
13
14/// One of the sixteen colours an ANSI terminal names.
15///
16/// The first eight are the original ANSI colours and the second eight their bright variants, which
17/// SGR reaches either as `90`--`97` or, historically, by pairing bold with a normal colour.
18#[derive(Clone, Copy, Debug, Eq, PartialEq)]
19pub enum NamedColour {
20 Black,
21 Red,
22 Green,
23 Yellow,
24 Blue,
25 Magenta,
26 Cyan,
27 White,
28 BrightBlack,
29 BrightRed,
30 BrightGreen,
31 BrightYellow,
32 BrightBlue,
33 BrightMagenta,
34 BrightCyan,
35 BrightWhite,
36}
37
38impl NamedColour {
39 /// The colour's index in the first sixteen entries of the 256 colour palette.
40 pub fn index(&self) -> u8 {
41 match self {
42 Self::Black => 0,
43 Self::Red => 1,
44 Self::Green => 2,
45 Self::Yellow => 3,
46 Self::Blue => 4,
47 Self::Magenta => 5,
48 Self::Cyan => 6,
49 Self::White => 7,
50 Self::BrightBlack => 8,
51 Self::BrightRed => 9,
52 Self::BrightGreen => 10,
53 Self::BrightYellow => 11,
54 Self::BrightBlue => 12,
55 Self::BrightMagenta => 13,
56 Self::BrightCyan => 14,
57 Self::BrightWhite => 15,
58 }
59 }
60
61 /// The named colour for a palette index below sixteen, or `None` above it.
62 pub fn from_index(i: u8) -> Option<Self> {
63 match i {
64 0 => Some(Self::Black),
65 1 => Some(Self::Red),
66 2 => Some(Self::Green),
67 3 => Some(Self::Yellow),
68 4 => Some(Self::Blue),
69 5 => Some(Self::Magenta),
70 6 => Some(Self::Cyan),
71 7 => Some(Self::White),
72 8 => Some(Self::BrightBlack),
73 9 => Some(Self::BrightRed),
74 10 => Some(Self::BrightGreen),
75 11 => Some(Self::BrightYellow),
76 12 => Some(Self::BrightBlue),
77 13 => Some(Self::BrightMagenta),
78 14 => Some(Self::BrightCyan),
79 15 => Some(Self::BrightWhite),
80 _ => None,
81 }
82 }
83
84 /// The bright variant of a colour, or the colour itself if it is already bright.
85 pub fn brighten(&self) -> Self {
86 match Self::from_index(self.index() | 0x08) {
87 Some(c) => c,
88 None => *self,
89 }
90 }
91}
92
93/// A cell colour, in any of the three forms a terminal can express.
94///
95/// `Default` means the colour the renderer uses when nothing has been selected, which is not the
96/// same as any particular named colour; a foreground default and a background default differ, and
97/// only the renderer knows what they are.
98#[derive(Clone, Copy, Debug, Eq, PartialEq)]
99pub enum TermColour {
100 /// The renderer's own foreground or background colour.
101 Default,
102 /// One of the sixteen named colours.
103 Named(NamedColour),
104 /// An index into the 256 colour palette.
105 Indexed(u8),
106 /// A direct 24 bit colour.
107 Rgb(u8, u8, u8),
108}
109
110impl Default for TermColour {
111 fn default() -> Self {
112 Self::Default
113 }
114}
115
116impl From<TermColour> for Colour {
117 fn from(c: TermColour) -> Self {
118 match c {
119 TermColour::Default => Colour::Reset,
120 TermColour::Named(n) => match n {
121 NamedColour::Black => Colour::Black,
122 NamedColour::Red => Colour::Red,
123 NamedColour::Green => Colour::Green,
124 NamedColour::Yellow => Colour::Yellow,
125 NamedColour::Blue => Colour::Blue,
126 NamedColour::Magenta => Colour::Magenta,
127 NamedColour::Cyan => Colour::Cyan,
128 NamedColour::White => Colour::Gray,
129 NamedColour::BrightBlack => Colour::DarkGray,
130 NamedColour::BrightRed => Colour::LightRed,
131 NamedColour::BrightGreen => Colour::LightGreen,
132 NamedColour::BrightYellow => Colour::LightYellow,
133 NamedColour::BrightBlue => Colour::LightBlue,
134 NamedColour::BrightMagenta => Colour::LightMagenta,
135 NamedColour::BrightCyan => Colour::LightCyan,
136 NamedColour::BrightWhite => Colour::White,
137 },
138 TermColour::Indexed(i) => Colour::Indexed(i),
139 TermColour::Rgb(r, g, b) => Colour::Rgb(r, g, b),
140 }
141 }
142}
143
144/// The graphic attribute bits, packed into one word.
145///
146/// The set is deliberately narrow: these are the attributes a renderer can be expected to honour.
147/// Sequences selecting anything outside it are parsed and discarded rather than stored.
148#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
149pub struct Attrs(u16);
150
151/// Increased intensity.
152pub const ATTR_BOLD: u16 = 1 << 0;
153/// Decreased intensity.
154pub const ATTR_DIM: u16 = 1 << 1;
155/// Italic.
156pub const ATTR_ITALIC: u16 = 1 << 2;
157/// Single underline.
158pub const ATTR_UNDERLINE: u16 = 1 << 3;
159/// Blink.
160pub const ATTR_BLINK: u16 = 1 << 4;
161/// Foreground and background exchanged.
162pub const ATTR_REVERSE: u16 = 1 << 5;
163/// Not drawn at all.
164pub const ATTR_HIDDEN: u16 = 1 << 6;
165/// Struck through.
166pub const ATTR_STRIKE: u16 = 1 << 7;
167
168impl Attrs {
169 /// An empty attribute set.
170 pub fn none() -> Self {
171 Self(0)
172 }
173
174 /// Whether every bit in `bits` is set.
175 pub fn has(&self, bits: u16) -> bool {
176 self.0 & bits == bits
177 }
178
179 /// Sets the given bits.
180 pub fn set(&mut self, bits: u16) {
181 self.0 |= bits;
182 }
183
184 /// Clears the given bits.
185 pub fn clear(&mut self, bits: u16) {
186 self.0 &= !bits;
187 }
188
189 /// The raw bit field, for a renderer that wants to compare two pens cheaply.
190 pub fn bits(&self) -> u16 {
191 self.0
192 }
193
194 /// Whether no attribute at all is set.
195 pub fn is_empty(&self) -> bool {
196 self.0 == 0
197 }
198
199 /// Bold.
200 pub fn bold(&self) -> bool {
201 self.has(ATTR_BOLD)
202 }
203
204 /// Dim.
205 pub fn dim(&self) -> bool {
206 self.has(ATTR_DIM)
207 }
208
209 /// Italic.
210 pub fn italic(&self) -> bool {
211 self.has(ATTR_ITALIC)
212 }
213
214 /// Underlined.
215 pub fn underline(&self) -> bool {
216 self.has(ATTR_UNDERLINE)
217 }
218
219 /// Blinking.
220 pub fn blink(&self) -> bool {
221 self.has(ATTR_BLINK)
222 }
223
224 /// Reversed.
225 pub fn reverse(&self) -> bool {
226 self.has(ATTR_REVERSE)
227 }
228
229 /// Hidden.
230 pub fn hidden(&self) -> bool {
231 self.has(ATTR_HIDDEN)
232 }
233
234 /// Struck through.
235 pub fn strike(&self) -> bool {
236 self.has(ATTR_STRIKE)
237 }
238}
239
240/// The colours and attributes a character is drawn with.
241#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
242pub struct Pen {
243 /// Foreground colour.
244 pub fore: TermColour,
245 /// Background colour.
246 pub back: TermColour,
247 /// Graphic attributes.
248 pub attrs: Attrs,
249}
250
251impl Pen {
252 /// The pen a reset leaves behind: default colours, no attributes.
253 pub fn plain() -> Self {
254 Self::default()
255 }
256
257 /// Whether this is the pen a reset leaves behind.
258 pub fn is_plain(&self) -> bool {
259 *self == Self::default()
260 }
261
262 /// The foreground and background as the renderer should draw them, with reverse video already
263 /// applied so that a caller need not think about it.
264 pub fn resolved(&self) -> (TermColour, TermColour) {
265 if self.attrs.reverse() {
266 (self.back, self.fore)
267 } else {
268 (self.fore, self.back)
269 }
270 }
271}
272
273/// How a cell relates to a character that is wider than one cell.
274#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
275pub enum Wide {
276 /// An ordinary single width cell.
277 #[default]
278 No,
279 /// The left half of a double width character; the character is in this cell.
280 Lead,
281 /// The right half of a double width character; the cell holds no character of its own.
282 Trail,
283 /// A column left over at the end of a row because the double width character that came next
284 /// would not fit in it.
285 ///
286 /// It has to be told apart from a space that the application actually printed there, because a
287 /// rewrap drops the one and keeps the other, and in the grid the two look identical. A renderer
288 /// draws it as a blank in the cell's own pen.
289 Filler,
290}
291
292/// One cell of the grid.
293#[derive(Clone, Copy, Debug, Eq, PartialEq)]
294pub struct Cell {
295 /// The character drawn in this cell. A [`Wide::Trail`] cell holds a space.
296 pub chr: char,
297 /// The colours and attributes.
298 pub pen: Pen,
299 /// Whether the cell is half of a double width character.
300 pub wide: Wide,
301}
302
303impl Default for Cell {
304 fn default() -> Self {
305 Self {
306 chr: ' ',
307 pen: Pen::default(),
308 wide: Wide::No,
309 }
310 }
311}
312
313impl Cell {
314 /// A blank cell drawn with the given pen.
315 ///
316 /// Erasure uses the current pen rather than the default one, because that is what a terminal
317 /// does: after selecting a background colour, an erase paints that colour.
318 pub fn blank(pen: Pen) -> Self {
319 Self {
320 chr: ' ',
321 pen,
322 wide: Wide::No,
323 }
324 }
325
326 /// A column left over at the end of a row because a double width character would not fit in it.
327 pub fn filler(pen: Pen) -> Self {
328 Self {
329 chr: ' ',
330 pen,
331 wide: Wide::Filler,
332 }
333 }
334
335 /// Whether the cell holds nothing but a space in the default pen.
336 ///
337 /// A leftover column counts as blank, since it holds no text of anybody's and a rewrap is going
338 /// to drop it in any case.
339 pub fn is_blank(&self) -> bool {
340 match self.wide {
341 Wide::No | Wide::Filler => self.chr == ' ' && self.pen.is_plain(),
342 Wide::Lead | Wide::Trail => false,
343 }
344 }
345}
346
347/// A run of adjacent cells sharing one pen, which is the unit a renderer most cheaply emits.
348#[derive(Clone, Debug)]
349pub struct Run {
350 /// Column at which the run starts.
351 pub col: usize,
352 /// The shared pen.
353 pub pen: Pen,
354 /// The text of the run, with the right hand halves of wide characters omitted.
355 pub text: String,
356 /// The number of cells the run covers, which exceeds the character count when it holds wide
357 /// characters.
358 pub cells: usize,
359}
360
361/// Splits a row of cells into runs of constant pen.
362///
363/// Trailing blank cells in the default pen are dropped, since a renderer that has cleared its
364/// background has nothing to draw for them.
365pub fn runs(row: &[Cell]) -> Outcome<Vec<Run>> {
366 let mut end = row.len();
367 while end > 0 && row[end - 1].is_blank() {
368 end -= 1;
369 }
370 let mut out: Vec<Run> = Vec::new();
371 let mut col = 0;
372 while col < end {
373 let cell = row[col];
374 if cell.wide == Wide::Trail || cell.wide == Wide::Filler {
375 // A trailing half with no lead before it, which can only arise from a resize that
376 // cut the character in two, or a column left over at the end of a row. Either way
377 // there is nothing to draw but a space.
378 match out.last_mut() {
379 Some(run) if run.pen == cell.pen => {
380 run.text.push(' ');
381 run.cells += 1;
382 }
383 _ => out.push(Run {
384 col,
385 pen: cell.pen,
386 text: fmt!(" "),
387 cells: 1,
388 }),
389 }
390 col += 1;
391 continue;
392 }
393 let step = if cell.wide == Wide::Lead { 2 } else { 1 };
394 match out.last_mut() {
395 Some(run) if run.pen == cell.pen => {
396 run.text.push(cell.chr);
397 run.cells += step;
398 }
399 _ => out.push(Run {
400 col,
401 pen: cell.pen,
402 text: cell.chr.to_string(),
403 cells: step,
404 }),
405 }
406 col += step;
407 }
408 Ok(out)
409}