ratatui/buffer/
cell.rs

1use compact_str::CompactString;
2
3use crate::style::{Color, Modifier, Style};
4
5/// A buffer cell
6#[derive(Debug, Clone, Eq, PartialEq, Hash)]
7#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
8pub struct Cell {
9    /// The string to be drawn in the cell.
10    ///
11    /// This accepts unicode grapheme clusters which might take up more than one cell.
12    ///
13    /// This is a [`CompactString`] which is a wrapper around [`String`] that uses a small inline
14    /// buffer for short strings.
15    ///
16    /// See <https://github.com/ratatui/ratatui/pull/601> for more information.
17    symbol: CompactString,
18
19    /// The foreground color of the cell.
20    pub fg: Color,
21
22    /// The background color of the cell.
23    pub bg: Color,
24
25    /// The underline color of the cell.
26    #[cfg(feature = "underline-color")]
27    pub underline_color: Color,
28
29    /// The modifier of the cell.
30    pub modifier: Modifier,
31
32    /// Whether the cell should be skipped when copying (diffing) the buffer to the screen.
33    pub skip: bool,
34}
35
36impl Cell {
37    /// An empty `Cell`
38    pub const EMPTY: Self = Self::new(" ");
39
40    /// Creates a new `Cell` with the given symbol.
41    ///
42    /// This works at compile time and puts the symbol onto the stack. Fails to build when the
43    /// symbol doesnt fit onto the stack and requires to be placed on the heap. Use
44    /// `Self::default().set_symbol()` in that case. See [`CompactString::const_new`] for more
45    /// details on this.
46    pub const fn new(symbol: &'static str) -> Self {
47        Self {
48            symbol: CompactString::const_new(symbol),
49            fg: Color::Reset,
50            bg: Color::Reset,
51            #[cfg(feature = "underline-color")]
52            underline_color: Color::Reset,
53            modifier: Modifier::empty(),
54            skip: false,
55        }
56    }
57
58    /// Gets the symbol of the cell.
59    #[must_use]
60    pub fn symbol(&self) -> &str {
61        self.symbol.as_str()
62    }
63
64    /// Sets the symbol of the cell.
65    pub fn set_symbol(&mut self, symbol: &str) -> &mut Self {
66        self.symbol = CompactString::new(symbol);
67        self
68    }
69
70    /// Appends a symbol to the cell.
71    ///
72    /// This is particularly useful for adding zero-width characters to the cell.
73    pub(crate) fn append_symbol(&mut self, symbol: &str) -> &mut Self {
74        self.symbol.push_str(symbol);
75        self
76    }
77
78    /// Sets the symbol of the cell to a single character.
79    pub fn set_char(&mut self, ch: char) -> &mut Self {
80        let mut buf = [0; 4];
81        self.symbol = CompactString::new(ch.encode_utf8(&mut buf));
82        self
83    }
84
85    /// Sets the foreground color of the cell.
86    pub fn set_fg(&mut self, color: Color) -> &mut Self {
87        self.fg = color;
88        self
89    }
90
91    /// Sets the background color of the cell.
92    pub fn set_bg(&mut self, color: Color) -> &mut Self {
93        self.bg = color;
94        self
95    }
96
97    /// Sets the style of the cell.
98    ///
99    ///  `style` accepts any type that is convertible to [`Style`] (e.g. [`Style`], [`Color`], or
100    /// your own type that implements [`Into<Style>`]).
101    pub fn set_style<S: Into<Style>>(&mut self, style: S) -> &mut Self {
102        let style = style.into();
103        if let Some(c) = style.fg {
104            self.fg = c;
105        }
106        if let Some(c) = style.bg {
107            self.bg = c;
108        }
109        #[cfg(feature = "underline-color")]
110        if let Some(c) = style.underline_color {
111            self.underline_color = c;
112        }
113        self.modifier.insert(style.add_modifier);
114        self.modifier.remove(style.sub_modifier);
115        self
116    }
117
118    /// Returns the style of the cell.
119    #[must_use]
120    pub const fn style(&self) -> Style {
121        Style {
122            fg: Some(self.fg),
123            bg: Some(self.bg),
124            #[cfg(feature = "underline-color")]
125            underline_color: Some(self.underline_color),
126            add_modifier: self.modifier,
127            sub_modifier: Modifier::empty(),
128        }
129    }
130
131    /// Sets the cell to be skipped when copying (diffing) the buffer to the screen.
132    ///
133    /// This is helpful when it is necessary to prevent the buffer from overwriting a cell that is
134    /// covered by an image from some terminal graphics protocol (Sixel / iTerm / Kitty ...).
135    pub fn set_skip(&mut self, skip: bool) -> &mut Self {
136        self.skip = skip;
137        self
138    }
139
140    /// Resets the cell to the empty state.
141    pub fn reset(&mut self) {
142        self.symbol = CompactString::const_new(" ");
143        self.fg = Color::Reset;
144        self.bg = Color::Reset;
145        #[cfg(feature = "underline-color")]
146        {
147            self.underline_color = Color::Reset;
148        }
149        self.modifier = Modifier::empty();
150        self.skip = false;
151    }
152}
153
154impl Default for Cell {
155    fn default() -> Self {
156        Self::EMPTY
157    }
158}
159
160impl From<char> for Cell {
161    fn from(ch: char) -> Self {
162        let mut cell = Self::EMPTY;
163        cell.set_char(ch);
164        cell
165    }
166}
167
168#[cfg(test)]
169mod tests {
170    use super::*;
171
172    #[test]
173    fn new() {
174        let cell = Cell::new("ใ‚");
175        assert_eq!(
176            cell,
177            Cell {
178                symbol: CompactString::const_new("ใ‚"),
179                fg: Color::Reset,
180                bg: Color::Reset,
181                #[cfg(feature = "underline-color")]
182                underline_color: Color::Reset,
183                modifier: Modifier::empty(),
184                skip: false,
185            }
186        );
187    }
188
189    #[test]
190    fn empty() {
191        let cell = Cell::EMPTY;
192        assert_eq!(cell.symbol(), " ");
193    }
194
195    #[test]
196    fn set_symbol() {
197        let mut cell = Cell::EMPTY;
198        cell.set_symbol("ใ‚"); // Multi-byte character
199        assert_eq!(cell.symbol(), "ใ‚");
200        cell.set_symbol("๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ"); // Multiple code units combined with ZWJ
201        assert_eq!(cell.symbol(), "๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ");
202    }
203
204    #[test]
205    fn append_symbol() {
206        let mut cell = Cell::EMPTY;
207        cell.set_symbol("ใ‚"); // Multi-byte character
208        cell.append_symbol("\u{200B}"); // zero-width space
209        assert_eq!(cell.symbol(), "ใ‚\u{200B}");
210    }
211
212    #[test]
213    fn set_char() {
214        let mut cell = Cell::EMPTY;
215        cell.set_char('ใ‚'); // Multi-byte character
216        assert_eq!(cell.symbol(), "ใ‚");
217    }
218
219    #[test]
220    fn set_fg() {
221        let mut cell = Cell::EMPTY;
222        cell.set_fg(Color::Red);
223        assert_eq!(cell.fg, Color::Red);
224    }
225
226    #[test]
227    fn set_bg() {
228        let mut cell = Cell::EMPTY;
229        cell.set_bg(Color::Red);
230        assert_eq!(cell.bg, Color::Red);
231    }
232
233    #[test]
234    fn set_style() {
235        let mut cell = Cell::EMPTY;
236        cell.set_style(Style::new().fg(Color::Red).bg(Color::Blue));
237        assert_eq!(cell.fg, Color::Red);
238        assert_eq!(cell.bg, Color::Blue);
239    }
240
241    #[test]
242    fn set_skip() {
243        let mut cell = Cell::EMPTY;
244        cell.set_skip(true);
245        assert!(cell.skip);
246    }
247
248    #[test]
249    fn reset() {
250        let mut cell = Cell::EMPTY;
251        cell.set_symbol("ใ‚");
252        cell.set_fg(Color::Red);
253        cell.set_bg(Color::Blue);
254        cell.set_skip(true);
255        cell.reset();
256        assert_eq!(cell.symbol(), " ");
257        assert_eq!(cell.fg, Color::Reset);
258        assert_eq!(cell.bg, Color::Reset);
259        assert!(!cell.skip);
260    }
261
262    #[test]
263    fn style() {
264        let cell = Cell::EMPTY;
265        assert_eq!(
266            cell.style(),
267            Style {
268                fg: Some(Color::Reset),
269                bg: Some(Color::Reset),
270                #[cfg(feature = "underline-color")]
271                underline_color: Some(Color::Reset),
272                add_modifier: Modifier::empty(),
273                sub_modifier: Modifier::empty(),
274            }
275        );
276    }
277
278    #[test]
279    fn default() {
280        let cell = Cell::default();
281        assert_eq!(cell.symbol(), " ");
282    }
283
284    #[test]
285    fn cell_eq() {
286        let cell1 = Cell::new("ใ‚");
287        let cell2 = Cell::new("ใ‚");
288        assert_eq!(cell1, cell2);
289    }
290
291    #[test]
292    fn cell_ne() {
293        let cell1 = Cell::new("ใ‚");
294        let cell2 = Cell::new("ใ„");
295        assert_ne!(cell1, cell2);
296    }
297}