Compare commits

..

13 Commits

Author SHA1 Message Date
Dheepak Krishnamurthy
ccf9d92f10 test: Add more tests for selection and marks behavior 2024-05-13 23:26:47 -04:00
Dheepak Krishnamurthy
b712034644 feat: Show highlight_column only if there are marks 2024-05-13 23:23:57 -04:00
Dheepak Krishnamurthy
38fca62fa9 chore: Use assert_eq instead of assert_buffer_eq 2024-05-13 22:51:31 -04:00
Dheepak Krishnamurthy
3a51a027a6 Merge branch 'main' into kd/multi-select-table 2024-05-13 22:49:22 -04:00
Dheepak Krishnamurthy
5b30f2275c docs: Update docstring for field 2024-05-13 22:45:35 -04:00
Dheepak Krishnamurthy
b4c27c744c chore: Move pretty assert to top of file 2024-05-13 20:05:20 -04:00
Dheepak Krishnamurthy
977a4899c8 chore: Update serialized data representation 2024-05-13 20:03:59 -04:00
Dheepak Krishnamurthy
cd27b4829a docs: use pretty assertion 2024-05-13 20:00:56 -04:00
Dheepak Krishnamurthy
feee871519 docs: fix broken test 2024-05-13 19:57:56 -04:00
Dheepak Krishnamurthy
0051bb2037 build(test): Add more tests 2024-05-13 03:59:28 -04:00
Dheepak Krishnamurthy
477217c77a feat: Better spacing 2024-05-13 03:45:22 -04:00
Dheepak Krishnamurthy
31de3586f7 feat: Add mark, unmark and mark_highlight symbols 2024-05-13 03:27:22 -04:00
Dheepak Krishnamurthy
f702025b75 feat: Add multi-selection for table 2024-05-13 01:20:12 -04:00
40 changed files with 5959 additions and 6039 deletions

16
.cargo-husky/hooks/pre-push Executable file
View File

@@ -0,0 +1,16 @@
#!/usr/bin/env bash
if !(command cargo-make >/dev/null 2>&1); then # Check if cargo-make is installed
echo Attempting to run cargo-make as part of the pre-push hook but it\'s not installed.
echo Please install it by running the following command:
echo
echo " cargo install --force cargo-make"
echo
echo If you don\'t want to run cargo-make as part of the pre-push hook, you can run
echo the following command instead of git push:
echo
echo " git push --no-verify"
exit 1
fi
cargo make ci

10551
CHANGELOG.md

File diff suppressed because it is too large Load Diff

View File

@@ -56,9 +56,11 @@ documented.
### Run CI tests before pushing a PR
Running `cargo make ci` before pushing will perform the same checks that we do in the CI process.
It's not mandatory to do this before pushing, however it may save you time to do so instead of
waiting for GitHub to run the checks.
We're using [cargo-husky](https://github.com/rhysd/cargo-husky) to automatically run git hooks,
which will run `cargo make ci` before each push. To initialize the hook run `cargo test`. If
`cargo-make` is not installed, it will provide instructions to install it for you. This will ensure
that your code is formatted, compiles and passes all tests before you push. If you need to skip this
check, you can use `git push --no-verify`.
### Sign your commits

View File

@@ -1,6 +1,6 @@
[package]
name = "ratatui"
version = "0.26.3" # crate version
version = "0.26.2" # crate version
authors = ["Florian Dehau <work@fdehau.com>", "The Ratatui Developers"]
description = "A library that's all about cooking up terminal user interfaces"
documentation = "https://docs.rs/ratatui/latest/ratatui/"
@@ -47,6 +47,9 @@ unicode-width = "0.1"
anyhow = "1.0.71"
argh = "0.1.12"
better-panic = "0.3.0"
cargo-husky = { version = "1.5.0", default-features = false, features = [
"user-hooks",
] }
color-eyre = "0.6.2"
criterion = { version = "0.5.1", features = ["html_reports"] }
derive_builder = "0.20.0"
@@ -271,12 +274,6 @@ name = "list"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "minimal"
required-features = ["crossterm"]
# prefer to show the more featureful examples in the docs
doc-scrape-examples = false
[[example]]
name = "modifiers"
required-features = ["crossterm"]

View File

@@ -39,7 +39,7 @@ use crossterm::{
ExecutableCommand,
};
use palette::{convert::FromColorUnclamped, Okhsv, Srgb};
use ratatui::{layout::Position, prelude::*};
use ratatui::prelude::*;
#[derive(Debug, Default)]
struct App {
@@ -217,7 +217,7 @@ impl Widget for &mut ColorsWidget {
// pixel below it
let fg = colors[yi * 2][xi];
let bg = colors[yi * 2 + 1][xi];
buf[Position::new(x, y)].set_char('▀').set_fg(fg).set_bg(bg);
buf.get_mut(x, y).set_char('▀').set_fg(fg).set_bg(bg);
}
}
self.frame_count += 1;

View File

