feat(buffer): add Buffer::cell, cell_mut and index implementations (#1084)

Code which previously called `buf.get(x, y)` or `buf.get_mut(x, y)`
should now use index operators, or be transitioned to `buff.cell()` or
`buf.cell_mut()` for safe access that avoids panics by returning
`Option<&Cell>` and `Option<&mut Cell>`.

The new methods accept `Into<Position>` instead of `x` and `y`
coordinates, which makes them more ergonomic to use.

```rust
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 10));

let cell = buf[(0, 0)];
let cell = buf[Position::new(0, 0)];

let symbol = buf.cell((0, 0)).map(|cell| cell.symbol());
let symbol = buf.cell(Position::new(0, 0)).map(|cell| cell.symbol());

buf[(0, 0)].set_symbol("🐀");
buf[Position::new(0, 0)].set_symbol("🐀");

buf.cell_mut((0, 0)).map(|cell| cell.set_symbol("🐀"));
buf.cell_mut(Position::new(0, 0)).map(|cell| cell.set_symbol("🐀"));
```

The existing `get()` and `get_mut()` methods are marked as deprecated.
These are fairly widely used and we will leave these methods around on
the buffer for a longer time than our normal deprecation approach (2
major release)

Addresses part of: https://github.com/ratatui-org/ratatui/issues/1011

---------

Co-authored-by: EdJoPaTo <rfc-conform-git-commit-email@funny-long-domain-label-everyone-hates-as-it-is-too-long.edjopato.de>
This commit is contained in:
Josh McKinney 2024-08-06 00:40:47 -07:00 committed by GitHub
parent bb71e5ffd4
commit a23ecd9b45
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
30 changed files with 380 additions and 153 deletions

View file

@ -42,7 +42,7 @@ use ratatui::{
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
ExecutableCommand,
},
layout::{Constraint, Layout, Rect},
layout::{Constraint, Layout, Position, Rect},
style::Color,
text::Text,
widgets::Widget,
@ -224,7 +224,7 @@ impl Widget for &mut ColorsWidget {
// pixel below it
let fg = colors[yi * 2][xi];
let bg = colors[yi * 2 + 1][xi];
buf.get_mut(x, y).set_char('▀').set_fg(fg).set_bg(bg);
buf[Position::new(x, y)].set_char('▀').set_fg(fg).set_bg(bg);
}
}
self.frame_count += 1;

View file

@ -247,7 +247,7 @@ impl App {
for (i, cell) in visible_content.enumerate() {
let x = i as u16 % area.width;
let y = i as u16 / area.width;
*buf.get_mut(area.x + x, area.y + y) = cell;
buf[(area.x + x, area.y + y)] = cell;
}
if scrollbar_needed {

View file

@ -19,7 +19,7 @@ impl Widget for RgbSwatch {
let hue = xi as f32 * 360.0 / f32::from(area.width);
let fg = color_from_oklab(hue, Okhsv::max_saturation(), value_fg);
let bg = color_from_oklab(hue, Okhsv::max_saturation(), value_bg);
buf.get_mut(x, y).set_char('▀').set_fg(fg).set_bg(bg);
buf[(x, y)].set_char('▀').set_fg(fg).set_bg(bg);
}
}
}

View file

