screen_buffer_ui/
screen.rs

1use 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            // decide whether moving the cursor is needed
87            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            // take as many changes with the same style as possible
93
94            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}