@@ -238,7 +238,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[(area.x + x, area.y + y)] = cell;
*buf.get_mut(area.x + x, area.y + y) = cell;
}
if scrollbar_needed {

View File

@@ -245,7 +245,7 @@ fn render_glyph(glyph: [u8; 8], area: Rect, buf: &mut Buffer, pixel_size: PixelS
.clone()
.zip(area.left()..area.right())
{
let cell = &mut buf[(x, y)];
let cell = buf.get_mut(x, y);
let symbol_character = match pixel_size {
PixelSize::Full => match glyph[row] & (1 << col) {
0 => ' ',

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[(x, y)].set_char('▀').set_fg(fg).set_bg(bg);
buf.get_mut(x, y).set_char('▀').set_fg(fg).set_bg(bg);
}
}
}

View File

@@ -45,7 +45,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[(src_x, src_y)].clone();
let src = buf.get_mut(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
@@ -53,7 +53,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 = &mut buf[(dest_x, dest_y)];
let dest = buf.get_mut(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) {
@@ -66,7 +66,8 @@ 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
buf[(dest_x, dest_y)] = src;
let dest = buf.get_mut(dest_x, dest_y);
*dest = src;
}
}
}
@@ -97,8 +98,8 @@ fn text(frame_count: usize, area: Rect, buf: &mut Buffer) {
for row in area.rows() {
for col in row.columns() {
let cell = &mut buf[(col.x, col.y)];
let mask_cell = &mut mask_buf[(col.x, col.y)];
let cell = buf.get_mut(col.x, col.y);
let mask_cell = mask_buf.get(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 = &mut buf[(x, y)];
let cell = buf.get_mut(x, y);
let rat_color = THEME.logo.rat;
let term_color = THEME.logo.term;
match (ch1, ch2) {

View File

@@ -334,7 +334,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[(area.x + x, area.y + y)] = cell;
*buf.get_mut(area.x + x, area.y + y) = cell;
}
if scrollbar_needed {

View File

@@ -1,44 +0,0 @@
//! # [Ratatui] Minimal example
//!
//! The latest version of this example is available in the [examples] folder in the repository.
//!
//! Please note that the examples are designed to be run against the `main` branch of the Github
//! repository. This means that you may not be able to compile with the latest release version on
//! crates.io, or the one that you have installed locally.
//!
//! See the [examples readme] for more information on finding examples that match the version of the
//! library you are using.
//!
//! [Ratatui]: https://github.com/ratatui-org/ratatui
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
//! [examples readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md
use crossterm::{
event::{self, Event, KeyCode, KeyEventKind},
execute,
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
use ratatui::{backend::CrosstermBackend, text::Text, Terminal};
/// This is a bare minimum example. There are many approaches to running an application loop, so
/// this is not meant to be prescriptive. See the [examples] folder for more complete examples.
/// In particular, the [hello-world] example is a good starting point.
///
/// [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
/// [hello-world]: https://github.com/ratatui-org/ratatui/blob/main/examples/hello_world.rs
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut terminal = Terminal::new(CrosstermBackend::new(std::io::stdout()))?;
enable_raw_mode()?;
execute!(terminal.backend_mut(), EnterAlternateScreen)?;
loop {
terminal.draw(|frame| frame.render_widget(Text::raw("Hello World!"), frame.size()))?;
if let Event::Key(key) = event::read()? {
if key.kind == KeyEventKind::Press && key.code == KeyCode::Char('q') {
break;
}
}
}
disable_raw_mode()?;
execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
Ok(())
}

View File

@@ -139,7 +139,8 @@ impl Backend for TestBackend {
I: Iterator<Item = (u16, u16, &'a Cell)>,
{
for (x, y, c) in content {
self.buffer[(x, y)] = c.clone();
let cell = self.buffer.get_mut(x, y);
*cell = 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[(x, y)];
let expected_cell = expected.get(x, y);
format!("{i}: at ({x}, {y})\n expected: {expected_cell:?}\n actual: {cell:?}")
})
.collect::<Vec<String>>()

View File

@@ -1,9 +1,9 @@
use std::{fmt, ops};
use std::fmt;
use unicode_segmentation::UnicodeSegmentation;
use unicode_width::UnicodeWidthStr;
use crate::{buffer::Cell, layout::Position, prelude::*};
use crate::{buffer::Cell, prelude::*};
/// A buffer that maps to the desired content of the terminal after the draw call
///
@@ -23,8 +23,8 @@ use crate::{buffer::Cell, layout::Position, prelude::*};
/// width: 10,
/// height: 5,
/// });
/// buf[(0, 2)].set_symbol("x");
/// assert_eq!(buf[(0, 2)].symbol(), "x");
/// buf.get_mut(0, 2).set_symbol("x");
/// assert_eq!(buf.get(0, 2).symbol(), "x");
///
/// buf.set_string(
/// 3,
@@ -32,13 +32,13 @@ use crate::{buffer::Cell, layout::Position, prelude::*};
/// "string",
/// Style::default().fg(Color::Red).bg(Color::White),
/// );
/// let cell = &buf[(5, 0)];
/// let cell = buf.get(5, 0);
/// assert_eq!(cell.symbol(), "r");
/// assert_eq!(cell.fg, Color::Red);
/// assert_eq!(cell.bg, Color::White);
///
/// buf[(5, 0)].set_char('x');
/// assert_eq!(buf[(5, 0)].symbol(), "x");
/// 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))]
@@ -93,90 +93,19 @@ impl Buffer {
}
/// Returns a reference to Cell at the given coordinates
///
/// Callers should generally use the [`ops::Index`] trait ([`Buffer[Position]`]) or the
/// [`Buffer::cell`] method instead of this method.
///
/// Note that conventionally methods named `get` usually return `Option<&T>`, but this method
/// panics instead. This is kept for backwards compatibility. See `get_opt` for a safe
/// alternative.
///
/// # Panics
///
/// Panics if the index is out of bounds.
#[track_caller]
#[deprecated(note = "Use Buffer[] or Buffer::cell instead")]
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
///
/// Callers should generally use the [`ops::IndexMut`] trait (`&mut Buffer[Position]`) or the
/// [`Buffer::cell_mut`] method instead of this method.
///
/// Note that conventionally methods named `get_mut` usually return `Option<&mut T>`, but this
/// method panics instead. This is kept for backwards compatibility. See `cell_mut` for a safe
/// alternative.
///
/// # Panics
///
/// Panics if the index is out of bounds.
#[track_caller]
#[deprecated(note = "Use Buffer[] or Buffer::cell_mut instead")]
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 Cell at the given coordinates.
///
/// Returns `None` if the index is out of bounds.
///
/// Note that unlike `get`, this method accepts a `Position` instead of `x` and `y` coordinates.
///
/// # 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);
/// ```
pub fn cell<P: Into<Position>>(&self, pos: P) -> Option<&Cell> {
let pos = pos.into();
let index = self.index_of_opt(pos.x, pos.y)?;
self.content.get(index)
}
/// Returns a mutable reference to Cell at the given coordinates
///
/// Returns `None` if the index is out of bounds.
///
/// Note that unlike `get`, this method accepts a `Position` instead of `x` and `y` coordinates.
///
/// # 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 = Cell::default();
/// }
/// if let Some(cell) = buffer.cell_mut((0, 0)) {
/// *cell = Cell::default();
/// }
/// ```
pub fn cell_mut<P: Into<Position>>(&mut self, pos: P) -> Option<&mut Cell> {
let pos = pos.into();
let index = self.index_of_opt(pos.x, pos.y)?;
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`).
@@ -185,7 +114,8 @@ impl Buffer {
///
/// ```
/// # use ratatui::prelude::*;
/// let buffer = Buffer::empty(Rect::new(200, 100, 10, 10));
/// 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);
/// ```
@@ -196,35 +126,23 @@ impl Buffer {
///
/// ```should_panic
/// # use ratatui::prelude::*;
/// let buffer = Buffer::empty(Rect::new(200, 100, 10, 10));
/// 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 {
self.index_of_opt(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>
const fn index_of_opt(&self, x: u16, y: u16) -> Option<usize> {
let area = self.area;
if x < area.left() || x >= area.right() || y < area.top() || y >= area.bottom() {
return None;
}
// remove offset
let y = y - self.area.y;
let x = x - self.area.x;
Some((y * self.area.width + x) as 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
@@ -300,12 +218,12 @@ impl Buffer {
});
let style = style.into();
for (symbol, width) in graphemes {
self[(x, y)].set_symbol(symbol).set_style(style);
self.get_mut(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[(x, y)].reset();
self.get_mut(x, y).reset();
x += 1;
}
}
@@ -348,7 +266,7 @@ impl Buffer {
let area = self.area.intersection(area);
for y in area.top()..area.bottom() {
for x in area.left()..area.right() {
self[(x, y)].set_style(style);
self.get_mut(x, y).set_style(style);
}
}
}
@@ -454,52 +372,6 @@ impl Buffer {
}
}
impl<P: Into<Position>> ops::Index<P> for Buffer {
type Output = Cell;
/// Returns the Cell at the given position
///
/// # Panics
///
/// May panic if the given position is outside the buffer's area.
///
/// # 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, pos: P) -> &Self::Output {
let pos = pos.into();
let index = self.index_of(pos.x, pos.y);
&self.content[index]
}
}
impl<P: Into<Position>> ops::IndexMut<P> for Buffer {
/// Returns a mutable reference to the Cell at the given position
///
/// # Panics
///
/// May panic if the given position is outside the buffer's area.
///
/// # 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, pos: P) -> &mut Self::Output {
let pos = pos.into();
let index = self.index_of(pos.x, pos.y);
&mut self.content[index]
}
}
impl fmt::Debug for Buffer {
/// Writes a debug representation of the buffer to the given formatter.
///
@@ -690,87 +562,14 @@ mod tests {
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]
fn test_cell() {
let buf = Buffer::with_lines(["Hello", "World"]);
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);
#[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);
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");
// width is 10; zero-indexed means that 10 would be the 11th cell.
buf.index_of(10, 0);
}
#[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[(position.x, position.y)].set_symbol("x");
/// buf.get_mut(position.x, position.y).set_symbol("x");
/// }
/// }
/// ```

View File

@@ -179,7 +179,7 @@ impl fmt::Debug for Modifier {
/// ];
/// let mut buffer = Buffer::empty(Rect::new(0, 0, 1, 1));
/// for style in &styles {
/// buffer[(0, 0)].set_style(*style);
/// buffer.get_mut(0, 0).set_style(*style);
/// }
/// assert_eq!(
/// Style {
@@ -190,7 +190,7 @@ impl fmt::Debug for Modifier {
/// add_modifier: Modifier::BOLD | Modifier::UNDERLINED,
/// sub_modifier: Modifier::empty(),
/// },
/// buffer[(0, 0)].style(),
/// buffer.get(0, 0).style(),
/// );
/// ```
///
@@ -208,7 +208,7 @@ impl fmt::Debug for Modifier {
/// ];
/// let mut buffer = Buffer::empty(Rect::new(0, 0, 1, 1));
/// for style in &styles {
/// buffer[(0, 0)].set_style(*style);
/// buffer.get_mut(0, 0).set_style(*style);
/// }
/// assert_eq!(
/// Style {
@@ -219,7 +219,7 @@ impl fmt::Debug for Modifier {
/// add_modifier: Modifier::empty(),
/// sub_modifier: Modifier::empty(),
/// },
/// buffer[(0, 0)].style(),
/// buffer.get(0, 0).style(),
/// );
/// ```
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
@@ -600,9 +600,9 @@ mod tests {
let mut buffer = Buffer::empty(Rect::new(0, 0, 1, 1));
for m in mods {
buffer[(0, 0)].set_style(Style::reset());
buffer[(0, 0)].set_style(Style::new().add_modifier(m));
let style = buffer[(0, 0)].style();
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();
assert!(style.add_modifier.contains(m));
assert!(!style.sub_modifier.contains(m));
}

View File

@@ -83,7 +83,6 @@ impl Frame<'_> {
/// # Example
///
/// ```rust
/// # #[cfg(feature = "unstable-widget-ref")] {
/// # use ratatui::{backend::TestBackend, prelude::*, widgets::Block};
/// # let backend = TestBackend::new(5, 5);
/// # let mut terminal = Terminal::new(backend).unwrap();
@@ -91,7 +90,6 @@ impl Frame<'_> {
/// let block = Block::new();
/// let area = Rect::new(0, 0, 5, 5);
/// frame.render_widget_ref(block, area);
/// # }
/// ```
#[allow(clippy::needless_pass_by_value)]
#[stability::unstable(feature = "widget-ref")]
@@ -140,7 +138,6 @@ impl Frame<'_> {
/// # Example
///
/// ```rust
/// # #[cfg(feature = "unstable-widget-ref")] {
/// # use ratatui::{backend::TestBackend, prelude::*, widgets::*};
/// # let backend = TestBackend::new(5, 5);
/// # let mut terminal = Terminal::new(backend).unwrap();
@@ -149,7 +146,6 @@ impl Frame<'_> {
/// let list = List::new(vec![ListItem::new("Item 1"), ListItem::new("Item 2")]);
/// let area = Rect::new(0, 0, 5, 5);
/// frame.render_stateful_widget_ref(list, area, &mut state);
/// # }
/// ```
#[allow(clippy::needless_pass_by_value)]
#[stability::unstable(feature = "widget-ref")]

View File

@@ -377,16 +377,18 @@ impl WidgetRef for Span<'_> {
if next_x > max_x {
break;
}
buf[(current_x, y)].set_symbol(g.symbol).set_style(g.style);
buf.get_mut(current_x, y)
.set_symbol(g.symbol)
.set_style(g.style);
// multi-width graphemes must clear the cells of characters that are hidden by the
// grapheme, otherwise the hidden characters will be re-rendered if the grapheme is
// overwritten.
for i in (current_x + 1)..next_x {
buf[(i, y)].reset();
buf.get_mut(i, y).reset();
// 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[(i, y)].set_style(g.style);
// buf.get_mut(i, y).set_style(g.style);
}
current_x = next_x;
}

View File

@@ -248,7 +248,6 @@ pub trait StatefulWidget {
/// # Examples
///
/// ```rust
/// # #[cfg(feature = "unstable-widget-ref")] {
/// use ratatui::{prelude::*, widgets::*};
///
/// struct Greeting;
@@ -295,7 +294,6 @@ pub trait StatefulWidget {
/// widget.render_ref(area, buf);
/// }
/// # }
/// # }
/// ```
#[stability::unstable(feature = "widget-ref")]
pub trait WidgetRef {
@@ -323,7 +321,6 @@ impl<W: WidgetRef> Widget for &W {
/// # Examples
///
/// ```rust
/// # #[cfg(feature = "unstable-widget-ref")] {
/// use ratatui::{prelude::*, widgets::*};
///
/// struct Parent {
@@ -343,7 +340,6 @@ impl<W: WidgetRef> Widget for &W {
/// self.child.render_ref(area, buf);
/// }
/// }
/// # }
/// ```
impl<W: WidgetRef> WidgetRef for Option<W> {
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
@@ -372,7 +368,6 @@ impl<W: WidgetRef> WidgetRef for Option<W> {
/// # Examples
///
/// ```rust
/// # #[cfg(feature = "unstable-widget-ref")] {
/// use ratatui::{prelude::*, widgets::*};
///
/// struct PersonalGreeting;
@@ -391,11 +386,10 @@ impl<W: WidgetRef> WidgetRef for Option<W> {
/// }
/// }
///
/// fn render(area: Rect, buf: &mut Buffer) {
/// let widget = PersonalGreeting;
/// let mut state = "world".to_string();
/// widget.render(area, buf, &mut state);
/// }
/// # fn render(area: Rect, buf: &mut Buffer) {
/// let widget = PersonalGreeting;
/// let mut state = "world".to_string();
/// widget.render(area, buf, &mut state);
/// # }
/// ```
#[stability::unstable(feature = "widget-ref")]
@@ -476,79 +470,90 @@ mod tests {
use super::*;
use crate::prelude::*;
struct Greeting;
struct Farewell;
struct PersonalGreeting;
impl Widget for Greeting {
fn render(self, area: Rect, buf: &mut Buffer) {
self.render_ref(area, buf);
}
}
impl WidgetRef for Greeting {
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
Line::from("Hello").render(area, buf);
}
}
impl Widget for Farewell {
fn render(self, area: Rect, buf: &mut Buffer) {
self.render_ref(area, buf);
}
}
impl WidgetRef for Farewell {
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
Line::from("Goodbye").right_aligned().render(area, buf);
}
}
impl StatefulWidget for PersonalGreeting {
type State = String;
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
self.render_ref(area, buf, state);
}
}
impl StatefulWidgetRef for PersonalGreeting {
type State = String;
fn render_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
Line::from(format!("Hello {state}")).render(area, buf);
}
}
#[fixture]
fn buf() -> Buffer {
Buffer::empty(Rect::new(0, 0, 20, 1))
}
mod widget {
use super::*;
struct Greeting;
impl Widget for Greeting {
fn render(self, area: Rect, buf: &mut Buffer) {
Line::from("Hello").render(area, buf);
}
}
#[rstest]
fn render(mut buf: Buffer) {
let widget = Greeting;
widget.render(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["Hello "]));
}
#[rstest]
fn widget_render(mut buf: Buffer) {
let widget = Greeting;
widget.render(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["Hello "]));
}
mod widget_ref {
use super::*;
#[rstest]
fn widget_ref_render(mut buf: Buffer) {
let widget = Greeting;
widget.render_ref(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["Hello "]));
}
struct Greeting;
struct Farewell;
/// This test is to ensure that the blanket implementation of `Widget` for `&W` where `W`
/// implements `WidgetRef` works as expected.
#[rstest]
fn widget_blanket_render(mut buf: Buffer) {
let widget = &Greeting;
widget.render(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["Hello "]));
}
impl WidgetRef for Greeting {
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
Line::from("Hello").render(area, buf);
}
}
#[rstest]
fn widget_box_render_ref(mut buf: Buffer) {
let widget: Box<dyn WidgetRef> = Box::new(Greeting);
widget.render_ref(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["Hello "]));
}
impl WidgetRef for Farewell {
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
Line::from("Goodbye").right_aligned().render(area, buf);
}
}
#[rstest]
fn render_ref(mut buf: Buffer) {
let widget = Greeting;
#[rstest]
fn widget_vec_box_render(mut buf: Buffer) {
let widgets: Vec<Box<dyn WidgetRef>> = vec![Box::new(Greeting), Box::new(Farewell)];
for widget in widgets {
widget.render_ref(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["Hello "]));
}
/// Ensure that the blanket implementation of `Widget` for `&W` where `W` implements
/// `WidgetRef` works as expected.
#[rstest]
fn blanket_render(mut buf: Buffer) {
let widget = &Greeting;
widget.render(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["Hello "]));
}
#[rstest]
fn box_render_ref(mut buf: Buffer) {
let widget: Box<dyn WidgetRef> = Box::new(Greeting);
widget.render_ref(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["Hello "]));
}
#[rstest]
fn vec_box_render(mut buf: Buffer) {
let widgets: Vec<Box<dyn WidgetRef>> = vec![Box::new(Greeting), Box::new(Farewell)];
for widget in widgets {
widget.render_ref(buf.area, &mut buf);
}
assert_eq!(buf, Buffer::with_lines(["Hello Goodbye"]));
}
assert_eq!(buf, Buffer::with_lines(["Hello Goodbye"]));
}
#[fixture]
@@ -556,143 +561,98 @@ mod tests {
"world".to_string()
}
mod stateful_widget {
use super::*;
struct PersonalGreeting;
impl StatefulWidget for PersonalGreeting {
type State = String;
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
Line::from(format!("Hello {state}")).render(area, buf);
}
}
#[rstest]
fn render(mut buf: Buffer, mut state: String) {
let widget = PersonalGreeting;
widget.render(buf.area, &mut buf, &mut state);
assert_eq!(buf, Buffer::with_lines(["Hello world "]));
}
#[rstest]
fn stateful_widget_render(mut buf: Buffer, mut state: String) {
let widget = PersonalGreeting;
widget.render(buf.area, &mut buf, &mut state);
assert_eq!(buf, Buffer::with_lines(["Hello world "]));
}
mod stateful_widget_ref {
use super::*;
struct PersonalGreeting;
impl StatefulWidgetRef for PersonalGreeting {
type State = String;
fn render_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
Line::from(format!("Hello {state}")).render(area, buf);
}
}
#[rstest]
fn render_ref(mut buf: Buffer, mut state: String) {
let widget = PersonalGreeting;
widget.render_ref(buf.area, &mut buf, &mut state);
assert_eq!(buf, Buffer::with_lines(["Hello world "]));
}
// Note this cannot be tested until the blanket implementation of StatefulWidget for &W
// where W implements StatefulWidgetRef is added. (see the comment in the blanket
// implementation for more).
// /// This test is to ensure that the blanket implementation of `StatefulWidget` for `&W`
// where /// `W` implements `StatefulWidgetRef` works as expected.
// #[rstest]
// fn stateful_widget_blanket_render(mut buf: Buffer, mut state: String) {
// let widget = &PersonalGreeting;
// widget.render(buf.area, &mut buf, &mut state);
// assert_eq!(buf, Buffer::with_lines(["Hello world "]));
// }
#[rstest]
fn box_render_render(mut buf: Buffer, mut state: String) {
let widget = Box::new(PersonalGreeting);
widget.render_ref(buf.area, &mut buf, &mut state);
assert_eq!(buf, Buffer::with_lines(["Hello world "]));
}
#[rstest]
fn stateful_widget_ref_render(mut buf: Buffer, mut state: String) {
let widget = PersonalGreeting;
widget.render_ref(buf.area, &mut buf, &mut state);
assert_eq!(buf, Buffer::with_lines(["Hello world "]));
}
mod option_widget_ref {
use super::*;
// Note this cannot be tested until the blanket implementation of StatefulWidget for &W where W
// implements StatefulWidgetRef is added. (see the comment in the blanket implementation for
// more).
// /// This test is to ensure that the blanket implementation of `StatefulWidget` for `&W` where
// /// `W` implements `StatefulWidgetRef` works as expected.
// #[rstest]
// fn stateful_widget_blanket_render(mut buf: Buffer, mut state: String) {
// let widget = &PersonalGreeting;
// widget.render(buf.area, &mut buf, &mut state);
// assert_eq!(buf, Buffer::with_lines(["Hello world "]));
// }
struct Greeting;
impl WidgetRef for Greeting {
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
Line::from("Hello").render(area, buf);
}
}
#[rstest]
fn render_ref_some(mut buf: Buffer) {
let widget = Some(Greeting);
widget.render_ref(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["Hello "]));
}
#[rstest]
fn render_ref_none(mut buf: Buffer) {
let widget: Option<Greeting> = None;
widget.render_ref(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines([" "]));
}
#[rstest]
fn stateful_widget_box_render(mut buf: Buffer, mut state: String) {
let widget = Box::new(PersonalGreeting);
widget.render(buf.area, &mut buf, &mut state);
assert_eq!(buf, Buffer::with_lines(["Hello world "]));
}
mod str {
use super::*;
#[rstest]
fn render(mut buf: Buffer) {
"hello world".render(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["hello world "]));
}
#[rstest]
fn render_ref(mut buf: Buffer) {
"hello world".render_ref(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["hello world "]));
}
#[rstest]
fn option_render(mut buf: Buffer) {
Some("hello world").render(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["hello world "]));
}
#[rstest]
fn option_render_ref(mut buf: Buffer) {
Some("hello world").render_ref(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["hello world "]));
}
#[rstest]
fn widget_option_render_ref_some(mut buf: Buffer) {
let widget = Some(Greeting);
widget.render_ref(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["Hello "]));
}
mod string {
use super::*;
#[rstest]
fn render(mut buf: Buffer) {
String::from("hello world").render(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["hello world "]));
}
#[rstest]
fn widget_option_render_ref_none(mut buf: Buffer) {
let widget: Option<Greeting> = None;
widget.render_ref(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines([" "]));
}
#[rstest]
fn render_ref(mut buf: Buffer) {
String::from("hello world").render_ref(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["hello world "]));
}
#[rstest]
fn str_render(mut buf: Buffer) {
"hello world".render(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["hello world "]));
}
#[rstest]
fn option_render(mut buf: Buffer) {
Some(String::from("hello world")).render(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["hello world "]));
}
#[rstest]
fn str_render_ref(mut buf: Buffer) {
"hello world".render_ref(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["hello world "]));
}
#[rstest]
fn option_render_ref(mut buf: Buffer) {
Some(String::from("hello world")).render_ref(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["hello world "]));
}
#[rstest]
fn str_option_render(mut buf: Buffer) {
Some("hello world").render(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["hello world "]));
}
#[rstest]
fn str_option_render_ref(mut buf: Buffer) {
Some("hello world").render_ref(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["hello world "]));
}
#[rstest]
fn string_render(mut buf: Buffer) {
String::from("hello world").render(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["hello world "]));
}
#[rstest]
fn string_render_ref(mut buf: Buffer) {
String::from("hello world").render_ref(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["hello world "]));
}
#[rstest]
fn string_option_render(mut buf: Buffer) {
Some(String::from("hello world")).render(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["hello world "]));
}
#[rstest]
fn string_option_render_ref(mut buf: Buffer) {
Some(String::from("hello world")).render_ref(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["hello world "]));
}
}

