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}