@ -49,7 +49,7 @@ fn drip(frame_count: usize, area: Rect, buf: &mut Buffer) {
for _ in 0..pixel_count {
let src_x = rng.gen_range(0..area.width);
let src_y = rng.gen_range(1..area.height - 2);
let src = buf.get_mut(src_x, src_y).clone();
let src = buf[(src_x, src_y)].clone();
// 1% of the time, move a blank or pixel (10:1) to the top line of the screen
if rng.gen_ratio(1, 100) {
let dest_x = rng
@ -57,7 +57,7 @@ fn drip(frame_count: usize, area: Rect, buf: &mut Buffer) {
.clamp(area.left(), area.right() - 1);
let dest_y = area.top() + 1;
let dest = buf.get_mut(dest_x, dest_y);
let dest = &mut buf[(dest_x, dest_y)];
// copy the cell to the new location about 1/10 of the time blank out the cell the rest
// of the time. This has the effect of gradually removing the pixels from the screen.
if rng.gen_ratio(1, 10) {
@ -70,8 +70,7 @@ fn drip(frame_count: usize, area: Rect, buf: &mut Buffer) {
let dest_x = src_x;
let dest_y = src_y.saturating_add(1).min(area.bottom() - 2);
// copy the cell to the new location
let dest = buf.get_mut(dest_x, dest_y);
*dest = src;
buf[(dest_x, dest_y)] = src;
}
}
}
@ -101,8 +100,8 @@ fn text(frame_count: usize, area: Rect, buf: &mut Buffer) {
for row in area.rows() {
for col in row.columns() {
let cell = buf.get_mut(col.x, col.y);
let mask_cell = mask_buf.get(col.x, col.y);
let cell = &mut buf[(col.x, col.y)];
let mask_cell = &mut mask_buf[(col.x, col.y)];
cell.set_symbol(mask_cell.symbol());
// blend the mask cell color with the cell color

View file

@ -116,7 +116,7 @@ pub fn render_logo(selected_row: usize, area: Rect, buf: &mut Buffer) {
for (x, (ch1, ch2)) in line1.chars().zip(line2.chars()).enumerate() {
let x = area.left() + x as u16;
let y = area.top() + y as u16;
let cell = buf.get_mut(x, y);
let cell = &mut buf[(x, y)];
let rat_color = THEME.logo.rat;
let term_color = THEME.logo.term;
match (ch1, ch2) {

View file

@ -346,7 +346,7 @@ impl App {
for (i, cell) in visible_content.enumerate() {
let x = i as u16 % area.width;
let y = i as u16 / area.width;
*buf.get_mut(area.x + x, area.y + y) = cell;
buf[(area.x + x, area.y + y)] = cell;
}
if scrollbar_needed {

View file

@ -102,9 +102,7 @@ impl WidgetRef for Hyperlink<'_> {
{
let text = two_chars.collect::<String>();
let hyperlink = format!("\x1B]8;;{}\x07{}\x1B]8;;\x07", self.url, text);
buffer
.get_mut(area.x + i as u16 * 2, area.y)
.set_symbol(hyperlink.as_str());
buffer[(area.x + i as u16 * 2, area.y)].set_symbol(hyperlink.as_str());
}
}
}

View file

@ -133,8 +133,7 @@ impl Backend for TestBackend {
I: Iterator<Item = (u16, u16, &'a Cell)>,
{
for (x, y, c) in content {
let cell = self.buffer.get_mut(x, y);
*cell = c.clone();
self.buffer[(x, y)] = c.clone();
}
Ok(())
}

View file

@ -18,7 +18,7 @@ macro_rules! assert_buffer_eq {
.into_iter()
.enumerate()
.map(|(i, (x, y, cell))| {
let expected_cell = expected.get(x, y);
let expected_cell = &expected[(x, y)];
format!("{i}: at ({x}, {y})\n expected: {expected_cell:?}\n actual: {cell:?}")
})
.collect::<Vec<String>>()

View file

@ -1,9 +1,12 @@
use std::fmt;
use std::{
fmt,
ops::{Index, IndexMut},
};
use unicode_segmentation::UnicodeSegmentation;
use unicode_width::UnicodeWidthStr;
use crate::{buffer::Cell, prelude::*};
use crate::{buffer::Cell, layout::Position, prelude::*};
/// A buffer that maps to the desired content of the terminal after the draw call
///
@ -15,16 +18,34 @@ use crate::{buffer::Cell, prelude::*};
/// # Examples:
///
/// ```
/// use ratatui::{buffer::Cell, prelude::*};
/// use ratatui::{
/// buffer::{Buffer, Cell},
/// layout::{Position, Rect},
/// style::{Color, Style},
/// };
///
/// # fn foo() -> Option<()> {
/// 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");
///
/// // 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,
@ -32,13 +53,12 @@ use crate::{buffer::Cell, prelude::*};
/// "string",
/// Style::default().fg(Color::Red).bg(Color::White),
/// );
/// let cell = buf.get(5, 0);
/// 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);
///
/// buf.get_mut(5, 0).set_char('x');
/// assert_eq!(buf.get(5, 0).symbol(), "x");
/// # Some(())
/// # }
/// ```
#[derive(Default, Clone, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
@ -92,20 +112,101 @@ impl Buffer {
&self.area
}
/// Returns a reference to Cell at the given coordinates
/// 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(note = "Use Buffer[] or Buffer::cell instead")]
#[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 Cell at the given coordinates
/// 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(note = "Use Buffer[] or Buffer::cell_mut instead")]
#[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::{prelude::*, buffer::Cell, layout::Position};
/// 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<P: Into<Position>>(&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::{prelude::*, buffer::Cell, layout::Position};
/// 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<P: Into<Position>>(&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<Cell>` for the given global (x, y) coordinates.
///
/// Global coordinates are offset by the Buffer's area offset (`x`/`y`).
@ -114,8 +215,7 @@ impl Buffer {
///
/// ```
/// # use ratatui::prelude::*;
/// let rect = Rect::new(200, 100, 10, 10);
/// let buffer = Buffer::empty(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);
/// ```
@ -126,23 +226,37 @@ impl Buffer {
///
/// ```should_panic
/// # use ratatui::prelude::*;
/// let rect = Rect::new(200, 100, 10, 10);
/// let buffer = Buffer::empty(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 {
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
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<Cell>` 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 <https://github.com/ratatui-org/ratatui/issues/1122>
#[must_use]
const fn index_of_opt(&self, position: Position) -> Option<usize> {
let area = self.area;
if !area.contains(position) {
return None;
}
// remove offset
let y = position.y - self.area.y;
let x = position.x - self.area.x;
Some((y * self.area.width + x) as usize)
}
/// Returns the (global) coordinates of a cell given its index
@ -170,6 +284,7 @@ impl Buffer {
/// // 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, i: usize) -> (u16, u16) {
debug_assert!(
i < self.content.len(),
@ -219,12 +334,12 @@ impl Buffer {
});
let style = style.into();
for (symbol, width) in graphemes {
self.get_mut(x, y).set_symbol(symbol).set_style(style);
self[(x, y)].set_symbol(symbol).set_style(style);
let next_symbol = x + width;
x += 1;
// Reset following cells if multi-width (they would be hidden by the grapheme),
while x < next_symbol {
self.get_mut(x, y).reset();
self[(x, y)].reset();
x += 1;
}
}
@ -267,7 +382,7 @@ impl Buffer {
let area = self.area.intersection(area);
for y in area.top()..area.bottom() {
for x in area.left()..area.right() {
self.get_mut(x, y).set_style(style);
self[(x, y)].set_style(style);
}
}
}
@ -373,6 +488,60 @@ impl Buffer {
}
}
impl<P: Into<Position>> Index<P> for Buffer {
type Output = Cell;
/// Returns a reference to the [`Cell`] at the given position.
///
/// This method accepts any value that can be converted to [`Position`] (e.g. `(x, y)` or
/// `Position::new(x, y)`).
///
/// # Panics
///
/// May panic if the given position is outside the buffer's area. For a method that returns
/// `None` instead of panicking, use [`Buffer::cell`](Self::cell).
///
/// # Examples
///
/// ```
/// # use ratatui::{prelude::*, buffer::Cell, layout::Position};
/// let buf = Buffer::empty(Rect::new(0, 0, 10, 10));
/// let cell = &buf[(0, 0)];
/// let cell = &buf[Position::new(0, 0)];
/// ```
fn index(&self, position: P) -> &Self::Output {
let position = position.into();
let index = self.index_of(position.x, position.y);
&self.content[index]
}
}
impl<P: Into<Position>> IndexMut<P> for Buffer {
/// Returns a mutable reference to the [`Cell`] at the given position.
///
/// This method accepts any value that can be converted to [`Position`] (e.g. `(x, y)` or
/// `Position::new(x, y)`).
///
/// # Panics
///
/// May panic if the given position is outside the buffer's area. For a method that returns
/// `None` instead of panicking, use [`Buffer::cell_mut`](Self::cell_mut).
///
/// # Examples
///
/// ```
/// # use ratatui::{prelude::*, buffer::Cell, layout::Position};
/// let mut buf = Buffer::empty(Rect::new(0, 0, 10, 10));
/// buf[(0, 0)].set_symbol("A");
/// buf[Position::new(0, 0)].set_symbol("B");
/// ```
fn index_mut(&mut self, position: P) -> &mut Self::Output {
let position = position.into();
let index = self.index_of(position.x, position.y);
&mut self.content[index]
}
}
impl fmt::Debug for Buffer {
/// Writes a debug representation of the buffer to the given formatter.
///
@ -554,17 +723,90 @@ mod tests {
let buf = Buffer::empty(rect);
// There are a total of 100 cells; zero-indexed means that 100 would be the 101st cell.
buf.pos_of(100);
let _ = buf.pos_of(100);
}
#[rstest]
#[case::left(9, 10)]
#[case::top(10, 9)]
#[case::right(20, 10)]
#[case::bottom(10, 20)]
#[should_panic(
expected = "index outside of buffer: the area is Rect { x: 10, y: 10, width: 10, height: 10 } but index is"
)]
fn index_of_panics_on_out_of_bounds(#[case] x: u16, #[case] y: u16) {
let _ = Buffer::empty(Rect::new(10, 10, 10, 10)).index_of(x, y);
}
#[test]
#[should_panic(expected = "outside the buffer")]
fn index_of_panics_on_out_of_bounds() {
let rect = Rect::new(0, 0, 10, 10);
let buf = Buffer::empty(rect);
fn test_cell() {
let buf = Buffer::with_lines(["Hello", "World"]);
// width is 10; zero-indexed means that 10 would be the 11th cell.
buf.index_of(10, 0);
let mut expected = Cell::default();
expected.set_symbol("H");
assert_eq!(buf.cell((0, 0)), Some(&expected));
assert_eq!(buf.cell((10, 10)), None);
assert_eq!(buf.cell(Position::new(0, 0)), Some(&expected));
assert_eq!(buf.cell(Position::new(10, 10)), None);
}
#[test]
fn test_cell_mut() {
let mut buf = Buffer::with_lines(["Hello", "World"]);
let mut expected = Cell::default();
expected.set_symbol("H");
assert_eq!(buf.cell_mut((0, 0)), Some(&mut expected));
assert_eq!(buf.cell_mut((10, 10)), None);
assert_eq!(buf.cell_mut(Position::new(0, 0)), Some(&mut expected));
assert_eq!(buf.cell_mut(Position::new(10, 10)), None);
}
#[test]
fn index() {
let buf = Buffer::with_lines(["Hello", "World"]);
let mut expected = Cell::default();
expected.set_symbol("H");
assert_eq!(buf[(0, 0)], expected);
}
#[rstest]
#[case::left(9, 10)]
#[case::top(10, 9)]
#[case::right(20, 10)]
#[case::bottom(10, 20)]
#[should_panic(
expected = "index outside of buffer: the area is Rect { x: 10, y: 10, width: 10, height: 10 } but index is"
)]
fn index_out_of_bounds_panics(#[case] x: u16, #[case] y: u16) {
let rect = Rect::new(10, 10, 10, 10);
let buf = Buffer::empty(rect);
let _ = buf[(x, y)];
}
#[test]
fn index_mut() {
let mut buf = Buffer::with_lines(["Cat", "Dog"]);
buf[(0, 0)].set_symbol("B");
buf[Position::new(0, 1)].set_symbol("L");
assert_eq!(buf, Buffer::with_lines(["Bat", "Log"]));
}
#[rstest]
#[case::left(9, 10)]
#[case::top(10, 9)]
#[case::right(20, 10)]
#[case::bottom(10, 20)]
#[should_panic(
expected = "index outside of buffer: the area is Rect { x: 10, y: 10, width: 10, height: 10 } but index is"
)]
fn index_mut_out_of_bounds_panics(#[case] x: u16, #[case] y: u16) {
let mut buf = Buffer::empty(Rect::new(10, 10, 10, 10));
buf[(x, y)].set_symbol("A");
}
#[test]

View file

@ -291,7 +291,7 @@ impl Rect {
/// # use ratatui::prelude::*;
/// fn render(area: Rect, buf: &mut Buffer) {
/// for position in area.positions() {
/// buf.get_mut(position.x, position.y).set_symbol("x");
/// buf[(position.x, position.y)].set_symbol("x");
/// }
/// }
/// ```

View file

@ -180,7 +180,7 @@ impl fmt::Debug for Modifier {
/// ];
/// let mut buffer = Buffer::empty(Rect::new(0, 0, 1, 1));
/// for style in &styles {
/// buffer.get_mut(0, 0).set_style(*style);
/// buffer[(0, 0)].set_style(*style);
/// }
/// assert_eq!(
/// Style {
@ -191,7 +191,7 @@ impl fmt::Debug for Modifier {
/// add_modifier: Modifier::BOLD | Modifier::UNDERLINED,
/// sub_modifier: Modifier::empty(),
/// },
/// buffer.get(0, 0).style(),
/// buffer[(0, 0)].style(),
/// );
/// ```
///
@ -209,7 +209,7 @@ impl fmt::Debug for Modifier {
/// ];
/// let mut buffer = Buffer::empty(Rect::new(0, 0, 1, 1));
/// for style in &styles {
/// buffer.get_mut(0, 0).set_style(*style);
/// buffer[(0, 0)].set_style(*style);
/// }
/// assert_eq!(
/// Style {
@ -220,7 +220,7 @@ impl fmt::Debug for Modifier {
/// add_modifier: Modifier::empty(),
/// sub_modifier: Modifier::empty(),
/// },
/// buffer.get(0, 0).style(),
/// buffer[(0, 0)].style(),
/// );
/// ```
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash)]
@ -595,9 +595,9 @@ mod tests {
let mut buffer = Buffer::empty(Rect::new(0, 0, 1, 1));
for m in mods {
buffer.get_mut(0, 0).set_style(Style::reset());
buffer.get_mut(0, 0).set_style(Style::new().add_modifier(m));
let style = buffer.get(0, 0).style();
buffer[(0, 0)].set_style(Style::reset());
buffer[(0, 0)].set_style(Style::new().add_modifier(m));
let style = buffer[(0, 0)].style();
assert!(style.add_modifier.contains(m));
assert!(!style.sub_modifier.contains(m));
}

View file

@ -384,23 +384,23 @@ impl WidgetRef for Span<'_> {
if i == 0 {
// the first grapheme is always set on the cell
buf.get_mut(x, y)
buf[(x, y)]
.set_symbol(grapheme.symbol)
.set_style(grapheme.style);
} else if x == area.x {
// there is one or more zero-width graphemes in the first cell, so the first cell
// must be appended to.
buf.get_mut(x, y)
buf[(x, y)]
.append_symbol(grapheme.symbol)
.set_style(grapheme.style);
} else if symbol_width == 0 {
// append zero-width graphemes to the previous cell
buf.get_mut(x - 1, y)
buf[(x - 1, y)]
.append_symbol(grapheme.symbol)
.set_style(grapheme.style);
} else {
// just a normal grapheme (not first, not zero-width, not overflowing the area)
buf.get_mut(x, y)
buf[(x, y)]
.set_symbol(grapheme.symbol)
.set_style(grapheme.style);
}
@ -411,7 +411,7 @@ impl WidgetRef for Span<'_> {
for x_hidden in (x + 1)..next_x {
// it may seem odd that the style of the hidden cells are not set to the style of
// the grapheme, but this is how the existing buffer.set_span() method works.
buf.get_mut(x_hidden, y).reset();
buf[(x_hidden, y)].reset();
}
x = next_x;
}

View file

@ -431,7 +431,7 @@ impl BarChart<'_> {
} else {
self.bar_set.empty
};
buf.get_mut(bars_area.left() + x, bar_y)
buf[(bars_area.left() + x, bar_y)]
.set_symbol(symbol)
.set_style(bar_style);
}
@ -507,7 +507,7 @@ impl BarChart<'_> {
let bar_style = self.bar_style.patch(bar.style);
for x in 0..self.bar_width {
buf.get_mut(bar_x + x, area.top() + j)
buf[(bar_x + x, area.top() + j)]
.set_symbol(symbol)
.set_style(bar_style);
}
@ -698,7 +698,7 @@ mod tests {
"f b ",
]);
for (x, y) in iproduct!([0, 2], [0, 1]) {
expected.get_mut(x, y).set_fg(Color::Red);
expected[(x, y)].set_fg(Color::Red);
}
assert_eq!(buffer, expected);
}
@ -790,8 +790,8 @@ mod tests {
"█1█ █2█ ",
"foo bar ",
]);
expected.get_mut(1, 1).set_fg(Color::Red);
expected.get_mut(5, 1).set_fg(Color::Red);
expected[(1, 1)].set_fg(Color::Red);
expected[(5, 1)].set_fg(Color::Red);
assert_eq!(buffer, expected);
}
@ -808,8 +808,8 @@ mod tests {
"1 2 ",
"f b ",
]);
expected.get_mut(0, 2).set_fg(Color::Red);
expected.get_mut(2, 2).set_fg(Color::Red);
expected[(0, 2)].set_fg(Color::Red);
expected[(2, 2)].set_fg(Color::Red);
assert_eq!(buffer, expected);
}
@ -827,7 +827,7 @@ mod tests {
"f b ",
]);
for (x, y) in iproduct!(0..10, 0..3) {
expected.get_mut(x, y).set_fg(Color::Red);
expected[(x, y)].set_fg(Color::Red);
}
assert_eq!(buffer, expected);
}
@ -958,9 +958,9 @@ mod tests {
let mut expected = Buffer::with_lines(["label", "5████"]);
// first line has a yellow foreground. first cell contains italic "5"
expected.get_mut(0, 1).modifier.insert(Modifier::ITALIC);
expected[(0, 1)].modifier.insert(Modifier::ITALIC);
for x in 0..5 {
expected.get_mut(x, 1).set_fg(Color::Yellow);
expected[(x, 1)].set_fg(Color::Yellow);
}
let expected_color = bar_color.unwrap_or(Color::Yellow);
@ -968,13 +968,13 @@ mod tests {
// second line contains the word "label". Since the bar value is 2,
// then the first 2 characters of "label" are italic red.
// the rest is white (using the Bar's style).
let cell = expected.get_mut(0, 0).set_fg(Color::Red);
let cell = expected[(0, 0)].set_fg(Color::Red);
cell.modifier.insert(Modifier::ITALIC);
let cell = expected.get_mut(1, 0).set_fg(Color::Red);
let cell = expected[(1, 0)].set_fg(Color::Red);
cell.modifier.insert(Modifier::ITALIC);
expected.get_mut(2, 0).set_fg(expected_color);
expected.get_mut(3, 0).set_fg(expected_color);
expected.get_mut(4, 0).set_fg(expected_color);
expected[(2, 0)].set_fg(expected_color);
expected[(3, 0)].set_fg(expected_color);
expected[(4, 0)].set_fg(expected_color);
assert_eq!(buffer, expected);
}
@ -1027,9 +1027,9 @@ mod tests {
// bold: because of BarChart::label_style
// red: is included with the label itself
let mut expected = Buffer::with_lines(["2████", "G1 "]);
let cell = expected.get_mut(0, 1).set_fg(Color::Red);
let cell = expected[(0, 1)].set_fg(Color::Red);
cell.modifier.insert(Modifier::BOLD);
let cell = expected.get_mut(1, 1).set_fg(Color::Red);
let cell = expected[(1, 1)].set_fg(Color::Red);
cell.modifier.insert(Modifier::BOLD);
assert_eq!(buffer, expected);

View file

@ -711,7 +711,7 @@ impl Block<'_> {
fn render_left_side(&self, area: Rect, buf: &mut Buffer) {
if self.borders.contains(Borders::LEFT) {
for y in area.top()..area.bottom() {
buf.get_mut(area.left(), y)
buf[(area.left(), y)]
.set_symbol(self.border_set.vertical_left)
.set_style(self.border_style);
}
@ -721,7 +721,7 @@ impl Block<'_> {
fn render_top_side(&self, area: Rect, buf: &mut Buffer) {
if self.borders.contains(Borders::TOP) {
for x in area.left()..area.right() {
buf.get_mut(x, area.top())
buf[(x, area.top())]
.set_symbol(self.border_set.horizontal_top)
.set_style(self.border_style);
}
@ -732,7 +732,7 @@ impl Block<'_> {
if self.borders.contains(Borders::RIGHT) {
let x = area.right() - 1;
for y in area.top()..area.bottom() {
buf.get_mut(x, y)
buf[(x, y)]
.set_symbol(self.border_set.vertical_right)
.set_style(self.border_style);
}
@ -743,7 +743,7 @@ impl Block<'_> {
if self.borders.contains(Borders::BOTTOM) {
let y = area.bottom() - 1;
for x in area.left()..area.right() {
buf.get_mut(x, y)
buf[(x, y)]
.set_symbol(self.border_set.horizontal_bottom)
.set_style(self.border_style);
}
@ -752,7 +752,7 @@ impl Block<'_> {
fn render_bottom_right_corner(&self, buf: &mut Buffer, area: Rect) {
if self.borders.contains(Borders::RIGHT | Borders::BOTTOM) {
buf.get_mut(area.right() - 1, area.bottom() - 1)
buf[(area.right() - 1, area.bottom() - 1)]
.set_symbol(self.border_set.bottom_right)
.set_style(self.border_style);
}
@ -760,7 +760,7 @@ impl Block<'_> {
fn render_top_right_corner(&self, buf: &mut Buffer, area: Rect) {
if self.borders.contains(Borders::RIGHT | Borders::TOP) {
buf.get_mut(area.right() - 1, area.top())
buf[(area.right() - 1, area.top())]
.set_symbol(self.border_set.top_right)
.set_style(self.border_style);
}
@ -768,7 +768,7 @@ impl Block<'_> {
fn render_bottom_left_corner(&self, buf: &mut Buffer, area: Rect) {
if self.borders.contains(Borders::LEFT | Borders::BOTTOM) {
buf.get_mut(area.left(), area.bottom() - 1)
buf[(area.left(), area.bottom() - 1)]
.set_symbol(self.border_set.bottom_left)
.set_style(self.border_style);
}
@ -776,7 +776,7 @@ impl Block<'_> {
fn render_top_left_corner(&self, buf: &mut Buffer, area: Rect) {
if self.borders.contains(Borders::LEFT | Borders::TOP) {
buf.get_mut(area.left(), area.top())
buf[(area.left(), area.top())]
.set_symbol(self.border_set.top_left)
.set_style(self.border_style);
}

View file

@ -771,7 +771,7 @@ where
(index % width) as u16 + canvas_area.left(),
(index / width) as u16 + canvas_area.top(),
);
let cell = buf.get_mut(x, y).set_char(ch);
let cell = buf[(x, y)].set_char(ch);
if colors.0 != Color::Reset {
cell.set_fg(colors.0);
}

View file

@ -959,14 +959,14 @@ impl WidgetRef for Chart<'_> {
// Sample the style of the entire widget. This sample will be used to reset the style of
// the cells that are part of the components put on top of the grah area (i.e legend and
// axis names).
let original_style = buf.get(area.left(), area.top()).style();
let original_style = buf[(area.left(), area.top())].style();
self.render_x_labels(buf, &layout, chart_area, graph_area);
self.render_y_labels(buf, &layout, chart_area, graph_area);
if let Some(y) = layout.axis_x {
for x in graph_area.left()..graph_area.right() {
buf.get_mut(x, y)
buf[(x, y)]
.set_symbol(symbols::line::HORIZONTAL)
.set_style(self.x_axis.style);
}
@ -974,7 +974,7 @@ impl WidgetRef for Chart<'_> {
if let Some(x) = layout.axis_y {
for y in graph_area.top()..graph_area.bottom() {
buf.get_mut(x, y)
buf[(x, y)]
.set_symbol(symbols::line::VERTICAL)
.set_style(self.y_axis.style);
}
@ -982,7 +982,7 @@ impl WidgetRef for Chart<'_> {
if let Some(y) = layout.axis_x {
if let Some(x) = layout.axis_y {
buf.get_mut(x, y)
buf[(x, y)]
.set_symbol(symbols::line::BOTTOM_LEFT)
.set_style(self.x_axis.style);
}

View file

@ -34,7 +34,7 @@ impl WidgetRef for Clear {
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
for x in area.left()..area.right() {
for y in area.top()..area.bottom() {
buf.get_mut(x, y).reset();
buf[(x, y)].reset();
}
}
}

View file

@ -184,23 +184,23 @@ impl Gauge<'_> {
for y in gauge_area.top()..gauge_area.bottom() {
// render the filled area (left to end)
for x in gauge_area.left()..end {
let cell = buf.get_mut(x, y);
// Use full block for the filled part of the gauge and spaces for the part that is
// covered by the label. Note that the background and foreground colors are swapped
// for the label part, otherwise the gauge will be inverted
if x < label_col || x > label_col + clamped_label_width || y != label_row {
cell.set_symbol(symbols::block::FULL)
buf[(x, y)]
.set_symbol(symbols::block::FULL)
.set_fg(self.gauge_style.fg.unwrap_or(Color::Reset))
.set_bg(self.gauge_style.bg.unwrap_or(Color::Reset));
} else {
cell.set_symbol(" ")
buf[(x, y)]
.set_symbol(" ")
.set_fg(self.gauge_style.bg.unwrap_or(Color::Reset))
.set_bg(self.gauge_style.fg.unwrap_or(Color::Reset));
}
}
if self.use_unicode && self.ratio < 1.0 {
buf.get_mut(end, y)
.set_symbol(get_unicode_block(filled_width % 1.0));
buf[(end, y)].set_symbol(get_unicode_block(filled_width % 1.0));
}
}
// render the label
@ -404,12 +404,12 @@ impl WidgetRef for LineGauge<'_> {
let end = start
+ (f64::from(gauge_area.right().saturating_sub(start)) * self.ratio).floor() as u16;
for col in start..end {
buf.get_mut(col, row)
buf[(col, row)]
.set_symbol(self.line_set.horizontal)
.set_style(self.filled_style);
}
for col in end..gauge_area.right() {
buf.get_mut(col, row)
buf[(col, row)]
.set_symbol(self.line_set.horizontal)
.set_style(self.unfilled_style);
}

View file

@ -442,7 +442,7 @@ impl<'a> Paragraph<'a> {
// If the symbol is empty, the last char which rendered last time will
// leave on the line. It's a quick fix.
let symbol = if symbol.is_empty() { " " } else { symbol };
buf.get_mut(area.left() + x, area.top() + y - self.scroll.y)
buf[(area.left() + x, area.top() + y - self.scroll.y)]
.set_symbol(symbol)
.set_style(*style);
x += width as u16;

View file

@ -192,7 +192,7 @@ impl Sparkline<'_> {
RenderDirection::LeftToRight => spark_area.left() + i as u16,
RenderDirection::RightToLeft => spark_area.right() - i as u16 - 1,
};
buf.get_mut(x, spark_area.top() + j)
buf[(x, spark_area.top() + j)]
.set_symbol(symbol)
.set_style(self.style);

View file

@ -39,21 +39,21 @@ fn barchart_can_be_stylized() {
for y in area.y..area.height {
// background
for x in area.x..area.width {
expected.get_mut(x, y).set_bg(Color::White);
expected[(x, y)].set_bg(Color::White);
}
// bars
for x in [0, 1, 3, 4, 6, 7] {
expected.get_mut(x, y).set_fg(Color::Red);
expected[(x, y)].set_fg(Color::Red);
}
}
// values
for x in 0..3 {
expected.get_mut(x * 3, 3).set_fg(Color::Green);
expected[(x * 3, 3)].set_fg(Color::Green);
}
// labels
for x in 0..3 {
expected.get_mut(x * 3, 4).set_fg(Color::Blue);
expected.get_mut(x * 3 + 1, 4).set_fg(Color::Reset);
expected[(x * 3, 4)].set_fg(Color::Blue);
expected[(x * 3 + 1, 4)].set_fg(Color::Reset);
}
terminal.backend().assert_buffer(&expected);
}
@ -80,14 +80,11 @@ fn block_can_be_stylized() -> io::Result<()> {
]);
for x in area.x..area.width {
for y in area.y..area.height {
expected
.get_mut(x, y)
.set_fg(Color::Cyan)
.set_bg(Color::Cyan);
expected[(x, y)].set_fg(Color::Cyan).set_bg(Color::Cyan);
}
}
for x in 1..=5 {
expected.get_mut(x, 0).set_fg(Color::LightBlue);
expected[(x, 0)].set_fg(Color::LightBlue);
}
terminal.backend().assert_buffer(&expected);
Ok(())
@ -105,7 +102,7 @@ fn paragraph_can_be_stylized() -> io::Result<()> {
let mut expected = Buffer::with_lines(["Text "]);
for x in 0..4 {
expected.get_mut(x, 0).set_fg(Color::Cyan);
expected[(x, 0)].set_fg(Color::Cyan);
}
terminal.backend().assert_buffer(&expected);
Ok(())

View file

@ -36,14 +36,14 @@ fn terminal_draw_returns_the_completed_frame() -> Result<(), Box<dyn Error>> {
let paragraph = Paragraph::new("Test");
f.render_widget(paragraph, f.area());
})?;
assert_eq!(frame.buffer.get(0, 0).symbol(), "T");
assert_eq!(frame.buffer[(0, 0)].symbol(), "T");
assert_eq!(frame.area, Rect::new(0, 0, 10, 10));
terminal.backend_mut().resize(8, 8);
let frame = terminal.draw(|f| {
let paragraph = Paragraph::new("test");
f.render_widget(paragraph, f.area());
})?;
assert_eq!(frame.buffer.get(0, 0).symbol(), "t");
assert_eq!(frame.buffer[(0, 0)].symbol(), "t");
assert_eq!(frame.area, Rect::new(0, 0, 8, 8));
Ok(())
}

View file

@ -83,11 +83,11 @@ fn widgets_barchart_group() {
]);
for y in 1..(TERMINAL_HEIGHT - 3) {
for x in 1..5 {
expected.get_mut(x, y).set_fg(Color::Red);
expected.get_mut(x + 5, y).set_fg(Color::Green);
expected[(x, y)].set_fg(Color::Red);
expected[(x + 5, y)].set_fg(Color::Green);
}
}
expected.get_mut(2, 7).set_fg(Color::Blue);
expected.get_mut(3, 7).set_fg(Color::Blue);
expected[(2, 7)].set_fg(Color::Blue);
expected[(3, 7)].set_fg(Color::Blue);
terminal.backend().assert_buffer(&expected);
}

View file

@ -34,7 +34,7 @@ fn widgets_block_renders() {
" ",
]);
for x in 1..=5 {
expected.get_mut(x, 0).set_fg(Color::LightBlue);
expected[(x, 0)].set_fg(Color::LightBlue);
}
terminal.backend().assert_buffer(&expected);
}

View file

@ -32,11 +32,11 @@ fn widgets_canvas_draw_labels() {
let mut expected = Buffer::with_lines(["", "", "", "", "test "]);
for row in 0..5 {
for col in 0..5 {
expected.get_mut(col, row).set_bg(Color::Yellow);
expected[(col, row)].set_bg(Color::Yellow);
}
}
for col in 0..4 {
expected.get_mut(col, 4).set_fg(Color::Blue);
expected[(col, 4)].set_fg(Color::Blue);
}
terminal.backend().assert_buffer(&expected);
}

View file

@ -470,7 +470,7 @@ fn widgets_chart_can_have_a_legend() {
// Set expected background color
for row in 0..30 {
for col in 0..60 {
expected.get_mut(col, row).set_bg(Color::White);
expected[(col, row)].set_bg(Color::White);
}
}
@ -532,10 +532,10 @@ fn widgets_chart_can_have_a_legend() {
(57, 2),
];
for (col, row) in line1 {
expected.get_mut(col, row).set_fg(Color::Blue);
expected[(col, row)].set_fg(Color::Blue);
}
for (col, row) in legend1 {
expected.get_mut(col, row).set_fg(Color::Blue);
expected[(col, row)].set_fg(Color::Blue);
}
// Set expected colors of the second dataset
@ -603,16 +603,16 @@ fn widgets_chart_can_have_a_legend() {
(57, 3),
];
for (col, row) in line2 {
expected.get_mut(col, row).set_fg(Color::Green);
expected[(col, row)].set_fg(Color::Green);
}
for (col, row) in legend2 {
expected.get_mut(col, row).set_fg(Color::Green);
expected[(col, row)].set_fg(Color::Green);
}
// Set expected colors of the x axis
let x_axis_title = vec![(53, 26), (54, 26), (55, 26), (56, 26), (57, 26), (58, 26)];
for (col, row) in x_axis_title {
expected.get_mut(col, row).set_fg(Color::Yellow);
expected[(col, row)].set_fg(Color::Yellow);
}
terminal.backend().assert_buffer(&expected);
}

View file

@ -215,13 +215,13 @@ fn widgets_line_gauge_renders() {
"└──────────────────┘",
]);
for col in 4..10 {
expected.get_mut(col, 0).set_fg(Color::Green);
expected[(col, 0)].set_fg(Color::Green);
}
for col in 10..20 {
expected.get_mut(col, 0).set_fg(Color::White);
expected[(col, 0)].set_fg(Color::White);
}
for col in 5..7 {
expected.get_mut(col, 2).set_fg(Color::Green);
expected[(col, 2)].set_fg(Color::Green);
}
terminal.backend().assert_buffer(&expected);
}

View file

@ -52,7 +52,7 @@ fn widgets_list_should_highlight_the_selected_item() {
" Item 3 ",
]);
for x in 0..10 {
expected.get_mut(x, 1).set_bg(Color::Yellow);
expected[(x, 1)].set_bg(Color::Yellow);
}
terminal.backend().assert_buffer(&expected);
}
@ -86,7 +86,7 @@ fn widgets_list_should_highlight_the_selected_item_wide_symbol() {
" Item 3 ",
]);
for x in 0..10 {
expected.get_mut(x, 1).set_bg(Color::Yellow);
expected[(x, 1)].set_bg(Color::Yellow);
}
terminal.backend().assert_buffer(&expected);
}
@ -217,8 +217,8 @@ fn widgets_list_should_display_multiline_items() {
" Item 3c",
]);
for x in 0..10 {
expected.get_mut(x, 2).set_bg(Color::Yellow);
expected.get_mut(x, 3).set_bg(Color::Yellow);
expected[(x, 2)].set_bg(Color::Yellow);
expected[(x, 3)].set_bg(Color::Yellow);
}
terminal.backend().assert_buffer(&expected);
}
@ -252,8 +252,8 @@ fn widgets_list_should_repeat_highlight_symbol() {
" Item 3c",
]);
for x in 0..10 {
expected.get_mut(x, 2).set_bg(Color::Yellow);
expected.get_mut(x, 3).set_bg(Color::Yellow);
expected[(x, 2)].set_bg(Color::Yellow);
expected[(x, 3)].set_bg(Color::Yellow);
}
terminal.backend().assert_buffer(&expected);
}