View File

@@ -431,7 +431,7 @@ impl BarChart<'_> {
} else {
self.bar_set.empty
};
buf[(bars_area.left() + x, bar_y)]
buf.get_mut(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[(bar_x + x, area.top() + j)]
buf.get_mut(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[(x, y)].set_fg(Color::Red);
expected.get_mut(x, y).set_fg(Color::Red);
}
assert_eq!(buffer, expected);
}
@@ -790,8 +790,8 @@ mod tests {
"█1█ █2█ ",
"foo bar ",
]);
expected[(1, 1)].set_fg(Color::Red);
expected[(5, 1)].set_fg(Color::Red);
expected.get_mut(1, 1).set_fg(Color::Red);
expected.get_mut(5, 1).set_fg(Color::Red);
assert_eq!(buffer, expected);
}
@@ -808,8 +808,8 @@ mod tests {
"1 2 ",
"f b ",
]);
expected[(0, 2)].set_fg(Color::Red);
expected[(2, 2)].set_fg(Color::Red);
expected.get_mut(0, 2).set_fg(Color::Red);
expected.get_mut(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[(x, y)].set_fg(Color::Red);
expected.get_mut(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[(0, 1)].modifier.insert(Modifier::ITALIC);
expected.get_mut(0, 1).modifier.insert(Modifier::ITALIC);
for x in 0..5 {
expected[(x, 1)].set_fg(Color::Yellow);
expected.get_mut(x, 1).set_fg(Color::Yellow);
}
let expected_color = if let Some(color) = bar_color {
@@ -972,13 +972,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[(0, 0)].set_fg(Color::Red);
let cell = expected.get_mut(0, 0).set_fg(Color::Red);
cell.modifier.insert(Modifier::ITALIC);
let cell = expected[(1, 0)].set_fg(Color::Red);
let cell = expected.get_mut(1, 0).set_fg(Color::Red);
cell.modifier.insert(Modifier::ITALIC);
expected[(2, 0)].set_fg(expected_color);
expected[(3, 0)].set_fg(expected_color);
expected[(4, 0)].set_fg(expected_color);
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);
assert_eq!(buffer, expected);
}
@@ -1031,9 +1031,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[(0, 1)].set_fg(Color::Red);
let cell = expected.get_mut(0, 1).set_fg(Color::Red);
cell.modifier.insert(Modifier::BOLD);
let cell = expected[(1, 1)].set_fg(Color::Red);
let cell = expected.get_mut(1, 1).set_fg(Color::Red);
cell.modifier.insert(Modifier::BOLD);
assert_eq!(buffer, expected);

View File

@@ -628,7 +628,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[(area.left(), y)]
buf.get_mut(area.left(), y)
.set_symbol(self.border_set.vertical_left)
.set_style(self.border_style);
}
@@ -638,7 +638,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[(x, area.top())]
buf.get_mut(x, area.top())
.set_symbol(self.border_set.horizontal_top)
.set_style(self.border_style);
}
@@ -649,7 +649,7 @@ impl Block<'_> {
if self.borders.contains(Borders::RIGHT) {
let x = area.right() - 1;
for y in area.top()..area.bottom() {
buf[(x, y)]
buf.get_mut(x, y)
.set_symbol(self.border_set.vertical_right)
.set_style(self.border_style);
}
@@ -660,7 +660,7 @@ impl Block<'_> {
if self.borders.contains(Borders::BOTTOM) {
let y = area.bottom() - 1;
for x in area.left()..area.right() {
buf[(x, y)]
buf.get_mut(x, y)
.set_symbol(self.border_set.horizontal_bottom)
.set_style(self.border_style);
}
@@ -669,7 +669,7 @@ impl Block<'_> {
fn render_bottom_right_corner(&self, buf: &mut Buffer, area: Rect) {
if self.borders.contains(Borders::RIGHT | Borders::BOTTOM) {
buf[(area.right() - 1, area.bottom() - 1)]
buf.get_mut(area.right() - 1, area.bottom() - 1)
.set_symbol(self.border_set.bottom_right)
.set_style(self.border_style);
}
@@ -677,7 +677,7 @@ impl Block<'_> {
fn render_top_right_corner(&self, buf: &mut Buffer, area: Rect) {
if self.borders.contains(Borders::RIGHT | Borders::TOP) {
buf[(area.right() - 1, area.top())]
buf.get_mut(area.right() - 1, area.top())
.set_symbol(self.border_set.top_right)
.set_style(self.border_style);
}
@@ -685,7 +685,7 @@ impl Block<'_> {
fn render_bottom_left_corner(&self, buf: &mut Buffer, area: Rect) {
if self.borders.contains(Borders::LEFT | Borders::BOTTOM) {
buf[(area.left(), area.bottom() - 1)]
buf.get_mut(area.left(), area.bottom() - 1)
.set_symbol(self.border_set.bottom_left)
.set_style(self.border_style);
}
@@ -693,7 +693,7 @@ impl Block<'_> {
fn render_top_left_corner(&self, buf: &mut Buffer, area: Rect) {
if self.borders.contains(Borders::LEFT | Borders::TOP) {
buf[(area.left(), area.top())]
buf.get_mut(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[(x, y)].set_char(ch);
let cell = buf.get_mut(x, y).set_char(ch);
if colors.0 != Color::Reset {
cell.set_fg(colors.0);
}

View File

@@ -957,14 +957,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[(area.left(), area.top())].style();
let original_style = buf.get(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[(x, y)]
buf.get_mut(x, y)
.set_symbol(symbols::line::HORIZONTAL)
.set_style(self.x_axis.style);
}
@@ -972,7 +972,7 @@ impl WidgetRef for Chart<'_> {
if let Some(x) = layout.axis_y {
for y in graph_area.top()..graph_area.bottom() {
buf[(x, y)]
buf.get_mut(x, y)
.set_symbol(symbols::line::VERTICAL)
.set_style(self.y_axis.style);
}
@@ -980,7 +980,7 @@ impl WidgetRef for Chart<'_> {
if let Some(y) = layout.axis_x {
if let Some(x) = layout.axis_y {
buf[(x, y)]
buf.get_mut(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[(x, y)].reset();
buf.get_mut(x, y).reset();
}
}
}

View File

@@ -195,23 +195,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 {
buf[(x, y)]
.set_symbol(symbols::block::FULL)
cell.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 {
buf[(x, y)]
.set_symbol(" ")
cell.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[(end, y)].set_symbol(get_unicode_block(filled_width % 1.0));
buf.get_mut(end, y)
.set_symbol(get_unicode_block(filled_width % 1.0));
}
}
// render the label
@@ -377,7 +377,7 @@ 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[(col, row)]
buf.get_mut(col, row)
.set_symbol(self.line_set.horizontal)
.set_style(Style {
fg: self.gauge_style.fg,
@@ -389,7 +389,7 @@ impl WidgetRef for LineGauge<'_> {
});
}
for col in end..gauge_area.right() {
buf[(col, row)]
buf.get_mut(col, row)
.set_symbol(self.line_set.horizontal)
.set_style(Style {
fg: self.gauge_style.bg,

View File

@@ -376,7 +376,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[(area.left() + x, area.top() + y - self.scroll.0)]
buf.get_mut(area.left() + x, area.top() + y - self.scroll.0)
.set_symbol(symbol)
.set_style(*style);
x += width as u16;

View File

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

View File

@@ -213,6 +213,15 @@ pub struct Table<'a> {
/// Symbol in front of the selected row
highlight_symbol: Text<'a>,
/// Symbol in front of the marked row
mark_symbol: Text<'a>,
/// Symbol in front of the unmarked row
unmark_symbol: Text<'a>,
/// Symbol in front of the marked and selected row
mark_highlight_symbol: Text<'a>,
/// Decides when to allocate spacing for the row selection
highlight_spacing: HighlightSpacing,
@@ -232,6 +241,9 @@ impl<'a> Default for Table<'a> {
style: Style::new(),
highlight_style: Style::new(),
highlight_symbol: Text::default(),
mark_symbol: Text::default(),
unmark_symbol: Text::default(),
mark_highlight_symbol: Text::default(),
highlight_spacing: HighlightSpacing::default(),
flex: Flex::Start,
}
@@ -503,6 +515,60 @@ impl<'a> Table<'a> {
self
}
/// Set the symbol to be displayed in front of the marked row
///
/// This is a fluent setter method which must be chained or used as it consumes self
///
/// # Examples
///
/// ```rust
/// # use ratatui::{prelude::*, widgets::*};
/// # let rows = [Row::new(vec!["Cell1", "Cell2"])];
/// # let widths = [Constraint::Length(5), Constraint::Length(5)];
/// let table = Table::new(rows, widths).mark_symbol("\u{2714}");
/// ```
#[must_use = "method moves the value of self and returns the modified value"]
pub fn mark_symbol<T: Into<Text<'a>>>(mut self, mark_symbol: T) -> Self {
self.mark_symbol = mark_symbol.into();
self
}
/// Set the symbol to be displayed in front of the unmarked row
///
/// This is a fluent setter method which must be chained or used as it consumes self
///
/// # Examples
///
/// ```rust
/// # use ratatui::{prelude::*, widgets::*};
/// # let rows = [Row::new(vec!["Cell1", "Cell2"])];
/// # let widths = [Constraint::Length(5), Constraint::Length(5)];
/// let table = Table::new(rows, widths).unmark_symbol(" ");
/// ```
#[must_use = "method moves the value of self and returns the modified value"]
pub fn unmark_symbol<T: Into<Text<'a>>>(mut self, unmark_symbol: T) -> Self {
self.unmark_symbol = unmark_symbol.into();
self
}
/// Set the symbol to be displayed in front of the marked and selected row
///
/// This is a fluent setter method which must be chained or used as it consumes self
///
/// # Examples
///
/// ```rust
/// # use ratatui::{prelude::*, widgets::*};
/// # let rows = [Row::new(vec!["Cell1", "Cell2"])];
/// # let widths = [Constraint::Length(5), Constraint::Length(5)];
/// let table = Table::new(rows, widths).mark_highlight_symbol("\u{29bf}");
/// ```
#[must_use = "method moves the value of self and returns the modified value"]
pub fn mark_highlight_symbol<T: Into<Text<'a>>>(mut self, mark_highlight_symbol: T) -> Self {
self.mark_highlight_symbol = mark_highlight_symbol.into();
self
}
/// Set when to show the highlight spacing
///
/// The highlight spacing is the spacing that is allocated for the selection symbol column (if
@@ -604,8 +670,8 @@ impl StatefulWidgetRef for Table<'_> {
return;
}
let selection_width = self.selection_width(state);
let columns_widths = self.get_columns_widths(table_area.width, selection_width);
let highlight_column_width = self.highlight_column_width(state);
let columns_widths = self.get_columns_widths(table_area.width, highlight_column_width);
let (header_area, rows_area, footer_area) = self.layout(table_area);
self.render_header(header_area, buf, &columns_widths);
@@ -614,8 +680,13 @@ impl StatefulWidgetRef for Table<'_> {
rows_area,
buf,
state,
selection_width,
&self.highlight_symbol,
highlight_column_width,
(
&self.highlight_symbol,
&self.mark_symbol,
&self.unmark_symbol,
&self.mark_highlight_symbol,
),
&columns_widths,
);
@@ -670,14 +741,16 @@ impl Table<'_> {
area: Rect,
buf: &mut Buffer,
state: &mut TableState,
selection_width: u16,
highlight_symbol: &Text<'_>,
highlight_column_width: u16,
symbols: (&Text<'_>, &Text<'_>, &Text<'_>, &Text<'_>),
columns_widths: &[(u16, u16)],
) {
if self.rows.is_empty() {
return;
}
let (highlight_symbol, mark_symbol, unmark_symbol, mark_highlight_symbol) = symbols;
let (start_index, end_index) =
self.get_row_bounds(state.selected, state.offset, area.height);
state.offset = start_index;
@@ -698,22 +771,37 @@ impl Table<'_> {
);
buf.set_style(row_area, row.style);
let is_selected = state.selected().is_some_and(|index| index == i);
if selection_width > 0 && is_selected {
let selection_area = Rect {
width: selection_width,
let is_marked = state.marked().contains(&(i + state.offset));
let is_highlighted = state.selected().is_some_and(|index| index == i);
if highlight_column_width > 0 {
let area = Rect {
width: highlight_column_width,
..row_area
};
buf.set_style(selection_area, row.style);
highlight_symbol.clone().render(selection_area, buf);
};
buf.set_style(area, row.style);
match (is_marked, is_highlighted) {
(true, true) => {
mark_highlight_symbol.render(area, buf);
}
(true, false) => {
mark_symbol.render(area, buf);
}
(false, true) => {
highlight_symbol.render(area, buf);
}
(false, false) => {
unmark_symbol.render(area, buf);
}
};
}
for ((x, width), cell) in columns_widths.iter().zip(row.cells.iter()) {
cell.render(
Rect::new(row_area.x + x, row_area.y, *width, row_area.height),
buf,
);
}
if is_selected {
if is_highlighted {
buf.set_style(row_area, self.highlight_style);
}
y_offset += row.height_with_margin();
@@ -788,15 +876,35 @@ impl Table<'_> {
(start, end)
}
/// Returns the width of the selection column if a row is selected, or the `highlight_spacing`
/// is set to show the column always, otherwise 0.
fn selection_width(&self, state: &TableState) -> u16 {
let has_selection = state.selected().is_some();
if self.highlight_spacing.should_add(has_selection) {
/// Returns the width of the indicator column if a row is selected, rows are marked,
/// or the `highlight_spacing` is set to show the column always, otherwise 0.
fn highlight_column_width(&self, state: &TableState) -> u16 {
let has_highlight = state.selected().is_some() || state.marked().len() > 0;
let highlight_column_width = if self.highlight_spacing.should_add(has_highlight) {
self.highlight_symbol.width() as u16
} else {
0
}
};
let mark_column_width = if self.highlight_spacing.should_add(has_highlight) {
self.mark_symbol.width() as u16
} else {
0
};
let mark_highlight_column_width = if self.highlight_spacing.should_add(has_highlight) {
self.mark_highlight_symbol.width() as u16
} else {
0
};
let unmark_column_width = if self.highlight_spacing.should_add(has_highlight) {
self.unmark_symbol.width() as u16
} else {
0
};
highlight_column_width
.max(mark_column_width)
.max(mark_highlight_column_width)
.max(unmark_column_width)
}
}
@@ -1190,6 +1298,100 @@ mod tests {
]);
assert_eq!(buf, expected);
}
#[test]
fn render_with_selected_marked_unmarked() {
let rows = vec![
Row::new(vec!["Cell", "Cell"]),
Row::new(vec!["Cell", "Cell"]),
Row::new(vec!["Cell", "Cell"]),
Row::new(vec!["Cell", "Cell"]),
Row::new(vec!["Cell", "Cell"]),
Row::new(vec!["Cell", "Cell"]),
Row::new(vec!["Cell", "Cell"]),
];
let table = Table::new(rows, [Constraint::Length(5); 2])
.highlight_symbol("")
.mark_symbol("")
.unmark_symbol(" ")
.mark_highlight_symbol("⦿");
let mut state = TableState::new().with_selected(0);
state.mark(1);
state.mark(3);
state.mark(5);
let mut buf = Buffer::empty(Rect::new(0, 0, 15, 10));
StatefulWidget::render(table.clone(), Rect::new(0, 0, 15, 10), &mut buf, &mut state);
let expected = Buffer::with_lines(Text::from(vec![
"• Cell Cell ".into(),
"⦾ Cell Cell ".into(),
" Cell Cell ".into(),
"⦾ Cell Cell ".into(),
" Cell Cell ".into(),
"⦾ Cell Cell ".into(),
" Cell Cell ".into(),
" ".into(),
" ".into(),
" ".into(),
]));
assert_eq!(buf, expected);
state.mark(0);
let mut buf = Buffer::empty(Rect::new(0, 0, 15, 10));
StatefulWidget::render(table.clone(), Rect::new(0, 0, 15, 10), &mut buf, &mut state);
let expected = Buffer::with_lines(Text::from(vec![
"⦿ Cell Cell ".into(),
"⦾ Cell Cell ".into(),
" Cell Cell ".into(),
"⦾ Cell Cell ".into(),
" Cell Cell ".into(),
"⦾ Cell Cell ".into(),
" Cell Cell ".into(),
" ".into(),
" ".into(),
" ".into(),
]));
assert_eq!(buf, expected);
state.select(Some(1));
let mut buf = Buffer::empty(Rect::new(0, 0, 15, 10));
StatefulWidget::render(table.clone(), Rect::new(0, 0, 15, 10), &mut buf, &mut state);
let expected = Buffer::with_lines(Text::from(vec![
"⦾ Cell Cell ".into(),
"⦿ Cell Cell ".into(),
" Cell Cell ".into(),
"⦾ Cell Cell ".into(),
" Cell Cell ".into(),
"⦾ Cell Cell ".into(),
" Cell Cell ".into(),
" ".into(),
" ".into(),
" ".into(),
]));
assert_eq!(buf, expected);
state.unmark(0);
let mut buf = Buffer::empty(Rect::new(0, 0, 15, 10));
StatefulWidget::render(table.clone(), Rect::new(0, 0, 15, 10), &mut buf, &mut state);
let expected = Buffer::with_lines(Text::from(vec![
" Cell Cell ".into(),
"⦿ Cell Cell ".into(),
" Cell Cell ".into(),
"⦾ Cell Cell ".into(),
" Cell Cell ".into(),
"⦾ Cell Cell ".into(),
" Cell Cell ".into(),
" ".into(),
" ".into(),
" ".into(),
]));
assert_eq!(buf, expected);
}
}
// test how constraints interact with table column width allocation
@@ -1388,6 +1590,214 @@ mod tests {
assert_eq!(table.get_columns_widths(10, 0), [(0, 5), (5, 5)]);
}
#[track_caller]
fn test_table_with_selection_and_marks<'line, Lines, Marks>(
highlight_spacing: HighlightSpacing,
columns: u16,
spacing: u16,
selection: Option<usize>,
marks: Marks,
expected: Lines,
) where
Lines: IntoIterator,
Lines::Item: Into<Line<'line>>,
Marks: IntoIterator<Item = usize>,
{
let table = Table::default()
.rows(vec![Row::new(vec!["ABCDE", "12345"])])
.highlight_spacing(highlight_spacing)
.highlight_symbol(">>>")
.mark_symbol(" MMM ")
.mark_highlight_symbol(" >M> ")
.column_spacing(spacing);
let area = Rect::new(0, 0, columns, 3);
let mut buf = Buffer::empty(area);
let mut state = TableState::default().with_selected(selection);
for mark in marks {
state.mark(mark);
}
StatefulWidget::render(table, area, &mut buf, &mut state);
assert_eq!(buf, Buffer::with_lines(expected));
}
#[test]
#[allow(clippy::too_many_lines)]
fn highlight_symbol_mark_symbol_and_column_spacing_with_highlight_spacing() {
// no highlight_symbol or mark_symbol rendered ever
test_table_with_selection_and_marks(
HighlightSpacing::Never,
15, // width
0, // spacing
None, // selection
[], // marks
[
"ABCDE 12345 ", /* default layout is Flex::Start but columns length
* constraints are calculated as `max_area / n_columns`,
* i.e. they are distributed amongst available space */
" ", // row 2
" ", // row 3
],
);
// no highlight_symbol or mark_symbol rendered ever
test_table_with_selection_and_marks(
HighlightSpacing::Never,
15, // width
0, // spacing
None, // selection
[0], // marks
[
"ABCDE 12345 ", /* default layout is Flex::Start but columns length
* constraints are calculated as `max_area / n_columns`,
* i.e. they are distributed amongst available space */
" ", // row 2
" ", // row 3
],
);
// no highlight_symbol or mark_symbol rendered ever
test_table_with_selection_and_marks(
HighlightSpacing::Never,
15, // width
0, // spacing
None, // selection
[], // marks
[
"ABCDE 12345 ", /* default layout is Flex::Start but columns length
* constraints are calculated as `max_area / n_columns`,
* i.e. they are distributed amongst available space */
" ", // row 2
" ", // row 3
],
);
// no highlight_symbol or mark_symbol rendered
test_table_with_selection_and_marks(
HighlightSpacing::WhenSelected,
15, // width
0, // spacing
None, // selection
[], // marks
[
"ABCDE 12345 ", /* default layout is Flex::Start but columns length
* constraints are calculated as `max_area / n_columns`,
* i.e. they are distributed amongst available space */
" ", // row 2
" ", // row 3
],
);
// mark_symbol rendered
test_table_with_selection_and_marks(
HighlightSpacing::WhenSelected,
15, // width
0, // spacing
None, // selection
[0], // marks
[
" MMM ABCDE12345", /* default layout is Flex::Start but columns length
* constraints are calculated as `max_area / n_columns`,
* i.e. they are distributed amongst available space */
" ", // row 2
" ", // row 3
],
);
// highlight symbol rendered with mark symbol width
test_table_with_selection_and_marks(
HighlightSpacing::WhenSelected,
15, // width
0, // spacing
Some(0), // selection
[], // marks
[
">>> ABCDE12345", /* default layout is Flex::Start but columns length
* constraints are calculated as `max_area / n_columns`,
* i.e. they are distributed amongst available space */
" ", // row 2
" ", // row 3
],
);
// mark highlight symbol rendered
test_table_with_selection_and_marks(
HighlightSpacing::WhenSelected,
15, // width
0, // spacing
Some(0), // selection
[0], // marks
[
" >M> ABCDE12345", /* default layout is Flex::Start but columns length
* constraints are calculated as `max_area / n_columns`,
* i.e. they are distributed amongst available space */
" ", // row 2
" ", // row 3
],
);
// no highlight_symbol or mark_symbol rendered
test_table_with_selection_and_marks(
HighlightSpacing::Always,
15, // width
0, // spacing
None, // selection
[], // marks
[
" ABCDE12345", /* default layout is Flex::Start but columns length
* constraints are calculated as `max_area / n_columns`,
* i.e. they are distributed amongst available space */
" ", // row 2
" ", // row 3
],
);
// mark_symbol rendered
test_table_with_selection_and_marks(
HighlightSpacing::Always,
15, // width
0, // spacing
None, // selection
[0], // marks
[
" MMM ABCDE12345", /* default layout is Flex::Start but columns length
* constraints are calculated as `max_area / n_columns`,
* i.e. they are distributed amongst available space */
" ", // row 2
" ", // row 3
],
);
// highlight symbol rendered with mark symbol width
test_table_with_selection_and_marks(
HighlightSpacing::Always,
15, // width
0, // spacing
Some(0), // selection
[], // marks
[
">>> ABCDE12345", /* default layout is Flex::Start but columns length
* constraints are calculated as `max_area / n_columns`,
* i.e. they are distributed amongst available space */
" ", // row 2
" ", // row 3
],
);
// mark highlight symbol rendered
test_table_with_selection_and_marks(
HighlightSpacing::Always,
15, // width
0, // spacing
Some(0), // selection
[0], // marks
[
" >M> ABCDE12345", /* default layout is Flex::Start but columns length
* constraints are calculated as `max_area / n_columns`,
* i.e. they are distributed amongst available space */
" ", // row 2
" ", // row 3
],
);
}
#[track_caller]
fn test_table_with_selection<'line, Lines>(
highlight_spacing: HighlightSpacing,

View File

@@ -49,6 +49,7 @@
pub struct TableState {
pub(crate) offset: usize,
pub(crate) selected: Option<usize>,
pub(crate) marked: Vec<usize>,
}
impl TableState {
@@ -64,6 +65,7 @@ impl TableState {
Self {
offset: 0,
selected: None,
marked: vec![],
}
}
@@ -175,6 +177,78 @@ impl TableState {
self.offset = 0;
}
}
/// Sets the index of the row as marked
///
/// # Examples
///
/// ```rust
/// # use ratatui::{prelude::*, widgets::*};
/// let mut state = TableState::default();
/// state.mark(1);
/// ```
pub fn mark(&mut self, index: usize) {
if !self.marked.contains(&index) {
self.marked.push(index);
}
}
/// Sets the index of the row as unmarked
///
/// # Examples
///
/// ```rust
/// # use ratatui::{prelude::*, widgets::*};
/// let mut state = TableState::default();
/// state.unmark(1);
/// ```
pub fn unmark(&mut self, index: usize) {
self.marked.retain(|i| *i != index);
}
/// Toggles the index of the row as marked or unmarked
///
/// # Examples
///
/// ```rust
/// # use ratatui::{prelude::*, widgets::*};
/// let mut state = TableState::default();
/// state.toggle_mark(1);
/// ```
pub fn toggle_mark(&mut self, index: usize) {
if self.marked.contains(&index) {
self.unmark(index);
} else {
self.mark(index);
}
}
/// Returns a iterator of all marked rows
///
/// # Examples
///
/// ```rust
/// # use ratatui::{prelude::*, widgets::*};
/// # use itertools::Itertools;
/// let mut state = TableState::default();
/// state.marked().contains(&1);
/// ```
pub fn marked(&self) -> std::slice::Iter<'_, usize> {
self.marked.iter()
}
/// Clears all marks from all rows
///
/// # Examples
///
/// ```rust
/// # use ratatui::{prelude::*, widgets::*};
/// let mut state = TableState::default();
/// state.clear_marks();
/// ```
pub fn clear_marks(&mut self) {
self.marked.drain(..);
}
}
#[cfg(test)]

