screen_buffer_ui/
screen.rs1use super::Rect;
2
3use std::{collections::HashMap, hash::Hash, io::Write};
4
5use crossterm::{
6 cursor, queue,
7 style::{self, ContentStyle},
8};
9
10use itertools::Itertools;
11
12#[derive(Clone, Copy, PartialEq, Debug)]
13pub struct Pixel<T: Default + Clone + Copy + PartialEq> {
14 pub text: Option<char>,
15 pub style: T,
16}
17
18impl<T: Default + Clone + Copy + PartialEq> Default for Pixel<T> {
19 fn default() -> Self {
20 Self {
21 text: None,
22 style: T::default(),
23 }
24 }
25}
26
27pub struct Screen<T: Default + Clone + Copy + PartialEq + Eq + Hash> {
28 pub width: u16,
29 pub height: u16,
30 pixels: Vec<Pixel<T>>,
31 changes: HashMap<usize, Pixel<T>>,
32 pub styles: HashMap<T, ContentStyle>,
33}
34
35impl<T: Default + Clone + Copy + PartialEq + Eq + Hash> Default for Screen<T> {
36 fn default() -> Self {
37 Self {
38 width: 0,
39 height: 0,
40 pixels: Vec::new(),
41 changes: HashMap::new(),
42 styles: HashMap::new(),
43 }
44 }
45}
46
47impl<T: Default + Hash + Clone + Copy + PartialEq + Eq> Screen<T> {
48 pub fn set_styles(&mut self, styles: HashMap<T, ContentStyle>) {
49 self.styles = styles;
50 }
51
52 pub fn resize(&mut self, width: u16, height: u16) {
53 self.width = width;
54 self.height = height;
55 self.pixels = (0..width * height).map(|_| Pixel::default()).collect();
56 }
57
58 pub fn draw_changes<W: Write>(&mut self, stdout: &mut W) -> Result<(), crossterm::ErrorKind> {
59 let mut changes = self.changes.clone();
60 changes.retain(|k, v| {
61 if let Some(pixel) = self.pixels.get(*k) {
62 if *pixel == *v {
63 false
64 } else {
65 true
66 }
67 } else {
68 false
69 }
70 });
71
72 self.changes = changes;
73
74 let changes = self.changes.keys().sorted().collect_vec();
75
76 let mut last = None;
77 let mut i = 0;
78 while i < changes.len() {
79 if self.pixels.get(*changes[i]).is_none() {
80 i += 1;
81 continue;
82 }
83
84 let start_i = i;
85
86 if last.is_none() || last.unwrap() + 1 != *changes[i] {
88 let (x, y) = self.coord_to_xy(*changes[i]);
89 queue!(stdout, cursor::MoveTo(x, y))?;
90 }
91
92 let style = self.changes.get(changes[i]).unwrap().style;
95 while i + 1 < changes.len() {
96 if changes[i] + 1 != *changes[i + 1]
97 || self.pixels.get(*changes[i + 1]).is_none()
98 || self.changes.get(changes[i + 1]).unwrap().style != style
99 {
100 break;
101 }
102
103 i += 1;
104 }
105
106 for k in &changes[start_i..i + 1] {
107 self.pixels[**k] = *self.changes.get(*k).unwrap();
108 }
109
110 let range = *changes[start_i]..(*changes[i] + 1);
111 let text = self.pixels[range]
112 .iter()
113 .map(|pixel| pixel.text.unwrap_or(' '))
114 .collect::<String>();
115
116 let style = match self.styles.get(&style) {
117 Some(s) => *s,
118 None => ContentStyle::default(),
119 };
120
121 queue!(stdout, style::PrintStyledContent(style.apply(text)))?;
122
123 last = Some(*changes[i]);
124 i += 1;
125 }
126
127 stdout.flush()?;
128
129 Ok(())
130 }
131
132 fn coord_to_xy(&self, coord: usize) -> (u16, u16) {
133 let y = (coord as f32 / self.width as f32).floor() as usize;
134 let x = coord - (y * self.width as usize);
135 (x as u16, y as u16)
136 }
137
138 fn xy_to_coord(&self, x: u16, y: u16) -> usize {
139 (y * self.width + x) as usize
140 }
141
142 pub fn rect(&mut self, rect: Rect, text: char, style: T) {
143 let text: String = (0..rect.width).map(|_| text).collect();
144 for y in 0..rect.height {
145 self.string(rect.x, rect.y + y, text.clone(), style);
146 }
147 }
148
149 pub fn string(&mut self, x: u16, y: u16, text: String, style: T) {
150 let coord = self.xy_to_coord(x, y);
151
152 for (i, c) in text.chars().enumerate() {
153 if i + coord >= self.pixels.len() {
154 break;
155 }
156
157 self.pixel(
158 coord + i,
159 Pixel {
160 text: Some(c),
161 style,
162 },
163 );
164 }
165 }
166
167 pub fn pixels(&mut self, x: u16, y: u16, pixels: Vec<Pixel<T>>) {
168 let coord = self.xy_to_coord(x, y);
169
170 for (i, p) in pixels.iter().enumerate() {
171 if i + coord >= self.pixels.len() {
172 break;
173 }
174
175 self.pixel(coord + i, *p);
176 }
177 }
178
179 pub fn pixel(&mut self, coord: usize, pixel: Pixel<T>) {
180 if self.pixels[coord] != pixel {
181 self.changes.insert(coord, pixel);
182 } else {
183 self.changes.remove(&coord);
184 }
185 }
186}