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 | |
| 9 | use crate::lib_tui::style::Colour; |
| 10 | |
| 11 | use 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)] |
| 19 | pub 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 | |
| 38 | impl 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)] |
| 99 | pub 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 | |
| 110 | impl Default for TermColour { |
| 111 | fn default() -> Self { |
| 112 | Self::Default |
| 113 | } |
| 114 | } |
| 115 | |
| 116 | impl 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)] |
| 149 | pub struct Attrs(u16); |
| 150 | |
| 151 | /// Increased intensity. |
| 152 | pub const ATTR_BOLD: u16 = 1 << 0; |
| 153 | /// Decreased intensity. |
| 154 | pub const ATTR_DIM: u16 = 1 << 1; |
| 155 | /// Italic. |
| 156 | pub const ATTR_ITALIC: u16 = 1 << 2; |
| 157 | /// Single underline. |
| 158 | pub const ATTR_UNDERLINE: u16 = 1 << 3; |
| 159 | /// Blink. |
| 160 | pub const ATTR_BLINK: u16 = 1 << 4; |
| 161 | /// Foreground and background exchanged. |
| 162 | pub const ATTR_REVERSE: u16 = 1 << 5; |
| 163 | /// Not drawn at all. |
| 164 | pub const ATTR_HIDDEN: u16 = 1 << 6; |
| 165 | /// Struck through. |
| 166 | pub const ATTR_STRIKE: u16 = 1 << 7; |
| 167 | |
| 168 | impl 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)] |
| 242 | pub 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 | |
| 251 | impl 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)] |
| 275 | pub 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)] |
| 294 | pub 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 | |
| 303 | impl Default for Cell { |
| 304 | fn default() -> Self { |
| 305 | Self { |
| 306 | chr: ' ', |
| 307 | pen: Pen::default(), |
| 308 | wide: Wide::No, |
| 309 | } |
| 310 | } |
| 311 | } |
| 312 | |
| 313 | impl 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)] |
| 349 | pub 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. |
| 365 | pub 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 | } |