View File

@@ -14,6 +14,7 @@
// not too happy about the redundancy in these tests,
// but if that helps readability then it's ok i guess /shrug
use pretty_assertions::assert_eq;
use ratatui::{backend::TestBackend, prelude::*, widgets::*};
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
@@ -98,7 +99,8 @@ const DEFAULT_STATE_REPR: &str = r#"{
},
"table": {
"offset": 0,
"selected": null
"selected": null,
"marked": []
},
"scrollbar": {
"content_length": 10,
@@ -135,7 +137,8 @@ const SELECTED_STATE_REPR: &str = r#"{
},
"table": {
"offset": 0,
"selected": 1
"selected": 1,
"marked": []
},
"scrollbar": {
"content_length": 10,
@@ -174,7 +177,8 @@ const SCROLLED_STATE_REPR: &str = r#"{
},
"table": {
"offset": 4,
"selected": 8
"selected": 8,
"marked": []
},
"scrollbar": {
"content_length": 10,

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[(x, y)].set_bg(Color::White);
expected.get_mut(x, y).set_bg(Color::White);
}
// bars
for x in [0, 1, 3, 4, 6, 7] {
expected[(x, y)].set_fg(Color::Red);
expected.get_mut(x, y).set_fg(Color::Red);
}
}
// values
for x in 0..3 {
expected[(x * 3, 3)].set_fg(Color::Green);
expected.get_mut(x * 3, 3).set_fg(Color::Green);
}
// labels
for x in 0..3 {
expected[(x * 3, 4)].set_fg(Color::Blue);
expected[(x * 3 + 1, 4)].set_fg(Color::Reset);
expected.get_mut(x * 3, 4).set_fg(Color::Blue);
expected.get_mut(x * 3 + 1, 4).set_fg(Color::Reset);
}
terminal.backend().assert_buffer(&expected);
}
@@ -80,11 +80,14 @@ fn block_can_be_stylized() -> io::Result<()> {
]);
for x in area.x..area.width {
for y in area.y..area.height {
expected[(x, y)].set_fg(Color::Cyan).set_bg(Color::Cyan);
expected
.get_mut(x, y)
.set_fg(Color::Cyan)
.set_bg(Color::Cyan);
}
}
for x in 1..=5 {
expected[(x, 0)].set_fg(Color::LightBlue);
expected.get_mut(x, 0).set_fg(Color::LightBlue);
}
terminal.backend().assert_buffer(&expected);
Ok(())
@@ -102,7 +105,7 @@ fn paragraph_can_be_stylized() -> io::Result<()> {
let mut expected = Buffer::with_lines(["Text "]);
for x in 0..4 {
expected[(x, 0)].set_fg(Color::Cyan);
expected.get_mut(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.size());
})?;
assert_eq!(frame.buffer[(0, 0)].symbol(), "T");
assert_eq!(frame.buffer.get(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.size());
})?;
assert_eq!(frame.buffer[(0, 0)].symbol(), "t");
assert_eq!(frame.buffer.get(0, 0).symbol(), "t");
assert_eq!(frame.area, Rect::new(0, 0, 8, 8));
Ok(())
}

