tsu_editor/editor/render_buffer.rs
1use crate::{
2 color::{Color, blend_color},
3 theme::{Style, Theme},
4};
5
6#[derive(Debug, Clone, PartialEq, Eq)]
7pub struct Cell {
8 pub c: char,
9 pub style: Style,
10}
11
12#[derive(Debug)]
13pub struct Change<'a> {
14 pub x: usize,
15 pub y: usize,
16 pub cell: &'a Cell,
17}
18
19#[derive(Debug, Clone)]
20pub struct RenderBuffer {
21 pub cells: Vec<Cell>,
22 pub width: usize,
23 #[allow(unused)]
24 pub height: usize,
25}
26
27impl RenderBuffer {
28 #[must_use]
29 /// Creates a new `RenderBuffer` with a default style
30 ///
31 /// # Arguments
32 /// - `width`: The desired width of the buffer measured in "cells"
33 /// - `height`: The desired height of the buffer measured in "cells"
34 /// - `default_style`: Some default style for formatting the empty cells
35 ///
36 /// # Returns
37 /// - A newly constructed `RenderBuffer` with a default style and no content
38 pub fn new(width: usize, height: usize, default_style: &Style) -> Self {
39 let cells = vec![
40 Cell {
41 c: ' ',
42 style: default_style.clone(),
43 };
44 width * height
45 ];
46
47 Self {
48 cells,
49 width,
50 height,
51 }
52 }
53
54 #[must_use]
55 /// Creates a new `RenderBuffer` with text
56 ///
57 /// # Arguments
58 /// - `width`: The desired width of the buffer measured in "cells"
59 /// - `height`: The desired height of the buffer measured in "cells"
60 /// - `style`: Styling to apply to the cells based on the provided content
61 /// - `contents`: A vector of strings representing the buffer's text
62 ///
63 /// # Returns
64 /// - A constructed `RenderBuffer` with styling and textual content
65 pub fn new_with_contents(
66 width: usize,
67 height: usize,
68 style: &Style,
69 contents: Vec<String>,
70 ) -> Self {
71 let mut cells = vec![];
72
73 for line in contents {
74 for c in line.chars() {
75 cells.push(Cell {
76 c,
77 style: style.clone(),
78 });
79 }
80 for _ in 0..width.saturating_sub(line.len()) {
81 cells.push(Cell {
82 c: ' ',
83 style: style.clone(),
84 });
85 }
86 }
87
88 Self {
89 cells,
90 width,
91 height,
92 }
93 }
94
95 /// Clears the buffer by replacing all characters within the cells with whitespace
96 pub fn clear(&mut self) {
97 self.cells = vec![
98 Cell {
99 c: ' ',
100 style: Style::default(),
101 };
102 self.width * self.height
103 ];
104 }
105
106 /// Add a character to a buffer cell with appropriate styling and theme
107 ///
108 /// # Arguments
109 /// - `x`: The x coordinate of the cell
110 /// - `y`: The y coordinate of the cell
111 /// - `c`: The character to emplace
112 /// - `style`: The styling of the character and its encapsulating cell
113 /// - `theme`: The theme to use for styling
114 pub fn set_char(&mut self, x: usize, y: usize, c: char, style: &Style, theme: &Theme) {
115 if x > self.width || y > self.height {
116 return;
117 }
118 let position = (y * self.width) + x;
119 if position >= self.cells.len() {
120 return;
121 }
122
123 let background = style.background.map(|color| match color {
124 Color::Rgba { r, g, b, a } => blend_color(
125 Color::Rgba { r, g, b, a },
126 theme
127 .style
128 .background
129 .unwrap_or(Color::Rgb { r: 0, g: 0, b: 0 }),
130 ),
131 Color::Rgb { .. } => color,
132 });
133
134 self.cells[position] = Cell {
135 c,
136 style: Style {
137 foreground: style.foreground,
138 background,
139 bold: style.bold,
140 italic: style.italic,
141 },
142 };
143 }
144
145 /// Adds a string of text to the buffer with appropriate styling
146 ///
147 /// # Arguments
148 /// - `x`: The x coordinate representing the start of the text's destination within the buffer
149 /// - `y`: The y coordinate representing the start of the text's destination within the buffer
150 /// - `text`: The text to emplace
151 /// - `style`: The styling to apply to the text on a cell-wise basis
152 pub fn set_text(&mut self, x: usize, y: usize, text: &str, style: &Style) {
153 let pos = (y * self.width) + x;
154 for (i, c) in text.chars().enumerate() {
155 if x + i >= self.width {
156 break;
157 }
158 if pos + i >= self.cells.len() {
159 break;
160 }
161 self.cells[pos + i] = Cell {
162 c,
163 style: style.clone(),
164 }
165 }
166 }
167
168 #[must_use]
169 /// Computes the differences between two `RenderBuffer`s
170 ///
171 /// # Arguments
172 /// - `other`: The buffer being compared to the current buffer
173 ///
174 /// # Returns
175 /// - A vector of `Change`s which contains information about which positions and cells differ
176 pub fn diff(&self, other: &RenderBuffer) -> Vec<Change<'_>> {
177 let mut changes = vec![];
178 for (pos, cell) in self.cells.iter().enumerate() {
179 if *cell != other.cells[pos] {
180 let y = pos / self.width;
181 let x = pos % self.width;
182
183 changes.push(Change { x, y, cell });
184 }
185 }
186
187 changes
188 }
189}