use std::fmt; use unicode_segmentation::UnicodeSegmentation; use unicode_width::UnicodeWidthStr; use crate::{buffer::Cell, prelude::*}; /// A buffer that maps to the desired content of the terminal after the draw call /// /// No widget in the library interacts directly with the terminal. Instead each of them is required /// to draw their state to an intermediate buffer. It is basically a grid where each cell contains /// a grapheme, a foreground color and a background color. This grid will then be used to output /// the appropriate escape sequences and characters to draw the UI as the user has defined it. /// /// # Examples: /// /// ``` /// use ratatui::{buffer::Cell, prelude::*}; /// /// let mut buf = Buffer::empty(Rect { /// x: 0, /// y: 0, /// width: 10, /// height: 5, /// }); /// buf.get_mut(0, 2).set_symbol("x"); /// assert_eq!(buf.get(0, 2).symbol(), "x"); /// /// buf.set_string( /// 3, /// 0, /// "string", /// Style::default().fg(Color::Red).bg(Color::White), /// ); /// let cell = buf.get(5, 0); /// assert_eq!(cell.symbol(), "r"); /// assert_eq!(cell.fg, Color::Red); /// assert_eq!(cell.bg, Color::White); /// /// buf.get_mut(5, 0).set_char('x'); /// assert_eq!(buf.get(5, 0).symbol(), "x"); /// ``` #[derive(Default, Clone, Eq, PartialEq, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Buffer { /// The area represented by this buffer pub area: Rect, /// The content of the buffer. The length of this Vec should always be equal to area.width * /// area.height pub content: Vec, } impl Buffer { /// Returns a Buffer with all cells set to the default one #[must_use] pub fn empty(area: Rect) -> Self { Self::filled(area, Cell::EMPTY) } /// Returns a Buffer with all cells initialized with the attributes of the given Cell #[must_use] pub fn filled(area: Rect, cell: Cell) -> Self { let size = area.area() as usize; let content = vec![cell; size]; Self { area, content } } /// Returns a Buffer containing the given lines #[must_use] pub fn with_lines<'a, Iter>(lines: Iter) -> Self where Iter: IntoIterator, Iter::Item: Into>, { let lines = lines.into_iter().map(Into::into).collect::>(); let height = lines.len() as u16; let width = lines.iter().map(Line::width).max().unwrap_or_default() as u16; let mut buffer = Self::empty(Rect::new(0, 0, width, height)); for (y, line) in lines.iter().enumerate() { buffer.set_line(0, y as u16, line, width); } buffer } /// Returns the content of the buffer as a slice pub fn content(&self) -> &[Cell] { &self.content } /// Returns the area covered by this buffer pub const fn area(&self) -> &Rect { &self.area } /// Returns a reference to Cell at the given coordinates #[track_caller] pub fn get(&self, x: u16, y: u16) -> &Cell { let i = self.index_of(x, y); &self.content[i] } /// Returns a mutable reference to Cell at the given coordinates #[track_caller] pub fn get_mut(&mut self, x: u16, y: u16) -> &mut Cell { let i = self.index_of(x, y); &mut self.content[i] } /// Returns the index in the `Vec` for the given global (x, y) coordinates. /// /// Global coordinates are offset by the Buffer's area offset (`x`/`y`). /// /// # Examples /// /// ``` /// # use ratatui::prelude::*; /// let rect = Rect::new(200, 100, 10, 10); /// let buffer = Buffer::empty(rect); /// // Global coordinates to the top corner of this buffer's area /// assert_eq!(buffer.index_of(200, 100), 0); /// ``` /// /// # Panics /// /// Panics when given an coordinate that is outside of this Buffer's area. /// /// ```should_panic /// # use ratatui::prelude::*; /// let rect = Rect::new(200, 100, 10, 10); /// let buffer = Buffer::empty(rect); /// // Top coordinate is outside of the buffer in global coordinate space, as the Buffer's area /// // starts at (200, 100). /// buffer.index_of(0, 0); // Panics /// ``` #[track_caller] pub fn index_of(&self, x: u16, y: u16) -> usize { debug_assert!( x >= self.area.left() && x < self.area.right() && y >= self.area.top() && y < self.area.bottom(), "Trying to access position outside the buffer: x={x}, y={y}, area={:?}", self.area ); ((y - self.area.y) * self.area.width + (x - self.area.x)) as usize } /// Returns the (global) coordinates of a cell given its index /// /// Global coordinates are offset by the Buffer's area offset (`x`/`y`). /// /// # Examples /// /// ``` /// # use ratatui::prelude::*; /// let rect = Rect::new(200, 100, 10, 10); /// let buffer = Buffer::empty(rect); /// assert_eq!(buffer.pos_of(0), (200, 100)); /// assert_eq!(buffer.pos_of(14), (204, 101)); /// ``` /// /// # Panics /// /// Panics when given an index that is outside the Buffer's content. /// /// ```should_panic /// # use ratatui::prelude::*; /// let rect = Rect::new(0, 0, 10, 10); // 100 cells in total /// let buffer = Buffer::empty(rect); /// // Index 100 is the 101th cell, which lies outside of the area of this Buffer. /// buffer.pos_of(100); // Panics /// ``` pub fn pos_of(&self, i: usize) -> (u16, u16) { debug_assert!( i < self.content.len(), "Trying to get the coords of a cell outside the buffer: i={i} len={}", self.content.len() ); ( self.area.x + (i as u16) % self.area.width, self.area.y + (i as u16) / self.area.width, ) } /// Print a string, starting at the position (x, y) pub fn set_string(&mut self, x: u16, y: u16, string: T, style: S) where T: AsRef, S: Into