use alloc::vec; use alloc::vec::Vec; use core::ops::{Index, IndexMut}; use core::{cmp, fmt}; use unicode_segmentation::UnicodeSegmentation; use unicode_width::UnicodeWidthStr; use crate::buffer::Cell; use crate::layout::{Position, Rect}; use crate::style::Style; use crate::text::{Line, Span}; /// 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_core::buffer::{Buffer, Cell}; /// use ratatui_core::layout::{Position, Rect}; /// use ratatui_core::style::{Color, Style}; /// /// # fn foo() -> Option<()> { /// let mut buf = Buffer::empty(Rect { /// x: 0, /// y: 0, /// width: 10, /// height: 5, /// }); /// /// // indexing using Position /// buf[Position { x: 0, y: 0 }].set_symbol("A"); /// assert_eq!(buf[Position { x: 0, y: 0 }].symbol(), "A"); /// /// // indexing using (x, y) tuple (which is converted to Position) /// buf[(0, 1)].set_symbol("B"); /// assert_eq!(buf[(0, 1)].symbol(), "x"); /// /// // getting an Option instead of panicking if the position is outside the buffer /// let cell = buf.cell_mut(Position { x: 0, y: 2 })?; /// cell.set_symbol("C"); /// /// let cell = buf.cell(Position { x: 0, y: 2 })?; /// assert_eq!(cell.symbol(), "C"); /// /// buf.set_string( /// 3, /// 0, /// "string", /// Style::default().fg(Color::Red).bg(Color::White), /// ); /// let cell = &buf[(5, 0)]; // cannot move out of buf, so we borrow it /// assert_eq!(cell.symbol(), "r"); /// assert_eq!(cell.fg, Color::Red); /// assert_eq!(cell.bg, Color::White); /// # Some(()) /// # } /// ``` #[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 the [`Cell`] at the given coordinates /// /// Callers should use [`Buffer[]`](Self::index) or [`Buffer::cell`] instead of this method. /// /// Note: idiomatically methods named `get` usually return `Option<&T>`, but this method panics /// instead. This is kept for backwards compatibility. See [`cell`](Self::cell) for a safe /// alternative. /// /// # Panics /// /// Panics if the index is out of bounds. #[track_caller] #[deprecated = "use `Buffer[(x, y)]` instead. To avoid panicking, use `Buffer::cell((x, y))`. Both methods take `impl Into`."] #[must_use] pub fn get(&self, x: u16, y: u16) -> &Cell { let i = self.index_of(x, y); &self.content[i] } /// Returns a mutable reference to the [`Cell`] at the given coordinates. /// /// Callers should use [`Buffer[]`](Self::index_mut) or [`Buffer::cell_mut`] instead of this /// method. /// /// Note: idiomatically methods named `get_mut` usually return `Option<&mut T>`, but this method /// panics instead. This is kept for backwards compatibility. See [`cell_mut`](Self::cell_mut) /// for a safe alternative. /// /// # Panics /// /// Panics if the position is outside the `Buffer`'s area. #[track_caller] #[deprecated = "use `Buffer[(x, y)]` instead. To avoid panicking, use `Buffer::cell_mut((x, y))`. Both methods take `impl Into`."] #[must_use] pub fn get_mut(&mut self, x: u16, y: u16) -> &mut Cell { let i = self.index_of(x, y); &mut self.content[i] } /// Returns a reference to the [`Cell`] at the given position or [`None`] if the position is /// outside the `Buffer`'s area. /// /// This method accepts any value that can be converted to [`Position`] (e.g. `(x, y)` or /// `Position::new(x, y)`). /// /// For a method that panics when the position is outside the buffer instead of returning /// `None`, use [`Buffer[]`](Self::index). /// /// # Examples /// /// ```rust /// use ratatui_core::buffer::{Buffer, Cell}; /// use ratatui_core::layout::{Position, Rect}; /// /// let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 10)); /// /// assert_eq!(buffer.cell(Position::new(0, 0)), Some(&Cell::default())); /// assert_eq!(buffer.cell(Position::new(10, 10)), None); /// assert_eq!(buffer.cell((0, 0)), Some(&Cell::default())); /// assert_eq!(buffer.cell((10, 10)), None); /// ``` #[must_use] pub fn cell>(&self, position: P) -> Option<&Cell> { let position = position.into(); let index = self.index_of_opt(position)?; self.content.get(index) } /// Returns a mutable reference to the [`Cell`] at the given position or [`None`] if the /// position is outside the `Buffer`'s area. /// /// This method accepts any value that can be converted to [`Position`] (e.g. `(x, y)` or /// `Position::new(x, y)`). /// /// For a method that panics when the position is outside the buffer instead of returning /// `None`, use [`Buffer[]`](Self::index_mut). /// /// # Examples /// /// ```rust /// use ratatui_core::buffer::{Buffer, Cell}; /// use ratatui_core::layout::{Position, Rect}; /// use ratatui_core::style::{Color, Style}; /// let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 10)); /// /// if let Some(cell) = buffer.cell_mut(Position::new(0, 0)) { /// cell.set_symbol("A"); /// } /// if let Some(cell) = buffer.cell_mut((0, 0)) { /// cell.set_style(Style::default().fg(Color::Red)); /// } /// ``` #[must_use] pub fn cell_mut>(&mut self, position: P) -> Option<&mut Cell> { let position = position.into(); let index = self.index_of_opt(position)?; self.content.get_mut(index) } /// 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_core::buffer::Buffer; /// use ratatui_core::layout::Rect; /// /// let buffer = Buffer::empty(Rect::new(200, 100, 10, 10)); /// // 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_core::buffer::Buffer; /// use ratatui_core::layout::Rect; /// /// let buffer = Buffer::empty(Rect::new(200, 100, 10, 10)); /// // 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] #[must_use] pub fn index_of(&self, x: u16, y: u16) -> usize { self.index_of_opt(Position { x, y }).unwrap_or_else(|| { panic!( "index outside of buffer: the area is {area:?} but index is ({x}, {y})", area = self.area, ) }) } /// Returns the index in the `Vec` for the given global (x, y) coordinates. /// /// Returns `None` if the given coordinates are outside of the Buffer's area. /// /// Note that this is private because of #[must_use] const fn index_of_opt(&self, position: Position) -> Option { let area = self.area; if !area.contains(position) { return None; } // remove offset let y = (position.y - self.area.y) as usize; let x = (position.x - self.area.x) as usize; let width = self.area.width as usize; Some(y * width + x) } /// 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_core::buffer::Buffer; /// use ratatui_core::layout::Rect; /// /// 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_core::buffer::Buffer; /// use ratatui_core::layout::Rect; /// /// 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 /// ``` #[must_use] pub fn pos_of(&self, index: usize) -> (u16, u16) { debug_assert!( index < self.content.len(), "Trying to get the coords of a cell outside the buffer: i={index} len={}", self.content.len() ); let x = index % self.area.width as usize + self.area.x as usize; let y = index / self.area.width as usize + self.area.y as usize; ( u16::try_from(x).expect("x overflow. This should never happen as area.width is u16"), u16::try_from(y).expect("y overflow. This should never happen as area.height is u16"), ) } /// 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