View File

@@ -85,11 +85,11 @@ fn widgets_barchart_group() {
]);
for y in 1..(TERMINAL_HEIGHT - 3) {
for x in 1..5 {
expected[(x, y)].set_fg(Color::Red);
expected[(x + 5, y)].set_fg(Color::Green);
expected.get_mut(x, y).set_fg(Color::Red);
expected.get_mut(x + 5, y).set_fg(Color::Green);
}
}
expected[(2, 7)].set_fg(Color::Blue);
expected[(3, 7)].set_fg(Color::Blue);
expected.get_mut(2, 7).set_fg(Color::Blue);
expected.get_mut(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[(x, 0)].set_fg(Color::LightBlue);
expected.get_mut(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[(col, row)].set_bg(Color::Yellow);
expected.get_mut(col, row).set_bg(Color::Yellow);
}
}
for col in 0..4 {
expected[(col, 4)].set_fg(Color::Blue);
expected.get_mut(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[(col, row)].set_bg(Color::White);
expected.get_mut(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[(col, row)].set_fg(Color::Blue);
expected.get_mut(col, row).set_fg(Color::Blue);
}
for (col, row) in legend1 {
expected[(col, row)].set_fg(Color::Blue);
expected.get_mut(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[(col, row)].set_fg(Color::Green);
expected.get_mut(col, row).set_fg(Color::Green);
}
for (col, row) in legend2 {
expected[(col, row)].set_fg(Color::Green);
expected.get_mut(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[(col, row)].set_fg(Color::Yellow);
expected.get_mut(col, row).set_fg(Color::Yellow);
}
terminal.backend().assert_buffer(&expected);
}

View File

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

View File

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

View File

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