View file

@ -672,7 +672,7 @@ fn widgets_table_can_have_elements_styled_individually() {
]);
// First row = row color + highlight style
for col in 1..=28 {
expected.get_mut(col, 2).set_style(
expected[(col, 2)].set_style(
Style::default()
.fg(Color::Green)
.add_modifier(Modifier::BOLD),
@ -681,26 +681,18 @@ fn widgets_table_can_have_elements_styled_individually() {
// Second row:
// 1. row color
for col in 1..=28 {
expected
.get_mut(col, 3)
.set_style(Style::default().fg(Color::LightGreen));
expected[(col, 3)].set_style(Style::default().fg(Color::LightGreen));
}
// 2. cell color
for col in 11..=16 {
expected
.get_mut(col, 3)
.set_style(Style::default().fg(Color::Yellow));
expected[(col, 3)].set_style(Style::default().fg(Color::Yellow));
}
for col in 18..=23 {
expected
.get_mut(col, 3)
.set_style(Style::default().fg(Color::Red));
expected[(col, 3)].set_style(Style::default().fg(Color::Red));
}
// 3. text color
for col in 21..=22 {
expected
.get_mut(col, 3)
.set_style(Style::default().fg(Color::Blue));
expected[(col, 3)].set_style(Style::default().fg(Color::Blue));
}
terminal.backend().assert_buffer(&expected);
}