Skip to main content

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}