1use std::collections::VecDeque;
5
6use crate::palette::{self, Rgb, VGA};
7
8#[derive(Clone, Copy, Debug, PartialEq, Eq)]
12pub enum Color {
13 Pc(u8),
15 Xterm(u8),
17 Rgb(Rgb),
19}
20
21pub mod flags {
22 pub const BOLD: u8 = 1 << 0;
23 pub const BLINK: u8 = 1 << 1;
24 pub const UNDERLINE: u8 = 1 << 2;
25 pub const REVERSE: u8 = 1 << 3;
26 pub const CONCEAL: u8 = 1 << 4;
27 pub const FAINT: u8 = 1 << 5;
28}
29
30pub const DEFAULT_FG: Color = Color::Pc(7);
31pub const DEFAULT_BG: Color = Color::Pc(0);
32
33#[derive(Clone, Copy, Debug, PartialEq, Eq)]
34pub struct Cell {
35 pub ch: u8,
37 pub fg: Color,
38 pub bg: Color,
39 pub flags: u8,
40}
41
42impl Default for Cell {
43 fn default() -> Self {
44 Cell { ch: b' ', fg: DEFAULT_FG, bg: DEFAULT_BG, flags: 0 }
45 }
46}
47
48impl Cell {
49 pub fn new(ch: u8, fg: Color, bg: Color) -> Self {
50 Cell { ch, fg, bg, flags: 0 }
51 }
52
53 pub fn is_blank(&self) -> bool {
54 (self.ch == b' ' || self.ch == 0) && self.bg == DEFAULT_BG && self.flags & flags::REVERSE == 0
55 }
56
57 pub fn resolve(&self, ice: bool) -> (Rgb, Rgb, bool) {
62 self.resolve_with(ice, Look::standard())
63 }
64
65 pub fn resolve_with(&self, ice: bool, look: &Look) -> (Rgb, Rgb, bool) {
67 let bold = self.flags & flags::BOLD != 0 && !look.bright_plain;
68 let blink = self.flags & flags::BLINK != 0;
69 let mut fg = match self.fg {
70 Color::Pc(i) if bold && i < 8 => look.rgb(Color::Pc(i + 8)),
71 c => look.rgb(c),
72 };
73 let mut bg = match self.bg {
74 Color::Pc(i) if blink && ice && i < 8 => look.rgb(Color::Pc(i + 8)),
75 c => look.rgb(c),
76 };
77 if self.flags & flags::REVERSE != 0 {
78 std::mem::swap(&mut fg, &mut bg);
79 }
80 if self.flags & flags::CONCEAL != 0 {
81 fg = bg;
82 }
83 (fg, bg, blink && !ice && !look.blink_steady)
84 }
85}
86
87#[derive(Clone, Debug, PartialEq, Eq)]
90pub struct Look {
91 pub palette: [Rgb; 256],
94 pub fonts: [u8; 4],
96 pub bright_font: bool,
98 pub bright_plain: bool,
100 pub blink_font: bool,
102 pub blink_steady: bool,
104}
105
106impl Default for Look {
107 fn default() -> Look {
108 Look::standard().clone()
109 }
110}
111
112impl Look {
113 pub fn standard() -> &'static Look {
115 static STANDARD: std::sync::LazyLock<Look> = std::sync::LazyLock::new(|| Look {
116 palette: std::array::from_fn(|i| palette::xterm256(i as u8)),
117 fonts: [0; 4],
118 bright_font: false,
119 bright_plain: false,
120 blink_font: false,
121 blink_steady: false,
122 });
123 &STANDARD
124 }
125
126 pub fn rgb(&self, c: Color) -> Rgb {
127 match c {
128 Color::Pc(i) => {
130 let i = i & 15;
131 let ansi = palette::ANSI_TO_VGA.iter().position(|&v| v == i & 7).unwrap_or(0) as u8;
132 self.palette[(ansi + (i & 8)) as usize]
133 }
134 Color::Xterm(i) => self.palette[i as usize],
135 Color::Rgb(rgb) => rgb,
136 }
137 }
138
139 pub fn font_for(&self, cell: &Cell) -> u8 {
141 let bright = self.bright_font && cell.flags & flags::BOLD != 0;
142 let blink = self.blink_font && cell.flags & flags::BLINK != 0;
143 self.fonts[bright as usize | (blink as usize) << 1]
144 }
145}
146
147pub fn color_rgb(c: Color) -> Rgb {
148 match c {
149 Color::Pc(i) => VGA[(i & 15) as usize],
150 Color::Xterm(i) => palette::xterm256(i),
151 Color::Rgb(rgb) => rgb,
152 }
153}
154
155#[derive(Clone, Copy, Debug)]
156struct SavedCursor {
157 x: usize,
158 y: usize,
159 pen: Cell,
160}
161
162pub struct Screen {
163 cols: usize,
164 rows: usize,
165 cells: Vec<Cell>,
166 x: usize,
167 y: usize,
168 pending_wrap: bool,
171 pub pen: Cell,
173 saved: Option<SavedCursor>,
174 scroll_top: usize,
175 scroll_bottom: usize,
176 pub autowrap: bool,
177 pub cursor_visible: bool,
178 pub ice_colors: bool,
179 pub look: Look,
181 pub base_font: u8,
183 pub base_ice: bool,
185 scrollback: VecDeque<Vec<Cell>>,
186 scrollback_limit: usize,
187}
188
189impl Screen {
190 pub fn new(cols: usize, rows: usize, scrollback_limit: usize) -> Self {
191 let cols = cols.max(1);
192 let rows = rows.max(1);
193 Screen {
194 cols,
195 rows,
196 cells: vec![Cell::default(); cols * rows],
197 x: 0,
198 y: 0,
199 pending_wrap: false,
200 pen: Cell::default(),
201 saved: None,
202 scroll_top: 0,
203 scroll_bottom: rows - 1,
204 autowrap: true,
205 cursor_visible: true,
206 ice_colors: false,
207 look: Look::default(),
208 base_font: 0,
209 base_ice: false,
210 scrollback: VecDeque::new(),
211 scrollback_limit,
212 }
213 }
214
215 pub fn cols(&self) -> usize {
216 self.cols
217 }
218
219 pub fn rows(&self) -> usize {
220 self.rows
221 }
222
223 pub fn cursor(&self) -> (usize, usize) {
224 (self.x, self.y)
225 }
226
227 pub fn row(&self, y: usize) -> &[Cell] {
228 &self.cells[y * self.cols..(y + 1) * self.cols]
229 }
230
231 pub fn cell_mut(&mut self, x: usize, y: usize) -> Option<&mut Cell> {
232 if x < self.cols && y < self.rows { Some(&mut self.cells[y * self.cols + x]) } else { None }
233 }
234
235 pub fn scrollback_len(&self) -> usize {
236 self.scrollback.len()
237 }
238
239 pub fn scrollback_line(&self, i: usize) -> &[Cell] {
241 &self.scrollback[i]
242 }
243
244 pub fn clear_scrollback(&mut self) {
245 self.scrollback.clear();
246 }
247
248 pub fn reset(&mut self) {
250 self.pen = Cell::default();
251 self.cells.fill(Cell::default());
252 self.x = 0;
253 self.y = 0;
254 self.pending_wrap = false;
255 self.saved = None;
256 self.scroll_top = 0;
257 self.scroll_bottom = self.rows - 1;
258 self.autowrap = true;
259 self.cursor_visible = true;
260 self.ice_colors = self.base_ice;
261 self.look = Look::default();
262 self.look.fonts[0] = self.base_font;
263 }
264
265 pub fn resize(&mut self, cols: usize, rows: usize) {
266 let cols = cols.max(1);
267 let rows = rows.max(1);
268 if cols == self.cols && rows == self.rows {
269 return;
270 }
271 let mut cells = vec![Cell::default(); cols * rows];
272 for y in 0..rows.min(self.rows) {
273 let n = cols.min(self.cols);
274 cells[y * cols..y * cols + n].copy_from_slice(&self.row(y)[..n]);
275 }
276 self.cells = cells;
277 self.cols = cols;
278 self.rows = rows;
279 self.x = self.x.min(cols - 1);
280 self.y = self.y.min(rows - 1);
281 self.scroll_top = 0;
282 self.scroll_bottom = rows - 1;
283 self.pending_wrap = false;
284 }
285
286 fn blank(&self) -> Cell {
289 Cell { ch: b' ', flags: self.pen.flags & (flags::BLINK | flags::REVERSE), ..self.pen }
290 }
291
292 pub fn put(&mut self, ch: u8) {
294 if self.pending_wrap {
295 self.pending_wrap = false;
296 self.x = 0;
297 self.line_feed();
298 }
299 let idx = self.y * self.cols + self.x;
300 self.cells[idx] = Cell { ch, ..self.pen };
301 if self.x + 1 < self.cols {
302 self.x += 1;
303 } else if self.autowrap {
304 self.pending_wrap = true;
305 }
306 }
307
308 pub fn carriage_return(&mut self) {
309 self.x = 0;
310 self.pending_wrap = false;
311 }
312
313 pub fn line_feed(&mut self) {
315 self.pending_wrap = false;
316 if self.y == self.scroll_bottom {
317 self.scroll_up(1);
318 } else if self.y + 1 < self.rows {
319 self.y += 1;
320 }
321 }
322
323 pub fn reverse_index(&mut self) {
325 self.pending_wrap = false;
326 if self.y == self.scroll_top {
327 self.scroll_down(1);
328 } else if self.y > 0 {
329 self.y -= 1;
330 }
331 }
332
333 pub fn backspace(&mut self) {
334 self.pending_wrap = false;
335 self.x = self.x.saturating_sub(1);
336 }
337
338 pub fn tab(&mut self) {
339 self.pending_wrap = false;
340 self.x = ((self.x / 8 + 1) * 8).min(self.cols - 1);
341 }
342
343 pub fn back_tab(&mut self, n: usize) {
344 for _ in 0..n {
345 self.x = self.x.saturating_sub(1) / 8 * 8;
346 }
347 self.pending_wrap = false;
348 }
349
350 pub fn move_to(&mut self, x: usize, y: usize) {
352 self.x = x.min(self.cols - 1);
353 self.y = y.min(self.rows - 1);
354 self.pending_wrap = false;
355 }
356
357 pub fn move_by(&mut self, dx: isize, dy: isize) {
358 let x = (self.x as isize + dx).max(0) as usize;
359 let y = (self.y as isize + dy).max(0) as usize;
360 self.move_to(x, y);
361 }
362
363 pub fn set_scroll_region(&mut self, top: usize, bottom: usize) {
364 let bottom = bottom.min(self.rows - 1);
365 if top < bottom {
366 self.scroll_top = top;
367 self.scroll_bottom = bottom;
368 } else {
369 self.scroll_top = 0;
370 self.scroll_bottom = self.rows - 1;
371 }
372 self.move_to(0, 0);
373 }
374
375 pub fn save_cursor(&mut self) {
376 self.saved = Some(SavedCursor { x: self.x, y: self.y, pen: self.pen });
377 }
378
379 pub fn restore_cursor(&mut self) {
380 if let Some(s) = self.saved {
381 self.move_to(s.x, s.y);
382 self.pen = s.pen;
383 }
384 }
385
386 fn push_scrollback(&mut self, line: Vec<Cell>) {
387 if self.scrollback_limit == 0 {
388 return;
389 }
390 if self.scrollback.len() >= self.scrollback_limit {
391 self.scrollback.pop_front();
392 }
393 self.scrollback.push_back(line);
394 }
395
396 pub fn scroll_up(&mut self, n: usize) {
399 let (top, bottom) = (self.scroll_top, self.scroll_bottom);
400 let n = n.min(bottom - top + 1);
401 if top == 0 && bottom == self.rows - 1 {
402 for y in 0..n {
403 let line = self.row(y).to_vec();
404 self.push_scrollback(line);
405 }
406 }
407 let c = self.cols;
408 self.cells.copy_within((top + n) * c..(bottom + 1) * c, top * c);
409 let blank = self.blank();
410 self.cells[(bottom + 1 - n) * c..(bottom + 1) * c].fill(blank);
411 }
412
413 pub fn scroll_down(&mut self, n: usize) {
414 let (top, bottom) = (self.scroll_top, self.scroll_bottom);
415 let n = n.min(bottom - top + 1);
416 let c = self.cols;
417 self.cells.copy_within(top * c..(bottom + 1 - n) * c, (top + n) * c);
418 let blank = self.blank();
419 self.cells[top * c..(top + n) * c].fill(blank);
420 }
421
422 pub fn erase_display(&mut self, mode: u32) {
428 let blank = self.blank();
429 let pos = self.y * self.cols + self.x;
430 match mode {
431 0 => self.cells[pos..].fill(blank),
432 1 => self.cells[..=pos].fill(blank),
433 _ => {
434 let used = (0..self.rows).rev().find(|&y| self.row(y).iter().any(|c| !c.is_blank()));
435 if let Some(last) = used {
436 for y in 0..=last {
437 let line = self.row(y).to_vec();
438 self.push_scrollback(line);
439 }
440 }
441 self.cells.fill(blank);
442 self.move_to(0, 0);
443 }
444 }
445 self.pending_wrap = false;
446 }
447
448 pub fn erase_line(&mut self, mode: u32) {
450 let blank = self.blank();
451 let start = self.y * self.cols;
452 let (a, b) = match mode {
453 0 => (self.x, self.cols),
454 1 => (0, self.x + 1),
455 _ => (0, self.cols),
456 };
457 self.cells[start + a..start + b].fill(blank);
458 self.pending_wrap = false;
459 }
460
461 pub fn erase_chars(&mut self, n: usize) {
463 let blank = self.blank();
464 let start = self.y * self.cols;
465 let end = (self.x + n).min(self.cols);
466 self.cells[start + self.x..start + end].fill(blank);
467 }
468
469 pub fn insert_chars(&mut self, n: usize) {
471 let blank = self.blank();
472 let start = self.y * self.cols;
473 let n = n.min(self.cols - self.x);
474 let line = &mut self.cells[start..start + self.cols];
475 line.copy_within(self.x..self.cols - n, self.x + n);
476 line[self.x..self.x + n].fill(blank);
477 }
478
479 pub fn delete_chars(&mut self, n: usize) {
481 let blank = self.blank();
482 let start = self.y * self.cols;
483 let n = n.min(self.cols - self.x);
484 let line = &mut self.cells[start..start + self.cols];
485 line.copy_within(self.x + n.., self.x);
486 let cols = self.cols;
487 line[cols - n..].fill(blank);
488 }
489
490 pub fn insert_lines(&mut self, n: usize) {
492 if self.y < self.scroll_top || self.y > self.scroll_bottom {
493 return;
494 }
495 let top = self.scroll_top;
496 self.scroll_top = self.y;
497 self.scroll_down(n);
498 self.scroll_top = top;
499 self.x = 0;
500 }
501
502 pub fn delete_lines(&mut self, n: usize) {
504 if self.y < self.scroll_top || self.y > self.scroll_bottom {
505 return;
506 }
507 let (top, bottom) = (self.scroll_top, self.scroll_bottom);
508 self.scroll_top = self.y;
510 let n = n.min(bottom - self.y + 1);
511 let c = self.cols;
512 self.cells.copy_within((self.y + n) * c..(bottom + 1) * c, self.y * c);
513 let blank = self.blank();
514 self.cells[(bottom + 1 - n) * c..(bottom + 1) * c].fill(blank);
515 self.scroll_top = top;
516 self.x = 0;
517 }
518
519 pub fn draw_text(&mut self, x: usize, y: usize, text: &[u8], fg: Color, bg: Color) {
522 for (i, &ch) in text.iter().enumerate() {
523 if let Some(c) = self.cell_mut(x + i, y) {
524 *c = Cell::new(ch, fg, bg);
525 }
526 }
527 }
528
529 pub fn fill_rect(&mut self, x: usize, y: usize, w: usize, h: usize, cell: Cell) {
530 for yy in y..(y + h).min(self.rows) {
531 for xx in x..(x + w).min(self.cols) {
532 self.cells[yy * self.cols + xx] = cell;
533 }
534 }
535 }
536
537 pub fn row_text(row: &[Cell]) -> String {
539 let s: String = row.iter().map(|c| crate::cp437::to_unicode(c.ch.max(b' '))).collect();
540 s.trim_end().to_string()
541 }
542}
543
544#[cfg(test)]
545mod tests {
546 use super::*;
547
548 fn text(s: &Screen, y: usize) -> String {
549 Screen::row_text(s.row(y))
550 }
551
552 #[test]
553 fn delayed_wrap_at_last_column() {
554 let mut s = Screen::new(4, 3, 10);
555 for &b in b"abcd" {
556 s.put(b);
557 }
558 assert_eq!(s.cursor(), (3, 0));
559 s.carriage_return();
560 s.line_feed();
561 s.put(b'e');
562 assert_eq!(text(&s, 0), "abcd");
563 assert_eq!(text(&s, 1), "e");
564 }
565
566 #[test]
567 fn scrolling_feeds_scrollback() {
568 let mut s = Screen::new(4, 2, 10);
569 for &b in b"ab" {
570 s.put(b);
571 s.carriage_return();
572 s.line_feed();
573 }
574 assert_eq!(s.scrollback_len(), 1);
575 assert_eq!(Screen::row_text(s.scrollback_line(0)), "a");
576 assert_eq!(text(&s, 0), "b");
577 }
578
579 #[test]
580 fn clear_screen_saves_to_scrollback() {
581 let mut s = Screen::new(4, 3, 10);
582 s.put(b'x');
583 s.erase_display(2);
584 assert_eq!(s.scrollback_len(), 1);
585 assert_eq!(s.cursor(), (0, 0));
586 }
587
588 #[test]
589 fn insert_delete_chars() {
590 let mut s = Screen::new(5, 1, 0);
591 for &b in b"abcde" {
592 s.put(b);
593 }
594 s.move_to(1, 0);
595 s.delete_chars(2);
596 assert_eq!(text(&s, 0), "ade");
597 s.insert_chars(1);
598 assert_eq!(text(&s, 0), "a de");
599 }
600
601 #[test]
602 fn ice_colors_resolve() {
603 let c = Cell { ch: b'A', fg: Color::Pc(1), bg: Color::Pc(4), flags: flags::BOLD | flags::BLINK };
604 assert_eq!(c.resolve(false), (VGA[9], VGA[4], true));
605 assert_eq!(c.resolve(true), (VGA[9], VGA[12], false));
606 }
607}