katui_present/surface/
buffer.rs1use crate::Sink;
5use std::io::{self, Write as _};
6
7use katui_scene::cell::{Buffer, all_cells, diff};
8
9#[derive(Debug, Default)]
16pub struct BufferSurface {
17 prev: Option<Buffer>,
18}
19
20impl BufferSurface {
21 #[must_use]
23 pub fn new() -> Self {
24 Self::default()
25 }
26
27 pub fn render_frame<W: Sink>(&mut self, out: &mut W, next: Buffer) -> io::Result<usize> {
32 let full = self
33 .prev
34 .as_ref()
35 .is_none_or(|prev| prev.size() != next.size());
36 let changed = match self.prev.as_ref() {
37 Some(prev) if !full => diff(prev, &next),
38 _ => all_cells(&next),
39 };
40 let mut bytes = Vec::new();
41 if !changed.is_empty() {
42 bytes.write_all(b"\x1b[?2026h")?;
43 if full {
44 bytes.write_all(b"\x1b[H\x1b[2J")?;
45 }
46 for (position, cell) in changed {
47 crate::cells::render_cell(&mut bytes, position, cell)?;
48 }
49 bytes.write_all(b"\x1b[0m\x1b[?2026l")?;
50 }
51 self.prev = Some(next);
52 out.write_all(&bytes)?;
53 Ok(bytes.len())
54 }
55}
56
57#[cfg(test)]
58mod tests {
59 use super::BufferSurface;
60 use katui_scene::CellSize;
61 use katui_scene::cell::{Buffer, Cell, Color, Position};
62
63 fn grid(columns: u16, rows: u16, alive: &[(u16, u16)]) -> Buffer {
64 let mut buffer = Buffer::blank(CellSize { columns, rows });
65 for &(x, y) in alive {
66 buffer.set(
67 Position { x, y },
68 Cell {
69 symbol: "█".to_owned(),
70 fg: Color::Indexed(9),
71 bg: Color::Reset,
72 attrs: katui_scene::cell::Attrs::EMPTY,
73 },
74 );
75 }
76 buffer
77 }
78
79 #[test]
80 fn an_identical_frame_writes_nothing() {
81 let mut presenter = BufferSurface::new();
82 let mut first = Vec::new();
83 presenter
84 .render_frame(&mut first, grid(8, 4, &[(1, 1), (2, 2)]))
85 .expect("writing to a Vec cannot fail");
86 let mut second = Vec::new();
87 let written = presenter
88 .render_frame(&mut second, grid(8, 4, &[(1, 1), (2, 2)]))
89 .expect("writing to a Vec cannot fail");
90 assert_eq!(written, 0, "no cell change means no bytes");
91 assert!(second.is_empty());
92 }
93
94 #[test]
95 fn a_fresh_frame_clears_and_a_later_change_does_not() {
96 let mut presenter = BufferSurface::new();
97 let mut first = Vec::new();
98 presenter
99 .render_frame(&mut first, grid(8, 4, &[(1, 1)]))
100 .expect("writing to a Vec cannot fail");
101 assert!(
102 String::from_utf8_lossy(&first).contains("\x1b[H\x1b[2J"),
103 "the first frame clears"
104 );
105 let mut second = Vec::new();
106 presenter
107 .render_frame(&mut second, grid(8, 4, &[(2, 2)]))
108 .expect("writing to a Vec cannot fail");
109 let text = String::from_utf8_lossy(&second);
110 assert!(!text.contains("\x1b[2J"), "a diffed frame never erases");
111 assert!(
112 text.contains("\x1b[38;5;9m"),
113 "indexed colors emit the 256-color SGR"
114 );
115 }
116
117 #[test]
118 fn a_resize_repaints_everything() {
119 let mut presenter = BufferSurface::new();
120 let mut first = Vec::new();
121 presenter
122 .render_frame(&mut first, grid(8, 4, &[(1, 1)]))
123 .expect("writing to a Vec cannot fail");
124 let mut second = Vec::new();
125 presenter
126 .render_frame(&mut second, grid(6, 4, &[(1, 1)]))
127 .expect("writing to a Vec cannot fail");
128 assert!(
129 String::from_utf8_lossy(&second).contains("\x1b[2J"),
130 "a resize clears and repaints whole"
131 );
132 }
133}