centaurea_ui/
buffer.rs

1use std::{
2    collections::{BTreeMap, HashMap},
3    io::Write,
4    iter::Iterator,
5};
6
7use crossterm::{
8    cursor, queue,
9    style::{self, ContentStyle},
10};
11
12use super::{Canvas, Coord, Drawable, Pixel, Pixels, Rect, Style};
13
14pub struct Buffer<S: Style> {
15    pub width: u16,
16    pub height: u16,
17    pixels: Vec<Pixel<S>>,
18    pub changes: BTreeMap<usize, Pixel<S>>,
19    pub styles: HashMap<S, ContentStyle>,
20}
21
22impl<S: Style> Default for Buffer<S> {
23    fn default() -> Self {
24        Self {
25            width: 0,
26            height: 0,
27            pixels: Vec::new(),
28            changes: BTreeMap::new(),
29            styles: HashMap::new(),
30        }
31    }
32}
33
34impl<S: Style> Buffer<S> {
35    pub fn canvas<'a>(&'a mut self, frame: Rect) -> Canvas<'a, S> {
36        Canvas::new(self, frame)
37    }
38
39    pub fn set_styles(&mut self, styles: HashMap<S, ContentStyle>) {
40        self.styles = styles;
41    }
42
43    pub fn resize(&mut self, width: u16, height: u16) {
44        self.width = width;
45        self.height = height;
46        self.pixels = (0..width * height).map(|_| Pixel::default()).collect();
47    }
48
49    pub fn draw_changes<W: Write>(&mut self, stdout: &mut W) -> Result<(), crossterm::ErrorKind> {
50        let mut last_style = None;
51        let mut last_coord = None;
52        let mut text = "".to_string();
53
54        for (k, v) in self.changes.iter() {
55            if let Some(pixel) = self.pixels.get(*k) {
56                if *pixel != *v {
57                    self.pixels[*k] = *v;
58                } else {
59                    continue;
60                }
61            } else {
62                continue;
63            }
64
65            if last_style != Some(v.style) || *k == 0 || last_coord != Some(*k - 1) {
66                if !text.is_empty() {
67                    let style = match self.styles.get(&last_style.unwrap()) {
68                        Some(s) => *s,
69                        None => ContentStyle::default(),
70                    };
71                    queue!(stdout, style::PrintStyledContent(style.apply(text)))?;
72                }
73
74                let coord = Coord::from_index(*k, self.width);
75                queue!(stdout, cursor::MoveTo(coord.x, coord.y))?;
76
77                text = v.text.unwrap_or(' ').to_string();
78                last_style = Some(v.style);
79            } else {
80                text = format!("{}{}", text, v.text.unwrap_or(' '));
81            }
82
83            last_coord = Some(*k);
84        }
85
86        if !text.is_empty() {
87            let style = match self.styles.get(&last_style.unwrap()) {
88                Some(s) => *s,
89                None => ContentStyle::default(),
90            };
91
92            queue!(stdout, style::PrintStyledContent(style.apply(text)))?;
93        }
94
95        self.changes.clear();
96
97        stdout.flush()?;
98
99        Ok(())
100    }
101
102    pub fn get_pixel(&self, x: u16, y: u16) -> Option<&Pixel<S>> {
103        let index = Coord::from((x, y)).as_index(self.width);
104
105        self.pixels.get(index)
106    }
107}
108
109impl<S: Style> Drawable<S> for Buffer<S> {
110    fn fill(&mut self, text: char, style: S) {
111        self.rect(Rect::new(0, 0, self.width, self.height), text, style);
112    }
113    fn rect(&mut self, rect: Rect, text: char, style: S) {
114        let text: String = (0..rect.width).map(|_| text).collect();
115        for y in 0..rect.height {
116            self.string(rect.x, rect.y + y, text.clone(), style);
117        }
118    }
119
120    fn string(&mut self, x: u16, y: u16, text: String, style: S) {
121        let index = Coord::from((x, y)).as_index(self.width);
122
123        for (i, c) in text.chars().enumerate() {
124            if i + index >= self.pixels.len() {
125                break;
126            }
127
128            self.pixel_at_index(
129                index + i,
130                Pixel {
131                    text: Some(c),
132                    style,
133                },
134            );
135        }
136    }
137
138    fn pixels(&mut self, x: u16, y: u16, pixels: &Pixels<S>) {
139        let index = Coord::from((x, y)).as_index(self.width);
140
141        for (i, p) in pixels.iter().enumerate() {
142            if i + index >= self.pixels.len() {
143                break;
144            }
145
146            self.pixel_at_index(index + i, *p);
147        }
148    }
149
150    fn pixel(&mut self, x: u16, y: u16, pixel: Pixel<S>) {
151        let index = Coord::from((x, y)).as_index(self.width);
152        self.pixel_at_index(index, pixel);
153    }
154
155    fn pixel_at_index(&mut self, coord: usize, pixel: Pixel<S>) {
156        if self.pixels[coord] != pixel {
157            self.changes.insert(coord, pixel);
158        } else {
159            self.changes.remove(&coord);
160        }
161    }
162}