Compare commits
13 Commits
jm/depreca
...
kd/multi-s
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ccf9d92f10 | ||
|
|
b712034644 | ||
|
|
38fca62fa9 | ||
|
|
3a51a027a6 | ||
|
|
5b30f2275c | ||
|
|
b4c27c744c | ||
|
|
977a4899c8 | ||
|
|
cd27b4829a | ||
|
|
feee871519 | ||
|
|
0051bb2037 | ||
|
|
477217c77a | ||
|
|
31de3586f7 | ||
|
|
f702025b75 |
16
.cargo-husky/hooks/pre-push
Executable file
16
.cargo-husky/hooks/pre-push
Executable 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
10551
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
|
||||
11
Cargo.toml
11
Cargo.toml
@@ -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"]
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 => ' ',
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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>>()
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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");
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
14
src/style.rs
14
src/style.rs
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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")]
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
354
src/widgets.rs
354
src/widgets.rs
@@ -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 "]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(())
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user