katui_present/surface/
blocks.rs1use crate::Sink;
6use std::io;
7
8use katui_scene::cell::{Attrs, Buffer, Color, Position};
9use katui_scene::surface::Surface;
10
11use super::BufferSurface;
12use crate::blocks::{Rgb, quarter};
13
14#[derive(Debug, Default)]
18pub struct BlocksSurface {
19 cells: BufferSurface,
20}
21
22impl BlocksSurface {
23 #[must_use]
25 pub fn new() -> Self {
26 Self::default()
27 }
28
29 pub fn render_frame<W: Sink>(&mut self, out: &mut W, surface: &Surface) -> io::Result<usize> {
35 self.cells.render_frame(out, cells_for_surface(surface))
36 }
37}
38
39fn cells_for_surface(surface: &Surface) -> Buffer {
41 let columns = u16::try_from(surface.width() / 2).unwrap_or(u16::MAX);
42 let rows = u16::try_from(surface.height() / 2).unwrap_or(u16::MAX);
43 let mut buffer = Buffer::blank(katui_scene::CellSize { columns, rows });
44 for row in 0..rows {
45 for column in 0..columns {
46 let pixel = |dx: u32, dy: u32| -> Rgb {
47 let x = u32::from(column) * 2 + dx;
48 let y = u32::from(row) * 2 + dy;
49 surface
50 .get(katui_scene::surface::PixelPos::new(x, y))
51 .map_or(Rgb { r: 0, g: 0, b: 0 }, |color| Rgb {
52 r: color.r,
53 g: color.g,
54 b: color.b,
55 })
56 };
57 let block = quarter([[pixel(0, 0), pixel(1, 0)], [pixel(0, 1), pixel(1, 1)]]);
58 let cell = katui_scene::cell::Cell {
59 symbol: block.glyph.to_string(),
60 fg: Color::Rgb(block.fg.r, block.fg.g, block.fg.b),
61 bg: Color::Rgb(block.bg.r, block.bg.g, block.bg.b),
62 attrs: Attrs::EMPTY,
63 };
64 buffer.set(Position { x: column, y: row }, cell);
65 }
66 }
67 buffer
68}
69
70#[cfg(test)]
71mod tests {
72 use crate::surface::test_support::solid;
73
74 mod diffed_blocks {
75 use super::super::BlocksSurface;
76 use super::solid;
77 use katui_scene::surface::Rgba;
78
79 #[test]
80 fn an_identical_frame_writes_nothing() {
81 let mut presenter = BlocksSurface::new();
82 let surface = solid(16, 16, Rgba::solid(30, 40, 50));
83 let mut first = Vec::new();
84 presenter
85 .render_frame(&mut first, &surface)
86 .expect("writing to a Vec cannot fail");
87 let mut second = Vec::new();
88 let written = presenter
89 .render_frame(&mut second, &surface)
90 .expect("writing to a Vec cannot fail");
91 assert_eq!(written, 0, "no pixel change means no bytes");
92 assert!(second.is_empty());
93 }
94
95 #[test]
96 fn a_small_change_costs_a_handful_of_cells() {
97 let mut presenter = BlocksSurface::new();
98 let mut surface = solid(32, 32, Rgba::solid(10, 10, 10));
99 let mut first = Vec::new();
100 presenter
101 .render_frame(&mut first, &surface)
102 .expect("writing to a Vec cannot fail");
103 surface.set(
104 katui_scene::surface::PixelPos::new(20, 20),
105 Rgba::solid(255, 0, 0),
106 );
107 let mut second = Vec::new();
108 let written = presenter
109 .render_frame(&mut second, &surface)
110 .expect("writing to a Vec cannot fail");
111 assert!(
112 written < first.len() / 8,
113 "one pixel change is far cheaper than a full frame"
114 );
115 let text = String::from_utf8_lossy(&second);
116 assert!(
117 !text.contains("\x1b[2J"),
118 "a diffed frame never erases the screen"
119 );
120 assert!(
121 text.matches("\x1b[").count() < 10,
122 "a couple of cells repaint, not the screen"
123 );
124 }
125
126 #[test]
127 fn a_size_change_repaints_everything() {
128 let mut presenter = BlocksSurface::new();
129 let mut first = Vec::new();
130 presenter
131 .render_frame(&mut first, &solid(16, 16, Rgba::solid(9, 9, 9)))
132 .expect("writing to a Vec cannot fail");
133 let mut second = Vec::new();
134 let written = presenter
135 .render_frame(&mut second, &solid(32, 32, Rgba::solid(9, 9, 9)))
136 .expect("writing to a Vec cannot fail");
137 assert!(written > 0, "a resize repaints the whole grid");
138 }
139 }
140}