tui_temp_fork/
buffer.rs

1use std::cmp::min;
2use std::fmt;
3use std::usize;
4
5use unicode_segmentation::UnicodeSegmentation;
6use unicode_width::UnicodeWidthStr;
7
8use crate::layout::Rect;
9use crate::style::{Color, Modifier, Style};
10
11/// A buffer cell
12#[derive(Debug, Clone, PartialEq)]
13pub struct Cell {
14    pub symbol: String,
15    pub style: Style,
16}
17
18impl Cell {
19    pub fn set_symbol(&mut self, symbol: &str) -> &mut Cell {
20        self.symbol.clear();
21        self.symbol.push_str(symbol);
22        self
23    }
24
25    pub fn set_char(&mut self, ch: char) -> &mut Cell {
26        self.symbol.clear();
27        self.symbol.push(ch);
28        self
29    }
30
31    pub fn set_fg(&mut self, color: Color) -> &mut Cell {
32        self.style.fg = color;
33        self
34    }
35
36    pub fn set_bg(&mut self, color: Color) -> &mut Cell {
37        self.style.bg = color;
38        self
39    }
40
41    pub fn set_modifier(&mut self, modifier: Modifier) -> &mut Cell {
42        self.style.modifier = modifier;
43        self
44    }
45
46    pub fn set_style(&mut self, style: Style) -> &mut Cell {
47        self.style = style;
48        self
49    }
50
51    pub fn reset(&mut self) {
52        self.symbol.clear();
53        self.symbol.push(' ');
54        self.style.reset();
55    }
56}
57
58impl Default for Cell {
59    fn default() -> Cell {
60        Cell {
61            symbol: " ".into(),
62            style: Default::default(),
63        }
64    }
65}
66
67/// A buffer that maps to the desired content of the terminal after the draw call
68///
69/// No widget in the library interacts directly with the terminal. Instead each of them is required
70/// to draw their state to an intermediate buffer. It is basically a grid where each cell contains
71/// a grapheme, a foreground color and a background color. This grid will then be used to output
72/// the appropriate escape sequences and characters to draw the UI as the user has defined it.
73///
74/// # Examples:
75///
76/// ```
77/// use tui_temp_fork::buffer::{Buffer, Cell};
78/// use tui_temp_fork::layout::Rect;
79/// use tui_temp_fork::style::{Color, Style, Modifier};
80///
81/// # fn main() {
82/// let mut buf = Buffer::empty(Rect{x: 0, y: 0, width: 10, height: 5});
83/// buf.get_mut(0, 2).set_symbol("x");
84/// assert_eq!(buf.get(0, 2).symbol, "x");
85/// buf.set_string(3, 0, "string", Style::default().fg(Color::Red).bg(Color::White));
86/// assert_eq!(buf.get(5, 0), &Cell{
87///     symbol: String::from("r"),
88///     style: Style {
89///         fg: Color::Red,
90///         bg: Color::White,
91///         modifier: Modifier::empty()
92///     }});
93/// buf.get_mut(5, 0).set_char('x');
94/// assert_eq!(buf.get(5, 0).symbol, "x");
95/// # }
96/// ```
97#[derive(Clone, PartialEq)]
98pub struct Buffer {
99    /// The area represented by this buffer
100    pub area: Rect,
101    /// The content of the buffer. The length of this Vec should always be equal to area.width *
102    /// area.height
103    pub content: Vec<Cell>,
104}
105
106impl Default for Buffer {
107    fn default() -> Buffer {
108        Buffer {
109            area: Default::default(),
110            content: Vec::new(),
111        }
112    }
113}
114
115impl fmt::Debug for Buffer {
116    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
117        writeln!(f, "Buffer: {:?}", self.area)?;
118        f.write_str("Content (quoted lines):\n")?;
119        for cells in self.content.chunks(self.area.width as usize) {
120            let mut line = String::new();
121            let mut overwritten = vec![];
122            let mut skip: usize = 0;
123            for (x, c) in cells.iter().enumerate() {
124                if skip == 0 {
125                    line.push_str(&c.symbol);
126                } else {
127                    overwritten.push((x, &c.symbol))
128                }
129                skip = std::cmp::max(skip, c.symbol.width()).saturating_sub(1);
130            }
131            f.write_fmt(format_args!("{:?},", line))?;
132            if !overwritten.is_empty() {
133                f.write_fmt(format_args!(
134                    " Hidden by multi-width symbols: {:?}",
135                    overwritten
136                ))?;
137            }
138            f.write_str("\n")?;
139        }
140        f.write_str("Style:\n")?;
141        for cells in self.content.chunks(self.area.width as usize) {
142            f.write_str("|")?;
143            for cell in cells {
144                write!(
145                    f,
146                    "{} {} {}|",
147                    cell.style.fg.code(),
148                    cell.style.bg.code(),
149                    cell.style.modifier.code()
150                )?;
151            }
152            f.write_str("\n")?;
153        }
154        Ok(())
155    }
156}
157
158impl Buffer {
159    /// Returns a Buffer with all cells set to the default one
160    pub fn empty(area: Rect) -> Buffer {
161        let cell: Cell = Default::default();
162        Buffer::filled(area, &cell)
163    }
164
165    /// Returns a Buffer with all cells initialized with the attributes of the given Cell
166    pub fn filled(area: Rect, cell: &Cell) -> Buffer {
167        let size = area.area() as usize;
168        let mut content = Vec::with_capacity(size);
169        for _ in 0..size {
170            content.push(cell.clone());
171        }
172        Buffer { area, content }
173    }
174
175    /// Returns a Buffer containing the given lines
176    pub fn with_lines<S>(lines: Vec<S>) -> Buffer
177    where
178        S: AsRef<str>,
179    {
180        let height = lines.len() as u16;
181        let width = lines.iter().fold(0, |acc, item| {
182            std::cmp::max(acc, item.as_ref().width() as u16)
183        });
184        let mut buffer = Buffer::empty(Rect {
185            x: 0,
186            y: 0,
187            width,
188            height,
189        });
190        for (y, line) in lines.iter().enumerate() {
191            buffer.set_string(0, y as u16, line, Style::default());
192        }
193        buffer
194    }
195
196    /// Returns the content of the buffer as a slice
197    pub fn content(&self) -> &[Cell] {
198        &self.content
199    }
200
201    /// Returns the area covered by this buffer
202    pub fn area(&self) -> &Rect {
203        &self.area
204    }
205
206    /// Returns a reference to Cell at the given coordinates
207    pub fn get(&self, x: u16, y: u16) -> &Cell {
208        let i = self.index_of(x, y);
209        &self.content[i]
210    }
211
212    /// Returns a mutable reference to Cell at the given coordinates
213    pub fn get_mut(&mut self, x: u16, y: u16) -> &mut Cell {
214        let i = self.index_of(x, y);
215        &mut self.content[i]
216    }
217
218    /// Returns the index in the Vec<Cell> for the given global (x, y) coordinates.
219    ///
220    /// Global coordinates are offset by the Buffer's area offset (`x`/`y`).
221    ///
222    /// # Examples
223    ///
224    /// ```
225    /// # use tui_temp_fork::buffer::Buffer;
226    /// # use tui_temp_fork::layout::Rect;
227    /// let rect = Rect::new(200, 100, 10, 10);
228    /// let buffer = Buffer::empty(rect);
229    /// // Global coordinates to the top corner of this buffer's area
230    /// assert_eq!(buffer.index_of(200, 100), 0);
231    /// ```
232    ///
233    /// # Panics
234    ///
235    /// Panics when given an coordinate that is outside of this Buffer's area.
236    ///
237    /// ```should_panic
238    /// # use tui_temp_fork::buffer::Buffer;
239    /// # use tui_temp_fork::layout::Rect;
240    /// let rect = Rect::new(200, 100, 10, 10);
241    /// let buffer = Buffer::empty(rect);
242    /// // Top coordinate is outside of the buffer in global coordinate space, as the Buffer's area
243    /// // starts at (200, 100).
244    /// buffer.index_of(0, 0); // Panics
245    /// ```
246    pub fn index_of(&self, x: u16, y: u16) -> usize {
247        debug_assert!(
248            x >= self.area.left()
249                && x < self.area.right()
250                && y >= self.area.top()
251                && y < self.area.bottom(),
252            "Trying to access position outside the buffer: x={}, y={}, area={:?}",
253            x,
254            y,
255            self.area
256        );
257        ((y - self.area.y) * self.area.width + (x - self.area.x)) as usize
258    }
259
260    /// Returns the (global) coordinates of a cell given its index
261    ///
262    /// Global coordinates are offset by the Buffer's area offset (`x`/`y`).
263    ///
264    /// # Examples
265    ///
266    /// ```
267    /// # use tui_temp_fork::buffer::Buffer;
268    /// # use tui_temp_fork::layout::Rect;
269    /// let rect = Rect::new(200, 100, 10, 10);
270    /// let buffer = Buffer::empty(rect);
271    /// assert_eq!(buffer.pos_of(0), (200, 100));
272    /// assert_eq!(buffer.pos_of(14), (204, 101));
273    /// ```
274    ///
275    /// # Panics
276    ///
277    /// Panics when given an index that is outside the Buffer's content.
278    ///
279    /// ```should_panic
280    /// # use tui_temp_fork::buffer::Buffer;
281    /// # use tui_temp_fork::layout::Rect;
282    /// let rect = Rect::new(0, 0, 10, 10); // 100 cells in total
283    /// let buffer = Buffer::empty(rect);
284    /// // Index 100 is the 101th cell, which lies outside of the area of this Buffer.
285    /// buffer.pos_of(100); // Panics
286    /// ```
287    pub fn pos_of(&self, i: usize) -> (u16, u16) {
288        debug_assert!(
289            i < self.content.len(),
290            "Trying to get the coords of a cell outside the buffer: i={} len={}",
291            i,
292            self.content.len()
293        );
294        (
295            self.area.x + i as u16 % self.area.width,
296            self.area.y + i as u16 / self.area.width,
297        )
298    }
299
300    /// Print a string, starting at the position (x, y)
301    pub fn set_string<S>(&mut self, x: u16, y: u16, string: S, style: Style)
302    where
303        S: AsRef<str>,
304    {
305        self.set_stringn(x, y, string, usize::MAX, style);
306    }
307
308    /// Print at most the first n characters of a string if enough space is available
309    /// until the end of the line
310    pub fn set_stringn<S>(&mut self, x: u16, y: u16, string: S, width: usize, style: Style)
311    where
312        S: AsRef<str>,
313    {
314        let mut index = self.index_of(x, y);
315        let mut x_offset = x as usize;
316        let graphemes = UnicodeSegmentation::graphemes(string.as_ref(), true);
317        let max_offset = min(self.area.right() as usize, width.saturating_add(x as usize));
318        for s in graphemes {
319            let width = s.width();
320            // `x_offset + width > max_offset` could be integer overflow on 32-bit machines if we
321            // change dimenstions to usize or u32 and someone resizes the terminal to 1x2^32.
322            if width > max_offset.saturating_sub(x_offset) {
323                break;
324            }
325
326            self.content[index].set_symbol(s);
327            self.content[index].set_style(style);
328            // Reset following cells if multi-width (they would be hidden by the grapheme),
329            for i in index + 1..index + width {
330                self.content[i].reset();
331            }
332            index += width;
333            x_offset += width;
334        }
335    }
336
337    /// Resize the buffer so that the mapped area matches the given area and that the buffer
338    /// length is equal to area.width * area.height
339    pub fn resize(&mut self, area: Rect) {
340        let length = area.area() as usize;
341        if self.content.len() > length {
342            self.content.truncate(length);
343        } else {
344            self.content.resize(length, Default::default());
345        }
346        self.area = area;
347    }
348
349    /// Reset all cells in the buffer
350    pub fn reset(&mut self) {
351        for c in &mut self.content {
352            c.reset();
353        }
354    }
355
356    /// Merge an other buffer into this one
357    pub fn merge(&mut self, other: &Buffer) {
358        let area = self.area.union(other.area);
359        let cell: Cell = Default::default();
360        self.content.resize(area.area() as usize, cell.clone());
361
362        // Move original content to the appropriate space
363        let size = self.area.area() as usize;
364        for i in (0..size).rev() {
365            let (x, y) = self.pos_of(i);
366            // New index in content
367            let k = ((y - area.y) * area.width + x - area.x) as usize;
368            if i != k {
369                self.content[k] = self.content[i].clone();
370                self.content[i] = cell.clone();
371            }
372        }
373
374        // Push content of the other buffer into this one (may erase previous
375        // data)
376        let size = other.area.area() as usize;
377        for i in 0..size {
378            let (x, y) = other.pos_of(i);
379            // New index in content
380            let k = ((y - area.y) * area.width + x - area.x) as usize;
381            self.content[k] = other.content[i].clone();
382        }
383        self.area = area;
384    }
385
386    /// Builds a minimal sequence of coordinates and Cells necessary to update the UI from
387    /// self to other.
388    ///
389    /// We're assuming that buffers are well-formed, that is no double-width cell is followed by
390    /// a non-blank cell.
391    ///
392    /// # Multi-width characters handling:
393    ///
394    /// ```text
395    /// (Index:) `01`
396    /// Prev:    `コ`
397    /// Next:    `aa`
398    /// Updates: `0: a, 1: a'
399    /// ```
400    ///
401    /// ```text
402    /// (Index:) `01`
403    /// Prev:    `a `
404    /// Next:    `コ`
405    /// Updates: `0: コ` (double width symbol at index 0 - skip index 1)
406    /// ```
407    ///
408    /// ```text
409    /// (Index:) `012`
410    /// Prev:    `aaa`
411    /// Next:    `aコ`
412    /// Updates: `0: a, 1: コ` (double width symbol at index 1 - skip index 2)
413    /// ```
414    pub fn diff<'a>(&self, other: &'a Buffer) -> Vec<(u16, u16, &'a Cell)> {
415        let previous_buffer = &self.content;
416        let next_buffer = &other.content;
417        let width = self.area.width;
418
419        let mut updates: Vec<(u16, u16, &Cell)> = vec![];
420        // Cells invalidated by drawing/replacing preceeding multi-width characters:
421        let mut invalidated: usize = 0;
422        // Cells from the current buffer to skip due to preceeding multi-width characters taking their
423        // place (the skipped cells should be blank anyway):
424        let mut to_skip: usize = 0;
425        for (i, (current, previous)) in next_buffer.iter().zip(previous_buffer.iter()).enumerate() {
426            if (current != previous || invalidated > 0) && to_skip == 0 {
427                let x = i as u16 % width;
428                let y = i as u16 / width;
429                updates.push((x, y, &next_buffer[i]));
430            }
431
432            to_skip = current.symbol.width().saturating_sub(1);
433
434            let affected_width = std::cmp::max(current.symbol.width(), previous.symbol.width());
435            invalidated = std::cmp::max(affected_width, invalidated).saturating_sub(1);
436        }
437        updates
438    }
439}
440
441#[cfg(test)]
442mod tests {
443    use super::*;
444
445    fn cell(s: &str) -> Cell {
446        let mut cell = Cell::default();
447        cell.set_symbol(s);
448        cell
449    }
450
451    #[test]
452    fn it_translates_to_and_from_coordinates() {
453        let rect = Rect::new(200, 100, 50, 80);
454        let buf = Buffer::empty(rect);
455
456        // First cell is at the upper left corner.
457        assert_eq!(buf.pos_of(0), (200, 100));
458        assert_eq!(buf.index_of(200, 100), 0);
459
460        // Last cell is in the lower right.
461        assert_eq!(buf.pos_of(buf.content.len() - 1), (249, 179));
462        assert_eq!(buf.index_of(249, 179), buf.content.len() - 1);
463    }
464
465    #[test]
466    #[should_panic(expected = "outside the buffer")]
467    fn pos_of_panics_on_out_of_bounds() {
468        let rect = Rect::new(0, 0, 10, 10);
469        let buf = Buffer::empty(rect);
470
471        // There are a total of 100 cells; zero-indexed means that 100 would be the 101st cell.
472        buf.pos_of(100);
473    }
474
475    #[test]
476    #[should_panic(expected = "outside the buffer")]
477    fn index_of_panics_on_out_of_bounds() {
478        let rect = Rect::new(0, 0, 10, 10);
479        let buf = Buffer::empty(rect);
480
481        // width is 10; zero-indexed means that 10 would be the 11th cell.
482        buf.index_of(10, 0);
483    }
484
485    #[test]
486    fn buffer_set_string() {
487        let area = Rect::new(0, 0, 5, 1);
488        let mut buffer = Buffer::empty(area);
489
490        // Zero-width
491        buffer.set_stringn(0, 0, "aaa", 0, Style::default());
492        assert_eq!(buffer, Buffer::with_lines(vec!["     "]));
493
494        buffer.set_string(0, 0, "aaa", Style::default());
495        assert_eq!(buffer, Buffer::with_lines(vec!["aaa  "]));
496
497        // Width limit:
498        buffer.set_stringn(0, 0, "bbbbbbbbbbbbbb", 4, Style::default());
499        assert_eq!(buffer, Buffer::with_lines(vec!["bbbb "]));
500
501        buffer.set_string(0, 0, "12345", Style::default());
502        assert_eq!(buffer, Buffer::with_lines(vec!["12345"]));
503
504        // Width truncation:
505        buffer.set_string(0, 0, "123456", Style::default());
506        assert_eq!(buffer, Buffer::with_lines(vec!["12345"]));
507    }
508
509    #[test]
510    fn buffer_set_string_double_width() {
511        let area = Rect::new(0, 0, 5, 1);
512        let mut buffer = Buffer::empty(area);
513        buffer.set_string(0, 0, "コン", Style::default());
514        assert_eq!(buffer, Buffer::with_lines(vec!["コン "]));
515
516        // Only 1 space left.
517        buffer.set_string(0, 0, "コンピ", Style::default());
518        assert_eq!(buffer, Buffer::with_lines(vec!["コン "]));
519    }
520
521    #[test]
522    fn buffer_with_lines() {
523        let buffer =
524            Buffer::with_lines(vec!["┌────────┐", "│コンピュ│", "│ーa 上で│", "└────────┘"]);
525        assert_eq!(buffer.area.x, 0);
526        assert_eq!(buffer.area.y, 0);
527        assert_eq!(buffer.area.width, 10);
528        assert_eq!(buffer.area.height, 4);
529    }
530
531    #[test]
532    fn buffer_diffing_empty_empty() {
533        let area = Rect::new(0, 0, 40, 40);
534        let prev = Buffer::empty(area);
535        let next = Buffer::empty(area);
536        let diff = prev.diff(&next);
537        assert_eq!(diff, vec![]);
538    }
539
540    #[test]
541    fn buffer_diffing_empty_filled() {
542        let area = Rect::new(0, 0, 40, 40);
543        let prev = Buffer::empty(area);
544        let next = Buffer::filled(area, Cell::default().set_symbol("a"));
545        let diff = prev.diff(&next);
546        assert_eq!(diff.len(), 40 * 40);
547    }
548
549    #[test]
550    fn buffer_diffing_filled_filled() {
551        let area = Rect::new(0, 0, 40, 40);
552        let prev = Buffer::filled(area, Cell::default().set_symbol("a"));
553        let next = Buffer::filled(area, Cell::default().set_symbol("a"));
554        let diff = prev.diff(&next);
555        assert_eq!(diff, vec![]);
556    }
557
558    #[test]
559    fn buffer_diffing_single_width() {
560        let prev = Buffer::with_lines(vec![
561            "          ",
562            "┌Title─┐  ",
563            "│      │  ",
564            "│      │  ",
565            "└──────┘  ",
566        ]);
567        let next = Buffer::with_lines(vec![
568            "          ",
569            "┌TITLE─┐  ",
570            "│      │  ",
571            "│      │  ",
572            "└──────┘  ",
573        ]);
574        let diff = prev.diff(&next);
575        assert_eq!(
576            diff,
577            vec![
578                (2, 1, &cell("I")),
579                (3, 1, &cell("T")),
580                (4, 1, &cell("L")),
581                (5, 1, &cell("E")),
582            ]
583        );
584    }
585
586    #[test]
587    #[rustfmt::skip]
588    fn buffer_diffing_multi_width() {
589        let prev = Buffer::with_lines(vec![
590            "┌Title─┐  ",
591            "└──────┘  ",
592        ]);
593        let next = Buffer::with_lines(vec![
594            "┌称号──┐  ",
595            "└──────┘  ",
596        ]);
597        let diff = prev.diff(&next);
598        assert_eq!(
599            diff,
600            vec![
601                (1, 0, &cell("称")),
602                // Skipped "i"
603                (3, 0, &cell("号")),
604                // Skipped "l"
605                (5, 0, &cell("─")),
606            ]
607        );
608    }
609
610    #[test]
611    fn buffer_diffing_multi_width_offset() {
612        let prev = Buffer::with_lines(vec!["┌称号──┐"]);
613        let next = Buffer::with_lines(vec!["┌─称号─┐"]);
614
615        let diff = prev.diff(&next);
616        assert_eq!(
617            diff,
618            vec![(1, 0, &cell("─")), (2, 0, &cell("称")), (4, 0, &cell("号")),]
619        );
620    }
621
622    #[test]
623    fn buffer_merge() {
624        let mut one = Buffer::filled(
625            Rect {
626                x: 0,
627                y: 0,
628                width: 2,
629                height: 2,
630            },
631            Cell::default().set_symbol("1"),
632        );
633        let two = Buffer::filled(
634            Rect {
635                x: 0,
636                y: 2,
637                width: 2,
638                height: 2,
639            },
640            Cell::default().set_symbol("2"),
641        );
642        one.merge(&two);
643        assert_eq!(one, Buffer::with_lines(vec!["11", "11", "22", "22"]));
644    }
645
646    #[test]
647    fn buffer_merge2() {
648        let mut one = Buffer::filled(
649            Rect {
650                x: 2,
651                y: 2,
652                width: 2,
653                height: 2,
654            },
655            Cell::default().set_symbol("1"),
656        );
657        let two = Buffer::filled(
658            Rect {
659                x: 0,
660                y: 0,
661                width: 2,
662                height: 2,
663            },
664            Cell::default().set_symbol("2"),
665        );
666        one.merge(&two);
667        assert_eq!(
668            one,
669            Buffer::with_lines(vec!["22  ", "22  ", "  11", "  11"])
670        );
671    }
672
673    #[test]
674    fn buffer_merge3() {
675        let mut one = Buffer::filled(
676            Rect {
677                x: 3,
678                y: 3,
679                width: 2,
680                height: 2,
681            },
682            Cell::default().set_symbol("1"),
683        );
684        let two = Buffer::filled(
685            Rect {
686                x: 1,
687                y: 1,
688                width: 3,
689                height: 4,
690            },
691            Cell::default().set_symbol("2"),
692        );
693        one.merge(&two);
694        let mut merged = Buffer::with_lines(vec!["222 ", "222 ", "2221", "2221"]);
695        merged.area = Rect {
696            x: 1,
697            y: 1,
698            width: 4,
699            height: 4,
700        };
701        assert_eq!(one, merged);
702    }
703}